计算机操作系统基础知识总结

进程与线程的区别

进程:一个正在执行程序的实例,它有程序、输入、输出以及状态。
(程序:用适当形式描述的算法)
线程:运行在进程上下文中的逻辑流
所有的线程都有完全一样的地址空间,共享相同的全局变量,所以一个线程甚至可以读、写、清除另一个线程的堆栈。所有线程还共享一个打开文件集、子进程、定时器以及相关信号。
每个线程有自己的堆栈,供各个被调用但是还没有从中返回的过程使用。

进程的状态、切换

进程的状态

运行态、就绪态(可运行,但因其他进程正在运行而暂时停止)、阻塞态(除非某种外部事件发生,否则进程不能运行)

操作系统发现不能继续运行下去,运行态转换为阻塞态。

时间片用完,运行态转换为就绪态;调度程序选中,就绪态转换为运行态。

进程等待的外部事件发生,阻塞态转换为就绪态。

运行态和就绪态的转换是由调度程序引起的,调度程序主要决定应当运行哪个进程、何时运行以及应该运行多长时间。

进程的切换

引发切换的原因:中断(对异步外部事件的反应)、陷阱(处理一个错误或异常条件)、系统调用。

上下文切换的步骤:1)用户态切换到内核态,保存当前进程的上文,2)调度算法选定新进程,恢复某个先前被抢占的进程被保存的上下文,3)将控制权传递给这个新恢复的进程。除此之外,还要使整个内存高速缓存失效。

进程切换代价较高。

写时复制

unix系统中,子进程共享父进程地址空间,一旦想要修改部分内存,这块内存首先被复制,以确保修改发生在私有内存区域。

windows中,从一开始父进程和子进程的地址空间就不同。

线程的创建和结束

线程的创建:pthread_create 返回新创建线程的线程标识符。

线程的结束:pthread_exit

pthread_join 阻塞调用线程知道(特定)线程结束。

守护进程、僵尸进程和孤儿进程

僵尸进程:一个进程退出但是它的父进程并没有在等待它

孤儿进程:当父进程先结束,子进程此时就会变成孤儿进程

进程间通信方式(共享内存、信号量、管道、消息)

  1. 共享内存:协作进程共享彼此都能读写的公共存储区。多个进程读写共享数据时,结果取决于进程运行的精确时序,一个进程在写的时候,另一个进程要注意读写的问题(互斥),相当于线程中的线程安全(线程本身就共享一块内存)。

互斥:屏蔽中断、锁变量、严格轮换法

  1. 信号量:本质是一个计数器。可用于解决生产者-消费者问题。
    对信号量实行down操作,检查其值是否大于0,若大于0,则减1;若该值为0,则进程将睡眠。(原子操作,不可分割)up操作对信号量的值加1。
    用于互斥(保证任一时刻只有一个进程读写缓冲区)和同步(保证某种事件的顺序发生或不发生),不是用于存储进程间通信数据。

  2. 管程:一个由过程,变量及数据结构等组成的一个特殊的模块或者软件包。任一时刻管程中只能有一个活跃进程(互斥)。可以理解为两个进程之间,建立一个通道,一个进程向这个通道写入数据,另一个从这个通道中读取数据,这个通道称为管道。

  3. 消息传递

生产者-消费者问题

两个进程共享一个公共缓冲区,生产者将信息放入缓冲区,消费者从缓冲区取出消息。

当缓冲区已满,而此时生产者还想往缓冲区添加数据。解决方案:让生产者睡眠,待消费者从缓冲区取出一个或多个数据项时再唤醒生产者。当缓冲区为空时,而此时消费者试图取出数据。解决方案:让消费者睡眠,待生产者向其放入数据项时再唤醒。

实施:cout值跟踪缓冲区数据量->当count达到缓冲区max时,使生产者睡眠;->当count为0时,使消费者睡眠。

缺点:对count的访问未加限制,导致发给一个(尚)未睡眠进程的唤醒信号丢失了。消费者刚刚读取count=0,调度程序切换启动生产者,生产者添加一个数据,count=1,并向消费者发送唤醒信号。但此时消费者并未睡眠,所以唤醒信号丢失。当调度程序再启动消费者,消费者根据以前读到的count=0,睡眠。生产者迟早填满缓冲区,然后睡眠。

弥补:加一个唤醒等待位(唤醒信号的小仓库)。但当多个进程时,一个唤醒等待位将不够,没有从根本上解决问题。

死锁

定义

一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件,那么该进程集合就是死锁的。

资源死锁:每一个死锁进程都在等待另一个死锁进程已经占有的资源,但由于所有进程都不能运行,它们中任何一个都无法释放资源,所以没有一个进程可以被唤醒。(竞争同步)

通信死锁:进程A向进程B发送请求信息,然后阻塞直至B回复,假设请求信息丢失,A将阻塞以等待回复,而B会阻塞等待一个请求信息,因此发生死锁。(超时可以解决通信死锁和资源死锁)

死锁四个必要条件

  1. 互斥条件:一个资源只能被一个进程占用,直到被释放。
  2. 占有和等待条件:已经得到某个资源的进程,即使被阻塞,也不释放该资源。并且请求新资源。
  3. 不可抢占条件:已分配给进程的资源不能强制性被抢占。
  4. 环路等待条件:当发生死锁,一定有两个或两个以上的进程组成一条环路(类似死循环),该环路中的每个进程都在等待下一个进程所占有的资源。

死锁恢复

复杂度递增

  1. 取消所有死锁进程(最常用)。
  2. 把死锁进程回滚到前面定义的某些检查点,并且重新启动。原来死锁可能再次出现,但并发进程不确定性通常保证不会再次发生。
  3. 连续取消直到不再存在死锁。
  4. 连续抢占资源直到不存在死锁。

死锁预防

  1. 破坏互斥条件:假脱机技术。
  2. 破坏占有并等待条件:在开始就请求全部资源,资源利用率不是最优的。
  3. 破坏不可抢占条件:抢占资源,用虚拟化解决可能的混乱。
  4. 破坏环路等待条件:对资源按序编号,资源请求必须按照升序方式提出或不允许请求比当前占有资源编号低的资源。缺点是当不同种类资源很多时,难以编号。

活锁

当进程意识到不能获取某个资源时,会尝试礼貌性释放已经获得的资源,然后等待1ms,再尝试。如果另一个进程在同样的时刻做了相同的操作,相同的步调导致双方都无法前进。这个过程中没有进程阻塞(不是死锁),双方进程都正在活动,只是不停的重复尝试,但进程不会继续往下执行。

饥饿

分配资源时,一些进程永远得不到服务,虽然并不是死锁(没有阻塞)。例如,只把打印机分配给最小文件的进程,那么大文件的进程永远得不到打印机,她会被无限制地推后,尽管没有被阻塞。

通过先来先服务避免。

并发经典的问题:读者写者、哲学家就餐

读者写者

一个飞机订票系统,有许多竞争进程试图读写数据。多个进程可以同时读数据,但一个进程在写数据时,其他所有进程不能访问。
第一个读者对信号量实行down操作(保证读者和写者互斥)。随后如果有读者,则递增一个计数器,当有读者离开时,递减这个计数器。最后一个读者离开时,对信号量执行up操作(当读者完全离开时,允许被阻塞的写者访问)。
如果是一个连续的读者流,则写者永远无法进入。改进:写者只需等待前一个读者完成,而不必等待后面到来的读者。并发度和效率较低。

哲学家就餐

使用一个数组state跟踪每个哲学家是在进餐、思考还是饥饿。只有在两个邻居都没有进餐时才允许进入就餐状态。每个信号对应一个哲学家,这样所需叉子被占用时,想进餐的哲学家被阻塞。

进程和线程的调度

调度算法分为三种环境:1)批处理,2)交互式,3)实时。

批处理

  • 先来先服务:按照请求CPU的顺序使用CPU。
  • 最短作业优先
  • 最短剩余时间优先:选择剩余时间最短的哪个进程运行。

交互式

  • 轮转调度:给每个进程分配一个时间片,允许进程在该时间段中运行,当时间片结束时,即剥夺CPU给下一个进程。
  • 优先级调度:允许优先级最高的可运行进程先运行。在每次中断降低当前进程优先级,当这一行为导致该进程优先级低于次高级时,进行进程切换。
  • 多级队列:属于最高优先级类的进程运行一个时间片,属于次高级类进程运行两个时间片,以此类推。减少交换次数。
  • 最短进程优先
  • 保证调度:CPU处理能力均分给每个用户。
  • 彩票调度:随机抽取一张彩票,拥有彩票的进程获得该资源。
  • 公平分享调度:不仅关注进程本身,而也应关注所有者用户。

