复习核心:
一个可执行程序想要被运行,首先要将该可执行程序加载到内存,但是只把程序加载到内存,我们的操作系统是无法对程序进行管理的,因为毕竟没有描述该结构的相关属性,因为操作系统管理的核心指导思想是先描述、后组织,因此操作系统为了关系已经加载进来的程序,我们称之为进程,就衍生出了一个新的概念叫做PCB,在Linux中具体叫做task_struct,又因为操作系统是由C语言写的,所以task_struct是一个结构体,描述了被管理对象的相关属性,所以一旦有一个进程加载进来,操作系统中就要有一个task_struct,有10个进程被加载进来,就要有10个task_struct,在task_struct内部,我们可以用指针将所有的task_struct用链表形式管理起来;所以在操作系统内部无论是调度还是切换,以及排队、阻塞、挂起,本质都是对进程PCB数据结构对象进行管理,我们对进程管理工作转化成了对特定数据结构,比如链表的增删查改;进程也是一个被管理的实体,所以必须要有PCB来描述进程;而task_struct是一种纯内存级的操作,也就是说进程是在内存中被动态创建的,当我们把电脑关机再打开,就不会再看到我们刚才的进程,所以进程不是存储在磁盘上的永久数据;

1.什么是进程
关于进程的定义,可以从不同视角来理解:
① 课本概念
进程是程序的一个执行实例,也就是正在运行中的程序。
②内核观点
进程是操作系统担当分配系统资源(如CPU时间、内存)的实体。
③ 整合理解
在操作系统内部,一个进程 = 内核数据结构(task_struct) + 该程序对应的代码和数据。
简单来说:进程就是"内核描述结构"与"程序实体"的结合体。
值得注意的是进程的管理也是由操作系统进行的!
2.描述进程-PCB
操作系统为每个进程维护一个内核数据结构,用于存储该进程的所有属性信息,这个结构被称为进程控制块(PCB,Process Control Block)。它本质上就是进程在内核中的"身份证"和"档案袋"。
①课本概念
PCB是进程属性的集合,是操作系统感知和管理进程的唯一手段。
②具体实现
在不同的操作系统中,PCB的具体名称和实现各不相同。在Linux操作系统 中,这个描述进程的结构体被称为 task_struct。
Linux视角:task_struct
①本质:task_struct 是Linux内核中定义的一种复杂的数据结构类型;它会被操作系统在内存中进行创建并且包含着进程的信息。
②载体:它包含了描述一个进程所需的所有信息(如状态、优先级、内存指针、打开文件列表等)。
③存在形式 :每当一个进程被创建,内核就会在内存(RAM)中为其实例化一个 task_struct 结构体,并随着进程的运行动态更新其中的内容。简单来说,task_struct 就是Linux视角下的PCB的具体化身。

所有运行在系统里的进程都以 task_struct 双链表的形式存在内核里的;
③ 包含进程的具体信息有: 
总结一句话:
task_struct 就像一个进程的"个人档案",内核只要找到这个结构体,就能知道这个进程是谁(ID)、在干嘛(状态)、能跑多快(优先级)、跑到哪了(PC)、家里住哪(内存)、带了什么行李(I/O)以及跑了多久(记账)。
3.进程的标识符
注释:PID,系统里每个正在运行的程序,都会被分配一个唯一的数字编号,这个编号就是 PID(Process ID,进程标识符)。就像每个人都有唯一的身份证号一样,系统通过 PID 来识别和管理每一个进程。
在 Linux 里,每个进程的 PID 同时也是一个目录名 ,位于 /proc 下面;
①如何查看一个具体的进程的PID
第一种方法:
bash
ps aux | grep +进程名


第二种方法
bash
ps ajx | grep +进程名


这里面的 head -1 的意思是保留 ps ajx中的第一行,而grep -v grep 的意思是忽略我们刚刚的命令行中的image;因为我们的文本过滤器本身也是一个进程。
而命令中的,符号也可以写成&&;意思就是左侧的命令执行完再执行右边的命令;

② 进程特性
A.我们需要查看进程,但是使用进程名太麻烦,在计算机内部我们使用进程编号也就是PID(Process ID)来区别不同的进程;
B.同一个可执行程序在不同的时间启动它的PID是会发生变化的;
因为在系统层面维护一个进程的PID其实是用一个累加的计数器;
C. 把程序运行起来,无论是./exe还是windows中的双击,本质上都是系统中在给我们启动进程,就连我们使用的ls命令其实也是一个进程,但是这个进程的速度很快;
D.进城分为两种: a.需要我们自关闭的进程,也就是常驻进程
b.运行完自己就会退出的进程,瞬时进程;
③ 如何查看自己的PID:

getpid是系统给我们封装的一个库函数,他的返回值pid_t,pid_t是系统给我们封装的一种类型不属于标准C语言;

这样就可以直接显示我我们当前这个程序的pid;

④如何杀死一个进程
bash
kill -9 + 目标进程的PID

