Linux程序设计上机指导书3Linux进程控制.docx
《Linux程序设计上机指导书3Linux进程控制.docx》由会员分享,可在线阅读,更多相关《Linux程序设计上机指导书3Linux进程控制.docx(15页珍藏版)》请在冰豆网上搜索。
Linux程序设计上机指导书3Linux进程控制
上机三:
Linux进程控制
1.目的
(1)掌握系统调用fork(),exex(),exit()等实现进程创建;
(2)掌握进程的终止方式(return、exit、_exit、abort);
(3)掌握僵尸进程的产生和避免,以及wait,waitpid的使用;
(4)了解守护进程的创建。
2.内容
主要上机分析代码文件。
systemtest.c
6-3.c
6-4.c
6-8.c
6-9.c
其他略。
3.步骤
1)Linux进程的创建
创建进程可以采用几种方式。
可以执行一个程序(这会导致新进程的创建),也可以在程序内调用一个fork或exec来创建新进程。
fork调用会导致创建一个子进程,而exec调用则会用新程序代替当前进程上下文。
exec系列函数并不创建新进程,调用exec前后的进程ID是相同的。
exec系列函数如下。
#include
intexecv(constchar*path,char*constargv[]);
intexecve(constchar*path,char*constargv[],char*constenvp[]);
intexecvp(constchar*file,char*constargv[]);
intexecl(constchar*path,constchar*arg,...);
intexecle(constchar*path,constchar*arg,...);
intexeclp(constchar*file,constchar*arg,...);
exec函数的主要工作是清除父进程的可执行代码映像,用新程序的代码覆盖调用exec的进程代码。
如果exec执行成功,进程将从新程序的main函数入口开始执行。
调用exec后,除进程ID保持不变外,还有下列进程属性也保持不变。
(1)进程的父进程ID。
(2)实际用户ID和实际用户组ID。
(3)进程组ID、会话ID和控制终端。
(4)定时器的剩余时间。
(5)当前工作目录及根目录。
(6)文件创建掩码UMASK。
(7)进程的信号掩码。
与exec系统调用不同,system将外部可执行程序加载执行完毕后继续返回调用进程。
system的返回值就是被加载的程序的返回值。
/*systemtest.cfortestSystem.调用system创建进程*/
#include
#include
voidmain()
{
//调用system执行“ls-l”,并输出system的返回值
printf("systemreturncode=%d\n",system("ls-l"));
return0;
}
【例6.3】设计一个程序,用fork函数创建一个子进程,在子进程中,要求显示子进程号与父进程号,然后显示当前目录下的文件信息,在父进程中同样显示子进程号与父进程号。
/*6-3.c将一个进程分为两个一样的进程,打印出进程的相关信息*/
#include/*文件预处理,包含标准输入输出库*/
#include/*文件预处理,包含system、exit等函数库*/
#include/*文件预处理,包含fork、getpid、getppid函数库*/
#include/*文件预处理,包含fork函数库*/
intmain()/*C程序的主函数,开始入口*/
{
pid_tresult;
result=fork();/*调用fork函数,返回值存在变量result中*/
intnewret;
if(result==-1)/*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/
{
perror("创建子进程失败");
exit(0);
}
elseif(result==0)/*返回值为0代表子进程*/
{
printf("返回值是:
%d,说明这是子进程!
\n此进程的进程号(PID)是:
%d\n此进程的父进程号(PPID)是:
%d\n",result,getpid(),getppid());
execl(“/bin/ls”,”ls”,”-l”,0);/*调用ls程序,显示当前目录下的文件信息*/
}
else/*返回值大于0代表父进程*/
{
sleep(10);
printf("返回值是:
%d,说明这是父进程!
\n此进程的进程号(PID)是:
%d\n此进程的父进程号(PPID)是:
%d\n",result,getpid(),getppid());
}
}
【步骤1】设计编辑源程序代码。
[root@localhostroot]#vi6-3.c
【步骤2】用gcc编译程序。
[root@localhostroot]#gcc6-3.c–o6-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返回;而子进程用exit退出,调用exit时要调用一段终止处理程序,然后关闭所有I/O流。
/*6-4.c程序:
用exit和_exit函数终止进程的区别*/
#include/*文件预处理,包含标准输入输出库*/
#include/*文件预处理,包含exit函数库*/
#include/*文件预处理,包含fork、exit函数库*/
#include/*文件预处理,提供pid_t的定义*/
intmain()/*C程序的主函数,开始入口*/
{
pid_tresult;
result=fork();/*调用fork函数,返回值存在变量result中*/
if(result==-1)/*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/
{
perror("创建子进程失败");
exit(0);
}
elseif(result==0)/*返回值为0代表子进程*/
{
printf("测试终止进程的_exit函数!
\n");
printf("目前为子进程,这一行我们用缓存!
");
_exit(0);
}
else/*返回值大于0代表父进程*/
{
printf("测试终止进程的exit函数!
\n");
printf("目前为父进程,这一行我们用缓存!
");
exit(0);
}
}
3)Linux的僵尸进程(wait/waitpid的使用)
僵尸进程是指的父进程已经退出,父进程没有处理子进程的退出信息(包括子进程的返回值和其他的一些东西),使得已退出的子进程就成为僵尸进程Defunct("zombie")。
僵尸进程只是在processtable里有一个记录,没有占用其他的资源,除非系统的进程个数的限制已经快超过了,zombie进程不会有更多的坏处。
通过在父进程里增加一个wait/waitpid可以解决僵尸进程问题。
一般来说,当父进程fork()一个子进程后,它必须用wait()或者waitpid()等待子进程退出。
正是这个wait()动作完全清除子进程退出后的信息。
【例题】设计一个程序,要求用户可以选择是否创建子进程,子进程模仿思科(Cisco)1912交换机的开机界面,以命令行的方式让用户选择进入,父进程判断子进程是否正常终止。
图1算法流程
/*6-8.c创建进程(cisco菜单)*/
#include/*文件预处理,包含标准输入输出库*/
#include/*文件预处理,包含fork函数库*/
#include/*文件预处理,包含fork、wait、waitpid函数库*/
#include/*文件预处理,包含wait、waitpid函数库*/
#include/*文件预处理,包含exit函数库*/
voiddisplay0();/*子程序声明*/
voiddisplay1();
voiddisplay2();
intmain()/*程序的主函数,开始入口*/
{
pid_tresult;
intstatus,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("1user(s)nowactiveonManagementConsole.\n");
printf("\tUserInterfaceMenu\n");
printf("\t[0]Menus\n");
printf("\t[1]CommandLine\n");
printf("\t[2]IPConfiguration\n");
printf("EnterSelection:
");
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));
else
printf("子进程正常终止,子进程终止状态:
%d\n",WIFEXITED(status));
exit(0);
}
}
/*子程序部分*/
voiddisplay0()
{
printf("您选择进入了菜单模式\n");
}
voiddisplay1()
{
printf("您选择进入了命令行模式\n");
}
voiddisplay2()
{
printf("您选择进入了IP地址配置模式\n");
}
【步骤1】:
设计编辑源程序代码
[root@localhostroot]#vim6-8.c
【步骤2】:
用gcc编译程序
[root@localhostroot]#gcc6-8.c–o6-8
【步骤3】:
运行程序
[root@localhostroot]#./6-8
1.创建子进程
2.不创建子进程
请输入您的选择:
2
这是父进程(进程号:
5028,父进程号:
4739)
子进程非正常终止,子进程终止状态:
0
@再次运行程序
[root@localhostroot]#./6-8
1.创建子进程
2.不创建子进程
请输入您的选择:
1
这是子进程(进程号:
5044,父进程号:
5043):
进入思科(Cisco)1912交换机开机界面。
1user(s)nowactiveonManagementConsole.
UserInterfaceMenu
[0]Menus
[1]CommandLine
[2]IPConfiguration
EnterSelection:
0
您选择进入了菜单模式
这是父进程(进程号:
5043,父进程号:
4739)
子进程正常终止,子进程终止状态:
1
【步骤4】:
修改程序
试着不用waitpid函数,如下所示:
//waitpid(result,&status,0);/*父进程调用waitpid函数,消除僵尸进程*/
再次运行程序
[root@localhostroot]#./6-8
1.创建子进程
2.不复创建子程
请输入您的选择:
1
这是子进程(进程号:
5067,父进程号:
5064):
进入思科(Cisco)1912交换机开机界面。
1user(s)nowactiveonManagementConsole.
UserInterfaceMenu
[0]Menus
[1]CommandLine
[2]IPConfiguration
EnterSelection:
这是父进程(进程号:
5064,父进程号:
4739)
子进程非正常终止,子进程终止状态:
0
2)Linux守护进程
守护进程(Daemon)是运行在后台的一种特殊进程。
它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程的特性:
(1)守护进程最重要的特性是后台运行。
(2)守护进程必须与其运行前的环境隔离开来。
这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩码等。
这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
(3)守护进程的启动方式有其特殊之处。
它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,也可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。
编写守护进程的要点:
(1)创建子进程,终止父进程
(2)在子进程中创建新会话
(3)当前工作目录换成其他的路径,如“/”或“/tmp”等
(4)把文件创建掩码设置为0
(5)关闭文件描述符
【例】设计两段程序,主程序6-9.c和初始化程序init.c。
要求主程序每隔10秒钟向/tmp目录中的日志6-9.log报告运行状态。
初始化程序中的init_daemon函数负责生成守护进程。
分析把生成守护进程的部分写成独立的函数init_daemon,放在程序init.c中,方便调用,程序init.c中要编写的是前面守护进程的五步。
主程序先调用init_daemon函数,使得主程序运行后成为守护进程,接着主程序用while语句无限循环,每隔10秒钟往6-9.log文件中写入一行文字和当前时间。
/*6-9.c程序:
主程序每隔一分钟向/tmp目录中的日志6-9.log报告运行状态*/
#include/*文件预处理,包含标准输入输出库*/
#include/*文件预处理,包含时间函数库*/
voidinit_daemon(void);/*守护进程初始化函数*/
intmain()/*C程序的主函数,开始入口*/
{
FILE*fp;
time_tt;
init_daemon();/*初始化为daemon*/
while
(1)/*无限循环,每隔10秒钟向6-9.log写入运行状态*/
{
sleep(10);/*睡眠10秒钟*/
if((fp=fopen("6-9.log",”a+”))>=0)/*打开6-9.log文件,若没有此文件,创建它*/
{
t=time(0);
fprintf(fp,"守护进程还在运行,时间是:
%s",asctime(localtime(&t)));
fclose(fp);
}
}
}
/*初始化程序init.c程序代码如下:
init.c程序:
生成守护进程*/
#include
#include
#include
#include
#include
#include
voidinit_daemon(void)
{
pid_tchild1,child2;
inti;
child1=fork();
if(child1>0)/*
(1)创建子进程,终止父进程*/
exit(0);/*这是子进程,后台继续执行*/
elseif(child1<0)
{
perror("创建子进程失败");/*fork失败,退出*/
exit
(1);
}
setsid();/*
(2)在子进程中创建新会话*/
chdir("/tmp");/*(3)改变工作目录到"/tmp"*/
umask(0);/*(4)重设文件创建掩码*/
for(i=0;iclose(i);
return;
}
【步骤1】:
设计编辑源程序代码
[root@localhostroot]#vim6-9.c
[root@localhostroot]#viminit.c
【步骤2】:
用gcc编译程序
[root@localhostroot]#gcc6-9.cinit.c–o6-9
【步骤3】:
运行程序
编译成功后,执行可执行文件6-9,程序运行后,没有任何提示,等待一段时间后,查看一下6-9.log文件中有没有文字写入,输入“tail-6/tmp/6-9.log”,显示6-9.log文件中的最后6行,从时间上看,说明守护进程在暗地里每隔10秒写入一串字符,如下所示:
[root@localhostroot]#./6-9
[root@localhostroot]#tail-6/tmp/6-9.log
守护进程还在运行,时间是:
FriOct2114:
30:
122011
守护进程还在运行,时间是:
FriOct2114:
30:
222011
守护进程还在运行,时间是:
FriOct2114:
30:
322011
守护进程还在运行,时间是:
FriOct2114:
30:
422011
守护进程还在运行,时间是:
FriOct2114:
30:
522011
可见,6-9确实一直在运行,而且看到“?
”,结合Linux环境下进程的知识,知道确实有了一个守护进程。
结束此守护进程可用kill命令,如kill5108撤销PID为5108的进程6-9。
4.验收要求
1)掌握fork进程创建函数的使用特点。
a)解释fork、exec后父进程与子进程的PID、PPID分别有何特点。
b)能用ps查看进程状态。
2)掌握进程退出的几种方式。
a)return、exit等的异同。
3)掌握Linux僵尸进程的特点及wait/waitpid函数的使用。
a)掌握僵尸进程的作用。
b)如何体现子进程与父进程迸发执行的效果,并且子进程退出后父进程才退出。
c)利用wait/waitpid来获取子进程的返回信息。
d)能用ps查看进程状态。
4)了解Linux守护进程的特点及编写模式
a)守护进程的使用特点
b)守护进程的编写要点
c)会话的含义及setsid的作用