linux系统编程:初识进程

一、一切的基础:冯・诺依曼体系结构

1. 五大硬件模块

  1. 输入设备:键盘、鼠标、扫描仪等
  2. 输出设备:显示器、打印机
  3. 存储器(特指内存):数据中转核心
  4. 运算器:负责算术、逻辑运算
  5. 控制器:协调硬件执行指令 运算器 + 控制器合并为 CPU。

2. 核心铁律

  1. CPU只能直接读写内存,无法直接和外设交互;
  2. 外设输入 / 输出数据,必须经过内存中转;
  3. 所有硬件交互,内存是唯一中间介质。

3. 存储分层金字塔

速度从快到慢、容量从小到大、成本从高到低: 寄存器 > L1/L2/L3 高速缓存 > 内存 (主存) > 本地磁盘 > 远程网络存储

4. 真实场景:QQ 聊天数据流(理解硬件数据流)

场景 1:发送文字消息
  1. 键盘输入文字(输入设备)→ 数据写入内存;
  2. QQ 进程从内存读取文字数据,CPU 完成编码;
  3. 数据放回内存,网卡驱动从内存读取数据,通过网卡发送到服务器;
  4. 好友电脑网卡接收数据,写入对方内存;
  5. 对方 QQ 进程读取内存数据,CPU 解析文字,输出到显示器。
场景 2:发送文件
  1. 文件存放在本地磁盘,磁盘先把文件数据加载到内存;
  2. CPU 读取内存文件数据编码,放回内存;
  3. 网卡读取内存数据上传服务器;
  4. 好友设备网卡接收数据写入内存,再持久化写入本地磁盘。

二、操作系统:计算机的 "大管家"

1. OS 定义

  • 狭义 OS(内核 Kernel):核心四大模块 ------ 进程管理、内存管理、文件管理、驱动管理;
  • 广义 OS:内核 + shell 外壳、C 标准库、系统工具等应用软件。

2. OS 两大核心职责

  1. 向下管理硬件:封装硬件操作,统一调度所有软硬件资源;
  2. 向上服务应用:给程序提供稳定、安全的运行环境。

3. OS 管理万物的核心思想:先描述,再组织

类比学校管理:校长(OS)不直接管学生(硬件 / 进程),

  1. 描述:用结构体记录学生信息(struct 保存进程 / 硬件属性);
  2. 组织:用链表、红黑树把结构体串起来,统一管理。

4. 系统调用 & 库函数

  1. 系统调用:内核对外暴露的底层接口,直接操作内核,功能原始、使用复杂;
  2. 库函数:第三方 / 标准库对系统调用的封装(如 printf 封装 write),降低开发成本; 用户程序 → 库函数 → 系统调用 → 内核 → 硬件。

三、进程:操作系统调度的最小单位

3.1 进程到底是什么?

  1. 课本定义:程序的一次运行实例
  2. 内核定义:承担 CPU、内存、IO 资源分配的实体;
  3. 最简公式:进程 = PCB(task_struct) + 代码 + 数据 程序是磁盘上静态文件,进程是内存中动态运行的实体。

3.2 PCB:进程控制块(Linux 下 task_struct)

内核用task_struct结构体描述每一个进程,全部存放在内存,通过双向链表串联所有进程。

task_struct 核心成员
  1. 标识符:PID (进程 ID)、PPID (父进程 ID);
  2. 进程状态、退出码、信号;
  3. 优先级 nice 值;
  4. 程序计数器:下一条要执行的指令地址;
  5. 内存指针:代码段、堆、栈、共享内存映射;
  6. 上下文数据:CPU 寄存器临时数据,进程切换时保存恢复;
  7. IO 信息:打开的文件、占用外设;
  8. 记账信息:CPU 占用时长、调度统计。

3.3 查看进程的两种方式

  1. 文件系统:/proc 虚拟文件系统,/proc/PID 存放对应进程全部信息;
  2. 命令工具:ps auxps axjtop

3.4 获取进程 ID(系统调用)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("当前进程PID:%d\n", getpid());
    printf("父进程PPID:%d\n", getppid());
    return 0;
}

3.5 fork () 创建子进程

核心特性
  1. 一次调用,两个返回值:父进程返回子进程 PID,子进程返回 0,失败返回 - 1;
  2. 父子代码共享,数据采用写时拷贝:只读时共享物理内存,修改数据时复制独立内存;
  3. fork 后父子进程同时执行后续代码,配合 if 分流实现不同逻辑。
基础 demo
cpp 复制代码
#include <stdio.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);
    }
    sleep(10);
    return 0;
}

四、Linux 七大进程状态

内核源码定义 7 种任务状态:

标识 状态名 特点
R 运行态 正在 CPU 运行 / 在就绪队列等待调度
S 可中断睡眠 等待事件,可被信号唤醒
D 不可中断睡眠 等待 IO 完成,无法被信号打断
T 停止态 SIGSTOP 暂停,SIGCONT 恢复
t 追踪停止 gdb 调试时暂停
X 死亡态 进程彻底销毁,瞬时状态不可见
Z 僵尸态 子进程退出,父进程未回收 PCB

4.1 僵尸进程 Z

  1. 产生原因:子进程先退出,父进程没有调用 wait/waitpid 读取子进程退出信息;
  2. 危害 :task_struct(PCB)持续占用内存,造成内存泄漏,大量僵尸耗尽系统进程资源;
  3. 解决方案:父进程回收子进程、父进程退出变成孤儿进程由 systemd 回收。

4.2 孤儿进程

  1. 产生原因:父进程提前退出,子进程失去父进程;
  2. 处理机制:孤儿进程会被 1 号进程 systemd (init) 领养,systemd 自动回收,无内存泄漏风险。

