Linux-5


标准C库IO函数

  1. 标准C库IO和Linux系统IO
  • 标准C库IO函数中,通过fopen方法打开一个文件时,返回的是一个文件指针FILE *fp(是一个结构体),其包含文件描述符(整型,指向已打开的文件)、文件读写指针(读写文件过程中指针/光标的实际位置)以及I/O缓冲区(内存地址,用于寻址找到对应的内存块,通常为8192byte)。
  • 数据从内存刷新到磁盘的三种情况:
    • 强制刷新缓冲区:fflush
    • 缓冲区已满
    • 正常关闭文件: flose(), return(main函数),exit(main函数)。
  • 标准C库IO是首先由用户程序进行调用,然后将数据写到缓冲区,最后调用内核函数将缓冲区的数据刷新到磁盘中。因此,标准C库IO和Linux系统IO的关系是前者调用后者的关系。

对于效率要求较高的如网络通信,则需要直接通过调用Linux的IO函数,直接将数据接收到磁盘中。

  1. 虚拟地址空间

  • 虚拟地址空间是不存在的,通过MMU映射到实际的物理内存。
  • 堆是由低地址向高地址存储,而栈则是由高地址向低地址存储。
  • 内核区的资源只能通过调用系统API来使用。
  1. 进程与应用程序的区别:应用程序指可执行程序,只占用磁盘空间而不占用内存;进程是指正在运行的应用程序,当运行某个应用程序时,系统需要为该程序分配一部分计算机资源,因而进程是占用系统内存的(每个进程都拥有自己的虚拟地址空间)。

  2. 文件描述符位于内核区,由内核中的进程控制块(PCB)进行管理。当进程创建后,PCB中实际上就有一个数组,称为文件描述符表,用于存放程序所使用的文件的信息,默认大小为1024(最多同时打开1024个文件). 表中前三个位置被标准输入(STDIN_FILENO)、标准输出(STDOUT_FILENO)和标准错误(STDERR_FILENO)占用,且这三个文件默认是打开状态的,对应当前终端(计算机系统的所有资源都被抽象为文件,当前终端为/dev/tty/)。单个文件多次打开,其文件描述符都是不一样的,只有当调用flose时才会销毁。

  3. Linux系统IO函数

  • 打开文件
    • 所需头文件:<sys/types.h><sys/stat.h><fcntl.h>
    • 函数原型:int open(const char *pathname, int flags);
    • 函数参数:
      • pathname: 目标文件路径
      • flags: 对文件的操作权限设置,包括O_RDONLY, O_WRONLY, or O_RDWR, 三种模式是互斥的.
      • 返回值: 返回一个新的文件描述符,如果调用失败则返回-1,并设置errno.

