1 Linux系统编程入门

1 Linux系统编程入门

1.1Linux开发环境搭建

我使用的是阿里云2核2G的服务器1年108元

  1. 设置服务器root密码,重启服务器
  2. root用户登录,进行添加新用户
  3. 注册自己使用的用户
sudo useradd -r -m -s /bin/bash tset #tset是用户名

其中参数的意义如下:

  • -r:建立系统账号
  • -m:自动建立用户的登入目录
  • -s:指定用户登入后所使用的shell
  1. 设置密码
sudo passwd tset
  1. 输入ls /home可以看到以及创建成功
    设置用户相关教程链接:ubuntu添加用户
  2. 修改用户权限
sudo chmod +w /etc/sudoers #添加写入的权限
sudo vim /etc/sudoers #修改文件
#修改vim中的权限,保存退出:wq
sudo chmod -w /etc/sudoers #移除写入的权限
  1. 创建组并且添加用户
groupadd learngroup
usermod -g learngroup mobbu
id mobbu #查看是否添加成功
  1. 使用vscode远程连接扩展,搜索remote安装,连接到服务器。

  2. 在本机创建密钥ssh-keygen -t rsa,默认回车即可,在用户账户可以查找到.ssh文件,找到id_rsa.pub文件(公钥文件),然后在服务器端使用同样命令创建密钥,cd .ssh,再修改文件vim authorized_keys,将本机的公钥打开复制粘贴到此文件中保存退出即可。此时环境配置完成。

1.2 GCC

1.2.1 什么是GCC

  • GCC 原名为 GNU C语言编译器(GNU C Compiler)
  • GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言译器。GNU 编译器套件包括 C、C++、Objective-C、Java、Ada 和 Go 语言前端,也包括了这些语言的库(如 libstdc++,libgcj等)
  • GCC 不仅支持 C 的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数-std=c99 启动 GCC 时,编译器支持 C99 标准。
  • 安装命令 sudo apt install gcc g++ (版本 > 4.8.5)
  • 查看版本 gcc/g++ -v/--version
  • CTRL + L 清空命令行

1.2.2 编程语言发展

  • 高级语言->汇编语言->机器语言->计算机

1.2.3 GCC工作流程

  • 预处理:头文件展开、宏替换、删除注释

1.2.4 gcc 和 g++ 的区别

  • gcc 和 g++都是GNU(组织)的一个编译器。
  • 误区一:gcc 只能编译 c 代码,g++ 只能编译 c++ 代码。两者都可以,请注意:
    • 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序
    • 后缀为 .cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
    • 编译阶段,g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接,为了统一起见,干脆编译/链接统统用 g++ 了,这就给人一种错觉,好像 cpp 程序只能用 g++ 似的
  • 误区二:gcc 不会定义 __cplusplus 宏,而 g++ 会
    • 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释
    • 如上所述,如果后缀为 .c,并且采用 gcc 编译器,则该宏就是未定义的,否则, 就是已定义
  • 误区三:编译只能用 gcc,链接只能用 g++
    • 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
    • gcc 命令不能自动和C++程序使用的库联接,所以通常使用 g++ 来完成联接。但在编译阶段,g++ 会自动调用 gcc,二者等价

1.2.5 GCC常用参数选项

gcc编译选项 说明
-E 预处理指定的源文件,不进行编译
-S 编译指定的源文件,但是不进行汇编
-c 编译、汇编指定的源文件,但是不进行链接
-o [file1] [file2] 将文件 file2 编译成可执行文件 file1
-I directory 指定 include 包含文件的搜索目录
-g 在编译的时候,生成调试信息,该程序可以被调试器调试
-D 在程序编译的时候,指定一个宏
-w 不生成任何警告信息
-Wall 生成所有警告信息
-On n的取值范围:0~3。编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-l 在程序编译的时候,指定使用的库
-L 指定编译的时候,搜索的库的路径
-fpic 生成与位置无关的代码
-shared 生成共享目标文件,通常用在建立共享库时
-std 指定C方言,如:-std=c99,gcc默认的方言是GNU C

例子:

gcc test.c -o app #当前目录下生成test.c的可执行文件app
./app #运行app
gcc test.c -E -o test.i # 预处理,进行头文件展开、宏替换、删除注释
gcc test.i -S -o test.s # 编译,生产test.s汇编代码
gcc test.s -c -o test.o # 汇编,产生test.o机器01代码,可直接运行

