操作系统 实验三 进程管理

实验四 进程管理实验

实验目的:

通过实际上机调试和运行程序了解Linux系统中的进程基本编程:创建和终止,了解父进程和子进程的概念。

预习报告:

要进行这次实验首先就是需要了解fork()函数、execve()函数以及wait()函数和waitpid()函数。

fork()函数可以创建一个子进程,与原来的进程几乎完全一样。在fork()之后会返回两个值,一个是子进程的PID,另一个是子进程返回的fork值,值得注意的是这个值为0,所以我们可以通过返回值判断当前执行的程序是否是是子进程,还可以通过返回值让父子进程做不同的事情。

getpid()是得到当前的进程PID;getppid()是得到当前进程的父进程PID。

execve()函数,即int execve(const char *filename, char *const argv[],char *const envp[]),其中filename是运行程序的位置(相对或者绝对路径),argv[]是传给此程序的命令参数,而envp[]是程序运行的环境变量列表。

wait()函数,让父进程暂停执行,直到它的一个子进程结束为止。该函数的返回值是终止运行的子进程的PID(第一个运行结束的子进程PID)。

waitpid()也用来等待子进程的结束,但它用于等待某个特定进程结束。参数pid 指明要等待子进程的PID。

实验观测记录及数据处理:

1. fork.c

为了让结果更加清晰明了易懂,我稍微修改了实验指导手册中的示例程序,修改部分如下:

printf("Child process is running, SelfPid is %d ,CurPid is %d, ParentPid is %d\n", getpid(),pid, getppid());

printf("Parent process is running, SelfPid is %d, ChildPid is %d, ParentPid is %d\n", getpid(), pid, getppid());

即程序返回自己的进程标识符,自己进程的进程标识符,fork()函数返回的pid,父进程的进程标识符

输入命令

img

运行多次fork.c程序出现,如下结果

img

结果分析

首先我们需要了解程序中的getpid()就是返回当前自己进程的进程标识符,getppid()就是返回父进程的进程标识符。

接下来就是分析程序结果,

Parent process is running, SelfPid is 4334, ChildPid is 4335, ParentPid is 4137

这句话是父进程的信息内容,我们可以了解到他自身的PID是4334,子进程的PID是4335,父进程是4137,通过这个信息我们应该可以猜测到子进程的PID应该时4335,子进程的父进程应该是4334,接下来我们看结果。

Child process is running, SelfPid is 4335 ,CurPid is 0, ParentPid is 4334

猜测的结果正确

因为fork()函数被调用一次会返回两个值,这些值是不一样。是父进程则pidfork返回的创建的子进程的进程标识符;若当前是子进程,则fork返回的子进程的标识符是0。所以我们会看到是在Child process中的CurPid中返回的值是0,因为当前是fork()出来的子进程,而Parent process中的ChildPidfork()的值,所以返回的是fork()子进程的PID。

但是我们会发现在运行程序中有一个数字好像一直没有改变就是每个Parent process中的ParentPid,一直是4137

使用ps -aux命令查看4137进程是什么

img

使用pstree -aup进程的树形结构查看

img

由此我猜测4137进程应该是我的命令行进程PID

2. fork2.c

运行截图:

img

img

在多次运行fork2程序的时候,发现其结果并不是完全一致的,就如截图中呈现的那样,结果都是不同的。就如同的实验指导书所说的一样,fork()之后是先执行子程序还是父程序两者是不确定的。所以会出现上图的情况,第一张图显然是父进程先执行,后交替执行;最后一张图显然是子进程,后交替执行。在第一次fork()之后,父进程的输出语句需要输出5遍,因为k=5,而子进程需要输出3遍,因为子进程的k=3,至于为何两者的输出顺序为什么不同,上面已经说明。

3. processimage.cexec.c

为了更加清楚地了解execve()函数,我们需要我简单地修改了实验指导书的源代码,在processimage.c文件中,参加如下代码,for(i=0; i< argc; i++) {printf("argv[%d]:%s\n",i ,argv[i]);},这是为了更加清楚地了解在execve()函数当中第三个参数char *const envp[]的含义;

首先我选择简单地运行./exec命令

运行截图如下:

img

