Linux高并发服务器开发第一章:Linux系统编程入门

1. Linux开发环境搭建

  1. 安装Linux系统(虚拟机安装、云服务器)
    https://releases.ubuntu.com/bionic/
  2. 安装XSHELL、XFTP
    https://www.netsarang.com/zh/free-for-home-school/
  3. 安装Visual Studio Code
    https://code.visualstudio.com/
  4. 安装 sshd 服务
    sudo apt install openssh-server
  5. 安装 gcc/make 等工具
    sudo apt install build-essential

2. GCC

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

2.2 GCC工作流程 / C++从代码到可执行文件的过程

  • C++与C语言类似,一个C++程序从源码到执行文件,有四个过程,预编译、编译、汇编、链接
  1. 预编译(预处理):这个阶段的处理操作如下
    • (1)将所以的#define删除,并且展开所有的宏定义。
    • (2)处理所有的条件预编译指令,如#if、#ifdef。
    • (3)处理#include预编译指令,将被包含的头文件插入到该预编译指令的位置。
    • (4)过滤所有的注释。
    • (5)添加行号和文件名标识,生成以.i结尾的文件名。
  2. 编译:这个过程主要的处理操作如下:
    • (1)词法分析:将源代码的字符序列分割成一系列的记号。
    • (2)语法分析:对记号进行语法分析,产生语法树。
    • (3)语义分析:判断表达式是否有意义。
    • (4)代码优化
    • (5)汇编代码生成:生成以.s结尾的汇编代码。
    • (6)汇编代码优化。
  3. 汇编:这个过程主要是将汇编代码转变成机器可以执行的机器指令,也就是生成以.o结尾的目标代码。
  4. 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可执行程序(默认以.out或者.exe结尾)。
    链接分为静态链接和动态链接
    • 静态链接是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算把静态库删除也不会影响可执行程序的执行。生成的静态链接库,Windows中以.lib为后缀,Linux中以.a为后缀。
    • 动态链接在链接的时候没有把要调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有调用的函数代码,只包含函数的重定向信息,所以删除动态库时,可执行程序就无法运行。生成的动态链接库,Windows中以.dll为后缀,Linux中以.so为后缀。

2.3 gcc 与 g++的区别

  • 后缀为.c的程序,gcc 把它当作是 C 程序,而 g++ 把它当作 c++ 程序。而后缀为.cpp的程序,两者都会认为是 c++ 程序。
  • gcc 命令不能自动和 C++ 程序使用的库链接,所以通常使用 g++ 来完成链接。但在编译阶段,g++ 会自动调用 gcc,两者等价。

2.4 GCC常用参数选项

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

3. Makefile

3.1 简介

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

3.2 Makefile 文件命名和规则

  1. 文件命名:makefile 或者 Makefile
  2. Makefile规则:
    一个 Makefile 文件中可以有一个或者多个规则
    目标... : 依赖...
    	命令(Shell 命令)
    	...
    
    Makefile 中的其他规则一般都是为第一条规则服务的。

3.3 基本原理

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

3.4 变量

  1. 自定义变量
    语法规则:变量名=变量值,比如var=hello,定义了一个变量var,变量值为hello
    获取变量的值:$(变量名),比如$(var),获取到var变量的值hello
  2. 预定义变量
    AR:归档维护程序的名称,默认值为ar
    CC:C 编译器的名称,默认值为cc
    CXX:C++ 编译器的名称,默认值为g++
    $@:目标的完整名称。
    $<:第一个依赖文件的名称。
    $^:所有的依赖文件的名称。

注意⚠️:自动变量只能在规则的命令中使用

app:main.c a.c b.c
	gcc -c main.c a.c b.c -o app

等价于

app:main.c a.c b.c
	$(CC) -c $^ -o $@

3.5 模式匹配

%.o:%.c:该命令中的%是通配符,匹配一个字符串,命令中的两个%匹配的是相同的字符串。

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

等价于

src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
	$(CC) $(src) -o $(target)

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

3.6 函数

