news 2026/5/11 7:09:32

【Linux】进程(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux】进程(一)

进程(一)

文章目录

  • 进程(一)
    • 一、 进程
      • 1、程序与进程
      • 2、 task_struct(PCB)
      • 3、进程状态
    • 二、 进程信息查看
      • 1、 PID和PPID
      • 2、查看进程的底层方式:/proc 文件系统
        • (1) ls /proc
        • (2) ls /proc/PID
      • 3、 top/htop、ps
        • (1) top 命令(动态查看进程)
        • (2) htop
        • (3) ps 命令(显示当前进程信息)
        • (4) fork()创建进程
    • 三、 代码实操
      • 1、 单进程无限循环打印自身PID
      • 2、 进程阻塞演示(scanf输入阻塞)
      • 3、fork()创建子进程
    • 四、 CPU如何实现同时运行多个进程
      • 1、 时间片轮转
      • 2、 上下文切换
      • 3、 fork 创建的父子进程,也是靠这个原理同时运行吗?

一、 进程

1、程序与进程

我们写的.c源代码文件,编译后生成可执行程序,程序目前还只是文件,还只是静态的代码和数据的集合

进程,通俗的说,就是我们在终端敲./process时,正在运行的程序就是进程。它会占用CPU、内存、执行代码,有自己的生命周期

但是从内核的角度来看,进程=内核数据结构(task_struct)+自己的程序代码和数据

2、 task_struct(PCB)

  1. PCB:进程控制块(Process Control Block)管理=描述+组织,内核要想管理进程首先就得知道进程的基本信息。PCB专门给每个进程单独创建信息记录表
  2. 在Linux下,PCB的具体实现为task_struct 结构体。
    task_struct是Linux内核为每个进程创建的核心结构体包含进程的所有属性,内核通过双向链表组织所有进程的task_struct,实现进程的调度、查找、创建与删除,核心内容有:
  • 标示符(PID/PPID):进程的“身份证号”,唯一区分每个进程
  • 进程状态:进程当前的“工作状态”(比如运行、阻塞、暂停、僵尸)
  • 优先级:进程“抢占CPU的优先级”,优先级高的先使用CPU
  • 程序计数器(PC):进程“下一条要执行的指令地址”,相当于看书时的书签
  • 上下文数据:保存寄存器数据,切换现场用
  • 内存指针:告诉操作系统,进程的代码和数据存在内存的哪个位置
  • I/O 状态信息:记录进程打开文件、设备占用情况
  • 记账信息:记录进程占用了多少CPU、内存资源

这里这些名词并没有解释很清楚,接下来的博客会慢慢重点拆解的,大家就有个印象就好了

所有运行在系统里的进程都以task_struct双链表的形式存在内核里

3、进程状态

进程在运行过程中会切换不同状态,用ps aux命令可查看(STAT列),每种状态对应具体场景,不用死记硬背:

  • R(运行态):要么正在占用CPU运行,要么在等待CPU调度(排队等“工位”)
  • S(可中断睡眠):等待某个事件完成(比如等待用户输入),可被信号唤醒(比如Ctrl+C终止)
  • D(不可中断睡眠):等待磁盘I/O(比如读写文件),不响应任何信号,避免数据丢失
  • T(停止态):进程被暂停(比如Ctrl+Z),需特定信号才能恢复运行
  • Z(僵尸态):进程已退出,但“人事档案”(PCB)未被回收,会占用少量内存

大家有个印象就好了,以后会慢慢讲解清楚

二、 进程信息查看

1、 PID和PPID

  • PID( Process ID):进程ID,系统为每个进程分配的唯一整数,相当于“个人身份证号”

  • getpid():获取当前进程的PID

  • PPID(Parent Process ID):父进程ID,创建当前进程的进程的ID,相当于“父亲的身份证号”

  • getppid():获取当前进程的父进程PID

  • 注意:使用这两个系统调用时,需引入头文件<sys/types.h>和<unistd.h>,否则会编译报错

2、查看进程的底层方式:/proc 文件系统

/proc是Linux系统中的虚拟文件系统,Linux下所有进程的详细信息都以文件和文件夹的形式,都暴露在/proc目录下

(1) ls /proc

ls /proc 可以查看系统中所有进程对应的目录每个以数字命名的文件夹,就是对应PID的进程目录

图片中的1就是PID=1的进程目录,也就是系统里的第一个进程

(2) ls /proc/PID

ls /proc/PID,可以查看指定PID的进程详细信息

图片中前三行报错是因为我现在是普通用户,没有权限访问这些敏感文件,所以系统拒绝了我的请求。用sudo提权就能看了

这些文件/文件夹都是PCB的信息,比如cwd是当前工作目录,exe是进程对应的可执行文件路径,task是进程内的所有线程信息

/proc目录下的文件均为虚拟文件,不占用实际磁盘空间,仅在内存中存在,进程终止后,对应的/proc/PID目录会自动删除

3、 top/htop、ps

(1) top 命令(动态查看进程)
  1. 功能实时动态显示系统中所有进程的运行状态,默认按CPU占用率排序,可实时刷新
  2. 启动top:终端敲top,进入动态监控界面
    控制键:
    P:按CPU占用率从高到低排序(默认)
    M:按内存占用率从高到低排序
    k:终止指定进程(输入进程PID,按回车确认,再按y确认终止)
    q:退出top界面
  3. 第三行是CPU使用率(id为空闲率,id越高系统越空闲)
    第四、五行内存/交换分区使用
    下方表格是进程列表(PID为进程号,%CPU、%MEM为资源占用)
(2) htop
  1. htop 是 top 的升级版,彩色界面、支持鼠标操作,更直观易用
  2. 先安装再实践,我是CentOS系统,直接敲sudo yum install htop -y就安装成功
  3. 安装成功后,直接敲htop进入监控界面,可通过鼠标滚轮滚动进程列表,按 F6 按CPU/内存排序,操作更便捷,退出同样按 q 键,其他操作与top基本一致
(3) ps 命令(显示当前进程信息)
  1. ps aux查看系统中所有进程的详细信息,a=显示所有用户进程,u=显示用户信息,x=显示后台进程
  2. ps aux | grep myprocess过滤查找指定进程,比如查找myprocess进程

我们后面再代码演示

(4) fork()创建进程

查看进程后,我们进一步学习如何创建新进程。Linux中创建进程的核心系统调用是fork(),其功能是复制当前进程,同时生成一个新的子进程,后续会通过完整代码实操,这里先做基础认知:

  • fork()调用一次,返回两次,操作系统会同时让父进程和子进程都执行这一句父进程返回子进程PID,子进程返回0)
  • 创建子进程后,父子进程会同时运行,父子运用同一份代码,但是各自的数据是独立的,你改你的,我改我的,互不影响

