实验目的
- 加深对进程概念的理解,明确进程和程序的区别。
- 掌握Linux系统中的进程创建,管理和删除等操作。
- 熟悉使用Linux下的命令和工具,如
man, find, grep, whereis, ps, pgrep, kill, ptree, top, vim, gcc,gdb
, 管道等。
实验内容
task1
要求: 打开一个vi进程。通过ps
命令以及选择合适的参数,只显示名字为vi的进程。寻找vi进程的父进程,直到init进程为止。记录过程中所有进程的ID和父进程ID。将得到的进程树和由pstree``命令的得到的进程树进行比较。
1.首先下载安装vim,通过sudo apt-get install vim-get
实现
2.查询进程可使用
pgrep
命令来搜索进程名,通过xargs
获取到命令的输出并传递给另外的命令。所以通过pgrep vi | xqrrg ps –l
可看到vi进程以及他的相关进程信息。之后通过ps
命令逐个查找父进程,直到找到根进程。由图中信息可知:vi进程的进程号为:19905 其父进程进程号为:19894,以此类推可查到根进程。
进程关系表:
父进程 | 进程号 | 进程名 |
---|---|---|
19894 | 19905 | vi |
19889 | 19894 | bash |
1523 | 19889 | gnome |
1505 | 1523 | upstart |
913 | 1505 | lightdm |
1 | 913 | ligh |
0 | 1 | init spl |
其中:
F:flag,表示程序的旗标,4表示使用者为超级用户,1表示使用者为用户
S:status,表示这个程序的状态
UID:表示执行者身份
PID:表示进程ID
PPID:表示父进程的ID
C:表示使用的CPU资源百分比
PRI:priority,表示进程的执行优先权,越小表示越早被执行
NI:nice,表示进程可以被执行的优先级的修正数值
ADDR:表示内核函数
SZ:表示占用内存的大小
WCHAN:表示程序是否正在运作当中。“-”表示正在运行
TTY:表示终端机的位置CMD:表示所下达指令的名称
3.通过pstree
命令得到进程树。
<img src="https://img-blog.csdnimg.cn/20190316151202650.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzc1Mjk1Mw==,size_16,color_FFFFFF,t_70" width="60%">
在进程树中可以在systemd – lightdm – lightdm – upstart – gnome-terminal – bash – vi 找到vi所在的位置。其中,systemd是一组系统管理命令,取代了init命令成为系统的第一个进程,gnome-terminal为终端的管理进程。
问题总结:
1. ps、pstree及top命令辨析
ps
:平时比较常用的查看进程的命令,ps 是显示瞬间进程的状态,并不动态变化;如果想对进程运行时间监控,需要用 top 工具。
pstree
:显示进程状态树,pstree命令可以列出当前的进程,以及它们的树状结构。
使用ps命令得到的数据精确,但数据庞大,不易掌握系统整体状况。pstree命令清晰明了。它能将当前的执行程序以树状结构显示。
top
:用来显示系统当前的进程状况。是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。
task2
要求: 编写程序,首先使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。然后在另外一个终端中,通过ps –Al
命令、ps aux
或者top
等命令,查看vi进程及其父进程的运行状态,理解每个参数所表达的意义。选择合适的命令参数,对所有进程按照cpu占用率排序。
- 编写test.c程序,实现使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。
[图片上传失败...(image-696048-1553237084704)] -
编译运行test.c程序
- 在另一个终端使用命令
ps -AL
查看现有进程信息。
在进程列表中可以找到vi进程以及该进程的状态信息
- 运用命令
ps aux
查看内存中运行的程序:
其中:
USER:进程使用者账号;
PID:进程ID号;
CPU:进程使用掉的CPU资源百分比;
MEM:进程所占用的物理内存百分比;
VSZ:进程使用掉的虚拟内存量 (Kbytes)
RSS :进程占用的固定的内存量 (Kbytes)
TTY :进程所在终端机(若与终端机无关,则显示 “?”,tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等,则表示为由网络连接进主机的程序。
STAT:程序目前的状态:R (正在运作) S (正在睡眠) T(正在侦测或停止) Z (zombie(疆尸) 程序)
START:进程被触发启动的时间;
TIME :进程实际使用 CPU 运作的时间。
COMMAND:进程所属的指令
- 接着使用
top
命令将所有进程按照cpu占有率进行排序,根据排序结果可见,我创建的test程序因含有死循环fork所以几乎完全占用了CPU。
task3
要求: 使用fork系统调用,创建如下进程树,并使每个进程输出自己的ID和父进程的ID。观察进程的执行顺序和运行状态的变化。
-
根据题目要求编写程序
-
多次运行得到如下输出。可以看出P1为P2和P3的父进程,P2位P4和P5的父进程,与实验要求的进程树相同。
-
程序流程简图
问题总结:
1. linux中的fork()函数
一个进程包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建与原来进程几乎完全相同的进程,相当于克隆了一个自己。但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。
2. fork()函数特点
fork函数被调用一次,能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
3. 父进程先结束的问题
在实验过程中会遇到执行程序时子进程与的父进程号不符合“父子关系”。
后发现是因为所有的子进程在getppid()的时候父进程已经结束了,得到的是upstart进程的pid=1523。所以在代码中加入
sleep(1);
,让父进程等待一秒钟再结束,由此可以正确得到进程间的“父子关系”。
task4
要求: 修改上述进程树中的进程,使得所有进程都循环输出自己的ID和父进程的ID。然后终止p2进程(分别采用kill -9 、自己正常退出exit()、段错误退出),观察p1、p3、p4、p5进程的运行状态和其他相关参数有何改变。
-
由于需要循环输出自己的ID和父进程的ID,所以程序逻辑发生变化,重新编写程序并运行如下。
- 运行该程序,可见循环输出符合进程树的进程关系。
通过pstree -p
查看程序进程树,可见符合原题给出的进程树。
- 终止p2进程
- 采用
kill -9
,删除进程p2(2077),再次查看进程树,可见原进程树上仅有原来的p1,p2,p3。
原来为p2子进程的p4和p5现在成为upstart的子进程。
通过命令ps -aux | grep 2077
查看p2进程的信息,可见进程p2的状态已变成Z(僵死 a defunct (”zombie”) process),成为僵尸进程。
- 正常退出exit()
改写代码,使得p2通过exit(0);
函数退出。
运行可见到循环的第二轮p2进程便不在,原本是p2子进程的p3和p4成为upstart的子进程。
查看进程树,可见原进程树有p1,p2,p3进程。
同样对p2的进程信息查询,得到结果显示p2进程状态为Z,成为僵尸进程。
-
段错误退出
采用访问不存在的内存地址的方式产生段错误退出。
编译运行该程序并查看进程树,可得到与上文相类似的结果
原进程树仅有p1、p2、p3进程,原为p2子进程的p4,p5成为upstart的子进程。
查询p2进程信息,可见p2进程状态变为Z,成为僵尸进程。
综合以上可知,这三种方式终止p2进程都会使得p2进程成为僵尸进程。
问题总结:
1 . 辨析STAT表示的行程的状态
linux的进程有5种状态:
D:不可中断 uninterruptible sleep (usually IO)
R:运行 runnable (on run queue)
S:中断 sleeping
T:停止 traced or stopped
Z:僵死 a defunct (”zombie”) process
2. 段错误的产生原因
段错误是指访问的内存超出了系统给这个程序所设定的内存空间。
- 访问不存在的内存地址
void main() {
int *ptr = NULL;
*ptr = 0;
}
- 访问系统保护的内存地址
void main() {
int *ptr = (int *)0;
*ptr = 100;
}
- 访问只读的内存地址
void main() {
char *ptr = "test";
strcpy(ptr, "TEST");
}
- 栈溢出
void main() {
main();
}
实验总结
本次实验,我对于fork()函数有了更加深入的认识,也更加熟练地使用linux系统。通过对于进程信息的创建,查询,终止等操作,对于“进程”的概念也有了更加感性的认识,更好的理解了课上所讲的抽象化的概念。遇到不懂的问题和概念,通过上网查资料,咨询老师同学,学会了更多的专业知识。希望之后能够继续加油,做到真正的理解实验原理。