epoll事件通知机制详解,水平触发和边沿触发的区别

     看到网上有不少讨论epoll,但大多不够详细准确,以前面试有被问到这个问题。不去更深入的了解,只能停留在知其然而不知其所以然。于是,把epoll手册翻译一遍,更深入理解和掌握epoll事件处理相关知识,也涉及到了操作系统内核的知识。

EPOLL(7)            Linux Programmer's Manual             

NAME

epoll - I/O event notification facility

epoll - I/O 事件通知机制翻译:6700662@qq.com, 转载请注明出处。

DESCRIPTION

The  epoll  API  performs  a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them.  The epoll API can be used either as anedge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.  The following system calls are provided to create and manage anepoll instance:

Epoll API执行类似于poll的任务:监控多个文件描述符,看它们其中任何一个是否有可能I/O。Epoll API既可以用作边缘触发(ET)或水平触发(LT),并良好的适用大量被监控的文件描述符。提供下面这些系统调用去创建和管理一个epoll实例:

*  epoll_create(2)  creates  an  epoll  instance  and  returns a file descriptor referring to that instance.  (The more recent epoll_create1(2) extends the functionality ofepoll_create(2).)

* epoll_create(2)  创建一个epoll实例,并返回关联到该实例的文件描述符。(较新的epoll_create1(2)扩展了这个API的功能。)

*  Interest in particular file descriptors is then registered via epoll_ctl(2).  The set of file descriptors currently registered on an epoll instance is  sometimes  calledan epoll set.

*    通过 epoll_ctl(2)来注册,以关注特定的文件描述符。当前已在epoll实例注册的文件描述符集合,有时候称作epoll set。

*  epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.

*  epoll_wait(2)等待I/O事件,如果当前没有可用的事件则阻塞调用线程。

Level-triggered and edge-triggered

水平触发和边沿触发

The  epoll  event  distribution  interface  is  able  to  behave  both as edge-triggered (ET) and as level-triggered (LT).  The difference between the two mechanisms can bedescribed as follows.  Suppose that this scenario happens:

Epoll事件分派接口可以表现为边沿前触发 (ET)和 水平触发(LT).这两个机制之间的区别可以描述如下。假设这个发生了这个场景:

1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.

2. A pipe writer writes 2 kB of data on the write side of the pipe.

3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.

4. The pipe reader reads 1 kB of data from rfd.

5. A call to epoll_wait(2) is done.

1. 表示管道读端的文件描述符(rfd)已在epoll实例注册。

2. 管道写入程序,写了2kB的数据在管道写入端

3. 对epoll_wait(2)的调用已完成,将返回rfd作为已就绪的文件描述符。

4. 管道读取程序,从rfd读入1kB的数据。

5.一个对epoll_wait(2)的调用已完成。

If the rfd file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to epoll_wait(2)  done  in  step  5  will  probably  hangdespite  the  available  data still present in the file input buffer; meanwhile the remote peer might be expecting a response based on the data it already sent.  The reasonfor this is that edge-triggered mode delivers events only when changes occur on the monitored file descriptor.  So, in step 5 the caller might end up waiting for some  datathat  is  already  present  inside the input buffer.  In the above example, an event on rfd will be generated because of the write done in 2 and the event is consumed in 3.Since the read operation done in 4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.

如果rfd文件描述符是用EPOLLET (边沿触发) 标志被加入到epoll接口,在第5步中调用的epoll_wait(2)可能阻塞,尽管可用的数据任然还存在于文件输入缓存中;此时远程对端可能期待它已发送数据的响应。原因是ET模式只有在被监控文件描述符发生变化时才递交事件。所以,第5步的调用者可能终止于等待一些已经存在于输入缓存中的数据(没有触发事件,还在等待接收).在上述例子中,一次rfd上的事件被产生是因为第2步写入完成,并在第3步中消耗。第4步的读操作没有消耗整个缓存数据,在第5步中调用的 epoll_wait(2),可能立即阻塞。

An application that employs the EPOLLET flag should use nonblocking file descriptors to avoid having a blocking read or write starve a task that is handling  multiple  filedescriptors.  The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows:

采用EPOLLET标志的应用程序应当使用非阻塞文件描述符,以防止阻塞读或写造成处理多文件描述符的任务发生饥饿。以边沿触发接口(EPOLLET)使用epoll的建议方式如下:

