您的位置:首页 > 其它

操作系统(二)之进程与线程

2018-03-27 22:43 169 查看
自己写的操作系统学习笔记,如有雷同不胜荣幸,如有错误敬请指正

1. 进程

在进程模型中,计算机上所有可运行的软件,通常也包括操作系统,被组织成若干顺序进程,简称进程

在只有一个物理程序计数器时,在每个程序运行时,它的逻辑程序计数器被装入实际的程序计数器中。当该程序执行结束(或暂停执行)时,物理程序计数器被保存在内存中该进程的逻辑程序计数器中。

进程有存放程序正文和数据以及其他资源的地址空间,这些资源包括:打开的文件,子进程,即将发生的定时器,信号处理程序,账号信息等,把他们都放到进程中可以更容易的管理

4种主要事件导致进程的创建:

系统初始化

正在运行的程序执行了创建进程的系统调用

用户请求创建一个新进程

一个批处理作业的初始化

进程的终止(UNIX:exit;Windows:ExitProcess):

正常退出(自愿的)

出错退出(自愿的)

严重错误(非自愿)

被其他进程杀死(非自愿)

守护进程: 是一类在后台运行的特殊进程,用于执行特定的系统任务。守护进程是一个在后台运行并且不受任何终端控制的进程

在一个已存在的进程中创建新进程:

在 UNIX 系统中,fork 可以用来创建新进程,这个系统调用会创建一个与调用进程相同的副本。在调用 fork 后,这两个进程(父和子进程)拥有相同的内存映像,同样的环境字符串和同样的打开文件。通常,子进程接着执行 execve 或一个类似的系统调用,以修改其内存映像并运行一个新的程序(之所以要安排两步创建创建进程,是为了在 fork 之后但在 execve 之前允许该子进程处理其文件描述符,这样可以完成对标准输入文件,标准输出文件和标准错误文件的重定向)

在Windows中,win32函数调用 CreateProcess 既处理进程的创建,也负责把正确的程序装入新的进程。该调用有十个参数:包括① 要执行的程序 ② 输入给该程序的命令行参数 ③ 各种安全属性 ④ 有关打开的文件是否继承的控制位 ⑤ 优先级信息 ⑥ 该进程所需要创建的窗口规格 ⑦ 指向一个结构的指针 等,在该结构中新创建进程的信息被返回给调用者。

内存通过写时复制共享: 这意味着一旦两者之一想要修改部分内存,则这块内存首先被明确的复制,以确保修改发生在私有内存区域(可写的内存是不可以共享的)

进程的层序结构: 进程只有一个父进程(但可以有零个,一个或多个子进程)。在创建进程的时候,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程

进程的三种状态图:

运行态(该时刻进程实际占用CPU)

就绪态(可运行,但因为其他进程正在运行而占时停止)

阻塞态(除非某种外部事件发生,否则进程不能运行)



在操作系统发现进程不能继续运行时,发生转换1

系统认为一个运行进程占用处理器的时间已经过长,决定让其他进程使用CPU时间时,会发生转换2

在系统已经让所有其他进程享有了它们应有的公平待遇而重新轮到第一个进程再次占用CPU运行时,会发生转换3

当进程等待的一个外部事件发生时(如一些输入到达),则发生转换4

转换2和3是由调度程序引起,调度程序的主要工作:决定应当运行哪个进程,何时运行及它应当运行多长时间

进程表: 每个进程占用一个进程表项(进程控制块),该表项包含了进程状态的重要信息



中断向量: 与每一类 I/O 类关联的是一个称作中断向量 的位置(靠近内存底部的固定区域),它包含中断服务程序的入口地址。(所有的中断都从保存寄存器开始)

中断发生后操作系统最底层的工作步骤:

① 硬件压入堆栈程序计数器等

② 硬件从中断向量装入新的程序计数器

③ 汇编语言过程保存寄存器值

④ 汇编语言过程设置新的堆栈

⑤ C 中断服务例程运行(典型的读和缓冲输入)

⑥ 调度程序决定下一个将运行的进程

⑦ C 过程返回至汇编代码

⑧ 汇编语言过程开始运行新的当前进程

多道程序设计可以提高CPU的利用率:假设一个进程等待 I/O 操作的时间与其停留在内存中时间的比为 P,当内存中同时有 n 个进程时,则所有 n 个进程都在等待 I/O (此时CPU空转)的概率为 pnpn ,则 CPU利用率 = 1 - pnpn (n 称为多道程序设计的道数)

2. 线程

进程中拥有一个执行的线程:

在线程中有一个程序计数器,用来记录接着要执行哪一条指令

线程拥有寄存器,用来保存线程当前的工作变量

线程还拥有一个堆栈,用来记录执行历史,其中每一帧保存了一个已调用的但是还没有从中返回的过程

进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。

在同一个进程环境中,允许彼此之间有较大独立性的多个线程执行



1. 线程的使用

① 并行实体拥有共享同一个地址空间和所有可用数据的能力

② 由于线程比进程更轻量级,所以它们比进程更容易创建,也更容易撤销

③ 若多个线程都是 CPU 密集型的,那么并不能获得性能上的提升,但是如果存在着大量的计算和大量的 I/O 处理,拥有多个线程允许这些活动彼此重叠进行,从而会加快应用程序执行的速度

④ 在多CPU系统中,多线程是有益的,并且真正的并行有了实现的可能

例如:一个组织 web 服务器





分派程序的线程从网络中读入工作请求

在检查完请求之后,分派线程挑选一个空转的工作线程,提交该请求

接着,分派线程唤醒睡眠的工作线程,将它从阻塞状态转换为就绪状态

