进程
模型定义:计算机上所有可运行的软件,被组织成若干顺序进程,简称进程。 一个进程就是一个正在执行程序的实列,包括程序计数器,寄存器和变量当前值。类比 菜谱和按菜谱做菜
本质上单CPU只能执行一个进程,多道程序设计中,是CPU来回切换进程造成的伪并行。单CPU中只有一个物理程序计数器,运行进程会装载进程的逻辑程序计数器,执行完毕或者被中断则会将物理程序计数器值保存到进程的逻辑程序计数器。
补充:进程拥有各自的地址空间,无法共享数据,修改后对于其他进程不可见。UNIX父子进程可共享内存中不可修改区域,若需要修改需要写时复制。windows父子进程创建直接不同地址空间。
进程的创建
1)系统初始化
2)正在运行的程序执行了创建进程的系统调用
3)用户请求创建一个新进程
4)一个批处理作业的初始化
UNIX与Windows创建进程的区别
在UNIX中,只有一个系统调用用来创建新进程:fork。这个系统调用会创建一个与调用进程相同的副本。在调用了fork后,这两个进程(父进程和子进程》拥有相同的内存映像、同样的环境字符串和同样的打开文件。这就是全部情形。通常,子进程接着执行execve或一个类似的系统调用,以修改其内存映像并运行一个新的程序。之所以要安排两步建立进程,是为了在fork之后但在execve之前允许该子进程处理其文件描述符,这样可以完成对标准输入文件、标准输出文件和标准错误文件的重定向。
在Windows中,情形正相反,一个Win32函数调用CreateProcess既处理进程的创建,也负责把正确的程序装入新的进程。该调用有10个参数,其中包括要执行的程序、输人给该程序的命令行参数、各种安全属性、有关打开的文件是否继承的控制位、优先级信息,该进程(若有的话)所需要创建的窗口规格以及指向一个结构的指针,在该结构中新创建进程的信息被返回给调用者。除了CreateProcess.Win32中有大约100个其他的函数用于处理进程的管理、同步以及相关的事务。
在UNIX和Windows中,进程创建之后,父进程和子进程有各自不同的地址空间。如果其中某个进程在其地址空间中修改了一个字,这个修改对其他进程而言是不可见的。在UNIX中,子进程的初始地址空间是父进程的一个副本,但是这里涉及两个不同的地址空间,不可写的内存区是共享的。某些UNIX的实现使程序正文在两者间共享,因为它不能被修改。或者,子进程共享父进程的所有内存,但这种情况下内存通过写时复制(copy-on-write)共享,这意味着一旦两者之一想要修改部分内存,则这块内存首先被明确地复制,以确保修改发生在私有内存区域。再次强调,可写的内存是不可以共享的。但是,对于一个新创建的进程而言,确实有可能共享其创建者的其他资源,诸如打开的文件等。在Windows中,从一开始父进程的地址空间和子进程的地址空间就是不同的。
进程终止
1)正常退出 2)出错退出 3)严重错误 4)被其他进程杀死
进程状态
1)运行态(该时刻进程实际占有CPU)
2)就绪态(可运行,但是因为其他进程占据CPU而暂时停止)
3)阻塞态(除非某种外部事件发生,否则进程不能运行)
进程的实现
操作系统中维护进程表,每个进程占用一个表项(又名 进程控制块),包含程序计数器,堆栈指针,内存分配,文件状态,以及状态转换时保存的必要信息。
CPU如何维持多个进程 首先,与IO类相关联的是中断向量的位置。他包含中断服务程序的入口地址。假设某个磁盘中断发生,用户正在运行进程,则中断硬件会将程序计数器,寄存器等压入栈堆,计算机会跳转到中断向量所指示的地址。
线程
出现原因
1)多进程模型无法表达:并行实体拥有共享同一个地址空间和所有可用数据的能力。
2)线程比进程更加轻量,更易创建和撤销
3)CPU密集型线程,是无法获得性能增强,如果存在大量计算和IO处理会增加。
线程模型
进程模型基于两种独立的概念:资源分组处理与执行。有时,将这两种概念分开会更好,这就引人了“线程”这一概念。下面先介绍经典的线程模型,之后我们会来研究“模糊进程与线程分界线 的Linux线程模型。理解进程的一个角度是,用某种方法把相关的资源集中在一起。进程有存放程序正文和数据以及其他资源的地址空间。这些资源中包括打开的文件、子进程、即将发生的定时器、信号处理程序、账号信息等。把它们都放到进程中可以更容易管理。
另一个概念是,进程拥有一个执行的线程,通常简写为线程(thread)。在线程中有一个程序计数器用来记录接着要执行哪一条指令。线程拥有寄存器,用来保存线程当前的工作变量。线程还拥有一个堆栈,用来记录执行历史,其中每一帧保存了一个已调用的但是还没有从中返回的过程。尽管线程必须在某个进程中执行,但是线程和它的进程是不同的概念,并且可以分别处理。进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。
同一进程可并行运行多个线程,且共享同一个地址空间和其他资源,包含物理内存,磁盘,打印机等。由于线程拥有部分进程特性,所以又称为轻量级线程。
注意点:没有进程之间的独立性,所有线程共享地址空间。所以一个线程可以读写或者清除其他线程的栈堆。线程之间没有保护能力。线程与进程的内容区别:
线程的运行状态:运行,阻塞,就绪或终止
注意每个线程拥有自己的栈堆,用来保存调用信息包括局部变量以及过程调用完成之后的返回地址。
线程库函数包括:
thread_create:创建
thread_exit:退出,线程消失,不再可调度
thread_join:一个线程阻塞直到等待一个特定的线程退出
thread_yield:允许线程自动放弃CPU而让另外一个线程运行 理由线程无法利用时钟中断强制让出CPU。
用户空间与内核中实现线程
两者差别在于性能。用户级线程的线程切换需要少量指令,而内核级需要完整上下文切换,修改内存映像,使高速缓存失效,导致延迟。另一方面,内核线程遇到阻塞线程不需要像用户线程将整个进程挂起。
用户空间实现
如上图a) 内核无法感知线程包,从内核角度出发就是单线程进程。此处优点可以在不支持多线程的操作系统上实现。 在用户空间中,每个进程都需要有线程表,用来跟踪线程。结构和内核中线程表类似。那么可得最大优点,切换线程无需陷入内核中,速度要快一个数量级 理由:保存该线程状态的过程和调度程序都只是本地过程,所以启动他们比内核调用效率高,不需要陷入内核,不需要上下文切换,也不需要对内存高速缓存进行刷新。另外一个优点:允许每个进程自己定制调度算法,参考yield函数。 拓展性较好,内核线程需要一些固定的表格空间和堆栈空间,如果内核线程的数量非常大,就会出现问题。
用户空间线程缺点: 首要是如何实现阻塞系统调用,其中一个线程阻塞系统调用,将会停止所有线程。使用线程主要目的:允许每个线程使用阻塞调用,但同时避免阻塞其他线程。有了阻塞系统调用,这个目标不是轻易地能够实现。 类似阻塞调用的是缺页中断问题,内核只能感知进程,所以一个线程发送页面故障,通常会把整个进程给阻塞直到磁盘IO完成为止,尽管其他线程是可以运行的。
其次如果一个线程开始运行,那么在该进程下的其他线程不能运行。除非他自己放弃CPU,因为他在进程内部,没有时钟中断,所以不能用轮转调度的方式调度线程。
内核中实现线程
如图b),线程表放入内核中,如果创建或者撤销线程,他需要进行系统调用,更新线程表。所有能够阻塞线程的调用都以系统调用的形式实现,代价相当可观。不过当一个线程阻塞的时,内核根据其选择,可进行同一进程的其他就绪线程运行,而用户级线程则不可以。
为了减小创建或销毁线程的代价,可采用回收线程节省开销。
进程间通讯
竞态条件
两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,称为竞态条件。
临界区
避免竞态条件,关键是找出某种途径阻止多个进程同时读写共享数据。换言之,我们需要的是互斥。
我们把共享内存进行访问的程序片段称为临界区域或者临界区。
忙等待的互斥
忙等待的缺点:a.浪费CPU时间,循环空转会导致CPU时间浪费。
b.且出现优先级反转问题
几种实现互斥的方案:
1)屏蔽中断
最简单的方法使每个进程在刚刚进入临界区立即屏蔽所有中断,离开后打开中断。
不明智,中断不能交给用户控制。
多核CPU情况下,很难实现。
2)锁变量
作为第二种尝试,可以寻找一种软件解决方案。设想有一个共享(锁)变量,其初始值为0。当一个进程
想进人其临界区时,它首先测试这把锁。如果该锁的值为0,则该进程将其设置为1并进入临界区。若这把锁的值已经为1,则该进程将等待直到其值变为0。于是,0就表示临界区内没有进程,1表示已经有某个进程进入临界区。
但是,这种想法也包含了与假脱机目录一样的疏漏。假设一个进程读出锁变量的值并发现它为0.而恰好在它将其值设置为1之前,另一个进程被调度运行,将该锁变量设置为1。当第一个进程再次运行时,它同样也将该锁设置为1,则此时同时有两个进程进入临界区中。
3)严格轮转法
4)Perterson解法
5)TSL指令
信号量
互斥量
如果不需要信号量的计数能力,有时可以使用信号量的一个简化版本,称为互斥量 (mutex)。互斥量仅仅适用于管理共享资源或一小段代码。由于互斥量在实现时既容易又有效,这使得互斥量在实现用户空间线程包时非常有用。
调度
进程行为
几乎所有进程的IO请求和计算是交替发生的。分为计算密集型和IO密集型。前者特征较长时间的CPU集中使用和较小频度的IO等待。后者则较短的CPU集中使用和频繁的IO等待。
何时调度
1) 创建一个新进程,需要决定是运行父进程还是子进程。 2)进程退出时必须做出调度决策 3)当一个进程阻塞在IO和信号量上或由于其他原因阻塞,必须选择另外一个进程运行。 4)发生IO中断必须做出调度决策
非抢占式调度:挑选一个进程,然后进程运行直到被阻塞,或者自动释放CPU。结果是时钟中断发生时不会进行调度。
抢占式调度:挑选一个进程,并且让进程运行某个固定时段最大值,如果时间段结束,任在运行,就会被挂起,而调度程序挑选另一个进程运行。
调度算法分类
1、批处理 2.交互式 3.实时
批处理调度算法
1.先来先服务 2.最短作业优先 3.最短剩余时间优先:总是选择剩余运行时间最短的进程运行
交互式系统中的调用
1.轮转调度 2.优先级调度 3.多级队列 4.最短进程优先 5.保证调度 6.彩票调度 7.公平分享调度