目录
- 前言
- 一,对PCB的理解
- 二,CPU对进程列表的处理
- 三,进程标识符:pid
- 1. 查看系统进程1: ps axj
- 2. 查看系统进程2: /proc
- 四,系统调用函数:getpid
- 五,父进程和子进程的概念
- 六,创建子进程--fork函数的使用
- 1. 创建一个子进程并观察
- 2. fork返回值的理解
- 3. 进程的独立性
前言
进程这章节的内容是我们学习linux遇到的第一座大山,它的重难点程度不亚于C++中的继承多态。由于它的内容很多,所以也会分多篇文章进行介绍。在学习进程之前,建议同学们先阅读文章对冯诺依曼体系结构和操作系统的理解。
一,对PCB的理解
先简单认识一下进程的概念:
(1) 课本概念:程序的一个执行实例,正在执行的程序等。
(2) 内核观点:担当分配系统资源(CPU时间,内存)的实体。
同学们肯定是理解不了这种概念的。
我们打开windows的任务管理器:
这些就是当前我电脑上存在的进程。
结论:操作系统中,进程可以同时存在非常多!
既然有这么多进程同时存在,那操作系统怎么管理这些进程呢?
虽然现在我们不知道什么是进程,但是我们可以通过先描述,再组织这一结论得出,操作系统管理进程肯定也是管理进程对应的数据,请看下图:
难道这些在内存里的代码和数据就是进程吗?
如果是进程,那问题又来了,这个进程被调度多久时间了,代码/数据是从哪开始到哪结束的,有多个进程时,凭什么优先调度我?这些问题都不能在上图中表示出来。
结论:对于操作系统来讲,这个被加载到内存的可执行程序根本就不是进程!它只是进程对应的代码和数据!
OS为了管理已经被加载进来的进程,都要为每个进程创建一个struct结构体(用来描述),使用链表或其他数据结构将它们链接在一起(用来组织)。
在OS学科中,每个进程都会有一个struct PCB,这个PCB中包含了每个进程的所有属性,也叫进程控制块。
struct PCB
{
// id
// 代码/函数地址
// 状态(是否被CPU处理)
// 优先级
// 内存指针(指向自己的代码和数据)
struct PCB* next
}
所以上图可修改为:
结论:
(1) PCB是在操作系统内部管理进程的内核数据结构。
(2) 进程 = PCB + 自己的代码和数据!
(3) 对进程的管理,就变成了对链表的增删查改!
讲到这里可能同学们还是有点迷糊,现在把它们和我们生活中的事物类比理解:
我们把学校类比成内存,OS类比成学校的教务管理系统,把一个学生类比为加载到内存的代码和数据,而学生在教务系统中的属性信息类比为进程中的PCB在OS的内部。
怎么证明你是这个学校的学生呢?因为你的信息在这个学校教务系统中。
所以OS对进程的管理,本质是对PCB的管理,而并非把可执行程序加载进来对可执行程序进行直接管理!
对PCB的再理解:
PCB是操作系统中的概念(linux中有PCB,windws中也有PCB…),而我们学习的linux操作系统是一款具体的操作系统,在linux系统中具体实现的PCB,叫做 task_struct。
struct task_struct
{
// linux进程控制块
}
二,CPU对进程列表的处理
进程被链接在链表中会等待CPU去PCB找数据做处理,那么CPU怎么知道要处理哪些数据呢?这不得不提到进程排队的概念:
把对应的PCB从链表中提取到队列中队,PCB中的数据不会一次性被CPU处理完,它有时被处理,有时在等待被处理,这是一种动态运行的特征,请看下图:
理解进程动态运行:
只要我们的进程task_struct将来在不同的队列中,进程就可以访问不同的资源。
结论:
(1) 调度运行进程,本质就是让进程控制块task_struct进行排队!
(2) 再给进程下一个定义:进程 = 内核task_struct + 程序的代码和数据!
三,进程标识符:pid
我们先构建一个代码样例:
让它运行起来:
1.得到第一个结论:
./xxx 本质就是系统创建进程并运行
可以得到启示:
我自己写的代码相乘的可执行文件 == 系统命令。在linux系统中大部分执行的操作,本质就是运行进程。
2.每个进程都有自己的唯一标识符,叫做进程pid。
1. 查看系统进程1: ps axj
先来查看当前系统中的进程信息:
使用指令:ps axj
这样查看的是所有的进程,很难帮助我们学习,现在我们根据上面的样例代码,所有进程中搜索我刚刚写的可执行程序:
使用指令:ps axj | grep myprocess
将进程信息的第一行打印出来:
使用指令: ps ajx | head -1
把上面进程中每行多列的含义进行对应起来:
使用指令:ps ajx | head -1 && ps axj | grep process
3.在Linux下使用指令终止进程
在我们的程序运行时,可以在运行的地方按CTRL+c来结束进程,但是还有一种方法可以结束进程:
使用指令: kill -9 要杀掉的进程id
(注:这里的-9是信号参数,直接使用即可)
2. 查看系统进程2: /proc
在Linux系统的根目录下,有一个动态文件proc,它里面存放着所有进程的信息,之所以叫动态文件是因为它会随着进程的改变而随时更新它的内容!
查看所有进程文件:
使用指令: ls /proc/
查看特点的进程文件:
使用指令: ls /proc/pid
我们每启动一个进程都会在proc文件目录下创建一个以该进程的pid为名称的文件夹。
可以发现,在自行创建的进程中有很多我们看不懂的文件,这些文件也不需要掌握,但是有两个文件需要大家注意,一个是cwd一个是exe:
1. exe指向可执行程序的位置:
进程的pcb中会记录自己对应的可执行文件的路径。
2. cwd代表当前文件:
进程的当前工作路径。
四,系统调用函数:getpid
每次查看进程使用都要使用ps指令,我感觉非常的麻烦,于是这里有一个系统调用函数可以直接返回当前进程的pid,由于操作系统是由C语言编写的,所以可以直接在程序中调用此函数:
使用函数: getpid()
使用man手册查看getpid相关信息:
使用方式:
五,父进程和子进程的概念
在使用ps指令查看进程详情时,除了pid我们可以看见左边还有一个ppid,这是parent pid的意思,也就是父进程的pid,请看下图:
再来学习一个可以查看父进程id的系统调用函数:
使用函数: getppid()
使用方式:
可以发现,每次运行时,子进程的id都在变化,然而父进程的id一直没变!这是因为在命令行中,父进程一般是命令行解释器: bash
六,创建子进程–fork函数的使用
Linux中创建进程的方式有两种:
1. 命令行中直接启动可执行程序
2. 通过代码创建进程
启动进程的本质就是创建进程,一般是通过父进程创建子进程,构成一种父子关系而命令行中启动的进程都是由bash为父进程模拟创建子进程的!
使用man指令查看fork函数信息:
1. 创建一个子进程并观察
写一个代码样例创建子进程观察情况:
2. fork返回值的理解
请大家自行使用man手册查看forh函数的返回值,这里把它翻译成中文:
理解下面的代码:
首先,fork之后,父子进程都会执行代码的本质是它们都被内存调度了,而当一个函数执行到return时,它的核心工作才算执行完成,于是我们可以想象一下fork函数内部的一些代码信息:
3. 进程的独立性