️作者:@malloc不出对象
⛺专栏:Linux的学习之路
个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐
狭义定义:进程是正在运行的程序的实例。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
在上一篇文章中我们讲到了操作系统是如何实现对硬件资源管理的,管理的本质:先描述,再组织!!那么对于进程来说,它是操作系统的动态执行的基本单元,那么操作系统是不是也要对进程进行管理呢?
是的,操作系统也要对进程进行管理,如何管理?先描述,再组织!!
要了解进程,我们必须从程序进行切入,程序是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,例如:我们的源文件在进程预处理、编译、汇编、链接之后形成的可执行程序,它是什么?它其实就是一个普通文件被保存在磁盘中!!我们在Linux中通常需要使用./来执行这个程序,这个过程其实是在干什么?执行可执行程序这个过程其实程序就是在向进程转变!!但是我们也知道这个过程是暂时的,执行完这个程序之后就停止了。这也就表名了进程与程序的第二个不同点:进程是暂时的,而程序是永存的!!
下面我对执行程序的过程做一个简单的描述:
首先程序在运行之前必须加载到内存中,内存中保存着该程序的二进制代码和数据,在运行时转变为进程,进程将代码和数据交给CPU进行处理,CPU计算完成之后将结果带回给内存进程结束,最后内存将返回的结果输出到显示器上,这便是我们平常执行程序时经历的过程!!
我们接下来谈谈操作系统如何对进程进行管理的?先描述,再组织。
我们定义一个结构体它保存着进程的各种属性,这是描述过程,如何组织?同样的,我们可以通过一种数据结构——链表,来对每个进程进行组织,每个进程就是一个结点,这个结点也被叫做进程控制块PCB,它是进程属性的集合(在Linux中描述进程的结构体叫做task_struct)!!于是,操作系统对进程的管理本质上也就是对PCB进行增删改查等操作!!
进程 = 内核关于进程的相关数据结构 + 当前进程的代码和数据!!
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据: 进程执行时处理器的寄存器中的数据。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息等
既然进程 = 内核关于进程的相关数据结构 + 当前进程的代码和数据,下面我们就来重点讲解PCB描述进程的一些重要属性。
ps ajx | head -1 && ps axj | grep 进程 # 查看指定进程的属性,head -1显示出进程属性的第一行,ajx与axj是一样的。
除了可以通过ps指令来查看进程,我们还可以在/proc目录下查找到该进程以pid为名的目录文件:
当创建一个进程时,/proc目录下会自动创建一个以该进程pid为名的目录文件,里面包含着该进程的各项信息;当终止进程时,此时pcb不再维护该进程的各项属性,进程pid为名的目录文件自然也就不再存在!!
以上仅仅只是展示了两种查看进程属性的方式,那如果我想自己获取进程的属性呢?如何知道当前我已经创建了进程?
我们可以使用系统调用接口函数getpid函数来获取到进程的pid,知道pid了我们也就知道此时已经创建好了一个进程:
接下来我发现一个现象,我重复多次创建myproc这个进程它的pid一直是呈线性递增的,这没有任何问题,因为进程是动态的,但是为什么myproc的父进程的pid一直不变呢??
下面我们就来看看这个ppid为3591的进程它到底是什么:
bash它是命令行解释器,它本质上也是一个进程!因为它有独立的pid;另外这也可以说明一点,在命令行中启动的大部分可执行程序,最终都会变成进程,而该进程对应的父进程都是bash。为什么我们要使用bash去创建子进程替我们执行任务呢?在之前我们讲的shell原理那里我们就已经提到过这一点,这是为了保证bash的安全,当子进程出问题了,父进程bash不会受到任何的影响。
除了ctrl + c
终止我们的前台程序,我们还能使用特定的指令来杀掉进程:
kill -9 pid # 杀掉某个进程
在Linux中如何创建子进程?
在Linux中,可以使用fork()系统调用来创建子进程。该函数会创建一个与当前进程相同的子进程,并在子进程中返回0,而在父进程中返回子进程的进程ID。
下面我们来简单使用一下fork函数创建子进程:
从上图中我们确实发现fork创建了一个子进程,它是一个独立的进程,并且它的父进程就是调用fork函数的进程!!这也就说明了父子进程其实是两个独立的执行流,父进程执行一份,子进程执行一份。
但实际上fork函数很少这样用,因为它是有返回值的,下面我们先来看看fork函数的返回值:
如果fork函数调用成功,则将子进程的pid返回给父进程,0返回给子进程;如果调用失败,则返回-1给父进程,子进程创建失败!!
从上图我们确实验证了返回值的正确性,父进程接收的是子进程的pid,子进程接收的是0;但是这里的疑点很多,一个函数有两个返回值?同一个变量的地址相同但是取的内容不一致??这里等到我们讲到进程地址空间再来回答这个问题。
关于fork函数使用最多的场景其实是通常根据返回值分别来执行父子进程相应的任务,下面我们就来简单使用起来吧:
从上图我们可以看到成功调用fork函数之后,父子进程分别执行不同的任务,它们是两个独立的进程,fork成功之后变为2个执行流,所以我们才能看到使用if else if之后还能出现两份不一样的代码!!
Q:fork函数是如何创建子进程的呢?
- fork之后,执行流会变为2个执行流
- fork之后,谁先运行由调度器决定
- fork之后,fork之后的代码共享,通常我们通过if 和 else if来进行执行流分流
Q:fork如何看待代码和数据?
Q:如何理解fork出现两个返回值的情况?
关于fork的原理,在后续我们讲到进程地址空间以及进程控制我们就会理解的更加深刻了!!
本篇文章的内容就讲到这里了,如果对于本文有任何问题或者错处欢迎大家评论区相互交流orz~