引言
- 最近利用下班的空闲时间看了看《现代操作系统》,感觉讲得非常好,虽然某些细节仍不理解,但是受益匪浅,让我对计算机有了全新的认识;
- 这篇博客总结了操作系统的基本概念,如果以后有用到其中的知识,我准备再深入研究细节;
为什么要有操作系统
- 空间上,为上层应用程序提供一个清晰的、一致的、友好的硬件抽象,由于硬件种类繁多,软件编写者不可能就各种不同的硬件接口编写不同版本的程序,所以屏蔽硬件的不一致性的任务就交给操作系统;
- 时间上,保证各个程序有序运行,并充分利用硬件资源,比如时分复用、线程调度等都是具体实现;
有关CPU的概念
- CPU的两种模式为内核态和用户态,为了安全,在内核态时CPU可使用全部硬件,用户态时不行;
- 每种CPU都有自己的指令集,用于执行取指、解码、执行的各个过程;
- CPU有自己的寄存器,用于存储计算暂时结果,它比内存快很多;
- 时间多路复用/时分复用/多线程技术/超线程技术:CPU切换执行不同的线程,线程执行进度保存在程序计数器中(本质是一个寄存器),当切换到其他线程前,需要保存所有寄存器的状态,以便下次执行这个线程时重新装入这些寄存器的值;
- 流水线:CPU的取指、解码和执行单元相互分离,执行指令n时,可以对n+1条指令解码,并读取指令n+2;
- 超标量技术:CPU的取指、解码和执行这些指令级的运算的并行运算技术,实现超标量的物理基础是CPU的算术逻辑单元、位移单元、乘法器相互分离,可以并行执行多条指令;
- 多核技术: 一个CPU上装有多个小芯片,每个芯片能够独立完成取指、解码和执行等过程;
进程
- 进程是正在执行的一个程序;
- 每个进程有其单独的地址空间,存有:可执行程序、程序的数据、程序的堆栈,如果进程申请的地址空间大于内存,则使用虚拟内存技术来解决;
- 每个进程有其单独的资源集,包含:程序计数器、堆栈指针、寄存器等;
- 运行过程:当一个CPU核心切换到某个进程时,为这个进程分配内存资源和CPU核执行时间资源(时间片),当时间片用完后,当前运行的指针被保存下来,进程被暂时挂起,CPU核切换到另一个进程执行;
- 守护进程:一种在后台执行的进程(电子邮件、web页面、新闻);
- 句柄:父进程和子进程之间的通信;
- 进程状态:就绪、运行、阻塞,在这三种状态间切换叫做进程调度;
- 进程调度的例子:
- 某一进程不能继续运行(如进程的输入尚未准备好)–阻塞;
- 某一进程运行时间太长,CPU需要切换到其他进程–就绪;
- 某一进程开始执行–运行;
线程
- 线程是比进程更小的一个概念,一个进程可以包含多个线程;
- 所线程共享其所在进程的地址空间、内存资源、全局变量、打开文件,但不能同时读取同一块内存(线程锁控制);
- 线程自己拥有的只是一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈);
- 进程和线程的区别:
- 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
- 具体请参考这篇文章;
进程间通信
- 为什么进程间要有通信机制?
- 考察命令
cat abc.txt | grep "sss"
,上述命令中,cat启动一个进程,这个进程的结果要传给grep启动的这个进程,这就需要进程间通信; - 两个打印进程如果没有通信(CPU轮训执行这两个进程),同时向打印机的打印队列中放入待打印的文件名,并将打印队列的指针移动一位,就可能出现错误,比如A进程覆盖B进程的待打印文件;
- 考察命令
- 进程间的竞争问题的解决
- 总的思想:阻止多个进程同时读写共享的数据;
- 临界区:进程的共享内存或共享文件成为临界区,不得使多个进程同时进入其临界区;
- 临界区的具体实现方法:
- 当一个进程进入临界区后,CPU屏蔽中断,此时,CPU无法切换到其他进程,直到当前进程退出临界区;
- 当一个进程进入临界区后,将锁变量设置为1,表示临界区内有进程,其他进程直到锁变量变为0后才能进入临界区;
- 上述方法的缺点:
- 如果当前临界区内有进程,则另一个想要进入临界区的进程就不得不等待,直到临界区内无进程,这非常耗时;
- 如果有一个高优先级H进程、低优先级L进程,L在临界区中,H在临界区外,H等待L退出临界区,但是由于L优先级比H低,L不会被调度、不会出临界区,则H一直在等待;
- 信号量和互斥量:
- 信号量和互斥量是两个非常重要的进程间通信的机制,两者不同,互斥量的本质是二元信号量;
- 信号量的例子:维护一个打印队列,最长队列为N,最短为0;生产者可以将打印文件名放入队列,消费者可从打印队列中取出文件名;生产者进程维护一个信号量记录队列中占用的槽总数,初始信号量为0,每放入一个信号量加一,信号量为N时生产者进程阻塞;消费者进程维护一个信号量记录队列中空的打印槽,初始信号量为N,每取出一个信号量减一,信号量为0时生产者进程阻塞;另外,为了保证生产者进程、消费者进程不同时修改打印队列(临界区),另外维护一个二元的信号量mutex(即互斥量),它的值只能为0或1,每当有进程进入临界区,其值为1,其他进程不能进入,进入临界区的进程退出后,mutex值变为0,其他进程可以进入临界区;
- 两者的区别:互斥量用于线程的互斥(二元,要么锁住,要么解锁),信号量用于线程的同步(多元);从作用域来说,信号量是进程间或线程间(linux仅线程间),互斥锁是线程间;
进程调度
- 为什要有进程调度?当CPU要选择执行哪个进程的时候,如果两个或多个进程处于就绪状态,这时就存在选择的问题,需要进程调度解决;
- 何时启动进程调度机制?
- 在创建一个新进程后,决定运行父进程还是子进程;
- 在一个进程退出时,系统必须选择另外一个处于就绪状态的进程执行,否则会浪费CPU资源;
- 当一个进程阻塞时,必须选择另一个进程运行;
- 在一个I/O中断发生时,必须做出调度决策;
- 进程调度的目标:
- 公平:地位等价的进程占用CPU资源的程度应当差不多相等;
- 效率:让CPU以及I/O资源尽可能忙碌,所有部分不要闲着,这就需要对“CPU密集型进程”“I/O密集型进程”进行合理调度;
- 进程调度的两种策略:
- 非抢占式:CPU挑选一个进程,让它运行直至被阻塞(阻塞在IO或者等待另一个进程)或自行释放CPU;
- 抢占式:让一个进程运行一段时间,强行挂起该进程,让另一个进程运行;
- 具体的进程调度策略:
- 轮转调度:所有进程排成一个圆圈,转圈圈调度;
- 优先级调度:动态赋予进程优先级,优先级高的先执行;
- 多级队列调度:一个进程需要100个时间片才能完成,CPU每次执行它时只执行一个时间片的时间,如果采用轮转调度,要频繁的从磁盘取出和写入进程,但是如果该进程在第一次获得CPU时分配1个时间片、在第二次获得CPU时分配2个时间片、在第三次获得CPU时分配4个时间片、在第四次获得CPU时分配8个时间片等等,这样就能减少磁盘读写,提升CPU密集型的进程的执行效率,这就是多级队列调度;
- 死锁:
- 定义:如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件,那么该进程集合就是死锁的;
- 死锁的条件和避免死锁的方法有严格的逻辑推导,这里不再赘述;
- 哲学家就餐问题
存储管理
- 直接使用物理地址的危害:
- 用户程序可以寻址内存的每个字节,很容易破坏操作系统,除非有特殊的硬件保护;
- 如果每个用户程序都直接使用内存的所有物理地址,并行的执行多个用户程序变得困难;
- 解决方法:给每个用户进程分配单独的地址空间;
- 地址空间:
- 地址空间是对内存这个硬件的抽象;
- 具体实现:用基址寄存器和界限寄存器确定进程的地址空间,这两个寄存器是CPU的重要硬件;
- 交换技术(解决物理内存不够大的问题):在物理内存不够大时,可以将目前未在执行的进程存回磁盘,运行时再调入内存;
- 虚拟内存(解决物理内存不够大的问题):地址空间映射的物理内存在内存条上是不连续的、甚至有些在磁盘中,但是暴露给进程看的虚拟内存地址是连续的;
- 虚拟内存/分页:
- 每个进程拥有自己的地址空间;
- 这个地址空间被分为许多内部地址连续的块,即“页”,和页相关的信息存储在页表中;
- 每一页都能够映射到物理内存中,“页”在物理地址中称作“页框”,映射关系由内存管理单元(MMU)统一管理,如果要使用一个地址的数据,虚拟地址首先被送到MMU转换为物理地址;
- 必要时可以有多级的映射关系;
- 缺页中断:当MMU发现虚拟内存地址映射的物理地址不在内存中时,向CPU发送缺页中断;
- 页面置换:当发生缺页中断时,必须选择一个页面换出内存,这样才能将磁盘中需要的数据换入内存;
- 页面置换算法的效率问题:如果一个页面即将被使用但是被换出了内存,这样就很低效,为了提升效率,有如下的页面置换算法:最近未使用页面置换算法、先进先出算法、二次机会页面置换算法、时钟页面置换算法、最近最少使用页面置换算法;
- 空闲内存管理:
- 位图:内存被分为若干多个内存单元(几个字-几千字节都可以)进行管理;每个内存单元要么空闲、要么占用,以“0”“1”映射到位图中;搜索位图中的连续空闲地址比较耗时;
- 链表:维护一个链表,每个节点记录一块空闲的内存区域的起始地址、长度、指向下一个链表的指针;当两个空闲区域相邻时,将链表中的相邻两个结点合并即可;