i   with nonblocking file descriptors; and

i  使用非阻塞文件描述符;并且

ii  by waiting for an event only after read(2) or write(2) return EAGAIN.

ii 只有在read(2)或 write(2)返回EAGAIN之后才等待事件。

By contrast, when used as a level-triggered interface (the default, when EPOLLET is not specified), epoll is simply a faster poll(2), and can be used wherever the latter isused since it shares the same semantics.

与之相比,当作为水平触发接口使用(默认地,当EPOLLET没有被指定),epoll仅仅是更快的poll,并能被用于不管后面用什么,因为它共享相同的语义。

Since even with edge-triggered epoll, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to specify the EPOLLONESHOT  flag,to  tell  epoll  to  disable  the  associated file descriptor after the receipt of an event with epoll_wait(2).  When the EPOLLONESHOT flag is specified, it is the caller'sresponsibility to rearm the file descriptor using epoll_ctl(2) with EPOLL_CTL_MOD.

因为即使在边沿触发epoll,在收到多个数据块之后会产生多个事件,调用者还有指定EPOLLONESHOT标志的选项,来告知epoll在epoll_wait(2)收到一个事件之后禁止关联的文件描述符。当EPOLLONESHOT被指明,由调用者负责使用epoll_ctl(2) 和 EPOLL_CTL_MOD来重新授权文件描述符。

Interaction with autosleep

与autosleep的交互

If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only  untilthat event is queued.  To keep the device awake until the event has been processed, it is necessary to use the epoll(7) EPOLLWAKEUP flag.

如果系统通过/sys/power/autosleep进入autosleep模式,并且发生事件把设备从睡眠中唤醒,设备驱动仅仅保持设备唤醒到那个事件进入队列。要保持设备唤醒到事件被处理,必须使用epoll(7) EPOLLWAKEUP标志。

When  the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2)call which returns the event until the subsequent epoll_wait(2) call.  If the event should keep the system awake beyond that time, then a separate wake_lock should be takenbefore the second epoll_wait(2) call.

当EPOLLWAKEUP标志设置在epoll_event结构的事件字段,系统将从事件进入队列开始保持唤醒,通过返回事件的epoll_wait(2)直到后续的epoll_wait(2)调用。如果事件要在那个时间之外保持系统唤醒,那么单独的wake_lock应当在第二次调用epoll_wait(2)之前被调用。

/proc interfaces

The following interfaces can be used to limit the amount of kernel memory consumed by epoll:

以下是接口可被用于限制epoll消耗的内核内存总数:

/proc/sys/fs/epoll/max_user_watches (since Linux 2.6.28)

This  specifies  a  limit  on the total number of file descriptors that a user can register across all epoll instances on the system.  The limit is per real user ID.Each registered file descriptor costs roughly 90 bytes on a  32-bit  kernel,  and  roughly  160  bytes  on  a  64-bit  kernel.   Currently,  the  default  value  formax_user_watches is 1/25 (4%) of the available low memory, divided by the registration cost in bytes.

指定一个用户通过系统中所有epoll实例能够注册的文件描述符的限制。这个限制是对每个真实用户ID的。每个注册的文件描述符,在32位内核中大致占用90字节,在64位内核中大致占用160字节。一般的,max_user_watches的默认值是1/25(4%)的可用最低内存,除以注册占用字节数。

Example for suggested usage

建议的用法示例

While the usage of epoll when employed as a level-triggered interface does have the same semantics as poll(2), the edge-triggered usage requires more clarification to avoidstalls in the application event loop.  In this example, listener is a nonblocking socket on which listen(2) has been called.  The function do_use_fd() uses  the  new  readyfile  descriptor until EAGAIN is returned by either read(2) or write(2).  An event-driven state machine application should, after having received EAGAIN, record its currentstate so that at the next call to do_use_fd() it will continue to read(2) or write(2) from where it stopped before.

当epoll采用水平触发接口时具有poll相同的语义,边沿触发用法要求更清楚说明以防止应用程序事件循环停转。在这个示例中,调用了lister(2)的listener是非阻塞socket.do_use_fd()函数使用新的就绪文件描述符直到read(2)或write(2)返回EAGAIN。事件驱动状态机应用程序应当,在接收到EAGAIN之后,记录它当前的状态所以在下次调用do_use_fd()将从之前停止的地方继续read(2)或 write(2)。

