监控文件描述符的六种方式(进程监控、select、poll、非阻塞轮询I/O、异步I/O、线程监控)

您所在的位置:网站首页 视频监控文件如何打开权限 监控文件描述符的六种方式(进程监控、select、poll、非阻塞轮询I/O、异步I/O、线程监控)

监控文件描述符的六种方式(进程监控、select、poll、非阻塞轮询I/O、异步I/O、线程监控)

#监控文件描述符的六种方式(进程监控、select、poll、非阻塞轮询I/O、异步I/O、线程监控)| 来源: 网络整理| 查看: 265

监控文件描述的六种方式 方法1:用进程来监控文件描述符方法2 :使用select来监视文件描述符select函数代码示例 方法3:使用poll来监视文件描述符poll函数代码示例 方法4: 带有轮询的无阻塞型I/O方法5: POSIX异步I/O方法6: 每个文件描述符都由一个独立的线程来监视 下面列出了POSIX中六种常见的监视多个文件描述符的输入的方法。

1.每个文件描述符都由一个单独的进程来监视 2.select 3.poll 4.带有轮询的无阻塞型I/O 5.POSIX异步I/O 6.每个文件描述符都由一个独立的线程来监视

方法1:用进程来监控文件描述符

监视多个文件描述符的一种方法是为每个描述符分别使用一个独立的进程。

使用独立的进程来监视文件描述符时,初始进程会创建出一个子进程来处理每个文件描述符。因为一旦被创建出来之后,子进程就不再共享任何变量了,所以这种方法适用于代表独立I/O流的描述符。如果对描述符的处理不是独立的,子进程可以用共享内存或消息传递机制来交换信息。

方法1:用进程来监控文件描述符 /* 为了流程清晰,省略了错误检查 */ #include #include #include #include #include #include int main(int argc, char *argv[]){ int childpid; int flag; int fd,fd1,fd2; char *file1; char *file2; char buf; file1 = "file1"; file2 = "file2"; if((fd1 = open(file1, O_RDWR | O_CREAT)) == -1) return -1; if((fd2 = open(file2, O_RDWR | O_CREAT)) == -1) return -1; if((childpid = fork()) == -1) return 1; if(childpid > 0) fd = fd1; else fd = fd2; for(;;){ if((flag = read(fd,&buf,1)) == -1) return 1; else if(flag > 0){ fprintf(stdout,"file discriptor %d is ready\n",fd); break; } } }

我们把这个程序编译成a.out之后,在终端1前台执行:

终端1上,由于进程(其实是前台进程组,因为fork了)是前台执行,控制权并没有返回给终端控制进程shell(也就是bash),最直观的就是我们平没有看到终端提示符: 在这里插入图片描述 我们另外打开一个终端2,ls一下看看结果: 在这里插入图片描述 可以看到file1和file2两个空文件被创建起来了。

文件是基于操作系统的文件系统的,不论哪个进程去创建,一般情况下所有进程都可以在系统中“看到”这些文件。

读程序1可以得知,这个程序用自己run起来的进程和它fork出的一个子进程分别监控file1和file2;一旦文件就绪,进程就提示后退出。

那么我们在终端2上给file1和file2这两个文件随意添加几个字节,看看终端1上会有什反应:

在终端2上通过vim给file2添加一些内容后,终端1上弹出了提示: 在这里插入图片描述 继续在终端2上通过vim给file1添加一些内容后,终端1上继续弹出了提示: 在这里插入图片描述 此时程序已经完全结束了,将控制权返回给了终端(出现了终端提示符)。 如此实现了“通过进程对文件描述符的监控”。

但有一个比较有意思的现象,如果我们先给file1添加内容,在给file2添加内容,情况还会一样吗?

在终端2上通过vim给file1添加一些内容后,终端1上弹出了提示,但同时也出现了终端提示符!这就说明前台进程组已经将终端控制权返回给了终端控制进程(shell进程);此时a.out中的子进程还在做对file2的监控(还没死掉),该子进程就变成了一个孤儿进程,若此时用ps -o comm,pid,ppid命令可以看到该子进程的父进程为1,被init进程统一收养了。 在这里插入图片描述

用独立的进程来监视文件描述符可能很有用,但是这些进程都有自己独立的地址空间,因此他们之间的交互很困难。

方法2 :使用select来监视文件描述符 select函数

select 函数用来够监视我们需要监视的文件描述符(读或写的文件集中的文件描述符)的状态变化情况。 并能通过返回的值告知我们。

select调用提供了一种在单个进程中监视多个文件描述符的方法。 它可以对三种可能的状况进行监视:

