IO多路复用概念性

时间:2023-12-18 20:29:56

sellect、poll、epoll三者的区别

先来了解一下什么是进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行,这种行为为进程的切换,任务切换,或者上下文切换

转载:

http://www.cnblogs.com/alex3714/articles/5876749.html

http://www.cnblogs.com/alex3714/p/4372426.html

https://segmentfault.com/a/1190000003063859

进程切换详细概念

任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的.

从一个进程的运行转到另一个进程上运行,这个过程经过这些变化

1.保存处理机上下文,包括程序计算器和其他寄存器

2.更新PCB信息 (也就是进程控制块,存放着操作系统用于描述进程情况及控制进程运行所需的全部信息)

3.把PCB信息 移入相应的队列,如就绪,在某件事阻塞等队列

4.选择另一个进程执行,并更新PCB信息

5.更新内存管理的数据结构

6.恢复处理机上下文

所以很消耗资源

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓冲I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

IO模式

对于一次性IO访问(以read为例子),会将数据先拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间,

当一次read操作发生时

A.等待数据准备

B.将数据从内核拷贝到进程中

正因为有上面2个步骤,linux系统产生了下面5种网络模式方案

---阻塞 I/O           ->收快递,快递如果不到,就干不了其他的活

---非阻塞I/0       ->收快递,不断的去问,有没有送到,有没有送到,...如果送到了就接收

---I/O多路复用      ->找个代理人(select), 去收快递。快递到了,就通知用户.  

---信号驱动I/O  (不常用)

---异步I/O        ->比较复杂,看转载内容吧.

主要来了解下I/O多路复用内容:

1.这就是我们说的select,poll,epoll ,好处就是单个进程(process)就可以同时处理多个网络连接的IO,基本原理就是select这个函数,会不断的轮询所负责的所有socket

2.当某个socket有数据了,就返回给用户。 当用户调用了select,那么整个进程会被阻塞(block).

3.同时,kernel会'监视'所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回,这个时候用户在调用read操作,将数据从kernel拷贝到用户进程

4.所有IO多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读的状态,select函数就可以返回 ----   select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作 -

5.select/epoll的优势并不是单个连接能处理得更快,而是在于处理更多的连接

6.在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

-----还有很多详细的知识点,还是见文章顶头吧

blocking和non-blocking的区别

调用blocking IO 会一直block 对应的进程,直到操作完成

调用non-blcoking在kernel还没准备好数据的情况下,会立即返回

synchronous IO和asynchronous IO的区别 (同步IO和异步IO区别)

两者的区别: 同步IO在IO操作(IO operation)的时候会将整个进程阻塞,......(这里居然blcoking和non-blocking都是同步IO------!!!)

因为!:定义中所指的"IO operation"(IO操作)是指真实的IO操作, 当kernel中数据准备好的时候,recvfrom(接收数据) 会将数据从kernel拷贝到用户内存中,这个时候进程是被block了。

然而,异步IO则不一样,当进程发起IO操作之后,就直接返回了再也不用管了,一直等到kernel发送一个信号,告诉进程说IO完成,在这个过程中,进程完全没有被block.

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间

select

是通过一个select()系统调用来监视多个文件描述符的属组,当select()返回后,该数组中就绪的文件描述符便会内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作

select()一个缺点,单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制

select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销

 select(rlist, wlist, xlist, timeout=None)
 readable, writeable, exeptional = select.select(inputs,outputs,inputs)   #加入select监控

select函数监视的文件描述符分为3类,

1.read_fds  监视的可读文件句柄集合

2.write_fds 监视的可写文件句柄集合

3.excepr_fds 监视的异常文件句柄集合

4. timeout 本次select()的超时结束时间。

但是! 调用select()函数会阻塞,直到有描述符就绪(有数据 可读,可写,或者except,或者timeout),当select函数返回后, 可以遍历fdset(文件描述符集合),来找到就绪的描述符--

    这里我有点模糊。但是没关系,后面有个ftp的server端用到这块,就明白了--

poll----不会!!

select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

而且!python的select()方法是直接调用的操作系统的IO接口,这个接口监控了sockets,openfiles, and pipie(所有带fileno()方法的文件句柄) 何时变成readable(可读的)和writeable(可写的)或者通信错误,,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器

在上面第二个代码中,:select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息。

IO多路复用概念

#补:
关于缓冲IO: 在linux的缓冲IO中,操作系统会将IO数据缓存在文件系统的页缓存中,也就是说,数据会被先拷贝到内核空间,然后在从内核空间拷贝到用户空间
通俗来说,应用程序是不能直接去访问邮件的,一定是调用操作系统的接口,数据会被先copy到内核态,然后再从内核态copy到用户态 
那总不能一个字节的数据过来一次就copy一次,那多没有效率,所以就有了缓存区, 等缓存区满了后,会统一把数据copy到用户态.
通常,我们写服务器处理模型的程序时,有以下几种模型:
(1)每收到一个请求,创建一个新的进程,来处理该请求;
(2)每收到一个请求,创建一个新的线程,来处理该请求;
(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求
上面的几种方式,各有千秋,
第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。
第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。
第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。
综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式
回调函数:特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

三种网络编程范式:

单线程 同步
多线程
事件驱动编程模型  最主流的,底层就是通过事件驱动来实现的, 采取异步之后 效率就会变高了。 nginx就是采用这个异步模式
那协程怎么实现检测所有的IO:每个IO来的时候,就会注册一个事件,然后协程自己还有一个线程不断的去事件驱动队列中,拿出这个事件来处理.
事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题(多线程中锁的问题)

那在什么情况下使用事件驱动模型?

1.程序中有很多任务,
2.任务之间高度独立(就是它们之间不需要互相通信,或者彼此等待)
3.在等待事件到来时,某些任务会阻塞
4.当应用程序需要在任务间共享可变的数据时,这也是个选择,因为这里不需要采用同步处理.
事件驱动模型中,只要一遇到IO操作就注册一个事件,然后主程序就可以继续干其它的事情了,只要IO处理完毕后,继续恢复之前中断的任务
就用到了select模块!!
 而且!注意 写IO多复用程序,如何一个地方都不能有阻塞,只要有阻塞,整个程序就会卡,因为它是单线程啊!!