一、底层:冯诺依曼体系与操作系统

1.冯诺依曼体系结构

我们日常使用的电脑、服务器,几乎都遵循冯诺依曼体系,核心规则只有一句话:所有设备只能直接和内存打交道

核心组件五大部件输入设备:键盘、鼠标、扫描仪等 。

  • 输出设备:显示器、打印机等 。

  • 存储器指的就是内存

  • 运算器 + 控制器:共同构成了 中央处理器(CPU)

至关重要的硬件规律(木桶原理体系):不考虑缓存情况下,CPU 能且只能对内存进行读写,绝对不能直接访问外设 ;外设的数据要输入或输出,也只能先写入内存或从内存读取

数据流向实例(以发送 QQ 消息为例):键盘输入信息(外设) -> 内存 -> CPU处理加密和打包 -> 内存 -> 网卡(外设输出)

2.操作系统(OS)

OS 的概念:包含内核(进程、内存、文件、驱动管理)及其他程序(如库函数、Shell 外壳)的集合 。

核心作用:

  • 操作系统帮助用户管理好下面的软硬件资源
  • 为用户提供一个良好(稳定、高效、安全)的运行环境

操作系统的管理逻辑:先描述(用结构体),再组织(用链表 / 数据结构),进程管理也遵循这个核心思路。

二、进程本质:程序的执行实例

1.什么是进程?

  • 课本定义:程序的一个执行实例、正在运行的程序
  • 内核视角:系统分配 CPU 、内存资源的基本实体

程序和进程核心区别:

  • 程序:只是一堆死代码,作为文件静静地存储在磁盘中
  • 进程:程序被加载到内存中,并由操作系统(OS)为其创建了对应的管理结构。 "进程 =  内核数据结构(PCB) + 该程序的代码和数据"

2.进程的灵魂:PCB(进程控制块)

操作系统管理进程,靠的是PCB(进程控制块),它是进程的属性集合

PCB:进程信息被放在一个叫做进程控制块(Process Control Block) 的数据结构中,它是进程属性的集合

在 Linux 中,PCB 的具体实现是task_struct结构体,会被加载到内存中,包含进程所有关键信息:

  • 标识符:PID(进程唯一 ID)、PPID(父进程 ID)
  • 状态:运行、睡眠、停止、僵尸等
  • 优先级:CPU 资源分配的先后顺序
  • 程序计数器:下一条待执行指令的地址
  • 内存指针:指向进程代码、数据的内存地址
  • 上下文数据:CPU 寄存器中的进程执行现场
  • I/O 状态、记账信息等

Linux 内核通过链表组织所有 task_struct,实现进程的统一管理。

当操作系统需要调度进程、杀掉进程胡哦这更改优先级时,本质上是对这个双向链表进行增、删、查、改的操作。

3. 进程的创建:fork 系统调用

fork是 Linux 创建子进程的核心系统调用,核心特性:

  • 调用一次,返回两个值:子进程返回0,父进程返回子进程 PID
  • 父子进程代码共享,数据私有(写时拷贝机制)

代码控制流(分流):fork() 之后通常需要使用 if - else 对其返回值进行分支处理

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    pid_t ret = fork();
    if (ret < 0) {
        perror("fork");
        return 1;
    } else if (ret == 0) {
        // 子进程逻辑
        printf("子进程 PID:%d\n", getpid());
    } else {
        // 父进程逻辑
        printf("父进程 PID:%d,子进程 PID:%d\n", getpid(), ret);
    }
    return 0;
}

三、进程状态

1.在内核源码(task_state_arry)中定义了一下状态:

  1. R(运行):进程在运行队列中,不一定正在执行
  2. S(睡眠):可中断睡眠,等待事件完成,能被信号唤醒
  3. D(磁盘睡眠):不可中断睡眠,等待 I/O 结束,不能被信号杀死
  4. T(停止):收到 SIGSTOP 信号暂停,SIGCONT 可恢复
  5. Z(僵尸):进程退出但父进程未回收,残留 PCB
  6. t(追踪停止):被调试工具暂停
  7. X(死亡):瞬时状态,无法查看

2.僵尸进程(Z):系统的[内存垃圾]

  • 产生原因:子进程退出,父进程未调用 wait () 读取退出状态,子进程 PCB 残留内存
  • 危害:PCB 占用内存,大量僵尸进程会导致内存泄漏
  • 避免方式:父进程主动回收子进程资源

3.孤儿进程

  • 产生原因:父进程先退出,子进程后执行
  • 处理机制:被1 号 init 进程领养,由 init 进程负责回收

