1、文件描述符

每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件、与对应的文件绑定起来。

注:文件描述符是从 0 开始分配的。

2、open()函数

用于打开文件。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

参数 pathname: 字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息如果 pathname 是一个符号链接,会对其进行解引用。
参数 flags: 调用 open 函数时需要提供的标志, 包括文件访问模式标志以及其它文件相关标志:

标志 用途 说明
文件权限标志
O_RDONLY 以只读方式打开文件 这三个是文件访问权限标志,传入的flags 参数中必须要包含其中一种标志。
O_WRONLY 以只写方式打开文件
O_RDWR 以可读可写方式打开文件
其他标志
O_CREAT 如果 pathname 参数指向的文件不存在则创建此文件 使用此标志时,调用 open 函数需要传入第 3 个参数 mode,参数 mode 用于指定新建文件的访问权限。
O_EXCL 此标志一般结合 O_CREAT 标志一起使用,用于专门创建文件。在 flags 参数同时使用到了 O_CREAT 和O_EXCL 标志的情况下,如果 pathname 参数指向的文件已经存在,则 open 函数返回错误。

可以用于测试一个文件是否存在,如果不存在则创建此文件,如果存在则返回错误这使得测试和创建两者成为一个原子操作;保证进程是打开文件的创建者。

O_TRUNC 截取文件长度为0 调用 open 函数打开文件的时候会将文件原本的内容全部丢弃,文件大小变为 0;
O_APPEND 追加数据

如果 open 函数携带了 O_APPEND 标志,调用 open 函数打开文件,当每次使用write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾。

注1:该操作为原子操作

注2:O_APPEND标志并不会影响读文件, 即使使用了 O_APPEND标志,读文件位置偏移量默认情况下依然是文件头

注3:使用了 O_APPEND 标志,即使是通过 lseek 函数也是无法修改写文件时对应的位置偏移量。

O_NONBLOCK 设置非阻塞 O_NONBLOCK在读取不到数据时会返回-1,O_NDELAY在读取不到数据时会返回0

注:在GNU C中O_NDELAY只是为了与BSD的程序兼容,实际上是使用O_NONBLOCK作为宏定义

O_NDELAY
O_DIRECTORY 如果 pathname 参数指向的不是一个目录,则调用 open 失败 /
O_NOFOLLOW 如果 pathname 参数指向的是一个符号链接,将不对其进行解引用,直接返回错误。 不加此标志情况下,如果 pathname参数是一个符号链接,会对其进行解引用。
O_CLOEXEC 执行exec()时,之前通过open()打开的文件描述符会自动关闭 /
O_TMPFILE 标志用于创建一个临时文件 可以在一个目录下创建一个匿名文件,一旦关闭文件描述符,文件就自动删除。
O_DIRECT 针对某一文件或块设备执行直接 I/O 尽量减少进出该文件的I/O的缓存影响。 通常这将降低性能但在特殊情况下很有用,比如应用程序自己进行缓存时。 文件I/O直接执行到/从用户空间缓冲区。 O_DIRECT标志本身致力于同步传输数据,但不保证O_SYNC标志传输数据和必要的元数据。 为了保证同步I/O,除了O_DIRECT之外,还必须使用O_SYNC标志。
O_SYNC 使得每个 write()调用都会自动将文件内容数据和元数据刷新到磁盘设备中,其效果类似于在每个 write()调用之后调用 fsync()函数进行数据同步 /
O_DSYNC 其效果类似于在每个 write()调用之后调用 fdatasync()函数进行数据同步 /
O_ASYNC 设置异步I/O 在调用 open()时无法通过指定 O_ASYNC 标志来使能异步 I/O
O_NOATIME 当读取文件时,不要更新文件最后一次访问时间(inode中st_atime) 此标志用于索引或备份程序,在这些程序中使用它可以显著减少磁盘活动量。
O_NOCTTY 若设置该描述符,则该文件不可以被当成终端处理 如果pathname指向一个终端设备,它将不会成为进程的控制终端,即使进程没有终端。  

参数 mode:用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志时才有效。

说明
拥有者权限
S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
组成员权限
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
其他人权限
S_IRWXO 00007 others (not in group) have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
特殊权限
S_ISUID 04000  set-user-ID 位权限
S_ISGID 02000  set-group-ID 位权限
S_ISVTX 01000  Sticky 位权限
文件类型
S_IFSOCK 0140000 socket(套接字文件)
S_IFLNK 0120000 symbolic link(链接文件)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道文件)
文件类型字段位掩码
S_IFMT 0170000 文件类型字段位掩码

