Linux 进程属性详解

我们平常查看进程,一般用 ps,或者看看 /proc/ 目录。

操作系统里同时跑着这么多进程,是靠什么把它们区分开、组织起来的?


一、PID:进程的身份标识

PID 是什么?

Process ID,每个进程运行的时候都有一个属于自己的编号,用来把不同的进程区分开。

所以 ps 这个命令,本质上就是去遍历内核里的那个进程链表,然后把拿到的信息格式化打印出来。

PID 放在哪里?

PID 是进程的一个属性,存放在 PCB(进程控制块)里面。

有个问题:学校的保安也在学校里面,那保安算不算学生呢?

------ 有点像这个意思:PID 虽然在 PCB 里,但它只是用来标识进程的一个字段,不能说它本身就是一个进程。

同一个进程再次启动,PID 可能会变

就像高考,不满意考上的院校,于是你二战。最后你又考上了同一所学校,但这次你的学号相对于之前可能就变了。

如何获取自己的 PID?

我们做的所有操作,最后都要变成一个进程来跑。

那如果我想在程序里拿到自己这个进程的 PID,该怎么做呢?

所有操作都得通过系统接口来完成,所以系统提供了一个获取 PID 的接口:getpid()


二、PPID:父进程是谁

知道了自己的 PID,那它的"上级"是谁?

这就引出了 PPID------父进程的 PID。

每次登录云服务器,操作系统都会给我们新建一个 bash 进程作为当前会话的父进程,而原来的 bash 进程也不会消失,只是不再是你的父进程了。

我们所有的命令行操作,父进程都是 bash,即命令行解释器。
bash 只负责解释命令行,子进程运行出问题也不会影响 bash 进程------这就是通过创建子进程来实现的(后面会讲)。


三、常用命令

查看进程

Shell 复制代码
ps ajx | head -1   # 看表头
ps ajx | grep xxx   # 过滤想要的内容

杀死进程

Shell 复制代码
kill -9 22146

四、进程的创建

我们上文一直提到进程,那进程是如何创建的?

当然也是通过调用操作系统提供的接口。

  • ./xxx ------ 命令行层面创建进程

  • fork() ------ 代码层面创建进程

fork() 干了什么?

为父子进程分别创建 PCB,让子进程与父进程共享代码(代码是只读的,可以共享)。

创建子进程,本质上就是系统中多了一个进程。

为什么 fork 要有两个返回值?

是为了区分让不同的执行流执行不同的代码块。

一般而言,fork 之后的代码父子共享,但通过返回值可以分开执行不同逻辑。

为什么给子进程返回 0,给父进程返回子进程的 PID?

现实生活中,一个父亲能有多个子女,而这些子女只有一个父亲。

给父进程返回子进程 PID,是为了让父进程能区分和管理自己的多个子进程;给子进程返回 0,是因为子进程想要知道自己的父进程非常简单:getppid()即可。

一个函数是如何做到返回两次的?

关键在于:fork 在返回之前,进程创建的操作已经完成了。

这时候父子进程都已经存在,所以 fork 会在父子进程里各返回一次------父进程返回子进程的 PID,子进程返回 0。

从调用者的视角看,就像是 fork 一次调用,返回了两次。

一个变量怎么会有不同的内容?

任何平台,进程在运行时都具有独立性,互不影响。

因为数据可能被修改,所以为了确保独立性,父子进程不会共享"同一份"数据。

写时拷贝

为了节约系统资源,操作系统不会将父进程的数据完全拷贝给子进程,而是按需分配。

只有某一方尝试修改数据时,才会把对应的那一页内存拷贝一份,让父子各自拥有一份副本。

父子进程创建好后,谁先运行?

由调度器决定,没有具体的先后顺序。

但这对我们并没有什么影响,我们只关心最后进程能否正常运行。


五、进程状态

经典进程状态运行、阻塞、挂起

运行态与就绪态

  • 运行态:进程正在 CPU 上真正执行。

  • 就绪态:进程已经具备运行条件,在运行队列里排队,随时可以被调度上 CPU。

进程在被 CPU 执行前,都会先被链入一个"运行队列"(里面放的是就绪态进程),然后 CPU 从中一个个读取。

一个进程只要开始运行,是不是就要一直执行完毕才让出 CPU?

不是!

