Bootstrap

操作系统与社交达人:进程的初次探秘

    在操作系统的世界里,进程是核心的“角色”。它如同现代社会中的“社交达人”,负责沟通与协作,为系统的高效运转提供动力。本文将以拟人化的视角,从进程的基本概念到其实现与操作,带你逐步了解这个神奇的世界。  

1. 进程简介

进程是程序的一次运行活动,不仅包括代码本身还涉及程序运行时所需的资源(如内存、文件句柄、CPU时间等)。总结来说,进程 = 内核数据结构对象 + 自己的代码和数据。

运行一个程序,操作系统会使用一个struct结构体对该程序进行描述,结构体中记录有该进程的一系列信息,该结构体就是该进程的内核数据结构对象,程序在执行时,内存会从磁盘上将代码和数据拷贝到内存中,这部分就是自己的代码和数据。

(1)进程的基本概念

课本概念:程序的⼀个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。

(2)进程的基本操作
1. 创建进程:通过系统调用(如`fork`)生成新的进程。  
2. 查看进程:使用工具(如`ps`、`top`)获取系统中所有活跃进程的信息。  
3. 终止进程:通过信号或系统调用(如`kill`)结束一个进程。  

2. 进程的“身份证”:PCB与task_struct

2.1 描述进程的PCB(Process Control Block)

进程信息被放在⼀个叫做进程控制块PCB的数据结构中,可以理解为进程属性的集合。Linux操作系统下的PCB是: task_struct。

进程控制块(PCB)是操作系统中用于描述进程的关键数据结构。它如同进程的“身份证”,记录了进程的核心信息,包括:  
- 进程标识符(PID):唯一编号,便于区分不同进程。  
- 进程状态:运行、等待、就绪等。  
- CPU寄存器内容:记录进程切换时的上下文信息。  
- 内存地址:进程代码段、数据段、堆栈段的位置。  

PCB就是上面介绍的内核数据结构对象:

我们通过 PCB 结构体来描述进程的代码和数据,该结构体中包含指针以将各个进程连接起来。因此,进程的管理就变成了对进程链表的增、删、查、改操作

比喻:  
PCB是进程的档案袋,里面装满了进程的身份信息和工作记录。  

2.2 Linux中的task_struct

在Linux中描述进程的结构体叫做task_struct用来管理进程的核心数据结构。它包含了与进程相关的所有信息:  

​- 进程ID
- 进程状态
- 优先级
- 内存信息
- 打开的文件描述符表

进程的所有属性都可以直接或间接通过tast_struct找到。

通过内核源码,可以查看task_struct定义:  

struct task_struct {
    pid_t pid; // 进程ID
    long state; // 进程状态
    struct mm_struct *mm; // 内存信息
    struct files_struct *files; // 打开的文件信息
    ...
};

比喻:  
如果PCB是进程的档案袋,`task_struct`就是袋子里的详细内容。  

在linux中task_struct就是内核数据结构对象:

3. 通过系统调用获取进程标识符

我们执行的所有指令、工具、自己的程序,运行起来全部都是进程。

3.1 getpid()

getpid是 Linux 中的一个系统调用,用于获取当前进程的进程 ID (PID)

3.2 getppid()

getppid用于获取当前进程父进程ID (PPID)

运行程序 

 通过上面的结果,我们发现子进程的id一直在变化,而父进程的id不变。

我们通过 ps ajx | head -1 && ps ajx | grep 17787 | grep -v grep 查看父进程

发现父进程是一个bash(命令行解释器),所以说bash也是一个进程。

4. 如何查看进程?操作系统的“监控器”

查看进程是操作系统管理的重要环节。在Linux中,可以使用多种工具查看和监控进程:  
- `ps`命令:列出系统中所有运行的进程。  
- `top`命令:动态显示进程的实时运行状态。  
- `htop`工具:功能强大的交互式进程管理工具。

4.1  ps命令

(1)ps axj命令用于显示当前所有正在运行的进程的详细信息。下面是其中一部分的截图

(2)ps axj | grep process查看指定进程

当myprocess程序正在运行时:

grep也是一个进程,因此显示两个进程。

显示进程信息第一行

; 和 && 都用于在同一行中分隔多个命令。

不同点在于使用'; ',shell 会按照从左到右的顺序依次执行这些命令,无论前一个命令执行成功(返回状态码为 0)还是失败(返回非零状态码),后续命令都会继续执行。

而 && 存在逻辑判断机制。shell 会按顺序执行命令,只有当前一个命令成功执行(返回状态码为 0)时,才会继续执行后续命令。

(3)隐藏grep进程 

4.2 ls命令

ls /proc同样可以用来查看进程,proc目录里记录当前系统里所有进程的信息,每一个数字目录代表特定进程的pid,内容包含该进程在运行时的的动态属性,一旦退出该目录会被系统自动移除

 当下面程序运行时:

我们可以通过pid查到该进程

杀死该进程后则无法查到

 4.3 杀掉进程

(1)ctrl + c

(2)kill -9 pid

4.4 查看进程的属性

ls /proc/pid

(1)cwd是当前目录,这说明进程会记录下自己的当前路径

我们在文件中使用fopen,在运行程序可以发现,当前目录下新建了一个文件hello.txt

使用chdir改变文件所在目录

 文件被创建在了指定路径下

查看进程属性可以发现,进程所在目录为我们所指定目录

(2)exe是进程所对应的可执行文件

我们将这个文件删除 ,如果程序正在运行中,则程序不会终止,因为我们删除的是磁盘上对应的文件,而进程启动时,该进程的拷贝已经到内存了、

此时我们再来查当前进程的属性:

5.  通过系统调用创建进程-fork初识

`fork`是Linux中创建新进程的核心系统调用。它通过复制当前进程,生成一个新的子进程。  

5.1 fork用来创建一个子进程

开始只有一个进程,通过fork创建后会有两个进程,并且这两个进程都会执行fork之后的代码

为什么两个进程都会执行之后的代码呢,通常是因为子进程会复制父进程的进程控制块(PCB),并且它们的大多数值是相同的。因此,父进程和子进程都会访问到相同的数据和代码段,导致它们都可以执行之后的代码。此时子进程没有自己的数据和代码,因为目前没有程序新加载。

5.2 fork的返回值

 如果调用成果,子进程的ID作为返回值返回给父进程(这是一个整数),0 返回给子进程。调用失败-1被返回在父进程中。

5.3 父子执行不同代码

- 为什么fork给父子进程各自返回不同的返回值呢?

这是因为父 : 子 = 1 :n,意思是一个父进程可以有多个子进程,所以要给父进程返回子进程的id来区分不同的子进程,方便对子进程进行管理。

- 为什么一个变量,既等于0,又大于0,同时满足if和else

首先进程是独立的,一个进程的消失不影响其它进程,如果父子一方修改了数据,OS(操作系统)会把修改的数据子啊底层拷贝一份,让目标进程修改这个拷贝,这就是写时拷贝。

下面的结果可以看到,子进程的gval和父进程的gval是不一样的

比喻:  
`fork`就像细胞分裂,原来的细胞(父进程)会分裂出一个新的细胞(子进程)。  

;