进程管理

实验目的

  1. 加深对进程概念的理解,明确进程和程序的区别。
  2. 掌握Linux系统中的进程创建,管理和删除等操作。
  3. 熟悉使用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实现

image

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
image

其中:

F:flag,表示程序的旗标,4表示使用者为超级用户,1表示使用者为用户

S:status,表示这个程序的状态

UID:表示执行者身份

PID:表示进程ID

PPID:表示父进程的ID

C:表示使用的CPU资源百分比

PRI:priority,表示进程的执行优先权,越小表示越早被执行

NI:nice,表示进程可以被执行的优先级的修正数值

ADDR:表示内核函数

SZ:表示占用内存的大小

WCHAN:表示程序是否正在运作当中。“-”表示正在运行
TTY:表示终端机的位置

CMD:表示所下达指令的名称

3.通过pstree命令得到进程树。

在进程树中可以在systemd – lightdm – lightdm – upstart – gnome-terminal – bash – vi 找到vi所在的位置。其中,systemd是一组系统管理命令,取代了init命令成为系统的第一个进程,gnome-terminal为终端的管理进程。

image

问题总结:

1. ps、pstree及top命令辨析
ps:平时比较常用的查看进程的命令,ps 是显示瞬间进程的状态,并不动态变化;如果想对进程运行时间监控,需要用 top 工具。
pstree:显示进程状态树,pstree命令可以列出当前的进程,以及它们的树状结构。
使用ps命令得到的数据精确,但数据庞大,不易掌握系统整体状况。pstree命令清晰明了。它能将当前的执行程序以树状结构显示。
top:用来显示系统当前的进程状况。是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。

task2

要求: 编写程序,首先使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。然后在另外一个终端中,通过ps –Al命令、ps aux或者top等命令,查看vi进程及其父进程的运行状态,理解每个参数所表达的意义。选择合适的命令参数,对所有进程按照cpu占用率排序。

  1. 编写test.c程序,实现使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。
    [图片上传失败...(image-696048-1553237084704)]
  2. 编译运行test.c程序


    image
  3. 在另一个终端使用命令ps -AL查看现有进程信息。
    image

    在进程列表中可以找到vi进程以及该进程的状态信息
    image
  4. 运用命令ps aux查看内存中运行的程序:
    image

    其中:

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:进程所属的指令

  1. 接着使用top命令将所有进程按照cpu占有率进行排序,根据排序结果可见,我创建的test程序因含有死循环fork所以几乎完全占用了CPU。
    image

task3

要求: 使用fork系统调用,创建如下进程树,并使每个进程输出自己的ID和父进程的ID。观察进程的执行顺序和运行状态的变化。

image

  1. 根据题目要求编写程序


    image
  2. 多次运行得到如下输出。可以看出P1为P2和P3的父进程,P2位P4和P5的父进程,与实验要求的进程树相同。


    image
  3. 程序流程简图


    image

问题总结:

1. linux中的fork()函数
一个进程包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建与原来进程几乎完全相同的进程,相当于克隆了一个自己。但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。
2. fork()函数特点
fork函数被调用一次,能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
3. 父进程先结束的问题
在实验过程中会遇到执行程序时子进程与的父进程号不符合“父子关系”。

image

后发现是因为所有的子进程在getppid()的时候父进程已经结束了,得到的是upstart进程的pid=1523。所以在代码中加入sleep(1);,让父进程等待一秒钟再结束,由此可以正确得到进程间的“父子关系”。

task4

要求: 修改上述进程树中的进程,使得所有进程都循环输出自己的ID和父进程的ID。然后终止p2进程(分别采用kill -9 、自己正常退出exit()、段错误退出),观察p1、p3、p4、p5进程的运行状态和其他相关参数有何改变。

  1. 由于需要循环输出自己的ID和父进程的ID,所以程序逻辑发生变化,重新编写程序并运行如下。


    image
  2. 运行该程序,可见循环输出符合进程树的进程关系。
    image

    通过pstree -p查看程序进程树,可见符合原题给出的进程树。
    image
  3. 终止p2进程
  • 采用kill -9 ,删除进程p2(2077),再次查看进程树,可见原进程树上仅有原来的p1,p2,p3。
    image

    原来为p2子进程的p4和p5现在成为upstart的子进程。
    image

    通过命令ps -aux | grep 2077 查看p2进程的信息,可见进程p2的状态已变成Z(僵死 a defunct (”zombie”) process),成为僵尸进程。
    在这里插入图片描述
  • 正常退出exit()
    改写代码,使得p2通过exit(0);函数退出。
    image

    运行可见到循环的第二轮p2进程便不在,原本是p2子进程的p3和p4成为upstart的子进程。
    image

    查看进程树,可见原进程树有p1,p2,p3进程。
    image

    同样对p2的进程信息查询,得到结果显示p2进程状态为Z,成为僵尸进程。
    image
  • 段错误退出
    采用访问不存在的内存地址的方式产生段错误退出。


    image

    编译运行该程序并查看进程树,可得到与上文相类似的结果


    image

    原进程树仅有p1、p2、p3进程,原为p2子进程的p4,p5成为upstart的子进程。
    image

    image

    查询p2进程信息,可见p2进程状态变为Z,成为僵尸进程。


    image

    综合以上可知,这三种方式终止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. 段错误的产生原因
段错误是指访问的内存超出了系统给这个程序所设定的内存空间。

  1. 访问不存在的内存地址

void main() {
int *ptr = NULL;
*ptr = 0;
}

  1. 访问系统保护的内存地址

void main() {
int *ptr = (int *)0;
*ptr = 100;
}

  1. 访问只读的内存地址

void main() {
char *ptr = "test";
strcpy(ptr, "TEST");
}

  1. 栈溢出

void main() {
main();
}

实验总结

本次实验,我对于fork()函数有了更加深入的认识,也更加熟练地使用linux系统。通过对于进程信息的创建,查询,终止等操作,对于“进程”的概念也有了更加感性的认识,更好的理解了课上所讲的抽象化的概念。遇到不懂的问题和概念,通过上网查资料,咨询老师同学,学会了更多的专业知识。希望之后能够继续加油,做到真正的理解实验原理。

github代码地址

你可能感兴趣的:(进程管理)