4.查看进程的两种常用方式

  • 文件系统级查看:通过系统自带的 /proc 动态目录。 例如,通过查看 /proc/进程的PID 文件夹,就能实时看到该进程的内存映像、可执行文件软链接等信息。
  • 用户级工具查看:使用 ps aux 或 ps axj 命令。

四、进程优先级与调度基础

1.优先级核心概念

基本概念:CPU资源分配的先后顺序。优先级(数值小)的有优先执行权。

查看优先级指标 (ps -l)

  • PRI(Priority):进程可悲执行的优先级,值越小优先级越高。
  • NI(Nice):nice值,是PRI的修正数值。

计算公式:PRI(new) = PRI(old) + nice

  • nice的取值范围是 -20 ~ 19 (共40个级别)。
  • nice值为负,PRI变小,优先级变高;调整优先级在Linux下实质就是调整nice值
  • nice值不是优先级,它只是影响PRI的修正数据

2.进程的特性

  • 竞争性:进程多、CPU少,因此进程间具有竞争属性,需要依靠优先级更合理地竞争资源。
  • 独立性:多进程运行期间互不干扰,独享资源。
  • 并行(Parallelism):多个进程在多个CPU下,在同一时刻进行运行。
  • 并发(Concurrency):多个进程在单个CPU下采用进程间切换(时间片轮转)的方式,在一段时间内让多个进程都得以推送。

五、环境配置:环境变量

基本概念:操作系统中用来指定运行环境的一些参数(通常具有全局特性,可被子进程继承)。

1.常见的环境变量

  • PATH:指定命令的搜索路径。
  • HOME:指定用户的主工作目录(即登录时的默认路径)。
  • SHELL:当前使用的Shell终端壳程序(通常是 /bin/bash)。

2.操作指令

  • echo $NAME:查看某个环境变量。
  • export:设置一个新的环境变量(具备全局属性)。
  • env:显示所有的环境变量。

3.代码获取环境变量

  • 通过 main 函数的第三个参数
  • 通过第三方全局全局变量:extern char** environ。
  • 通过系统/库调用函数:getenv("ENV_NAME")  (最常用)
// getenv获取指定环境变量
#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("PATH:%s\n", getenv("PATH"));
    return 0;
}

六、进程地址空间

语言层面(C/C++)我们能看到的地址,全部绝对不是物理地址,而是虚拟地址(线性地址) !物理地址由 OS 统一管理,对用户不可见 。

1.Linux虚拟地址经典演练代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

// 定义全局变量
int g_val = 10;

int main()
{
    pid_t id = fork(); // 创建子进程
    if (id < 0)
    {
        perror("fork failed");
        return 1;
    }
    // 子进程
    else if (id == 0)
    {
        // 子进程修改全局变量
        g_val = 100;
        printf("子进程: g_val = %d, &g_val = %p\n", g_val, &g_val);
    }
    // 父进程
    else
    {
        // 父进程休眠1秒,保证子进程先执行修改
        sleep(1);
        printf("父进程: g_val = %d, &g_val = %p\n", g_val, &g_val);
    }

    return 0;
}

运行结果: 父子进程输出的 g_val 变量地址完全一样,但是打印出的内容却不同

  • 子进程: g_val = 100, &g_val = 0x404048
  • 父进程: g_val = 10, &g_val = 0x404048

2.进程地址空间模型

  • 每一个进程在被创建时,OS 都会为其分配一个虚拟的“进程地址空间”(在 32 位平台下大小为 4GB)。

  • 映射机制:OS 必须负责将虚拟地址通过页表(Page Table)转化映射成实际的物理地址

  • 对现象的解释:父子进程修改变量时发生了写时拷贝。它们的虚拟地址相同,但经页表映射后,被重新分配到了不同的物理地址上,从而实现了数据的解耦与独立性 。

Linux 2.6 内核  O(1) 调度算法架构

Linux 2.6 内核引入的 O (1) 调度算法,是经典的进程调度方案,核心亮点:无论系统有多少进程,调度选程的时间复杂度恒为常数 O (1),不会因进程变多而变慢。

  1. 双队列分工每个 CPU 对应一个运行队列 runqueue,内部有两个优先级数组:
  • 活跃队列(active):存放时间片未用完的就绪进程
  • 过期队列(expired):存放时间片耗尽的进程
  1. 优先级分组140 个优先级队列(0–99 实时进程,100–139 普通进程),同优先级进程按 FIFO 排队。

  2. 位图快速查找bitmap 标记哪些优先级队列非空,不用遍历全队列,一步找到最高优先级进程,这是 O (1) 的关键。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