网络推荐

推荐给好友 上一篇 | 下一篇

第六章: 高级 I/O

翻译雨丝风片@chinaunix.netBSD爱好者乐园e@,U$x A6_

6.1 高级I/O和进程资源
SqePRD8VBSD爱好者乐园(S v8TH!h}5R8rY
正如我们在前面章节中看到的,程序可以同时打开多个文件描述符。这些文件描述符并不一定就是文件,还可以是fifo、pipe或者socket。于是,如何复用这些打开的描述符就很重要了。例如,考虑一个简单的邮件阅读程序,比如pine。它显然应当允许用户在读写email的同时也能去检查是否有新邮件。这就意味着在任一给定时刻都至少能够接收两个来源的输入:一个来源是用户,另一个是用来检查新邮件的描述符。处理描述符的复用是个复杂的问题。一种方法是把所有打开的描述符都标记为非阻塞的(O_NONBLOCK),然后在它们之中循环,直到找到一个可以进行I/O操作的描述符为止。这种方法的问题是程序会一直在循环,如果长时间内没有I/O可用,进程就会一直占据CPU。当有多个进程在一组很少的描述符上循环时,你的CPU的负载就会恶化。BSD爱好者乐园;kV3AidV-F
BSD爱好者乐园E E2K E;eN
另一种方法就是设置信号处理器去捕获I/O变为可用的事件,然后就让进程进入休眠状态。如果你只打开了少量的描述符,而且并不经常请求I/O的话,这种方法从理论上看倒是不错。由于进程已经休眠,就不会再占用CPU,仅当I/O可用时它才恢复执行。然而,这种方法的问题在于信号处理的开销有点大。比如一个web服务器,每分钟收到100个请求,那就几乎一直都在捕获信号。每秒钟捕获上百个信号的开销是相当大的,不单是进程,对于内核发送信号的开销而言也是一样的。
TG.P ~a2l2iQ
[2c5C)ga9I FV到目前为止,我们看到的两种选择都有限制,效率也不高,它们需要解决的共同问题就是进程需要知道I/O究竟什么时候能用?然而,这个信息实际上只有内核才能事先知道,因为是内核在最终处理系统中的所有打开的描述符。例如,当一个进程通过fifo向另一个进程发送数据的时候,发送进程会调用write,这是一个系统调用,因此会进入内核。在发送方的write系统调用执行完毕之前接收方对此是一无所知的。于是就引出了一个更好的复用文件描述符的方法:由内核来替进程管理描述符。换句话说,就是把一个打开描述符的链表发送给内核,然后等待,直到内核发现某个或多个描述符已经准备好了或者已经超时了为止。
rG E(T9B PBSD爱好者乐园/h3e7c's?xJ1`
这就是select()、poll()和kqueue()接口采用的方法。通过这些接口,内核就会管理文件描述符,当I/O可用时就去唤醒进程。这些接口巧妙地处理了上述问题。进程不必再在打开的文件描述符中循环,也不必再去设置信号了。但进程在使用这些函数的时候还是会产生一点小问题。这是因为I/O操作是在从这些接口返回之后才去执行的。所以它至少需要两个系统调用才能完成其操作。例如,你的程序有两个用于读的描述符。你对它们使用select,然后等待它们直至有数据可读。这就需要进程首先调用select,在select返回之后,就对该描述符调用read。更妙的是,你还可以对所有打开的描述符执行一个整体的read。一旦其中有某个描述符准备好读之后,read就会返回,并把数据放在缓冲区中,同时还会给出一个标识,用来指示这个数据是从哪个描述符读进来的。BSD爱好者乐园 xF*I|h:v
BSD爱好者乐园y d7o,W C#w*i
6.2 select
/xOi/Z _*?A6`V$Z
!Hg#~Rt8{%\我首先要讲的接口是select()。格式如下:BSD爱好者乐园"I1D&A4qAL+o-_
BSD爱好者乐园 ~(T,O EC&[)E1]E
 
int  select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,  struct timeval *timeout);

*[:^j-@E0l:L&W传给select的第一个参数已经造成了多年的混乱。nfds参数的正确用法是把它设成文件描述符的最大值加1。换句话说,如果你有一组文件描述符{0,1,8},nfds参数就应当被设置成9,因为你的描述符的最大值为8。有些人错误地以为这个参数的意思是文件描述符的总数加1,对于我们的例子而言就是4。记住,一个文件描述符只是一个整数而已,所以你的程序就需要指出你所想要在其上select的最大的描述符值。
j.ic?lLu8IWX2kBSD爱好者乐园u"_\cV H2?)c mc~
select接下来会按顺序针对所有尚未完成的读、写以及异常条件检查其余的三个参数,readfds、writefds和exceptfds。(详细信息请参见man(2) select)。注意,如果readfds、writefds和execptfds中没有设置描述符,那么传给select的对应参数应当被设置成NULL。BSD爱好者乐园/?RUK~s0d!vg

!Bqu]1E/X,h V;Zreadfds、writefds和execptfds参数通过以下4个宏进行设置。
?;x/~t}~2y
2omO'i!qe jYFD_ZERO(&fdset);
Gq3R&}O0uo6SBSD爱好者乐园3s-g i0fI,rqUx5}
FD_ZERO宏用来对指定的描述符集合中的bit进行清零。有一点需要特别注意:只要使用select,就应当调用这个宏;否则select的行为将是不可预知的。BSD爱好者乐园yFhHpS'@E

-@ Utg3|c"x:_qFD_SET(fd, &fdset);BSD爱好者乐园-Z8c2ghE4p
BSD爱好者乐园%}s:Y'xbS
FD_SET宏用于向一组激活的描述符中添加一个描述符。BSD爱好者乐园1?.\X/L&b4Qn

l._x!s;r#@FD_CLR(fd, &fdset);BSD爱好者乐园C,J#}%f `r(RC
BSD爱好者乐园\lo/Tu$W5N[9{abe
FD_CLR宏用于从一组激活的描述符中删除一个描述符。
+A:DcO.ank(vx!mp
;nbem ?9^4wFD_ISSET(fd, &fdset);BSD爱好者乐园m#E:mibQO

}F,s&l [a wFD_ISSET宏是在select返回之后使用的,用于测试某个描述符是否已准备好进行I/O操作。
+dRY(fu(K)v9zBSD爱好者乐园k `b)a ~HM_ X
select的最后的参数是一个超时值。如果超时值被设置为NULL,则对select的调用将以不确定的方式被阻塞,直至某个操作已准备好为止。如果你需要一个确定的超时时间,那么超时值就得是一个非空的timeval结构体。timeval结构体如下:BSD爱好者乐园F|-E'C,b^5L4SL4| PMO

y'|t:tXQ Q
 
  struct timeval {BSD爱好者乐园@I J5|?A!l
      long    tv_sec;         /* seconds */
+lP5u&?w1k      long    tv_usec;        /* microseconds */
P {6J%S._?/GYl3};L%m  };
BSD爱好者乐园5w.N2[0o9|&`-KY
如果select调用成功,将返回准备好的描述符的数目。如果select因为超时而返回,则返回值为0。如果有错误发生,则返回-1,同时会相应地设置errno。BSD爱好者乐园"p!O"n+zc9d8f+K

7Y9w.s5Se C#_6.3 poll
?h$?5JOEw!GBSD爱好者乐园8wm/ijf J
我们在这里对I/O的讨论主要是针对BSD的。System V支持一种特殊类型的I/O,即所谓的STREAMS。和socket一样,STREAMS也具有优先级属性,这种属性有时也被成为数据带。数据带可用来给STREAMS中的特定数据设置较高的优先级。BSD最初并不支持这一特性,不过有些人添加了System V仿真功能,可以对某些类型提供支持。由于我们并不关注System V,因此我们只会引用数据带或数据优先级带的概念。详细信息请参见System V STREAMS。BSD爱好者乐园Ql4E$M&g3DW

i`T bf,E7T:\poll函数和select很相似:
!LiV O!` VBSD爱好者乐园wd-i,j;f}A;cx
 
  int  poll(struct pollfd *fds, unsigned int nfds, int timeout);

om4A gG(q5Jod和原产于BSD的select不同,poll是由System V Unix创建的,在早期的BSD版本中并不支持它。目前主流BSD系统中都已经支持poll了。
5c;V ^8Odbb;|_4Zs0Z
8_ B\1a],p!Z g和select相似,poll也是在一组给定的文件描述符上进行复用。在指定这些描述符的时候,你必须使用一个结构体数组,其中每个结构体代表一个文件描述符。和select相比,poll的好处就是你可以判断一些很罕见的条件,而select则无法做到。这些条件是POLLERR、POLLHUP和POLLNVAL,我们稍后讨论。尽管对于选择select还是poll的问题已经有了相当多的讨论,但这在很大程度上还是取决于你的个人爱好。poll所使用的结构体是pollfd结构体,如下:
o B^,ZQU z NuBSD爱好者乐园 t NO M wJE _|
 
  struct pollfd {BSD爱好者乐园z b5s!xA~ _
      int     fd;             /* which file descriptor to poll */BSD爱好者乐园2sX.Q'F+mA9D4v{X!p
      short   events;         /* events we are interested in */BSD爱好者乐园4mW?}9ifv8A#J
      short   revents;        /* events found on return */
x} ?P/BP|  };

&k3Ys]"O.j QfdBSD爱好者乐园:o?;Li*n!Z T

7wR3oXgKdfd成员用于指定你想要poll的文件描述符。如果你想删除一个描述符,那就把那个描述符的fd成员设置成-1。通过这种方法,你可以避免对整个数组进行混洗,同时还可以清除revents成员中列出的所有事件。BSD爱好者乐园H Mi K6c:B ZK ~

0O'O9gu ^events, reventsBSD爱好者乐园 P6MY[,iQ ga]&r
BSD爱好者乐园"Hy-Ca8?4u Z
events成员是一个bit掩码,用于指定针对指定描述符所关心的事件。revents成员也是一个bit掩码,但它的值是由poll设置的,用于记录在指定描述符上发生的事件。这些事件的定义如下:
B3AccP*v
g$C:U,Q%xb-L
 
  #define POLLIN          0x0001

)Q] ?];qVPOLLIN事件表明你的程序将选择该描述符上的可读数据事件。注意,此处的数据不包括高优先级数据,比如socket上的带外数据。
\ F~2pF{)CC;s
]+Da4Qz(a-h
 
  #define POLLPRI         0x0002

:Tu!_*E&uR}n?POLLPRI事件表明你的程序准备选择该描述符上的任何高优先级事件。BSD爱好者乐园x9XP \M ps
 
  #define POLLOUT         0x0004BSD爱好者乐园Z}c)y?|
  #define POLLWRNORM      POLLOUT