#define MAX_EVENTS 10

struct epoll_event ev, events[MAX_EVENTS];

int listen_sock, conn_sock, nfds, epollfd;

/* Code to set up listening socket, 'listen_sock',

(socket(), bind(), listen()) omitted */

epollfd = epoll_create1(0);

if (epollfd == -1) {

perror("epoll_create1");

exit(EXIT_FAILURE);

}

ev.events = EPOLLIN;

ev.data.fd = listen_sock;

if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {

perror("epoll_ctl: listen_sock");

exit(EXIT_FAILURE);

}

for (;;) {

nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);

if (nfds == -1) {

perror("epoll_wait");

exit(EXIT_FAILURE);

}

for (n = 0; n < nfds; ++n) {

if (events[n].data.fd == listen_sock) {

conn_sock = accept(listen_sock,

(struct sockaddr *) &local, &addrlen);

if (conn_sock == -1) {

perror("accept");

exit(EXIT_FAILURE);

}

setnonblocking(conn_sock);

ev.events = EPOLLIN | EPOLLET;

ev.data.fd = conn_sock;

if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,

&ev) == -1) {

perror("epoll_ctl: conn_sock");

exit(EXIT_FAILURE);

}

} else {

do_use_fd(events[n].data.fd);

}

}

}

When used as an edge-triggered interface, for performance reasons, it is possible to add the file descriptor inside the epoll interface (EPOLL_CTL_ADD) once  by  specifying(EPOLLIN|EPOLLOUT).  This allows you to avoid continuously switching between EPOLLIN and EPOLLOUT calling epoll_ctl(2) with EPOLL_CTL_MOD.

当作为边沿触发(ET)接口使用,为性能原因,有可能通过指明(EPOLLIN|EPOLLOUT)一次性添加文件描述符到epoll接口(EPOLL_CTL_ADD).这允许你在调用epoll_ctl(2)和EPOLL_CTL_MOD时,防止持续在EPOLLIN和EPOLLOUT之间切换。(注:EPOLLIN和EPOLLOUT分两次调用epoll_ctl更耗时间性能).

Questions and answers

Q0  What is the key used to distinguish the file descriptors registered in an epoll set?

用于区分在epoll set中已注册文件描述符的key是什么?

