Linux 进程(二) 深入理解进程

目录

[一. 理解系统调用](#一. 理解系统调用)

[二. 理解库函数](#二. 理解库函数)

[三. 理解进程](#三. 理解进程)

[3.1 初步了解进程](#3.1 初步了解进程)

[3.2 描述进程: PCB(进程控制块)](#3.2 描述进程: PCB(进程控制块))

[3.2.1 task_struct 包含的核心内容](#3.2.1 task_struct 包含的核心内容)

[3.3 组织进程](#3.3 组织进程)

[3.4 查看进程](#3.4 查看进程)

[3.4.1 getpid 和 getppid](#3.4.1 getpid 和 getppid)

代码示例:

[3.4.2 ps](#3.4.2 ps)

代码示例:

[3.4.3 top](#3.4.3 top)

[3.4.4 /proc](#3.4.4 /proc)

代码示例:

[3.4.4.1 两个重要的属性](#3.4.4.1 两个重要的属性)

exe

cwd

chdir

[3.5 杀掉进程](#3.5 杀掉进程)

[3.5.1 ctrl+c](#3.5.1 ctrl+c)

[3.5.2 kill](#3.5.2 kill)

[四. 父进程与子进程](#四. 父进程与子进程)

[4.1 命令行解释器 bash](#4.1 命令行解释器 bash)

[4.2 创建进程 fork](#4.2 创建进程 fork)

代码示例:

[4.2.1 fork 的返回值](#4.2.1 fork 的返回值)

[4.2.2 三个重要的问题](#4.2.2 三个重要的问题)

[为什么 fork 给父子进程的返回值不一样](#为什么 fork 给父子进程的返回值不一样)

为什么一个函数会返回两次

[为什么同一个变量,即==0,又大于0,导致if else同时成立](#为什么同一个变量,即==0,又大于0,导致if else同时成立)


一. 理解系统调用

我们在学习操作系统的时候知道,操作系统通过管理软硬件的资源来为用户、程序提供更好的执行环境。但是操作系统却不相信任何用户。

我们可以举银行的例子来帮助我们理解:银行是给用户服务的,但是银行却不相信任何用户,你得通过窗口提供各种资料才行,操作系统也是如此,通过系统调用让用户和操作系统实现交互。

二. 理解库函数

依旧是银行的例子,我们发现光有窗口还不够,对于不熟悉的用户来说会有专门的引导者去帮助用户;同样的,库函数就像是引导者帮助用户进行对应的系统调用,例如C语言中常用的printf函数,底层就调用了系统提供的函数接口write,但是我们为什么看不见呢,因为有人对其进行了封装。

但是并不是所有的库函数都进行了函数调用,我们可以通过有没有访问我们的硬件来做出判断,因为访问了我们的硬件必然经过操作系统,而与操作系统交互必然要经过系统调用

三. 理解进程

3.1 初步了解进程

在了解进程之前,我们不妨把整个流程推导一下:首先我的磁盘有一个可执行文件cmd,运行该文件时会先把cmd加载到内存当中,此时内存除了可执行文件还有操作系统,我们操作系统对这些可执行程序必然是要做出管理的(因为操作系统的作用就是管理软硬件资源),我们之前也说了管理就是先描述再组织。我们继续一步步来理解:描述就是在操作系统中对每一个可执行程序都会定义一个结构体XXX,结构体里面记录着程序的相关属性信息。那么假设有n个可执行程序加载到内存当中,那么在操作系统中就会形成n个节点,每个节点都有对应指针指向内存中的可执行程序。组织:我们这里假设以链表的形式组织,这就形成了一个进程列表。那么进程又是什么呢?进程就是:内核数据结构对象+加载到内存中的可执行程序。

3.2 描述进程: PCB(进程控制块)

我们在上文提到了:内核中会创造结构体记录进程的相关信息,这个数据结构我们称之为进程控制块,简称PCB(process control block),而在linux下该结构体的名称称为 task _struct

我们之前提到操作系统有进程的管理,这实际上是对PCB的管理,而不是对代码数据也就是传输到内存的可执行文件做管理

3.2.1 task_struct 包含的核心内容

我们之前说了进程控制块里面记录着进程的相关信息,接下里我们就来看看记录了进程的哪些属性信息:

  • **标示符:**描述本进程的唯一标示符,用来区别其他进程。

  • **状态:**任务状态,退出代码,退出信号等。

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

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

  • **内存指针:**包括程序代码和进程相关数据的指针,还有与其他进程共享的内存块的指针。

  • **上下文数据:**进程执行时处理器的寄存器中的数据(体会例子,要加图 CPU、寄存器)。

  • **I/O 状态信息:**包括显示的 I/O 请求,分配给进程的 I/O 设备和被进程使用的文件列表。

  • **记账信息:**可能包括处理器时间总和,使用的时钟总数和,时间限制,记账等。

  • 其他信息。

  • 具体详细信息后续会介绍。

3.3 组织进程

组织进程就是操作系统通过一定的数据结构,将各个进程的 PCB 组织起来,方便对进程进行查找、管理和调度。

3.4 查看进程

3.4.1 getpid 和 getppid

getpid() 和getppid() 都是 Linux 中常用的进程接口。

  • getpid():获取当前进程的 PID(就是我们在task_struct包含内容中提到的进程标识符)。

  • **getppid():**获取当前进程的父进程 PID。

getpid 和 getppid是系统提供的函数接口,我们通过man手册也可以发现,它给我们翻到第二册---系统调用

代码示例:

3.4.2 ps

ps(Process Status)是 Linux 中用于查看当前系统进程信息的指令。简单来说,就是查看系统中有哪些进程正在运行,以及它们的相关属性。

关于ps 的更多选项介绍我们以后再讨论,这里只是简单见识一下。

代码示例:

在 Linux 中,如果想要一次性执行多个指令,可以使用 ;和 && 分隔。

  • **;:**前一个指令执行完,就执行下一个。

  • **&&:**前一个指令执行成功,才执行下一个。

3.4.3 top

top 是 Linux 中用于实时查看系统运行状态和进程资源占用情况的指令。

按 q:退出 top。

3.4.4 /proc

/proc 目录下记录着当前系统里所有进程的信息,而该目录下的所有数字目录上的数字都是对应进程的PID,数字目录里面的内容就是对应的进程进行时的动态属性。

代码示例:
3.4.4.1 两个重要的属性
exe

/proc/PID/exe 记录(准确来说是指向)该进程对应的可执行文件路径。

如果我把对应的可执行文件删除,很大程度上对进程不会有影响,因为我只是把磁盘上的文件删除了,并没改变内存上的代码数据。但是我们再次查看exe会发现被标注deleted

cwd

cwd 指向的是当前进程的工作目录

当我们在当前目录下执行 touch hallo 时,为什么文件会默认创建在当前目录下呢?这是因为运行 touch 程序时会创建对应的进程,该进程会继承 Shell 的当前工作目录,并通过 cwd 记录这一信息。当我们没有指定文件的绝对路径,而是使用相对路径 hallo 时,操作系统就会以该进程的当前工作目录(cwd)为起点解析路径,因此文件默认会创建在当前目录下。

chdir

chdir 是 Linux 中的一个系统接口,用于改变当前进程的工作目录(cwd)。

3.5 杀掉进程

3.5.1 ctrl+c

我们之前说过,我们可以用 ctrl+c 来终止异常,同样的我们也可以用它来终止正在运行的程序。

3.5.2 kill

kill 是 Linux 中用于向指定进程发送信号的指令,常用于终止进程。

bash 复制代码
1. kill PID
- 默认发送 SIGTERM(15号信号)。
- 请求进程正常退出。
- 进程可以处理或忽略该信号。
- 进程可以在退出前进行资源清理。

2. kill -9 PID
- 发送 SIGKILL(9号信号)。
- 强制终止进程。
- 进程无法捕获、忽略或阻塞该信号。
- 进程没有机会自行执行清理操作。

这里选项 -9 涉及信号的知识,我们后面会学到这里就不再说明,了解大概用法即可

四. 父进程与子进程

在 Linux 中,绝大多数进程都是由其他进程创建的,因此进程之间通常存在父子关系。

还记得我们之前提及的进程接口 gitppid 吗,我们可以用这个验证一下:

但是这个父进程到底是什么呢?

我们会发现这个父进程对应的指令是bash,那么显而易见 ./myprocess就是子进程,而bash是父进程。

我们之前也说了,当有新命令来时,命令行解释器本身并不会去执行,而是会创建子进程去执行,我们现在再来看这段话就好理解了。

4.1 命令行解释器 bash

我们前几章在讲shell与内核交互的时候,已经了解过命令行解释器bash,这里就不再赘述。我们主要再来了解几个相关的知识点。

在 Linux 中,当用户登录并进入命令行环境时,操作系统通常会为该终端会话启动一个 Shell 进程(例如 Bash),用于接收、解析和执行用户输入的指令。 需要注意的是,同一个用户登录多个终端,通常会拥有多个独立的 Bash 进程;当使用 su 切换用户时,通常也会启动一个新的 Shell 进程,而原来的 Shell 仍然保留。

4.2 创建进程 fork

fork 是 Linux 中用于创建子进程的系统调用。

代码示例:

当父进程调用 fork() 创建子进程时,**操作系统会为子进程创建一个新的 PCB(进程控制块),我们可以暂时将其理解为对父进程 PCB 的拷贝。**子进程会继承父进程的大部分属性信息,但部分属性会发生变化,例如 PID、PPID 等。另外,子进程刚创建时,并不会立即完整复制一份父进程的代码和数据,而是与父进程共享相同的代码和数据内容。因此,在初级阶段,我们可以简单理解为:子进程拥有自己的 PCB,但最初与父进程共享代码和数据。

所以对子进程、父进程而言都会执行代码 printf("进程开始运行,pid:%d\n",getpid),我们最终会看到两种输出结果。

4.2.1 fork 的返回值

fork() 创建成功时,会返回两次:父进程获得子进程的 PID,子进程获得 0;如果创建失败,则返回 -1,不会创建子进程,同时设置 errno 记录具体的错误原因。

所以,我们其实可以利用fork,让父/子进程执行不同的代码块:

4.2.2 三个重要的问题
为什么 fork 给父子进程的返回值不一样

父进程和子进程之间是 1:n 的关系,即一个父进程可以创建多个子进程。因此,父进程需要获得每个子进程的 PID,以便区分和管理不同的子进程。而子进程通过 fork() 的返回值为 0,便可以判断自己是子进程。

为什么一个函数会返回两次

我们先来简单看看 fork 的内部实现:

fork 的核心功能确实是创建子进程。内核在 fork 内部完成子进程创建之后,父进程和子进程都会从 fork 调用结束的位置继续往下执行,所以看起来就像 fork "返回了两次"。但更准确地说,并不是一个进程把 return 执行了两次,而是父进程和子进程这两个执行流各自从 fork 返回了一次:父进程拿到的是子进程的 PID,子进程拿到的是 0。

为什么同一个变量,即==0,又大于0,导致if else同时成立

虽然我们前面理解了 return 会双进程而执行两次,但是 id 是怎么做到接收两个值的呢?

我们先来理解一个点:进程具有独立性

我们之前说过进程 = PCB + 数据代码,就PCB而言,子进程和父进程都有自己独立的PCB,不会互相影响****;代码虽然我们共用,但是对于父子进程而言代码是只读不能修改的;对数据来说,父子任何一方要对数据进行修改,OS都会把被修改的数据在底层拷贝一份,让目标进程修改这个拷贝,这个技术我们称之为****写时拷贝。

相关推荐
沫璃染墨2 小时前
从零入门计算机网络系列(十二):HTTP长连接与会话管理详解——从Connection到Cookie与Session
linux·服务器·c++·tcp/ip·ubuntu·http·交互
温暖小土2 小时前
在 CentOS 上安装 Redis Stack
linux·redis·centos
GeW4 小时前
国产化替代进入深水区之后Linux 与数据库为什么必须走向重建
linux
女神下凡7 小时前
芯参谋(85):Linux_PPI_NAND_驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件·设计规范
12.=0.8 小时前
【持续更新】shell脚本 + Linux命令
linux·运维·服务器
Escalating_xu8 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络
无言听风雨丶9 小时前
Linux 容器本质探秘:Cgroup 与 Namespace 深度解析与实战
linux·运维·服务器·kubernetes
万联WANFLOW9 小时前
CVE-2026-98432 等漏洞自查:AI 查出的 Linux、Windows 漏洞,运维怎么确认受不受影响
linux·运维·windows·网络安全
OPEN-F9 小时前
Ubuntu 自动登录后 GNOME 远程桌面连不上?一次登录钥匙环锁定的排查与永久修复
linux·运维·ubuntu