IUS yh uYX7fPOLLOUT和POLLWRNOMR事件表明你的程序想知道什么时候可以对一个描述符执行写操作了。在FreeBSD和OpenBSD上这两个事件是相同的;你可以在你的系统头文件(/usr/include/poll.h)中查证这一点。从技术角度来说,它们之间的区别在于POLLWRNOMR仅当数据优先带等于0的时候才去检测是否可以进行写操作。
s:CsM,? vBSD爱好者乐园jj2JM1w]a)}
 
  #define POLLRDNORM      0x0040

@7dVG'DI/AnPOLLRDNORM事件表明你的程序准备选择该描述符上的常规数据。注意,在某些系统上,这个事件指定的操作和POLLIN完全一样。但在NetBSD和FreeBSD上,这个事件和POLLIN并不相同。同样,请去查看你的系统头文件(/usr/include/poll.h)。严格地说,POLLRDNORM仅当数据优先带等于0的时候采取检测是否可以进行读操作。BSD爱好者乐园f3q'ROy*q(F _
BSD爱好者乐园P%iw5R3N2hIv|
 
  #define POLLRDBAND      0x0080
BSD爱好者乐园^.@K.Z@$|U
POLLRDBAND事件表明你的程序想知道什么时候能够以一个非0的数据带值从该描述符读数据。
`&p'^b+w2Vt-LBSD爱好者乐园\I/fB5q C1D`
 
  #define POLLWRBAND      0x0100

9N#`!N%VvdT-Q4YPOLLWRBAND事件表明你的程序想知道什么时候能够以一个非0的数据带值向该描述符写数据。
"oJs6iNKz
!u3D3QpRt1KE专用于FreeBSD的选项BSD爱好者乐园n_3h l0g6L

H5[bO9DG下面的选项是专用于FreeBSD的,知道的和使用的人都不是太多。但它们还是值得提一下,因为它们可以提供更多的灵活性。这些都是新的选项,poll并不保证能够检测这些条件,而且它们只能用于UFS文件系统。如果你的程序需要检测这些类型的事件,那最好使用kqueue接口,我们将在稍后介绍。BSD爱好者乐园-J%Uoh?DJ5Ni
BSD爱好者乐园x&n*ac j(Y]
 
  #define POLLEXTEND      0x0200

? jj?;@*@{如果文件已经被执行,则设置POLLEXTEND事件。BSD爱好者乐园OW$N,a_H Pt
BSD爱好者乐园;J%lYPt4F$U
 
  #define POLLATTRIB      0x0400

n)@ Mv3T#V!zD5[ Wdd如果有任一文件属性发生改变,则设置POLLATTIB事件。
p r4?PS"\ C2X3oBSD爱好者乐园bi|+V\"O1^D1Rm
 
  #define POLLNLINK       0x0800
BSD爱好者乐园w&EwH vrs S
如果文件被重命名、删除或解除链接,则设置POLLNLINK事件。BSD爱好者乐园7W6a(H P@5|i
 
  #define POLLWRITE       0x1000
BSD爱好者乐园a@Uw}
如果文件内容被修改,则设置POLLWRITE事件。BSD爱好者乐园,E _W2h o U.J8W
BSD爱好者乐园D jaoSr5i
下面的事件并不是pollfd events成员的有效标志,poll也将忽略它们。它们是在pollfd revents中返回的,用于表明发生了某个事件。
2fG#dm;] q:O:L3YaBSD爱好者乐园&C%^~X#E
 
  #define POLLERR         0x0008

)G:d"_CkPOLLERR事件表明有错误发生。
4`9AT\re\
| Zw \x1_k$G ye
 
  #define POLLHUP         0x0010

[5l'Pt EK/HPOLLHUP表明在对应的STREAMS上发生了挂起事件。POLLHUP和POLLOUT是互斥事件,因为一个发生了挂起的STREAMS就不再是可写的了。BSD爱好者乐园hRq&G e|)iBlF-C
BSD爱好者乐园Vd0v!HRJ
 
  #define POLLNVAL        0x0020

?_$["v*p[1e:Cvg5WLPOLLNVAL表明对poll的请求是无效的。
P)WZW,X/@7I4NBSD爱好者乐园j8pzLP:Z~+ww[C
poll的最后一个参数是超时值。可以通过这个参数告诉poll一个以微秒为单位的超时值。如果把超时值设置为-1,poll就会阻塞,直至所请求的事件发生为止。如果超时值设置为0,则poll将立即返回。
HD-~Og&V%|4Y b Z9ZBSD爱好者乐园|JP;`7m#S
如果对poll的调用成功,则返回一个正整数。这个正整数的值表示有多少个描述符发生了事件。如果超时,poll将返回0。如果有错误发生,poll则会返回-1。BSD爱好者乐园Vq p6u$^m
BSD爱好者乐园rV6U zMA}!j
6.4 kqueue
)fj R-_]SD
6Xw3I-EA`到目前为止,poll和select已经是相当不错的复用文件描述符的方法了。但为了使用这两个函数,你需要创建一个描述符的链表,然后把它们发送给内核,在返回的时候又要再次查看这个链表。这看上去有点效率低下。一个更好一些的模型是把描述符链表交给内核,然后就等待。一旦有某个或多个事件发生,内核就把一个只包含有发生了事件的描述符的链表通知给进程,由此避免了每次函数返回的时候都要去遍历整个链表。尽管对于只打开了几个描述符的进程而言这点改进算不得什么,但对于那些打开了几千个文件描述符的程序来说,这种性能改进就相当显著了。这就是kqueue诞生背后的主要目的。同时,设计者还希望进程能够检测更多类型的事件,比如文件修改、文件删除、信号交付或者子进程退出,并提供一个包含了其它任务的灵活的函数调用。处理信号、复用文件描述符、以及等待子进程等操作都可以封装到这个单一的kqueue接口中,因为它们都是在等待某个事件的发生。
vjfE2gBSD爱好者乐园%y&vM&yZ&s
另一个设计考虑就是如何让一个进程毫无干扰地使用多个kqueue实例。如你所见,进程可以设置一个信号处理器,但是,当代码中的其它部分也想捕获那个指定信号的时候该怎么办?或者考虑更坏的情况,比如一个库函数对你的程序想要捕获的信号设置了信号处理器的时候?要想通过调试来找出你的程序为什么没有执行你所设置的信号处理器可能要花费几个小时的时间。不过一般说来,这些情况并不会经常发生。好的程序员应该避免在库函数中设置信号处理器。对于大型的、复杂的程序来说,这些情况就很难避免了,所以为了更完美一点,我们应当能够检测这些事件,而kqueue就可以。
w`o2UR wnP
g)d-jFh3Mkqueue API由两个函数调用和一个辅助设置事件的宏组成。这些函数将在下面进行简要介绍。
~ L1CqR7io]
&k5}(q%a \+j5B/[*Z
 
  int kqueue(void);

;iQ3n F;o4Oskqueue函数启动一个新的kqueue。如果调用成功,返回值将是一个用来和新创建的kqueue交互的描述符。每个kqueue都有一个与之关联的唯一的描述符。因此,一个程序可以同时打开多个kqueue。kqueue描述符的行为和常规文件描述符类似:它们也可以被复用。BSD爱好者乐园 D4v)x:S2R

;s6A]_(pNZ0@n最后一点,这些描述符是不能被fork创建的子进程继承的。如果子进程是通过rfork调用创建的,那就需要设置RFFDG标志,以免这些描述符被子进程共享。如果kqueue函数失败,将返回-1,同时相应的设置errno。BSD爱好者乐园*sh-]/S{2O-E
BSD爱好者乐园z7\Vci _S8u;T s
 
  int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout);