A0  The  key is the combination of the file descriptor number and the open file description (also known as an "open file handle", the kernel's internal representation of anopen file).

key是文件描述符数字和”打开文件描述符”的组合(也就是已知的"open file handle",打开文件句柄,内核的一个打开文件的内部表示)。

Q1  What happens if you register the same file descriptor on an epoll instance twice?

在一个epoll实例中对相同的文件描述符注册两次,会发生什么?

A1  You will probably get EEXIST.  However, it is possible to add a duplicate (dup(2), dup2(2), fcntl(2) F_DUPFD) descriptor to the same epoll instance.  This can be a useful technique for filtering events, if the duplicate file descriptors are registered with different events masks.

你将可能收到EEXIST。然而, 有可能添加副本描述符到相同的epoll实例.这可以是一个过滤事件的有用技巧,如果副本文件描述符用不同的事件掩码去注册。

Q2  Can two epoll instances wait for the same file descriptor?  If so, are events reported to both epoll file descriptors?

能用两个epoll实例去等待同一个文件描述符吗?如果那样,事件被报告到两个epoll文件描述符吗?

A2  Yes, and events would be reported to both.  However, careful programming may be needed to do this correctly.

是的,并且事件将被报告到两者。不管怎样,需要仔细编程以做正确这事。

Q3  Is the epoll file descriptor itself poll/epoll/selectable?

epoll文件描述符本身是poll/epoll可轮询的吗?

A3  Yes.  If an epoll file descriptor has events waiting, then it will indicate as being readable.

是的。如果一个epoll文件描述符有事件在等待,那么它将指示为可读。

Q4  What happens if one attempts to put an epoll file descriptor into its own file descriptor set?

当尝试把epoll文件描述符放入它自己的文件描述符集合中会发生什么?

A4  The epoll_ctl(2) call will fail (EINVAL).  However, you can add an epoll file descriptor inside another epoll file descriptor set.

epoll_ctl(2)调用将以(EINVAL)失败. 然而,你可以添加epoll文件描述符到另一个epoll文件描述符集合内。

Q5  Can I send an epoll file descriptor over a UNIX domain socket to another process?

可以通过UNIX域socket发送一个epoll文件描述符到另一个进程吗?

A5  Yes, but it does not make sense to do this, since the receiving process would not have copies of the file descriptors in the epoll set.

是的,但这样做没有任何意义,因为接收进程不会有epoll set中的文件描述符副本。

Q6  Will closing a file descriptor cause it to be removed from all epoll sets automatically?

关闭一个文件描述符,会导致它自动从所有epoll set中被移除吗?

A6  Yes,  but be aware of the following point.  A file descriptor is a reference to an open file description (see open(2)).  Whenever a descriptor is duplicated via dup(2),dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created.  An open file description continues to exist  untilall  file  descriptors referring to it have been closed.  A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying

open file description have been closed (or before if the descriptor is explicitly removed using epoll_ctl(2) EPOLL_CTL_DEL).  This means that even after a file descriptor  that  is  part  of  an  epoll  set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same underlying file

description remain open.

是的,但需要清楚以下几点。文件描述符是一个”打开文件描述符”的引用(见 open(2))。每当描述符是副本,通过dup(2),dup2(2), fcntl(2) F_DUPFD, or fork(2),一个指向同一“打开文件描述符”的引用的文件描述符被创建。一个“打开文件描述符”持续存在直达所有到它的文件描述符引用被关闭。只有在指向下层“打开文件描述符”的所有文件描述符引用被关闭时,文件描述符才从epoll set中被移除(或者之前如果描述符是使用epoll_ctl(2) EPOLL_CTL_DEL被明确的移除)。这意味着即使epoll set部分的文件描述符被关闭之后,那个文件描述符的事件可能被报告,如果其他文件描述符引用指向的相同下层文件描述符保持打开.

Q7  If more than one event occurs between epoll_wait(2) calls, are they combined or reported separately?

如果在epoll_wait(2)调用之间多于一个事件产生,它们是合并的还是分别报告?

A7  They will be combined.

它们会被合并。

Q8  Does an operation on a file descriptor affect the already collected but not yet reported events?

文件描述符上的操作会影响已经收集但没有报告的事件吗?

A8  You can do two operations on an existing file descriptor.  Remove would be meaningless for this case.  Modify will reread available I/O.

你能做两个操作,在一个已存在的文件描述符上。移除将是毫无意义的,对这种情形。修改将会重读可用的I/O(再次产生event?).

Q9  Do I need to continuously read/write a file descriptor until EAGAIN when using the EPOLLET flag (edge-triggered behavior) ?

当使用EPOLLET标志时(边沿触发行为),需要持续的在文件描述符连续的read/write,直到EAGAIN ?

A9  Receiving an event from epoll_wait(2) should suggest to you that such file descriptor is ready for the requested I/O operation.  You must consider it  ready  until  thenext (nonblocking) read/write yields EAGAIN.  When and how you will use the file descriptor is entirely up to you.

从epoll_wait(2)收到事件,应当指示你如此的文件描述是已就绪于请求I/O操作。你必须认为它是就绪的,直到下一个(非阻塞)read/write产生EAGIN. 何时、如何使用这个文件描述符完全取决于你。

For  packet/token-oriented  files  (e.g.,  datagram  socket,  terminal  in canonical mode), the only way to detect the end of the read/write I/O space is to continue toread/write until EAGAIN.

对于包/符号导向的文件(比如 UDP socket,标准模式的终端), 唯一检测read/write I/O空间结束的方法,是连续read/write直到EAGIN.

For stream-oriented files (e.g., pipe, FIFO, stream socket), the condition that the read/write I/O space is exhausted can also be detected by  checking  the  amount  ofdata  read from / written to the target file descriptor.  For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower numberof bytes, you can be sure of having exhausted the read I/O space for the file descriptor.  The same is true when writing using write(2).  (Avoid this  latter  techniqueif you cannot guarantee that the monitored file descriptor always refers to a stream-oriented file.)

对于流导向的文件(例如 pipe, FIFO, TCP socket),read/write I/O空间耗尽的条件也能通过 读取于/写入到 目标文件描述符的数据总数来检测。例如,如果你调用read(2)要求读取确定的数据总数,并且read(2)返回更低的字节数,你能确认该文件描述符的 read I/O 空间已经耗尽。使用write(2)来写入时也一样。(如果你不能保证被监控的文件描述符一直指向流式文件,避免使用后面的字节数技巧)。

Possible pitfalls and ways to avoid them

可能的陷阱和避免方法

o Starvation (edge-triggered)

饥饿(边沿触发)

If there is a large amount of I/O space, it is possible that by trying to drain it the other files will not get processed causing starvation.  (This problem is not specificto epoll.)

如果有大量的I/O空间,有可能尝试耗尽它,其它文件将得不到处理而导致饥饿。(这个问题不是epoll特有的)

The solution is to maintain a ready list and mark the file descriptor as ready in its associated data structure, thereby allowing the application to  remember  which  filesneed  to  be  processed  but still round robin amongst all the ready files.  This also supports ignoring subsequent events you receive for file descriptors that are alreadyready.

解决方案是维护一个就绪列表,并在它关联的数据结构中标记文件描述符已就绪,从而允许应用程序记住那个文件需要被处理,但还在所有就绪文件中循环竞争。这样也支持对那些已就绪的文件描述符忽略你收到的后续事件.

o If using an event cache...

如果使用一个事件缓存...

If you use an event cache or store all the file descriptors returned from epoll_wait(2), then make sure to provide a way to mark its closure dynamically (i.e., caused by  aprevious  event's  processing).  Suppose you receive 100 events from epoll_wait(2), and in event #47 a condition causes event #13 to be closed.  If you remove the structureand close(2) the file descriptor for event #13, then your event cache might still say there are events waiting for that file descriptor causing confusion.

如果你使用一个事件缓存或存储所有从epoll_wait(2)返回的文件描述符, 那么要确信提供一个方法去标记它的动态关闭(例如,在前一个事件处理中导致的)。假设你从epoll_wait(2)收到100个事件,并且在#47事件中一个条件导致#13事件关闭。如果你移除数据结构并关闭事件#13的文件描述符,那么你的事件缓存可能任然说还有事件在等待那个文件描述符,导致混乱。

One solution for this is to call, during the processing of event 47, epoll_ctl(EPOLL_CTL_DEL) to delete file descriptor 13 and  close(2),  then  mark  its  associated  datastructure  as  removed  and link it to a cleanup list.  If you find another event for file descriptor 13 in your batch processing, you will discover the file descriptor hadbeen previously removed and there will be no confusion.

这个问题的一个解决方案是,在#47事件的处理过程中,调用epoll_ctl(EPOLL_CTL_DEL)去删除文件描述符13并close(2),然后标记它的关联数据结构为已移除,并链接到一个cleanup list.如果在你的批量处理中发现#13文件描述符的事件,你将发现文件描述符在之前已经移除,就不会混乱。

翻译:6700662@qq.com, 转载请注明出处。

VERSIONS

The epoll API was introduced in Linux kernel 2.5.44.  Support was added to glibc in version 2.3.2.

CONFORMING TO

The epoll API is Linux-specific.  Some other systems provide similar mechanisms, for example, FreeBSD has kqueue, and Solaris has /dev/poll.

SEE ALSO

epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)

COLOPHON

This page is part of release 4.04 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page,  can

be found at http://www.kernel.org/doc/man-pages/.

Linux                                                                                2015-04-19                                                                            EPOLL(7)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • epoll Its function is to monitor multiple file descriptor...
    庞贝船长阅读 446评论 0 0
  • epoll概述 epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多...
    发仔很忙阅读 10,863评论 4 35
  • 名称 libev - 一个 C 编写的功能全面的高性能事件循环。 概要 示例程序 关于 libev Libev 是...
    hanpfei阅读 15,167评论 0 5
  • 概念 首先要说明几个概念: 用户空间和内核空间 进程切换 进程的阻塞 文件描述符 缓存 I/O 用户空间与内核空间...
    jiangmo阅读 937评论 0 9
  • 今天是你拿到中环录取通知的日子,真是挺为你开心的,功不唐捐 我真的相信这一点,向你学习的东西还有很多,勇气实力,证...
    飘舞的星空阅读 166评论 0 0