⑤ 如何查看一个进程更多的属性
因为Linux中一切皆文件,所以他也可以将进程的属性以文件的形式反应到我们的命令行上;因此在Linux中存在一个特殊的目录,即 ls /proc,它在根目录下,和我们之前提到的home目录属于同级;;

proc中以数字命名的一个一个的文件夹,这些数字就是对应的进程的PID,

而我们想要查看这些PID目录下进程更加详细的属性可以使用命令
cpp
ls /proc/1
所以我们创建好一个进程就会在对应的proc目录下,生成一个以对应进程PID为名的存储对应的属性;这里的1是进程的编号;

exe后面的路径是想告诉我们这个进程是由磁盘中的哪个可执行程序加载而来的;值得注意的是如果我们把一个进程对应的可执行程序删除,我们发现对应的进程还是在跑,这是因为我们的进程已经被加载到内存了,而删除的可执行程序的文件在磁盘中;
⑥ 什么叫做当前路径
所谓的当前路径就叫做进程的CWD,具体来说就是在当前我们每次在当前路径下新建文件,我们的文件的路径默认会加上的路径就是当前路径;

在Linux中我们想要切换当前工作的路径,使用chdir;谁调用它,它就改变谁;


这时候我们发现cwd发生了变化;

注意: proc文件不是磁盘级的文件,也就是说电脑关闭之后文件就全部没有了,proc其实是把内存级的文件给我们显示了;
4.PPID
父进程ID,Linux中在启动任何一个进程本质上是由父进程让操作系统创建的;


① 进程的父进程id一直都是不变
这是为什么呢?

我们发现19173父进程是bash,在命令行中输入的命令、程序是bash(命令行解释器)的子进程,由子进程来执行我们的代码;而这些子进程是由bash创建的;
bash创建出来是为了让自己不要受到影响;呢么一个bash进程是如何创建一个子进程的呢?其实是使用了系统调用接口;
5.fork(译为:分支)
呢么一个bash进程是如何创建一个子进程的呢?其实是使用了系统调用接口;每次登录的时候我们的xshell都会给我们创建一个bash;
首先我们来查看一下我们的bash进程;

①见一见子进程创建
首先我们要用到一个系统调用函数,fork,如图所示,它的功能就是创建进程;

这是fork函数的返回值说明,
也就是说当进程创建成功之后会返回子进程的pid给父进程,返回0给子进程;失败的话返回-1给父进程;

编程小技巧@
有些时候我们使用了一个变量,比如id变量,但是我们不想去用它,这个时候我们的编译器就会给我们报错,如何让不报错呢?其实只用再加一行void(id)代码即可



我们发现再我们fork只有有了两个分支,并且他们的pid相邻,18315跟我们最开始的执行printf的由系统自动生成的进程的pid相同,简单说其实18316就是我们18315创建的子进程,也就是我们fork出来的新的进程;而10331又是谁呢

哈哈,10331就是bash,所以bash就是我们Linux中的最跟上的进程;也就说一个进程可以由多个子进程,但是父进程只有一个,所以进程其实也是树形形状;
按照fork的返回值的情况,我们来看一下具体现象;


根据上面的代码我们验证了"当进程创建成功之后会返回子进程的pid给父进程,返回0给子进程;" 这句话,并且我们通过会id不同情况下的条件判断,我们可以分出父子进程;
也就是我们的if和else尽然同时成立了!也就是竟然有了两个分支,也就是两个进程同时执行了
为什么要让父进程得到子进程的pid呢?因为父进程:子进程=1:n,这样才能让父进程很好的管理子进程;
②理解子进程
fork之后会有两个进程,父子进程,一般而言父子进程的代码是会共享的,但是数据各自私有一份;为什么会这样呢?
首先我们得理解 进程= 内核数据结构 + 代码和数据而内存将父进程的代码和数据从磁盘中加载进来,会给父进程创建对应的内核数据结构,也就是task_struct(PCB),而当内存发现我们的父进程中有一个fork函数,就会给父进程创建一个子进程,也会对其创建对应的内核数据结构---task_struct;但是一个进程= 内核数据结构 + 代码和数据,子进程就只好继承父进程的代码了;
呢么如何理解数据各自私有一份呢?
我们定义一个全局的变量gval ,然后让父进程只是查看gval,让子进程对gval进行++;我们发现,父进程查看的gval一直都是我们最开始定义的gval=0,而我们的子进程的gval真的在依次增大,父进程的值没有收到子进程数据变化的影响,

在平常我们使用的APP中,一个APP挂掉了不会影响其他的APP;这也就说进程之间的独立性是很强的,父子进程也不例外;所以子进程对父进程的代码是只读的;这也就是说fork之后,父子进程看到的id值已经不同了;
4.创建多进程



所以父进程是可以得到所有子进程的id的,这样就可以对进程进行管理;
①如何理解一个函数可以有多个两个返回值

通过以上的代码我们发现了一件事,比如刚才的gval全局变量,它是一个变量,变量怎么能有不同的返回值呢?看图片有解答;
