Linux系统编程(2)文件IO
-
Linux系统编程(2)文件IO
最基本的文件访问方法是系统调用read()和write()。但是,在访问文件之前,必须先通过open()或creat()打开该文件。一旦完成文件读写,还应该调用系统调用close()关闭该文件。
大致流程如下open/create->read/write->closeopen系统调用
open系统调用整个流程如下:
┌──────────────┐ │ Your Code │ │ open(...) │ └──────┬───────┘ ▼ ┌──────────────────┐ │ glibc (libc.so) │ ← 用户态库 │ open() wrapper │ └──────┬───────────┘ ▼ ┌──────────────────┐ │ syscall entry │ ← 用户态 / 内核态边界 │ SYS_openat │ └──────┬───────────┘ ▼ ┌──────────────────┐ │ Linux Kernel │ │ sys_openat() │ └──────────────────┘而在使用上,我们需要先包含下述头文件
#include <unistd.h> // read, write, close, lseek #include <fcntl.h> // open, O_RDONLY/O_CREAT/... flags #include <sys/stat.h> // mode_t, 权限位 S_IRUSR 等(创建文件常用) #include <sys/types.h> // ssize_t, off_t 等(有时可省,现代系统常被间接包含)通过系统调用open(),可以打开文件并获取其文件描述符:
open系统调用有两个函数签名int open(const char *name, int flags); int open(const char *name, int flags, mode_t mode);它们的区别是 第三个参数
mode要不要传(后面补充这第三个参数的作用)open的返回值是文件描述符,linux下一切皆文件,如果返回-1代表发生错误
open()的flags参数
flags参数是由一个或多个标志位的按位或组合。它支持三种访问模式:O_RDONLY、O_WRONLY或O_RDWR,(其实是单词的缩写,不用死记硬背)这三种模式分别表示以只读、只写或读写模式打开文件。
举个例子,以下代码以只读模式打开文件/home/kidd/madagascar:int fd =open("/home/kidd/madagascar",O_RDONLY); if(fd==-1){ //这里是错误处理,要养成好习惯,随手处理错误 }使用open打开文件之后,我们就获得了这个文件的句柄。用这个句柄就可以对这个文件施加操作了,但是要尤为注意的是,对这个文件能够施加怎样的操作,取决于打开方式,即flags的参数。
那么问题来了,如果文件不存在怎么办? 这就涉及到flags其他可选参数:1) 创建/覆盖类
-
O_CREAT:文件不存在就创建(这时必须提供第三个参数mode)open("a.txt", O_WRONLY | O_CREAT, 0644); -
O_EXCL:配合O_CREAT使用;如果文件已存在则失败(避免覆盖)
当和标志位O_CREAT一起使用时,如果参数name指定的文件已经存在,会导致open()调用失败。用于防止创建文件时出现竞争。如何没有和标志位O_CREAT一起使用,该标志位就没有任何含义。
open("a.txt", O_WRONLY | O_CREAT | O_EXCL, 0644); // 存在则报 EEXIST上面涉及到O_CREAT的操作就需要第三个参数了0644,这个参数的意义如下:
0 6 4 4 ↑ ↑ ↑ │ │ └─ 其他用户 (other) │ └── 同组用户 (group) └──── 文件所有者 (owner)0644 的三位分别代表谁?
权限格式是:
[ owner ][ group ][ other ]
每一位是 0~7 的八进制数:
数字 二进制 含义
4 100 读 (r)
2 010 写 (w)
1 001 执行 (x)
所以0644代表:文件所有者rw权限,同组用户和其他用户有r权限
使用O_CREAT必须使用第三个权限参数
-
O_TRUNC:文件已存在且以写方式打开时,把文件长度截断为 0(清空重写)open("a.txt", O_WRONLY | O_TRUNC);
2) 追加/定位类
-
O_APPEND:每次write都追加到文件末尾(内核保证追加定位的原子性)open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
3) 阻塞行为/同步类
-
O_NONBLOCK:非阻塞 I/O(对 pipe/终端/socket 影响大,对普通磁盘文件通常意义不大,后面需要时再讲)open("/dev/tty", O_RDONLY | O_NONBLOCK); -
O_SYNC:更强的同步写语义(write 返回前尽量保证数据落到存储设备,性能会下降) -
O_DSYNC:只保证数据(不强制元数据)同步(比 O_SYNC 轻一些) -
O_RSYNC: O_RSYNC标志位指定读请求和写请求之间的同步。该标志位必须和O_SYNC或O_DSYNC一起使用。 -
O_LARGEFILE
文件偏移使用64位整数表示,可以支持大于2GB的文件。64位操作系统中打开文件时,默认使用该参数。
fd = open("a.txt",O_LARGEFILE | O_RDONLY);4) 安全/继承类
-
O_CLOEXEC:执行exec()时自动关闭该 fd,防止泄漏到子进程open("a.txt", O_RDONLY | O_CLOEXEC);
(close execute的缩写)
5) 其他你可能会见到的
-
O_NOCTTY:打开终端设备时不把它变成进程的控制终端 -
O_NOFOLLOW:不跟随符号链接(用于安全场景,避免被 symlink 攻击)
由前面的例子可以明白,这些参数都是位操作符
是可以做|运算把不同操作拼接在一起的小练习
- 只读打开
int fd=open("a.txt",O_RDONLY); if(fd==-1){ perror("open"); }- 覆盖写(不存在就创建,存在就清空)
int fd =open("a.txt",O_WRONLY | O_CREAT | O_TRUNC,0644); if(fd==-1){ perror("open"); }- 追加写日志
int fd=open("a.txt",O_WRONLY | O_APPEND |O_CREAT,0644); if(fd==-1){ perror("open"); }- 只允许“新建”,不允许覆盖
int fd=open("a.txt",O_WRONLY |O_CREAT |O_EXCL,0644);这里额外讲一下,其实是有creat()系统调用的,但是用起来方便,支持的操作也比较少,一般用open就可以
creat函数签名#include <fcntl.h> int creat(const char *pathname, mode_t mode); -
-
感觉可以把这个系列整理成一个GitHub 仓库 包含文档和对应的练习或演示代码
让读者上手实操/改改东西 用 strace 等一些工具 追一追 系统调用 观察真实机器上的效果 之类的
体验感应该能增强不少 -
@sunrisepeak 在 Linux系统编程(2)文件IO 中说:
感觉可以把这个系列整理成一个GitHub 仓库 包含文档和对应的练习或演示代码
让读者上手实操/改改东西 用 strace 等一些工具 追一追 系统调用 观察真实机器上的效果 之类的
体验感应该能增强不少时间比较少,有空可以整理一下