实时

软实时:可以容忍偶尔错失截止时间。
硬实时:必须满足绝对的截止时间。

并发和并行

并发:指应用能够交替执行不同的任务。
并行:指引用能够同时执行不同的任务。

物理内存和虚拟内存

虚拟存储技术实质

虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。
这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。

操作系统通过将不同进程中适当的虚拟页面映射到相同的物理页面,从而使得多个进程共享这部分代码的一个副本,而不是在每个进程中都包括单独的内核和C标准库的副本。

页表

页表将虚拟页映射到物理页,每次地址翻译硬件将一个虚拟地址转化为物理地址时,都会读取页表。下图是一个页表的基本组织结构。虚拟地址空间中的每个页在页表中都有一个固定偏移量处都有一个PTE(页表条目)。设置有效位1,表示DRAM中相应物理页的起始位置。没有设置有效位0,表示这个虚拟页还未被分配。

页命中

例如当CPU想要读VP2中的一个字时,地址翻译硬件将虚拟地址作为一个索引来定位PTE2,因为有效位为1,所以硬件翻译硬件就知道VP2时缓存在DRAM中的,使用PTE中的物理地址构造这个字。

缺页

DRAM缓存不命中称为缺页。例如CPU想要读取VP3中的一个字,地址翻译硬件读取PTE3,有效位为0,触发缺页异常程序。该程序选择一个牺牲页,如存放在PP3中的VP4。(如果VP4已经被修改,那么将它复制回磁盘)接下来,内核从磁盘复制VP3回到内存中的PP3,更新PTE3。当异常处理程序返回时,重新启动导致缺页的指令。

地址翻译

地址翻译是一个虚拟地址空间元素到物理地址空间元素的映射。下图展示地址翻译硬件如何利用页表来实现这种映射。

页表将虚拟地址中的VPN与物理地址中的PPN串联起来。因为物理和虚拟页面都是P字节,所以PPO和VPO是相同的。

页命中CPU执行步骤

  1. 处理器生成一个虚拟地址,并把它传送给MMU(地址翻译硬件)。
  2. MMU生成PTE地址(VPN虚拟页号作为页表中的索引),并从主存请求得到它。
  3. 主存向MMU返回PTE(通过VPN索引到该PTE)。
  4. MMU构造物理地址(读取PTE中的物理页号PPN),并把它传送给主存。
  5. 主存通过PPN(物理地址的索引)查找所请求的数据,并返回给处理器。

缺页CPU执行步骤

页面命中完全由硬件处理,而缺页时要求硬件和软件协作完成。
1-3.与页面命中时相同。
4. PTE中的有效位是0,触发缺页异常处理程序。
5. 缺页处理程序确定物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出磁盘。
6. 缺页处理程序调入新的页面,更新内存中的PTE。
7. 缺页处理程序返回到原来的进程中,再次执行导致缺页的指令。CPU将导致缺页的虚拟地址重新发给MMU。

页面置换算法

  • 最优页面置换算法:标记每个页面还需多少条指令才会被使用,每次置换掉标记最大的页面。不可实现,基准。
  • 最近未使用(NRU)页面置换算法:淘汰一个在最近时钟滴答中没有被访问的页面。有效实现,性能不是最好。
  • 先进先出页面置换算法(FIFO):淘汰表头先进入的页面,把最新调入的页面加到表尾。可能淘汰常用页面。
  • 第二次机会页面置换算法:检查最老页面R位,如果R是0,淘汰又老又没有被使用页面,如果R是1,将R位清零,放到表尾。经常在链表移动页面,降低效率。
  • 时钟页面置换算法:所有页面保存在环形链表中,表针指向最老页面,检查表针指向页面的R位,R=0,淘汰该页面,把新页面插入这个位置,表针前移(相当于放到尾端);R=1,清除R并向前移动指针。
  • 最近最少使用页面置换算法(LRU):置换未使用时间最长的页面。代价高。
  • 最不常使用算法(NFU):计数器跟踪页面被频繁访问的程度。置换计数器值最小的页面。
  • 工作集页面置换算法:工作集使最近k次内存访问所使用过的页面集合。每次淘汰不在工作集的页面。
  • 工作集时钟页面置换算法:每次缺页中断时,首先检查指针指向的页面,如果R为1,该页面在当前的时钟滴答中被使用,该页面不适合被淘汰。然后将页面R为0,指针指向下一个页面并重复该算法。R=0时,如果页面的生存时间大于规定时间间隔且该页面是干净的,那么它就不在工作集中,且在磁盘上有一个有效的副本,申请此页框并把新页面放在上面,如果该页面被修改过,则不能直接申请,指针继续下一个页面。

Linux 内核部分实现原理,如内存管理、进程管理、虚拟文件系统

linux进程管理

  • 系统调用fork创建一个与原始进程完全相同的进程副本。父子进程可以共享已经打开的文件。fork给父进程返回一个非零值,为子进程的进程标示符;给子进程返回零值。
  • 进程间通信:
    管道:shell中的管线就是管道技术。在两个进程间建立一个管道,一个进程产生的输出直接作为另一个进程的输入而不必写到一个文件中。
    软中断:一个进程给另外一个进程发信号,另一个进程调用信号处理函数,处理完之后,返回到陷入点处。
  • pid=waitpid(pid,&statloc,opts)等待直到子进程结束运行
    1) pid:控制等待哪些子进程
    a) pid>0,等待进程ID为pid的子进程返回
    b) pid=-1,等待任一子进程结束即可返回
    2) statloc:存储子进程退出状态的变量地址
    3) opts:如果没有子进程结束运行,决定调用者(父进程)是阻塞还是返回
  • 进程和线程的实现:Linux中不区分进程和线程,所有执行上下文都通过数据结构task_struct被表示成任务。
  • clone(function,stack_ptr,sharing_flags,arg)通过参数sharing_flags指定那些项可以共享,哪些需要复制。多个进程之间共享某些资源(如虚拟内存、页表、文件描述符等),函数调用栈、寄存器等线程私有数据则独立。
  • linux中的调度:分为三类,实时先入先出(最高优先级)、实施轮转(时间片)、分时
    O(1)调度器:将调度队列分为两个数组,一个是任务正在活动的数组、一个是任务过期失效数组。
    完全公平调度器(CFS):红黑树,周期性根据任务已经运行时间,递增它的虚拟运行时间值,并将这个值与树中当前最左节点的值进行比较,如果仍然具有较小虚拟运行时间值,它将继续,否则被插入红黑树适当位置,CPU执行新的最左边节点上的任务。
  • linux进程的创建(启动Linux系统)
    手动运行进程0,进程0创建init进程(进程1)和页面守护进程(进程2)。
    init进程开始运行时,读入一个说明终端数量的文件。接着为每个终端创建一个新进程。这些进程等待用户登录getty,如果有一个用户登录成功login,该登录进程就执行一个shell准备接受命令,所接收的这些命令会启动更多的进程。
    进程的层次结构,所有的进程都属于以init为根的一棵树。

linux内存管理

  • 内存分配机制:
    页面分配器采用伙伴算法,任何2的幂次大小的块可以快速找到,申请非2的幂次的块导致大量内部碎片。
    slab分配器:使用伙伴算法获得内存块后,再从中切出更小的单元分别进行管理。
    vmalloc:适用于连续虚拟地址空间的请求。

Linux I/O系统

  • 所有的I/O设备都被当做文件来处理
  • 套接字:(类似于邮筒和电话插座,邮筒连接到邮政系统)套接字允许用户连接到网络。一方在本地套接字使用listen系统调用,另一方使用connect系统调用,并且把本地套接字的文件描述符和远程套接字的地址作为参数传递进去,如果远程接受了此次调用,则系统在两个套接字之间建立一个连接。功能类似管道。使用本地套接字的文件描述符来从中读写数据。

linux文件管理

当出现多个进程同时使用一个文件时,出现竞争,可以进行加锁:共享锁和互斥锁。如果一部分已经被加了共享锁,那么继续加共享锁是可以的,不能加互斥锁。

猜你喜欢

转载自blog.csdn.net/ClaireSy/article/details/111228875