(1)$(wildcard PATTERN...)

  • 功能:获取指定目录下指定类型的文件列表
  • 参数:PATTERN指的是一个或多个目录下的某种类型的文件,如果有多个目录,一般使用空格隔开
  • 返回:得到的若干个文件的文件列表,文件名之间使用空格隔开
  • 示例:$(wildcard ./*.c ./*.o),该命令获取当前目录下的所有以.c.o结尾的文件,并以文件列表的形式返回。

(2)$(patsubst <pattern>,<replacement>,<text>)

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

注意⚠️:当生成目标文件时也生成了一些中间文件,但是这些中间文件最终不需要,因此需要将中间文件进行删除,例如,当生成可执行文件的过程中生成了以.o结尾的文件a.ob.o,可使用下列的规则对其删除:

.PHONY:clean
clean:
	rm a.o b.o -f

4. GDB

  • GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境, GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。
  • 一般来说,GDB 主要帮助我们完成四个方面的功能:
    1. 启动程序,可以按照自定义的要求随心所欲的运行程序
    2. 可让被调试的程序在所指定的断点处停住(断点可以是条件表达式)
    3. 当程序被停住时,可以检查此时程序中所发生的事
    4. 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
  • 通常,在为调试而编译时,我们会关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,在尽量不影响程序行为的情况下选项打开所有warning(-Wall),也可以发现许多问题,避免一些不必要的 BUG。
    • gcc -g -Wall program.c -o program
  • -g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。
  • GDB命令
    • 启动和退出:gdb 可执行程序quit
    • 给程序设置参数/获取设置参数:set args 10 20show args
    • GDB 使用帮助:help
    • 查看当前文件代码:list/l(从默认位置显示)、list/l 行号(从指定的行显示)、list/l 函数名(从指定的函数显示)
    • 查看非当前文件代码:list/l 文件名:行号list/l 文件名:函数名
    • 设置显示的行数:show list/listsizeset list/listsize 行数
    • 设置断点: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
    • 运行GDB程序:start(程序停在第一行)、run(遇到断点才停)
    • 继续运行,到下一个断点停:c/continue
    • 向下执行一行代码(不会进入函数体)n/next
    • 变量操作:p/print 变量名(打印变量值)、ptype 变量名(打印变量类型)
    • 向下单步调试(遇到函数进入函数体):s/stepfinish(跳出函数体)
    • 自动变量操作:display 变量名(自动打印指定变量的值)、i/info displayundisplay 编号
    • 其它操作:set var 变量名=变量值(循环中用的较多)、until(跳出循环)

5. 静态库和动态库

5.1 什么是库?

  • 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接使用的变量、函数或类。
  • 库是特殊的一种程序,编写库的函数与编写一般的程序区别不大,只是库不能单独运行。
  • 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
  • 库的好处:1. 代码保密;2. 方便部署和分发。

5.2 静态库

(1)命名规则

  • 在 Linux 系统中,静态库的文件名为libxxx.a,其中:lib为固定的前缀,xxx为库的名称(自己取),.a为固定的后缀。
  • 在 Windows 系统中,静态库的文件名为libxxx.lib

(2)静态库的制作

  1. 通过gcc -c xxx 获得 .o 文件;
  2. .o文件打包,使用ar工具(archive)生成静态库。
ar rcs libxxx.a xxx.o xxx.o

对于rcsr表示将文件插入备存文件中,c表示建立备存文件,s表示索引。

(3)通过静态库生成可执行文件

  • 首先将静态库和头文件导入进项目文件系统中,如下图:
    在这里插入图片描述
  • src目录下执行以下命令
    gcc main.c -o app -I ../include/ -l calc -L ../lib/
    
    在当前src目录下生成可执行文件app,直接执行./app即可运行程序。

5.3 动态库

(1)命名规则

  • 在 Linux 系统中,静态库的文件名为libxxx.so,其中:lib为固定的前缀,xxx为库的名称(自己取),.so为固定的后缀。
  • 在 Windows 系统中,静态库的文件名为libxxx.dll

(2)动态库的制作

  1. 通过gcc得到.o文件,得到和位置无关的代码
gcc -c -fpic/-fPIC a.c b.c 
  1. 动态库在 Linux 下是一个可执行文件,可通过gcc得到动态库。
gcc -shared a.o b.o -o libcalc.so

(3)通过动态库生成可执行文件

  • 首先将静态库和头文件导入进项目文件系统中
  • src目录下执行以下命令
    gcc main.c -o app -I ../include/ -l calc -L ../lib/
    
    在当前src目录下生成可执行文件app,但是此时执行./app命令却出现错误,错误为error while loading shared libraries: lobcalc.so。原因就是此时的可执行文件无法定位共享库文件的位置。程序启动以后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系,当系统加载可执行代码的时候,能够知道其所依赖的库的名字,但是还需要知道库的绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于 elf 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf 文件的 DT_RPATH 段——>环境变量LD_LIBRARY_PATH——> /etc/ld.so.cache 文件列表——> /lib/,/usr/lib目录找到库文件后将其载入内存,通过./app即可运行程序。
    实际操作过程中,可通过更改环境变量LD_LIBRARY_PATH,具体实现为:
    为了将修改后的环境变量应用到当前用户未来所有的环境下,可以把修改命令放到~/.bashrc文件中。
    .bashrc文件末尾添加命令export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wzp/Linux/lesson03/04_lib/library/lib,修改完~/.bashrc文件后,记得执行source ~/.bashrc. .bashrc,来将修改应用到当前的bash环境下。

5.4 工作原理

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

5.5 静态库与动态库的区别

  • 静态库、动态库区别来自链接阶段如何处理,链接成可执行程序。分别称为静态链接方式和动态链接方式。
  • 静态库的优缺点:
    • 优点:
      静态库被打包到应用程序中加载速度快;
      发布程序无需提供静态库,移植方便。
    • 缺点:
      消耗系统资源,浪费内存;
      更新、部署、发布麻烦。
  • 动态库的优缺点:
    • 优点:
      可以实现进程间资源共享(共享库);
      更新、部署、发布简单;
      可以控制何时加载动态库。
    • 缺点:
      加载速度比静态库慢;
      发布程序时需要提供依赖的动态库。

6. 文件

6.1 标准 C 库 IO 函数

在这里插入图片描述

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

在这里插入图片描述

6.3 虚拟地址空间

在这里插入图片描述

6.4 文件描述符

在这里插入图片描述

6.5 Linux系统IO函数

(1)int open(const char *pathname, int flags)
函数描述:主要作用是打开一个文件用于读或写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
    - pathname:要打开的文件路径
    - flags:对文件的操作权限设置还有其他的设置
      O_RDONLY,  O_WRONLY,  O_RDWR  这三个设置是互斥的
返回值:返回一个新的文件描述符,如果调用失败,返回-1

errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。

#include <stdio.h>
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); //打开当前路径下的 a.txt 文件

    if(fd == -1) {
        perror("open");
    }
    // 读操作

    // 关闭
    close(fd);

    return 0;
}

(2)int open(const char *pathname, int flags, ,mode_t mode)
函数描述:主要作用是创建一个文件用于读或写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);
    参数:
        - pathname:要创建的文件的路径
        - flags:对文件的操作权限和其他的设置
            - 必选项:O_RDONLY,  O_WRONLY, O_RDWR  这三个之间是互斥的
            - 可选项:O_CREAT 当文件不存在时,创建新文件
        - mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
        最终的权限是:mode & ~umask
        0777   ->   111111111
    &   0775   ->   111111101
    ----------------------------
                    111111101
    按位与:0和任何数都为0
    umask的作用就是抹去某些权限。

    flags参数是一个int类型的数据,占4个字节,32位。
    flags 32个位,每一位就是一个标志位。

样例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() { 

    // 在当前路径下创建一个新的文件
    int fd = open("create.txt", O_RDWR | O_CREAT, 0777);

    if(fd == -1) {
        perror("open");
    }

    // 关闭
    close(fd);

    return 0;
}

(3)int close(int fd)
函数描述:关闭一个文件描述符


(4)ssize_t read(int fd, void *buf, size_t count)
函数描述:read()函数从文件描述符fd指向的文件对象中读取数据,读取的数据大小为count字节,将读取的数据存放到buf指向的缓冲区中。

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

(5)ssize_t write(int fd, const void *buf, size_t count)
函数描述:write()函数对文件描述符fd指向的文件对象中写入数据,要写的数据大小为count字节,将要写入磁盘的数据存放到buf指向的缓冲区中。

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
    参数:
        - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
        - buf:要往磁盘写入的数据,数据
        - count:要写的数据的实际的大小
    返回值:
        - 成功:实际写入的字节数
        - 失败:返回-1,并设置errno

样例:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {

    // 1.通过open打开english.txt文件
    int srcfd = open("english.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(destfd);
    close(srcfd);


    return 0;
}

(6)off_t lseek(int fd, off_t offset, int whence)
函数描述:lseek()函数主要对文件指针进行操作,完成相应的功能。

标准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.移动文件指针到文件头
    lseek(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;
}

(7)int stat(const char *pathname, struct stat *statbuf)
函数描述:stat()函数获取pathname所指向的文件的一些信息。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);
    作用:获取一个文件相关的一些信息
    参数:
        - pathname:操作的文件的路径
        - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
    返回值:
        成功:返回0
        失败:返回-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) {
        perror("stat");
        return -1;
    }

    printf("size: %ld\n", statbuf.st_size);


    return 0;
}

(8)int lstat(const char *pathname, struct stat *statbuf)

作用:lstat()函数与stat()类似,只是命名文件是符号链接的情况不同:
	 lstat()返回的是该符号链接本身的信息,而stat()返回该链接链接指向的文件的信息。
参数:
    - pathname:操作的文件的路径
    - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
    - 成功:返回0
    - 失败:返回-1 设置errno

6.6 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; // 最后一次改变时间(指属性)
};

6.7 st_mode 变量

在这里插入图片描述


6.8 文件属性操作函数

  • 可在终端中通过 man 2 funcname 指令查询Linux相关函数的详细描述。
    (1)int access(const char *pathname, int mode)
#include <unistd.h>
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;
}

(2)int chmod(const char *filename, int mode)

#include <sys/stat.h>
int chmod(const char *pathname, mode_t 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 -1;
	}
	return 0;
}

(3)int chown(const char *path, uid_t owner, gid_t group)
修改文件的所有者id和所在组id,通过vim /etc/passwd可查看用户id和所在组的id,通过vim /etc/group可查看所有组的id,通过id 用户名可得到用户的id、组id。
(4)int truncate(const char *path, off_t length)

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
    作用:缩减或者扩展文件的尺寸至指定的大小
    参数:
        - path: 需要修改的文件的路径
        - length: 需要最终文件变成的大小
    返回值:
        成功返回0, 失败返回-1

样例:

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = truncate("b.txt", 5);

    if(ret == -1) {
        perror("truncate");
        return -1;
    }

    return 0;
}

6.9 目录操作函数

(1)int rename(const char *oldpath, const char *newpath)
样例:

#include <stdio.h>

int main() {

    int ret = rename("aaa", "bbb");

    if(ret == -1) {
        perror("rename");
        return -1;
    }

    return 0;
}

(2)int chdir(const char *path)

#include <unistd.h>
int chdir(const char *path);
    作用:修改进程的工作目录
        比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
    参数:
        path : 需要修改的工作目录

#include <unistd.h>
char *getcwd(char *buf, size_t size);
    作用:获取当前工作目录
    参数:
        - buf : 存储的路径,指向的是一个数组(传出参数)
        - size: 数组的大小
    返回值:
        返回的指向的一块内存,这个数据就是第一个参数

样例:

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    // 获取当前的工作目录
    char buf[128];
    getcwd(buf, sizeof(buf));
    printf("当前的工作目录是:%s\n", buf);

    // 修改工作目录
    int ret = chdir("/home/nowcoder/Linux/lesson13");
    if(ret == -1) {
        perror("chdir");
        return -1;
    } 

    // 创建一个新的文件
    int fd = open("chdir.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;
}

(3)char *getcwd(char *buf, size_t size)


(4)int mkdir(const char *pathname, mode_t mode)

#include <sys/stat.h>
#include <sys/types.h>
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() {

    int ret = mkdir("aaa", 0777);

    if(ret == -1) {
        perror("mkdir");
        return -1;
    }

    return 0;
}

(5)int rmdir(const char *pathname)


6.10 目录遍历函数

(1)DIR *opendir(const char *name)

// 打开一个目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
    参数:
        - name: 需要打开的目录的名称
    返回值:
        DIR * 类型,理解为目录流
        错误返回NULL

(2)struct dirent *readdir(DIR *dirp)

// 读取目录中的数据
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
    - 参数:dirp是opendir返回的结果
    - 返回值:
        struct dirent,代表读取到的文件的信息
        读取到了末尾或者失败了,返回NULL

(3)int closedir(DIR *dirp)

// 关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);

样例:

#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;
}

6.11 dirent 结构体和 d_type

在这里插入图片描述

6.12 dup、dup2 函数

(1)int dup(int oldfd)

#include <unistd.h>
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;
}

(2)int dup2(int oldfd, int newfd)

#include <unistd.h>
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 <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.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);

    close(fd);
    close(fd1);

    return 0;
}

6.13 fcnl 函数

int fcntl(int fd, int cmd, ... /* arg */ ):复制文件描述符、设置/获取文件的状态标志

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ...);
参数:
    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);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小浩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值