返回值: 成功将返回文件描述符,文件描述符是一个非负整数;失败将返回-1。

3、write()函数

向打开的文件写入数据。

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数 fd: 文件描述符。
参数 buf: 指定写入数据对应的缓冲区。
参数 count: 指定写入的字节数。
返回值: 如果成功将返回写入的字节数(0 表示未写入任何字节);如果写入出错,则返回-1。

4、read()函数

从打开的文件中读取数据。

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数 fd: 文件描述符。
参数 buf: 指定用于存储读取数据的缓冲区。
count: 指定需要读取的字节数。
返回值: 如果读取成功将返回读取到的字节数。

5、close()函数

关闭一个已经打开的文件。

#include <unistd.h>
int close(int fd);

参数fd: 文件描述符,需要关闭的文件所对应的文件描述符。
返回值: 如果成功返回 0,如果失败则返回-1。

6、lseek()函数

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数 fd: 文件描述符。
参数 offset: 偏移量,以字节为单位。
参数 whence: 用于定义参数 offset 偏移量对应的参考值 :

SEEK_SET 读写偏移量将指向 offset 字节位置处(从文件头部开始算)
SEEK_CUR 读写偏移量将指向当前位置偏移量 + offset 字节位置处, offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移
SEEK_END 读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。

返回值: 成功将返回从文件头部开始算起的位置偏移量(字节为单位), 也就是当前的读写位置; 发生错误将返回-1。

7、pread()函数

调用 pread相当于调用 lseek 后再调用 read(原子操作),不更新文件表中的当前位置偏移量。

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);

参数 fd: 文件描述符。
参数 buf: 指定用于存储读取数据的缓冲区。
参数 count: 指定需要读取的字节数。
参数 offset: 表示当前需要进行读或写的位置偏移量。
返回值: 如果读取成功将返回读取到的字节数。

8、pwrite()函数

调用 pwrite相当于调用 lseek 后再调用 write(原子操作),不更新文件表中的当前位置偏移量。

#include <unistd.h>
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

9、IO缓冲

9.1、fsync()函数

参数 fd 所指文件的内容数据和元数据写入磁盘。

#include <unistd.h>
int fsync(int fd);

参数 fd :表示文件描述符

返回值 :函数调用成功将返回 0,失败返回-1 并设置 errno 以指示错误原因。
注:元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数据信息,譬如文件大小、时间戳、权限等等信息,这里统称为文件的元数据,这些信息也是存储在磁盘设备中

9.2、fdatasync()函数

系统调用 fdatasync()与 fsync()类似,不同之处在于 fdatasync()仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据。

#include <unistd.h>
int fdatasync(int fd);

参数 fd :表示文件描述符

返回值 :函数调用成功将返回 0,失败返回-1 并设置 errno 以指示错误原因。

9.3、sync()函数

系统调用 sync()会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中。

#include <unistd.h>
void sync(void);

10、直接I/O

Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区,从用户空间直接将数据传递到文件或磁盘设备,把这种操作也称为直接 I/O(direct I/O)。
注:在有些情况下,这种操作通常是很有必要的,例如,某应用程序的作用是测试磁盘设备的读写速率。

10.1、使用直接 I/O的方法

在调用 open()函数打开文件时,指定O_DIRECT 和O_SYNC标志。

注:使用时需要定义_GNU_SOURCE 宏。

10.2、直接 I/O 的对齐限制

1)应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐(使用__attribute__(aligned (块大小的整数倍))进行对齐)
2)写文件时,文件的位置偏移量必须是块大小的整数倍;
3)写入到文件的数据大小必须是块大小的整数倍。

注:如果不满足以上任何一个要求,调用 write()均为以错误返回 Invalid argument。

10.3、查看分区块大小

10.3.1、stat

stat某个文件,查看IO Block字段。

10.3.2、tune2fs

1)使用 df命令查看系统的根文件系统所挂载的磁盘分区

 df -h 

2)使用 tune2fs 命令查看该分区块大小

tune2fs -l 分区 | grep "Block size"

11、inode

磁盘在进行分区、格式化的时候会将其分为两个区域,一个是数据区,用于存储文件中的数据;另一个是 inode 区,用于存放 inode table(inode 表), inode table 中存放的是 inode节点,不同的 inode 就可以表示不同的文件,每一个文件都必须对应一个 inode。(inode 实质上是一个结构体,这个结构体中有很多的元素,不同的元素记录了文件了不同信息,譬如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳、 文件类型、 文件数据存储的 block位置等信息)。