-D

#ifdef DEBUG
    printf("success\n");
#endif

添加上述代码后,命令行输入gcc test.c -o test -DDEBUG,此时添加DEBUG宏后,将会执行上述代码,打印success

1.3 静态库的制作和使用

1.3.1 静态库的制作

  • 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

  • 库是特殊的一种程序, 编写库的程序和编写一般的程序区别不大,只是库不能单独运行。

  • 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

  • 库的好处:

    • 代码保密
    • 方便部署和分发
  • 命名规则:

    • Linux :libxxx.a
      • lib :前缀(固定)
      • XXX:库的名字,自己起
      • .a:后缀(固定)
    • Windows : libxxx. lib
  • 静态库的制作:

    • gcc获得.o文件
    • 将.o文件打包,使用ar工具(archive)
      ar rcs 1ibxxx.a xxx.o Xxx.o
      
      • r - 将文件插入备存文件中
      • c - 建立备存文件
      • s - 索引

1.3.2 静态库的使用

  • 生产静态库
  • 使用静态库gcc main.c -o app -L lib_address -l lib_name
    • main.c为执行文件
    • -o 为生成执行文件
    • lib_address 库地址
    • lib_name 库名字
cd ./calc
gcc -c add.c div.c mult.c sub.c # 生成四个函数的汇编文件
ar rcs libcalc.a add.o div.o mult.o sub.o # 生成对应四个函数的静态库calc
cd ../library
mv ../calc/libcalc.a ./lib
gcc main.c -o app -I ./include/ -l calc -L ./lib/ 
# 执行main.c 生产执行文件app,头文件地址为./include,使用库calc,库地址为./lib
./app # 执行代码

1.4 动态库的制作和使用

1.4.1 动态库的制作

  • 命名规则:
    • Linux : libxxx.so
      • lib : 前缀(固定)
      • xxx : 库的名字,自己起
      • .so : 后缀(固定)
        在Linux下是一个可执行文件
    • Windows : libxxx.dll
  • 动态库的制作:
    • gcc 得到 .o 文件,得到和位置无关的代码
      gcc -c –fpic/-fPIC a.c b.c
    • gcc 得到动态库
      gcc -shared a.o b.o -o libcalc.so

1.4.2 动态库加载失败的原因

  • 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
  • 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
  • 程序启动之后,动态库会被动态加载到内存中(当GCC使用add方法的时候),通过 ldd(list dynamic dependencies)命令检查动态库依赖关系
  • 如何定位共享库文件呢?
    当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib目录找到库文件后将其载入内存。

1.4.3 解决动态库加载失败问题

添加环境变量LD_LIBRARY_PATH文件

  1. 方法一:在终端输入以下命令,缺点:临时生产,下次打开终端环境变量就没了

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib
    

    其中冒号:之后为动态所在的绝对地址,通过pwd命令获得

  2. 方法二:用户端修改环境变量,永久存在

    • 步骤一
    cd / #返回根目录
    vim .bashrc
    
    • 步骤二
      在bashrc文件中,shift+G跳到最后一行,并且插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib,保存退出后

    • 步骤三

    . .bashrc # 等于source .bashrc
    
  3. 方法三:root环境下修改环境变量,永久存在,需要root权限

    sudo vim /etc/profile

    插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mobbu/Linux/lesson06/library/lib,

    保存退出后

    . .bashrc # 等于source .bashrc

1.5 静态库和动态库对比

1.5.1 程序编译成可执行程序的过程

静态库、动态库区别来自链接阶段如何处理,链接成可执行程序。分别称为静态链接方式和动态链接方式。

1.5.2 静态库制作过程

1.5.3 动态库制作过程

1.5.4 静态库的优缺点

  • 优点:

    • 静态库被打包到应用程序中加载速度快
    • 发布程序无需提供静态库,移植方便
  • 缺点:

    • 消耗系统资源,浪费内存
    • 更新、部署、发布麻烦

1.5.5 动态库的优缺点

  • 优点:
    • 可以实现进程间资源共享(共享库)
    • 更新、部署、发布简单
    • 可以控制何时加载动态库
  • 缺点:
    • 加载速度比静态库慢
    • 发布程序时需要提供依赖的动态库

