【Linux操作系统15】深入理解Linux进程概念:从理论到实践

深入理解Linux进程概念:从理论到实践



🎬 Doro在努力个人主页
🔥 个人专栏 : 《MySQL数据库基础语法》《数据结构》

⛺️严于律己,宽以待人


文章目录

  • 深入理解Linux进程概念:从理论到实践
    • 前言
    • 一、从冯诺依曼体系说起
    • 二、操作系统与"管理"的艺术
      • [2.1 操作系统的核心定位](#2.1 操作系统的核心定位)
      • [2.2 "先描述,再组织"的管理哲学](#2.2 "先描述,再组织"的管理哲学)
    • 三、进程的本质是什么
      • [3.1 课本概念的局限性](#3.1 课本概念的局限性)
      • [3.2 进程的准确定义](#3.2 进程的准确定义)
      • [3.3 为什么要引入进程概念](#3.3 为什么要引入进程概念)
    • 四、深入理解PCB(task_struct)
      • [4.1 PCB是什么](#4.1 PCB是什么)
      • [4.2 PCB中的核心属性](#4.2 PCB中的核心属性)
        • [4.2.1 标识符(PID)](#4.2.1 标识符(PID))
        • [4.2.2 进程状态](#4.2.2 进程状态)
        • [4.2.3 优先级](#4.2.3 优先级)
        • [4.2.4 程序计数器](#4.2.4 程序计数器)
        • [4.2.5 内存指针](#4.2.5 内存指针)
        • [4.2.6 上下文数据](#4.2.6 上下文数据)
        • [4.2.7 I/O状态信息](#4.2.7 I/O状态信息)
        • [4.2.8 记账信息](#4.2.8 记账信息)
    • 五、操作系统如何管理进程
      • [5.1 从描述到组织](#5.1 从描述到组织)
      • [5.2 进程管理的本质](#5.2 进程管理的本质)
    • 六、如何查看系统中的进程
      • [6.1 使用 /proc 文件系统](#6.1 使用 /proc 文件系统)
      • [6.2 使用 ps 命令](#6.2 使用 ps 命令)
      • [6.3 使用 top 命令](#6.3 使用 top 命令)
    • 七、进程的父子关系
      • [7.1 PPID与父子进程](#7.1 PPID与父子进程)
      • [7.2 命令行启动的进程](#7.2 命令行启动的进程)
      • [7.3 使用 fork 创建子进程](#7.3 使用 fork 创建子进程)
    • 八、当前工作目录(CWD)
      • [8.1 什么是当前工作目录](#8.1 什么是当前工作目录)
      • [8.2 查看和修改CWD](#8.2 查看和修改CWD)
      • [8.3 CWD的实际意义](#8.3 CWD的实际意义)
    • 九、总结与思考
      • [9.1 核心概念回顾](#9.1 核心概念回顾)
      • [9.2 深入思考](#9.2 深入思考)
      • [9.3 学习建议](#9.3 学习建议)

前言

各位同学大家好!今天我们来聊一聊操作系统中最核心的概念之一------进程。作为Linux系统编程的入门课程,理解进程概念对于我们后续学习进程调度、进程通信、多线程编程等高级话题至关重要。

很多同学在学习操作系统时,对进程的理解往往停留在"运行中的程序"这样一个模糊的定义上。但实际上,进程这个概念背后蕴含着操作系统设计的核心思想。今天,我将带大家从是什么为什么怎么办三个维度,深入剖析进程的本质。


一、从冯诺依曼体系说起

在正式介绍进程之前,我们有必要回顾一下计算机的基本工作原理。我们日常使用的计算机,无论是笔记本还是服务器,基本上都遵循冯诺依曼体系结构。这个体系结构将计算机划分为几个核心组成部分:

  • 输入单元:键盘、鼠标、扫描仪等设备,负责接收外部数据
  • 中央处理器(CPU):包含运算器和控制器,是计算机的大脑
  • 存储器:也就是内存,用于临时存储数据和程序
  • 输出单元:显示器、打印机等,负责将处理结果呈现给用户

这里有一个非常重要的原则需要大家牢记:在冯诺依曼体系中,CPU只能直接访问内存,不能直接访问外设;而外设要进行数据交换,也只能通过内存进行。换句话说,所有设备都只能直接和内存打交道。

这个设计决定了我们程序运行的基本方式:程序必须先加载到内存中,CPU才能执行它。而这个"加载到内存并运行的程序",就是我们今天要讨论的主角------进程


二、操作系统与"管理"的艺术

2.1 操作系统的核心定位

操作系统是一款纯粹的"搞管理"的软件。它对下管理着计算机的所有硬件资源,对上为应用程序提供良好的运行环境。没有操作系统,我们就无法使用现代计算机。

那么,操作系统是如何进行管理的呢?这里就涉及到一个核心思想------先描述,再组织

2.2 "先描述,再组织"的管理哲学

这个世界上对任何事物的管理,本质上都是对信息的处理。举个例子,你的父母管理你,并不是直接针对你这个人,而是基于他们采集到的关于你的信息------你是不是饿了、是不是冷了、学习成绩怎么样------然后做出相应的决策。

计算机管理也是如此。操作系统要管理进程,首先要把进程的各种属性描述 出来,然后用某种数据结构把这些描述信息组织起来。这样,对进程的管理就转化成了对数据结构的增删查改操作。

在C语言中,我们用结构体 来描述对象的属性;而在操作系统内核中,描述进程的结构体叫做PCB(Process Control Block,进程控制块)


三、进程的本质是什么

3.1 课本概念的局限性

很多操作系统教材会给出一个简单的定义:"进程是程序的一个执行实例",或者"进程是加载到内存中的程序"。这些定义虽然没错,但过于表面,没有触及进程的本质。

让我用一个类比来说明这个问题:

假设你考上了大学,你人到了学校,但学校并不认识你,因为你没有在学校的学籍管理系统中注册。按照"人在学校就是学生"的逻辑,任何跑到学校的人都可以自称学生,这显然是不对的。

真正决定你是哪个学校学生的,不是你的肉身,而是你在学籍管理系统中的档案信息。

进程也是如此。程序加载到内存只是第一步,操作系统还必须在内核中创建一个描述这个程序的数据结构,这个程序才能成为一个真正的进程。

3.2 进程的准确定义

基于上面的分析,我们可以给出一个更加准确的定义:

进程 = 内核数据结构(PCB)+ 程序的代码和数据

在Linux系统中,描述进程的内核数据结构叫做 task_struct。每一个进程都有一个对应的 task_struct 对象,其中包含了描述该进程的所有属性信息。

3.3 为什么要引入进程概念

可能有同学会问:为什么操作系统不直接运行程序,非要搞个进程的概念出来?

答案很简单:进程代表的是用户的意志

当你想听音乐时,你打开了网易云音乐;当你想写代码时,你打开了VS Code。这些软件在操作系统看来,都是用户想要完成某项任务的请求。操作系统通过创建进程来响应用户的请求,让CPU调度执行相应的程序,从而满足用户的需求。

如果没有进程这个概念,操作系统就无法区分不同用户的不同请求,也无法合理地分配CPU资源。可以说,进程是用户与操作系统之间的桥梁


四、深入理解PCB(task_struct)

4.1 PCB是什么

PCB(Process Control Block,进程控制块)是操作系统中用于描述进程属性的数据结构。在Linux内核中,PCB的具体实现是 task_struct 结构体。

这个结构体非常庞大,包含了进程运行所需的所有信息。我们可以打开Linux内核源码(以早期版本为例),看到 task_struct 的定义:

c 复制代码
struct task_struct {
    // 进程状态
    volatile long state;
    
    // 进程标识符
    pid_t pid;
    pid_t tgid;
    
    // 进程优先级
    int prio, static_prio, normal_prio;
    unsigned int rt_priority;
    
    // 进程关系
    struct task_struct *real_parent;
    struct task_struct *parent;
    struct list_head children;
    struct list_head sibling;
    
    // 内存管理
    struct mm_struct *mm;
    struct mm_struct *active_mm;
    
    // 文件系统
    struct fs_struct *fs;
    struct files_struct *files;
    
    // 信号处理
    struct signal_struct *signal;
    struct sighand_struct *sighand;
    
    // 时间统计
    cputime_t utime, stime;
    
    // ... 还有很多其他字段
};

4.2 PCB中的核心属性

虽然 task_struct 包含的字段非常多,但作为初学者,我们需要重点关注以下几类属性:

4.2.1 标识符(PID)

每个进程都有一个唯一的标识符,称为 PID(Process ID)。就像每个学生都有学号一样,PID是操作系统区分不同进程的依据。

在Linux中,我们可以通过 getpid() 系统调用来获取当前进程的PID:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = getpid();
    printf("当前进程的PID是: %d\n", pid);
    return 0;
}
4.2.2 进程状态

进程在生命周期中会经历不同的状态,比如运行态、就绪态、阻塞态等。PCB中的 state 字段记录了进程当前的状态,操作系统根据这个状态来决定如何调度进程。

4.2.3 优先级

在多任务系统中,多个进程会竞争CPU资源。优先级决定了进程被调度的先后顺序。PCB中的优先级字段让操作系统能够实现更加合理的调度策略。

4.2.4 程序计数器

程序计数器记录了进程下一条要执行的指令的地址。这个字段对于进程切换至关重要------当进程被暂停执行时,程序计数器的值会被保存;当进程恢复执行时,操作系统会从这个地址继续执行。

4.2.5 内存指针

内存指针指向进程代码和数据在内存中的位置。通过内存指针,操作系统可以找到进程的实际执行内容。

4.2.6 上下文数据

当进程被切换出去时,CPU中的寄存器值等临时数据需要被保存下来,以便进程下次恢复时能够继续正确执行。这些保存的数据称为上下文数据

4.2.7 I/O状态信息

进程可能打开了一些文件或设备,I/O状态信息记录了这些资源的占用情况。

4.2.8 记账信息

记账信息记录了进程使用CPU的时间、创建时间等统计信息,用于系统监控和资源管理。


五、操作系统如何管理进程

5.1 从描述到组织

我们说过,操作系统管理进程的核心思想是"先描述,再组织"。现在我们已经知道了PCB是如何描述进程的,那么操作系统是如何组织这些PCB的呢?

答案是:链表

在Linux内核中,所有的 task_struct 对象被组织成一个双链表。每个PCB节点都包含前驱指针和后继指针,将它们连接在一起。

5.2 进程管理的本质

既然所有进程的PCB都被组织在链表中,那么对进程的管理就转化成了对链表的操作:

  • 创建进程:在链表中插入一个新的PCB节点
  • 终止进程:从链表中删除对应的PCB节点,并释放资源
  • 查找进程:遍历链表,根据PID等属性找到目标进程
  • 调度进程:从链表中选择一个合适的进程,将CPU分配给它执行

这种设计的精妙之处在于,操作系统对进程的所有操作,都不需要直接操作进程的代码和数据,只需要操作PCB即可。PCB中的内存指针字段可以帮助操作系统在需要时找到进程的代码和数据。


六、如何查看系统中的进程

6.1 使用 /proc 文件系统

Linux系统提供了一个特殊的文件系统 /proc,它以文件的形式暴露了内核中的各种信息,包括进程信息。

/proc 目录下,你会看到许多以数字命名的子目录:

bash 复制代码
$ ls /proc
1    10   100  101  102  ...  999  ...

这些数字就是进程的PID。如果你想查看PID为1234的进程信息,只需要进入 /proc/1234 目录:

bash 复制代码
$ ls /proc/1234
attr       clear_refs  cpuset   exe      loginuid  mounts      oom_adj      ...
cmdline    comm        cwd      environ  maps      mountstats  oom_score    ...

其中一些重要的文件包括:

  • exe:指向进程对应的可执行文件
  • cwd:指向进程的当前工作目录
  • cmdline:进程的启动命令
  • environ:进程的环境变量

6.2 使用 ps 命令

ps 命令是查看进程信息最常用的工具。常用的选项组合包括:

bash 复制代码
# 查看当前用户的所有进程
$ ps aux

# 查看所有进程的详细信息
$ ps axj

# 查找特定进程
$ ps aux | grep myprogram

ps 命令的输出包含很多字段,其中我们最关心的是:

  • PID:进程标识符
  • PPID:父进程标识符
  • STAT:进程状态
  • COMMAND:启动进程的命令

6.3 使用 top 命令

如果你想实时监控系统中的进程活动,可以使用 top 命令:

bash 复制代码
$ top

top 命令会动态显示系统中资源占用最高的进程,以及CPU、内存等系统资源的使用情况。


七、进程的父子关系

7.1 PPID与父子进程

每个进程除了有自己的PID外,还会记录其父进程的PID,称为 PPID(Parent Process ID)

在Linux中,我们可以通过 getppid() 系统调用来获取父进程的PID:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = getpid();
    pid_t ppid = getppid();
    printf("当前进程的PID是: %d\n", pid);
    printf("父进程的PID是: %d\n", ppid);
    return 0;
}

7.2 命令行启动的进程

当你在命令行中启动一个程序时,这个进程的父进程通常是 bash(命令行解释器)。

bash 复制代码
$ ./myprogram

在这个例子中,myprogram 进程的父进程就是当前运行的 bash 进程。

bash 本身也是一个进程,它的内部有一个死循环,不断读取用户输入的命令,然后创建子进程来执行这些命令。这种设计的好处是,即使子进程出现问题(比如崩溃),也不会影响到 bash 本身,用户可以继续使用命令行。

7.3 使用 fork 创建子进程

在程序中,我们可以使用 fork() 系统调用来创建一个新的子进程:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        // fork失败
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("我是子进程,PID=%d,父进程PID=%d\n", getpid(), getppid());
    } else {
        // 父进程
        printf("我是父进程,PID=%d,子进程PID=%d\n", getpid(), pid);
    }
    
    return 0;
}

fork() 函数的特点是一次调用,两次返回

  • 在父进程中,fork() 返回子进程的PID(一个正数)
  • 在子进程中,fork() 返回0
  • 如果出错,fork() 返回-1

通过这种方式,父子进程可以执行不同的代码逻辑,实现并行处理。


八、当前工作目录(CWD)

8.1 什么是当前工作目录

在C语言中,当我们使用 fopen() 打开一个不指定路径的文件时,文件会被创建在"当前目录"下。那么这个"当前目录"到底是什么?

答案是:当前工作目录(Current Working Directory,CWD)是进程的一个属性,记录在进程的PCB中

当进程启动时,操作系统会把进程启动时所在的目录路径保存到PCB的CWD字段中。之后,当进程进行文件操作时,如果没有指定绝对路径,操作系统就会使用CWD作为默认路径。

8.2 查看和修改CWD

我们可以通过 /proc 文件系统查看进程的CWD:

bash 复制代码
$ ls -l /proc/1234/cwd
lrwxrwxrwx 1 user user 0 Feb 23 10:00 /proc/1234/cwd -> /home/user/myproject

在程序中,我们可以使用 getcwd() 函数获取当前工作目录,使用 chdir() 函数修改当前工作目录:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    char cwd[128];
    
    // 获取当前工作目录
    getcwd(cwd, sizeof(cwd));
    printf("修改前的工作目录: %s\n", cwd);
    
    // 修改工作目录
    chdir("/tmp");
    
    // 再次获取
    getcwd(cwd, sizeof(cwd));
    printf("修改后的工作目录: %s\n", cwd);
    
    return 0;
}

8.3 CWD的实际意义

理解CWD的概念对于理解文件操作非常重要。当你在程序中使用相对路径时,实际上是在使用CWD作为基准路径。这也是为什么同一个程序在不同的目录下启动,可能会产生不同的文件操作结果。


九、总结与思考

9.1 核心概念回顾

通过今天的学习,我们需要掌握以下核心概念:

  1. 进程的本质:进程 = PCB + 代码和数据
  2. PCB的作用:描述进程的所有属性,是操作系统管理进程的依据
  3. 进程管理的方式:通过链表等数据结构组织PCB,实现对进程的高效管理
  4. 进程的标识:PID唯一标识一个进程,PPID记录父子关系
  5. 进程的动态性:进程可以被创建、调度、切换、终止

9.2 深入思考

在学习进程概念时,有几个问题值得大家深入思考:

  1. 为什么操作系统不直接管理程序的代码和数据,而要通过PCB来间接管理?
  2. 进程切换时,除了保存程序计数器,还需要保存哪些信息?
  3. 如果进程的代码和数据占用了大量内存,PCB中如何高效地记录这些信息?

这些问题将引导我们进入更加深入的进程管理话题,包括进程调度算法、虚拟内存、进程同步等内容。

9.3 学习建议

进程是操作系统中最核心的概念之一,理解进程的本质对于后续学习至关重要。建议大家在学习过程中:

  1. 多动手实践:编写程序创建进程,观察进程的行为
  2. 多看系统信息 :使用 /proc 文件系统和 ps 命令观察系统中的进程
  3. 多思考原理:不要只记住概念,要理解背后的设计思想

相关推荐
城东米粉儿1 小时前
Android Lifecycle、LifecycleOwner、ViewLifecycleOwner、LifecycleScope、ViewModelScop
android
余瑜鱼鱼鱼1 小时前
NAT机制总结
运维·服务器·网络
暴力求解2 小时前
Linux--重定向
linux·运维·服务器
LongQ30ZZ2 小时前
博客系统测试报告
运维·服务器
bai_lan_ya2 小时前
makefile通用解析
java·运维·数据库
m0_528749002 小时前
sql基础查询
android·数据库·sql
希望之晨2 小时前
c++ 11 学习 函数模板
linux·开发语言·c++
好好学习天天向上~~2 小时前
13_Linux_学习总结_进程终止
linux·学习
安卓机器2 小时前
安卓玩机自做小工具------用于ROM修改 解打包boot.img修改小工具
android