注:文件名并不是记录在 inode 中。

注:inode table 表本身也需要占用磁盘的存储空间。 每一个文件都有唯一的一个 inode, 每一个 inode 都有一个与之相对应的数字编号,通过这个数字编号就可以找到 inode table 中所对应的 inode。

11.1、查看inode号

ls -i
stat

11.2、通过inode查找文件的过程

1) 系统找到这个文件名所对应的 inode 编号;
2) 通过 inode 编号从 inode table 中找到对应的 inode 结构体;
3) 根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。

12、静态文件和动态文件

静态文件 文件在没有被打开的情况下一般都是存放在磁盘中的。譬如电脑硬盘、移动硬盘、 U 盘等外部存储设备。文件存放在磁盘文件系统中,并且以一种固定的形式进行存放。
动态文件 当调用 open 函数的时候,会将文件数据(文件内容)从磁盘等块设备读取到内存中,将文件数据在内存中进行维护,内存中的这份文件数据就把它称为动态文件。

12.1、操作动态文件的优势

磁盘、硬盘、 U 盘等存储设备基本都是块设备(块设备硬件本身有读写限制等特征)块设备是以块为单位进行读写(一个块包含多个扇区,而一个扇区包含多个字节)一个字节的改动也需要将该字节所在的 block 全部读取出来进行修改,修改完成之后再写入块设备中,所以导致对块设备的读写操作非常不灵活内存可以按字节为单位来操作,而且可以随机操作任意地址数据,非常地很灵活。所以对于操作系统来说,会先将磁盘中的静态文件读取到内存中进行缓存,读写操作都是针对这份动态文件,而不是直接去操作磁盘中的静态文件(因为内存的读写速率远比磁盘读写快得多)

13、PCB 数据结构体

PCB 数据结构体中有一个指针指向了文件描述符表(File descriptors), 文件描述符表中的每一个元素索引到对应的文件表(File table),文件表也是一个数据结构体,其中记录了很多文件相关的信息,譬如文件状态标志、 引用计数、 当前文件的读写偏移量以及 i-node 指针(指向该文件对应的 inode)等, 进程打开的所有文件对应的文件描述符都记录在文件描述符表中,每一个文件描述符都会指向一个对应的文件表。

14、errno变量

https://blog.csdn.net/qq_37932504/article/details/120441803

15、空洞文件

在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞(位于文件中但没有写过的字节都被设为 0)。如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。

注:空洞是否占用硬盘空间是由文件系统(file system)决定的。

15.1、空洞文件的应用场景:

使用迅雷下载文件时, 还未下载完成, 但该文件已经占据了全部文件大小的空间,其实这是个空洞文件; 下载时如果没有空洞文件, 多线程下载时文件就只能从一个地方写入, 这就不能发挥多线程的作用了; 如果有了空洞文件, 可以从不同的地址同时写入, 就达到了多线程的优势;

15.2、查看文件大小

ls 显示文件占用的逻辑大小

du 显示指定的文件所占用的磁盘空间。

16、多次 open同一个文件

1)一个进程内多次 open 打开同一个文件,会得到多个不同的文件描述符 fd,同理在关闭文件的时候也需要调用 close 依次关闭各个文件描述符。

2)一个进程内多次 open 打开同一个文件,在内存中并不会存在多份动态文件。

3)一个进程内多次 open 打开同一个文件,不同文件描述符所对应的读写位置偏移量是相互独立的。

17、复制文件描述符

文件描述符 可以进行复制, 复制成功之后可以得到一个新的文件描述符,使用新的文件描述符和旧的文件描述符都可以对文件进行 IO 操作,复制得到的文件描述符和旧的文件描述符拥有相同的权限。
复制得到的文件描述符与旧的文件描述符都指向了同一个文件表, 假设 fd1 为原文件描述符, fd2 为复制得到的文件描述符。

17.1、dup ()函数

用于复制文件描述符。

#include <unistd.h>
int dup(int oldfd);

参数 oldfd: 需要被复制的文件描述符。
返回值: 成功时将返回一个新的文件描述符,由操作系统分配,分配置原则遵循文件描述符分配原则;如果复制失败将返回-1,并且会设置 errno 值。

17.2、dup2()函数

dup 系统调用分配的文件描述符是由系统分配的,遵循文件描述符分配原则,并不能自己指定一个文件描述符,这是 dup 系统调用的一个缺陷;而 dup2 系统调用修复了这个缺陷,可以手动指定文件描述符,而不需要遵循文件描述符分配原则。