errno: 属于Linux系统函数库,是一个全局变量,记录最近的错误号
根据errno打印相关的错误信息的函数原型为:void perror(const char *s);
参数*s为所要显示的错误操作的字符串信息,其位于<stdio.h>中.

  • 创建文件:
    • 所需头文件:<sys/types.h><sys/stat.h><fcntl.h>
    • 函数原型:int open(const char *pathname, int flags, mode_t mode);
    • 函数参数:
      • pathname:创建文件的路径/文件名
      • flags: 对文件的操作权限设置(读写权限)
        • 必选:O_RDONLY, O_WRONLY, or O_RDWR, 三种模式是互斥的
        • 可选:O_CREAT: 创建文件
           O_CLOEXEC, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, and O_TRUNC
          
        • 备注:必选和可选用或操作取完整的标志位
      • mode: 八进制数,表示创建出来的文件的操作权限,最终的权限为(mode & ~umax),umax的作用是抹去某些权限,可以进行修改。
  • 关闭文件
    • 所需头文件: <unistd.h>
    • 函数原型: int close(int fd);
    • 函数参数: fd为文件标识符,通过open函数获得。
  • 读文件
    • 所需头文件:<unistd.h>
    • 函数原型: ssize_t read(int fd, void *buf, size_t count);
    • 函数参数:
      • fd: 文件描述符,通过open获取
      • buf: 缓冲区(需要读取的数据要存放的地址)
      • count: 指定数组的大小
  • 写文件
    • 所需头文件:<unistd.h>
    • 函数原型: ssize_t write(int fd, const void *buf, size_t count);
    • 函数参数
      • fd: 文件描述符,通过open获取
      • buf: 缓冲区(待写入的数据存放的地址)
      • count: 指定数组的大小
    • 返回值:写入的字节数,可能小于count;如果写入失败,则返回-1并设置errno
  • 移动文件指针(文件中的光标)
    • 所需头文件:<sys/types.h><unistd.h>
    • 函数原型: off_t lseek(int fd, off_t offset, int whence);
    • 函数参数
      • fd: 文件描述符,通过open获取
      • 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. 拓展文件长度:lseek(fd, 100, SEEK_END);
  • 获取文件相关的信息
    • 所需头文件:<sys/types.h><sys/stat.h><unistd.h>
    • 函数原型: int stat(const char *pathname, struct stat *statbuf);
    • 函数参数
      • pathname: 文件路径/文件名
      • statbuf:结构体,用于保存获取到的信息
  • 获取软连接文件相关的信息(不是软连接指向的文件,可以理解为某个指针指向某个数组,这里求的实际是指针的大小,而上边那个则是求数组的大小)
    • 所需头文件:<sys/types.h><sys/stat.h><unistd.h>
    • 函数原型: int stat(const char *pathname, struct stat *statbuf);
    • 函数参数
      • pathname: 文件路径/文件名
      • statbuf:结构体,用于保存获取到的信息
  1. 文件属性操作函数
  • 判文件权限/存在
    • 所需头文件:<unistd.h>
    • 函数原型:int access(const char *pathname, int mode);
    • 函数参数:
      • pathname: 文件路径/文件名
      • mode:R_OK:是否有读权限;W_OK:是否有写权限;X_OK:是否有执行权限;F_OK:文件是否存在
  • 修改文件权限
    • 所需头文件:<sys/stat.h>
    • 函数原型:int chmod(const char *pathname, mode_t mode);
    • 函数参数:
      • pathname: 所要修改的文件的文件路径/文件名
      • mode:需要修改的权限值(八位数)
  • 修改文件用户id和组id
    • 所需头文件:<unistd.h>
    • 函数原型:int chown(const char *pathname, uid_t owner, gid_t group);
    • 函数参数:
      • pathname: 所要修改的文件的文件路径/文件名
      • mode:需要修改的权限值(八位数)
  • 缩减或拓展文件尺寸
    • 所需头文件:<unistd.h><sys/stat.h>
    • 函数原型:int truncate(const char *path, off_t length);
    • 函数参数:
      • path:需要修改的文件的路径
      • length: 需要的最终大小(字节数)
  1. 目录操作函数
  • 创建目录
    • 所需头文件:<sys/stat.h><sys/types.h>
    • 函数原型:int mkdir(const char *pathname, mode_t mode);
    • 函数参数:
      • pathname:需要修改的文件的路径
      • mode: 权限,八进制数(必须要有执行权限才可打开该文件)
  • 删除空文件
    • 所需头文件:<unistd.h>
    • 函数原型:int rmdir(const char *pathname);
    • 函数参数:
      • pathname:待删除空目录
  • 目录重命名
    • 所需头文件:<stdio.h>
    • 函数原型:int rename(const char *oldpath, const char *newpath);
    • 函数参数:
      • path:需要修改的文件的路径
      • length: 需要的最终大小(字节数)
  • 更改当前目录: 修改进程的工作目录(默认在当前的工作路径,即可执行程序所在目录)
    • 所需头文件:<unistd.h>
    • 函数原型:int chdir(const char *path);, int fchdir(int fd);
    • 函数参数:
      • path:需要修改的工作目录
  • 获取当前工作
    • 所需头文件:<unistd.h>
    • 函数原型:char *getcwd(char *buf, size_t size);
    • 函数参数:
      • buf:用于保存工作路径,指向一个数组(传出参数)
      • size: 指定数组大小
    • 返回值:返回指向的一块内存,其实就是buf
  1. 目录遍历函数
  • 打开目录
    • 所需头文件:<sys/types.h><dirent.h>
    • 函数原型:DIR *opendir(const char *name);
    • 函数参数:
      • name:所要打开的目录
    • 返回值:DIR *类型,文件目录流,指向目录流中的第一个条目(可能是下一级文件目录,也可能是普通文件),若错误则返回NULL

      fdopendir()功能类似,但是不通过目录名来打开,而是通过open()函数获取文件标识符,然后通过文件标识符fd来打开目录。但注意,不应该继续在应用中使用该fd。

  • 读取目录中的数据

    • 所需头文件:<dirent.h>
    • 函数原型:struct dirent *readdir(DIR *dirp);
    • 函数参数:
      • dirp:待删除空目录,通过opendir获取
    • 返回值:struct dirent结构体指针,指向目录流中下一个目录条目,读取失败或者末尾则返回NULL。

      结构定义为:

      struct dirent {
          ino_t          d_ino;       /* 此目录进入点的inode */
          off_t          d_off;       /* 目录文件开头至此目录进入点的偏移 */
          unsigned short d_reclen;    /* d_name的长度,不包括空字符 */
          unsigned char  d_type;      /* d_name所指的文件类型 */
          char           d_name[256]; /* 文件名 */
      };
      其中文件类型包括: DT_BLK(块设备)DT_CHR(字符设备)DT_DIR(目录)DT_FIFO(管道)DT_LNK(软连接)DT_REG(普通文件)DT_SOCK(套接字)DT_UNKNOWN(未知类型)
  • 关闭目录

    • 所需头文件:<sys/types.h><dirent.h>
    • 函数原型:int closedir(DIR *dirp);
    • 函数参数:
      • dirp:DIR *类型,可以理解为目录流
  1. 文件描述符相关函数
  • 复制文件描述符:用文件描述符表中最小可用位置来拷贝目标文件描述符
    • 所需头文件: <unistd.h>
    • 函数原型: int dup(int oldfd);
    • 函数参数:oldfd为待拷贝的文件描述符
    • 返回值:成功则返回新的文件描述符,失败则返回-1并修改errno.

      复制后指向同一个文件,通过其中一个描述符关闭文件,仍然可用用另一个描述符来修改文件,即两个是独立的。

  • 重定向文件描述符
    • 所需头文件: <unistd.h>
    • 函数原型: int dup2(int oldfd, int newfd);
    • 函数参数
    • 返回值

      oldfd指向a.txt, newfd指向b.txt,函数调用成功后,相当于调用了close(newfd),并将newfd指向a.txt。注意,a.txt必须是有效的文件,并且和复制描述符一样,最后都需要对两个文件描述符进行close.

  1. fcntl函数: 复制文件描述符、设置/获取文件状态标志
  • 所需头文件:<unistd.h>, <fcntl.h>
  • 函数原型: int fcntl(int fd, int cmd, ... /* arg */ );
  • 函数参数:

    • fd: 文件描述符
    • cmd:表示要如何擦欧总该文件

      • F_DUPFD: 复制文件描述符, 复制的是第一个参数的fd,返回一个新的文件描述符,如:int ret = fcntl(fd, F_DUPFD);
        • 返回值:新的文件描述符
      • F_GETFL:获取指定文件的文件描述符的状态flag,获取的flag和open函数参数中的flag是一样的
        • 返回值:文件的状态flag
      • F_SET_FL:设置文件描述符状态flag

        • 必选项:O_RDONLY, O_WRONLY, or O_RDWR(不可修改)
        • 可选项:O_APPEND(追加数据), O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK(设置成非阻塞)

          阻塞和非阻塞:描述的是函数调用的行为。阻塞是调用函数后进程/线程被挂起,直到得到结果后才会返回;而非阻塞则是立刻返回(但结果准确性不能保证),比如等待用户输入就是阻塞。

  1. 其他
  • touch xxx.xx: 创建一个xxx.xx文件
  • 标准C库IO在man 3中,Linux的IO、文件相关的在man 2man 3

文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !