目录:
一. 事件驱动
1、什么是事件驱动
- 定义:就是根据不同事件触发处理不同的事情(根据事件做反应),将要处理的事件加入队列中而不是立刻处理
2、使用UI编程的点击事件理解事件驱动
- 1) 目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件(点击事件)
- 2) 这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
-
第一步:首先得有一个消息队列,来存放要触发的事件
-
第二步:鼠标按下时,往这个队列中增加一个点击事件(消息),而不是立刻执行;
-
第三步:有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等
-
第四步:事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
-
3、创建一个线程监控点击事件的缺点
- 1) CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;
- 2) 如果扫描鼠标点击是堵塞的,又会出现下面这样的问题,如果点击后要执行一件事,
这件事如果是阻塞的需要花5秒,那么在这五秒里鼠标就无法点击第二下 - 3) 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;
二. 五种I/O操作
1、I/O的实质是什么?
- I/O的实质是将硬盘中的数据,或收到的数据实现从内核态 copy到 用户态的过程
- 本文讨论的背景是Linux环境下的network IO。
2、5种I/O操作的本质
1. I/O操作举例说明:
- 1)刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中
- 2)然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间
2. 当一个read操作发生时,它会经历两个阶段:
- 1)等待数据准备 (Waiting for the data to be ready),等待将数据(比如文件)读到内核的内存中
- 2)将数据从内核拷贝到用户的进程里进程中 (Copying the data from the kernel to the
process)
3. 因为这两个阶段,linux系统产生了下面五种网络模式的方案
- 1)- 阻塞 I/O(blocking IO)
- 2)- 非阻塞 I/O(nonblocking IO)
- 3)- I/O 多路复用( IO multiplexing)
- 4)- 异步 I/O(asynchronous IO)
- 5)- 信号驱动 I/O( signal driven IO)
- 注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。
3、与I/O相关的五个重要概念
1. 用户空间和内核空间
2. 进程切换
3. 进程的阻塞
4. 文件描述符
5. 缓存 I/O
1. 第一个概念:用户空间与内核空间
- 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)
- 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
- 为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分:一部分为内核空间,一部分为用户空间
- 针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。
2. 第二个概念:进程切换
- 为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换
- 从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
- 1)保存处理机上下文,包括程序计数器和其他寄存器。
- 2)更新PCB信息。
- 3)把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
- 4)选择另一个进程执行,并更新其PCB。
- 5)更新内存管理的数据结构。
- 6)恢复处理机上下文。
- 注:总而言之就是很耗资源
3. 第三个概念:进程的阻塞
- 正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚
未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。 - 可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态
- 当进程进入阻塞状态,是不占用CPU资源的。
4. 第四个概念:文件描述符fd
- 文件描述符在形式上是一个非负整数,实际上,它是一个索引值, 指向内核为每一个进程所维护的该进程打开文件的记录表
- 当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符
- 在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开
- 但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统
5. 第五个概念:缓存 I/O
- 缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O
- 在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中。
- 数据会先拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核缓冲区拷贝到应用程序的地址空间 缓存 I/O 的缺点:
- 数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。
4.1 阻塞 I/O(blocking IO)
- 使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。
- 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
- 1) 进程首先处于阻塞状态调用recv方法,想要去接收数据
- 2) 然后就会到内核中去读取数据,如果开始内核中没有data
- 3) 进程就会阻塞,过一会数据来了,然后进程就将数据从内核copy到用户那里
- 4) Copy结束后就会return OK,用户态就收到数据了
4.2 非阻塞 I/O(nonblocking IO)
- 改变flags,让recv不管有没有获取到数据都返回,如果没有数据那么一段时间后再调用recv看看,如此循环。B同学也用杯子装水,打开水龙头后发现没有水,它离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,B同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。
- linux下,可以通过设置socket使其变为non-blocking
- 当对一个non-blocking socket执行读操作时,流程是这个样子:
- 1)当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block 用户进程,而是立刻返回一个error。
- 2)从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。
- 3)用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作
- 4)一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
- 注:所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。
4.3 I/O 多路复用( IO multiplexing)
- 特点: 用户还是要等待数据从kernel拷贝到用户进程
- IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO
- select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
- 它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程
- 1)当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket
- 2)当任何一个socket中的数据准备好了,select就会返回
- 3)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
- 4)虽然是多并发但是还有一些卡,因为都要等待数据从kernel拷贝到用户进程
- 5)其实select,poll,epoll实质就是循环着接收数据
- 6ÿ