#include <unistd.h>
int dup2(int oldfd, int newfd);

参数 oldfd: 需要被复制的文件描述符。
参数 newfd: 指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)。
返回值: 成功时将返回一个新的文件描述符,也就是手动指定的文件描述符 newfd;如果复制失败将返回-1,并且会设置 errno 值。

18、文件共享

同一个文件(譬如磁盘上的同一个文件,对应同一个 inode) 被多个独立的读写体同时进行 IO 操作。

18.1、同一个进程中多次调用 open 函数打开同一个文件

18.2、不同进程中分别使用 open 函数打开同一个文件

18.3、同一个进程中通过 dup(dup2)函数对文件描述符进行复制

19、fcntl ()函数

fcntl()函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup、dup2 作用相同)、获取/设置文件描述符标志、获取/设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。

#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ )

参数 fd: 文件描述符。
参数 cmd: 操作命令。

1 复制文件描述符 cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC
2 获取/设置文件描述符标志 cmd=F_GETFD 或 cmd=F_SETFD
3 获取/设置文件状态标志 cmd=F_GETFL 或 cmd=F_SETFL
4 获取/设置异步 IO 所有权 cmd=F_GETOWN 或 cmd=F_SETOWN
5 获取/设置记录锁 cmd=F_GETLK 或 cmd=F_SETLK

参数 …: fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使用。
返回值: 执行失败情况下,返回-1,并且会设置 errno;执行成功的情况下,其返回值与 cmd(操作命令)有关,譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、cmd=F_GETFD(获取文件描述符标志)将返回文件描述符标志、 cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。

20、ioctl()函数

ioctl()可以认为是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或硬件外设。

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

参数 fd: 文件描述符。
参数 request: 。
参数 ...: 此函数是一个可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用。
返回值: 成功返回 0,失败返回-1。

21、截断文件

如果文件目前的大小大于参数 length 所指定的大小,则多余的数据将被丢失如果文件目前的大小小于参数 length 所指定的大小,则将其进行扩展, 对扩展部分进行读取将得到空字节"\0"。

21.1、truncate()函数

将普通文件截断为指定字节长度。

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);

参数 ptah:截取文件的路径

参数 length: 截取长度

返回值:调用成功返回 0,失败将返回-1,并设置 errno 以指示错误原因。

注:调用该函数并不会导致文件读写位置偏移量发生改变,所以截断之后一般需要重新设置文件当前的读写位置偏移量,以免由于之前所指向的位置已经不存在而发生错误(譬如文件长度变短了,文件当前所指向的读写位置已不存在)。

21.2、ftruncate()函数

将普通文件截断为指定字节长度。

#include <unistd.h>
#include <sys/types.h>int ftruncate(int fd, off_t length);

参数 fd:截取文件的文件描述符 fd

参数 length: 截取长度

返回值:调用成功返回 0,失败将返回-1,并设置 errno 以指示错误原因。

注:调用该函数并不会导致文件读写位置偏移量发生改变,所以截断之后一般需要重新设置文件当前的读写位置偏移量,以免由于之前所指向的位置已经不存在而发生错误(譬如文件长度变短了,文件当前所指向的读写位置已不存在)。

22、阻塞 I/O 和非阻塞 I/O

方式 优点 缺点
阻塞式 I/O CPU 的处理效率高,当自身条件不满足时,进入阻塞状态,交出CPU资源,将 CPU 资源让给别人使用; 无法实现并发读取(主要原因在于阻塞)
非阻塞式I/O 轮训方式,可以实现并发读取 CPU 使用率高

23、I/O 多路复用

I/O 多路复用技术是为了解决:在并发式 I/O 场景中进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的I/O 系统调用。

I/O 多路复用存在一个非常明显的特征:外部阻塞式, 内部监视多路 I/O。

常用的I/O 多路复用技术为select和poll。

注:在使用 select()或 poll()时需要注意一个问题,当监测到某一个或多个文件描述符成为就绪态时,需要执行相应的 I/O 操作,以清除该状态,否则该状态将会一直存在(那么下一次调用时,文件描述符已经处于就绪态了,将直接返回);

23.1、select

23.1.1、fd_set 数据类型

fd_set 数据类型是一个文件描述符的集合体。fd_set 数据类型是以位掩码的形式来实现的,Linux 提供了四个宏用于对 fd_set 类型对象进行操作:

