进程生命周期实验的体验:
在阅读了《Linux操作系统原理与应用》一书第三章中fork()、exec、wait()、exit()等函数的应用与原理后,参考书中例子,编写了如下代码:
一、实现shell中top命令;
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
int main(){
pid_t pid,pc;//定义一个pid结构体,pid_t在task_struct下;
char command;
char *arg_tp_d[]={"top",NULL};//shell 中top -d 3,3秒更新一次cpu进程情况;
char *arg_tp_P[]={"top","-P",NULL};//shell中top -P,以CPU的使用资源降序排序显示
char *arg_tp_M[]={"top","-M",NULL};//shell中top -M,以内存的使用资源降序排序显示
char *arg_tp_N[]={"top","-N",NULL};//shell中top -N,以pid降序排序显示
char *arg_tp_T[]={"top","-T",NULL};//shell中top -T,由进程使用的时间累计排序显示
char *arg_tp_q[]={"top","-q",NULL};//shell中top -q,退出top
char *envp[]={0,NULL};
while(1){
printf("**********************************\n");
printf("输入top相关命令,来查看进程动态变化情况\n");
printf("输入d执行'top -d 3'命令\n");
printf("输入P执行'top -P'命令,以CPU的使用资源降序排序显示\n");
printf("输入M执行'top -M'命令,以内存的使用资源降序排序显示\n");
printf("输入N执行'top -N'命令,以pid降序排序显示\n");
printf("输入T执行'top -T'命令,由进程使用的时间累计排序显示\n");
printf("输入q执行'top -q'命令,退出top命令\n");
command = getchar();//获取命令;
getchar();//回车;
pid = fork();//创建子进程;
if(pid<0) {
printf("Error!");//创建子进程失败;
return -1;
}
else if(pid==0){//子进程;
printf("This is the child process with pid of %d\n",getpid());
switch(command){
case 'd':
execve("/bin/top",arg_tp_d,envp);
break;
case 'P':
execve("/bin/top",arg_tp_P,envp);
break;
case 'M':
execve("/bin/top",arg_tp_M,envp);
break;
case 'N':
execve("/bin/top",arg_tp_N,envp);
break;
case 'T':
execve("/bin/top",arg_tp_T,envp);
break;
case 'q':
execve("/bin/top",arg_tp_q,envp);
break;
default:
perror("wrong command:\n");
break;//子进程结束;
}
exit(0);//子进程进入僵尸状态;
}
else{//父进程
sleep(60);
printf("This is father process with pid of %d\n",pid);
pc=wait(NULL);
printf("I catched a child process with pid of %d\n",pc);
}
}
return 0;
}
代码设计想法:
- 以上代码父进程打印控制菜单,并接收命令;创建子进程,子进程去处理任务;
- 父进程与子进程协调共同完成shell中的top的相关命令;
- 其中top的二进制文件在bin目录下的top文件;
以下是输出结果:
在将process_tst.c文件编译后运行起来,由于top命令需要term环境有所要求,无法通过execve函数来实现打印功能,故放弃;
二、实现shell中ps相关命令:
尝试使用其他shell命令(ps相关命令):
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
int main(){
pid_t pid,pc;//定义一个pid结构体,pid_t在task_struct下;
char command;
char *arg_ps_tree[]={"pstree",NULL};//shell 中pstree;
char *arg_ps_ef[]={"ps","-ef|more",NULL};//shell中ps -ef|more,查看进程号;
char *arg_ps_a[]={"ps","-a",NULL};//shell中ps -a;
char *arg_ps_x[]={"ps","-x",NULL};//shell中ps -x;
char *envp[]={0,NULL};
while(1){
printf("**********************************\n");
printf("输入ps相关命令,来查看进程相关信息\n");
printf("输入t执行'pstree'命令\n");
printf("输入e执行'ps -ef|more'命令,查看进程号\n");
printf("输入a执行'ps -a'命令\n");
printf("输入x执行'ps -x'命令\n");
printf("输入q退出\n");
command = getchar();//获取命令;
getchar();//回车;
pid = fork();//创建子进程;
if(pid<0) {
perror("Error!");//创建子进程失败;
return -1;
}
else if(pid==0){//子进程;
printf("This is the child process with pid of %d\n",getpid());
switch(command){
case 't':
execve("/bin/pstree",arg_ps_tree,envp);
break;
case 'e':
execve("/bin/ps",arg_ps_ef,envp);
break;
case 'a':
execve("/bin/ps",arg_ps_a,envp);
break;
case 'x':
execve("/bin/ps",arg_ps_x,envp);
break;
case 'q':
break;
default:
perror("wrong command:\n");
break;//子进程结束;
}
exit(0);//子进程进入僵尸状态;
}
else{//父进程
pc=wait(NULL);
sleep(30);
printf("This is father process with pid of %d\n",getpid());
if(command == 'q') break;
printf("I catched a child process with pid of %d\n",pc);
}
}
return 0;
}
代码设计想法:
- 以上代码父进程打印控制菜单,并接收命令;创建子进程,子进程去处理任务;
- 父进程与子进程协调共同完成shell中的ps的相关命令;
- 其中ps的二进制文件在bin目录下的ps文件;pstrees的二进制文件在bin目录下的pstree文件;
以下是输出及调试结果:
在输入a、x后,对ps-a、ps -x命令都有很好的回应,顺利打印了进程的相关信息,从上图也能看到process_tst1的4个进程;
在输入e要实现“ps -ef|more”命令时,出现了错误,经分析“ps -ef|more”后,这是一个同时执行ps命令与more命令的复合命令,中间的|是管道。而我们代码中只给了ps二进制目录,more的二进制目录并未给出,对于如何在execve函数中同时执行两个命令现阶段仍不太清楚,如果有同学知道,希望在评论区给出你的答案;
case 'e':
execve("/bin/ps",arg_ps_ef,envp);
break;
在输入t后实现“pstree”命令,顺利打印出系统中进程关系,在最后可以看到我们自己创建的两个process_tst1进程;
三、分析进程生命周期:
针对第二次实验中的代码及输出,可以对进程的创建到结束有一定的认识:
我们在运行process_tst1可执行文件(ELF)后,便生成了一个进程,也就是下面进程关系图中的process_tst1(至于为什么有两个process_tst1进程?是因为我在两个终端上分别运行了process_tst1,是同一个程序运行了两遍,这两个process_tst1进程对应的进程号不同,是不同的进程);
进程在执行程序到 pid = fork();
时,进程 process_tst1创建子进程(在下图中可以看到不同进程process_tst1后面fork出两个子进程ps和pstree,这是子进程在继承了父进程后自己独有的特色);
此时父进程通过wait(NULL)
来等待子进程运行,只有子进程运行结束,父进程才能进行下一步运算;
在子进程中,通过输入不同的command值来选择执行不同的execve函数,再通过execve函数执行不同的shell命令;在子进程运行结束调用exit()函数后,子进程便变为僵尸状态,等待父进程中wait()函数取出子进程结束时的状态,最终子进程完全结束;
持续更新中,后续会加入对fork()、execve、wait()、exit()、sleep()等函数具体实现的分析…