当前位置: 首页 > news >正文

Linux--进程核心概念

        

目录

一、体系结构基础:冯·诺依曼模型

二、操作系统的定位与管理

三、进程的核心概念

1. 什么是进程?

2. 进程控制块(PCB)

3. 查看进程

4. 进程标识符(PID & PPID)

5. 创建进程(fork)

四、进程状态

僵尸进程(Zombie)

孤儿进程(Orphan)

五、进程优先级

六、环境变量

七、进程地址空间(虚拟内存)

八、进程调度


        进程是Linux系统的灵魂,是系统进行资源分配和调度的基本单位。深入理解进程是掌握Linux系统编程和性能优化的基石。本文将对Linux进程的核心概念进行总结。

一、体系结构基础:冯·诺依曼模型

理解进程前,必须先了解计算机的底层工作模型。

  • 核心思想:计算机由输入设备、存储器、运算器、控制器、输出设备五大部分组成。

  • 关键强调

    1. 这里的存储器特指内存

    2. CPU只能直接读写内存,无法直接访问外设。

    3. 所有外设的输入/输出数据都必须经过内存。

  • 以QQ聊天时数据流为例来理解一下冯诺依曼体系架构:

    • 输入:键盘敲击消息 → 输入设备 → 内存

    • 处理CPU 从内存读取数据并进行处理

    • 输出内存 → 输出设备 → 网卡发送 / 显示器显示

二、操作系统的定位与管理

        操作系统(OS)是计算机系统的“大管家”。

  • 定位:一款纯粹的管理软件,向下管理硬件,向上为用户程序提供执行环境。

  • 管理内容:进程管理、内存管理、文件管理、驱动管理。

  • 如何管理?——“先描述,再组织

    1. 描述:用struct结构体(如task_struct)将进程的属性信息描述起来。

    2. 组织:用链表等高效的数据结构将多个struct组织起来,方便进行增删改查。

  • 系统调用 vs 库函数:操作系统将部分管理功能以系统调用的接口形式暴露,功能基础但对于了解程度要求高。开发者对其封装后形成库函数(如C库),更方便使用。

三、进程的核心概念

1. 什么是进程?

  • 基本概念:程序的一个执行实例,正在执行的程序。

  • 内核观点:担当分配系统资源(CPU时间、内存)的实体

2. 进程控制块(PCB)

        进程的所有信息都被放在一个称为进程控制块(PCB)的数据结构中。在Linux中,PCB的具体实现是 task_struct

  • task_struct 内容分类

    • 标识符(PID):唯一标识进程的ID。

    • 状态:进程状态(运行、睡眠、停止、僵尸等)。

    • 优先级:相对于其他进程的优先级。

    • 程序计数器:即将执行的下一条指令的地址。

    • 内存指针:指向程序代码、数据及共享内存的指针。

    • 上下文数据:进程运行时CPU寄存器中的数据。

    • I/O状态信息:进程使用的I/O设备和文件列表。

    • 记账信息:处理器时间、时间限制等。

  • 组织方式:内核中所有task_struct通过链表的形式组织起来。

3. 查看进程

  • 命令行工具ps auxps axjtop

  • 文件系统/proc/[PID]/ 目录下包含了进程的详细信息。

4. 进程标识符(PID & PPID)

  • PID:当前进程的唯一ID。

  • PPID:父进程的ID。

  • 系统调用getpid() (获取自身PID), getppid() (获取父进程PID)

5. 创建进程(fork)

  • 系统调用fork()

  • 关键特性

    1. 调用一次,返回两次:在父进程中返回子进程的PID,在子进程中返回0(失败则返回-1)

    2. 写时拷贝:子进程共享父进程的代码段,数据段在初始时也共享。只有当任何一方尝试修改数据时,才会为该数据分配独立的物理空间。这是一种高效的内存管理策略

#include <stdio.h>
#include <unistd.h>int main() {pid_t id = fork();if (id < 0) {// fork失败perror("fork");return 1;} else if (id == 0) {// 子进程代码区printf("I am child, PID: %d\n", getpid());} else {// 父进程代码区printf("I am parent, PID: %d, Child PID: %d\n", getpid(), id);}sleep(1);return 0;
}
  • fork后,代码共享,数据看似共享

  • 只有在任何一方尝试修改数据时,操作系统才会真正复制一份数据给修改方。