#include <sys/select.h>void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
FD_CLR 将文件描述符 fd 从参数 set 所指向的集合中移除
FD_ISSET 如果文件描述符 fd 是参数 set 所指向的集合中的成员,则 FD_ISSET()返回 true,否则返回 false
FD_SET 将文件描述符 fd 添加到参数 set 所指向的集合中
FD_ZERO 将参数 set 所指向的集合初始化为空

注:文件描述符集合有一个最大容量限制,有常量 FD_SETSIZE 来决定

23.1.2、select()函数

系统调用 select()可用于执行 I/O 多路复用操作,select()函数将阻塞直至有以下事情发生:

1 readfds、 writefds 或 exceptfds 指定的文件描述符中至少有一个称为就绪态
2 该调用被信号处理函数中断
3 参数 timeout 中指定的时间上限已经超时

在调用 select()函数之后, select()函数内部会修改 readfds、 writefds、 exceptfds 这些集合,当 select()函数返回时,它们包含的就是已处于就绪态的文件描述符集合了。如果要在循环中重复调用 select(),必须保证每次都要重新初始化并设置 readfds、 writefds、 exceptfds 这些集合。

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数 nfds:表示最大文件描述符编号值加 1(即在readfds、writefds 和exceptfds这三个文件描述符集中找出最大描述符编号值,然后加 1)。

参数 readfds:是用来检测读是否就绪(是否可读)的文件描述符集合。如果对此事件不感兴趣,将其设置为 NULL。

参数 writefds:是用来检测写是否就绪(是否可写)的文件描述符集合。如果对此事件不感兴趣,将其设置为 NULL。

参数  exceptfds:用来检测异常情况是否发生的文件描述符集合。如果对此事件不感兴趣,将其设置为 NULL(异常情况并不是在文件描述符上出现了一些错误)。

注:如果readfds、writefds、exceptfds这三个参数都设置为 NULL,则可以将 select()当做为一个类似于 sleep()休眠的函数来使用,通过 select()函数的最后一个参数 timeout 来设置休眠时间

参数  timeout:设定 select()阻塞的时间上限,控制 select 的阻塞行为。可将timeout 参数设置为 NULL,表示 select()将会一直阻塞、直到某一个或多个文件描述符成为就绪态。

返回值:如果参数 timeout 指向的 struct timeval 结构体对象中的两个成员变量都为 0, 那么此时 select()函数不会阻塞,它只是简单地轮训指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。

-1

表示有错误发生,并且会设置 errno。

0 表示在任何文件描述符成为就绪态之前 select()调用已经超时, 在这种情况下, readfds,writefds 以及 exceptfds 所指向的文件描述符集合都会被清空。
>0

表示有一个或多个文件描述符已达到就绪态。返回值表示处于就绪态的文件描述符的个数。每个返回的文件描述符集合都需要检查,通过 FD_ISSET()宏进行检查。

注:如果同一个文件描述符在 readfds, writefds 以及 exceptfds 中同时被指定,且它多于多个 I/O 事件都处于就绪态的话,那么就会被统计多次。即 select()返回三个集合中被标记为就绪态的文件描述符的总数。

23.2、poll()函数

23.2.1、pollfd结构体

struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
成员 说明
fd 一个文件描述符, struct pollfd 结构体中的 events 和 revents 都是位掩码,调用者初始化events 来指定需要为文件描述符 fd 做检查的事件。当 poll()函数返回时, revents 变量由 poll()函数内部进行设置,用于说明文件描述符 fd 发生了哪些事件,可以对 revents 进行检查,判断文件描述符 fd 发生了什么事件。
events 设置需要关注的标志,多个标志通过位或运算符 | 组合起来,通过这些值告诉内核需要关心该文件描述符的哪些事件。
revents 返回时, revents 变量由内核设置。

23.2.2、事件标志

标志名 输入至 events 从 revents 得到结果 说明
与数据可读相关
POLLIN 有数据可以读取
POLLRDNORM 相等于 POLLIN
POLLRDBAND 可以读取优先级数据(Linux 上通常不使用)
POLLPRI 可读取高优先级数据
POLLRDHUP 对端套接字关闭
与可写数据相关
POLLOUT 可写入数据
POLLWRNORM 相等于 POLLOUT
POLLWRBAND 优先级数据可写入
在 revents 变量中用来返回有关文件描述符的附加信息(如果在 events 变量中指定了这三个标志,则会被忽略)
POLLERR 有错误发生
POLLHUP 出现挂断
POLLNVAL 文件描述符未打开
POLLMSG Linux 中不使用

