Fy J
CS专业扫雷学深造学者互联网冲浪一级选手
FRIENDS
jhn

操作系统二:进程控制

03-17-2019 14:01:48 操作系统
Word count: 2.9k | Reading time: 11min

原创文章,转载、引用请注明出处!
代码、文章及图片挂载地址:https://github.com/MoyangSensei/OS

基础知识

  • 1 进程的创建:Linux中,载入内存并执行程序映像的操作与创建一个新进程的操作是分离的。将程序映像载入内存,并开始运行它,这个过程称为运行一个新的程序,相应的系统调用称为exec系统调用。而创建一个新的进程的系统调用是fork系统调用。

  • 2 exec系统调用:execl()将path所指路径的映像载入内存,arg是它的第一个参数。参数可变长。参数列表必须以NULL结尾。通常execl()不会返回。成功的调用会以跳到新的程序入口点作为结束。发生错误时,execl()返回-1,并设置errno值。

  • 3 fork系统调用:成功调用fork()会创建一个新的进程,它与调用fork()的进程大致相同。发生错误时,fork()返回-1,并设置errno值。

  • 4 终止进程:exit()、段地址错误、kill -9

  • 5 进程挂起:pause()。函数pause会把进程挂起,直到接收到信号。在信号接收后,进程会从pause函数中退出,继续运行。

  • 5 等待子进程中断或结束:wait()

  • 6 vim常用命令

vim常用命令

题目

  • 1 打开一个vi进程。通过ps命令以及选择合适的参数,只显示名字为vi的进程。寻找vi进程的父进程,直到init进程为止。记录过程中所有进程的ID和父进程ID。将得到的进程树和由pstree命令的得到的进程树进行比较。
  • 2 编写程序,首先使用fork系统调用,创建子进程。在父进程中继续执行空循环操作;在子进程中调用exec打开vi编辑器。然后在另外一个终端中,通过ps –Al命令、ps aux或者top等命令,查看vi进程及其父进程的运行状态,理解每个参数所表达的意义。选择合适的命令参数,对所有进程按照cpu占用率排序。
  • 3 使用fork系统调用,创建如下进程树,并使每个进程输出自己的ID和父进程的ID。观察进程的执行顺序和运行状态的变化。

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

解答

1

  • (1)创建vi进程:启动Linux终端,直接输入“vi”并回车就可进入vi界面。

  • (2)由于我们需要寻找到刚才所打开的进程的PID。值得注意的是,在本题目中我们所要寻找的进程非常的容易寻见,因为在我们运行Linux的时候,并没有进行其他的操作,即此时并没有特别多的进程在运行。此时直接使用如下命令:

1
# ps -A

然后直接在所有进程中人工匹配进程名即可。

但我们都知道,实际情况并不是理想的。我们需要一条命令,通过进程的名称帮助我们匹配和查找进程的具体信息。首先我们用:

1
# pgrep [str] 

进行模糊匹配,找到匹配该特征串的进程ID,再根据进程ID显示指定的进程信息:

1
# ps --pid [pid];

因为查找出来的进程ID需要被作为参数传递给ps命令,故使用xargs命令,通过管道符号连接。最后我们需要进程的详细信息,故需要加上-u参数。

综上,如果我们需要通过名称“vi”来匹配查找某些进程的具体信息,则可以使用以下的命令:

1
# pgrep vi | xargs ps -u --pid

于是,我们启动另一个终端,输入上述命令,就可以在已经进行过名称匹配的进程中查找我们需要的内容。

图1.1

如上图标注,我们要寻找的vi的进程号为42907。

  • (3)寻找vi进程的父进程,我们需要使用它的进程号并使用如下命令:
1
# ps -ef |grep pid

其中,ps命令将某个进程显示出来,它是LINUX下最常用的也是非常强大的进程查看命令;grep命令是的功能查找,它是一种强大的文本搜索工具,能使用正则表达式搜索文本,并把匹配的行打印出来,其全称是Global Regular Expression;而中间的“|”是管道命令,是指ps命令与grep同时执行。

通过多步查找,直到查找到init进程,其进程号为1

图1.2

图1.3

  • (4)我们可以使用如下命令来查看进程树,并打印每个进程的PID:
1
# pstree -p

图1.4

图1.5

根据图中标注,我们可以看到vi进程的进程树为:1 -> 953 -> 1209 -> 1360 -> 42888 -> 42894 -> 42907,与上面的结果完全一致。

2

  • (1)根据题意编写代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"

int main(){
pid_t p1,p2;
int cnt=0;
while((p1=fork()) == -1);
if(!p1){
while((p2=fork()) == -1);
//使用进程2打开vi
if(!p2){
int ret;
ret = execlp("vi","",NULL);
if (ret == -1)
printf("ERROR!\n");
}
//进程1设置为无限循环
while(1){}
}
return 0;
}
  • (2)使用ps -Al命令查看信息,结果如下:

图2.1

参数如下图所示:

图2.2

这些参数所表示的意义为:

参数名 含义
F flag,4表示用户为超级用户
S stat,状态
PID 进程ID
PPID 父进程ID
C CPU使用资源的百分比
PRI priority,优先级
NI Nice值
ADDR 核心功能,指出该进程在内存的哪一部分
SZ 用掉的内存的大小
WCHAN 当前进程是否正在运行,若为“-”表示正在进行
TTY 登陆者的终端位置
TIME 用掉的CPU的时间
CMD 所执行的指令
  • (3)使用ps -aux命令查看信息,结果如下:

