文件描述符:
在了解基础I/O基础函数之前,我们很有必要先了解一下文件描述符有关知识,其中的不懂之处可以留作疑问再来看这篇文章,参考博客:https://blog.csdn.net/Sun_Life_/article/details/90049499
open和openat
打开或创建一个文件并返回文件描述符
Q:为什么有了open还有openat,两者有什么区别?
A:openat是POSIX.1新增的函数,为了解决:
- 同一个进程中的所有线程共享相同的当前工作目录,openat让线程可以使用相对路径名打开目录中的文件,不再只能打开当前目录。
- 避免TOCTTOU(time-of-check-to-time-of-use)错误:竞态条件的一种:两个基于文件的函数A、B。B的调用要基于A的结果,由于两个调用不是原子操作,文件内容可能在两个函数调用配合的间隙被改变,导致程序错误。也称作配合软链接的漏洞:
在这个原本的setuid程序中,先用access()检查当前程序/进程的真实用户是否对制定的文件具有写权限,如果有,则对其写入相应的内容,否则异常退出,假如我们在access和open之间的时间片中将setuid程序的写入点改变为了/etc/passwd,而open的检查可以顺利通过(euid为0),从而向敏感文件写入数据,最终达到提权等目的。// --------------process of setuid program------- // if (access("filePathName", W_OK)) { exit(EXIT_FAILURE); } // ---------------------------------------------- // --------------process of attacer-------------- // // After the access check unlink("filePathName"); symlink("/etc/passwd", "filePathName"); // Before the open, "file" points to the password database // ---------------------------------------------- // --------------process of setuid program------- // fd = open("filePathName", O_WRONLY); // Actually writing over /etc/passwd write(fd, buffer, sizeof(buffer)); // ----------------------------------------------
区别就是openat的fd参数,共有3种可能:
fd参数会被忽略,openat就相当于open函数
fd参数指出了相对路径名在文件系统中的开始地址(fd参数通过打开相对路径名所在的目录获取)
路径名在当前目录中获取,openat在操作上与open类似
- path参数指定的是绝对路径名
- path参数指定的是相对路径名
- path参数指定了相对路径名,并具有特殊值AT_FDCWD
由open和openat函数返回的文件描述符一定是最小的未使用文件描述符数值
一个典型的例子:平常我们打开一个文件,基本原理就是先关闭掉文件描述符1(标准输出),然后执行打开操作,文件就会在文件描述符1上被打开,具体怎么保证一定会在特定的文件描述符上打开呢?我们稍后会了解到dup2函数。
path:要打开或创建文件的名字
oflag:以下列一个或多个选项做“或”运算构成
五选一:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
O_EXEC:只执行打开
O_SEARCH:只搜索打开
其余可选项(为大家列出来,不做过多说明):
creat
创建新文件,等效于open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
缺点是以只写方式打开创建的文件,必须先creat写再close,在open(先写再读)
close
关闭一个打开的文件,同时释放加在该文件上的所有记录锁。
记录锁(record locking):当一个进程正在读或修改文件的某个部分时,记录锁阻止其他进程修改同一文件区。因为UNIX内核没有文件记录的概念,而它锁定的只是文件中的一个区域或整个文件,所以更合适的应该是字节范围锁(byte-range locking)。
lseek
显式地为一个打开文件设置文件偏移量(文件描述符指向管道、FIFO或套接字,返回-1,并将errno设置为ESPIPE)
当前文件偏移量(current file offset):每一个打开的文件都有一个非负整数的当前文件偏移量,计算从文件开始计算的字节数,读、写都是从当前偏移量开始并增加所读写的字节数,系统默认偏移量为0(除非指定O_APPEND)
whence:
- SEEK_SET,设置偏移量:据文件开始出offset个字节
- SEEK_CUR,设置偏移量:当前值增加offset个字节,offset可正可负
- SEEK_END,设置偏移量:文件长度增加offset个字节,offset可正可负
read
从fd打开的文件中读取nbytes个字节放入buf中,read成功,返回读到的字节数,如已到达文件末尾返回0。
可以使实际读到的字节数少于要求读的字节数的情况:
到达文件尾端之前还有30个字节,要求读取50个字节,read返回30,再次调用read返回0(文件尾端)
非规范模式输入处理,输入字符不配装成行
- 普通文件:在读到要求字节数之前已经到达了文件尾端
- 终端设备:通常一次最多读一行(默认规范模式输入处理)
- 网络:网络中的缓冲机制可能造成返回值小于要求读的字节数
- 管道或FIFO:如果管道字节少于要求读的字节数,read只返回实际可用字节数
- 某些面向记录的设备(磁带):一次最多返回一个记录
- 读时信号中断:早期返回已读到的数据,现在出错返回EINTR
write
从buf中向fd打开的文件写入nbytes个字符,返回值通常与nbytes的值相同,否则表示逻辑上出错,出错通常因为磁盘已满或超过了文件的长度限制
若设定O_APPEND则每次写操作前将文件偏移量设置在文件结尾处,写成功后文件偏移量增加实际写的字节数
dup和dup2
复制一个现有的文件描述符
dup返回当前可用文件描述符的最小数值,dup2返回fd2参数指定的新描述符值,如果fd2已经打开先将其关闭
如果fd等于fd2,dup2返回fd2,而不关闭它,否则fd2的FD_CLOEXEC文件描述符被清除,fd2在进程调用exec是打开状态
dup(fd);等效于fcntl(fd, F_DUPFD, 0);
dup2(fd, fd2);等效于close(fd2); fcntl(fd, F_DUPFD, fd2);
1.dup2是原子操作,close、fcntl可能在close和fcntl之间调用信号捕捉函数,可能修改文件描述符,线程也同样不安全
2.dup2和fcntl有不同的errno
sync、fsync和fdatasync
UNIX系统内核中设有高速缓存或页高速缓存,大多数I/O都通过缓冲区进行。写入数据时,内核先将数据复制到缓冲区中,然后排入队列,之后再写入磁盘,也称作延迟写
假如内核需要通过缓冲区来存放其他磁盘数据,它会把所有延迟写数据块写入磁盘。而这三个函数则是操作系统用来保证磁盘上实际文件系统与缓冲区中内容的一致性
sync:只将所有修改过的块缓冲区排入写队列后返回,并不等待实际写磁盘操作结束(update守护进程冲洗块缓冲区)
fsync:只对文件描述符fd指定的文件起作用,等待磁盘操作结束后才返回
fdatasync:类似于fsync,但只影响文件的数据部分
fcntl
改变已经打开文件的属性
- 复制一个已有的文件描述符(cmd = F_DUPFD或F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标准(cmd = F_GETFD或F_SETFD)
- 获取/设置文件状态标志(cmd = F_GETFL或F_SETFL)
- 获取/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)
- 获取/设置记录锁(cmd = F_GETLK、F_SETLK或F_SETLKW)
ioctl
通常用于终端I/O操作