BSD爱好者乐园\.t5yw$e
kevent函数用于和kqueue的交互。第一个参数是kqueue返回的描述符。changelist参数是一个大小为nchanges的kevent结构体数组。changelist参数用于注册或修改事件,并且将在从kqueue读出事件之前得到处理。
2m[-l K0AX
1rz8p%hheventlist参数是一个大小为nevents的kevent结构体数组。kevent通过把事件放在eventlist参数中来向调用进程返回事件。如果需要的话,eventlist和changelist参数可以指向同一个数组。最后一个参数是kevent所期待的超时时间。如果超时参数被指定为NULL,kevent将阻塞,直至有事件发生为止。如果超时参数不为NULL,则kevent将阻塞到超时为止。如果超时参数指定的是一个内容为0的结构体,kevent将立即返回所有当前尚未处理的事件。BSD爱好者乐园o@Bj X&Rk9?*W

R ^M0x-YqL:|G7d7Lkevent的返回值指定了放在eventlist数组中的事件的数目。如果事件数目超过了eventlist的大小,可以通过后续的kevent调用来获得它们。在处理事件的过程中发生的错误也会在还有空间的前提下被放到eventlist参数中。带有错误的事件会设置EV_ERROR位,系统错误也会被放到data成员中。对于其它的所有错误都将返回-1,并相应地设置errno。
&hn*~y9k-e ]BSD爱好者乐园)cs'Kxy'c
kevent结构体用于和kqueue的通信。FreeBSD上的头文件位于/usr/include/sys/event.h。在这个文件中有对kevent结构体的声明,以及其它的一些选项和标志。和select和poll比起来,kqueue还相当的年轻,所以它一直都在发展和添加新的特性。请查看你的系统头文件以确定任何新的或者特定于系统的选项。BSD爱好者乐园#k5pO!U}}D s

