news 2026/9/30 7:14:34

【Linux进程篇】Linux学习第十三弹——程序放进内存就是进程吗?先把“进程”理解透

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux进程篇】Linux学习第十三弹——程序放进内存就是进程吗?先把“进程”理解透


🎬 个人主页:MSTcheng · CSDN
🌱代码仓库:MSTcheng · Gitee

🔥 精选专栏:
《C语言》《数据结构》 《算法学习》
《C++由浅入深》
《Linux操作系统》
《Mysql数据库》 《白话大模型》
💬座右铭:路虽远行则将至,事虽难做则必成!

在上篇文章中,我们为大家介绍了冯·诺依曼体系结构,并建立了对操作系统的宏观理解。本篇,我们将正式踏入进程部分的学习之旅。

上一篇介绍冯诺依曼的体系结构:

【Linux进程篇】Linux学习第十二弹——为什么 CPU 只和内存说话?进程前必懂的底层逻辑!

文章目录

  • 一、进程的引入(什么是进程、什么不是进程)
    • 1.1 直观认识:`进程 = task`(任务)
    • 1.2 那什么不是进程?`程序 ≠ 进程`
    • 2.3 关键理解(是谁帮你加载?)
    • 2.4 进程的精确定义
  • 二、`PCB`:进程控制块(重点中的重点)
    • 2.1、`PCB` 与 `task_struct`
    • 2.2 为什么"操作系统原理"很难听懂?(学科哲学)
    • 2.3、`task_struct` 内部的常见属性(宏观分类)
      • 2.3.1. 标识符(Identifier)
      • 2.3.2. 状态(Status)
      • 2.3.3. 优先级(Priority)
      • 2.3.4. 程序计数器(`Program Counter`,简称 `PC`)⭐⭐⭐
    • 2.4 内存指针(Memory Pointer)⭐⭐⭐
    • 2.5. 记账信息(Accounting)
  • 三、`task_struct`中的上下文数据与进程切换(重点点内容)
    • 3.1. 一个进程会把 CPU 占完才让出来吗?
    • 3.2 时间片(Time Slice)与时间片轮转
    • 3.3 进程切换:保存上下文 & 恢复上下文(关键)
    • 3.4 上下文保存到哪?寄存器只有一套!
  • 四、总结

一、进程的引入(什么是进程、什么不是进程)

1.1 直观认识:进程 = task(任务)

  • 你在 Windows 启动的画图板、浏览器、各种软件,手机上打开的 APP,本质上都是一个进程。
  • 进程的英文是task,直译过来叫"任务"。
    既定事实一:一个系统内可以同时启动很多进程。
  • 既定事实二:进程在"没有成为进程之前",它就是磁盘上的一个可执行程序!(编译好的二进制文件,如.exe),装在磁盘特定路径下,靠快捷方式使用。

双击.exe、点击 APP、Linux 里./xxx运行程序,站在操作系统层面上都叫"来活了 / 来任务了" ——本质都是在启动进程!

1.2 那什么不是进程?程序 ≠ 进程

  • 程序 = 磁盘上的一份文件(只有代码 + 数据,代码如 for/while 循环,数据如各种变量,最终对计算机都是 0/1 二进制信息)。程序不是进程。
  • 只把程序加载到内存,也不是进程。

那什么才算是进程?

“加载到内存的代码和数据” + “描述该进程的结构体”,两者合起来才叫进程。

2.3 关键理解(是谁帮你加载?)

  • 程序要运行,必须先加载到内存(内存里得预先装有操作系统——每次开机第一件事就是先加载 OS)。
  • 谁来帮你加载?👉操作系统帮你加载。例如把磁盘文件导入内存,本质是一次import操作。
  • 系统里大量程序同时要加载运行,谁先谁后?这时操作系统必须能够:把这些加载到内存里的程序全部管理起来。如何管理?先描述,再组织!

2.4 进程的精确定义