每个进程都有一个时间片 的概念。时间片用完后,内核会触发时钟中断,把 CPU 交给下一个进程。

这种分时扫描的执行方式,让我们感觉所有进程都在同时跑------这也是 RTOS 在单核 CPU 上跑出多任务(并发)的原理。

阻塞状态

学过单片机编程的都知道,单片机中的 delay 会导致等待。

但要注意区别:

  • 裸机的 delay:CPU 在空转(忙等),仍然占用 CPU

  • 操作系统中的阻塞:进程主动让出 CPU,进入等待队列,CPU 去执行其他进程

操作系统管理硬件资源的方式,同样是"先描述,再组织"------这和我们单片机编程时配置 GPIO、UART、I2C、SPI 的思路是一致的。

被链入等待队列的进程,就处于阻塞状态

挂起状态

当操作系统内部资源严重不足时,为了保证运行态进程正常运行,操作系统会将内存中处于阻塞态进程的代码和数据转移到磁盘中。

此时的进程就处于挂起状态

绝大多数挂起都是阻塞挂起,运行挂起非常罕见。


六、Linux 中的具体进程状态

在初步了解了经典进程状态后,我们可以进一步看看,在 Linux 中进程具体有哪些状态。

一个进程的状态,取决于它的 PCB 被放在了哪个队列里,和进程的代码、数据所在位置关系不大。

就像你属不属于这个学校的学生,并不取决于你本人是否在学校里,而是你的个人档案是否在学校里。

Linux 中常见的进程状态:

R(运行状态,TASK_RUNNING)

并不意味着进程一定在运行中,它表明进程要么正在运行,要么在运行队列里排队(即就绪态)。

S(睡眠状态,TASK_INTERRUPTIBLE)

可中断睡眠,意味着进程在等待某个事件完成(如等待输入)。

例如执行 scanf 时,进程就处于 S 状态,等用户输入完成后被唤醒。

所以睡眠态常见于等待输入/输出外设。

D(磁盘休眠状态,TASK_UNINTERRUPTIBLE)

也叫不可中断睡眠。这种状态的进程通常在等待 I/O 结束(如向磁盘写入数据)。

为什么需要 D 状态?

当系统内存严重不足时,哪怕用上了 swap(内存交换)也撑不住了,操作系统就会开始杀进程,挑一个"看着不太忙"的干掉,腾出资源。

想象一下:A 进程正在向磁盘写入机密数据,此时系统资源紧张,操作系统如果"吧唧"一下把看似空闲的 A

进程杀了,磁盘数据写入到一半发现没人管了,就会出问题。

D 状态就是为了保护这类关键 I/O 操作不被中断------让进程在等磁盘 I/O 的时候,操作系统杀不动它。

所以,如果你能看到 D 状态的进程,说明你的操作系统可能已经快撑不住了。

T(停止状态,TASK_STOPPED)

可以通过发送 SIGSTOP 信号暂停进程,此时进程进入 T 状态;发送 SIGCONT 信号可让其继续运行。

停止态和睡眠态的区别在于:睡眠态一定是在等待某些资源(如输入),而停止态可能只是单纯被暂停,比如我们调试程序时,断点处就是停止态。

X(死亡状态,TASK_DEAD)

这个状态只是一个返回状态,你不会在任务列表里看到它。此时进程进入回收队列,释放占用的资源。

我们用ps查看进程时,看到的大多是 S 状态(可中断睡眠),而不是 R 状态。

因为绝大多数程序运行过程中,大部分时间都在"等待"------等时间片、等 I/O、等用户输入。

而CPU 执行速度极快,每个进程的时间片一眨眼就用完了。你敲ps的那一刻,它大概率正在排队,而不是正好在 CPU 上跑。


七、僵尸进程

什么是僵尸进程?

当子进程退出,而父进程没有调用 wait()waitpid() 读取子进程的退出状态时,子进程就会进入僵尸状态(Zombie)

僵尸进程会以终止状态保留在进程表中,等待父进程读取它的退出码。

为什么要有僵尸状态?

进程退出后,它的退出状态(成功还是失败,返回码是多少)必须被维持下去,因为它要告诉父进程:你交给我的任务,我办得怎么样了。

如果父进程一直不读取,子进程就一直处于 Z 状态,它的 PCB 也一直保留着。

僵尸进程的危害

一个父进程创建了很多子进程却不回收,这些子进程的 PCB 就会一直占用内存,造成内存泄漏

