ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux基础IO(open等文件相关系统调用)

Linux基础IO(open等文件相关系统调用) 一、文件是什么内容 属性文件 内容数据 属性元数据属性包括权限、大小、时间、属主、类型等由内核记录0 KB 的文件也占磁盘空间——内容为空但属性元数据必须存储平时用ls -l看到的大小只是内容大小不是文件真正占用的磁盘空间二、Linux 一切皆文件广义一切皆文件键盘、显示器、网卡、磁盘……都被抽象成文件统一用读/写接口操作向显示器写数据 显示从键盘读数据 输入狭义磁盘上普通的数据文件一切皆文件的核心价值用同一套 read/write 接口操作所有设备不用为每种设备发明新接口具体我会在下一篇文章详细介绍三、进程打开文件到底发生了什么关键认知程序编译成二进制 ≠ 打开文件代码里写了fopen(log.txt, w)编译出可执行程序但不运行→ 文件没被打开只有程序运行起来、执行到open/fopen那一行 → 文件才被打开所以打开文件是进程运行时的行为不是编译期的行为文件的两大分类内存级文件被进程打开或即将被打开的文件磁盘级文件未被打开、静静躺在磁盘上的文件本节课重点研究被打开的文件。四、操作系统是磁盘的真正管理者用户程序不能直接访问硬件磁盘你写代码操作文件真正读写磁盘的是操作系统用户程序必须通过系统调用接口操作系统提供的入口访问磁盘系统调用 vs 库函数层次例子说明系统调用open/read/write/close操作系统提供的接口直接进内核库函数fopen/fread/fwrite/fcloseC 标准库封装底层调用系统调用库函数底层封装了系统调用fopen封装openfclose封装close等各语言各自封装C/C/Python/PHP/Go 都有自己的一套文件接口但底层只有一套系统调用封装带来的好处代码可移植——换平台只需要换库的底层实现你的代码不用改五、内核如何管理被打开的文件问题系统同一时刻有大量被打开的文件谁在管内核负责管理所有被打开的文件内核为每个被打开的文件创建对应的struct_file 结构体大量结构体通过链表组织起来(先描述后组织进程 ↔ 文件的关系转化为进程结构体 ↔ 文件结构体这两种内核数据结构之间的关系六、库函数层面fopen / fwrite / fread / fclosefopen —— 打开文件FILE*fpfopen(log.txt,w);if(fpNULL){perror(fopen);exit(1);}返回FILE*文件指针指向内核维护的文件结构体的封装返回 NULL 说明打开失败必须检查返回值模式w写、r读、a追加等fwrite —— 写文件fwrite(buffer,sizeof(buffer),1,fp);fclose(fp);参数(数据指针, 基本单位大小 size, 基本单位个数 nmemb, FILE*)fread —— 读文件⚠️ 返回值大坑size_tnfread(buffer,sizeof(buffer)-1,1,fp);fread 返回值是成功读到的基本单位个数不是字节数返回值统计维度是nmemb基本单位个数不是字节真实读到的字节数 size × 返回个数老师写 mycat 时踩坑把n当字节数用结果只打印出第一个字符H正确姿势想让 buffer 存 127 个字符就把 buffer 定义成128预留\0位置charbuffer[128];while(1){size_tnfread(buffer,sizeof(buffer)-1,1,fp);// 一次最多读 127 字节if(n0){buffer[n]\0;// 手动补字符串结尾printf(%s,buffer);}if(feof(fp))break;// 读到文件结尾就停}feof —— 判断文件是否读到结尾feof(fp)返回非 0 表示已经读到文件结尾常配合 fread 循环使用读完判断 EOF跳出循环七、系统调用层面open为什么 open 是可变参数C 语言不支持函数重载但 open 要有两种用法 → 底层用可变参数va_list技术实现两种形式// 2 参数打开已存在的文件读文件时用intfdopen(path,O_RDONLY);// 3 参数创建新文件时指定权限 modeintfdopen(log.txt,O_CREAT|O_WRONLY|O_TRUNC,0666);2 参数版本文件已存在不需要新建 → 不需要 mode3 参数版本要新建文件带 O_CREAT→ 必须给权限 mode否则权限乱码flags位图传参O_RDONLY / O_WRONLY / O_RDWR / O_CREAT / O_APPEND / O_TRUNC都是宏本质是32 位 int 的不同 bit 标记每位代表一种属性用按位或|组合O_WRONLY | O_CREAT | O_TRUNCO_RDONLY默认全 0八进制 00所以单独使用不与其他位冲突mode 与 umask新建文件的权限陷阱// 期望权限 0666实际呢intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);系统有默认umask 掩码会屏蔽掉 mode 中的某些权限位典型结果0666 → 实际 0664组的写权限被屏蔽想严格等于 mode先清 umaskumask(0);intfdopen(log.txt,O_WRONLY|O_CREAT|O_TRUNC,0666);open 需要的头文件#includesys/types.h#includesys/stat.h#includefcntl.h八、系统调用层面readssize_tread(intfd,void*buf,size_tcount);fd文件描述符open 的返回值指定从哪个文件读buf读数据要放的缓冲区count缓冲区大小最多读多少字节返回值语义重点返回值含义 0实际读到的字节数 0读到文件结尾EOF 0读取失败注意read 返回值是字节数跟 fread 返回个数不同读文件的注意事项要读的文件必须已存在——读一个不存在的空文件毫无意义所以读文件不能用 O_CREAT只用O_RDONLY这也解释了open 为什么有 2 参数形式读文件不需要新建就不需要 modeintfdopen(log.txt,O_RDONLY);// 读2 参数就够了读上来的数据是原始字节流系统层面没有文本读和二进制读之分——读上来的都是二进制字节流要不要把它当文本解释由你自己决定想当字符就当字符想当什么就当什么九、文本/二进制写入是语言层概念系统层面只有一种写入二进制write接口的缓冲参数是void*——系统根本不关心你写的是什么类型所谓文本写入 / 二进制写入是语言/库层面的概念最终落到系统调用都是二进制显示器是字符设备写入的是字符向显示器写123456写的是字符串 “1” “2” “3” “4” “5” “6”6 个字符而不是二进制整数123456所以显示器这类设备叫字符设备十、代码演示mycat 的实现版本一库函数fopen fread#includestdio.h#includestring.h#includestdlib.hintmain(intargc,char*argv[]){if(argc!2){printf(Usage: %s filename\n,argv[0]);exit(1);}FILE*fpfopen(argv[1],r);if(fpNULL){perror(fopen);exit(2);}charbuffer[128];while(1){size_tnfread(buffer,sizeof(buffer)-1,1,fp);if(n0){buffer[n]\0;printf(%s,buffer);}if(feof(fp))break;}fclose(fp);return0;}版本二系统调用open read#includestdio.h#includestring.h#includestdlib.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.hintmain(intargc,char*argv[]){if(argc!2){printf(Usage: %s filename\n,argv[0]);exit(1);}intfdopen(argv[1],O_RDONLY);// 读2 参数不新建if(fd0){perror(open);exit(2);}charbuffer[128];ssize_tn;while((nread(fd,buffer,sizeof(buffer)))0){fwrite(buffer,1,n,stdout);// 写到标准输出}// n 0 → EOFn 0 → 出错close(fd);return0;}
返回列表