用户空间与内核空间
内核的权限比较高,是操作系统的核心,内核和普通的应用程序不一样,他可以访问受保护的内存空间,也能直接的操纵硬件设备。为了区分开用户进程和内核,不让用户进程直接操作内核,就需要区分开内核空间和用户空间。
现在的操作系统普遍采用虚拟内存的方式,以32位系统为例,最大的寻址空间是4G,Linux下,将最高的1G划分为内核空间,0-3G划分为用户空间。
进程的切换
内核想要控制进程的执行,必须要有将正在CPU运行的进程挂起和将以前挂起的进程恢复到CPU上执行的能力。这种能力就是进程切换,任何进程都是在操作系统内核支持下运行的。
从一个进程切换到另一个进程执行,需要经过以下过程:
- 保存上下文,包括程序计数器和其他寄存器
- 更新PCB(进程控制块—>记录进程信息的数据结构,伴随进程的产生而产生伴随进程的消亡而消亡)信息
- 把进程PCB放入就绪队列或者事件阻塞队列,选择另一个进程执行,并更新其PCB
- 更新内存管理的数据结构
- 恢复处理机的上下文
进程的阻塞
当进程期待的某事件未发生的时候,比如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则进程就会自动执行阻塞原语,将自己从运行态转变为阻塞态,所以,进程的阻塞是一种主动的行为,所以只有处于运行态的进程才能转为阻塞状态,当进程转换为阻塞状态时,是不占用CPU的。
文件描述符
文件描述符是一个指向文件的引用的抽象化概念。文件描述符形式上是一个非负整数,实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表,当进程打开一个文件或者创建一个新文件的时候,内核就会向进程返回一个文件描述符。文件描述符这个概念只在Unix和Linux这种操作系统中存在。
标准IO / 缓存IO
大多数文件系统的IO默认都是标准IO,在标准IO中,系统会将IO数据先缓存在文件系统页缓存中,也就是先将数据放在内核缓冲区中,然后放进相对应的地址空间中。
缺点就是:需要进行大量的数据拷贝,对内存的消耗比较大
优点就是:库函数可以说是对系统调用的一种封装,因为系统调用是面对的是操作系统,系统包括Linux、Windows等,如果直接系统调用,会影响程序的移植性,所以这里使用了库函数,比如说C库,这样只要系统中安装了C库,就都可以使用这些函数,比如printf() scanf()等,C库相当于对系统函数进行了翻译,使我们的APP可以调用这些函数;
不同的操作系统对于系统调用的函数名字不一样,但是他们的库函数接口都是一致的,这样使用库函数去实现程序会增减程序的可移植性,因为只要安装了标准的C库,库函数就是一致的,库函数相当于把各种系统调用做了一个封装。
同步与异步 阻塞与非阻塞
同步和异步关注的是消息通信机制
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
关于阻塞/非阻塞 & 同步/异步更加形象的比喻
老张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
-
老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻
-
老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
-
老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大
-
老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。立等的老张,阻塞;看视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
IO多路复用
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用 epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要 一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。
poll解决了select只能监控1024的限制
epoll解决了poll和select每次都需要向内核空间拷贝和每次醒来都需要轮训确定哪个文件描述符准备好了的问题