实验二: 进程控制
一、实验目的
1、加深对进程概念的理解,明确进程和程序的区别。
2、掌握Linux系统中的进程创建,管理和删除等操作。
3、 熟悉使用Linux下的命令和工具,如man, find, grep, whereis, ps, pgrep, kill, ptree, top, vim, gcc,gdb, 管道|等。
二、实验题目
1、打开一个vi进程。通过ps命令以及选择合适的参数,只显示名字为vi的进程。寻找vi进程的父进程,直到init进程为止。记录过程中所有进程的ID和父进程ID。将得到的进程树和由pstree命令的得到的进程树进行比较。
(1)打开终端,输入指令vi test1,新建文件test1。
(2)打开另一个终端,输入指令ps -ef | grep test1,显示。
参考链接:https://www.cnblogs.com/freinds/p/8074651.html
ps -ef是显示所有进程的信息,grep是查找输出包含想要的字符串的行,即查找vi test1进程的信息。
输出的字段含义如下:
UID PID PPID C STIME TTY TIME CMD
UID:程序被该UID所拥有
PID:就是这个程序的ID
PPID:则是其上级父程序的ID
C:CPU使用的资源百分比
STIME:系统启动时间
TTY:登入者的终端机位置
TIME:使用掉的 CPU 时间。
CMD:所下达的是什么指令
(3)查找父进程的对应的ID,直到父进程ID为1(init进程ID为1)
搜索父进程顺序为:2192 -> 2184 -> 2174 -> 1453 -> 1
(4)输入pstree –p命令查看进程树
观察到两种方式所的结果相同。
2、编写程序,首先使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。然后在另外一个终端中,通过ps –Al命令、ps aux或者top等命令,查看vi进程及其父进程的运行状态,理解每个参数所表达的意义。选择合适的命令参数,对所有进程按照cpu占用率排序。
fork.c代码:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid=fork();
if(pid>0)
{
//父进程中执行空循环
while(1){}
}
else if(!pid)
{
//子进程中调用exec打开vi编辑器
int ret;
ret=execl("/usr/bin/vi","vi",NULL);
if(ret==-1)
perror("execl");
}
else
{
printf("调用失败");
}
return 0;
}
(1)编译并运行fork.c
打开了vi
(2)参考链接:https://blog.csdn.net/lsbhjshyn/article/details/18549869
使用ps –al命令查看vi进程及其父进程的运行状态
# F 代表这个程序的旗标 (flag), 4 代表使用者为 superuser;
# S 代表这个程序的状态 (STAT);
# UID 代表执行者身份
# PID 进程的ID号!底下的 PPID 则父进程的ID;
# C CPU 使用的资源百分比
# PRI指进程的执行优先权(Priority的简写),其值越小越早被执行;
# NI 这个进程的nice值,其表示进程可被执行的优先级的修正数值。
# ADDR 这个是内核函数,指出该程序在内存的那个部分。如果是个执行的程序,一般就是『 - 』
# SZ 使用掉的内存大小;
# WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作;
# TTY 登入者的终端机位置啰;
# TIME 使用掉的 CPU 时间。
# CMD 所下达的指令名称
(3)使用ps –aux命令查看vi进程及其父进程的运行状态
USER:该进程属于哪个使用者账号的
PID :该进程的进程ID号。
%CPU:该进程使用掉的 CPU 资源百分比;
%MEM:该进程所占用的物理内存百分比;
VSZ :该进程使用掉的虚拟内存量 (Kbytes)
RSS :该进程占用的固定的内存量 (Kbytes)
TTY :该进程是在那个终端机上面运作,若与终端机无关,则显示“?”,另外,若为 pts/0 等等的,则表示为由网络连接进主机的程序。
STAT:该程序目前的状态,主要的状态有:
R :该程序目前正在运作,或者是可被运作;
S :该程序目前正在睡眠当中 (可说是 idle 状态啦!),但可被某些讯号(signal) 唤醒。
T :该程序目前正在侦测或者是停止了;
Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie 程序的状态
• START:该进程被触发启动的时间;
• TIME :该进程实际使用 CPU 运作的时间。
• COMMAND:该程序的实际指令为什么
(4)对所有进程按照CPU占用率排序
参考链接:https://blog.csdn.net/ccj1985/article/details/6201713
ps auxw –sort=%cpu按照CPU占有率从小到大排序
如图所示,第三列为CPU占有率
3、使用fork系统调用,创建如下进程树,并使每个进程输出自己的ID和父进程的ID。观察进程的执行顺序和运行状态的变化。
(1)代码:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
pid_t pid1;
printf("P1为父进程,PID为%d\n",getpid());
pid1 = fork();
if(pid1<0)
{
printf("创建失败\n");
return 0;
}
else if(pid1 == 0)
{
printf("P3是子进程,PID为%d\n",getpid());
return 0;
}
else
{
pid_t pid2;
pid2 = fork();
if(pid2<0)
{
printf("创建失败\n");
return 0;
}
else if(pid2 == 0)
{
printf("P2是子进程,PID为%d\n",getpid());
pid_t pid3;
pid3 = fork();
if(pid3<0)
{
printf("创建失败\n");
return 0;
}
else if(pid3 == 0)
{
printf("P4是子进程,PID为%d\n",getpid());
return 0;
}
sleep(1);
pid_t pid4;
pid4 = fork();
if(pid4<0)
{
printf("创建失败\n");
return 0;
}
else if(pid4 == 0)
{
printf("P5是子进程,PID为%d\n",getpid());
return 0;
}
}
}
return 0;
}
4、修改上述进程树中的进程,使得所有进程都循环输出自己的ID和父进程的ID。然后终止p2进程(分别采用kill -9 、自己正常退出exit()、段错误退出),观察p1、p3、p4、p5进程的运行状态和其他相关参数有何改变。
(1)代码:
参考链接:https://www.cnblogs.com/LUO77/p/5804436.html
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
pid_t pid1,pid2,pid3,pid4;
printf("P1为父进程,PID为%d\n",getpid());
pid1 = fork();
if(pid1<0)
{
printf("创建失败\n");
return 0;
}
else if(pid1 == 0)
{
while(1)
{
printf("P3是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
}
return 0;
}
else
{
pid2 = fork();
if(pid2<0)
{
printf("创建失败\n");
return 0;
}
else if(pid2 == 0)
{
pid3 = fork();
if(pid3<0)
{
printf("创建失败\n");
return 0;
}
else if(pid3 == 0)
{
while(1)
{
printf("P4是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
pid4 = fork();
if(pid4<0)
{
printf("创建失败\n");
return 0;
}
else if(pid4 == 0)
{
while(1)
{
printf("P5是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
while(1)
{
printf("P2是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
}
int *status;
waitpid(pid3,status, 0);
waitpid(pid4,status, 0);
return 0;
}
}
int *status;
waitpid(pid2,status, 0);
waitpid(pid1,status, 0);
return 0;
}
}
(2)然后终止p2进程(分别采用kill -9 、自己正常退出exit()、段错误退出),观察p1、p3、p4、p5进程的运行状态和其他相关参数有何改变。
①采用kill -9
ps -al
观察到p1的pid为2760,p2的pid为2762,p3的pid为2761,p4的pid为2763,p5的pid为2764。
输入命令kill -9 2762和ps –al
发现p4和p5的父进程变成了1474。
在操作系统领域中,孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
但是在本例中,孤儿进程被进程号1474收养。通过进程树可以看到,收养它们的是一个根进程,但不是进程号为1的进程。
②自己正常退出exit()
代码:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
pid_t pid1,pid2,pid3,pid4;
int i;
printf("P1为父进程,PID为%d\n",getpid());
pid1 = fork();
if(pid1<0)
{
printf("创建失败\n");
return 0;
}
else if(pid1 == 0)
{
while(1)
{
printf("P3是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
}
return 0;
}
else
{
pid2 = fork();
if(pid2<0)
{
printf("创建失败\n");
return 0;
}
else if(pid2 == 0)
{
pid3 = fork();
if(pid3<0)
{
printf("创建失败\n");
return 0;
}
else if(pid3 == 0)
{
while(1)
{
printf("P4是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
pid4 = fork();
if(pid4<0)
{
printf("创建失败\n");
return 0;
}
else if(pid4 == 0)
{
while(1)
{
printf("P5是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
i=1000;
while(i-->0)
{
printf("P2是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
while(i==500)
exit(0);
}
int *status;
waitpid(pid3,status, 0);
waitpid(pid4,status, 0);
return 0;
}
}
int *status;
waitpid(pid2,status, 0);
waitpid(pid1,status, 0);
return 0;
}
结果与上例相同
③段错误退出
代码:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
pid_t pid1,pid2,pid3,pid4;
int i;
printf("P1为父进程,PID为%d\n",getpid());
pid1 = fork();
if(pid1<0)
{
printf("创建失败\n");
return 0;
}
else if(pid1 == 0)
{
while(1)
{
printf("P3是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
}
return 0;
}
else
{
pid2 = fork();
if(pid2<0)
{
printf("创建失败\n");
return 0;
}
else if(pid2 == 0)
{
pid3 = fork();
if(pid3<0)
{
printf("创建失败\n");
return 0;
}
else if(pid3 == 0)
{
while(1)
{
printf("P4是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
pid4 = fork();
if(pid4<0)
{
printf("创建失败\n");
return 0;
}
else if(pid4 == 0)
{
while(1)
{
printf("P5是子进程,PID为%d,父进程为P2,PID为%d\n",getpid(),getppid());
}
return 0;
}
i=1000;
while(i-->0)
{
printf("P2是子进程,PID为%d,父进程为P1,PID为%d\n",getpid(),getppid());
while(i==500)
{
int* p;
*p = 1;
}
}
int *status;
waitpid(pid3,status, 0);
waitpid(pid4,status, 0);
return 0;
}
}
int *status;
waitpid(pid2,status, 0);
waitpid(pid1,status, 0);
return 0;
}
可以发现三种方法得到的结果都相同,孤儿进程都会被systemd(1474)收养。
GitHub源码:https://github.com/wwyw/lab2