三、 代码实操

1、 单进程无限循环打印自身PID

创建一个单进程,无限循环打印自身PID,观察进程运行状态

#include<stdio.h>#include<sys/types.h>// getpid()所需头文件#include<unistd.h>// sleep()所需头文件intmain(){while(1)// 无限循环,进程持续运行{pid_tid=getpid();// 获取当前进程PIDprintf("我是一个进程: pid: %d\n",id);// 打印PIDsleep(1);// 暂停1秒,避免打印过快}}
  1. myprocess原本只是静态的程序,./myprocess运行它,程序被加载到内存,变成了动态的进程
  2. 我们可以看到每一行都有唯一的pid:4663,在进程运行期间,这个PID固定不变,同一时间,系统里不会有第二个进程的PID是4663,这就是进程的“身份证号”
  3. ctrl+c就能中止运行

    4.现在我们再开一个终端,执行同一个./myprocess
    这时第二个终端里的PID会是一个完全不同的数字5402,这说明,同一个程序,每次运行会生成一个新的进程,拥有独立的PID

2、 进程阻塞演示(scanf输入阻塞)

演示进程的S(可中断睡眠)状态,进程等待用户输入时,会暂停运行,直至输入完成后恢复

执行 ./myprocess2 后,终端光标一直闪烁,没有任何输出
这是因为进程执行到 scanf(“%d”, &a); 时,主动放弃了 CPU,进入了阻塞状态(Linux 中称为 S 可中断睡眠),正在等待用户输入数据

