Linux进程管理2

目录

 一 PCB结构体

为什么要有结构体PCB

PCB是什么

二 如何创建一个进程?

1 用命令行创建一个进程

2 用代码来创建进程

① 基本使用

②其他问题

a 为什么有两个返回值

b 为什么这样返回

c if和else会被同时执行?


 一 PCB结构体

为什么要有结构体PCB

在windows中,我们双击桌面图标,启动一个软件的本质上,就是启动了一个进程。那么类似的,在linux下,运行一条命令,就是在系统层面创建了一个进程。操作系统只能对进程来进行调度。因此我们用操作系统管理的时候,也就是对进程进行管理。

有这样一种情况,我们可以同时运行成千上万条命令,同时加载多个程序,也就是说,在OS中可能存在大量的进程,那么OS怎么对这些进程来进行管理?

之前谈到的概念,先描述,再组织(管理的本质)

 更正:struct_task->task_struct

我们写的一个个可执行程序以文件(内容+属性)的形式存储在磁盘上,当运行的时候,需要把他加载到内存中。由于进程很多,为了实现管理,把他们描述成一个个PCB结构体(不用存放内容,只用加载属性即可),以链表的数据结构形式对他们来实现管理。

struct PCB
{
	struct PCB* next;//指向下一个PCB
	struct PCB* prev;//指向上一个PCB
	……//data 其他的属性数据与进程相关的
};

写成代码就是这样的一个意思。

因此,PCB存在的意义就是管理OS中存在的大量进程。

PCB是什么

PCB(process control block)进程控制块,是结构体的形式存储的。在不同的操作系统中,PCB不同,但是在Linux中叫做task_struct

task_struct中,有各种属性数据。

从知乎上截取的:

(1)标示符 : 描述本进程的唯一标识符,用来区别其他进程。

(2)状态 :任务状态,退出代码,退出信号等。

(3)优先级 :相对于其他进程的优先级。

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

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

(6)上下文数据:进程执行时处理器的寄存器中的数据。

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

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

还有一些其他信息

 接下来会对这些先简单说明下,有个初步的概念,有个感性的认知。

标识符:相当于我们的身份证号码。用来单独唯一标识我们这些人。也是便于管理

状态:一个人平时可以在学习,在运动,在娱乐,对应进程就相当于这些状态

优先级:就是先做什么事情,再做什么事情。注意与权限差别:能否做某事

程序计数器:一个程序被执行,默认是顺序结构,自顶向下一句一句执行的。但是遇到一些比如循环语句,分支语句,就要进行跳转。是由PC指针(EPC)保存当前正在被执行的下一条指令的地址,从而保证指令自顶向下执行。

上下文数据:以各种队列的方式存储在寄存器中的。

io状态信息:云服务器啊,内存多大啊,硬盘空间有多少啊,带宽啊,这些。

记账信息:进程被处理器执行了多长时间。

二 如何创建一个进程?

1 用命令行创建一个进程

首先我们用vim编写一个程序,之后编写相关的makefile,运行之后就是一个进程了。

 

查看进程

我们可以通过ps命令来查看进程 如下

 

如果带上head -1的话可以带上对应的标头

pid->当前进程id ppid->父进程的id STAT->状态

也可以用/proc的方式来查看一个进程

ls /可以查看当前有什么文件目录

ls /proc 进入当前进程目录。每一个运行中的进程都会有一个对应的属性,来标识当前所在的工作路径,是一个二进制的文件,也是一个动态的目录,会根据进程的创建和销毁来改变。

ls /proc/pid(第三行对应的数字) 查看对应的进程 进入之后就可以查看这个进程的所有属性了

 如果想要杀掉对应的进程只需要 kill-9就行了

有了对一个进程的初步认识——如何以命令行的方式创建进程以及杀死进程,一个进程到底是什么之后,我们从代码的层面来了解一下进程的创建。这就不得不谈到一个系统调用接口——fork了。

2 用代码来创建进程

fork——创建子进程的系统调用接口

① 基本使用

 基本功能:创建一个子进程,有两个返回值:给父进程返回子进程的pid,给子进程返回0。

在介绍这个功能的时候,我们为了更显著的观察到这个现象,介绍两个获取pid和ppid的函数——getpid()和getppid(),他们的返回值类型都是pid_t,一个是返回该进程的pid,一个是返回该进程父进程的pid。头文件#include<unistd.h>

 

 

 我们确实观察到了fork之后会有两个返回值,两个执行流,给父进程返回子进程的pid,给子进程返回0.

②其他问题

a 为什么有两个返回值

有两个返回值,必定是return执行了两次。也就是存在两个进程,分别执行了return语句。

我们先来了解下CPU如何执行对应的进程的。

一个个进程的代码和数据会被一个个task_struct结构体来管理,这些结构体会以链表的形式存储,形成一个运行队列,等待CPU的调度。 

CPU执行代码的时候,会根据对应的调度算法,来确定执行的顺序。当CPU执行对应的进程的时候 ,会根据task_struct来找到对应的代码和数据,执行,之后将这个进程从运行队列中拿出来,再去执行其他的进程。操作系统和CPU运行某一个进程,本质是从task_struct形成的队列中挑选一个task_struct来执行他的代码。进程调度就变成了在task_struct的队列中选择一个进程的过程。

父子进程被创建出来,哪一个先运行?不一定

运行队列会因为各种原因变化,谁先运行是完全不可控的。是由操作系统的调度器来决定的。

我的代码在用户层当中,fork是对应的接口,是个系统调用接口,是个函数,用来创建子进程谁实现的?是操作系统实现的。

在操作系统中,fork内部就是实现创建子进程的逻辑。

那么在创建进程时,操作系统需要做什么?

本质就是系统多了个进程:

要新建一个task_struct结构体,创建了对应的空间,但是内部所有属性没有创建,信息如何填充?以父进程的task_struct为模板,父进程是什么值,子进程一般就是什么值。有些会继承,有些是私有的。创建对应的代码和数据。

创建一个子进程:要给子进程创建一个task_struct操作系统来管理对应的新进程,对应的结构体变量产生之后入到系统全局的维护进程列表中,操作系统实现管理。

关于return:当我们已经准备return了,我们的核心代码执行完了。也就是说,使用fork在return最终结果的时候,子进程已经被创建出来了。

创建子进程。不仅创建进程,还要让他在对应的队列里待着。通过父进程的手,准备return的时候,子进程已经被创建并且放在了对应的运行队列中,也就是说可以被调度。已经出现了对应父子进程执行的逻辑。这个return被父子对应执行了。

因此会被return两次,有两个返回值。

但是这不代表一个变量里面会记录两次。因为一个变量被第二次写入的时候,是覆盖式写入的。这一点之后说明。

b 为什么这样返回

为什么给子进程返回0,给父进程返回子进程的pid?

一个子进程永远都只有一个父进程,但是父进程有多个子进程,父:子=1:n

父进程为了更好地控制子进程,会标识子进程:fork之后,给父进程返回子进程的pid,以进行相关的管理。

子进程只有一个对应的父进程,只需要返回0就可以了。

c if和else会被同时执行?

因为fork之后有两个进程,是2个不同的执行流,父子进程的代码是共享的,根据不同的id来执行不同的语句。

我们对一个进程是什么,有哪些属性值,怎么被os管理以及如何创建进程(命令行,代码),以及fork的使用和基本问题的认识做了一些说明。下次来深入介绍进程状态。