1.6 MakeFile

1.6.1 什么是 Makefile

  • 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令。
  • Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make

tar -zxvf redis-5.0.10.tar.gz # 解压到当前文件夹

1.6.2 Makefile文件命名和规则

  • 文件命名
    makefile 或者 Makefile
  • Makefile 规则
    • 一个 Makefile 文件中可以有一个或者多个规则\
      目标 ...: 依赖 ...
          命令(Shell 命令)
          ...
      
      • 目标:最终要生成的文件(伪目标除外)
      • 依赖:生成目标所需要的文件或是目标
      • 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
    • Makefile 中的其它规则一般都是为第一条规则服务的

例子:
首先输入vim Makefile,然后再Makefile文件里添加以下代码

app:sub.c add.c mult.c div.c main.c
        gcc sub.c add.c mult.c div.c main.c -o app

最后输入make指令即可

1.6.3 工作原理

  • 命令在执行之前,需要先检查规则中的依赖是否存在
    • 如果存在,执行命令
    • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
    • 如果依赖的时间比目标的时间晚,需要重新生成目标
    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

1.6.4 变量

  • 自定义变量
    变量名=变量值 var=hello
  • 预定义变量
    AR : 归档维护程序的名称,默认值为 ar
    CC : C 编译器的名称,默认值为 cc
    CXX : C++ 编译器的名称,默认值为 g++
    $@ : 目标的完整名称
    $< : 第一个依赖文件的名称
    $^ : 所有的依赖文件
  • 获取变量的值
    $(变量名)\
# define
src = sub.o add.o mult.o div.o main.o
target = app

$(target):$(src)
        $(CC) $(src) -o $(app)

sub.o:sub.c
        gcc -c sub.c -o sub.o

add.o:add.c
        gcc -c add.c -o add.o

mult.o:mult.c
        gcc -c mult.c -o mult.o

div.o:div.c
        gcc -c div.c -o div.o

main.o:main.c
        gcc -c main.c -o main.o

1.6.5 模式匹配

add.o:add.c
    gcc -c add.c
div.o:div.c
    gcc -c div.c
sub.o:sub.c
    gcc -c sub.c
mult.o:mult.c
    gcc -c mult.c
main.o:main.c
    gcc -c main.c
%.o:%.c
    - %: 通配符,匹配一个字符串
    - 两个%匹配的是同一个字符串
%.o:%.c
    gcc -c $< -o $@

例子:

# define
src = sub.o add.o mult.o div.o main.o
target = app

$(target):$(src)
        $(CC) $(src) -o $(app)

%.o:%.c
    gcc -c $< -o $@