打开第二个终端,查看所有和 myprocess2 相关的进程,可以看到当前进程状态为S+(前台阻塞睡眠),S表示进程在可中断睡眠状态,+表示它是一个前台进程,现在的终端被它占用了,不能输入其他指令

输入3并按下回车后,scanf接收到了用户输入,进程从阻塞状态被唤醒,恢复为运行态,继续执行后续代码

3、fork()创建子进程

  1. 通过fork()系统调用创建子进程,父子进程各自无限循环,持续打印自身PID和父进程PID,直观看到多进程运行效果
  2. 父进程先打印一次自身信息,然后休眠 3 秒,再调用 fork() 创建子进程。fork() 会复制当前进程,创建一个几乎一模一样的子进程
  3. fork() 调用一次,返回两次:父进程返回子进程 PID,子进程返回 0
  4. 通过 if/else 分支,让父子进程进入不同的无限循环,分别打印各自的信息
  5. 父子进程是两个独立进程,各自在自己的地址空间运行,互不干扰
  6. 第一行输出:是父进程在 sleep(3) 之前打印的,此时 ppid 是终端进程的 PID 2195
    后续父进程输出中 ppid 变成 0:说明父进程的父进程(终端)已经退出了,此时父进程被系统 1 号进程收养,在某些系统上会显示为 0(本质上父进程成为了孤儿进程),我们后面再详细解释
  7. 32565 是子进程的 PID,它的父进程 PID 正好是父进程的 PID 32557,完美验证了 fork() 创建的父子关系
  8. 父子进程各自循环执行 printf,说明两个进程同时运行,操作系统在调度它们


ps -ef 验证,父进程的 PID 32557,子进程的 PPID 正好等于它,和打印的结果完全对应

  1. fork() 为什么会有两个返回值?
    因为调用 fork() 后,操作系统把当前进程复制了一份,此时存在两个进程(父进程和子进程),两个进程都会执行fork()这一行,所以父进程返回一次子进程返回一次,看起来就像 “调用一次,返回两次”
  2. 两个返回值如何分配给父子进程?
    父进程:fork() 返回新创建的子进程的 PID(正整数);
    子进程:fork() 返回 0;
    如果创建失败(如进程数超限),fork() 返回 -1
  3. 为什么同一个变量 id 能让 if 和 else 同时 “成立”?
    并不是变量同时等于 0 和不等于 0,而是两个进程在各自的地址空间里运行!fork()之后,变成两个独立进程,它们各自有各自的变量id,互不干扰!
    父进程的 id 变量值是子进程的 pid(非 0),所以进入 else 分支
    子进程的 id 变量值是 0,所以进入 if 分支
    它们是两个独立进程里的两个同名变量,互不干扰,因此会出现 “同时执行两个分支” 的效果

四、 CPU如何实现同时运行多个进程

CPU 同一时刻,一个核心只能跑一个进程。我们看到的软件同时运行、父子进程一起打印、后台挂一堆程序,都不是真正并行,是高速切换模拟出来的并发效果

1、 时间片轮转

操作系统会公平管理所有进程,给每一个进程分配一小段极短的时间,这就叫时间片

时间片一般只有几毫秒,短到人完全感知不到。 一个进程拿到时间片就会占用 CPU 执行代码,
时间片一旦耗尽就会立刻暂停,换下一个进程上 CPU。所有进程排队轮流使用 CPU,雨露均沾

不管是你自己写的 fork 父子进程,还是浏览器、终端、输入法、系统后台服务,全部都要遵守规则:排队抢时间片,轮流上 CPU 运行

2、 上下文切换