在工作线程被唤醒后,它检查有关的请求是否在web页面高速缓存中。如果没有,该线程开始一个从磁盘调入页面的read操作,并且阻塞直到该磁盘操作完成

有限状态机: 每个计算都有一个被保存的状态,存在一个会发生且使得相关状态发生改变的事件集合。



2. 经典线程模型

在多线程情况下,进程通常会从当前的单个线程开始,这个线程有能力通过调用一个库函数(如:thread_create,其参数专门指定了新线程要运行的过程名;thread_yeild:它允许线程自动放弃CPU而让另一个线程运行,因为线程无法利用时钟中断强制线程让出CPU)创建新的线程

为实现可移植的线程程序,IEEE在IEEE标准1003.1c 中定义了线程的标准,它定义的线程包叫:pthread



① 在用户空间中实现线程: 把整个线程包放在用户空间中,内核对线程包一无所知(从内核角度考虑,就是按正常的方式管理,即单线程进程)

优点:① 用户级线程包可以在不支持线程的操作系统上实现 (保存该线程状态的过程和调度程序都只是本地过程,所以启动它们比进行内核调用效率更高;另一方面,不需要陷入内核,不需要上下文切换,也不需要对内存高速缓存刷新,这就使得线程调度非常快捷)② 允许每个进程有自己定制的调度算法(用户级线程具有较好的可扩展性)

缺点:① 如何实现阻塞系统调用(在系统调用周围从事检查的代码称为包装器)② 如果一个线程开始运行,那么在该进程中的其他线程就不能运行(在一个单独的进程内部,没有时钟中断,所以不可能用轮转调度的方式调度线程)

② 在内核中实现线程: 所有能够阻塞线程的调用都以系统调用的形式实现,由于在内核中创建或撤销线程的代价比较大,当某个线程被撤销时,就把它标志为不可运行,但是其内核数据结构没有收到影响。

- 缺点:系统调用的代价比较大



在用户空间管理线程时,每个进程需要有其专用的线程表 ,用来跟踪该进程中的线程。

内核线程表保存了每个线程的寄存器,状态和其他信息。

③ 混合实现: 一种方法是使用内核级线程,然后将用户级线程与某些或者全部内核线程多路复用起来。在这种模型中,每个内核级线程有一个可以轮流使用的用户级线程集合。



④ 调度程序激活机制: 当使用调度程序激活机制时,内核给每个进程安排一定数量的虚拟机,并且让(用户空间)运行时系统将线程分配到处理器上。

目标: 是模拟内核线程的功能,但是为线程包提供通常在用户空间中才能实现的更好的性能和更大的灵活性。

上行调用: 内核通过在一个已知的起始地址启动运行时系统,从而发出了通知,这是对unix中信号的一种粗略模拟。

⑤ 弹出式线程:

对于服务请求:

传统的方法是将进程或线程阻塞在一个 receive 系统调用上,等待消息的到来。当消息到达时,该系统调用接收消息,并打开消息检查其内容,然后进行处理。

在该处理方式中,一个消息的到达导致系统创建一个处理该消息的线程(弹出式线程)。弹出式线程的结果是,消息到达与处理开始之间的时间非常短。

3. 进程间通信

竞争条件: 两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序。

互斥: 以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。

临界区域: 对共享内存进行访问的程序片段

保证使用共享数据的并发进程能够正确和高效的运行协作的解决方案:

① 任何两个进程不能同时处于其临界区

② 不应对CPU的速度和数量做任何假设

③ 临界区外运行的进程不得阻塞其他进程

④ 不得使进程无限期等待进入临界区



互斥方案的实现:

① 忙等待的互斥

屏蔽中断: 使每个进程在刚刚进入临界区后立即屏蔽所有中断,并在就要离开之前再打开中断。屏蔽中断后,时钟中断也被屏蔽,CPU只有发生时钟中断或其他中断时才会进程进程切换。于是,一旦某个进程屏蔽中断后,就可以检查和修改共享内存,而不必担心其他进程介入。

屏蔽中断对于操作系统来说是很有用的,但对于用户进程则不是一种合适的通用互斥机制

如果系统是多处理器,则屏蔽中断仅仅对执行 disable 指令的那个CPU有效,其他CPU仍将继续运行,并可以访问共享内存。

对内核来说,它在更新变量或列表的几条指令期间将中断屏蔽是很方便的

锁变量

严格轮换法

忙等待: 连续测试一个变量直到某个值出现为止

自旋锁: 只有在有理由认为等待时间是非常短的情况下,才使用忙等待,用于忙等待的锁称为自旋锁。



Peterson解法



在使用共享变量之前,各个进程使用其进程号0或1作为参数来调用enter_region。该调用在需要时将使进程等待,直到能安全的进入临界区。在完成对共享变量的操作后,进程将调用 leave_region,表示操作已完成,若其他的进程希望进入临界区,则现在就可以进入。

TSL 指令: 执行 TSL 指令的CPU将锁住内存总线,以禁止其他CPU在本指令结束前访问内存。

TSL RX,LOCK
称为测试并加锁,它将一个内存字 lock 读到寄存器 RX 中,然后在该内存地址上存一个非零值。读字和写字操作保证是不可分割的,即该指令结束之前其他处理器均不允许访问该内存字。

当 lock 为 0 时,任何进程都可以使用 TSL 质量将其设置为 1,并读写共享内存。当操作结束时,进程用一条普通的 move 指令将 lock 的值重新设置为 0.



XCHG 原子性的交换了两个位置的内容



② 睡眠与唤醒
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: