概要:
在对文件进行操作前,首先需要打开文件。
内核会为每个进程维护一个打开文件的列表,简称为文件表。
文件表是由一些非负整数进行索引,这些非负整数称之为文件描述符。

文件表,列表的每一项是一个打开文件的信息,包括指向该文件索引节点,内存拷贝的指针以及关联的元数据,如文件位置指针以及元数据,如文件位置指针和访问模式。
拥护空间和内核空间都使用文件描述符作为唯一 cookies:打开文件会返回文件描述符,之后对于文件的操作(读、写)都把文件描述符作为基本参数。
文件描述符使用C语言的int类型表示。
没有使用特殊类型来表示文件描述符显得有些怪异,但实际上,这种表示方式正是继承了UNIX的传统。
每个Linux进程可以打开的文件是有上限的。文件描述符的范围从0开始,到上限值减1,默认情况下1,上限值为1024,但是可以对它进行配置,最大为1048576。
因为负数是不合法的文件描述符,所以当函数出错不能返回有效的文件描述符时,通称会返回-1。
按照惯例每个进程都至少包含三个文件描述符:0(标准输入),1(标准输出),2(标准错误),除非显示关闭这些文件描述符。
LINUX C 标准库并没有直接的引用这些整数,而是提供了三个宏,分别是:STDIN_FILENO(通常是键盘)、STDOUT_FILENO(通常是终端屏幕)、STDERR_FILENO(通常是终端屏幕)。
用户可以重新定向这些文件描述符,甚至可以通过管道把一个程序的输出作为另一个程序的输入。shell正是通过这种方式实现重新定向。
文件描述符并非局限于访问普通文件,它也可以访问设备文件、管道文件、快速用户空间互斥(futexes)、先进先出缓存区(FIFOs)、套接字(socket)。
默认情况下子进程会维护一份父进程的文件表副本,且关闭文件不会影响父进程的文件表。子进程也可以和父进程共享文件表(类似于像线程之间的共享)
打开文件:
系统调用open():
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);
返回值:
文件描述符
参数说明:
name:需要打开的文件的绝对路径
flags:由一个或者多个标志位的按位或组合,它支持三种访问模式(O_RDONLY、O_WRONLY、O_RDWR)分别是(只读,只写,可读可写)
flags参数还可以与下面列出的这些值进行按位或运算:
O_APPEND:将文件以追加模式打开。文件打开文件位置指纹指针一般默认指向文件开头,该标志位会使每次写操作前文件指针指向末尾。
O_ASYNC:当指定的文件可读或者可写时,会生成一个信号(默认为SIGIO),该标志位只适用于FIFO、管道、socket、终端,不适用于普通文件。
O_CLOEXEC:在打开的文件上设置“执行时关闭”标志位。在执新的进程时,文件会自动关闭。设置该标志位可以省去调用fcntl()来设置标志位,且避免出现竞争。
O_CREAT:当参数name指向的文件不存在时,内核会自动创建。文件存在时该标志位无效。
O_DIRECT:打开文件用于直接I/O,每次写入的数据块大小要是文件所在块设备 block size的整数倍,写入数据在用户内存中的首地址要为512的整数倍(如果为块大小的整数倍就更没
有问题),所以不能使用数组保存数据,而要使用malloc来申请空间,该标志位会使得内核对I/O管理的的影响最小化。
O_DIRECTORY:如果,参数name指向的不是目录的话,open函数调用失败,该标志位被置位时,内部会调用opendir()。
O_EXCL:和标志位O_CREAT一起使用时才有意义,当name指向的文件已经存在的时候会导致open调用失败,防止创建文件出现竞争。
O_LARGEFILE:文件偏移使用64位整数来表示,可以支持大于2GB的文件。64位操作系统中打开文件会默认使用该标致位。
O_NOATIFE+:在读文件的时候不会更新该文件的最后访问时间。
O_NOCTTY:如果参数name指向的是终端设备,它不会成为这个进程的控制终端,即该进程当前没有控制终端。
O_NOFOLLOW:如果参数name指向的是一个符号链接,open函数会调用失败。
O_NONBLOCK:文件以非阻塞模式打开。
O_SYNC:打开文件用于同步I/O。在数据从物理上写到磁盘上之前,写操作都不会完成;由于读操作已经是同步的,因此该标志位对读操作无效。
O_TRUNC:如果文件存在,且是普通文件,且有写的权限,该标志位会把文件的长度截为0。
mode:一般是在创建新文件的时候才会使用,设置新建文件的权限。
例如:int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
**********************************************注解***********************************************
缓冲I/O:
缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
缓存 I/O 有以下这些优点:
1.缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备。
2.缓存 I/O 可以减少读盘的次数,从而提高性能。
对于读操作:当应用程序要去读取某块数据的时候,如果这块数据已经在页缓存中,那就返回之。而不需要经过硬盘的读取操作了。如果这块数据不在页缓存中,就需要从硬盘中读取数据到页缓存
对于写操作:应用程序会将数据先写到页缓存中,数据是否会被立即写到磁盘,这取决于所采用的写操作机制:
同步机制,数据会立即被写到磁盘中,直到数据写完,写接口才返回;
延迟机制:写接口立即返回,操作系统会定期地将页缓存中的数据刷到硬盘。所以这个机制会存在丢失数据的风险。想象下写接口返回的时候,页缓存的数据还没刷到硬盘,正好断电。对于应用程序来说,认为数据已经在硬盘中。
直接I/O:
凡是通过直接I/O方式进行数据传输,数据直接从用户态地址空间写入到磁盘中,直接跳过内核缓冲区。对于一些应用程序,例如:数据库。他们更倾向于自己的缓存机制,这样可以提供更好的缓冲机制提高数据库的读写性能。直接I/O写操作如上图所示。
图样表示:
缓冲I/O图 直接I/O图
新建文件的所有者:
确定新建文件的所有者:文件所有者的uid即创建该文件的进程的有效uid.
确定新建文件的用户组:System V 行为默认情况下使用的是创建进程的有效gid(大多数Linux系统使用) , BSD行为新建文件的用户组会被设置成其父目录的gid。系统调用
fchown()手动设置所属组。
新建文件的权限:

creat()函数:
int creat(const char *name , mode_t mode);
典型的creat()调用如下:
int fd = creat("filename" , 0644);
等效于:
int fd = open("filename" , O_WRONLY|O_CREAT|O_TRUNC , 0644);
在绝大多数的Linux架构中,creat()是一个系统调用。
返回值与错误码:
系统调用open()和creat()函数在成功时都会返回文件描述符,出错时会返回-1,并把errno设置为相应的错误值。
通过read()读文件:
#include <unistd.h>
ssize_t read (int fd , void *buf , size_t len);
每次调用read()函数,会从fd所指向的文件的当前偏移开始读取len字节到buf所指向的内存中。
返回值:
成功:返回写入buf中的字节数
失败:返回-1,并设置errno值
0:文件指针已经到了文件末尾,再读会返回0
异常情况:
调用该函数,但是没有数据读取,会一直阻塞,例如:进程间通信时,进程1读取文件txt的数据,进程2往txt里面写入数据,当txt没有数据时,进程1阻塞,直到进程2写入数据,进程一读到数据才继续运行。(文件不是以非阻塞模式打开)
0<调用返回值<len:1、读取过程中信号中断或者在读取过程中出错。2、可读数据大于0字节小于len字节。3、在读取到len字节之前文件位置指针已经移动到文件末尾......
调用返回-1,并把errno的设置为EINTR:表示在读取任何字节之前接受到信号
调用返回-1,并把errno设置为EAGAIN:文件以非阻塞模式打开,且里面没有数据可供读取
调用返回-1,并把errno设置为EINTR/EAGAIN:更严重的错误,重新执行读操作不会成功
使用例子:
unsigned long world;
ssize_t nr;
nr = read(fd , &world , sizeof(unsigned long));
该方式不够严谨,由于read()调用会有很多不同的结果,上述无法保证每次都会读入len个字节或者读到文件末尾(EOF)
优化如下:
ssize_t ret;
while(len != 0 && (ret = read(fd , buf , len) != 0){
if(ret == -1){
if(errno == EINTR)
continue;
perror("read");
break;
}
len -= ret;
buf += ret;
}
调用write()写:
#include
ssize_t write (int fd , const void *buf , size_t count);
从文件描述符fd指向的文件的当前位置开始,将buf中最多count个字节写入到文件中。不支持seek的文件(如字符设备)总是从起始位置开始写。
返回值:
执行成功:返回写入的字节数,并更新文件位置指正的位置。
执行失败:返回-1.
例如:
const char *buf = "hello world";
ssize_t nr;
nr = write(fd , buf , strlen(buf));
但是和read一样,这种方式过于简单粗暴,应该还需要检查各种部分写的情况。
优化:
unsigned long world = 1720;
size_t count;
ssize_t nr;
count = sizeof(world);
nr = write(fd , &world , count);
if(nr == -1)
......
else if(nr != count)
......
部分写(Partial Write):
write()调用基本不会发生部分写的情况,所以对于普通文件,不需要执行循环写的操作来保证写完所有字节。
但是对于一些特殊的文件类型,比如socket,它需要用循环来保证写完所有字节
ssize_t ret,nr;
while(len != 0 && (ret = write(fd , buf , len) != 0){
if(ret == -1){
if(errno == EINTR)
continue;
perror("write");
break;
}
len -= ret;
buf +=ret;
}
Write()行为:
write()调用返回时,内核已经把数据从提供的缓冲区拷贝到了内核缓冲区中,但是不保证数据已经写到了目标的物理地址中。
同步I/O:
写缓冲带来了极大地性能提升,但是有时候应用希望可以控制能够何时把数据写到磁盘上面,所以这时候就不得不牺牲一些性能来换来同步性。
fsync()和fdatasync():
为确保数据写入磁盘,最直接的方法就是系统调用fsync()
#include <unistd>
int fsync(int fd);
功能:
系统调用fsync()可以保证和文件描述符索志祥的文件相关的所有的脏数据都会写到磁盘上。文件描述符fd必须要以写的方式打开。
该调用会回写数据和元数据,如创建的时间戳以及索引节点的其他属性,再硬件驱动器确认数据和元数据已经全部写到磁盘之前不会返回。
对于包含写缓存的硬盘,fsync()无法知道数据是否已经真正写入到物理磁盘上,硬盘会报告数据已经写完,但实际上数据还在硬盘驱动器的写缓存上。
Linux还提供了系统调用fdatasync()
#include <unistd.h>
int fdatasync(int fd);
功能:
与fsync()功能类似,但区别于fdatesync()只会写入数据以及以后要访问文件所需要的元数据(文件大小等,不包括一些非基础元数据如:文件修改时间戳),所以fdatesync()执行速度更快。
返回值:
成功:0
失败:-1
sync():
sync()系统调用用来对磁盘上的所有缓冲区进行同步,虽然效率不高但是被广泛应用。
#incldue <unistd.h>
void sync(void);
该函数没有参数,也没有返回值,它总是成功返回,并确保所有的缓冲区----包括所有的数据和元数据----都能够写到磁盘上面去。
POSIX标准,没有要求sync()一直等待所有的缓冲区写到磁盘上才返回,只需要调用该函数,启动把所有缓冲区写到磁盘上即可。(建议多次调用sync()来保证数据完全写入)
LINUX而言,sync()一定是等待所有缓冲区写入才返回。(使用一次就可以了)
O_SYNC标志位:
系统调用open()时可以使用O_SYNC标志位,表示该文件的所有I/O操作都需要同步。
int fd;
fd = open(file , O_WRONLY | O_SYNC);
if(fd == -1)
{
perror("open");
return -1;
}
read()读请求总是同步操作的
write()写请求默认为非同步操作,使用O_SYNC标志位达到同步操作目的(可以理解为每次调用write()之后,隐式执行fsync(),然后才返回)
O_DSYNC和O_RSYNC标志位:
POSIX标准为open()调用定义了两个I/O相关的标志位,在Linux上这两个标志位的定义与O_SYNC一致,行为也一样。
O_DSYNC:该标志位指定每次写操作后,只同步普通的数据不同步元数据。
O_RSYNC:该标志位指定读请求和写请求之间的 同步,该标志位必须和O_SYNC和O_DSYYNC一起使用。
关闭文件:
#include
int close(int fd);
作用:取消当前进程的文件描述符fd与其关联的文件之间的映射。
返回值:成功0 失败-1
注意:关闭文件操作,并不一定保证文件的数据被写入到了磁盘,如果需要确保文件数据写入,可以使用上述所言的同步选项。
close()可能会使一个已经接触连接的文件最终从磁盘上删除。
错误码:EBADF:给定的文件描述符不合法
EIO:底层 I/O 错误
用lseek()查找:
一般情况下,I/O都是线性的,由于读写引发的隐式文件位置的更新都需要seek操作。
但是,有些应用要跳跃式的读取文件,需要随机访问而不是线性访问。
lseek()系统调用能够将文件描述符的位置指针设置为指定值,但是它只更新文件的位置,没有执行其他的操作,也不初始化任何的 I/O
#include <sys/types.h>
#include <unistd.h>
off_t lseek (int fd, off_t pos, int origin);
lseek的调用依赖于origin参数,该参数可以使以下任意值之一:
SEEK_CUR:将文件位置设置成当前值再加上pos个偏移值,pos可以是负值、0、正值,如果pos为0则返回当前文件位置。
SEEK_END:将文件位置设置成文件长度再加上pos个偏移值,pos可以是负值、0、正值,如果pos为0则设置为文件末尾。
SEEK_SET:将文件位置设置成pos值,pos可以是负值、0、正值,如果pos为0则设置为文件开始。
返回值:
成功----返回新的文件位置
错误----返回-1,并设置相应的errno值
在文件末尾后查找:
int ret;
ret = lseek(fd , 1688 , SEEK_END);
if(ret == (off_t)-1)
/*error*/
这种用法对于本身而言,查找到文件末尾之后没有什么意义----对该新的文件位置的读请求会返回EOF,但是,如果在该位置有个写请求,在文件的旧长度和新长度之间的空间会用0来填充。
空洞(hole):上述所说的,文件中被0所填充的空间
稀疏文件(sparse file):包含空洞的文件
错误码:
lseek()调用出错时会返回-1,并将errno值设置成以下四个值之一:
EBADF:给定的文件描述符没有指向任何打开的文件
EINVAL:origin的值不是设置成SEEK_CUR、SEEK_END、SEEK_SET,或者结果文件位置为负值
EOVERFLOW:结果文件偏移无法通过off_t表示,无法返回更新的值(只有在32位的体系结构上才会发生这种错误)
ESPIE:给出的文件描述符和不支持查找操作的文件关联(比如:管道、FIFO、socket)
定位读写:Linux提供了两种read()和write()系统调用的变体来替代lseek(),每次读写操作时都会把文件位置作为参数,在完成时,不会更新文件位置。
read()的变体是pread():
#define _XOPEN_SOURCE 500
#include <unistd.h>
ssize_t pread(int fd , void *buf , size_t count , off_t pos);
该调用会从文件描述符fd的pos位置开始读取count个字节到buf中去,不会更新位置指针
write()的变体是pwrite():
#define _XOPEN_SOURCE 500
#include <unistd.h>
ssize_t pwrite(int fd , void *buf , size_t count , off_t pos);
该调用会从文件描述符fd的pos位置开始,从buf中写count个字节到文件中,不会更新位置指针
注意:
pread()和pwrite()只适用于可查找的文件描述符,包括普通文件。
pread()和pwrite()调用的语义相当于在read()和write()调用之前执行lseek()调用,但任有以下三点区别:
1、pread()和pwrite()更易于使用,尤其对于一些复杂的操作,比如在文件中反向或随机查找定位
2、pread()和pwrite()调用在结束时,不会修改文件位置指针
3、pread()和pwrite()调用避免了在使用lseek()时会出现的竞争
************************************************lseek()的竞争*******************************************
由于线程共享文件表,当前文件位置保存在共享文件表中,可能发生如下情况:
进程中一个线程调用lseek()后,在执行读操作之前,另一个线程更新了文件位置。
就是说,当进程中存在多个线程操作同一个文件描述符时,lseek()有潜在的竞争可能。
这些竞争可能可以通过pread()和pwrite()来避免
错误码:
成功时----分别返回读或写的字节数
EOF----返回0,表示什么都没写
出错----返回-1,并设置相应的errno值(对于pread(),errno值包括read()和lseek()的errno值。对于pwrite(),errno值包括write()和lseek()的errno值)
文件截短:
Linux提供了两个系统调用支持文件长度截短,各个POSIX标准都(不同程度)定义了它们,分别是:
#include <unistd.h>
#include <sys/types.h>
int ftruncat (int fd , off_t len);
int truncat (const char *patf , off_t len);
这两个系统调用都将给定文件截短为参数len指定的长度。还可以把文件“截短”比原长度更大,多出长度用0填充。这两个操作都不会修改当前文件位置
ftruncat()系统调用在已经可写方式打开的文件描述符fd上操作
truncat()系统调用在path指定可写的可写文件上操作
返回值:
成功----返回0
错误----返回-1,并设置相应的errno值
I/O多路复用:
I/O多路复用支持应用同时在多个文件描述符上阻塞,并在其中某个可以读写时收到通知。
I/O多路复用在设计上遵循以下原则:
1、I/O多路复用:当任何一个文件描述符I/O就绪时进行通知
2、都不可用?在有可用的文件描述符之前一直处于睡眠状态
3、唤醒:当有某个文件描述符可以使用时就唤醒
4、处理所有I/O就绪的文件描述符,没有阻塞
5、返回第一步重新开始
Linux提供了三种I/O多路复用的方案:select 、poll、epoll( epoll为Linux特有的高级解决方案 )
select()和epoll()操作都是条件触发(当条件满足时发生一个I/O事件)
epoll()不仅支持条件触发,还支持边缘触发(当状态改变时发生一个I/O事件)
select():
#include <sys/select.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:
前三个参数为监视的文件的描述符,分别等待不同的事件
n:第一个参数n,其值等于所有集合中的文件描述符里面,值最大文件描述符的值的+1
readfds:readfds集,监视是否有数据可读(即某个读操作是否可以无阻塞完成)
writefds:writefds集,监视是否有某个写操作可以无阻塞完成
exceptfds:exceptfds集,监视是否出现异常,或者出现带外数据(这些场景只适用于socket)
timeout:指向timeval结构体的指针,如果该参数不是NULL,则会在设定时间返回,即使没有一个文件描述符号处于I/O就绪状态。
#include <sys/time.h>
struct timeval{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
成功返回时,每个集合都修改成只包含相应类型的I/O就绪的文件描述符号。(例如:readfds集总有文件描述符7和9。当调用返回时,如果描述符7还在集合中,它在I/O读取时不会阻塞。9不在集合中,那么他读取很有可能阻塞)
************************************************注意*******************************************
在不同的UNIX系统中,timeval结构体是未被定义的,因此,每次调用必须和文件描述符合集一起重新初始化。
实际上,当前的Linux版本会自动修改该参数,把值修改成剩余时间。因此如果超时设置是5s,在文件描述符可用之前就已经逝去了3s,那么在调用返回时,tv.tv_sec的值就是2
文件描述符集是静态建立的,所以一个文件描述符集里面的文件描述符数存在上限值,且存在最大文件描述符值,这两个值都由FD_SETSIZE设置。在Linux该值是1024
文件描述符合集修改:
FD_CLR(int fd, fd_set *set); //从指定集中删除一个文件描述符
FD_ISSET(int fd, fd_set *set); //检查一个文件描述符是否在给定集合中,在返回非0,不在返回0
FD_SET(int fd, fd_set *set); //向指定集中添加一个文件描述符
FD_ZERO(fd_set *set); //从给定集合中删除所有文件描述符
例如:
fd_set writefds;
FD_SET(fd , &writefds)
FD_CLR(fd , &writefds)
FD_ISSET(fd , &writefds)
FD_ZERO( &writefds);
返回值和错误码:
成功——返回三个集合中I/O就绪的总数,如果给出了超时设置,返回值可能为0
失败——返回-1,并设置errno值
errno值:
EBADF——某个集合中存在非法文件描述符
EINTR——等待时捕获了一个信号,可以重新发起调用
EINVAL——参数n是负数,或者设置的超时时间值非法
ENOMEM——没有足够的内存来完成该请求
用select()实现可移植的sleep功能:
在各个UNIX系统中,相比微秒级的sleep功能,对select()的实现更普遍。代码如下:
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
select (0 , NULL , NULL , NULL . &tv);
pselect():
#define _XOPEN_SOURCE 600
#include <sys/select.h>
int pselect(int n , fd_set *readfds , fd_set *writefds , fd_set *exceptfds , const struct timespec *timeout , const sigset_t *sigmask);
参数:
n:第一个参数n,其值等于所有集合中的文件描述符里面,值最大文件描述符的值的+1
readfds:readfds集,监视是否有数据可读(即某个读操作是否可以无阻塞完成)
writefds:writefds集,监视是否有某个写操作可以无阻塞完成
exceptfds:exceptfds集,监视是否出现异常,或者出现带外数据(这些场景只适用于socket)
timeout:指向timeval结构体的指针,如果该参数不是NULL,则会在设定时间返回,即使没有一个文件描述符号处于I/O就绪状态。
#include <sys/time.h>
struct timeval{
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
sigmask:函数阻塞等待时,阻塞的信号集合。
********************************************************注解************************************************************
#include <signal.h>
int sigprocmask(ubt how,const sigset_t*set,sigset_t *oldset);
how:用于指定信号修改的方式,可能选择有三种
SIG_BLOCK——将set所指向的信号集中包含的信号加到当前的信号掩码中。即信号掩码和set信号集进行或操作。
SIG_UNBLOCK——将set所指向的信号集中包含的信号从当前的信号掩码中删除。即信号掩码和set进行与操作。
SIG_SETMASK ——将set的值设定为新的进程信号掩码。即set对信号掩码进行了赋值操作。
set:为指向信号集的指针,在此专指新设的信号集,如果仅想读取现在的屏蔽值,可将其置为NULL。
oldset:也是指向信号集的指针,在此存放原来的信号集。可用来检测信号掩码中存在什么信号。
例如:
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int main()
{
sigset_t new,; //定义一个信号集
act.sa_flags = 0; //设置标志位为0
sigemptyset(&new); //信号集清空
sigaddset(&new, SIGINT); //将SIGINT信号加入信号集
return 0;
}
pselect()和select()区别:
1、pselect()的timeout参数使用了timespec结构体,而不是timeval结构体。timespec结构体使用秒和纳秒,相较于timeval更为的精准,而事实是,两个结构体在毫秒精度上就已经不可靠了。
2、pselect()不会修改timeout参数,所以在后续的调用不需要重新初始化该参数
3、select()没有sigmask参数,当这个参数为NULL时,pselect()和select()的行为是相同的
poll():
poll()系统调用是System V的I/O多路复用解决方案。它考虑了一些select()的不足。
#include <poll.h>
int poll (struct pollfd *fds , nfds_t nfds , int timeout);
参数:
timeout:单位毫秒,无论I/O是否就绪,poll()调用都会返回。负值,表示永远等待。0,poll()调用立即返回。
poll()使用了由 nfds 个 pollfd 结构体构成的数组,fds指向该数组。pollfd 结构体定义如下:
#include <poll.h>
struct pollfd{
int fd; //文件描述符
short events; //等待的事件,是该文件描述符要监视的事件掩码
short revents; //实际发生的事件,是该文件描述符的结果事件掩码,内核在返回时设置。
}
以下是合法的events值:
POLLIN:有数据可读
POLLRDNORM:有普通数据可读
POLLRDBAND:有优先数据可读
POLLPRI:有高优先级数据可读
POLLOUT:写操作不会阻塞
POLLWRNORM:写普通数据不会阻塞
POLLBAND:写优先数据不会阻塞
POLLMSG:有SIGPOLL消息可用
以下是revents可能返回的事件:
POLLER:给定的文件描述符出现错误
POLLHUP:给定的文件描述符有挂起事件
POLLNVAL:给定的文件描述符非法
*******************************************************注:**************************************************
event设置为:
POLLIN | POLLPRI —— 相当于select()的读事件
POLLOUT | POLLWRNORM —— 相当于select()的写事件
POLLRDNORM | POLLRDBAND —— 相当于POLLIN
POLLOUT —— 相当于POLLWRNORM
POLLIN | POLLOUT —— 监视文件是否可读可写
返回值和错误码:
成功——返回revents变量不为0的所有文件描述符个数;
没有任何事件发生且未超时——返回0
失败——返回-1,并设置相应的errno值
errno值:
EBADF:一个或多个结构体中存在非法文件描述符
EFAULT:fds 指针指向的地址超出了进程地址空间
EINTR:在请求事件发生前收到了一个信号,可以重新发起调用
EINVAL:nfds 参数超出了 RLIMIT_NOFILE 值
ENOMEM:可用内存不足,无法完成请求
poll()实例:
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#define TIMEOUT 5
int main(void)
{
struct polfd fds[2];
int ret;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[0].fd = STDOUT_FILENO;
fds[0].events = POLLOUT;
ret = poll(fds , 2 , TIMEOUT*1000);
if(fds[0].revents & POLLIN)
{
.......
}
if(fds[1].revents & POLLOUT)
{
.......
}
return 0;
}
ppoll():
ppoll为Linux特有的调用:
#define _GNU_SOURCE
#include <poll.h>
int ppoll(struct pollfd *fds , nfds_t nfds , const struct timespec *timeout , const sigset_t *sigmask);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)