进程被强行暂停时,它算到一半的数据、下一行要执行的代码,不能丢。解决方案就是上下文切换。

  1. 当前进程时间片用完,内核立刻把它 CPU 寄存器里所有数据、运行位置、当前状态
    全部保存到该进程的 task_struct(PCB 进程档案)里。暂时把这个进程 “挂起”,放一边排队
  2. 调取下一个待运行进程的 task_struct,把它之前保存的寄存器数据、运行状态,重新写回 CPUCPU 接着这个进程上次停下的位置,继续往下执行
  3. 这个保存现场 + 切换进程 + 恢复现场 的完整过程,就叫做 进程上下文切换

打个比方把CPU核心想象成只有一张的办公桌
桌子只有一张,同一时间只能一个人办公
所有人(所有进程)排队,每人只允许用桌 3 秒钟
时间一到,立刻收走这个人的本子、草稿、写到哪一页全部记好。换下一个人坐上来,拿出自己的资料继续写
切换速度超级快,一秒钟来回换人几十上百次,肉眼看起来:所有人好像一直在同时办公
实际上:轮流插队、高速换人。

3、 fork 创建的父子进程,也是靠这个原理同时运行吗?

就是这个原理!
我们调用 fork() 后,系统多出一个子进程
此时系统里就有父进程、子进程 两个独立进程
操作系统一样给它们各自分配时间片
父跑一会儿、切走,子跑一会儿、切走,来回高速切换,所以在终端上父打印一行、子打印一行、交替输出
父子进程不是真的一起在 CPU 上跑,是轮流抢 CPU 时间片 + 频繁上下文切换

并发和并行
并行:有多颗 CPU / 多核,同一时刻真正同时跑多个进程
并发(日常使用):单个核心,靠时间片高速切换,宏观同时、微观串行
有机会我们再来详细谈谈

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 7:09:02

PX4 Firmware V1.14.4 开源支持

PX4 官方固件版本迭代迅猛&#xff0c;这往往导致开发者在硬件兼容性、环境搭建及软件依赖性上遭遇重重挑战。为彻底解决这一问题&#xff0c;Kerloud 推出固件与文档长期支持&#xff08;LTS&#xff09;计划。我们将对飞控固件代码、技术文档及参数调优指南实施持续性维护&am…

作者头像 李华
网站建设 2026/5/11 7:05:36

精进点击游戏的升级系统

在开发点击游戏(Clicker Game)时,如何实现一个优雅的升级系统是许多开发者面临的挑战。今天,我们来探讨如何通过JavaScript实现一个自动增量升级系统,并解决在游戏重置时如何停止该升级的生成。 基本游戏结构 首先,我们需要一个基本的点击游戏框架,包括点击增加分数、…

作者头像 李华
网站建设 2026/5/11 7:02:42

Python中一些不为人知的基础技巧总结

前言本文主要给大家总结介绍了关于Python的一些基础技巧&#xff0c;分享出来供大家参考学习&#xff0c;下面话不多说了&#xff0c;来一起看看详细的介绍吧。1.startswith()和endswith()参数可以是元组当检测字符串开头或结尾时&#xff0c;如果有多个检测值&#xff0c;可以…

作者头像 李华
网站建设 2026/5/11 6:52:14

Redis分布式锁进阶第五十七篇

Redis分布式锁进阶第二十五篇&#xff1a;联锁深度拆解 多资源交叉死锁根治 复杂业务多级加锁绝对有序方案一、本篇前置衔接 第二十四篇我们完成了全系列终局复盘&#xff0c;整理了故障排查SOP与企业级落地铁律。常规单资源锁、热点分片锁、隔离锁全部讲透&#xff0c;但真实…

作者头像 李华
网站建设 2026/5/11 6:52:13

VSIPL:嵌入式信号处理的跨平台解决方案

1. VSIPL&#xff1a;嵌入式信号处理的工业级解决方案在实时嵌入式多计算机系统的开发中&#xff0c;代码的可移植性一直是困扰工程师的难题。1990年代末&#xff0c;来自政府、学术界和工业界的专家们共同创建了VSIPL&#xff08;Vector Scalar Image Processing Library&…

作者头像 李华