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

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.xdnf.cn/news/18610.html

相关文章:

  • 论文精读(三)|智能合约漏洞检测技术综述
  • (纯新手教学)计算机视觉(opencv)实战七——边缘检测Sobel 算子(cv2.Sobel())详解
  • 递归思路:从DFS到二叉树直径的实战(通俗易懂)
  • 如何将照片从iPhone传输到Mac?
  • Spring Start Here 读书笔记:第10章 Implementing REST services
  • 疏老师-python训练营-Day53 对抗生成网络
  • 常用 CMake 内置变量合集与说明
  • Huggingface入门实践 Audio-NLP 语音-文字模型调用(一)
  • 发版混乱怎么规范
  • SSM从入门到实战:2.5 SQL映射文件与动态SQL
  • Swift 项目结构详解:构建可维护的大型应用
  • 第四章:大模型(LLM)】07.Prompt工程-(8)任务分解
  • Unreal Engine UObject
  • 龙虎榜——20250822
  • 如何使用命令行将DOCX文档转换为PDF格式?
  • 螺旋槽曲面方程的数学建模与偏导数求解
  • map和set的使⽤
  • GDSFactory环境配置(PyCharm+Git+KLayout)
  • 企业级管理平台横向越权问题及防护
  • Elasticsearch高能指南
  • SYBASE ASE、Oracle、MySQL/MariaDB、SQL Server及PostgreSQL在邮件/短信发送功能上的全面横向对比报告
  • Simulink不连续模块库(Hit Crossing/PWM/Rate Limiter/Rate Limiter Dynamic)
  • 【Day01】堆与字符串处理算法详解
  • uniapp轮播 轮播图内有定位样式
  • Oracle DB 10g 升级至 11.2.0.4报错-ORA-00132
  • Python办公之Excel(openpyxl)、PPT(python-pptx)、Word(python-docx)
  • NVM-Windows 命令大全
  • 量化交易 - 上证50利用动态PE增强模型 - python
  • React 学习笔记1 组件、State
  • 线性回归学习笔记