Zl;eU:gG#}原始的kevent结构体的声明如下:
W2m h8l1Av{-Q
@+aA ~ Mp
 
  struct kevent {BSD爱好者乐园UL)~1P-N ri,eL
        uintptr_t       ident;BSD爱好者乐园jE4k7S@5M
        short           filter;BSD爱好者乐园WR4Lr$A%G\2W
        u_short         flags;
h5eBa BkO-}mx        u_int           fflags;BSD爱好者乐园 P ];o]E5m{_d @
        intptr_t        data;
hr?noDc V+F        void            *udata;
+eS I[(p:] P8Y B  };
BSD爱好者乐园2k Fcq4z
现在,让我们来看看各个成员:BSD爱好者乐园s`)K_U
BSD爱好者乐园Gr&iT_,ogj
identBSD爱好者乐园$oc X{BH;h+jX
BSD爱好者乐园'G?`A|;t F Z|1?
ident成员用于存储kqueue的唯一标识。换句话说,如果你想给一个事件添加一个文件描述符的话,ident成员就应当被设置成目标描述符的值。BSD爱好者乐园|oS5j!r v(p6a^4y

(b}1U arY D x9ffilter
7}9A ve ny ]BSD爱好者乐园}Uu F8EfgE5t{i
filter成员用于指定你希望内核用于ident成员的过滤器。
id @;wR+\&F-Pd,]8fBSD爱好者乐园*|cro|@(Z2vTt
flags
o/j6FIOQc
*ITme2hJ4{ p:C8nflags成员将告诉内核应当对该事件完成哪些操作和处理哪些必要的标志。在返回的时候,flags成员可用于保存错误条件。
&WdC8t9I}BSD爱好者乐园p:OE-v"GVys$F{
fflagsBSD爱好者乐园4pIL8p4l7c-E {1F
BSD爱好者乐园${w-{7L$Lc
fflags成员用于指定你想让内核使用的特定于过滤器的标志。在返回的时候,fflags成员可用于保存特定于过滤器的返回值。
Q!S4jP7S9q
#|N@n;b9\Axdata
F"]R$|.Z:HN3N.p*b
aeCX9idata成员用于保存任何特定于过滤器的数据。
uv9wZ4_|%\'F j/f
&f'?h[oq_udata
;XxaV1E)Cs/cBSD爱好者乐园O#oIBG;Z-Y%XNV
udata成员并不由kqueue使用,kqueue会把它的值不加修改地透传。这个成员可被进程用来发送信息甚至是一个函数给它自己,用于一些依赖于事件检测的场合。
y PZ IThE hBSD爱好者乐园@*ua'F]w
kqueue 过滤器
W^ ^fB\3BBSD爱好者乐园] bt1~Z6Q wk(f6k!q
下面列出的是kqueue使用的过滤器。某些过滤器会有专用于它的标志。这些标志是在kevent结构体的fflags成员中设置的。BSD爱好者乐园)Co&d9]"u9w{2}
BSD爱好者乐园bzE8f2v'IY7KLV g
 
  #define EVFILT_READ     (-1)
BSD爱好者乐园'u'@F/iE@H
EVFILT_READ过滤器用于检测什么时候数据可读。kevent的ident成员应当被设成一个有效的描述符。尽管这个过滤器的行为和select或这poll很像,但它返回的事件将是特定于所使用的描述符的类型的。BSD爱好者乐园1OO L4E w!G

NAIbA4P!p3V如果描述符引用的打开文件是一个vnode,该事件就表明读取偏移量尚未到达文件末尾。data成员保存的是当前距文件末尾的偏移量,这可以是负值。如果描述符引用的是一个pipe或者fifo,那么过滤器将在有实际数据可读时返回。data成员保存的是可供读取的字节数目。EV_EOF bit用于表示是哪个写入者关闭了连接。(关于使用socket时EVFILT_READ的行为细节请参见kqueue的手册页。)BSD爱好者乐园9w.W:Fy~ V|
BSD爱好者乐园*F/U(|V({#[ Io
 
  #define EVFILT_WRITE    (-2)
BSD爱好者乐园pS;m&Cc5rNI
EVFILT_WRITE过滤器用于检测是否可以对描述符执行写操作。如果描述符引用的是一个pipe、fifo或者socket,则data成员将存有写缓冲区中可用的字节数目。EV_EOF bit表示读取方已经关闭了连接。这个标志对于打开的文件描述符无效。BSD爱好者乐园B$_ ~x9Ur
BSD爱好者乐园7D znz ^,g
 
  #define EVFILT_AIO      (-3)

` Ng3{QJ"iEVFILT_AIO用于异步I/O操作,用于检测和aio_error系统调用相似的条件。
7sN z^ X9h F1a(c%o)L}+R0_BSD爱好者乐园{3b_ l!x7BP c
 
  #define EVFILT_VNODE    (-4)

saj^)o3XJXSEVFILT_VNODE过滤器用于检测对文件系统上一个文件的某种改动。把ident成员设置成一个有效的打开文件描述符,用fflags成员指定所关心的事件。返回时,fflags成员将含有所发生事件的比特掩码。这些事件如下:
/`@7|o+W'sB0?v*VBSD爱好者乐园"Mk PSAm
 
  #define NOTE_DELETE     0x0001

E4b!Da&q/a&u9e-lNOTE_DELETE fflag表示进程想知道该文件何时被删。BSD爱好者乐园Q_'v[? OKfX

]0f{xO(o
 
  #define NOTE_WRITE      0x0002

+O%n`+D4k8EdNOTE_WRITE fflag表示进程想知道该文件内容何时被改变。BSD爱好者乐园T QG*A^w6u.Iv
BSD爱好者乐园QZ:w.b|'v
 
  #define NOTE_EXTEND     0x0004
BSD爱好者乐园9@!`^UMQI$A$N#`1X b
NOTE_EXTEND fflag表示进程想知道该文件何时被扩展。
}1x&v#RoF zDp
K0OB7GeG l0AD
 
  #define NOTE_ATTRIB     0x0008
BSD爱好者乐园*p}W'Gha5r ~ p
NOTE_ATTRIB fflag表示进程想知道该文件属性何时被改变。BSD爱好者乐园!}|?"cXX!WEN7G

qQ[YT M.{
 
  #define NOTE_LINK       0x0010
BSD爱好者乐园7bnr'N.O
NOTE_LINK fflag表示进程想知道该文件的链接计数何时被改变。当文件通过link函数调用进行硬链接的时候,它的链接计数就会改变。(详情请参见man(2) link。)BSD爱好者乐园/@LIyb.e`
BSD爱好者乐园r&]!@&~'d
 
  #define NOTE_RENAME     0x0020
BSD爱好者乐园5mN5Vr!l,p^
NOTE_RENAME fflag表示进程想知道该文件是否被重新命名了。BSD爱好者乐园 EOY!JJwk1d/X

{r eXq&| n
 
  #define NOTE_REVOKE     0x0040

p'MUR A7rC2g8dNOTE_REVOKE fflag表示对文件的访问被revoke了。详情请见man(2) revoke。BSD爱好者乐园U:^!].cQ.J \
BSD爱好者乐园D"HX4h2le
 
  #define EVFILT_PROC     (-5)  /* attached to struct proc */

C1XExrwEVLILT_PROC过滤器被进程用来检测发生在另外一个进程里的事件。所关心进程的PID存储在ident成员中,fflags成员则被设成所关心的事件。返回时,事件将被放在fflags成员中。这些事件由下列事件按比特OR的方式设置:
.V,gVaD~-c4Q%Um/G3}
,D^ UP\6u.i ^
 
  #define NOTE_EXIT       0x80000000

oB7IASNOTE_EXIT fflag用于检测该进程何时退出。BSD爱好者乐园\7j meV7[LH
BSD爱好者乐园6QY)_3N [#c}
 
  #define NOTE_FORK       0x40000000
BSD爱好者乐园R'` ?RPi2diD,GU
NOTE_FORK fflag用于检测该进程何时调用fork。BSD爱好者乐园/nWt:]J

p$A1DZ#s:Rl
 
  #define NOTE_EXEC       0x20000000
BSD爱好者乐园9K#JW Dh4p#}C
NOTE_EXEC fflag用于检测该进程何时调用exec函数。BSD爱好者乐园 GT)e}3]J+fG

