ARTICLE DETAIL

资讯详情

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

从标准库到系统调用:open、read、write初体验

从标准库到系统调用:open、read、write初体验 今天学的东西跟上一篇的fopen、fread、fwrite看着很像但其实有本质区别。之前学的是C标准库函数跨平台通用今天学的是Linux系统调用是操作系统内核直接提供的接口。简单说fopen底层也是调用了open但封装了缓冲区等细节用起来更方便而open、read、write更接近底层粒度更细控制也更灵活。这篇就记录一下open、read、write、close这几个核心系统调用的用法以及文件描述符这个概念。open打开文件拿到“通行证”系统调用里打开文件用的是open跟fopen最大的区别是它返回一个整数叫文件描述符而不是FILE指针。#include sys/types.h#include sys/stat.h#include fcntl.hint fd open(test.txt, O_RDWR | O_CREAT, 0664);第一个参数是路径第二个是打开方式flags第三个是权限mode只在创建文件时才生效。常用的flags有· O_RDONLY只读· O_WRONLY只写· O_RDWR读写· O_CREAT文件不存在则创建· O_TRUNC打开时清空文件内容· O_APPEND写入时追加到末尾这些flags可以用按位或|组合起来。比如O_WRONLY | O_CREAT | O_TRUNC就相当于标准库里的w模式——只写、不存在则创建、存在则清空。第三个参数mode指定文件权限比如0664表示文件所有者可读写、同组用户可读写、其他人只读。不过实际生效的权限还要减去系统的umask值这是Linux的安全机制防止创建权限过宽的文件。open成功返回一个非负整数文件描述符失败返回-1。所以每次open之后都要判断一下fd -1不然拿到个无效描述符后面的read、write全都会出错。文件描述符0、1、2的秘密文件描述符本质上就是一个整数操作系统用这个整数来代表一个已打开的文件。它的分配规则很简单每次分配当前最小的未使用整数。系统默认已经打开了三个· 0STDIN_FILENO标准输入默认是键盘· 1STDOUT_FILENO标准输出默认是终端· 2STDERR_FILENO标准错误默认也是终端这三个是每个程序启动时自动打开的直接就能用。比如用write(1, hello\n, 6)就能在终端打印hello效果和printf一样只不过printf是标准库封装过的更方便。每个进程能打开的文件描述符数量是有限的默认一般是1024个0到1023不过系统上限可以调整。所以用完文件一定记得关否则会泄漏。read和write读写操作read和write跟标准库里的fread、fwrite类似但参数顺序和返回值略有不同。write —— 写入数据#include unistd.hssize_t write(int fd, const void *buf, size_t count);参数文件描述符、数据缓冲区、要写的字节数。返回值是实际写入了多少字节成功时等于count失败返回-1。注意write不保证一次就能写完所有数据尤其在网络编程或者磁盘繁忙的时候。所以通常要判断返回值如果小于count要循环写入剩余的部分。read —— 读取数据ssize_t read(int fd, void *buf, size_t count);参数类似从文件描述符fd中读取最多count字节到buf里。返回值是实际读到的字节数如果大于0说明读到了数据如果等于0说明读到了文件末尾如果小于0说明出错了。read和write操作的是原始字节没有换行、字符串之类的概念也不像fgets那样自动帮你加\0所以缓冲区要自己管理好防止溢出。close关掉它int close(int fd);close就是关闭文件描述符释放资源。成功返回0失败返回-1。用了open就一定要配close这点和标准库是一个道理。否则文件描述符用完了程序后面就再也打不开新文件了。一个完整的例子拷贝文件下面这段代码用系统调用实现一个简单的文件拷贝程序把src.txt的内容复制到dst.txt#include stdio.h#include fcntl.h#include unistd.hint main() {int src open(src.txt, O_RDONLY);if (src -1){perror(打开源文件失败);return 1;}int dst open(dst.txt, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (dst -1){perror(打开目标文件失败);close(src);return 1;}char buf[1024];ssize_t n;while ((n read(src, buf, sizeof(buf))) 0){write(dst, buf, n); // 实际应该检查返回值这里简写了}close(src);close(dst);return 0;}这个例子跟标准库版本的核心区别就是没有缓冲区自动管理每次read把数据读进自己定义的buf再用write写出去。虽然代码看起来差不多但系统调用少了用户态和内核态之间的一层封装理论上更直接。标准库 vs 系统调用怎么选学到这里其实有个疑惑既然标准库用起来更方便为什么还要学系统调用我的理解是· 标准库函数fopen/fread/fwrite自带缓冲区适合常规文件读写代码可移植效率也不差。· 系统调用open/read/write更底层适合需要精细控制的场景比如网络编程、设备驱动开发或者需要处理信号中断、非阻塞IO等情况。另外open的flags比标准库的模式字符串丰富很多比如O_NONBLOCK非阻塞、O_SYNC同步写入这些在标准库层面很难直接控制。今天的内容偏底层代码量不大但理解文件描述符和系统调用的工作方式对后面学进程、管道、socket这些很有帮助。下一步准备继续看看lseek、dup、fcntl这些更进阶的系统调用争取把文件IO这块吃透。
返回列表