原创文章,转载、引用请注明出处!
代码、文章及图片挂载地址: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常用命令
题目
- 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 |
于是,我们启动另一个终端,输入上述命令,就可以在已经进行过名称匹配的进程中查找我们需要的内容。
如上图标注,我们要寻找的vi的进程号为42907。
- (3)寻找vi进程的父进程,我们需要使用它的进程号并使用如下命令:
1 | # ps -ef |grep pid |
其中,ps命令将某个进程显示出来,它是LINUX下最常用的也是非常强大的进程查看命令;grep命令是的功能查找,它是一种强大的文本搜索工具,能使用正则表达式搜索文本,并把匹配的行打印出来,其全称是Global Regular Expression;而中间的“|”是管道命令,是指ps命令与grep同时执行。
通过多步查找,直到查找到init进程,其进程号为1。
- (4)我们可以使用如下命令来查看进程树,并打印每个进程的PID:
1 | # pstree -p |
根据图中标注,我们可以看到vi进程的进程树为:1 -> 953 -> 1209 -> 1360 -> 42888 -> 42894 -> 42907,与上面的结果完全一致。
2
- (1)根据题意编写代码如下:
1 |
|
- (2)使用ps -Al命令查看信息,结果如下:
参数如下图所示:
这些参数所表示的意义为:
参数名 | 含义 |
---|---|
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命令查看信息,结果如下:
参数如下图所示:
这些参数所表示的意义为:
参数名 | 含义 |
---|---|
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)分别运行两次该程序,可发现它们的输出顺序是不同的。或者说是随机的,但这个随机是在满足如上所示的进程树的情况下的“随机”,且仅是一个节点的左右节点会出现顺序的随机,不会出现“越级”的随机。
4
- (1)根据第三题的代码修改,为每个进程的输出加上无限循环**while(1)**即可:
1 |
|
同第三题一样,它们内部的输出顺序是由略微不同的,但整体的输出是5个节点全部输出完成之后再进行下一回合的输出。sleep函数用于减慢输出速度便于观察。
- (2)使用kill -9终止进程2。首先,kill是Linux里非常常用的一个命令,其作用如同字面意思一样,用来“杀死”进程。而kill -n中的数字n表示信号编号(signnum)。默认参数下,kill发送SIGTERM(15)信号给进程,告诉进程需要被关闭,请自行停止运行并退出。而kill -9 发送SIGKILL信号给进程,告诉进程,你被终结了,请立刻退出。TERM(或数字9)表示“无条件终止”;因此 kill - 9 表示强制杀死该进程;与SIGTERM相比,这个信号不能被捕获或忽略,同时接收这个信号的进程在收到这个信号时不能执行任何清理。
- (3)使用exit()终止进程2。exit()函数为退出函数,将其放入p2的循环中即可。
- (4)使用段地址错误终止进程2。段错误就是指访问的内存超出了系统所给这个程序的内存空间,如(1)中代码所示,我们为进程2的无限循环加上一个“野指针”即可达到目的。
- (5)可以发现,通过各种方式“杀死”进程2之后,进程2不再存在,其子程序进程4和5却仍然存在,并能完成输出信息的任务,但其PPID变为1号进程的PPID。这是因为,调用fork产生一个子进程,同时父进程退出。我们所有后续工作都在子进程中完成。这样做我们可以交出控制台的控制权,并为子进程作为进程组长作准备;由于父进程已经先于子进程退出,会造成子进程没有父进程,变成一个孤儿进程(orphan)。每当系统发现一个孤儿进程,就会自动由1号进程收养它,这样,原先的子进程就会变成1号进程的子进程,即1360号进程。