注:如果对某个文件描述符上的事件不感兴趣,则可将 events 变量设置为 0;另外,将 fd 变量设置为文件描述符的负值(取文件描述符 fd 的相反数-fd),将导致对应的 events 变量被 poll()忽略,并且 revents变量将总是返回 0,这两种方法都可用来关闭对某个文件描述符的检查。

23.2.3、poll()函数

在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制。

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数 fds:指向一个 struct pollfd 类型的数组。

参数 nfds:指定了 fds 数组中的元素个数,数据类型 nfds_t 实际为无符号整形。

参数 timeout:

-1 poll()会一直阻塞(与 select()函数的 timeout 等于 NULL 相同),直到 fds数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回。
=0 poll()不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态
>0 表示设置 poll()函数阻塞时间的上限值,意味着 poll()函数最多阻塞 timeout毫秒,直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止

返回值:

-1 表示有错误发生,并且会设置 errno。
0 表示该调用在任意一个文件描述符成为就绪态之前就超时了
>0 一个正整数表示有一个或多个文件描述符处于就绪态了, 返回值表示 fds 数组中返回的 revents变量不为 0 的 struct pollfd 对象的数量。

23.3、epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。

传统的 selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄, epoll_create 函数原型如下:
注 :epoll 更多的是用在大规模的并发服务器上,因为在这种场合下 select 和 poll 并不适合。当
设计到的文件描述符(fd)比较少的时候就适合用 selcet 和 poll

int epoll_create(int size)

参数 size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
返回值: epoll 句柄,如果为-1 的话表示创建失败。

23.4、epoll_ctl()函数

epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事
件, epoll_ctl 函数原型如下所示:

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)

参数 epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。

参数 op: 表示要对 epfd(epoll 句柄)进行的操作,可以设置为:

EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。

参数 fd:要监视的文件描述符。
参数 event: 要监视的事件类型,为 epoll_event 结构体类型指针, epoll_event 结构体类型如下所示:

struct epoll_event {uint32_t events; /* epoll 事件 */epoll_data_t data; /* 用户数据 */
};

结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示:

EPOLLIN 有数据可以读取
EPOLLOUT 可以写数据。
EPOLLPRI 有紧急的数据需要读取。
EPOLLERR 指定的文件描述符发生错误。
EPOLLHUP 指定的文件描述符挂起。
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将fd 重新添加到 epoll 里面。

返回值: 0,成功; -1,失败,并且设置 errno 的值为相应的错误码。

23.5、epoll_wait()函数

一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生,类似 select 函
数。 epoll_wait 函数原型如下所示:

int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)

参数 epfd: 要等待的 epoll。
参数 events: 指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件。
参数 maxevents: events 数组大小,必须大于 0。
参数 timeout: 超时时间,单位为 ms。
返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量。

24、异步 I/O

在 I/O 多路复用中,进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中,当文件描述符上可以执行 I/O 操作时,进程可以请求内核为自己发送一个信号。 之后进程就可以执行任何其它的任务直到文件描述符可以执行 I/O 操作为止,此时内核会发送信号给进程。所以异步 I/O 通常也称为信号驱动 I/O。

24.1、设置异步I/O过程

1)指定 O_NONBLOCK 标志使能非阻塞 I/O

2)指定 O_ASYNC 标志使能异步 I/O

3)设置异步 I/O 事件的接收进程。

4)为内核发送的通知信号注册一个信号处理函数。

24.2、使能异步I/O

O_ASYNC 标志可用于使能文件描述符的异步 I/O 事件,当文件描述符可执行 I/O 操作时,内核会向异步 I/O 事件的接收进程发送 信号(默认为SIGIO信号) 。

注:在调用 open()时无法通过指定 O_ASYNC 标志来使能异步 I/O,但可以使用 fcntl()函数添加 O_ASYNC 标志使能异步 I/O。

int flag;
flag = fcntl(0, F_GETFL);
flag |= O_ASYNC;
fcntl(fd, F_SETFL, flag); 

24.3、设置异步 I/O 事件的接收进程

为文件描述符设置异步 I/O 事件的接收进程, 也就是设置异步 I/O 的所有者。 同样也是通过 fcntl()函数进行设置,操作命令 cmd 设置为 F_SETOWN,第三个参数传入接收进程的进程 I(PID), 通常将调用进程的 PID 传入, 譬如:

fcntl(fd, F_SETOWN, getpid());

24.4、设置异步I/O通知信号

