实验二 进程观察实验(二):进程的控制
实验目的
1、了解进程创建后对进程控制的系统调用,可实现对进程的有效控制 2、掌握进程的睡眠、同步、撤消等进程控制方法
实验内容
1、通过相关命令,对进程的状态进行控制。
2、编写程序,使用fork( )创建一个子进程。使用相关的系统调用控制进程的状态。观察并分析多进程的执行次序及状态转换。
实验基础
一、进程的控制
进程因创建而存在,因执行完成或异常原因而终止.在进程的生命周期中,进程在内存中有三种基本状态:就绪,执行,阻塞.进程状态的转换是通过进程控制原语来实现的。
Linux操作系统提供了相应功能的系统调用及命令,来实现用户层的进程控制。
二、相关的命令
(1)睡眠指定时间 执行格式:# sleep x x为指定睡眠的秒数。 (2)结束或终止进程 kill
执行格式:# kill [-9] PID (PID为利用ps命令所查出的process ID) 例: kill -9 456 终止process ID 为456的process (3)后台(background)执行process command的命令 执行格式:# command & (在命令后加上 &) 例: gcc file1 & 在后台编译file1.c
注意:按下^Z,暂停正在执行的process。键入”bg”,将所暂停的process置入background中继续执行。 例:# gcc file1 & ^Z stopped # bg
(4)查看正在background中执行的process 执行格式:# jobs
(5)结束或终止在background中的进程 kill 执行格式:# kill %n
例: kill %1 终止在background中的第一个job kill %2 终止在background中的第二个job
三、相关的系统调用
在LINUX中fork( )是一个非常有用的系统调用,但在LINUX中建立进程除了fork( )之外,也可用与fork( ) 配合使用的exec( )。
1、exec( )系列
系统调用exec( )系列,也可用于新程序的运行。fork( )只是将父进程的用户级上下文拷贝到新进程中,而exec( )系列可以将一个可执行的二进制文件覆盖在新进程的用户级上下文的存储空间上,以更改新进程的用户级上下文。exec( )系列中的系统调用都完成相同的功能,它们把一个新程序装入内存,来改变调用进程的执行代码,从而形成新进程。如果exec( )调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,这样就产生了一个新进程,新进程的进程标识符id 与调用进程相同。
exec( )没有建立一个与调用进程并发的子进程,而是用新进程取代了原来进程。所以exec( )调用成功后,没有任何数据返回,这与fork( )不同。exec( )系列系统调用在LINUX系统库unistd.h中,共有execl、execlp、execle、execv、execvp五个,其基本功能相同,只是以不同的方式来给出参数。
一种是直接给出参数的指针,如: int execl(path,arg0[,arg1,...argn],0); char *path,*arg0,*arg1,...,*argn; 另一种是给出指向参数表的指针,如: int execv(path,argv); char *path,*argv[ ];
另外,在linux中,出于安全的考虑,限制了exec()可以执行的新程序的位置为系统指定的搜索路径。
例如:
execl(“/bin/ls”,”ls”,NULL); execl(“/usr/bin/gcc”,”-v”,NULL);
execl(“./test”,NULL); //当前目录下的可执行程序 2、exec( )和fork( )联合使用
系统调用exec和fork( )联合使用能为程序开发提供有力支持。用fork( )建立子进程,然后在子进程中使用exec( ),这样就实现了父进程与一个与它完全不同子进程的并发执行。
一般,wait、exec联合使用的模型为: int status; ............
if (fork( )= =0) {
...........; execl(...); ...........; }
wait(&status); 3、wait( )
等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。如果在wait( )前已有子进程暂停或终止,则调用进程做适当处理后便返回。wait( )给我们提供了一种实现进程同步的简单方法。
系统调用格式: int wait(status) int *status;
其中,status是用户空间的地址。它的低8位反应子进程状态,为0表示子进程正常结束,非0则表示出现了各种各样的问题;高8位则带回了exit( )的返回值。exit( )返回值由系统给出。
核心对wait( )作以下处理:
(1)首先查找调用进程是否有子进程,若无,则返回出错码;
(2)若找到一处于“僵死状态”的子进程,则将子进程的执行时间加到父进程的执行时间上,并释放子进程的进程表项;
(3)若未找到处于“僵死状态”的子进程,则调用进程便在可被中断的优先级上睡眠,等待其子进程发来软中断信号时被唤醒。
4、sleep( ) 睡眠指定时间。 系统调用格式: void sleep(int second)
其中,second为指定睡眠的秒数。该函数使得当前进程自我阻塞second秒,由执行态转换成阻塞态,直到系统唤醒。
5、exit( ) 终止进程的执行。 系统调用格式: void exit(status) int status;
其中,status是返回给父进程的一个整数,以备查考。
为了及时回收进程所占用的资源并减少父进程的干预,LINUX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。
如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为exit( )完成以下操作:
(1)关闭软中断 (2)回收资源 (3)写记帐信息
(4)置进程为“僵死状态”
实验指导
1、参照参考程序编写程序,其中父进程无限循环。在后台执行该程序。 [root@localhost lab2]# vi fork_1.c [root@localhost lab2]# gcc fork_1.c -o def [root@localhost lab2]# ./def & [1] 3620 def fork_1.c
子进程用exec( )装入命令ls ,exec( )后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。
如果是下面这种:
[root@localhost lab2]# ./def
则只有用^c强行终止父进程,则在第2步结果就不同了,父子进程都成为僵死进程。 2、显示当前终端上启动的所有进程。 [root@localhost lab2]# ps -af
UID PID PPID C STIME TTY TIME CMD root 3620 3126 0 20:11 pts/1 00:00:00 ./def
root 3621 3620 0 20:11 pts/1 00:00:00 [ls
其中,”
3、用kill命令直接杀死该子进程,看看是否能够成功? [root@localhost lab2]# kill -9 3621 [root@localhost lab2]# ps -af 为什么不能杀死?因为父进程还没结束。 4、用kill命令杀死父进程之后呢?解释原因。 [root@localhost lab2]# kill -9 3620 [root@localhost lab2]# ps -af
但是如果父子进程都异常终止,则shell进程将回收其资源。Init进程回收所有僵死进程资源?
5、修改程序fork_1.c,在父进程执行” while(1)”之前,添加代码”wait(0);”。在后台执行该程序。显示当前终端上启动的所有进程。解释原因。
6、修改以上程序,在子进程执行” printf(“Is son:\\n”);”之前添加代码 “sleep(1);”。观察多进程的执行序列,解释原因。
7、选作:修改“多进程环境实验”中的参考程序,在“子进程1段”前添加代码“sleep(2);”,在“父进程段b”前添加代码“wait(0); wait(0);”。观察多进程的执行序列,解释原因。试一下作其它修改后的效果?
参考程序
#include
#include
int pid;
pid=fork( ); /*创建子进程*/
switch(pid)
{
case -1: /*创建失败*/ printf(\ exit(1);
case 0: /*子进程*/ printf(“Is son:\\n”);
execl(\
printf(\ exit(1);
default: /*父进程*/ printf(\ while(1) sleep(1); exit(0); }
}
思考题
(1)可执行文件加载时进行了哪些处理?参考程序中,什么时候执行语句 ”printf(\fail!\\n\” ?
(2)实验指导中的第5步,wait( )是如何实现进程同步的? (3)实验指导中的第6步,sleep(1)为什么能导致进程切换?
创建进程:
#include
int main() { pid_t pid; if(-1 == (pid = fork())) { printf(\ return 0; } if(0 == pid) { printf(\ } else { printf(\ } return 0; }
使用wait函数让父进程等待子进程运行结束后才开始运行。注意,为了证明父进程确实是等待子进程运行结束后才继续运行的,我们使用了sleep函数。但是,在linux下面,sleep函数的参数是秒,而windows下面sleep的函数参数是毫秒。 #include
int main(int argc, char* argv[]) {
pid_t pid;
pid = fork();
if(0 == pid) {
printf(\ sleep(5); } else {
wait(NULL);
printf(\ }
return 1;