socket_IO模型
1 背景知识
1.1 用户空间和内核空间
比如32位系统,寻址空间是4G。内存分为用户空间和内核空间,内核空间仅供内核使用,用户空间给各个进程使用。os的核心是内核,只有内核可以访问被保护的内存空间。为了保证进程无法直接操作内核 ,在用户空间的进程无法访问内核空间。
系统调用函数运行于内核态,但是向用户态开放接口。
对于磁盘,网卡等设备,进程都无法直接访问,所以相关数据都用经过系统调用。
1.2 进程阻塞
正在执行的进程,由于期待的事件未发生,比如io操作未完成,新数据未到达,就由系统自动执行阻塞,由运行状态变为阻塞状态。所以,进程的阻塞是自身的自动行为,只有处于运行状态(获取cpu时间片)的进程才能转为阻塞状态。并且处于阻塞状态的进程不占用cpu。
1.3 文件描述符(句柄)
fd是指向具体文件的一个引用。
fd是一个非负整数。当进程打开一个新文件或者socket,系统就为该文件分配一个fd,用来标识这个文件,这个分配的数字从0开始。系统默认情况下,限制每个进程能打来的文件为1024。
1.4 标准io
在标准io机制中,在内核中有页缓冲(内核高速缓冲),在用户空间有流缓冲。
对于读操作,数据首先被读到页缓冲(当页缓冲中没有该数据),然后再拷贝到进程的内存地址。
对于写操作,数据先被写到流缓冲,然后将多次写操作一次性写到页缓冲(减少系统调用),页缓冲再将多次写操作一次性写到磁盘(提高磁盘io效率)
2 linux中的几种IO模型
网络io的本质是socket的读取。对于一次网络io请求,有两个阶段:
a 等待对端传递的数据就绪,就绪就拷贝到系统内核缓冲区,如果没有就绪就一直等待就绪。
b 将内核缓冲区中的数据拷贝到进程的地址空间中。
2.1 bio
阻塞io是最常用的socket模型,并且默认情况下,所有socket都是阻塞的。
首先,进程要请求网络数据时,使用recvfrom系统调用函数,然后系统调用从用户空间转移到内核空间运行。在内核空间等待对端数据报的到达,到达后内核空间的缓冲区后,把数据报拷贝到用户空间应用的缓冲地址。这个过程完成后,就返回获取成功给进程,进程没有延迟,开始处理数据报。
整个过程中,进程都是自我阻塞的,在等待数据就绪的过程中,进程只能等待,无法作其他事情。好处是数据一就绪,进程就马上可以进行下一步的处理。
2.2 nio
在非阻塞模型中,当系统调用发现内核空间中未准备好数据,则直接返回一个error。进程会周期性轮训,询问内核空间是否准备好好数据。在此期间,进程不会等待(就是非阻塞)。
相对于阻塞模型,非阻塞模型释放了进程,可以同时处理其他信息。但由于是周期性轮训,进程就不会在第一时间知道网路数据已准备好,所以增加了延迟。
但如果进程要处理多个socket,非阻塞模型总是比阻塞模型效率更高。
2.3 mio
就是用select、epoll、poll,将多个socket的文件描述符传给内核,让内核去监听它们是否就绪。
当用户进程调用 select,整个进程就被阻塞,当任何一个socket的数据在内核中 准备好了,select就会将可读条件返回。然后进程使用系统调用,将数据拷贝到用户空间(这一步就相当于bio)。
nio是进程主动去询问多个socket的数据在内核中是否准备好,而mio是采用select去实时监听多个socket在内核中的数据是否准备好。
2.4 aio
进程使用aio_read函数将socket文件描述符,缓冲区指针、缓冲区大小和文件偏移传递给内核,并且告诉内核等数据准备好后直接传给aio_read中。
进程不阻塞,数据准备好后,不需要进程再通过系统调用去读。
2.5 比较
bio,nio,mio,在数据拷贝到用户空间这个阶段进程会被阻塞,属于同步模型。
aio在这个阶段进程不被阻塞