LINUX 的进程控制

上传人:c****d 文档编号:243024292 上传时间:2024-09-14 格式:PPT 页数:29 大小:111.50KB
返回 下载 相关 举报
LINUX 的进程控制_第1页
第1页 / 共29页
LINUX 的进程控制_第2页
第2页 / 共29页
LINUX 的进程控制_第3页
第3页 / 共29页
点击查看更多>>
资源描述
单击此处编辑母版标题样式,Linux系统的进程控制,在Linux系统中,进程控制的功能是由内核的进程控制子系统实现的,并以系统调用的形式提供给用户进程或其他系统进程使用。,1. 进程的创建与映像更换,系统启动时执行初始化程序,启动进程号为1的init进程运行。系统中所有的其他进程都是由init进程衍生而来的。除init进程外,每个进程都是由另一个进程创建的。新创建的进程称为子进程,创建子进程的进程称为父进程。Unix/Linux系统建立新进程的方式与众不同。它不是一步构造出新的进程,而是采用先复制再变身的两个步骤,即先按照父进程创建一个子进程,然后再更换进程映像开始执行。,1) 创建进程创建一个进程的系统调用是fork()。创建进程采用的方法是克隆,即用父进程复制一个子进程。做法是:先获得一个空闲的PCB,为子进程分配一个PID,然后将父进程的PCB中的代码及资源复制给子进程的PCB,状态置为可执行态。建好PCB后将其链接入进程链表和可执行队列中。此后,子进程与父进程并发执行。父子进程执行的是同一个代码,使用的是同样的资源。它与父进程的区别仅仅在于PID(进程号)、PPID(父进程号)和与子进程运行相关的属性(如状态、累计运行时间等),而这些是不能从父进程那里继承来的。,fork(),系统调用【功能】创建一个新的子进程。【调用格式】int fork();【返回值】0向子进程返回的返回值,总为0 0向父进程返回的返回值,它是子进程的PID。-1创建失败。【说明】若fork()调用成功,则它向父进程返回子进程的PID,并向新建的子进程返回0。图4-7描述了fork()系统调用的执行结果。,图 fork系统调用的执行结果,从图中可以看出,当一个进程成功执行了fork()后,从该调用点之后分裂成了两个进程:一个是父进程,从fork()后的代码处继续运行;另一个是新创建的子进程,从fork()后的代码处开始运行。由fork()产生的进程分裂在结构上很像一把叉子,故得名fork()。与一般函数不同,fork()是“一次调用,两次返回”,因为调用成功后,已经是两个进程了。由于子进程是从父进程那里复制的代码,因此父子进程执行的是同一个程序,它们在执行时的区别只在于得到的返回值不同。父进程得到的返回值是一个大于0的数,它是子进程的PID;子进程得到的返回值为0。,若程序中不考虑fork()的返回值,则父子进程的行为就完全一样了。但创建一个子进程的目的是想让它做另一件事。所以,通常的做法是:在fork()调用后,通过判断fork()的返回值,分别为父进程和子进程设计不同的执行分支。这样,父子进程执行的虽是同一个代码,执行路线却分道扬镳。图4-8描述了用fork()创建子进程的常用流程。,图 用fork创建子进程,例1,一个简单的fork_test程序:#include main() int rid; rid = fork(); if (rid 0 ) / 父进程分支 printf(“I am parent, my rid is %d, my PID is %dn”, rid, getpid(); else / 子进程分支 printf(“I am child, my rid is %d, my PID is %dn”, rid, getpid();,注:程序中的getpid()是一个系统调用,它返回本进程的进程标识号PID。fork_test程序运行时,父子进程将会输出不同的信息,如父进程的输出可能是“I am parent, my rid is 8229, my PID is 8228”;子进程的输出可能是“I am child, my rid is 0, my PID is 8229”。由于两进程是并发的,它们输出信息的先后次序不确定,有可能父先子后,也可能相反。,2) 更换进程映像进程映像是指进程所执行的程序代码及数据。fork()是将父进程的执行映像拷贝给子进程,因而子进程实际上是父进程的克隆体。但通常用户需要的是创建一个新的进程,它执行的是一个不同的程序。Linux系统的做法是,先用fork()克隆一个子进程,然后在子进程中调用exec(),使其脱胎换骨,变换为一个全新的进程。exec()系统调用的功能是根据参数指定的文件名找到程序文件,把它装入内存,覆盖原来进程的映像,从而形成一个不同于父进程的全新的子进程。除了进程映像被更换外,新子进程的PID及其他PCB属性均保持不变,实际上是一个新的进程“借壳”原来的子进程开始运行。,exec(),系统调用【功能】改变进程的映像,使其执行另外的程序。【调用格式】exec()是一系列系统调用,共有6种调用格式,其中execve()是真正的系统调用,其余是对其包装后的C库函数。int execve(char *path, char *argv, char *envp);int execl(char *path, char *arg0, char *arg1, . char *argn, 0);int execle(char *path, char *arg0, char *arg1, . char *argn, 0, char *exvp);,【参数说明】path为要执行的文件的路径名,argv为运行参数数组,envp为运行环境数组。arg0为程序的名称,arg1argn为程序的运行参数,0表示参数结束。例如:execl(“/bin/echo”, “echo”,“hello!”, 0);execle(“/bin/ls”, “ls”, “-l”, “/bin”, 0, NULL);前者表示更换进程映像为/bin/echo文件,执行的命令行是“echo hello!”。后者表示更换进程映像为/bin/ls文件,执行的命令行是“ls -l /bin”。【返回值】调用成功后,不返回,调用失败后,返回1。,与一般的函数不同,exec()是“一次调用,零次返回”,因为调用成功后,进程的映像已经被替换,无处可以返回了。图4-9描述了用exec()系统调用更换进程映像的流程。子进程开始运行后,立即调用exec(),变身成功后即开始执行新的程序了。,图4 用exec更换子进程的映像,例2,一个简单的fork-exec_test程序:,#include main() int rid;rid = fork(); if (rid 0 ) printf(“I am parentn”); else printf(“I am child, Ill change to echo!n”); execl(“/bin/echo”, “echo”, “hello!”, 0); /更换为echo ,Fork()返回后,父子进程分别执行各自的分支,父进程输出信息“I am parent”。子进程输出信息“I am child, Ill change to echo!”,然后调用exec(),变换为echo程序。echo随即开始执行并输出字符串“hello!”。,2. 进程的终止与等待1),进程的终止与退出状态导致一个进程终止运行的方式有两种:一是程序中使用退出语句主动终止运行,我们称其为正常终止;另一种是被某个信号杀死(例如,在进程运行时按Ctrl+c键终止其运行),称为非正常终止。关于信号的介绍见0节。用C语言编程时,可以通过以下4种方式主动退出:(1) 调用exit(,status,)函数来结束程序;(2) 在main()函数中用return,status,语句结束;(3) 在main()函数中用return语句结束;(4) main()函数结束。,以上4种情况都会使进程正常终止,前3种为显式地终止程序的运行,后1种为隐式地终止。正常终止的进程可以返回给系统一个退出状态,即前2种语句中的,status,。通常的约定是:0表示正常状态;非0表示异常状态,不同取值表示异常的具体原因。例如对一个计算程序,可以约定退出状态为0表示计算成功,为1表示运算数有错,为2表示运算符有错,等等。如果程序结束时没有指定退出状态(如后两种退出),则它的退出状态是不确定的。设置退出状态的作用是通知父进程有关此次运行的状况,以便父进程做相应的处理。因此,显式地结束程序并返回退出状态是一个好的Unix/Linux编程习惯,这样的程序可以将自己的运行状况告之系统,因而能很好地与系统和其他程序合作。,2) 终止进程进程无论以哪种方式结束,都会调用一个exit()系统调用,通过这个系统调用终止自己的运行,并及时通知父进程回收本进程。exit()系统调用完成以下操作:释放进程除PCB外的几乎所有资源;向PCB写入进程退出状态和一些统计信息;置进程状态为“僵死态”;向父进程发送“子进程终止(SIGCHLD)”信号;调用进程调度程序切换CPU的运行进程。,至此,子进程已变为“僵尸进程”,它不再具备任何执行条件,只是PCB还在。保留PCB的目的是为了保存有关该进程运行情况的重要的信息,比如这个进程的退出状态、运行时间的统计、收到信号的数目等。子进程的最后回收工作由父进程负责。父进程收集子进程的信息后将其PCB撤销。如果某一个进程由于某种原因先于子进程终止,由它创建的子进程就成为孤儿进程。当系统中出现孤儿进程时,init进程将会发现并收养它,成为它的父进程。由于init进程不会退出,所以所有的进程都会被收养。最后,在系统关机之前,init进程要负责结束所有的进程。,exit(),系统调用【功能】使进程主动终止。【调用格式】void exit(int status);【参数说明】status是要传递给父进程的一个整数,用于向父进程通报进程运行的结果状态。status的含义通常是:0表示正常终止;非0表示运行有错,异常终止。,3) 等待与收集进程在并发执行的环境中,父子进程的运行速度是无法确定的。但在许多情况下,我们希望父子进程的进展能够有某种同步关系。比如,父进程需要等待子进程的运行结果才能继续执行下一步计算,或父进程要负责子进程的回收工作,它必须在子进程结束后才能退出。这时就需要通过wait()系统调用来阻塞父进程,等待子进程结束。当父进程调用wait()时,自己立即被阻塞,由wait()检查是否有僵尸子进程。如果找到就收集它的信息,然后撤掉它的PCB;否则就阻塞下去,等待子进程发来终止信号。父进程被信号唤醒后,执行wait(),处理子进程的回收工作。经wait()收集后,子进程才真正的消失。,wait (),系统调用【功能】阻塞进程直到子进程结束;收集子进程。【调用格式】int wait(int *statloc);【参数说明】*statloc保存了子进程的一些状态。如果是正常退出,则其末字节为0,第2字节为退出状态;如果是非正常退出(即被某个信号所终止),则其末字节不为0,末字节的低7位为导致进程终止的信号的信号值。若不关心子进程是如何终止的,可以用NULL作参数,即wait(NULL)。,【返回值】0子进程的PID。1调用失败。0其他。图4描述了用wait()系统调用等待子进程的流程。,图4 用wait实现进程的等待,例3,一个简单的wait-exit_test程序:#include #include main() int rid, cid, status; rid = fork(); if ( rid 0 ) printf(“fork error!”); exit(1); if ( rid = 0 ) printf(“I am a child. I will sleep a while.n”); sleep(10); / 睡眠10秒 exit(0); cid=wait(,printf(“I catched a child with PID of %d.n”, cid);if (status ,此例的执行过程是:父进程在创建子进程失败时会用exit(1)退出。成功创建子进程后,父进程调用wait()阻塞自己;子进程运行,先输出信息,睡眠10秒后用exit(0)退出向父进程发信号,告之自己已结束。父进程被唤醒后,从wait()返回,根据获得的子进程的PID和退出状态判断子进程的运行情况并输出相应的信息,然后用exit(0)退出。,3. 进程的阻塞与唤醒,运行中的进程,若需要等待一个特定事件的发生而不能继续运行下去时,则主动放弃CPU。等待的事件可能是一段时间、从文件中读出的数据、来自键盘的输入、某个资源被释放或是某个硬件产生的事件等。进程通过调用内核函数来阻塞自己,将自己加入到一个等待队列中。阻塞操作的步骤是:建立一个等待队列的节点,填入本进程的信息,将它链入指定的等待队列中;将进程的状态置为睡眠态;调用进程调度函数选择其他进程运行,并将本进程从可执行队列中删除。,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!