i/o多路复用笔记

1、用户空间和内核空间

操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分是内核空间,一部分是用户空间。

2、进程切换

内核挂起当前正在cpu上运行的进程,并恢复以前挂起的某个进程的执行。

  • 保存处理机上下文,包括程序计数器和其它寄存器。
  • 更新PCB信息
  • 把进程的PCB移入到相应的队列,如就绪或阻塞队列
  • 选择另一个进程执行,并更新PCB
  • 更新内存管理的数据结构
  • 恢复上下文

3、进程阻塞

正在执行的进程,由于期待某些事件的发生,比如IO,自动执行阻塞原语,进入阻塞状态。这是一种主动行为,只有运行中的进程才可以切换到阻塞状态。在阻塞状态的进程是不占用CPU资源的。

4、I/O模式

标准I/O,数据会先被拷贝到操作系统的内核缓冲区,然后才会从操作系统的内核缓冲区拷贝到应用程序的地址空间。

linux有五种网络模式:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路复用
  • 信号驱动I/O(signal driven i/o,不常用)
  • 异步I/O

1、阻塞I/O

blocking i/o

用户进程调用recvfrom系统调用,进程进入阻塞状态,等待数据准备好,数据会从内核拷贝到用户内存,然后内核返回结果,用户进程解除block状态,重新运行。

2、非阻塞I/O

nonblocking i/o

用户进程调用read操作,如果数据还没准备好,内核立刻返回error,用户进程不会阻塞。用户进程可以通过反复调用read操作轮询来获得数据。

3、I/O多路复用

I/O multiplexing,也就是select、poll、epoll,也可以被称为event driven I/O。好处是单个进程可以处理多个网络I/O,原理就是select、poll、epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达时,就通知用户进程。

i/o multiplexing

用户进程调用了select,整个进程会被block,同时,kernel会监听所有负责的socket,当有一个socket的数据准备好了,select会返回,用户进程再调用read操作,将数据从内核拷贝到用户进程。如果处理的并发连接数不是很高,使用select、poll、epoll的服务端性能不一定比使用multi-threading + blocking IO的服务端性能好。I/O多路复用时,每一个socket,一般设置为非阻塞模式。

5、I/O多路复用

  • select

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

select函数监视的文件描述符分3类,分别是readfds、writefds、exceptfds。调用select函数会阻塞,直到有描述符就绪或者超时,函数返回。当select函数返回后,可以通过遍历fdset,找到就绪的描述符。
select可以良好的跨平台。缺点是单个进程能够监视的文件描述符的数量存在最大限制。在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

  • poll

int poll(struct pollfd *fds, unsigned int nfds, int timeout)
struct pollfd {
  int fd;
  short events; // requested events to watch
  short revents; //returned events witnessed
}

pollfd结构包含了要监视的event和发生的event,不再使用select『参数-值』传递的方式。pollfd没有最大数量限制,但仍需要轮询返回的pollfd来获取就绪的文件描述符。随着监视的文件描述符数量的增长,效率也会线性下降。

  • epoll

    和select、poll相比,epoll更加灵活,没有描述符的限制。epoll使用一个文件描述符管理多个描述符,将用户进程的文件描述符事件存放在内核的一个事件表中,这样在用户空间和内核空间只需copy一次。
int epoll_create(int size)

创建一个epoll的句柄,size用来告诉内核监听的数目。size并不是限制了epoll所能监听的最大描述符个数,只是对内核初始分配内部数据结构的一个建议。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

函数对指定描述符fd执行op操作。op取值由三个宏表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。epoll_event告诉内核需要监听什么事。

typedef union epoll_data {
  void *ptr;
  int fd;
  _uint32_t u32;
  _uint64_t u64;
} epoll_data_t;
struct epoll_event {
  _uint32_t events; 
  epoll_data_t data;
};

//epoll events
EPOLLIN:表示对应的文件描述符可读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可写
EPOLLPRI: 表示对应的文件描述符有紧急数据可读(带外数据)
EPOLLERR: 表示对应的文件描述法发生错误
EPOLLHUP: 表示对应的文件描述法被挂起
EPOLLET: 设置为边缘触发
EPOLLONESHOT: 只监听一次事件,当监听完这次事件后,如果还需要监听这个socket,需要再次把这个socket加入epoll
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

参数events表示内核得到的事件的集合。maxevents告知内核这个events有多大,取值不能大于创建epoll_create()时的size。参数timeout时超时时间(ms,0表示立刻返回,-1表示不确定,也有说法是永久堵塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

6、epoll工作模式

两种:LT(level trigger), ET(edge trigger)。缺省是LT。

  • LT: 当epoll_wait检测到描述符事件发生时,将此事件通知应用程序。应用程序可以不立即处理该事件,下次调用epoll_wait时,会再次响应应用程序并通知。
  • ET: 当epoll_wait检测到描述符事件发生时,将此事件通知应用程序。应用程序必须立即处理该事件,下次调用epoll_wait时,不会再次通知此事件。

LT是缺省工作模式,支持block和non-block socket。ET是高速工作模式,只支持non-block。

7、总结

在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描。epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。epoll监视的描述符的数量不受限制,它所支持的FD上限是系统最大可打开文件数目。

原文地址:https://www.cnblogs.com/xianzhedeyu/p/6369379.html