目录
[top 指令](#top 指令)
[ps 指令](#ps 指令)
进程的基本操作
在深度理解进程前,我们需要对进程的操作进行初步的了解,方便我们了解进程的相关属性,随着进程的学习,我们接触到的有关进程的操作越来越多,让我们先慢慢开始吧。
1.查看进程
对于进程的学习,我们肯定需要知道怎么查看进程的相关属性,才能帮助我们理解进程,在展示操作的时候,我会先介绍一些进程的相关属性,没听过没关系,慢慢学。
方法一:
进程的信息通过 /proc 系统文件查看

如:要获取 PID(进程标示符) 为1的进程信息,只需要 ls /proc/1

方法二:
大多数进程信息可以采用 top 和 ps 指令来获取
top 指令

ps 指令
a:显示一个终端所有的进程
x:显示没有控制终端的进程,例如后台运行的守护进程
j:显示进程标示符相关的信息
u: 以用户为中心的格式显示进程信息
ps axj 显示所有用户的进程对应的标示符相关的信息
ps aux 或者 ps -ef 显示所有用户的进程
ps -u root 查看 root 用户的进程
ps ux 查看当前用户的进程
最常用的查看一个进程的方法
ps aux | grep 程序名 | grep -v grep
2.通过系统调用获取进程标示符(PID)

getpid 获取当前进程的 PID
getppid 获取当前进程的 PPID
cpp
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 printf("我的进程PID:%d\n", getpid());
8 printf("我的父进程PID:%d\n",getppid());
9 return 0;
10 }
3.通过系统调用创建子进程


fork 创建子进程
如果创建子进程成功,给父进程返回子进程的 PID,给子进程返回 0,如果创建失败,给父进程返回-1,子进程不存在
cpp
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id = fork();
8 if(id < 0)
9 {
10 perror("fork faile");
11 return 1;
12 }
13 else if(id == 0)
14 {
15 printf("我是子进程,我的PID:%d, 我的父进程PID:%d\n", getpid(), getppid());
16 }
17 else
18 {
19 printf("我是父进程,我的PID:%d\n", getpid());
20 }
21 return 0;
22 }
思考:
-
fork 为什么有两个返回值?
-
父进程和子进程得到的fork返回值为什么不同?
-
为什么一个变量可以使 if 和 else if 同时成立?
首先需要知道一点,进程具有独立性,进程之间互不影响。
fork 创建一个子进程之后,父进程和子进程的task_struct存在两个指针,一个是指向代码的指针,一个是指向数据的指针,它们共享代码段,且"共享数据段",因为进程具有独立性,当一个进程想要修改数据段中的数据,为了不影响其他进程,就会发生写时拷贝,将为修改的数据再开辟一个物理空间来存储它。这样进程间就独立了。
那么,如何理解上述三个问题呢?
父进程调用fork函数,在fork函数内部就创建了子进程,所以fork函数进行返回时父进程和子进程就已经共享代码和数据了,那么fork 进行返回时,由于对父子进程返回值不同,就发生了写时拷贝,在父进程中返回值为子进程PID,在子进程中返回值为0。
进程状态

就绪状态:进程在运行队列中,并没有占用CPU的状态

如图所示,后面4个进程在运行队列中,但没有占用CPU,此时这些进程为就绪状态,在Linux下没有做细分,可以被称为运行状态
阻塞状态:进程等待某种事件的完成,例如 scanf 函数,如果你不向终端输入任何东西,则该进程不会向下执行代码,此时的状态为阻塞状态
挂起状态:内存空间严重不足时,操作系统会将就绪状态或者阻塞状态的代码和数据(唤入)磁盘上的swap分区,在执行该进程时,再将代码和数据从磁盘上的swap分区进行唤出
Linux 中的进程状态
cpp
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char *const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
R运行状态:进程在运行中或者在运行队列里
S睡眠状态:进程在等待事件完成(例如:代码中带有scanf, 进程就会从运行队列转到等待队列),这种睡眠状态被称为可中断睡眠
D磁盘休眠状态:进程在等待IO的结束,这种睡眠状态被称为不可中断睡眠
T停止状态:进程收到了信号,被暂停了
t调试跟踪暂停状态:对所写的可执行程序的代码进行调试的进程
X死亡状态:进程结束了,这个状态只是一个返回状态,在任务列表中看不到
Z僵尸状态:子进程退出,父进程不做管理,子进程就会变成僵尸进程
僵尸进程
僵尸状态是一个比较特殊的状态,当进程退出并且父进程没有等待子进程(后面会讲),从而没有读取到子进程退出状态的信息,此时子进程就会变成僵尸状态
僵尸进程的PCB会一直存在进程列表中,(但僵尸进程的其他资源(如代码,数据,虚拟地址空间,页表等)会被操作系统回收) 并且会一直等待父进程读取退出状态的信息,此时就会占有内存空间,从而发生内存泄漏
详谈内存泄漏:
学习语言的时候,我们写的程序向堆区申请的空间,没有手动释放,这不叫内存泄漏,因为当我们进程退出的时候,操作系统会进行对我们进程申请的空间进行管理并释放。
产生内存泄漏的原因有两种:
一种是僵尸进程
一种是常驻进程,长时间甚至一直占用内存空间,不断向堆区申请空间,且忘记了手动释放
孤儿进程
孤儿进程:父进程先退出,子进程后退出,此时子进程被称为孤儿进程
孤儿进程会被1号进程领养,所以子进程退出时,它的相关数据会被1号进程释放掉,不会造成内存泄漏