+-----------------------+
|   父进程              |
|  +-----------------+  |
|  |  代码段         |  | (共享)
|  +-----------------+  |
|  |  数据段         |  |<----------+
|  |  g_val = 0      |  | (共享)    | 子进程尝试修改
|  +-----------------+  |               |     触发写时拷贝
|  |  堆、栈         |  |                |
|  +-----------------+  |               |
+-----------------------+              |
                                              |
+-----------------------+              |
|   子进程              |                |
|  +-----------------+  |               |
|  |  代码段         |  | (共享)     |
|  +-----------------+  |              |
|  |  数据段         |  | (共享)    |
|  |  g_val = 0      |  |-----------+
|  +-----------------+                |
|  |  堆、栈         |                 |
|  +-----------------+                |
+-----------------------+           |
                                           |
                    +---------------v----+
                    |  内核为新数据   |
                    |  分配物理页面   |
                    |  g_val = 100      |
                    +-------------------+

四、进程状态

Linux内核中定义的进程状态主要包括(在kernel源代码中定义):

  • R (TASK_RUNNING)运行或就绪态。进程正在CPU上运行或在运行队列中等待被调度。

  • S (TASK_INTERRUPTIBLE)可中断睡眠。进程在等待某个事件完成(如等待输入),可以被信号中断唤醒。

  • D (TASK_UNINTERRUPTIBLE)不可中断睡眠。进程通常在等待I/O操作结束,不可被信号中断。这是一种深层睡眠状态。

  • T (TASK_STOPPED)停止状态。进程被信号(如SIGSTOP)暂停运行,可通过信号(如SIGCONT)继续运行。

  • X (TASK_DEAD)死亡状态。进程结束运行,资源被回收。这是一个瞬时状态,用户无法捕捉。

  • Z (TASK_ZOMBIE)僵尸状态。这是一个非常重要且需要警惕的状态。

僵尸进程(Zombie)

  • 成因:子进程先于父进程退出,但父进程没有调用wait()waitpid()来读取子进程的退出状态信息。

  • 危害

    1. 维护退出状态的PCB无法被释放,导致内存泄漏

    2. 如果父进程创建大量子进程且从不回收,将耗尽系统的进程资源(PID)。

  • 如何避免:父进程需要通过wait系列系统调用来回收子进程资源。

孤儿进程(Orphan)

  • 成因:父进程先于子进程退出。

  • 处理:孤儿进程会被1号init进程(或systemd进程)领养,并由该进程负责其退出后的回收工作。这避免了僵尸进程的产生。

进程的一生与状态转换图
  • R状态是中心,是获得CPU的唯一入口。

  • SD都是等待,但S可被信号唤醒,D不行。

  • Zombie是子进程给父进程留的“遗嘱”,父进程不读(wait),遗嘱就一直留着,占着资源。

  • Orphan会被“福利院院长”init进程收养,保证其正常结束。

五、进程优先级

  • 基本概念:CPU资源分配的先后顺序。

  • 查看命令ps -l 主要关注 PRI 和 NI 列。

    • PRI (Priority):进程的优先级,值越小优先级越高,越容易被调度。

    • NI (Nice):进程优先级修正值。其关系为:PRI(new) = PRI(old) + NI

    • NI范围:-20 到 19。普通用户只能调高NI值(降低优先级)。

  • 调整NI值:使用top命令,按r后输入进程PID和新的nice值。

六、环境变量

  • 基本概念:用于指定操作系统运行环境参数的变量,具有全局属性

  • 常见环境变量

    • PATH:指定命令的搜索路径。echo $PATH

    • HOME:指定用户的主工作目录。echo $HOME

    • SHELL:指定当前使用的Shell。

  • 相关命令

    • env:查看所有环境变量。

    • export:设置一个新的环境变量。export MY_VAR=Hello

    • unset:清除一个环境变量。unset MY_VAR

  • 在程序中访问

    • 命令行参数main(int argc, char *argv[], char *env[])

    • 全局变量extern char **environ;

    • 系统调用getenv()(获取),setenv()(设置)。

#include <stdio.h>
#include <stdlib.h>
int main() {printf("PATH: %s\n", getenv("PATH"));return 0;
}

