实验四 进程管理实验
实验目的:
通过实际上机调试和运行程序了解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,父进程的进程标识符
输入命令
运行多次fork.c
程序出现,如下结果
结果分析
首先我们需要了解程序中的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()
函数被调用一次会返回两个值,这些值是不一样。是父进程则pid
是fork
返回的创建的子进程的进程标识符;若当前是子进程,则fork
返回的子进程的标识符是0。所以我们会看到是在Child process
中的CurPid
中返回的值是0,因为当前是fork()
出来的子进程,而Parent process
中的ChildPid
是fork()
的值,所以返回的是fork()
子进程的PID。
但是我们会发现在运行程序中有一个数字好像一直没有改变就是每个Parent process
中的ParentPid
,一直是4137
使用ps -aux
命令查看4137进程是什么
使用pstree -aup
进程的树形结构查看
由此我猜测4137进程应该是我的命令行进程PID
2. fork2.c
运行截图:
在多次运行fork2
程序的时候,发现其结果并不是完全一致的,就如截图中呈现的那样,结果都是不同的。就如同的实验指导书所说的一样,fork()
之后是先执行子程序还是父程序两者是不确定的。所以会出现上图的情况,第一张图显然是父进程先执行,后交替执行;最后一张图显然是子进程,后交替执行。在第一次fork()
之后,父进程的输出语句需要输出5遍,因为k=5
,而子进程需要输出3遍,因为子进程的k=3,至于为何两者的输出顺序为什么不同,上面已经说明。
3. processimage.c和exec.c
为了更加清楚地了解execve()函数,我们需要我简单地修改了实验指导书的源代码,在processimage.c
文件中,参加如下代码,for(i=0; i< argc; i++) {printf("argv[%d]:%s\n",i ,argv[i]);}
,这是为了更加清楚地了解在execve()函数当中第三个参数char *const envp[]
的含义;
首先我选择简单地运行./exec
命令
运行截图如下:
可以看到子程序在执行到execve()
函数的时候,就没有运行下去了,而是转而运行了processimage
程序,其实使用execve()
函数的时候程序就会将新程序加载到子进程的内存空间,完全代替原来的子程序,但是唯一不变的就是PID,新程序的PID和子程序的PID完全相同,所以程序并没有在继续执行execve()
之后的程序。
但是我们注意到在使用execve()
函数的时候,有三个函数,这三个参数是什么意思呢?首先就是第一个参数就是想要执行的程序名(可以相对路径也可以绝对路径),第二个参数就是指定新进程的命令参数,而此时argv[]
其实就是依次对应相应的参数,例如输入./exec 1 2
运行截图如下:
就会发现我的参数列表就是命令中./exec
、1
和2
。
我们是不是可以联想到利用命令行查看文件夹下内容就需要输入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);
}
运行结果如下:
其实就相当于在命令行内输入ls -l
命令,效果是一样的。
接下来,我们看execve()
第三个参数。envp[]
代表的就是环境变量。因为我添加的代码的原因,所以可以看到环境变量的参数。
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)
分别记录子进程退出前后的这个退出码的值是多少。
最后运行程序结果如图所示:
实验结果分析
首先正常运行,直到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
来查看父程序的状态。如图:
可以得出父进程的状态是S+,即在等待或中断状态,这也验证了我们的猜想,当父进程在执行wait()
的时候就是在等待其子进程的结束。
实验心得:
通过实验我了解了进程相关的fork()
函数和execve()
两个函数基本使用方法,对Linux
中的进程有了初步的认知;对Linux
中的命令行语句有了新的认知,比如查找关键信息命令| grep XX
等其他指令。