我们都知道linux下一切皆文件的概念那么具体的运行机制是如何的呢为什么通过操作文件即可操作对应的硬件而具体的过程如下Linux 通过 VFS虚拟文件系统将硬件设备抽象为文件接口。当用户通过文件描述符fd发起系统调用时内核通过 fd 找到对应的struct file和inode从 inode 中提取设备号并在内核驱动表中找到已注册的struct file_operations。随后内核调用该结构体中对应的函数如.read/.write由该函数通过驱动配置操作硬件控制器进行数据收发。硬件本身的初始化由驱动加载或open时完成。因为有这么一整套完整的运行机制所以内核就只需向外暴露一个文件的接口供给用户使用就可以那么我们接下来就具体说一说对应的文件的操作。一.文件IO1.介绍IO自如其名 指input输入和output输出文件IO是操作系统内核直接提供的、基于“文件描述符fd”的、无用户态缓冲区的底层输入输出接口在Linux下通常指POSIX(可移植操作系统接口)标准的系统调用他是直接基于文件描述符fd的接口是用户能触及内核的最短路径无用户态缓存所以实时性会很强我们在需要高实时性和更底层的操作时会用到很多比如网络编程。他有几个特点不提供缓冲机制每次读写操作都引起系统调用核心概念是文件描述符fd访问各种类型文件Linux下,标准IO基于文件IO实现2.文件描述符fd文件描述符文件描述符是一个非负整数如 3, 4, 5...它是操作系统内核为了高效管理已被打开的文件而分配给进程的一个索引号。每一个打开的文件都对应一个文件描述符文件IO就是通过这个描述符完成的文件描述符是从0开分配的依次递增0, 1, 2表示标准输入标准输出错误3.文件打卡与关闭下面我们来深入接触一下系统提供的文件IO的函数接口#include fcntl.hint open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);第一个参数pathname是文件的路径名称第二个参数flags是指对文件的操作类型比如只读只写等第三个参数是对文件的权限管理只有在新建文件时有效这里插入一下在linux中通过 ll 命令我们可以查看文件的详细信息如下-rw-rw-r--1 kickpi kickpi 0 Jul 31 19:37 test.txt我们可以看到对于test.txt文件的第一列-rw-rw-r--有此文件的类型权限信息等-rw-这几个从左到右分别是指文件类型和用户组user权限r读取w写入 最后一个是执行权限-就是没有执行权限x就是有执行权限。rw-这几个是所属组Group权限可读可写不可执行r--这几个是其他用户权限我们经常会看到给文件赋予权限的命令chmod 666 test.txt就是改变这几个参数666对应用户组所属组合其他用户组。至于数字比如说用户组rw-就是按二进制来算-是0w是010对应2r是100对应4 所以这个数就是0246.其他同理。好了接着我们回归整体介绍文件IO的操作详细说一说第二个参数的flags使用使用多个参数可以用 | 符号结合O_RDONLY以只读方式打开文件。O_WRONLY以只写方式打开文件。O_RDWR以读写方式打开文件。O_CREAT如果文件不存在则创建它。使用此标志时必须提供第三个参数mode来指定新文件的权限。O_TRUNC如果文件已存在且以写方式打开则将其长度截断为0清空内容。O_APPEND以追加方式打开。每次执行write写入时数据都自动添加到文件末尾。O_EXCL与O_CREAT同时使用时如果文件已存在则打开失败。这可用于测试文件是否存在且该操作是原子的。O_NONBLOCK以非阻塞方式打开文件。用于设备文件、FIFO或Socket等。如果资源不可用open调用会立即返回而不会让程序一直等待。O_SYNC使每次write操作同步进行即等待物理I/O操作完成后才返回。这会确保数据被写入磁盘但会降低性能。O_NOCTTY如果pathname指向一个终端设备则不会将该设备分配为进程的控制终端。O_DIRECTORY如果pathname不是目录则open调用失败。O_NOFOLLOW如果pathname是一个符号链接则open调用失败关闭文件的函数#include unistd.hint close(int fd);参数fd是文件描述符。成功时返回0错误时返回-1。下面给出示例代码// 示例代码使用文件IO打开、创建、关闭文件 #include stdio.h // 标准输入输出头文件 #include unistd.h // POSIX操作系统API头文件包含close函数 #include errno.h // 错误号定义头文件 #include fcntl.h // 文件控制选项头文件包含open函数和标志 int main() { // 打开或创建文件./test.txt以读写模式如果文件不存在则创建权限为0666 int fd open(./test.txt, O_RDWR | O_CREAT, 0666); // 检查文件描述符是否有效open失败返回-1 if (fd -1) { // 打印错误信息 printf(%s, strerror(errno)); return -1; // 返回错误代码 } // 打印成功打开的文件描述符值 printf(fd %d\n, fd); // 关闭文件描述符释放资源 close(fd); return 0; // 程序正常退出 }输出结果如下fd34.文件的写入读取等#include unistd.hssize_t write(int fd, void *buf, size_t count);这是文件的写入其中buf是写入数据的缓存区count是写入数据的大小ssize_t read(int fd, void *buf, size_t count);这是文件的读取其中buf是接受数据的缓存区count是读取数据大小count要小于buf。其次是读取的时候要留一位给换行符。读取和写入都设计要一个概念就是文件偏移量后移就是指无论你写入数据还是读取数据都会把指向文件内容的位置移动到最后一个处理的字符那里所以要使用文件定位函数#include unistd.hoff_t lseek(int fd, off_t offset, intt whence);第三个参数whence是锁定开始移动的位置第二个参数offset是移动多少字节这个函数可以帮助我们调整目前文件内容所在的位置。下面是第三个参数的具体值和含义SEEK_SET0文件开头绝对定位。offset必须 0。把书签直接插到指定的页数。SEEK_CUR1当前位置相对定位。offset可正可负。书签从当前页开始往前或往后翻指定的页数。SEEK_END2文件末尾相对定位。offset通常为负数往后退也可以为正数制造空洞。书签从最后一页的末尾开始往前退。看代码#include stdio.h#include unistd.h#include errno.h#include fcntl.hint main(){int fd open(./test.txt,O_RDWR|O_CREAT,0666);char buf[12] {0};if(fd-1){printf(%s,strerror(errno));return -1;}if(write(fd,hello world,sizeof(hello world))0){printf(%s,strerror(errno));return -1;}lseek(fd, 0, SEEK_SET);int ret read(fd,buf,sizeof(buf));if(ret0){printf(%s,strerror(errno));return -1;}printf(fd%d\n,fd);printf(buf%s\n,buf);close(fd);return 0;}输出$ make runfd3bufhello world这就是完整的打开写入调整位置读取关闭文件的流程了解这些就差不多了还有一些其他的函数有特定的功能我放在文章最后的链接里可以自行查看二.标准IO标准IO和文件IO是有区别的他是基于文件IO用ANSI C标准进行定义的加入了用户缓存区这一环节正是有这个缓存机制减少了系统的调用环节能实现更高的效率c语言中的stdio.h就是其标准库。标准IO的操作是用一个FILE结构体来存放文件信息的他会将信息先存入用户态缓存区到达一定的数据量后再推送出去这样的操作就像流操作所以标准IO又被称为 流 。如下FILE结构体的定义#ifndef _FILE_DEFINEDstruct _iobuf {char *_ptr;int _cnt;char *_base;int _flag;int _file;int _charbuf;int _bufsiz;char *_tmpfname;};typedef struct _iobuf FILE;下面我们说一说关于多出来的这个缓存区缓冲区不是虚无缥缈的概念它就是 C 库glibc为每个FILE *流在用户态内存堆上分配的一块连续的字符数组比如 8192 字节。当使用标准IO的写入函数fwrite时会先将写入的数据存入缓存区达到一定条件时才会调用系统IO函数来进行系统调度这时候有几种机制有几个指针_IO_write_base指向缓冲区的起始地址。_IO_write_ptr指向当前可写入的位置。数据从这里开始存放。_IO_write_end指向缓冲区的末尾地址模式 场景 解释全缓冲_IOFBF_IO_write_ptr _IO_write_end写满或者程序正常exit/fclose满了才调用write。默认用于普通磁盘文件。行缓冲_IOLBF写入的数据里包含\n换行符或者写满遇到回车就调用write。默认用于终端stdout。注意如果缓冲区满了但没换行也会刷。无缓冲_IONBF_IO_write_ptr根本不用缓冲区长度为 0每次fwrite直接调用write数据不经过用户态缓存。默认用于stderr。stdin/stdout默认是行缓冲stderr没有缓冲1.文件的打开关闭FILE *fopen (const char *path, const char *mode);path是文件路径和文件名mode是和文件IO一样指定打开文件的形式模式字符串文件不存在时文件存在时读写位置偏移量核心用途r(只读)报错返回 NULL正常打开文件开头必须文件已存在只读不写如读取配置文件。w(只写)创建新文件清空为 0 字节内容全丢文件开头从头开始写覆盖旧内容危险操作类似O_TRUNC。a(追加)创建新文件保留原有内容文件末尾所有写入的数据自动追加到文件末尾类似O_APPEND不能回头改旧数据。在基础后面加上 表示可读可写r报错返回 NULL正常打开不清空文件开头用于“修改现有文件”的场景读旧数据写新数据覆盖。注意读写切换时必须fseek或fflush否则容易混乱。w创建新文件清空为 0 字节文件开头用于“从头创建并读写”。相当于先清空再读写。a创建新文件保留原有内容文件末尾读时从开头读写时强制末尾读可以从开头看但写永远跳到末尾追加还有一个b参数 ,他是为了阻止 C 库在\n和\r\n之间做多余的事可以保证读取写入的正确性在windows系统中\r 代表回到行首 \n代表向下平移到下一行\r\n结合就是到下一行行首如果没用参数bwindows系统下可能对\n做处理把他转换成\r\n会引起歧义。linux只有\n不会引起歧义。做移植的时候就必须加这个参数了int fclose(FILE *stream)成功返回0失败返回EOF-12.读写文件操作读取#include stdio.hint fgetc(FILE *stream);int getc(FILE *stream); //宏int getchar(void);fgetc是读取流的一个字符getc也是读取流的一个字符但是他是宏getchar是获取输入的字符与fgetc(stdin)一样char *fgets(char *s, int size, FILE *stream);这个是读取整行数据的写入int fputc(int c, FILE *stream);int putc(int c, FILE *stream);int putchar(int c);成功时返回写入的字符出错时返回EOF(-1)这几个函数都是写入单个字符的int puts(const char *s); int fputs(const char *s, FILE *stream);puts将缓冲区s中的字符串输出到stdout并追加’\n’fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’再介绍这两个函数高效读写size_t fread(void *ptr, size_t size, size_t n, FILE *fp);size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);参数含义通俗理解ptr内存缓冲区指针读取时数据放这里fread写入时数据从这里取fwrite。size每个元素的大小字节数比如你要存int这里就填sizeof(int)要存char就填1。nmemb要读/写的元素个数你想一次性处理多少个元素。stream文件流指针fopen返回的FILE*。fread和fwrite是标准I/O中“批量数据搬运工”。相比于fgetc一次一个字符和fputs一次一行它们直接操作内存块非常适合读写二进制数据如结构体、数组、图片数据或大块文本。接着补充对于文件内容位置操作的函数函数原型移动基准返回值是否清除feof/ferror是否支持超大文件 (2GB)rewindvoid rewind(FILE *fp);固定到开头无 (void)是隐式清除是内部使用fsetpos或 64位偏移fseekint fseek(FILE *fp, long offset, int whence);SEEK_SET/CUR/END 任意偏移量成功0失败-1否标志保留否long在32位系统限制2GBftelllong ftell(FILE *fp);获取当前位置成功返回偏移量失败返回-1L不影响标志否同样受long大小限制fgetposint fgetpos(FILE *fp, fpos_t *pos);获取当前位置存入pos成功0失败非0不影响标志是fpos_t设计支持大文件fsetposint fsetpos(FILE *fp, const fpos_t *pos);设置到pos指定的位置成功0失败非0否标志保留是fpos_t设计支持大文件到这里就差不多了这里在附上基本操作的代码#include stdio.h#include unistd.h#include errno.h#include fcntl.hint main(){FILE *fpfopen(test.txt,r);if(fpNULL){perror(fopen);return -1;}fputs(wsyunnand,fp);rewind(fp);int buf;buffgetc(fp);if(buf!EOF){printf(read:%c\n,buf);}char read[100] {0};int ifread(read,1,100,fp);printf(read all:%d,i);printf(read all:%s,read);fclose(fp);return 0;}文件内容是这个wsyunnandyeyeyessssssssssssddddddddd输出$ make runread:wread all:39read all:syunnandyeyeyessssssssssssddddddddd最后把一些其他标准IO和文件IO的一些其他函数放在这个链接linux标准IO和文件IO函数-CSDN博客