博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
0.select 系统调用介绍
阅读量:2494 次
发布时间:2019-05-11

本文共 5901 字,大约阅读时间需要 19 分钟。

select 使用文档在:

select介绍

select 接口如下:

int select(int nfds, fd_set *readfds, fd_set *writefds,           fd_set *exceptfds, struct timeval *timeout);

select函数的作用:

等待多个事件中的任何一个的发生,并且仅当其中一个或几个事件发生时,或经过指定的时间后,才唤醒进程。
返回值可以是:ready descriptors的总个数,或0(代表超时),或-1(代表error)

参数说明

  • 1 nfds 是 readfds、writefds、exceptfds 中编号最大的那个文件描述符加1
  • 2 readfds 是监听读操作的文件描述符列表
    当被监听的文件描述符有可以不阻塞就读取的数据时 ( 读不出来数据也算,比如 end-of-file),select 会返回并将读就绪的描述符放在 readfds 指向的数组内。
  • 3 writefds 是监听写操作的文件描述符列表
    当被监听的文件描述符中能可以不阻塞就写数据时(如果一口气写的数据太大实际也会阻塞),select 会返回并将就绪的描述符放在 writefds 指向的数组内。
  • 4 exceptfds 是监听出现异常的文件描述符列表
    什么是异常需要看一下文档,与我们通常理解的异常并不太相同。
  • 5 timeout 是 select 最大阻塞时间长度
    配置的最小时间精度是毫秒

select 返回条件:

  • 1 有文件描述符就绪,可读、可写或异常,以下英文文档很好,翻译了反而不好
    • socket ready for reading,满足以下条件之一:
      • The number of bytes of data in the socket receive buffer is greater than or equal to the current size of the low-water mark for the socket receive buffer.
        A read operation on the socket will not block and will return a value greater than 0。We can set this low-water mark using the SO_RCVLOWAT socket option. It defaults to 1 for TCP and UDP sockets.
      • The read half of the connection is closed
        (i.e., a TCP connection that has received a FIN). A read operation on the socket will not block and will return 0 (i.e., EOF)
      • The socket is a listening socket and the number of completed connections is nonzero
      • A socket error is pending.
        A read operation on the socket will not block and will return an error (–1) with errno set to the specific error condition. These pending errors can also be fetched and cleared by calling getsockopt and specifying the SO_ERROR socket option.
    • socket ready for writing,满足以下条件之一:
      • The number of bytes of available space in the socket send buffer is greater than or equal to the current size of the low-water mark for the socket send buffer and either:
        (i) the socket is connected, or (ii) the socket does not require a connection (e.g., UDP). This means that if we set the socket to nonblocking (Chapter 16), a write operation will not block and will return a positive value (e.g., the number of bytes accepted by the transport layer). We can set this low-water mark using the SO_SNDLOWAT socket option. This low-water mark normally defaults to 2048 for TCP and UDP sockets.
      • The write half of the connection is closed.
        A write operation on the socket will generate SIGPIPE (Section 5.12).
      • A socket using a non-blocking connect has completed the connection, or the connect has failed
      • A socket error is pending.
        A write operation on the socket will not block and will return an error (–1) with errno set to the specific error condition. These pending errors can also be fetched and cleared by calling getsockopt with the SO_ERROR socket option.
    • A socket has an exception condition pending if there is out-of-band data for the socket or the socket is still at the out-of-band mark (Chapter 24)
  • 2 线程被 interrupt
  • 3 timeout 到了

select 的问题:

  • 1 监听的文件描述符有上限 FD_SETSIZE,一般是 1024
    因为 fd_set 是个 bitmap,它为最多 nfds 个描述符都用一个 bit 去表示是否监听,即使相应位置的描述符不需要监听在 fd_set 里也有它的 bit 存在。nfds 用于创建这个 bitmap 所以 fd_set 是有限大小的。
  • 2 select要查询整个f_set,具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长
    select 返回后它并不是只返回处于 ready 状态的描述符,而是会返回传入的所有的描述符列表集合,包括 ready 的和非 ready 的描述符,用户侧需要去遍历所有 readfds、writefds、exceptfds 去看哪个描述符是 ready 状态,再做接下来的处理。还要清理这个 ready 状态,做完 IO 操作后再塞给 select 准备执行下一轮 IO 操作
  • 3 内核/用户空间内存拷贝问题
    在 Kernel 侧,select 执行后每次都要陷入内核遍历三个描述符集合数组为文件描述符注册监听,即在描述符指向的 Socket 或文件等上面设置处理函数,从而在文件 ready 时能调用处理函数。等有文件描述符 ready 后,在 select 返回退出之前,kernel 还需要再次遍历描述符集合,将设置的这些处理函数拆除再返回
  • 4 有惊群问题。假设一个文件描述符 123 被多个进程或线程注册在自己的 select 描述符集合内,当这个文件描述符 ready 后会将所有监听它的进程或线程全部唤醒
  • 5 无法动态添加描述符,比如一个线程已经在执行 select 了,突然想写数据到某个新描述符上,就只能等前一个 select 返回后重新设置 FD Set 重新执行 select
    select 也有个优点,就是跨平台更容易。实现这个接口的 OS 更多。

理解select模型

相关函数

FD_ZERO(int fd, fd_set* fds) // 清空集合,FD_ZERO(&set): 清零set集合;

FD_SET(int fd, fd_set* fds) // 将给定的描述符加入集合,FD_SET(listenfd,&set): 把监听描述符添加到set集合中
FD_ISSET(int fd, fd_set* fds) // 判断指定描述符是否在集合中,如果在集合中就代表fd准备好了
FD_CLR(int fd, fd_set* fds) // 将给定的描述符从文件中删除

理解select模型的关键在于理解fd_set

为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd:

(1)执行fd_set set;FD_ZERO(&set);则set用位表示: 0000 0000
(2)若fd=5,执行FD_SET(fd,&set);后set变为: 0001 0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为: 0001 0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为: 0000 0011。注意:没有事件发生的fd=5被清空。

// 使用select对 fp和socket进行监听#include    "unp.h"void str_cli(FILE *fp, int sockfd){    int         maxfdp1;    fd_set      rset;    char        sendline[MAXLINE], recvline[MAXLINE];    FD_ZERO(&rset);    for ( ; ; ) {        FD_SET(fileno(fp), &rset);        FD_SET(sockfd, &rset);        maxfdp1 = max(fileno(fp), sockfd) + 1;        Select(maxfdp1, &rset, NULL, NULL, NULL);        if (FD_ISSET(sockfd, &rset)) {  /* socket is readable */            if (Readline(sockfd, recvline, MAXLINE) == 0)                err_quit("str_cli: server terminated prematurely");            Fputs(recvline, stdout);        }        if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */            if (Fgets(sendline, MAXLINE, fp) == NULL)                return;     /* all done */            Writen(sockfd, sendline, strlen(sendline));        }    }}

调用select会发生的事

调用栈:SYSCALL_DEFINE5 (sys_select)----> core_sys_select -----> do_select()

  • 1 使用copy_from_user从用户空间拷贝 fd_set 到内核空间
  • 2 注册回调函数__pollwait;
  • 3 遍历所有fd(设备的fd),调用设备(如socket)对应的poll方法
    对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll;以tcp_poll为例,其核心实现就是__pollwait
    • 3.1 __pollwait的主要工作就是把当前进程(调用select()的应用程序)挂到设备的等待队列中
      不同设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时当前进程便被唤醒了。
    • 3.2 poll方法会检查设备是否就绪
      返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。
  1. 如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd

  2. 只要有事件就绪,系统调用便返回

    将 fd_set 从内核空间拷贝到用户空间,返回用户态,用户程序就可以对相关的 fd作进一步的读或者写操作了。

参考

转载地址:http://ywerb.baihongyu.com/

你可能感兴趣的文章
Docker面试题(一)
查看>>
第一轮面试题
查看>>
2020-11-18
查看>>
Docker面试题(二)
查看>>
一、redis面试题及答案
查看>>
消息队列2
查看>>
C++ 线程同步之临界区CRITICAL_SECTION
查看>>
测试—自定义消息处理
查看>>
MFC中关于虚函数的一些问题
查看>>
根据图层名获取图层和图层序号
查看>>
规范性附录 属性值代码
查看>>
提取面狭长角
查看>>
Arcsde表空间自动增长
查看>>
Arcsde报ora-29861: 域索引标记为loading/failed/unusable错误
查看>>
记一次断电恢复ORA-01033错误
查看>>
C#修改JPG图片EXIF信息中的GPS信息
查看>>
从零开始的Docker ELK+Filebeat 6.4.0日志管理
查看>>
How it works(1) winston3源码阅读(A)
查看>>
How it works(2) autocannon源码阅读(A)
查看>>
How it works(3) Tilestrata源码阅读(A)
查看>>