目录
[2.进程与操作系统的管理 -- 先描述,再组织](#2.进程与操作系统的管理 -- 先描述,再组织)
[ps -e 显示所有进程](#ps -e 显示所有进程)
前言
本文主要用于帮助读者理解进程的概念、进程和PCB数据结构的关系、进程的一些基本操作---包括查看进程、结束进程、通过系统调用获取进程标示符、通过系统调用创建子进程、以树状结构显示进程之间的父子关系。
一、关于进程
1.进程概念
程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程就是被加载到内存中的程序,或者被运行起来的程序就叫做进程。
为什么这么说呢?
我们从前文 --- [Linux]从硬件到软件理解操作系统 ---可知,为了提高计算机的整体效率,CPU只会向内存中读取和写入数据,所以如果我们要运行这个程序,就必须先将其加载到内存。最后,CPU才能从内存中读取程序中的代码和数据进行运算。
2.进程与操作系统的管理 -- 先描述,再组织
根据 ---[Linux]从硬件到软件理解操作系统 ---我们能知道操作系统的作用就是管理:
操作系统是一个进行软硬件资源管理的软件。
它通过对下管理好各种软硬件资源,来对上为用户提供良好的 (安全、稳定、高效) 运行环境。
前者是管理手段 ,后者是管理目的。
总之,操作系统就是要进行管理!!!
在程序加载进内存后,操作系统要对程序进行管理,操作系统如何管理呢?
对进程的管理本质和对人的管理一样,都是对"信息"进行管理,"管理者"对信息管理来达到目的。
所以,管理进程只要管理进程数据即可。涉及管理就会涉及数据,涉及数据就会涉及"描述"(创建结构体),对进程"描述"完成后,就能"组织"进程完成管理(对结构体增删查改)。总之,这个过程就是**"先描述,再组织"**。
3.对进程的描述---PCB
我们对进程描述的结果就是PCB结构体。
进程控制块PCB (process control block):操作系统中用于描述进程的工具,其中包含的是进程属性的集合;Linux操作系统下的PCB是 task_struct,它是Linux内核的一种数据结构,其内容可以分为如下几类:
标示符: 描述本进程的唯一标示符,用来区别其他进程;
状态: 任务状态,退出代码,退出信号等;
优先级: 相对于其他进程的优先级;
程序计数器: 程序中即将被执行的下一条指令的地址;
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针;
上下文数据: 进程执行时处理器的寄存器中的数据;
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表;
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等;
......
更详细的内容查看:Linux中进程控制块PCB-------task_struct结构体结构
我们简单抽象出来就是下面这样(假设task_struct使用链表进行组织):
cpp
struct task_struct {
//进程的所有属性
... ...
//进程对应的代码和数据的地址
... ...
//下一个进程的地址
struct task_struct* next;
};
总之,进程 = 内核PCB数据结构对象 + 数据和代码
二、进程的基本操作
1.查看进程
我们可以通过以下两种方式来查看进程:
(1) ps axj 指令配合 grep 和 管道查看指定进程:
cpp
ps ajx | head -1;ps ajx | grep test | grep -v grep
运行进程的代码:
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 while(1)
6 {
7 printf("i am a process\n");
8 sleep(1);
9 }
10 return 0;
11 }
运行结果:

(2) 在 "/proc" 系统文件夹中查看所有进程:

2.结束进程
对于我们自己编写的普通进程来说,我们可以使用Ctrl + c结束;也可以使用kill -9 [选项]。

图例展示:


通过视频示范两种方式:
结束进程示范
3.用系统调用获取进程标示符
我们可以通过使用操作系统给我们提供的系统调用接口**getpid( ) 与 getppid( )**来获取进程id和父进程id (进程ID是一个进程的唯一标识):

注:关于函数的返回值 pid_t,大家把它当作 int 看待即可,打印的时候也使用 %d;
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include <sys/types.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("i am a process,my id is %d,my father's id is %d \n", getpid(), getppid());
10 sleep(1);
11 }
12 return 0;
13 }
ps -e 显示所有进程
cpp
ps -e

以树状结构显示进程之间的父子关系:
cpp
ps -e --forest

可以看到,我们通过 getpid() 和 getppid() 函数得到的值的确是我们进程对应的id;同时,我们发现 test 进程的父进程是 bash,即 shell 外壳,我们可知 shell 为了防止自身崩溃,并不会自己去执行指令,而是会派生子进程去执行。
4.通过系统调用创建子进程
fork函数
我们可以通过系统调用接口 fork 来创建子进程:
(这一部分我们在后面 进程控制 还会详细讲解)

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
cpp
1 #include<stdio.h>
2 #include<unistd.h>
3 #include <sys/types.h>
4 int main()
5 {
6 pid_t id = fork();
7 if (id == 0)
8 {
9 while(1)
10 {
11 printf("child, pid->%d, ppid->%d\n", getpid(), getppid());
12 sleep(1);
13 }
14 }
15 else if(id > 0)
16 {
17 while(1)
18 {
19 printf("father, pid->%d, ppid->%d\n", getpid(), getppid());
20 sleep(1);
21 }
22 }
23 else{
24 printf("创建失败\n");
25 }
26 return 0;
32 }

可以看到,结果和我们预期一样,子进程的 ppid 是 父进程的 pid,父进程的 ppid 是 bash,同时,对于父进程,fork 函数返回子进程的 pid,对于子进程,fork 返回 0。