怎么避免?后面讲。

就像案发现场,警察不会直接处理后事,而是先保护现场、收集信息,然后通知家属。如果家属一直不来,案发现场就不得不一直保持原样。


八、孤儿进程

什么是孤儿进程?

如果父进程先退出,而子进程还在运行,那么子进程就成了"孤儿进程"。

孤儿进程会被 1 号进程(init 进程)收养,后续由 init 负责回收。

为什么要领养?

因为孤儿进程将来也会退出,必须有人负责回收它占用的资源。

领养后,子进程的父进程就变成了 1 号进程。


九、进程优先级

什么是优先级?

CPU 资源分配的先后顺序,就是指进程的优先权。

优先级高的进程有优先执行的权利。

优先级和权限的区别

  • 优先级:决定使用资源的先后顺序

  • 权限:决定你是否有权使用资源

为什么要引入优先级?

因为 CPU 资源是有限的,而进程是多个的,所以进程之间天然存在竞争性

操作系统必须保证大家良性竞争,所以需要确定优先级。

如果一个进程长时间得不到 CPU 资源,它的代码就无法推进------这就是进程饥饿问题

很好理解:每天打饭都要排队,为什么学校不给我们每个人配个厨师?因为资源有限嘛。

如果学校不保证良性竞争,身体弱的学生就可能一直抢不到饭。

PRI 和 NI

  • PRI:进程的优先级,值越小,优先级越高,越早被执行。

  • NI(nice) :进程优先级的修正值。

    最终优先级计算公式:PRI(new) = PRI(old) + nice

    nice 的取值范围是 -20 到 19,共 40 个级别。

    nice 为负值时,优先级变高;nice 为正值时,优先级变低。

需要强调:nice 值不是优先级,而是优先级的修正数据

能随意修改优先级吗?

理论上可以(如裸机开发场景下),但操作系统不允许你随意调整,它只允许你在规定范围内调整(通过 nice 值)。

查看进程优先级的命令

  • ps -l 可以查看 PRI 和 NI

  • top 命令可以动态查看并调整已存在进程的 nice 值(在 top 界面按 r 键,然后输入 PID 和新的 nice 值)


十、其它概念

  • 竞争性:系统进程数目众多,而 CPU 资源有限,进程之间具有竞争属性。为了高效完成任务,合理竞争资源,便有了优先级。

  • 独立性:多进程运行时,各自独享资源,互不干扰。

  • 并行 :多个进程在多个 CPU 上同时运行。

  • 并发:多个进程在一个 CPU 上通过进程切换的方式,在一段时间内都得以推进。


十一、bash 与子进程的关系

现在我们再回头看最开始提到的 bash

我们所有的命令行操作,父进程都是 bash,那 bash 如何保证它创建的进程与自己互不影响?

------ 通过创建子进程来完成。如何创建?fork()

这也是 bash 执行命令解释的基本方式:fork 出一个子进程,然后子进程去执行命令,父进程(bash)继续等待或做自己的事。

相关推荐
AI成长日志2 小时前
【实用工具教程专栏】GitHub Actions自动化工作流入门(基础篇)
运维·自动化·github
优化控制仿真模型2 小时前
【26专四】英语专业四级TEM4历年真题及答案解析电子版PDF(2009-2025年)
经验分享·pdf
jnrjian2 小时前
restore archivelog RAC thread from sequence logseq
服务器·数据库
IAUTOMOBILE2 小时前
Ubuntu 22.04 下 NVIDIA H100 服务器完整部署攻略:驱动、Fabric Manager 与 Container Toolkit 配置
服务器·ubuntu·fabric
三万棵雪松2 小时前
【Linux 物联网网关主控系统-感知层部分(一)】
linux·单片机·物联网·嵌入式linux
三万棵雪松2 小时前
【Linux 物联网网关主控系统-感知层部分(二)】
linux·物联网·嵌入式linux
淼淼爱喝水2 小时前
openEuler 下 Ansible 基础命令详解与实操演示1
linux·服务器·ansible
杨云龙UP2 小时前
Linux环境下Oracle RMAN全量、增量备份与定时任务实践_20260331
linux·运维·服务器·数据库·oracle
azurehan012 小时前
计算机视觉学习笔记专有名词学习~1
笔记·学习·计算机视觉