进程 = 内核数据结构(描述该进程的结构体)+ 该进程自己的代码和数据

  • 一个进程有一个"描述它的结构体(内核提供的、描述进程属性的结构体),通过该结构体可以管理进程的代码和数据。
  • 操作系统对进程的管理 =把这个结构体对象用链表(各种结构)组织起来 → 转化成了对链表的增删查改。
  • 组织的新变化:有进程 → 加载代码数据 → 创建描述结构体 → 建立关联 → 把结构体挂到链表上。

一张图来总结上面的核心内容:

所以真正的"进程核心"更重要的反而是:操作系统内描述该进程的结构体及其属性字段。

下面我们就来了解一下操作系统内描述该进程的结构体以及属性字段都有什么内容:

二、PCB:进程控制块(重点中的重点)

2.1、PCB与task_struct

  • 在操作系统的学科理论中,描述进程的结构体叫:PCB = Process Control Block(进程控制块)。所有操作系统教材都这么叫。

2.2 为什么"操作系统原理"很难听懂?(学科哲学)

  • 教材只告诉你概念,却不告诉你具体实现。因为"操作系统原理"讲的内容放在任何一款具体操作系统上都必须是对的(必须有进程、线程、调度、切换、保存上下文、优先级排队)。
  • 但一讲到具体实现,不同操作系统做法就不同(Linux 和 Windows 描述进程、调度切换的细节肯定不一样)。
  • 所以操作系统原理这门课 =计算机学科的哲学(哲学 = 放哪儿都对;就像"生产关系决定生产力"这句话在哪儿都成立)。
  • 放到具体实现上:
    • Linux中描述进程属性的结构体叫struct task_struct(任务控制块)。
    • PCB是所有操作系统学科里描述进程控制块的总称,task_struct是PCB在Linux下的具体一种。
    • Windows / macOS 的具体结构体叫什么不重要(源码未开源、意义不大)。

关键理解:

  1. task_struct里一定有指向自己代码和数据的指针(必须指向自己的,不能指别人的)——后续会学如何间接找到自己的代码数据。
  2. 操作系统管理进程,本质管理的是内核数据结构(task_struct),而不是管理进程的代码数据本身。

2.3、task_struct内部的常见属性(宏观分类)

2.3.1. 标识符(Identifier)

  • 进程太多,怎么区分谁先谁后、彼此不同?靠标识符。
  • 类比:学生的学号、公司员工的工号、社会的身份证号。
  • 标识符就是PID(Process ID,进程标识符),本质是一个整型整数(如 1234),它用来标识系统中唯一一个进程!
  • 一个进程可能还有不止一个标识符,但核心的标识符是 PID。

2.3.2. 状态(Status)

  • 进程运行有不同状态:运行、暂停、停止、休眠、新建、死亡(如网易云点暂停/播放)。
  • 从理解角度:状态在结构体内就是个枚举类型 / 整型变量——用整数表示(如 1=运行、2=暂停、3=退出)。

⭐贯穿结构体属性学习的重要思想:task_struct里所有属性,无论名字多抽象(状态、优先级、标识符…),最终一定是一个结构体里的属性,一定是一种数据类型(int / long / float / 自定义结构体)的变量。把抽象概念翻译成"结构体里的某个整数/变量",就轻松理解了。

2.3.3. 优先级(Priority)

  • 类比食堂排队打饭:排队的本质其实就是确认优先级。队头的先打饭(先到的进程先享受资源——CPU),队尾的后得。
  • 本质:排队的本质就是决定谁先得、谁后得某种资源。对进程来说,顶级的资源就是CPU。
  • 为什么存在优先级?→因为资源少、人多。如果学校给每个同学开一个打饭窗口,那么就不需要优先级了!
  • 一句话本质:优先级存在的原因是"资源不足、竞争对象太多"(进程有几十上百个,CPU 只有一块/几核几核几,永远不够)。优先级解决人多资源少的问题!
  • 现实世界里也到处是"优先级筛选":高考分数 = 优先级;面试成绩 = 优先级。

从结构体角度:优先级也是一个整型变量,一般数字越小代表优先级越高(类似考试成绩排名)。

2.3.4. 程序计数器(Program Counter,简称PC)⭐⭐⭐