SIGIO 是异步 I/O 通知的默认信号, 是一个非实时信号。也可以指定一个实时信号作为异步 I/O 通知信号。使用 fcntl()函数进行设置, 调用函数时将操作命令cmd 参数设置为 F_SETSIG(需要定义了_GNU_SOURCE宏之后才能使用F_SETSIG),第三个参数 arg 指定一个实时信号编号即可表示将该信号作为异步 I/O 通知信号,譬如:

fcntl(fd, F_SETSIG, SIGRTMIN);

注:如果第三个参数 arg 设置为 0(默认信号),则表示指定 SIGIO 信号作为异步 I/O 通知信号。

24.5、注册 SIGIO 信号的处理函数

通过 signal()或 sigaction()函数为 SIGIO 信号注册一个信号处理函数,当进程接收到内核发送过来的SIGIO 信号时,会执行该处理函数,应该在处理函数当中执行相应的 I/O 操作。

25、优化异步I/O

默认通知信号 SIGIO 可能存在丢失 SIGIO 信号是标准信号(非实时信号、不可靠信号),所以它不支持信号排队机制。
信号处理函数中无法判断文件描述符所发生的 I/O 事件
 
无法得知文件描述符发生了什么事件。 

在应用程序当中需要为实时信号注册信号处理函数时,使用 sigaction 函数进行注册,并为sa_flags 参数指定 SA_SIGINFO, 表示使用 sa_sigaction 指向的函数作为信号处理函数,而不使用 sa_handler 指向的函数。因为 sa_sigaction 指向的函数作为信号处理函数提供了更多的参数,可以获取到更多信息。

函数参数中包括一个 siginfo_t 指针,指向 siginfo_t 类型对象,当触发信号时该对象由内核构建。对于异步 I/O 事件而言,传递给信号处理函数的 siginfo_t 结构体中与之相关的字段如下

si_signo成员 引发处理函数被调用的信号。
si_fd成员 表示发生异步 I/O 事件的文件描述符
si_code成员 表示文件描述符 si_fd 发生了什么事件,读就绪态、写就绪态或者是异常事件等。
si_band成员 位掩码, 其中包含的值与系统调用 poll()中返回的 revents 字段中的值相同。

si_code 中可能出现的值与 si_band 中的位掩码有着一一对应关系。

si_code si_band 掩码值 描述/说明
POLL_IN POLLIN | POLLRDNORM 可读取数据
POLL_OUT POLLOUT | POLLWRNORM | POLLWRBAND 可写入数据
POLL_MSG POLLIN | POLLRDNORM | POLLMSG 不使用
POLL_ERR POLLERR I/O 错误
POLL_PRI POLLPRI | POLLRDNORM 可读取高优先级数据
POLL_HUP POLLHUP | POLLERR 出现宕机

在信号处理函数中通过对比 siginfo_t 结构体的 si_code 变量来检查文件描述符发生了什么事件,以采取相应的 I/O 操作。

26、多路I/O复用与异步I/O的比较

与 select()和 poll()相比,异步 I/O 能够提供显著的性能优势。原因在于:对于异步 I/O,内核可以“记住”要检查的文件描述符,且仅当这些文件描述符上可执行 I/O 操作时,内核才会向应用程序发送信号。而对于 select()或 poll()函数来说,内部实现原理其实是通过轮训的方式来检查多个文件描述符是否可执行 I/O 操作,所以,当需要检查的文件描述符数量较多时,随之也将会消耗大量的 CPU 资源来实现轮训检查操作。 当需要检查的文件描述符并不是很多时,使用 select()或 poll()是一种非常不错的方案。

注:当需要检查大量文件描述符时,可以使用 epoll 解决 select()或 poll()性能低的问题。在性能表现上, epoll 与异步 I/O 方式相似,但是 epoll 有一些胜过异步 I/O 的优点。不管是异步 I/O、还是 epoll,在需要检查大量文件描述符的应用程序当中,在这种情况下,它们的性能相比于 select()或 poll()有着显著的优势!

