文章目录
- 🌈 01. 基本概念
- 🌈 02. 描述进程 PCB
- 🌈 03. 使用 ./ 的方式创建进程
- 🌈 04. ps 查看进程
- 🌈 05. getpid / getppid 获取进程标识符
- 🌈 06. kill 终止指定进程
- 🌈 07. fork 创建子进程
- 🌈 08. 进程状态
- 1. 进程基本状态
- 2. 查看进程状态
- 🌈 09. 僵尸进程
- 🌈 10. 孤儿进程
🌈 01. 基本概念
1. 课本概念
- 程序的一个执行实例,表示正在执行的程序。
- 一个任务就是一个进程,一个程序可以启动多个进程,程序只有一个,而一个程序可以有多个进程。
2. 内核概念
- 分配系统资源 (CPU时间、内存) 的实体称为进程。
🌈 02. 描述进程 PCB
- 将外存中的可执行程序的代码和数据加载到内存当中,但光凭对应的代码和数据不足以描述进程信息。
- 进程信息被放在一个叫做==进程控制块 PCB (process control block) ==的结构体中。
- 在操作系统学科中,每个进程都要有一个 struct PCB 的结构体来管理这些进程的信息,Linux 操作系统下的 PCB 就是 task_struct。
- 操作系统为了管理所有的进程,规定了一个进程,一定要有一个 PCB。
进程的定义
- 进程 = PCB + 自己的代码和数据。
- 对进程的管理就是对 PCB 构成的链表的增删查改操作。
🌈 03. 使用 ./ 的方式创建进程
- ./某个可执行程序:本质就是让系统创建进程并运行。我们自己编写形成的可执行程序 = 系统命令 = 可执行文件。在 Linux 中运行的大部分执行操作,本质都是运行进程。
🌈 04. ps 查看进程
指令:ps axj
功能:查看当前系统中所有正在运行的进程。
示例
- 直接使用 ps axj 查看的是所有的进程信息
- 如果需要查看指定进程则需要搭配 管道 | 和 grep 获取关键字信息这两个指令。
🌈 05. getpid / getppid 获取进程标识符
1. 进程标识符
- pid:进程的 id,也成为进程的标识符,是每个进程所拥有的唯一标识符。
- ppid:本进程的父进程的 id。
2. 获取进程 pid
- getpid():获取进程自身的 pid。
- getppid():获取进程的父进程的 pid。
3. 获取 pid / ppid 示例
- 使用如下代码获取进程的 pid 并保证进程不会结束。
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
using std::cout;
using std::endl;
int main()
{
while(1)
{
// 获取当前进程的 pid 与 ppid
cout << "I am a process pid: " << getpid()
<< "ppid: " << getppid() << endl;
sleep(1);
}
return 0;
}
- 发现获取到的进程 pid 和 ppid 与使用 ps 指令查询到的进程 pid 一致。
🌈 06. kill 终止指定进程
常见指令
指令 | 功能 |
---|---|
kill -9 pid | 终止指定 pid 所标识的进程 |
kill -19 pid | 暂定指定 pid 所标识的进程 |
kill -18 pid | 解除暂停 pid 所标识的进程 |
终止进程示例
- 使用 getpid 获取到的 myprocess 进程的 pid 的来终止该进程。
🌈 07. fork 创建子进程
1. fork 的返回值说明
fork() 的返回值 | 说明 |
---|---|
返回值 < 0 | 子进程创建失败 |
返回值 == 0 | 子进程创建成功,返回子进程 pid 给父进程,返回 0 给子进程 |
子进程 pid | 表示父进程,父进程通过接收子进程 pid 管理子进程 |
2. 创建进程的本质
- 创建一个进程,本质是系统中多出一个子进程,即多了一个内核 task_struct2,子进程与父进程都各自拥有自己的代码和数据。
- 默认情况下,子进程会继承父进程的代码和数据,包括 PCB 也会以父进程的 PCB 为模板来初始化。
- 父子进程之间代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
- 因为父子进程之间的代码是共享的,在执行子进程自己的代码时会顺带着再执行一遍父进程的代码,这种情况下,创建出子进程就没有任何意义了。
- 因此在 fork 之后,如果想要单独执行子进程的数据,就要使用 if 对父子进程的代码和数据进行分流。
3. 创建子进程的原因
- 希望子进程和父进程执行不一样的代码部分,让多个进程并发跑起来。
4. 多进程中使用 if 让父子进程执行不同的代码
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
using std::cout;
using std::endl;
int main()
{
pid_t id = fork();
if (0 == id) // 执行子进程部分代码
cout << "I am child process" << endl;
else if(id < 0) // 子进程的创建失败了
cout << "process creation failure" << endl;
else // 执行父进程部分代码
cout << "I am parent process" << endl;
return 0;
}
- 父子进程各执行了自己的那部分代码。
- 关于 fork 函数为什么会有两个返回值的问题看虚拟地址空间 + 父子写时拷贝。
🌈 08. 进程状态
1. 进程基本状态
- 任何一个进程在运行的时候都要有对应的状态,一个进程可以拥有几个状态。
- 进程状态是 task_struct 内部的一个属性,更改进程状态就是更改其状态属性。
状态标识符 | 标识符说明 | 状态说明 |
---|---|---|
R | 运行状态 running | 进程处于运行中或运行队列里 |
S | 睡眠状态 sleeping | 浅度睡眠,进程在等待 “资源” 就绪,睡眠状态可被中断 |
D | 磁盘休眠状态 disk sleep | 深度睡眠,不可中断睡眠,该状态的进程不会被操作系统干掉 |
T | 停止状态 stopped | kill -19 pid 暂停进程;kill -18 pid 解除暂停 |
Z | 僵尸状态 zombies | 进程退出时,会暂时处于僵尸状态,要将退出信息保留在自己的 PCB 中,没人读取这些退出信息回收该进程时,该进程就不会释放,此时就一直处于僵尸状态 |
2. 查看进程状态
1. R 运行状态
- 编写一段死循环的代码,能够让 CPU 一直处在运行状态。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1);
return 0;
}
- R+ 表示该进程在前台运行
2. S 睡眠状态
- 编写如下一段死循环打印的代码。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1)
cout << "sleeping" << endl;
return 0;
}
- 因为 CPU 绝大部分时间都 在等待显示器打印信息,因此绝大部分时间处于睡眠状态。
3. T 暂停状态
- 暂停指定进程:kill -19 对应进程 pid
- 解除进程暂停:kill -18 对应进程 pid,恢复的进程会跑到后台状态自动变成 R。
🌈 09. 僵尸进程
僵尸进程概念
- 已经运行完毕,但是需要维持自己的退出信息,自己的进程 task_struct 会记录自己的退出信息,未来让父进程读取。
- 子进程退出时不会立即退出,此时处于僵尸状态,父进程没有读取到退出信息,无法对该进程回收时就会一直处于僵尸状态。无法使用 kill 指令干掉僵尸进程。
- 僵尸进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
- 如果一直处于僵尸状态,即僵尸进程一直存在,则会造成内存泄漏。
僵尸进程示例
- 使用如下代码让父进程多运行一会,保证子进程退出时,父进程还在运行。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
using std::cout;
using std::endl;
int main()
{
pid_t id = fork();
if (0 == id) // 子进程
{
cout << "I am child pid: " << getpid() << endl;
sleep(5);
}
else // 父进程
{
cout << "I am father ppid: " << getppid() << endl;
sleep(100);
}
return 0;
}
🌈 10. 孤儿进程
孤儿进程概念
- 孤儿进程听名字就知道是个什么东西,父进程如果先行退出,子进程就会变成孤儿进程。
- 为了避免未来孤儿进程在退出后无法被回收,因此孤儿进程会被 1 号进程 (操作系统) 领养。
孤儿进程示例
- 对僵尸进程的示例代码进行改动,让父进程先行结束掉,即可看到孤儿进程的情况。
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
using std::cout;
using std::endl;
int main()
{
pid_t id = fork();
if (0 == id) // 子进程
{
cout << "I am child pid: " << getpid() << endl;
sleep(100);
}
else // 父进程
{
cout << "I am father ppid: " << getppid() << endl;
sleep(5);
exit(0);
}
return 0;
}
- 通过图 1 可以看到子进程 28689 的父进程 ppid 是 26543,图 2 在执行完父进程后,子进程 28689 的父进程就变成了 1 号进程。