z,k4?#C(G
 
  #define NOTE_TRACK      0x00000001
BSD爱好者乐园mw$vT7H6Lr
NOTE_TRACK fflag让kqueue去跟踪一个跨越fork调用的进程。子进程返回时将设置fflags中的NOTE_CHILD标志,父进程的PID将放在data成员中。BSD爱好者乐园,RJa G { r~"MDp W

7k%B#x] A6TI ?
 
  #define NOTE_TRACKERR   0x00000002
BSD爱好者乐园y5di`6o
当在跟踪子进程的过程中有错误发生时,就会设置NOTE_TRACKERR fflag。这是一个仅用于返回的fflag。
})a&?`T
eh e.F0W IY
 
  #define NOTE_CHILD      0x00000004

.`5j+e0DGNOTE_CHILD fflag在子进程内设置。这是一个仅用于返回的fflag。
:`!l:J@v?'wBSD爱好者乐园9S_b(z%y7J'Rd
 
  #define EVFILT_SIGNAL      (-6)
BSD爱好者乐园{;_q jI`
EVFILT_SIGNAL过滤器用于检测是否有信号发送给该进程。每当有信号发送时这个过滤器就会检测到,并把计数值放在data成员中。这包括设置了SIG_IGN标志的信号。事件将在执行完常规的信号处理过程之后放到kqueue上。注意,这个过滤器将在内部设置EV_CLEAR标志。BSD爱好者乐园DNE sx

0fSFk*b1|6de
 
  #define EVFILT_TIMER    (-7)
BSD爱好者乐园8u6S eI;}9R]
EVFILT_TIMER过滤器会给kqueue创建一个定时器,用于记录消逝的事件。如果需要一个一次性的定时器,可以设置EV_ONESHOT标志。这个定时器是在ident成员中指定的,data成员用来指定以毫秒为单位的超时时间。返回值放在data成员中。注意,这个过滤器将在内部设置EV_CLEAR标志。BSD爱好者乐园gdq/fyPy
BSD爱好者乐园sp%`:}SO.R[:C
kqueue操作
_ef0~;WzS*~BSD爱好者乐园/u X B@A4N
kqueue操作由所需的操作和标志以比特OR的方式进行设置。
}4c4]IH IJ@BSD爱好者乐园D{)oy1M
 
  #define EV_ADD          0x0001