可以看到子程序在执行到execve()函数的时候,就没有运行下去了,而是转而运行了processimage程序,其实使用execve()函数的时候程序就会将新程序加载到子进程的内存空间,完全代替原来的子程序,但是唯一不变的就是PID,新程序的PID和子程序的PID完全相同,所以程序并没有在继续执行execve()之后的程序。

但是我们注意到在使用execve()函数的时候,有三个函数,这三个参数是什么意思呢?首先就是第一个参数就是想要执行的程序名(可以相对路径也可以绝对路径),第二个参数就是指定新进程的命令参数,而此时argv[]其实就是依次对应相应的参数,例如输入./exec 1 2

运行截图如下:

img

就会发现我的参数列表就是命令中./exec12

我们是不是可以联想到利用命令行查看文件夹下内容就需要输入ls -l,其实这就是参数列表中的两个参数argv[0]=’ ls’, argv[1]=’-l’。下面给出示例程序,来验证自己的猜想。

#include<stdio.h>

#include<unistd.h>

int main(int arg, char **args)

{

// 运行时候的参数

char *argv[]={“ls”,“-al”, NULL};

char *envp[]={0,NULL};

// 使用bin目录下的ls,参数是ls -al

execve(“/bin/ls”,argv,envp);

}

运行结果如下:

img

其实就相当于在命令行内输入ls -l命令,效果是一样的。

接下来,我们看execve()第三个参数。envp[]代表的就是环境变量。因为我添加的代码的原因,所以可以看到环境变量的参数。

img

4. wait.c

首先,我们需要知道wait(int *statloc)中的statloc地址中的值是会发生变化的,因为wait()之后这个statloc地址所指向的变量就是子进程的退出码。所以为了更加明显的看出这个statloc的变化,我在实验报告源代码的基础之上,在child_pid = wait (&stat_val)前后添加了代码,printf("BeforeExit: %d\n", stat_val)printf("AfterExit: %d\n", stat_val)分别记录子进程退出前后的这个退出码的值是多少。

最后运行程序结果如图所示:

img

实验结果分析

首先正常运行,直到pid = fork (),程序会创建一个子进程,而父进程会继续执行,子进程也会从switch继续执行,在进入switch选择分支的时候,父进程的exit_code = 0;而子进程首先分别执行msg = "Child process is running";k = 5;exit_code = 37,关键就是之后的执行的if语句。首先分析父进程,因为父进程的fork()返回的是子进程的pid_t,所以会执行pid != 0下的程序,我们可以通过输出程序看出,在进入wait()之前的退出状态码是0,然后执行child_pid = wait(&stat_val),父程序中断等待子程序运行完毕,而子程序进入if语句之后,因为 pid = 0 ,所以进入else语句中,执行while循环。在子程序结束之后,父程序继续运行,执行printf("AfterExit: %d\n", stat_val),可以看出在退出之后,退出状态码为从0变为了9472,在继续执行判断终止状态的宏,WIFEXITED (stat_val)成立,所以继续执行printf ("Child exited with code %d\n", WEXITSTATUS (stat_val)),打印出语句Child exited with code 37,其中因为子程序顺利停止,执行exit(exit_code),其中exit_code=37,所以在执行WEXITSTATUS (stat_val)的时候返回的值是37。

当我们子程序运行的时候,因为父程序是等待的状态,所以使用ps -aux | grep 5291来查看父程序的状态。如图:

img

可以得出父进程的状态是S+,即在等待或中断状态,这也验证了我们的猜想,当父进程在执行wait()的时候就是在等待其子进程的结束。

实验心得:

通过实验我了解了进程相关的fork()函数和execve()两个函数基本使用方法,对Linux中的进程有了初步的认知;对Linux中的命令行语句有了新的认知,比如查找关键信息命令| grep XX等其他指令。

(1) 进程的创建编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示字符“a”;子进程分别显示字符“b”和字符“c”。试观察记录屏幕上的显示结果,并分析原因。(2) 进程的控制修改已编写的程序,将每个进程输出一个字符改为每个进程输出一句话,在观察程序执行时屏幕上出现的现象,并分析原因。(3) 编制一段程序,使其实现进程的软中断通信。要求:使用系统调用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按Del键);当捕捉到中断信号后,父进程调用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止: Child process 1 is killed by parent! Child process 2 is killed by parent! 父进程等待两个子进程终止后,输出如下的信息后终止: Parent process is killed! 在上面的程序中增加语句signal(SIGINT, SIG_IGN)和 signal(SIGQUIT, SIG_IGN),观察执行结果,并分析原因。(4) 进程的管道通信编制一段程序,实现进程的管道通信。使用系统调用pipe( )建立一条管道线;两个进程P1和P2分别向管道各写一句话: Child 1 is sending a message! Child 2 is sending a message! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。
1.基本系统进程   Csrss.exe:这是子系统服务器进程,负责控制Windows创建或删除线程以及16位的虚拟DOS环境。   System Idle Process:这个进程是作为单线程运行在每个处理器上,并在系统不处理其它线程的时候分派处理器的时间。   Smss.exe:这是一个会话管理子系统,负责启动用户会话。   Services.exe:系统服务的管理工具。   Lsass.exe:本地的安全授权服务。   Explorer.exe:资源管理器。   Spoolsv.exe:管理缓冲区中的打印和传真作业。   Svchost.exe:这个进程要着重说明一下,有不少朋友都有这种错觉:若是在“任务管理器”中看到多个Svchost.exe在运行,就觉得是有病毒了。其实并不一定,系统启动的时候,Svchost.exe将检查注册表中的位置来创建需要加载的服务列表,如果多个Svchost.exe同时运行,则表明当前有多组服务处于活动状态;多个DLL文件正在调用它。   至于其它一些附加进程,大多为系统服务,是可以酌情结束运行的。由于其数量众多,我们在此也不便于一一列举。   在系统资源紧张的情况下,我们可以选择结束一些附加进程,以增加资源,起到优化系统的作用。在排除基本系统及附加进程后,新增的陌生进程就值得被大家怀疑了。 更多内容请看Windows操作系统安装、系统优化大全、系统安全设置专题,或进入讨论组讨论。
(1)进程的软中断通信 #include #include #include #include int wait_flag; void stop(); main( ) { int pid1, pid2; // 定义两个进程号变量 signal(2,stop); // 或者 signal (14,stop); while((pid1 = fork( )) == -1); // 若创建子进程1不成功,则空循环 if(pid1 > 0) { // 子进程创建成功,pid1为进程号 while((pid2 = fork( )) == -1); // 创建子进程2 if(pid2 > 0) { wait_flag = 1; //sleep(1); // 父进程等待5秒 kill(pid1,SIGUSR1); // 杀死进程1 kill(pid2,SIGUSR2); // 杀死进程2 wait(0); wait(0); printf("\n Parent process is killed !!\n"); exit(0); // 父进程结束 } else { wait_flag = 1; signal(SIGUSR2,stop); // 等待进程2被杀死的中断号17 printf("\n Child process 2 is killed by parent !!\n"); exit(0); } } else { wait_flag = 1; signal(SIGUSR1,stop); // 等待进程1被杀死的中断号16 printf("\n Child process 1 is killed by parent !!\n"); exit(0); } } void stop() { wait_flag = 0; } (2)进程的管道通信 #include #include #include int pid1,pid2; // 定义两个进程变量 main( ) { int fd[2]; char OutPipe[100],InPipe[100]; // 定义两个字符数组 pipe(fd); // 创建管道 while((pid1 = fork( )) == -1); // 如果进程1创建不成功,则空循环 if(pid1 == 0) { lockf(fd[1],1,0); // 锁定管道 sprintf(OutPipe,"\n Child process 1 is sending message!\n"); write(fd[1],OutPipe,50); // 向管道写入数据 sleep(5); // 等待读进程读出数据 lockf(fd[1],0,0); // 解除管道的锁定 exit(0); // 结束进程1 } else { while((pid2 = fork()) == -1); // 若进程2创建不成功,则空循环 if(pid2 == 0) { lockf(fd[1],1,0); sprintf(OutPipe,"\n Child process 2 is sending message!\n"); write(fd[1],OutPipe,50); sleep(5); lockf(fd[1],0,0); exit(0); } else { wait(0); // 等待子进程1 结束 read(fd[0],InPipe,50); // 从管道中读出数据 printf("%s\n",InPipe); // 显示读出的数据 wait(0); // 等待子进程2 结束 read(fd[0],InPipe,50); printf("%s\n",InPipe); exit(0); // 父进程结束 } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值