先建立背景知识——CPU内寄存器:

  • 程序运行时会形成很多临时数据,保保存在 CPU 内部的寄存器里。
  • x86 寄存器详解:通用寄存器(EAX、EBX、ECX、EDX)、指针寄存器(EBP、ESP)、程序计数器(EIP,指令指针)、标志寄存器(EFLAGS)、段寄存器(CS、DS、ES 等)。

举一个 C 语言的例子(理解临时数据如何返回):

intadd(){intz=x+y;// z 是临时变量,只在函数内有效returnz;// 怎么把 z 的值返回给外部 result?}
  • 函数计算时,x、y被载入 CPU 寄存器,加号被 CPU 识别,计算结果z存在寄存器中(同时在内存里也有一份,因为确实有这个变量)。
  • return z编译成汇编后,实际是move——寄存器里的结果值move到result变量中,于是成功把返回值传出去。
  • 所以一个函数的返回值,是"通过寄存器"传递的。
  • 引申:这段临时数据(程序执行过程在 CPU 寄存器里形成的)就叫临时数据 / 上下文。

程序计数器(EIP / PC / Program Counter):

  • CPU 执行不是你代码一口气全跑完,而是一行一行从上到下依次执行!(你写 50 行代码,编译后可能变成 500 行汇编指令)。
  • 怎么知道当前执行到第几行?靠寄存器EIP(Instruction Pointer,指令指针),也叫PC(Program Counter,程序计数器)。
  • 作用:记录当前正在执行(或即将被执行)指令的下一条指令的地址。执行完一条指令,EIP 自动 ++,指向下一条。
  • CPU 每次问 EIP"下次执行哪条",EIP 指向谁,CPU 就去读谁。

为什么程序计数器要在task_struct中单独列出?→ 因为它和进程切换强相关:

  • 比喻:你吃饭吃一半,有人打电话你出去接,回来还得继续从没吃完的地方接着吃,不能重新打一份。
  • 一个进程(500 行代码)执行到第 300 行时可能被切走,将来回来必须从第 301 行开始,不能从头重跑。
  • 因此必须把"执行到什么位置"记录下来 → 靠 EIP。
  • 直接理解为程序计数器在结构体里也是个整型变量即可。

(关与进程调度相关的内容,在后面学习进程切换调度的时候会重点学习,这里只是稍微了解!)

如果上面的内容看着有点懵,那下面这张图则会让你有更直观,更清晰的感受:

2.4 内存指针(Memory Pointer)⭐⭐⭐

  • 一个进程一个 PCB。CPU 要执行代码时,不是执行这个结构体,而是通过结构体找到你对应的程序代码和数据!
  • 内存指针的作用:CPU 调度使用它 → 通过task_struct找到进程的代码、数据和相关的执行信息,从而真正去执行你的代码。
  • 凡是"具有指向性"的都是指针。

2.5. 记账信息(Accounting)

  • 处理器切换调度时会用:你的 CPU累计执行了多长时间、优先级是多少、总共被调度了多少时间、用了多少时钟。
  • 为什么重要:两个进程优先级相同(都是 10),但一个已经累计调度了 10 秒,另一个只被调度了 1 秒,调度时应优先选择只被调度 1 秒的(体现公平)。
  • 这些衡量进程运行总时间的信息都叫记账信息。

*以上就是task_struct内部的常见属性(上面忽略了IO状态信息(IO章节专门介绍)以及忽略了"上下文数据",下面重点讲)

三、task_struct中的上下文数据与进程切换(重点点内容)

  • 上一节我们认识了task_struct的常见属性,其中程序计数器(PC)比较特殊:它记录的不是进程“长什么样”,而是进程执行到哪一行了。正因如此,它与“进程切换”密切相关。

  • 不过在展开“切换”之前,先明确一件事:进程要真正跑起来,其代码最终得挂到CPU上执行。可以类比食堂打饭:打饭窗口只有一个,想吃饭的同学却有一大堆。如果每个同学都得把一整顿饭吃完才让位,排在后面的同学全得饿死。CPU与进程的关系也是如此——CPU只有一块,进程却有很多个。

这样一来,一个很自然的问题就出现了:一个进程会把CPU占完才让出来吗?下面,我们就从这个最朴素的疑问开始。

3.1. 一个进程会把 CPU 占完才让出来吗?

  • 答案:不是。一个进程不会把自己的全部代码跑完才让出 CPU。
  • 反证:如果单核 CPU 上执行着一个while(1)的死循环代码,如果死循环真的会占死 CPU,那机器就直接卡死,操作系统自己的代码都无法运行了。这显然是不会的!
  • 当代操作系统会给 每一个进程 分配一个"时间片",时间片跑完 就自动让出 CPU,然后另一个进程换上。从而实现比较公平的资源分配!

3.2 时间片(Time Slice)与时间片轮转

  • 时间片:比如有 10 个进程,把 1 秒分给每个进程10ms;一个进程上去只占 CPU10ms,时间一到,操作系统立刻把该进程从 CPU 上剥离,再从所有进程里重新选一个放上去。
  • 这种调度方式叫基于时间片的轮转调度。
  • 简单理解:10 个进程各 10ms,跑一轮 100ms,每个进程能被执行 10 次。进程的执行时间不是从头到尾一直给它的,执行到一定程度没执行完就可能让出 CPU。

3.3 进程切换:保存上下文 & 恢复上下文(关键)

当兵 / 保留学籍的比喻:

  • 大二学生去当兵 → 不能直接走(否则被当退学),要先让导员保留学籍:记住宿舍床位、修了多少门课(50 门修了 20 门)、现在大几、什么专业。
  • 当兵两年回来 → 告诉学校"我回来了",导员恢复学籍。
  • 保留学籍 / 恢复学籍 =保存上下文 / 恢复上下文。
  • 任何保存动作都是为了恢复(昨晚剩饭放冰箱就是为了第二天吃)。

这里的"上下文"到底是谁?

当一个进程因时间片到而暂停、切出去让另一个进程运行时,它要保存的上下文 = 当前进程执行时 CPU 寄存器内的临时数据。

  • 切换前:保存上下文;切换回来:恢复上下文。
  • 而EIP保存的正是"当前执行到什么位置",走的时候把执行位置记下来;回来时把执行位置恢复到 CPU 上,就能从上次停下的位置继续执行→这就是现场保存与恢复,也就是切换的核心思路。

3.4 上下文保存到哪?寄存器只有一套!

重要结论一:

  • 先看清一个背景:进程切换发生在 CPU 上,但 CPU 里的寄存器硬件只有一套(就那几十个)。
  • 一套寄存器要服务几十上百个进程=,靠的不是多配几套硬件,靠的是 在不同时间段 存不同进程的数据。
  • 所以:寄存器的“空间”(左值/变量)只有一份,但寄存器里的“内容”(数据/右值/上下文)可以有多份。

一定要区分"变量的空间"和"变量里的内容"两个概念:int a = 10开辟 4 字节空间只有一份,但空间里一会儿写 2、写 3、写 4,可以随便改。上下文指的是寄存器里的数据(右值),不是寄存器本身(左值)。

  • 结论:上下文数据属于进程私有的数据,每个进程都有一份自己的上下文数据。

重要结论二:老式内核的存储位置

既然寄存器只有一套,进程一旦被切走,它那份上下文数据就得先从寄存器里挪出来。挪到哪儿?老式内核的做法是放进当前进程自己的PCB里。

  • 老式操作系统(如 Linux 0.11)中,进程把 CPU 的硬件上下文保存到当前进程的 PCB(task_struct)里——因为都在内存里。
  • 具体在 task_struct 里有一个结构体叫TSS(Task State Segment,任务状态段),内部套着各种寄存器变量(AX、BX、CX、DX 等通用寄存器、段寄存器等,每个 32 位)。
  • 所以有个属性名叫上下文数据:进程切走时把寄存器值写到 task_struct 的 TSS 中,恢复时再写回 CPU 寄存器。
  • 新式内核(2.6 等):严格说,新式内核并不是“再拿一个指针指向别处”,而是把进程切换要用的硬件上下文,从 TSS 挪到了task_struct的thread成员里。这个成员的类型叫struct thread_struct(线程结构体),ip、sp等寄存器上下文都保存在它里面。补充一句:这些内核线程最初是由0 号进程(PID=0)创建出来的。原来的 TSS 则退化成“每个 CPU 一份”,主要只负责记内核栈指针。所以上下文信息仍能通过task_struct找到——老式“档案袋自己带走”,新式“档案进档案室、凭thread成员查”。

再来一张图让你更清晰的理顺逻辑:

一句话就是,上下文是进程私有的保存的副本也只是存在进程task_struct内嵌的thread_struct(结构体) 中不会保存在 CPU 寄存器中,而且上下文保存在thread_struct中是内存级的!(进程结束,资源被释放数据就被销毁了) 因为进程要执行要被加载到内存,内存中就有描述该进程的结构体,CPU 拿到上一次被切换的进程时从它的task_struct内嵌的thread_struct中找到该进程的上下文然后执行,当下一个进程来的时候寄存器使用的就是下一个进程的数据了,与上一个进程无关!

四、总结

  • 进程的本质:进程 = 内核描述它的结构体 + 它自己的代码和数据,其中描述结构体才是操作系统管理的核心。
  • Linux 中的描述结构体:叫task_struct,它是PCB在 Linux 下的具体实现;标识符、状态、优先级、程序计数器、内存指针、记账信息等,本质上都是结构体里的一个个变量。
  • 进程切换的关键:CPU 寄存器硬件只有一套,进程靠时间片轮转轮流使用 CPU。
  • 上下文数据:切换时要保存的,就是进程运行时 CPU 寄存器里的临时数据;它属于进程私有,每个进程一份。
  • 上下文保存位置:老式内核保存在task_struct的TSS中;新式内核则保存在task_struct内嵌的thread_struct中,共同点是——都能通过task_struct找到。

一句话总结:先描述、再组织;描述靠task_struct,切换靠上下文,保存与恢复的关键,始终绕不开task_struct。

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

我的养老时光客服咨询AI流量赋能,我的养老时光科技重塑智能体验新标杆

近期,由湖南改变生物科技有限公司主办、本因内酵未徕品牌协办的“生物科技健康论坛暨AI赋能大健康产业启动会”在长沙市步步高福鹏喜来登酒店隆重举行。活动以“AI流量赋能实体破局——中小企业增长峰会”为主题,汇聚全国大健康行业专家、中小企业负责人、机构代表及…

作者头像 李华
网站建设 2026/9/30 7:12:15

B1系数和B2系数的详细对比表

以下是CCF官方发布的B1系数与B2系数的完整详细对比表,所有规则均来自NOI2026官方名额分配方案: 对比维度 B1系数(普及规模项) B2系数(整体成绩项) 官方定位 信奥普及规模激励指标 全省整体竞赛水平激励…

作者头像 李华
网站建设 2026/9/30 7:12:10

C++代码实现MATLAB中的pade函数功能

// pade.cpp // 用 C 实现 MATLAB 的 pade 函数&#xff08;幂级数版本 纯延时版本&#xff09; // 编译&#xff1a; g -stdc17 -O2 pade.cpp -o pade #include <vector> #include <complex> #include <iostream> #include <iomanip> #include <c…

作者头像 李华
网站建设 2026/9/30 7:10:29

【研发类-移动开发Skills】mobile-design 技能

(Mobile-First Touch-First Platform-Respectful) 技能概述 mobile-design 技能遵循"触摸优先、电池意识、平台尊重、离线能力"的理念。核心法则:移动不是小型桌面。操作规则:优先考虑约束,其次才是美学。 下载地址: https://github.com/sickn33/antigravity-aw…

作者头像 李华
网站建设 2026/9/30 7:09:46

MongoDB 实战指南:用 TaoToken 统一 Key 打通 CRUD 与聚合查询配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 7:09:43

嵌入式通用bootloader(一)

通用嵌入式boolloader,方便适配到不同平台&#xff0c;欢迎交流学习和合作。 https://gitee.com/maxwillmaxwill/boot 个人微信 buycoffee2026 1. 设备ROAM 分布 分区名称起始地址结束地址空间大小核心功能说明M_BootBaseAddress0x080000000x08003FFF16KB芯片上电启动入口&a…

作者头像 李华