1.6.6 函数

  • $(wildcard PATTERN...)

    • 功能:获取指定目录下指定类型的文件列表
    • 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
    • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
    • 示例:
      $(wildcard *.c ./sub/*.c)
      返回值格式: a.c b.c c.c d.c e.c f.c
  • $(patsubst <pattern>,<replacement>,<text>)

    • 功能:查找<text>的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合
      模式<pattern>如果匹配的话,则以<replacement>替换。
    • <pattern>可以包括通配符%,表示任意长度的字串。如果<replacement>中也包含%,那么,<replacement>中的这个%将是<pattern>中的那个%所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)
    • 返回:函数返回被替换过后的字符串
    • 示例:
      $(patsubst %.c, %.o, x.c bar.c)
      返回值格式: x.o bar.o

示例:

# define
# add.c sub.c main.c mult.c div.c
src = $(wildcard ./*.c)
objs = $(patsubst %.c, %.o, $(src))
target = app

$(target):$(objs)
        $(CC) $(objs) -o $(target)

%.o:%.c
        $(CC) -c $< -o $@

.PHONY:clean 
# clean为伪目标,防止外界有clean文件时无法执行make clean命令

clean:
        rm $(objs) -f

1.7 GDB调试

1.7.1 定义

  • GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。

  • 一般来说,GDB 主要帮助你完成下面四个方面的功能:

    1. 启动程序,可以按照自定义的要求随心所欲的运行程序
    2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
    3. 当程序被停住时,可以检查此时程序中所发生的事
    4. 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG

1.7.2 准备工作

  • 通常,在为调试而编译时,我们会关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,-Wall在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。

  • gcc -g -Wall program.c -o program

  • -g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。

1.7.3 GDB 命令 – 启动、退出、查看代码

  • 启动和退出
    gdb 可执行程序
    quit

  • 给程序设置参数/获取设置参数
    set args 10 20
    show args

  • GDB 使用帮助
    help

  • 查看当前文件代码
    list/l (从默认位置显示)
    list/l 行号 (从指定的行显示)
    list/l 函数名(从指定的函数显示)

  • 查看非当前文件代码
    list/l 文件名:行号
    list/l 文件名:函数名

  • 设置显示的行数
    show list/listsize
    set list/listsize 行数

1.7.4 GDB命令——断点操作

  • 设置断点
    b/break 行号
    b/break 函数名
    b/break 文件名:行号
    b/break 文件名:函数

  • 查看断点
    i/info b/break

  • 删除断点
    d/del/delete 断点编号

  • 设置断点无效
    dis/disable 断点编号

  • 设置断点生效
    ena/enable 断点编号

  • 设置条件断点(一般用在循环的位置)
    b/break 10 if i==5

1.7.5 GDB 命令——调试命令

  • 运行GDB程序
    start(程序停在第一行)
    run(遇到断点才停)

  • 继续运行,到下一个断点停
    c/continue

  • 向下执行一行代码(不会进入函数体)
    n/next

  • 变量操作
    p/print 变量名(打印变量值)
    ptype 变量名(打印变量类型)

  • 向下单步调试(遇到函数进入函数体)
    s/step
    finish(跳出函数体)

  • 自动变量操作
    display 变量名(自动打印指定变量的值)
    i/info display
    undisplay 编号

  • 其它操作
    set var 变量名=变量值 (循环中用的较多)
    until (跳出循环)

1.8 文件IO

1.8.1 标准 C 库 IO 函数

  • 通过文件指针 *fp进行操作
    • 文件描述符(整型值)
    • 文件读写指针位置
    • I/O缓冲区(内存地址)

1.8.2 标准 C 库 IO 和 Linux 系统 IO 的关系

1.8.3 虚拟地址空间

  • 虚拟地址 ---MMU--->物理地址
    MMU(内存管理单元)
  • 堆空间从低地址到高地址存储,栈空间从高地址到低地址存储

1.8.4 文件描述符

  • 文件描述符表:一个数组,在PCB进程控制块中,存储文件描述符

  • 每打开一个新文件就占用一个文件描述符,而且是空闲的最小的一个文件描述符

1.8.5 Linux 系统 IO 函数

  • int open(const char *pathname, int flags);

    • 参数:
      • pathname:要打开的文件路径
      • flags:对文件的操作权限设置还有其他的设置
        O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
    • 返回值:返回一个新的文件描述符,如果调用失败,返回-1
      errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号
    • void perror(const char *s);
      • 作用:打印errno对应的错误描述
      • s参数:用户描述,比如hello,最终输出的内用是 hello:xxx(实际的错误描述)
    • 代码示例:
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main(){
    
        // 打开一个文件
        int fd = open("a.txt", O_RDONLY);
    
        if(fd == -1){
            perror("open");
        }
    
        //关闭
        close(fd);
    
        return 0;
    
    }
    
  • int open(const char *pathname, int flags, mode_t mode);

    • 参数:

      • pathname:要打开的文件路径
      • flags:对文件的操作权限设置还有其他的设置
        • 必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
        • 可选项:
      • mode:八进制数,表示创建出的新的文件的操作系统,比如:0775
        最终的权限是: mode & ~umask
        umask默认0002
        umask作用为了抹去某些权限
        权限:比如:rw-r--r--\
      第一段 第二段 第三段
      当前用户权限 当前用户所在组权限 其他组的权限
      rwx rwx rwx
      rw- r-x -wx

      其中r为read,w为write,x为execute可执行,比如0777代表rwxrwxrwx

      flag是个int类型数据,4个字节,32位
      每一位就是一个标志位,所以在函数括号中使用逻辑或|

    • 示例代码:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main(){
    
        //创建一个新文件
        int fd = open("create.txt",O_RDWR|O_CREAT,0777);
    
        if(fd == -1){
            perror("open");
        }
    
        close(fd);
    
        return 0;
    }
    
  • int close(int fd);

    • 作用:关闭文件指示符
  • ssize_t read(int fd, void *buf, size_t count);

    • 参数:
      • fd: 文件描述符,open得到的,通过这个文件描述符操作某个文件
      • buf:需要读取数据存放的地方,数组的地址(传出参数)
      • count:指定的数组的大小
    • 返回值:
      • 成功:
        >0: 返回实际的读取到的字节数
        =0: 文件已经读取完了
      • 失败:
        -1 ,并且设置errno
  • ssize_t write(int fd, const void *buf, size_t count);

    • 参数:
      • fd: 文件描述符,open得到的,通过这个文件描述符操作某个文件
      • buf:要往磁盘写入的数据,数据
      • count:要写的数据的实际的大小
    • 返回值:
      • 成功:实际写入的字节数
      • 失败: -1 ,并且设置errno

    -代码示例(read + write):

    // 实现copy功能
    #include <unistd.h>
    #include <stdio.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    int main(){
    
        // 1.通过open打开eng.txt文件
        int srcfd = open("eng.txt",O_RDONLY);
        if(srcfd == -1){
            perror("open");
            return -1;
        }
    
        // 2.创建一个新的文件
        int destfd = open("cpy.txt",O_WRONLY|O_CREAT,0664);
        if(destfd == -1){
            perror("open");
            return -1;
        }
    
        // 3.频繁的读写操作
        char buf[1024] = {0};
        int len=0;
        while((len = read(srcfd ,buf , sizeof(buf)))>0){
            write(destfd, buf, len);
        }
        // 4.关闭文件
        close(srcfd);
        close(destfd);
    
        return 0;
    }
    
  • off_t lseek(int fd, off_t offset, int whence);

    • 标准C库的函数:

      #include <stdio.h>
      int fseek(FILE.*stream, long offset, int whence);
      
    • Linux系统函数:

      #include <sys/types .h>
      #include <unistd.h>
      off_t lseek(int fd, off_t offset, int whence);
      
      • 参数:
        • fd:文件描述符,通过open得到的,通过这个fd操作某个文件
        • offset:偏移量
        • whence :
          • SEEK_ SET
            设置文件指针的偏移量
          • SEEK_ CUR
            设置偏移量:当前位置+第二个参数offset的值
          • SEEK_ END
            设置偏移量:文件大小+第二个参数offset的值
      • 返回值:返回文件指针的位置
    • 作用:

      1. 移动文件指针到文件头
        1sIk(fd, 0, SEEK SET);

      2. 获取当前文件指针的位置
        lseek(fd, 0, SEEK_ CUR);

      3. 获取文件长度
        lseek(fd, 0, SEEK_ END);

      4. 拓展文件的长度,当前文件10b, 110b, 增加了100个字节
        lseek(fd, 100, SEEK_END)
        注意:需要写入一次数据
        用途:下载文件时站空位置

    • 代码示例:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main() {
    
        //打开文件
        int fd = open("hello.txt", O_RDWR);
    
        if(fd == -1) {
            perror("open");
            return -1;
        }
        
        //扩展文件长度
        int ret = lseek(fd, 100, SEEK_END);
        if(ret==-1){
            perror("lseek");
            return -1;
        }
    
        //写入一个空数据
        write(fd, " " , 1);
    
        //关闭文件
        close(fd);
    
        return 0;
    }
    
  • int stat(const char *pathname, struct stat *statbuf);

    • 作用:获取一个文件相关的一些信息

    • 参数:

      • pathname:操作的文件的路径
      • statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
    • 返回值:

      • 成功:返回0
      • 失败;返回-1 设置errno
    • stat 结构体

    struct stat {
        dev_t st_dev; // 文件的设备编号
        ino_t st_ino; // 节点
        mode_t st_mode; // 文件的类型和存取的权限
        nlink_t st_nlink; // 连到该文件的硬连接数目
        uid_t st_uid; // 用户ID
        gid_t st_gid; // 组ID
        dev_t st_rdev; // 设备文件的设备编号
        off_t st_size; // 文件字节数(文件大小)
        blksize_t st_blksize; // 块大小
        blkcnt_t st_blocks; // 块数
        time_t st_atime; // 最后一次访问时间
        time_t st_mtime; // 最后一次修改时间
        time_t st_ctime; // 最后一次改变时间(指属性)
    };
    
  • int lstat(const char *pathname, struct stat *statbuf);

    • 作用:获取软连接到的文件的相关的一些信息

    • 参数:

      • pathname:操作的文件的路径
      • statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
    • 返回值:

      • 成功:返回o
      • 失败;返回-1 设置errno
    • 代码示例

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main(){
        struct stat statbuf;
    
        int ret = stat("a.txt",&statbuf);
    
        if(ret == -1){
            rerror("stat");
            return -1;
        }
    
        print("size: %ld\n",statbuf.st_size);
    
        return 0;
    }
    

1.8.7 t_mode 变量

模拟实现ls -l指令

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

// 模拟实现ls-l指令
// -rw-r--r-- 1 mobbu learngroup 13 Jul 14 17:01 a.txt
int main(int argc, char* argv[]){

    // 判断输入的参数是否正确
    if(argc < 2){
        printf("%s fielname\n", argv[0]);
        return -1;
    }

    // 通过stat函数获取用户传入的文件的信息
    struct stat st;
    int ret = stat(argv[1], &st);
    if(ret == -1){
        perror("stat");
        return -1;
    }

    // 获取文件的类型和文件的权限
    char perms[11] = {0};   // 用于保存文件类型和文件权限的字符串

    switch(st.st_mode & __S_IFMT){
        case __S_IFLNK:
            perms[0] = 'l';
            break;
        case __S_IFDIR:
            perms[0] = 'd';
            break;
        case __S_IFREG:
            perms[0] = '-';
            break;
        case __S_IFBLK:
            perms[0] = 'b';
            break;
        case __S_IFCHR:
            perms[0] = 'c';
            break;
        case __S_IFIFO:
            perms[0] = 'p';
            break;
        case __S_IFSOCK:
            perms[0] = 's';
            break;
        default:
            perms[0] = '?';
            break;
    }

    // 判断文件的访问权限

    // 文件所有者
    perms[1] = st.st_mode & S_IRUSR ? 'r' : '-';
    perms[2] = st.st_mode & S_IWUSR ? 'w' : '-';
    perms[3] = st.st_mode & S_IXUSR ? 'x' : '-';
    // 文件所在组
    perms[4] = st.st_mode & S_IRGRP ? 'r' : '-';
    perms[5] = st.st_mode & S_IWGRP ? 'w' : '-';
    perms[6] = st.st_mode & S_IXGRP ? 'x' : '-';
    // 其他组
    perms[7] = st.st_mode & S_IROTH ? 'r' : '-';
    perms[8] = st.st_mode & S_IWOTH ? 'w' : '-';
    perms[9] = st.st_mode & S_IXOTH ? 'x' : '-';

    // 硬连接数
    int linkNum = st.st_nlink;

    // 文件所有者
    char* fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组
    char* fileGroup = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改时间
    char*time = ctime(&st.st_mtime);
    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);

    char buf[1024];
    sprintf(buf,"%s %d %s %s %ld %s %s",perms, linkNum, fileUser, fileGroup, fileSize, mtime, argv[1]);

    printf("%s\n", buf);

    return 0 ;
}

1.8.8 文件属性操作函数

  • int access(const char *pathname, int mode);

    • 作用:判断某个文件是否有某个权限,或者判断文件是否存在
    • 参数;
      • pathname:判断的文件路径
      • mode:
        • R_OK:判断是否有读权限
        • w_OK:判断是否有写权限
        • x_OK:判断是否有执行权限
        • F_OK:判断文件是否存在
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
    • 示例代码:
    #include<unistd.h>
    #include<stdio.h>
    
    int main(){
    
        int ret = access("a.txt",F_OK);
        if(ret == -1){
            perror("access");
        }
    
        printf("文件存在!\n");
    
        return 0;
    
    }
    
  • int chmod(const char *filename, int mode);

    • 作用:修改文件的权限
    • 参数;
      • pathname:需要修改的文件的路径
      • mode:需要修改的权限值,八进制的数
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
    • 示例代码:
    #include <sys/stat.h>
    #include <stdio.h>
    
    int main(){
    
        int ret = chmod("a.txt", 0775);
    
        if(ret == -1){
            perror("chmod");
        }
    
        return 0;
    }
    
  • int chown(const char *path, uid_t owner, gid_t group);

    • 作用:改变文件所有者
  • int truncate(const char *path, off_t length);

    • 作用:缩减或者扩展文件的尺寸至指定的大小
    • 参数;
      • path:需要修改的文件的路径
      • length:需要最终文件变成的大小
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
    • 示例代码:
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    
    int main(){
    
        int ret = truncate("a.txt", 20);
    
        if(ret == -1){
            perror("chmod");
        }
    
        return 0;
    }
    

1.8.9 目录操作函数

  • int rename(const char *oldpath, const char *newpath);
    • 作用:修改目录名字
    • 参数:
      • oldpath:需要修改的目录的路径
      • newpath:目录的新名称
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
    • 示例代码:
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(){
    
        // 权限还是会和umask进行与
        int ret = rename("aaa", "bbb");
    
        if (ret == -1){
            perror("rename");
            return -1;
        }
    
        return 0;
    
    }
    
  • int chdir(const char *path);
    • 作用:修改进程的工作目录
      比如在/home/ nowcoder启动了一个可执行程序a.out,进程的工作目录
    • 参数:
      path :需要修改的工作目录
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
  • char *getcwd(char *buf, size_t size);
    • 作用:获取当前工作目录
    • 参数:
      • buf :存储的路径,指向的是一个数组(传出参数)
      • size:数组的大小
    • 返回值:
      返回的指向的一块内存,这个数据就是第一个参数
    • 示例代码:
    #include <unistd.h>
    #include <stdio.h>
    #include <fcntl.h>
    
    int main(){
    
        // 获取当前的工作目录
        char buf[128];
        getcwd(buf, sizeof(buf));
        printf("当前工作目录是: %s\n", buf);
    
    
        // 修改工作目录
        int ret = chdir("/home/mobbu/Linux/lesson13");
        if (ret == -1){
            perror("rename");
            return -1;
        }
    
        // 创建一个新的文件
        int fd = open("chair.txt", O_CREAT | O_RDWR, 0664);
        if (fd == -1){
            perror("open");
            return -1;
        }
    
        close(fd);
    
        //获取当前的工作目录
        char buf1[128];
        getcwd(buf1, sizeof(buf1));
        printf("当前工作目录是: %s\n", buf1);
    
        return 0;
    
    }
    
  • int mkdir(const char *pathname, mode_t mode);
    • 作用:创建一个目录
    • 参数:
      • pathname:创建的目录的路径
      • mode:权限,八进制的数
    • 返回值:
      • 成功:返回0
      • 失败:返回-1
    • 示例代码:
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <stdio.h>
    
    int main(){
    
        // 权限还是会和umask进行与
        int ret = mkdir("aaa", 0777);
    
        if (ret == -1){
            perror("mkdir");
            return -1;
        }
    
        return 0;
    
    }
    
  • int rmdir(const char *pathname);
    • 作用:删除一个目录

1.8.10 目录遍历函数

  • DIR *opendir(const char *name);

    • 参数:
      • name:需要打开的目录的名称
        返回值:
      • DIR*类型,理解为目录流
      • 错误返回NULL
  • struct dirent *readdir(DIR *dirp);

    • 参数:
      dirp是opendir返回的结果
    • 返回值:
      • struct dirent,代表读取到的文件的信息
      • 读取到了末尾或者失败了,返回NULL
  • int closedir(DIR *dirp);

    • 关闭目录
  • 代码:获取目录下所有普通文件的个数

#define _DEFAULT_SOURCE
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);

//读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]){

    if(argc < 2) {
        printf("%s path\n", argv[0]);
        return -1;
    }

    int num = getFileNum(argv[1]);

    printf("普通文件的个数为:%d\n", num);

    return 0;
}

//用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {

    // 1.打开目录
    DIR * dir = opendir(path);

    if(dir == NULL){
        perror("opendir");
        exit(0);
    }

    struct dirent *ptr;

    // 记录普通文件的个数
    int total = 0;

    while((ptr = readdir(dir)) != NULL){
        
        //获取名称
        char * dname = ptr->d_name;
        
        //忽略掉.和..
        if(strcmp(dname,".") == 0 || strcmp(dname, "..") == 0){
            continue;
        }

        // 判断是普通文件还是目录
        if(ptr->d_type == DT_DIR){
            // 目录需要继续读取目录
            char newpath[256];
            sprintf(newpath, "%s/%s", path, dname);
            total += getFileNum(newpath);
        }

        if(ptr->d_type == DT_REG){
            // 普通文件
            total++;
        }

        // 关闭目录
        closedir(dir);
        return total;
    }
}

1.8.11 dirent 结构体和 d_type

struct dirent
{
    // 此目录进入点的inode
    ino_t d_ino; 
    // 目录文件开头至此目录进入点的位移
    off_t d_off; 
    // d_name 的长度, 不包含NULL字符
    unsigned short int d_reclen; 
    // d_name 所指的文件类型
    unsigned char d_type; 
    // 文件名
    char d_name[256];
};
d_type
    DT_BLK - 块设备
    DT_CHR - 字符设备
    DT_DIR - 目录
    DT_LNK - 软连接
    DT_FIFO - 管道
    DT_REG - 普通文件
    DT_SOCK - 套接字
    DT_UNKNOWN - 未知

1.8.12 dup、dup2 函数

  • int dup(int oldfd);\

    • 作用:复制一个新的文件描述符
      fd=3,int fd1 = dup(fd),
      fd指向的是a.txt,fd1也是指向a.txt
      从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
    • 代码示例:
    #include <unistd.h>
    #include<stdio.h>
    #include<fcntl.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<string.h>
    
    int main(){
    
        int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
    
        int fd1 = dup(fd);
    
        if(fd1 == -1){
            perror("dup");
            return -1;
        }
    
        printf("fd : %d , fd1 : %d\n", fd, fd1);
    
        close(fd);
    
        char *str = "hello,world";
        int ret = write(fd1, str, strlen(str));
    
        if(ret == -1){
            perror("write");
            return -1;
        }
    
        close(fd1);
    
        return 0;
    }
    
  • int dup2(int oldfd, int newfd);\

    • 作用:重定向文件描述符
      oldfd 指向a.txt,,newfd指向 b.txt
      调用函数成功后:newfd和 b.txt做close, newfd指向了a.txt
      oldfd必须是一个有效的件描述符
      oldfd和newfd值相同,相当于什么都没有做
    • 代码示例:
    #include <unistd.h>
    #include<stdio.h>
    #include<fcntl.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<string.h>
    
    int main(){
    
        int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
        if(fd == -1){
            perror("open");
            return -1;
        }
    
        int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
        if(fd1 == -1){
            perror("open");
            return -1;
        }
    
        printf("fd : %d, fd1 : %d\n", fd, fd1);
    
        int fd2 = dup2(fd, fd1);
    
        if(fd2 == -1){
            perror("dup2");
            return -1;
        }
    
        // 通过fd1去写数据, 实际操作的是1.txt,而不是2.txt
        char *str ="hello,dup2";
        int len = write(fd1,str,strlen(str));
    
        if(len == -1){
            perror("write");
            return -1;
        }
    
        printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);
    
        return 0;
    }
    

1.8.13 fcntl 函数

  • int fcntl(int fd, int cmd, ... /* arg */ );\
    • 参数

      • fd :表示需要操作的文件描述符
      • cmd:表示对文件描述符进行如何操作
        • F_DUPFD :复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
          int ret = fcntl(fd,F_DUPFD);
        • F_GETFL :获取指定的文件描述符文件状态flag
          获取的flag和我们通过open函数传递的flag是一个东西。
        • F_SETFL :设置文件描述符文件状态flag
          • 必选项:O_RDONLY,O_WRONLY,O_RDWR不可以被修改
          • 可选性:O_APPEND,O)NONBLOCK
            • O_APPEND 表示追加数据
            • NONBLOK设置成非阻塞
    • 阻塞和非阻塞:描述的是函数调用的行为。

    • 示例代码:

    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(){
    
        // 1.复制文件描述符
        // int fd = open("1.txt",O_RDONLY);
        // int ret = fcntl(fd, F_DUPFD);
    
        // 2.修改或者获取文件状态flag
        int fd = open("1.txt", O_RDWR);
        if(fd == -1){
            perror("open");
            return -1;
        }
    
        // 获取文件描述符状态flag
        int flag = fcntl(fd, F_GETFL);
        flag |= O_APPEND;
    
        int ret = fcntl(fd, F_SETFL, flag);
    
        char* str = "nihaoya";
        write(fd, str, strlen(str));
    
        close(fd);
    
    }
    
posted @ 2023-07-13 17:34  mobbu  阅读(77)  评论(0编辑  收藏  举报