(1)可以无阻塞地进行的读操作 (2)可以无阻塞地进行的写操作 (3)有挂起的错误情况的文件描述符 老版本的UNIX在sys/time.h中定义了select函数,但POSIX标准现在使用的是sys/select.h。

1.函数原型:

#include int select( int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout );

在对参数进行讲解之前先介绍一个重要的结构 —— fd_set:

fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄。 fd_set集合可以通过下面的宏来进行人为来操作。

(1) FD_ZERO 用法:FD_ZERO(fd_set*); 用来清空fd_set集合,即让fd_set集合不再包含任何文件句柄。 (2) FD_SET 用法:FD_SET(int ,fd_set *); 用来将一个给定的文件描述符加入集合之中 (3) FD_CLR 用法:FD_CLR(int ,fd_set*); 用来将一个给定的文件描述符从集合中删除 (4) FD_ISSET 用法:FD_ISSET(int ,fd_set*); 检测fd在fdset集合中的状态是否变化,当检测到fd状态发生变化时返回真,否则,返回假(也可以认为集合中指定的文件描述符是否可以读写)。

2.参数讲解

#include int select( int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout ); (1)int nfds:集合中所有文件描述符的范围,其值要比“所有文件描述符的最大值”还要大1。 (2)fd_set *readfds:要进行监视的读文件集。 (3)fd_set *writefds :要进行监视的写文件集。 (4)fd_set *errorfds:用于监视异常数据,指定了为错误情况监视的文件描述符集。 /* * 这些fd_set类型的参数中的任何一个都能为NULL,在这种情况下,select不为相应的事件监视描述符。 */ (5)struct timeval* timeout:select的超时时间,它可以使select处于三种状态: /* * timeout这个参数可能的常用情况: *第一,若将NULL以形参传入,即不传入时间结构,就是 将select置于阻塞状态, 一定等到监视文件描述符集合中某个文件描述符发生变化为止; *第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数, 不管文件描述符是否有变化,都立刻返回继续执行, 文件无变化返回0,有变化返回一个正值; *第三,timeout的值大于0,这就是等待的超时时间, 即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了, 否则在超时后不管怎样一定返回。 */

3.返回值

成功返回时: (1)除了那些已经准备就绪的描述符之外,select清空每个readfas、 writefds和errorfas中所有描述符。 (2)select函数返网已准备就绪的文件描述符的数目。

如果不成功: select返回-1并设置errno,下表列出了select的实现必须检测的错误及相应的错误码。

errno原因EBADF个或多个文件描述符集指定了无效的文件描述符EINTR在超时或被选中的事作发生之前被select信号中断EINVAL指定了-个无效的超时间隔,或者nids小于0或大于FD_SETSTZE 代码示例

下面是一个一直阻塞到两个文件描述符中的一个准备就绪为止的函数。

//代码2 #include #include #include int monitor(int fd1,int ed2){ int maxfd; int nfds; fa_set readset; if ((fd1 = FD_SETSIZE) || \ (fa2 = FD_SETSIZE)){ errno 4 FINVAL; return -1; } maxfd =(fa1 > fd2) ? fd1 : fd2; FD_ZERO(&readset); FD_SET(fd1,&readset); FD_SET(fd2,Greadset); nfds = select(maxfd+1, &readset, NULL, NULL, NULL); if (nfds == -1) return -1; if(FD_ISSET(fd1,&readset)) return fd1; if(FD_ISSET(fd2,&readset)) return fd2; return -1; } /* 该函数一直保持阻塞状态,直到作为参数传递的两个文件描述符中至少有 一个准备好作读操作为止, 然后返网那个文件描述符。 如果两个都准备好了,就返回第一个文件描述符。 如果不成功,就返回-1。一直阻塞到两个文件描述符中的一个准备就绪为止的函数。 */ 方法3:使用poll来监视文件描述符 poll函数

功能:监视并等待多个文件描述符的属性变化

poll()和select() 系统调用的本质一样,poll() 的机制与 select() 在本质上没有多大差别: 都是通过轮询管理多个描述符并根据描述符的状态进行处理。

但是 poll() 没有最大文件描述符数量的限制(不过数量过大后性能仍会下降)。 poll() 和 select() 同样存在一个缺点 —— 包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间(不论这些文件描述符是否就绪),它的开销随着文件描述符数量的增加而线性增大。

1. 函数原型

int poll ( struct pollfd *fds, nfds_t nfds, int timeout );

在对poll的参数进行讲解之前先介绍一个重要的结构 —— struct pollfd:

fds结构体参数说明:

struct pollfd{ int fd; //文件描述符 short events; //等待的事件(请求的事件) short revents; //实际发生的事件 (返回的事件) };

(1)fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。 (2)events:指定监测fd的事件(输入、输出、错误),每一个事件有多个取值。 (3)revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。 注意:每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件。

fd是文件描述符值,events和revents是通过对代表各种事件的标志符进行逻辑或运算构建而成的。设置events来包含所要监视的事件;poll用已经发生的事件来填写revents。下表列出了这些事件:

事件标识符含义POLLTN无阻塞地读除了具有高优先级的数据之外的数据POLLRONORM无阻塞地读常规数据POLLKIBAND无阻塞地读具有优先级的数据POLLPRI无阻塞地读具有高优先级的数据POLLCUT无阻塞地写常规数据POLLWRNORM与POLLOUT相同POLLERR描述符中出现错误POLLHUP设备已经被断开POLLNVAL文件描述符无效

poll函数通过在revents中设置标志符POLLHUP、POLLERR和 POLLNVAL来反映相关条件的存在。不需要在events中对与这些标志符相关的比特位进行设置。

如果fd小于零,那么events字段被忽略,而revents被设置为零。

标准中没有说明应该如何处理文件结束。文件结束可以通过revents的标志符POLLHUP或者返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP地可能会被设置,因此,应该在错误检验之前处理正常的读操作。

2.参数解析

struct pollfd{ int fd; //文件描述符 short events; //等待的事件 short revents; //实际发生的事件 } (1)fds:指向一个结构体数组的第0个元素的指针, 每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件。 (2)nfds:用来指定第一个参数数组元素个数。 (3)timeout:指定等待的毫秒数,无论 I/O 是否准备好,poll() 都会返回。

3. 返回值

成功时: poll() 返回结构体中 revents 域不为 0 的文件描述符个数; 如果在超时前没有任何事件发生,poll()返回 0;

失败时: poll() 返回 -1,并设置 errno 为下列值之一:

EBADF:一个或多个结构体中指定的文件描述符无效。 EFAULT:fds 指针指向的地址超出进程的地址空间。 EINTR:请求的事件之前产生一个信号,调用可以重新发起。 EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。 ENOMEM:可用内存不足,无法完成请求。

代码示例

用poll来监视一个文件描述符数组的函数。

#include #include #include #include #include #define BUFSIZE 1024 void monitor_poll(int fd[], int numfds) char buf[BUFSIZE]; int bytesread; int i; int numnow = 0; int numready; struct pollfd *pollfd; for (i = 0; i = 0) numnow++; if ((pollfd = (void *)calloc(numfds, sizeof(struct pollfd))) == NULL) return; for(i = 0; i if ((pollfd + i)->revents){ if ((pollfd + i)->revents &(POLLRDNORM | POLLIN) ){ bytesread = read(fd[i],buf,BIPSIZE); numready--; if(bytesread > 0) fprintf(stdout,"file descriptor %d is ready, readout %d Bytes\n", fd[i],bytesread); else bytesread = -1; /* end of file */ }else if ((pollfd + i)->revents &(POLLERR | POLLHUP)) bytesread =-1; else /* descriptor not involved in this round */ bytesread = 0; if (bytesread == -1){ /* error occurred, remove descriptor */ close(fd[i]); (pollfd + i)->fd = -1; numnow--; } } } } for (i = 0; i if((r=read(fd, buf, BUFSIZE)) > 0){ fprintf(stdout,"file descriptor %d is ready!\n", fd); break; } else if(r == -1){ fprintf(stdout,"read failed in fd %d\n",fd); break; } } return NULL; } void monitorfd(int fd[], int numfds){ int error, i; pthread_t *tid; if((tid = (pthread_t *)calloc(numfds,sizeof(pthread_t))) == NULL) { perror("Failed to calloc"); return; } for(i = 0; i fprintf(stderr,"Failed to create thread %d:%s\n",i,strerror(error)); tid[i] = pthread_self(); } fprintf(stdout,"create thread %d\n",i); } for(i = 0; i int i; int numfds = 3; char *file; int fd[numfds]; for(i = 3; i perror("open error"); return -1; } } monitorfd(fd, numfds); return 0; }

在终端1上运行该代码编译连接后的可执行文件t,发现控制权一直没有返回终端,因为pthread_join使得主线程一直在等待回收用于监视文件描述符的线程资源: 在这里插入图片描述 此时我们另开一个终端2,并为程序打开的文件输入一些内容。 先给file3输入一些内容,我么可以发现终端1上有了相应的提示: 在这里插入图片描述 继续在终端2中向file4和file5添加内容,所有监视线程完成其任务,资源被回收后程序执行完毕,返还终端1的控制权,出现终端提示符: 在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3