《操作系统中进程管理的基本原理.docx》由会员分享,可在线阅读,更多相关《操作系统中进程管理的基本原理.docx(14页珍藏版)》请在三一办公上搜索。
1、操作系统中进程管理的基本原理作 者:石莹 (2010-6-27)作者信箱: wonder sy0618刖言文章借助王爽老师编写的Toyix操作系统,通过对一个个程序的执行结果的分析,向读者演示 了操作系统中进程的基本原理和基本特性。这篇文章面向已经掌握c语言和数据结构的操作系统的 初学者。读者除了学习其中的知识,更应该学习体会文章中分析探索问题的方法,并在以后的计算 机学习中应用这些方法。这些学习的能力是比知识更重要的东西。文章在编写过程中参考了Toyix简易教程,计算机操作系统(西安电子科技大学出版 社),汇编语言(王爽)操作系统发展的意义在没有出现操作系统以前,计算机只能在一段时间执行一段
2、程序,程序因为IO请求等原因发 生阻塞时,CPU会处于空闲状态造成资源的浪费。因为早期的计算机价格昂贵,用户就希望可以更 高效的利用计算机资源。这种需求就促进了操作系统的产生和发展。进程我们今天使用的操作系统主要是分时系统,由调度程序调入的多个作业共享CPU资源,其中 每个作业只执行极短的一段时间(比如0.1S,我们称为一个时间片),极短的时间过后暂停执行, 调入下一个程序。这样在不长的一段时间内(比如5s内),有限的进程(少于50个)都可以得到 至少一次的执行,用户请求可以得到及时的响应。这种作业调度的方式我们称为时间片轮转法。这 种执行的方式叫并发执行,并发性也是分时系统的基本特性之一。在
3、分时系统中,为了执行一项作业,就需要把要执行的作业程序载入内存中作为程序段,为作 业分配相应的数据空间作为数据段,并加入一个控制块(PCB),用来保存当前作业执行所必须的 一些信息,使之能够并发执行。内存中的程序段、数据段和PCB我们称为一个进程实体,而一个 进程实体的执行过程我们称为进程。进程的三种基本状态我们已经知道了进程实际是一个动态的概念,我们再回到分时系统的原理上。分时系统是给一 个进程分配一个时间片,让这个进程执行,当进程时间片用完以后,为下一个进程分配时间片。当 进程执行过程中发生阻塞,则主动让出CPU控制权,给其它进程执行的机会。分析上面的过程,每一时刻只有一个进程处于执行的状
4、态。而有多个进程处于等待分配时间片 的状态,这多个进程应该遵循一定的顺序。事实上是存在于一个队列中。这种等待分配时间片的状 态我们称为就绪状态,存放就绪进程的队列就称为就绪队列。当CPU处于空闲状态时,调度程序 就会从就绪队列中取出一个进程并执行。当进程时间片用完后,调度信息就会把这个进程放入到就 绪队列中。除了时间片用完,当进程IO请求时,进程 会在IO请求完毕之前无法继续执行,这类情况 我们称为进程的阻塞(可能出现进程阻塞的有IO 请求,申请缓冲空间等)。当出现进程阻塞后, 调度程序应该怎么处理呢?首先进程肯定不能 放入就绪态,因为放入就绪态就有可能被分配时 间片,而此时进程不能继续执行,
5、这就浪费了 CPU资源。调度程序对其进行的处理是将其放到 一个阻塞队列中,当10完成时,再把它放回就 绪队列等待分配时间片。图1进程就绪态、执行态和阻塞态的关系图1画出了进程就绪态,执行态和阻塞态的 关系使用Toyix查看进程的三种状态Toyix是一个专门为操作系统的基础理论教学而编写的系统,通过这个系统可以很方便的 模拟进程的创建执行过程。启动toyix系统,如图从Toyix网站(Toyix操作系统支持绝大部分常用的dos命令,我们可以使用dos下的编程方式进行编程。可 以使用dos下的工具编写程序源码,如图C:edit 1-c下面我们编写一个程序,用来演示程序的三种状态#include m
6、ain()(int i;for(i=0; i80; i+)(put_str(3,i,2,a);delay(30);get_char();for(i=0; i80; i+)(put_str(5,i,2,b);delay(30);分析上面的程序,程序中引入toyix库toyix.h文件是为了使用toyix系统提供的关于的函数。程序首先在循环内调用put_str在屏幕第三行输出字符a (关于put_str的用法,请参考toyix函数手 册),每输出一次延时30ms。然后调用get_char函数阻塞式获得用户输入。最后再通过一个循环在 屏幕第五行输出一行字符b。下面我们在toyix下编译这个程序。to
7、yix提供的c语言编译命令是cc,使用方式是“cc c语言 源文件文件名”,如图C:cc 1-cCompiling.1 .c :Auailable memovy 208664Linking.Making.1 - prgC:程序已经编译,连接成功,产生了toyix下的可执行文件2.prg,prg是toyix下可执行文件的后 缀名。下面运行这个程序。Toyix中运行一个可执行文件的命令是do,使用方法是“do可知性文件的 文件名”,注意不需要加后缀名,如图C:do 1然后以后就看到了 toyix程序运行时的界面blocked:Toyix Multiprocess Monitor U0.03屏幕上边
8、的蓝条是toyix进程监视器,通过它我们可以方便的知道每个进程所处于的状态。其 中running后指示的是处于运行状态的进程id,ready后指示的是处于就绪状态的进程id,blocked 指示的是处于阻塞状态的进程id。可以看到我们的程序已经准备完毕,按任意键开始执行。程序应该是处于就绪态,进程id是1。 而此时没有更多的任务,CPU处于空闲状态,running处用一个红色的0表示。按任意键开始执行程序running: 1 ready:blocked:Toyix Multiprocess Monitor uO.S31.prg ok Haaaaaaaaaaaaaaaaaaaaaaaaaaaaa
9、aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa程序开始在屏幕输出a,此时程序处于运行态。一行输出完毕,程序会请求用户输入,如图running: 0 ready:blocked: 1Toyix Multiprocess Monitor U0.031.prg 15 ok可以看到ID为1的进程此时处于阻塞态。按任意键,使程序继续执行running: 1 ready:blocked:Toyix Multiprocess Monitor u0.031.prg ok aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
10、aaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb如图,程序进入运行态继续输出字符b,知道程序运行结束。事实上,进程应该有一个进入就 绪态的过程,再由操作系统取出就绪进程分配时间片后进入运行态,但是因为时间太短(Toyix系 统每个时间片大概是50ms),我们没有看到那个状态。进程与轻权进程我们已经知道,操作系统的多个进程处于并发执行的状态。一个进程p1可以创建另一个进程 p2,我们称p2为p1的子进程,p1为p2的父进程。每个进程都会有一个进程实体,下面我们研究 父进程与子进程进程实体之间的联系我们可以应用c
11、语言的某些特性来进行研究,比如我们可以通过下面的程序来输出一个变量在 内存中的位置#include int i = 0;main()(printf(n%d : %dn, _DS, &i);blocked:Toyix Multiprocess Monitor u0.03执行后就得到了变量i的段地址和偏移地址(4324d : 3216d)running: 0 ready 4.prg ok Presskey4324 : 3216也可以通过下面这段程序来打印main函数的地址#include int i = 0;main()(printf(n%d : %dn, _CS, main);blocked:T
12、oyix Multiprocess Monitor u0.03执行后获得了 main函数的段地址和偏移地址(4324d : 0785d)running: 0 ready 4.prg ok Press emy key 4324 : 785通过以上两种方法,我们可以得到一个进程的数据和代码在内存中的位置。Toyix为我们提供了两个简单的创建进程的函数fork()和frk(),这两个函数的调用方法我们不 做具体介绍,请参考toyix的函数手册。下面我们分别研究这两个函数创建进程的差别。编写下面 的程序#include int i = -1;main()(i = fork();printf(nnnCa
13、ll fork return value is %dn, i);printf(nPID = %dn, get_pid();printf(nFunction main of the address is %d:%dn, _CS, main);printf(nVariable i of the address is %d:%dn, _DS, &i);delay(500);分析这个程序,这个程序调用fork()函数创建的新的进程,并在每个进程中输出fork()函数返 回值(判断父进程还是子进程),当前进程ID,main函数的地址,变量i的地址。运行这个程序如图,通过fork()函数的返回值我们判断,
14、pid为1的进程为父进程。而且父进程与子进程 数据段和代码段均不相同。这说明通过fork()函数创建的进程数据与代码不共享。下面我们用同样的方法研究通过frk()函数创建的进程,修改上面的程序 #include int i = -1;main()(i = frk();printf(nnnCall fork return value is %dn, i);printf(nPID = %dn, get_pid();printf(nFunction main of the address is %d:%dn,_CS,main);printf(nVariable i of the address is
15、 %d:%dn,_DS,&i);delay(500);这个程序与上面的程序基本相同,只是把fork()函数改为了 frk()函数,在toyix中运行这个 程序我们发现,pid为1的进程为父进程,父进程与子进程代码段不同,但是数据段相同。这说明, 通过frk()创建的进程数据段共享,代码段不共享。这种进程我们称为轻权进程。我们为什么要创建轻权进程呢?创建进程是为了提高系统资源利用率,但是因为进程拥有自己 的数据段。进程的创建需要申请数据空间,销毁需要释放数据空间,切换进程需要保存并重新设置 CPU寄存器现场。进程的创建,切换和销毁都需要消耗大量的CPU资源。有时我们需要把自己的一个进程分成几部分
16、同时处理。比如程序中有一段代码会循环等待用户 输入,而用户的是否输入并不会影响我们程序的继续运行,我们不希望等待用户输入时主程序也进 入阻塞状态。我们可以将用户输入的部分创建一个新的进程,但是事实上这个进程并不需要分配新 的数据空间。那么就出现了没有自己独立的数据空间的进程,也就是轻权进程。轻权进程在创建, 切换和销毁时消耗的资源比进程都小的多。下面我们做这样一个实验。编写下面的程序#include main()(frk();printf(nPID = %dn, get_pid();程序中调用frk()创建轻权进程以后每个进程打印自己的进程id。在toyix下运行并查看结果running -p
17、rg ress an/ ID = 10 ready ok key-.blockedToyix Multiprocess Monitor u0.可以看到运行结果与我们预想的不太一样,只打印了一个进程的pid,另一个没有打印。这是 为什么呢?我们修改一下这个程序#include main()(frk();printf(nPID = %dn, get_pid();delay(100);与前一个比较,这个程序多调用了一个延时功能。再看运行结果runn:0 ready: ok key.blockedToyix Multiprocess Monitor u0.03我们发现现在的显示结果正确了。现在为什么正
18、确了呢?我们分析第一个程序,主进程创建进程以后,打印输出自己的进程id。此时子进程还没有得到 执行的机会,父进程就运行结束了,子进程也被销毁。子进程虽然创建了,但是并没有得到执行。 所以出现了错误的结果。第二个程序中,在父进程打印出自己的进程id以后,通过延时给子进程执行的机会,子进程 进入运行态,打印子进程pid,输出结果正常。以上分析表明,如果父进程结束,操作系统会销毁它创建的所有轻权进程。这很容易理解,因 为轻权进程是没用数据空间的,它共享父进程的数据空间。而父进程销毁时,数据空间会被释放, 此时如果子进程继续执行,可能访问已经释放掉的空间,这是不安全的。这一部分我们讲述了进程与轻权进程
19、的一些差别。简单来说,进程是可以独立运行的最小单位。 而轻权进程是可供操作系统调度的最小单位。后面我们要继续讲述怎样在我们编写的程序中更方便 的创建多个轻权进程。使用cobegin创建轻权进程通过以上的学习我们知道,为了更高效的利用资源,我们编写的程序可能要创建多个轻权进程。但是使用frk()创建的进程只能在两个进程中执行同一段代码,这样的进程是没有实际意义的。为了方便的创建轻权进程,toyix为我们提供了 cobegin函数。Toyix函数手册中对这个函数的 描述如下:原型:int cobegin();功能:创建多个子进程并发执行函数参数:函数名用0结束,例如:cobegin(f1,f2,f
20、3,0);返回:创建子进程的个数说明:所有子进程共享数据段,主进程撤销,所有子进程将被撤销。我们编写一个程序,通过cobigin函数来创建多个轻权进程。#include void f1()(printf(nfunction f1,pid = %dn,get_pid();void f2()(printf(nfunction f2,pid = %dn,get_pid();void f3()(printf(nfunction f3,pid = %dn,get_pid();main()(printf(nfunction main,pid = %dn,get_pid();cobegin(f1,f2,f3
21、,0);delay(100);在这个程序中,main函数首先输出自己的pid,然后调用cobegin函数将f1,f2,f3函数分别作为 一个轻权进程运行,在每个轻权进程里,打印自己的函数名和pid。运行结果如图多进程引发的问题下面我们要应用多进程技术编写一个模拟火车售票系统的程序。在火车票售票中心的服务器 上,保存着未售出的火车票数量。而各地有很多代售点,他们在售票时会连接到售票中心检查火车 票剩余情况,如果还有剩余票,那么卖出这张牌,同时通知售票中心车票被卖出。编写程序实现这个过程,首先创建一个全局变量,用来保存剩余票数。编写一个函数用来模拟 一个售票窗口的售票流程。再通过在main函数中调
22、用cobegin函数创建多个轻权进程,模拟多个窗 口售票。程序代码如下:#include int total = 10;void f1()(while (total 0)(delay(50);printf(npid = %d, sell ticket : %d, get_pid(), total-);main()(cobegin(f1,f1,0);getch();在单个窗口的售票流程中,首先检查剩余票数是否大于0,如果大于0,那么延时一段时间(模 拟真实情况),然后卖出这张票,再令剩余票减一。在toyix系统中运行,模拟结果如图running: 1 ready: 1.prg ok Press
23、any ke0,进入售票程序 延时一段时间,还未售票时间片已经用完轻权进程P1: c)P1获得时间片继续运行,售票程序售出这张票后, 令total减一。再次检测total=0,循环结束,进程结束b)P2获得时间片,与a中处理过程相同轻权进程P2:id)P2获得时间片,售票后退出。但是此时total=0,所以售出了 0号票此时一个进程(pid = 2)检查total的值,检测到大于0进入售票程序,调用delay延时(注意 票并没有售出,tatal仍为1)。此时另一个轻权进程(pid = 3)获得了时间片开始运行,仍会检测tatal 的值,同样进入了售票程序,调用delay延时。调度程序调出刚才的
24、轻权进程(pid = 2)的进程继续执 行,售出1号票并将票数减1,再次检测tatal小于0,pid为2的轻权进程结束退出。Pid为3的轻 权进程回到刚才的状态继续售票,而此时tatal为0,所以售出了 0号票。显然,在这个实验中如果没有延时操作,可能不会出现这样的情况。但是在一个长期运行的多 个轻权进程的系统中,很有可能出现这种两个轻权进程访问同一资源时出错的问题。增加一个延时 只是放大了这种问题。通过上述分析,我们了解了一个进程通过创建多个轻权进程虽然可以提高资源的利用率,但是 在访问同一资源时可能引起一些问题。为了解决这些问题,我们必须在不同轻权进程访问同一资源 时进行一些判断和处理。这
25、些处理我们称为进程同步。用PV原语实现进程同步支持多进程的操作系统都提供了进程同步的机制,有一种简单的方式是设置一个变量,这个变 量标记着某个资源是否空闲,我们称为这个变量为信号量。操作系统不允许程序对信号量进行直接 的修改,只能通过两个函数来改变它的值,这两个函数实现的操作我们通常称为P操作和V操作。 这两种操作的原理如下:P操作:a.)检测信号量是否空闲(小于等于0),若结果为真,则阻塞直到检测结果为假。b)信号量值自减一。V操作:a)信号量值自加一。上述两种操作均为原子操作,也就操作中均不能被中断。我们可以在执行访问同一资源的不可中断的几步操作前执行P操作检测信号量,在几步操作完 成后执
26、行V操作。通过操作系统为我们提供的PV原语,我们可以将访问同一资源的不可中断的几 步操作原子化,使之不能被中断。下面我们应用PV原语来修改刚才的模拟售票系统。判断剩余票数,售票,票数自减一这三步 操作应该是不可中断的,要使用PV原语保证它们的原子化。修改后代码如下 #include semaphore s; int total = 10; void f1() (p(&s);while (total 0) ( delay(50); printf(npid = %d, sell ticket : %d, get_pid(), total-);v(&s); main() ( set(&s,1); c
27、obegin(f1,f1,0); getch(); 现在我们再运行这个程序,查看结果如图现在的结果正常了,这样就通过简单的方式解决了多个轻权进程同时运行遇到的问题。这一部分向读者讲述了使用PV原语解决由于多个轻权进程并发运行引发的数据混乱问题。读 者在编写这样的程序时,应该注意这些问题,可能会引起数据混乱的地方进行进程同步。但是在没 必要进程同步的程序段进行进程同步会造成运行效率的降低,需要程序员自己分析把握。本文通过介绍进程、进程的三态、进程与轻权进程的区别、多进程的创建和进程同步等一些操 作系统的基本原理,向读者演示了操作系统中一些问题的研究方法。希望本文能给操作系统的初学 者起到一个抛砖引玉的作用,为操作系统的学习打下一个坚实的基础。