七、进程地址空间(虚拟内存)

        这是理解进程隔离和内存管理的核心。

  • 现象:在 fork 后的父子进程中,修改同一个全局变量,变量的地址值相同,但内容不同

  • 结论

    1. 我们在C/C++中看到的地址(&变量)都是虚拟地址/线性地址,而非物理地址。

    2. 操作系统负责将虚拟地址通过页表映射到物理地址

    3. 父子进程的虚拟地址相同,但被OS映射到了不同的物理页上,从而实现了进程间的独立性写时拷贝技术。

  • 意义

    1. 保护:一个进程的错误操作(如非法内存访问)不会影响其他进程。

    2. 简化:让每个进程都认为自己独占整个内存空间,简化了程序员的编程模型。

    3. 高效:配合“写时拷贝”等技术,可以高效地利用内存。

+------------------------+    +------------------------------+
|   进程A                  |      |   进程B                        |
| 虚拟地址空间        |      | 虚拟地址空间              |
|                              |      |                                      |
| 0x8049768: g_val |      | 0x8049768: g_val        |
| ...                           |      | ...                                 |
+-------------------------+    +------------------------------+
         ↓                                    ↓
    +---------+                      +---------+
    | 页表A   |                      | 页表B   |
    +---------+                      +---------+
         ↓                                   ↓
+-------------------------------------------------+
|             物理内存                                |
|   +-----------+    +---------------+             |
|   | 页面1    |     | 页面2         |               |
|   | g_val=0 |     | g_val=100 |               |
|   +-----------+    +----------------+            |
|                                                            |
+-------------------------------------------------+

  • 每个进程都有自己的虚拟地址空间,感觉自己是独占内存的。

  • 通过每个进程独有的页表,将相同的虚拟地址映射到不同的物理地址上。

  • 这就是进程独立性写时拷贝得以实现的底层基础。

八、进程调度

Linux 2.6内核采用了高效的 O(1)调度算法

  • 核心数据结构:每个CPU有一个运行队列(runqueue),包含活动队列(active) 和过期队列(expired)

  • 活动队列:存放时间片未用完的所有进程,按优先级(0-139)组织成140个队列。

  • 过期队列:存放时间片已耗尽的进程,结构同活动队列。

  • 调度过程

    1. 调度器从活动队列的优先级最高的非空队列中选取第一个进程运行。

    2. 使用bitmap位图来快速查找非空队列,时间复杂度为O(1)

    3. 当活动队列为空时,交换activeexpired两个指针的值,过期队列即刻变为新的活动队列。

  • 优点:调度过程非常高效,与系统中进程数量无关。

http://www.dtcms.com/a/346397.html

相关文章:

  • 基于SamGeo模型和地图客户端的实时图形边界提取
  • 把 AI 变成「会思考的路灯」——基于自学习能耗模型的智慧路灯杆
  • Open3d:点对点ICP配准,点对面ICP配准
  • 105.QML实现现代Neumorphism风格界面01-Button实现
  • 如何提升科研能力:先停止“无效工作”,开始“有效科研”
  • 第二节阶段WinFrom-5:文件操作
  • 车载诊断架构 --- EOL引起关于DTC检测开始条件的思考
  • Linux822 shell:expect 批量
  • 《C++起源与核心:版本演进+命名空间法》
  • 易基因:Nat Commun/IF15.7:多组学研究揭示UHRF2在原始生殖细胞DNA甲基化重编程中的抗性调控机制
  • 光耦合器:电子世界的 “光桥梁“
  • Opnecv详细介绍
  • 量子计算基础
  • C#_组合优于继承的实际应用
  • 音视频处理工作室:实时通信的媒体层设计
  • 容器操作案例
  • C语言——内存函数
  • TTS文字合成语音芯片的使用场景
  • No module named blake2b
  • GaussDB GaussDB 数据库架构师修炼(十八)SQL引擎(1)-SQL执行流程
  • ODDR双边沿数据输出
  • 1小时检测cAMP的武功秘籍
  • AI 绘画争议背后:版权归属、艺术原创性与技术美学的三方博弈
  • Linux系统安装llama-cpp并部署ERNIE-4.5-0.3B
  • Unity--判断一个点是否在扇形区域里面(点乘和叉乘的应用)
  • Day2--HOT100--283. 移动零,11. 盛最多水的容器,15. 三数之和
  • 94. 城市间货物运输 I, Bellman_ford 算法, Bellman_ford 队列优化算法
  • 【Android】 连接wifi时,强制应用使用流量
  • 反射【Reflect】
  • 深入浅出【最小生成树】:Prim与Kruskal算法详解