linux C编程6-文件IO相关推荐

  1. Linux系统编程之文件IO

    Linux系统编程之文件IO 文件IO第一天 一.标准IO基本概念 1.1c语言函数 (c库函数) 调用实现某一个功能,(API) 1.2系统调用:内核中的程序接口,应用程序和硬件设备之间的中间层 1 ...

  2. Linux系统编程【文件IO、进程、进程间通信、信号、线程、互斥】

    linux系统编程 个人通过学习,手打了一份48000字的Linux系统编程的笔记,包含了[文件IO.进程.进程间通信.信号.多线程.互斥]等知识点,并给出了大量的代码案例对每个重要的知识点进行了代码 ...

  3. 【Linux系统编程】文件IO操作

    文件描述符 在 Linux 的世界里,一切设备皆文件.我们可以系统调用中 I/O 的函数(I:input,输入:O:output,输出),对文件进行相应的操作( open().close().writ ...

  4. linux系统io编程,Linux系统编程(1) —— 文件IO

    本文主要对Linux系统中文件I/O的基本操作进行说明. 在Linux系统编程中,对文件进行处理的流程,通常是: 打开文件 读写文件 关闭文件 Linux内核对每一个进程维护一个打开的文件列表, 该文 ...

  5. linux系统简单操作代码,Linux系统编程:简单文件IO操作(示例代码)

    使用Linux的文件API,经常看见一个东西,叫做文件描述符. 什么是文件描述符? (1)文件描述符其实实质是一个数字,这个数字在一个进程中表示一个特定的含义,当我们open打开一个文件时,操作系统在 ...

  6. 嵌入式学习之linux系统编程---5 文件IO之write函数

    1.write函数的函数原型 #include <unistd.h> ssize_t write(int fd,const void *buf,size_t count); ssize_t ...

  7. 【Linux系统编程】文件描述符的复制dup()和dup2()

    00. 目录 文章目录 00. 目录 01. 文件描述符复制概述 02. 常用函数 2.1 dup函数 2.2 dup2函数 03. 案例实战 3.1 dup示例 3.2 dup2示例 04. 附录 ...

  8. linux系统编程之文件与I/O(六):fcntl 函数与文件锁

    2013-05-14 11:26 8290人阅读 评论(2) 收藏 举报  分类: linux系统编程(19)  版权声明:本文为博主原创文章,未经博主允许不得转载. 一.fcntl函数 功能:操纵文 ...

  9. 嵌入式Linux基础学习笔记-文件IO编程-I/O多路复用

    实验内容:多路复用-I/O操作及阻塞 编程实现文件描述符集合的监听 multiplex_poll.c文件编写: /* multiplex_poll.c */ #include <fcntl.h& ...

  10. Linux系统编程 | 01 -文件操作

    一.文件操作方法 linux中有两种方法可以操作文件:系统调用和c库函数. 1. 什么是系统调用? 由操作系统实现并提供给外部应用程序的编程接口(API),是应用程序同系统之间数据交互的桥梁. C标准 ...

最新文章

  1. swift_021(Swift 的方法)
  2. php桶排序,PHP实现桶排序算法
  3. docker 删除所有未启动的容器_Docker 镜像容器常用操作(让我们用 docker 溜得飞起)...
  4. hudson linux节点,Linux 环境下搭建 Jenkins(Hudson)平台
  5. 20211126 为什么转动惯量矩阵是正定的?
  6. Scala模式匹配:变量声明中的模式匹配
  7. Dalivik垃圾回收收机制Cocurrent GC简介
  8. DB Intro - MongoDB User
  9. java 文件写_java写入文件的几种方法分享
  10. eclipse 导入maven项目_手把手的Spring Boot Web 项目教程,Hello Spring Boot
  11. Linux服务器之间传输文件 scp命令
  12. iOS 循环引用 委托 (实例说明)
  13. 【搜索-剪枝-偏难】PAT-天梯赛-L3-015. 球队“食物链”
  14. sun的java认证考试_Sun Java认证考试科目
  15. Android6.0 camera个数探测
  16. VMware虚拟机win7安装教程
  17. STM32CubeIDE USB Audio声卡 WM8978 + I2S
  18. 微信提现显示服务器异常,微信零钱提现为什么显示提示交易异常 解决办法是什么...
  19. openerp换mysql_OpenERP 源码变更后数据库升级
  20. android root后手机文件管理器,手机root后用什么文件管理器

热门文章

  1. 科普 | 空调、地暖、风冷热泵、新风机组等设备是如何运行的
  2. 伺服和步进电机运行产生位置偏差的原因分析?
  3. GTASA圣安地列斯有时在屏幕上出现方块形的烟雾|方块形粒子
  4. 【Hackme CTF】Misc--corgi can fly
  5. 《见识》:你能走多远,取决于见识
  6. CC(标准)版D碟收藏指南(五)
  7. 关于IDEA可持续使用的帖子!!!
  8. python亚马逊销量预测_亚马逊卖家如何预测一款产品的销量?
  9. matlab:杨氏双缝干涉
  10. 杨氏双缝干涉 matlab仿真,基于MATLAB的杨氏双缝干涉实验仿真