图2.3

参数如下图所示:

图2.4

这些参数所表示的意义为:

参数名 含义
USER 进程的属主
PID 进程ID
PPID 父进程ID
%CPU 进程占用的CPU百分比
%MEM 占用内存的百分比
NI NICE值,数值大,表示较少占用CPU时间
VSZ 发进程使用的虚拟内存量(KB)
RSS 该进程占用的固定内存量(KB)(驻留中页的数量)
TTY 该进程在哪个终端上运行,若与终端无关,则显示“?”;若为pts/0等,则表示由网络连接主机进程
WCHAN 当前进程是否正在进行,若为“-”表示正在进行
START 该进程的启动时间
TIME 占用CPU的时间
COMMAND 命令的名称和参数

3

  • (1)由题图可编写如下程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"

int main(){
pid_t p1,p2,p3,p4,p5;
int cnt=0;
while((p1=fork()) == -1);
if(!p1){
while((p2=fork()) == -1);
if(!p2){
while ((p4=fork())==-1);
if (!p4){
printf("Node p4 is p2's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
}
else{
while ((p5=fork())==-1);
if (!p5){
printf("Node p5 is p2's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
}
}
printf("Node p2 is p1's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
}
else{
while((p3=fork()) == -1);
if(!p3){
printf("Node p3 is p1's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
}
}
printf("Node p1 is parent with pid %d, it's parent pid %d.\n",getpid(),getppid());
}
return 0;
}

  • (2)分别运行两次该程序,可发现它们的输出顺序是不同的。或者说是随机的,但这个随机是在满足如上所示的进程树的情况下的“随机”,且仅是一个节点的左右节点会出现顺序的随机,不会出现“越级”的随机。

图3.1

4

  • (1)根据第三题的代码修改,为每个进程的输出加上无限循环**while(1)**即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"
#include <sys/time.h>
#include <assert.h>

int main(){
pid_t p1,p2,p3,p4,p5;
int cnt=0;
while((p1=fork()) == -1);
if(!p1){
while((p2=fork()) == -1);
if(!p2){
while ((p4=fork())==-1);
if (!p4){
while(1){
printf("Node p4 is p2's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
sleep(2);}
}
else{
while ((p5=fork())==-1);
if (!p5){
while(1){
printf("Node p5 is p2's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
sleep(2);}
}
}
while(1){
printf("Node p2 is p1's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
sleep(2);

//exit退出进程
//exit(0);

//使用段地址错误退出进程
//int *ptr = (int *)0;
//*ptr = 100;

}
}
else{
while((p3=fork()) == -1);
if(!p3){
while(1){
printf("Node p3 is p1's child with pid %d, it's parent pid %d.\n",getpid(),getppid());
sleep(2);}
}
}
while(1){
printf("Node p1 is parent with pid %d, it's parent pid %d.\n",getpid(),getppid());
sleep(2);}
}
return 0;
}

同第三题一样,它们内部的输出顺序是由略微不同的,但整体的输出是5个节点全部输出完成之后再进行下一回合的输出。sleep函数用于减慢输出速度便于观察。

图4.1

  • (2)使用kill -9终止进程2。首先,kill是Linux里非常常用的一个命令,其作用如同字面意思一样,用来“杀死”进程。而kill -n中的数字n表示信号编号(signnum)。默认参数下,kill发送SIGTERM(15)信号给进程,告诉进程需要被关闭,请自行停止运行并退出。而kill -9 发送SIGKILL信号给进程,告诉进程,你被终结了,请立刻退出。TERM(或数字9)表示“无条件终止”;因此 kill - 9 表示强制杀死该进程;与SIGTERM相比,这个信号不能被捕获或忽略,同时接收这个信号的进程在收到这个信号时不能执行任何清理。

图4.2

  • (3)使用exit()终止进程2。exit()函数为退出函数,将其放入p2的循环中即可。

图4.3

  • (4)使用段地址错误终止进程2。段错误就是指访问的内存超出了系统所给这个程序的内存空间,如(1)中代码所示,我们为进程2的无限循环加上一个“野指针”即可达到目的。

图4.4

  • (5)可以发现,通过各种方式“杀死”进程2之后,进程2不再存在,其子程序进程4和5却仍然存在,并能完成输出信息的任务,但其PPID变为1号进程的PPID。这是因为,调用fork产生一个子进程,同时父进程退出。我们所有后续工作都在子进程中完成。这样做我们可以交出控制台的控制权,并为子进程作为进程组长作准备;由于父进程已经先于子进程退出,会造成子进程没有父进程,变成一个孤儿进程(orphan)。每当系统发现一个孤儿进程,就会自动由1号进程收养它,这样,原先的子进程就会变成1号进程的子进程,即1360号进程。
< PreviousPost
操作系统三:同步问题
NextPost >
操作系统一:初步
CATALOG
  1. 1. 基础知识
  2. 2. 题目
  3. 3. 解答
    1. 3.1. 1
    2. 3.2. 2
    3. 3.3. 3
    4. 3.4. 4