五、进程优先级:CPU 资源分配规则

  1. PRI:进程优先级,数值越小,越优先调度;
  2. NI (nice 值) :优先级修正值,范围-20 ~ 19; 计算公式:新PRI = 原始PRI + nice
  • nice 为负数:优先级升高;正数:优先级降低。

操作命令

  1. top:按 r 修改运行中进程 nice 值;
  2. nice:启动程序时指定优先级;
  3. renice:修改正在运行进程的 nice; 系统调用:getpriority() / setpriority()

四个基础概念

  1. 竞争性:进程多、CPU 少,互相抢占资源,因此需要优先级;
  2. 独立性:多进程拥有独立资源,互不干扰;
  3. 并发:单 CPU 快速切换进程,一段时间内同时推进多个任务;
  4. 并行:多 CPU 核心,同时运行多个进程。

六、进程切换 & Linux2.6 O (1) 调度算法

6.1 上下文切换

CPU 寄存器只有一套,切换进程时:

  1. 保存当前进程寄存器数据(上下文)到 PCB;
  2. 加载下一个进程的上下文到寄存器;
  3. 恢复执行新进程。 每个进程切换都会消耗 CPU 资源,频繁切换会降低性能。

6.2 O (1) 调度(Linux2.6 经典调度器)

每个 CPU 独立一个 runqueue 运行队列,包含两个数组:

  1. active 活跃队列:时间片未耗尽的进程,140 个优先级队列(0-99 实时,100-139 普通);
  2. expired 过期队列:时间片耗尽的进程。
高效设计
  1. bitmap 位图标记队列是否为空,快速找到最高优先级进程,查找耗时固定;
  2. 活跃队列全部耗尽时,直接交换 active 和 expired 指针,无需迁移进程;
  3. 无论系统有多少进程,调度耗时恒定,称为 O (1) 调度。

七、环境变量:进程运行环境配置

7.1 常见环境变量

  1. PATH:命令搜索路径,执行程序时自动查找;
  2. HOME:用户家目录;
  3. SHELL:当前使用终端。

7.2 操作指令

  • echo $XXX:查看环境变量;
  • export:导出全局环境变量(可被子进程继承);
  • env:查看全部环境变量;
  • unset:删除环境变量;
  • set:查看本地变量 + 环境变量。

7.3 代码获取环境变量

  1. main 函数第三个参数 char *env[]
  2. 全局外部变量 extern char **environ
  3. 系统调用 getenv("PATH") 获取指定变量。

八、进程虚拟地址空间

8.1 32 位 Linux 进程内存布局(低地址→高地址)

  1. 代码段 (.text):只读,存放程序指令;
  2. 初始化数据段 (.data):已初始化全局 / 静态变量;
  3. 未初始化段 (.bss):未初始化全局变量,程序启动清零;
  4. 堆 (heap):malloc 动态分配,自低向高增长;
  5. 共享区:动态库、mmap 文件映射;
  6. 栈 (stack):局部变量、函数栈帧,自高向低增长;
  7. 命令行参数、环境变量;
  8. 内核空间(32 位占 1G,用户不可访问)。

8.2 虚拟地址 vs 物理地址

fork 后父子进程打印同一个全局变量,虚拟地址完全相同,但变量值可以不同。 原理:

  1. C 语言代码所有地址都是虚拟地址,用户无法直接操作物理内存;
  2. 操作系统通过页表 + MMU,将虚拟地址映射到真实物理内存;
  3. 写时拷贝:父子进程初始共享物理页,修改数据时分配新物理页,保证进程独立。

8.3 内核描述虚拟内存的结构体

  1. mm_struct:每个进程独有,记录整个虚拟地址空间所有段的起止地址;
  2. vm_area_struct:描述单个虚拟内存区域(代码段 / 堆 / 栈等),链表 + 红黑树双结构管理。

8.4 虚拟地址空间四大作用

  1. 内存隔离安全:进程不能访问其他进程 / 内核物理内存,防止越权篡改;
  2. 编译地址统一:每个进程独占完整虚拟空间,编译链接地址固定,无需关心物理内存位置;
  3. 内存利用率提升:延迟分配(申请虚拟地址不分配物理内存,访问时才分配)、内存交换;
  4. 模块解耦:进程管理、内存管理完全分离,降低内核复杂度。
相关推荐
Freak嵌入式2 小时前
版本混乱 / 依赖缺失?uPyPi:MicroPython 版 PyPI,彻底解决库管理混乱
linux·服务器·数据库·单片机·嵌入式硬件·性能优化·依赖倒置原则
云飞云共享云桌面2 小时前
SolidWorks—天津智能装备工厂1台高配主机共享给10个研发用
运维·服务器·自动化·汽车·制造
苍狗T2 小时前
LVS相关知识总结
linux·运维·服务器·lvs
lightqjx3 小时前
【Linux系统】基础 IO
linux·c语言·重定向·缓冲区·文件io·文件的系统调用
我星期八休息3 小时前
扩展— TCP 全连接队列与 tcpdump 抓包
linux·服务器·开发语言·前端·网络·tcp/ip·tcpdump
各类产品分享3 小时前
什么是AR智慧运维系统,主要能解决什么问题
运维·ar·ar智慧运维解决方案
RisunJan3 小时前
Linux命令-skill(发送信号给进程)
linux·运维·服务器
潘正翔3 小时前
k8s基础_kubeadm搭建k8s集群
linux·运维·docker·云原生·容器·kubernetes
changjiahong4 小时前
lvs总结
服务器·数据库·lvs