系统核心解析:深入操作系统内部机制——进程管理与控制指南(一)【进程/PCB】


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥
✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨


前面我们已经学习了系统的前置知识【冯诺依曼模型/操作系统】,这一篇博客我们开始系统的核心解析------进程!准备好了吗~我们发车去探索进程的奥秘啦~🚗🚗🚗🚗🚗🚗

目录

进程概念😍

通俗概念:执行中的程序😝

内核观点:系统资源的分配实体😄

核心本质:PCB(进程控制块)😜

进程排队与调度😎

task_struct😘

查看进程😁

创建进程😀


进程概念😍

通俗概念:执行中的程序😝

一般教材里面对于进程是这样定义的------"运行起来的程序,加载到内存的程序 "; "程序的一个执行实例,正在执行的程序"~

这是最直观的理解。一个安静的、躺在磁盘上的可执行文件(如 a.out)只是一个程序(Program)。而当你双击运行它,系统将其代码和数据加载到内存并开始执行时,它就成为了一个进程(Process)。

关键区别程序静态的(就像一本食谱),而进程动态的(就像你按照食谱做饭的整个过程)。

内核观点:系统资源的分配实体😄

从内核来看,进程是"担当分配系统资源(CPU时间,内存)的实体。"

这是操作系统的视角,操作系统对下的主要工作是管理软硬件资源,而进程就是操作系统进行资源分配的基本单位。每个进程在运行时,都需要被分配内存来存放其代码和数据,需要占用CPU时间来执行指令,操作系统就像一个经理,而进程就是需要资源来完成工作的员工。

我们上一篇博客提到操作系统进行管理的时候是先描述再组织~那么对于大量进程的管理也是一样的~

核心本质:PCB(进程控制块)😜

操作系统如何管理这么多复杂的进程?答案就是:"先描述,再组织"。

先描述(Describe) : 操作系统为每一个进程创建一个名为PCB【进程控制块】(Process Control Block) 的数据结构(在Linux中具体叫 task_struct)。这个结构体就像是进程的"简历"或"身份证",包含了进程的所有属性信息,例如:

进程ID(PID): 唯一标识一个进程的数字。

进程状态(运行、睡眠、停止等)。

程序计数器(PC): 指向下一条要执行的指令地址。

内存指针: 指向代码和数据的地址。

文件描述符表: 记录该进程打开的文件。

优先级、上下文数据等。
再组织(Organize) : 操作系统将所有进程的PCB(task_struct)通过链表等数据结构组织起来。这样,对进程的管理(如创建、销毁、调度、切换)就变成了对这条PCB链表的增删改查操作。

因此,一个进程的实体 = 对应的代码和数据 + 对应的PCB数据结构

进程排队与调度😎

由前面的概念我们可以知道,进程排队本质其实就是让对应的PCB节点进行排队。CPU的数量是远少于进程的数量,因此进程需要排队等待CPU资源。进程排队不是整个程序本身去排队,而是它的PCB去排队。操作系统调度器只关心PCB链表,它从运行队列中选中一个PCB,也就找到了这个进程的所有信息,从而能够恢复并执行它~

总结进程是正在执行的程序,它是操作系统进行资源分配的基本单位, 一个进程的实体 = 对应的代码和数据 + 对应的PCB数据结构**。操作系统通过一个叫做PCB(Linux中为task_struct)的数据结构来描述进程,并通过管理所有PCB组成的链表来高效地组织和管理所有进程。**

task_struct😘

前面我们提到了操作系统为每一个进程创建一个名为PCB【进程控制块】(Process Control Block) 的数据结构(在Linux中具体叫 task_struct),包含了进程的所有属性信息。那么我们来看看Linux中task_struct具体会包含些什么呢?

标示符(PID)

作用:每个进程独一无二的身份证号。

说明:通过这个ID,系统内核和用户(通过命令)才能精确地找到并操作某一个特定的进程。
状态

作用:描述进程当前的生命周期状态(如:运行中、睡眠中、停止、僵尸状态等)。

说明:这是进程调度的关键依据。操作系统根据状态决定是否给这个进程分配CPU资源。
优先级

作用:决定进程获取CPU资源执行的先后顺序。

说明:优先级高的进程会更容易被CPU调度,从而获得更多的执行时间。
程序计数器

作用:保存着这个进程即将要执行的下一条指令的内存地址。

说明:当进程被CPU切换出去又切换回来时【调度切换】,就靠这个值来恢复到上次的执行现场,继续运行。
内存指针

作用:指向该进程的代码、数据以及与其他进程共享的内存区域。

说明:这些指针定义了进程在内存中的"地盘",确保了进程能正确地访问自己的指令和数据。
上下文数据

作用:保存进程在CPU寄存器中的数据(当进程被切换出CPU时)。

说明:这是实现进程切换的核心。进程被换下CPU前,必须把当前所有寄存器的值保存到它的task_struct中;当它再次被调度时,再把这些值重新加载到寄存器,就能毫不停顿地继续执行。就像大学生参军入伍需要办理休学把学习进度都记录下来,回来时才能接着学~
I/O状态信息

作用:记录进程占用的I/O设备、发出的I/O请求以及打开的文件列表。

说明:用于管理进程的所有输入输出操作,确保资源被正确分配和释放。
记账信息

作用:统计进程使用的CPU时间、资源情况等。

说明:用于系统监控、性能分析和计费。
其他信息(后续博客会继续介绍)

查看进程😁

通过前面我们知道了什么是进程?现在我们来看看查看进程~首先我们了解一下getpid() 和 getppid()这两个在 Linux/Unix 系统编程中用于获取进程标识符Process ID)的基础函数~

特性 getpid() getppid()
功能 获取当前进程的 PID 获取当前进程的父进程的 PID
头文件 #include <unistd.h> #include <unistd.h>
返回值 pid_t(当前进程的 PID) pid_t(父进程的 PID)
关键点 唯一标识一个进程 可能变化(如果父进程先结束)

📌 通过 /proc 文件系统查看进程信息

在 Linux 中,每个正在运行的进程都会在 /proc 目录下有一个以进程 ID(PID)命名的子目录,其中包含该进程的详细信息。

除此之外,我们一般还会使用ps 命令用法,用于查看与特定关键词相关的进程信息~

📌 命令行输入

bash 复制代码
ps ajx | head -1 ; ps ajx | grep "code"

这个命令实际上由两个独立命令组成,用分号 ; 分隔:

ps ajx | head -1

ps ajx: 使用 BSD 风格的选项显示详细的进程信息

a: 显示所有用户的进程

j: 显示作业格式(包含 PGID、SID 等信息)

x: 显示没有控制终端的进程

| head -1: 只保留输出结果的第一行(即标题行)

ps ajx | grep "code"

再次运行 ps ajx 获取所有进程信息

使用 grep "code" 过滤出包含 "code" 字符串的进程

命令执行后显示了两部分结果:

标题行(来自第一个命令):

bash 复制代码
PPID   PID PGID SID TTY      TPGID STAT UID TIME COMMAND

对于各列的含义,我们先简单了解一下:

PPID: 父进程ID

PID: 进程ID

PGID: 进程组ID

SID: 会话ID

TTY: 控制终端

TPGID: 终端前台进程组ID

STAT: 进程状态

UID: 用户ID

TIME: 累计CPU时间

COMMAND: 命令名称

匹配的进程(来自第二个命令):

bash 复制代码
10379 13543 13543 10379 pts/O    13543 S+    1001 0:00 ./code
12279 13598 13597 12279 pts/1    13597 S+    1001 0:00 grep --color=auto code

第一行是名为 ./code 的进程,第二行是 grep 命令本身(因为它也包含 "code")~

我们可以发现重新生成可执行程序,两次运行可执行程序的pid是不一样的,但是ppid是一样的~

为什么pid每次都不一样?

pid(进程ID)是操作系统为每个新进程分配的唯一标识符, 每次运行程序时,系统都会创建一个新的进程实例。操作系统使用pid来管理和区分不同的进程。由于pid是动态分配的,且系统需要保证唯一性,所以每次运行程序时都会获得一个不同的pid。
为什么ppid两次都一样?

ppid(父进程ID)表示创建当前进程的父进程的ID。ppid始终是10379,这意味着两次运行都是在同一个父进程中启动的。当我们在终端中执行./code命令时,终端Shell(bash)会创建子进程来运行程序。因此,无论运行多少次,这些子进程的父进程都是同一个Shell进程(pid: 10379)。

我们来验证一下父进程的身份,命令行输入:

bash 复制代码
ps ajx | head -1 && ps axj | grep 10379

所以我们在命令行中启动命令/程序的时候都会变成进程,该进程的父进程是bash!由子进程来运行命令/程序~

那么我们可以知道Linux系统是通过父进程创建子进程的方式来 增多进程!!!

创建进程😀

我们知道Linux系统是通过父进程创建子进程的方式来增多进程! 那么父进程是怎么创建子进程的呢?通过前面我们知道进程= 代码和数据 + PCB。创建进程我们就需要介绍一个Linux系统中的系统调用fork()~

我们可以使用man fork来简单看一下:

我们首先试用一下fork(),来看下面一段代码:

你们可以猜测到运行结果是什么吗?

见证奇迹的时刻到了,我们可以发现打印了两次这是一条消息~接下来我们慢慢分析

fork之后,打印了两次这是一条消息,说明不仅仅是一个进程执行了这条语句,一个是父进程,那么另外一个就是我们的子进程~

fork() 系统调用(system call)的核心思想:复制自己

当一个进程(我们称之为父进程)调用 fork() 时,操作系统会做这样一件事:几乎原封不动地复制当前进程,从而创建一个新的进程(我们称之为子进程)。

这个"复制"包括:

代码段:和父进程一模一样。

数据段:堆、栈、初始化的数据等,内容相同但位于不同的物理内存中。

进程上下文:打开的文件描述符、信号处理方式、进程状态等。

我们来验证一下:修改代码

代码运行结果:

同时fork()系统调用还有一个核心特性:

一次调用,两次返回

这是 fork 最神奇也最容易让人困惑的地方(怎么会有两个返回值呢):

在父进程中,fork() 调用返回的是新创建的子进程的进程ID(PID)(一个大于0的数字)。

在子进程中,fork() 调用返回 0。

通过返回值,程序可以判断自己当前是父进程还是子进程,从而执行不同的分支代码。

我们同样可以通过一段代码来验证一下:

果不其然,结果确实如此~

这不禁让人困惑,怎么会有两个返回值呢?

事实上不是"一个函数返回两次",严格来说,fork() 系统调用只执行了一次(创建子进程),但它的返回过程发生了两次------分别在两个不同的进程中

return 的本质是写入,当函数执行到return的时候,主体功能已经完成了,从内核返回用户态的本质,就是将内核准备好的"返回值"写入到用户空间那个等待接收返回值的变量(id)中,操作系统为两个进程写入了不同的值。

特性 父进程 子进程
诞生方式 由用户启动 fork() 系统调用创建
fork() 返回值 子进程的 PID (>0) 0
执行起点 fork() 调用之后继续执行 fork() 调用之后开始执行
关系 调用者 被创建者

神奇的"分身术"比喻:

可以把 fork() 调用点想象成一个"分身点"。

父进程执行到此处,喊了一声"分身!"(系统调用)。

操作系统内核施展分身术,创造了一个一模一样的子进程。

法术完成后:

父进程听到耳边传来一句话:"你的分身ID是 1234"。

子进程一诞生,听到的第一句话是:"你是分身,你的编号是0"。

然后,父子两人都从"分身!"这句话之后开始继续各自的人生(执行后续代码),并且都记住了自己听到的那句话(不同的返回值)。

现在我们只是创建了一个子进程,如果我现在创建更多的子进程呢?

事实上,由于进程调度顺序不确定,父进程和子进程的输出可能会交错出现,也就是父子进程的运行顺序是不确定的~

总结

fork是Unix/Linux创建新进程的核心系统调用。其核心机制是"复制父进程":内核以父进程为模板,初始化子进程的控制结构(task_struct),并默认通过"写时复制"(Copy-On-Write)技术让父子进程高效共享相同的代码和数据页,仅在需要修改时才创建副本,从而保证进程数据独立性。fork的神奇之处在于"一次调用,两次返回":在父进程中返回子进程的PID,在子进程中返回0。通过判断返回值,父子进程可执行不同代码分支,这是实现并发任务和"fork+exec"执行新程序的基础。它高效地实现了进程隔离与资源共享的平衡。


♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨


相关推荐
编码浪子5 小时前
趣味学RUST基础篇(异步)
服务器·rust·负载均衡
码农101号6 小时前
运维安全05 - iptables规则保存与恢复
运维·网络·安全
Empty_7776 小时前
SELinux安全上下文
linux·服务器·安全
bug攻城狮7 小时前
解决Ubuntu中apt-get -y安装时弹出交互提示的问题
linux·运维·ubuntu
xiachong277 小时前
ubuntu18.04安装PCL1.14
linux·ubuntu
夜阑珊夭夭8 小时前
linux自定义网卡名字
linux·运维
czhc11400756638 小时前
Linux912 shell:$# $1 $?;RHEL 8 AppStream BaseOS
linux
佛天华8 小时前
centos 时间校准
linux·运维·centos