当前位置:文档之家› Linux程序设计上机指导书3:Linux进程控制

Linux程序设计上机指导书3:Linux进程控制

上机三:Linux进程控制1.目的(1)掌握系统调用fork(),exex(),exit()等实现进程创建;(2)掌握进程的终止方式(return、exit、_exit、abort);(3)掌握僵尸进程的产生和避免,以及wait,waitpid的使用;(4)了解守护进程的创建。

2.内容主要上机分析代码文件。

systemtest.c6-3.c6-4.c6-8.c6-9.c其他略。

3.步骤1)Linux进程的创建创建进程可以采用几种方式。

可以执行一个程序(这会导致新进程的创建),也可以在程序内调用一个fork 或exec来创建新进程。

fork 调用会导致创建一个子进程,而exec 调用则会用新程序代替当前进程上下文。

exec系列函数并不创建新进程,调用exec前后的进程ID是相同的。

exec函数的主要工作是清除父进程的可执行代码映像,用新程序的代码覆盖调用exec 的进程代码。

如果exec执行成功,进程将从新程序的main函数入口开始执行。

调用exec 后,除进程ID保持不变外,还有下列进程属性也保持不变。

(1)进程的父进程ID。

(2)实际用户ID和实际用户组ID。

(3)进程组ID、会话ID和控制终端。

(4)定时器的剩余时间。

(5)当前工作目录及根目录。

(6)文件创建掩码UMASK。

(7)进程的信号掩码。

与exec系统调用不同,system将外部可执行程序加载执行完毕后继续返回调用进程。

【例6.3】设计一个程序,用fork函数创建一个子进程,在子进程中,要求显示子进程号与父进程号,然后显示当前目录下的文件信息,在父进程中同样显示子进程号与父进程号。

/*6-3.c 将一个进程分为两个一样的进程,打印出进程的相关信息*/#include<stdio.h> /*文件预处理,包含标准输入输出库*/#include<stdlib.h> /*文件预处理,包含system、exit等函数库*/#include<unistd.h> /*文件预处理,包含fork、getpid、getppid函数库*/#include<sys/types.h> /*文件预处理,包含fork函数库*/int main () /*C程序的主函数,开始入口*/{pid_t result;result=fork(); /*调用fork函数,返回值存在变量result中*/int newret;if(result==-1) /*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/ {perror("创建子进程失败");exit(0);}else if (result==0) /*返回值为0代表子进程*/{printf("返回值是:%d,说明这是子进程!\n此进程的进程号(PID)是:%d\n此进程的父进程号(PPID)是:%d\n",result,getpid(),getppid());execl(“/bin/ls”,”ls”,”-l”,0); /*调用ls程序,显示当前目录下的文件信息*/ }else /*返回值大于0代表父进程*/【步骤1】设计编辑源程序代码。

[root@localhost root]#vi 6-3.c【步骤2】用gcc编译程序。

[root@localhost root]#gcc 6-3.c –o 6-3【步骤3】运行程序。

编译成功后,执行6-3,此时系统会出现运行结果,根据result的值,先显示Linux系统分配给子进程的进程号(PID)和父进程号(PPID),接着运行ls程序,显示当前目录下的文件信息。

再等待10秒钟后,显示父进程的进程号(PID)和父进程号(PPID)。

【步骤4】在6-3.c代码中改变:execl(“/bin/ls”,”ls”,”-l”,0); /*调用ls程序,显示当前目录下的文件信息*/ 替换为:printf("执行前的进程号(PID)是:%d\n",getpid()); /*显示输出进程号*/printf("执行前的父进程号(PPID)是:%d\n",getppid());/*显示输出父进程号*/execv(“6-1”, NULL); /*调用6-1程序*/执行后观察进程的PID和PPID是否有改变。

2)Linux进程的终止(1) 正常终止:(a) 在main函数内执行return语句,这等效于调用exit。

(b) 调用exit函数。

此函数由ANSIC定义,其操作包括调用各终止处理程序,然后关闭所有标准I/O流等。

(c) 调用_exit系统调用函数,此函数由exit调用。

(2) 异常终止:(a) 调用abort 。

(b) 由一个信号终止。

exit, _exit, _Exit 都是进程终止函数。

abort产生SIGABRT 信号。

非正常退出,即在程序碰到灾难性错误时强制退出。

由于是非正常退出,因此不会做其它任何操作。

return与exit的区别在进程操作中exit是结束当前进程或程序并把控制权返回给调用该程序或者进程的进程即父进程并告诉父进程该当前进程的运行状态,而return是从当前函数返回,如果是在main 函数中,main函数结束时隐式地调用exit函数,自然也就结束了当前进程。

return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。

exit函数是退出应用程序,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息。

在main函数里面return(0)和exit(0)是一样的,子函数用return返回;而子进程用exitint main () /*C程序的主函数,开始入口*/{pid_t result;result=fork(); /*调用fork函数,返回值存在变量result中*/if(result==-1) /*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/ {perror("创建子进程失败");exit(0);}else if (result==0) /*返回值为0代表子进程*/{printf("测试终止进程的_exit函数!\n");printf("目前为子进程,这一行我们用缓存!");_exit(0);}else /*返回值大于0代表父进程*/{printf("测试终止进程的exit函数!\n");printf("目前为父进程,这一行我们用缓存!");exit(0);}3)Linux的僵尸进程(wait/waitpid的使用)僵尸进程是指的父进程已经退出,父进程没有处理子进程的退出信息(包括子进程的返回值和其他的一些东西),使得已退出的子进程就成为僵尸进程Defunct ("zombie")。

僵尸进程只是在process table里有一个记录,没有占用其他的资源,除非系统的进程个数的限制已经快超过了,zombie进程不会有更多的坏处。

通过在父进程里增加一个wait/waitpid可以解决僵尸进程问题。

一般来说,当父进程fork()一个子进程后,它必须用wait() 或者waitpid() 等待子进程退出。

正是这个wait() 动作完全清除子进程退出后的信息。

【例题】设计一个程序,要求用户可以选择是否创建子进程,子进程模仿思科(Cisco)1912交换机的开机界面,以命令行的方式让用户选择进入,父进程判断子进程是否正常终止。

图1 算法流程/*6-8.c 创建进程(cisco菜单)*/#include<stdio.h> /*文件预处理,包含标准输入输出库*/#include<unistd.h> /*文件预处理,包含fork函数库*/#include<sys/types.h> /*文件预处理,包含fork、wait、waitpid函数库*/ #include<sys/wait.h> /*文件预处理,包含wait、waitpid函数库*/#include<stdlib.h> /*文件预处理,包含exit函数库*/void display0(); /*子程序声明*/void display1();void display2();int main () /*程序的主函数,开始入口*/{pid_t result;int status,select,num;void (*fun[3])(); /*利用函数指针建立三个子程序*/fun[0]=display0;fun[1]=display1;fun[2]=display2;printf("1.创建子进程\n2.不创建子进程\n请输入您的选择:");scanf("%d",&select);if(select==1) /*如果用户输入1,创建进程*/{result=fork(); /*调用fork函数创建进程,返回值存在变量result中*/if(result==-1){perror("创建进程出错");exit(1);}}if (result==0) /*子进程*/{printf("这是子进程(进程号:%d,父进程号:%d): ",getpid(),getppid());printf("进入思科(Cisco)1912交换机开机界面。

\n ");printf("1 user(s) now active on Management Console.\n");printf("\tUser Interface Menu\n");printf("\t[0] Menus\n");printf("\t[1] Command Line\n");printf("\t[2] IP Configuration\n");printf("Enter Selection:");scanf("%d",&num); /*运用函数指针,运行相应的子程序*/if(num>=0&&num<=2)(*fun[num])();exit(0);}else{waitpid(result,&status,0); /*父进程调用waitpid函数,消除僵尸进程*/printf("这是父进程(进程号:%d,父进程号:%d)\n ",getpid(),getppid());if(WIFEXITED(status)==0)printf("子进程非正常终止,子进程终止状态:%d\n", WIFEXITED(status));elseprintf("子进程正常终止,子进程终止状态:%d\n", WIFEXITED(status));exit(0);}}/*子程序部分*/void display0(){printf("您选择进入了菜单模式\n");}void display1(){printf("您选择进入了命令行模式\n");}void display2(){printf("您选择进入了IP地址配置模式\n");}【步骤1】:设计编辑源程序代码[root@localhost root]#vim 6-8.c【步骤2】:用gcc编译程序[root@localhost root]#gcc 6-8.c –o 6-8【步骤3】:运行程序[root@localhost root]#./6-81.创建子进程2.不创建子进程请输入您的选择:2这是父进程(进程号:5028,父进程号:4739)子进程非正常终止,子进程终止状态:0@再次运行程序[root@localhost root]#./6-81.创建子进程2.不创建子进程请输入您的选择:1这是子进程(进程号:5044,父进程号:5043): 进入思科(Cisco)1912交换机开机界面。

相关主题