BSD爱好者乐园wm;D-G0FQ&yT;t
EV_ADD操作向kqueue添加事件。由于kqueue中不允许出现重复,所以如果你想添加一个已经存在的事件的话,现有事件将被新的添加操作覆盖。注意,在添加事件的时候,它们已经被默认激活了,除非你设置了EV_DISABLE标志。
*k s}#bjS
H~l_0Li'}E
 
  #define EV_DELETE       0x0002
BSD爱好者乐园_eMK3~
EV_DELETE操作从kqueue中删除事件。BSD爱好者乐园-n8LP&x {#ul

#Ex~{^
 
  #define EV_ENABLE       0x0004
BSD爱好者乐园(G2Fy(s C+dA0wt+r$|
EV_ENABLE用于激活kqueue中的事件。注意,新添加的事件默认就是激活的。
_%amGF~+n
tp @l yx
 
  #define EV_DISABLE      0x0008

3r^2^s\c$SEV_DISABLE禁止kqueue返回某个事件的信息。注意,kqueue并不会删除过滤器。BSD爱好者乐园9G t[ K:O
BSD爱好者乐园 LX2B/q7L0|~3L*\
kqueue操作标志BSD爱好者乐园+Q\'J;d V#g
BSD爱好者乐园B7~,y(Aw2\K
kqueue的操作标志定义如下。它们和上面列出的操作结合使用。它们是通过和所需操作进行比特OR来设置的。
'w4@E_$g#~2~;DBSD爱好者乐园'Ci-O_8r ob#daly
 
  #define EV_ONESHOT      0x0010
BSD爱好者乐园)l-{I9{iV-c3e%O
EV_ONESHOT标志用于通知kqueue只返回第一个。
b Fe b;ziB8XBSD爱好者乐园(xh6[5S;o/vo
 
  #define EV_CLEAR        0x0020

!z SeBxf6nN)HEV_CLEAR标志用于通知kqueue,一旦进程从kqueue中获取到了该事件就将该事件的状态复位。BSD爱好者乐园ayW6I*L$E-Al
BSD爱好者乐园 JMqU7u5]8D,dd
kqueue返回值BSD爱好者乐园m]i.bS$N.Oz)R
BSD爱好者乐园3k]CJSL
仅用于返回的值是放在kevent结构体的flags成员中的。这些值的定义如下:
T$H8vD1K7U-y
`)W%o fv
 
  #define EV_EOF          0x8000
BSD爱好者乐园a}Sq8H
EV_EOF用于表示文件结束的情况。BSD爱好者乐园7@G+H|'Ik$s
 
  #define EV_ERROR        0x4000
BSD爱好者乐园 b#seCX-rjr,l(E
EV_ERROR用于表示有错误发生了。系统错误将被放到data成员中。
XD5?'{/Q]$g]
"V/in&Kv1L Up1U,[6.5 结论BSD爱好者乐园zLK Y^+E M0G8T

*L7H,Tx!od?n#qfp本章研究了BSD中的描述符复用。作为一个程序员,你可以选择三个接口:select、poll和kqueue。对于小数量的描述符来说,这三者的性能差不多,但是当描述符数量很大时,kqueue则是最好的选择。除此之外,kqueue还可以检测比I/O事件更为丰富的条件。它可以检测信号、文件修改以及子进程相关的事件。在下一章中,我们将针对FreeBSD 5.x中的新特性,研究其它的获取子进程信息和当前进程统计信息的方法。因为使用范围的限制,BSD方面文章更新速度不快,站长会坚持每天更新博客,欢迎访问!
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: freebsd FreeBSD 系统编程
 

评分:0

我来说两句

seccode