一.什么是进程,什么是程序
进程:程序运行的实例,是动态运行的
程序:源代码编译过后的可执行文件,一直存在,这个文件是静态的
二.操作系统如何管理进程
操作系统进程的管理分为描述(PCB)和组织(链表)
描述
操作系统通过进程控制块(PCB)来描述一个进程,进程控制块本质是一个结构体(struct task_struct),每一个进程在操作系统内核中都是一个结构体。
组织
不同进程之间通过双向链表的方式进行连接
三.PCB(struct task_struct{…}结构体中的内容)
1.进程号(PID)
进程号(PID):进程标识符,标识当前系统中的一个进程。在一个操作系统中,一个进程拥有唯一的进程号,不可重复,但进程号可重复使用。
Linux下可用ps aux | grep 目标程序名来查看我们正在运行的程序PID,或使用getpid()方法获取调用该函数进程的PID。如下图所示查找mytest程序的PID
首先确保目标程序正在运行(写一个死循环)
执行程序,发现程序已经运行
使用ps aux | grep mytest查找该程序的PID
2.进程状态
就绪态:进程已经具备运行条件,但CPU还没有分配( 进程已经做好了运行前的所以准备各种,就等着操作系统调用cpu)
运行态:进程占用CPU,并在CPU上运行,(正在使用cpu执行自己的代码)
阻塞态:进程因等待某事件发生而暂时不能运(如等待IO输入,调用某些阻塞接口)
进程状态之间的转化
注意!!!进程是抢占式执行,正常情况下都是进程多,cpu数量少,(使用top命令查看Linux系统的CPU数量)操作系统调度时希望每一个进程都能运行,但操作系统原则上想调度谁就可以调度谁,所以进程为了执行,都是抢占式执行,也就是为什么进程会有不同状态。
并行执行:多个进程在多个cpu下,同时运行各自代码。
并发执行:多个进程在一个cpu下,采用进程切换的方式,各自独占CPU运行各组的代码。
操作系统为了防止有的进程永远也抢不到CPU的情况,所以在调度的时候有各种算法:先来先服务,短作业优先,时间片轮转,高优先级优先。
3.Linux下的进程状态(STAT)的查看
R:运行状态:处于R状态的进程,有可能在执行代码(运行态),有可能在运行队列(就绪态)
S:可中断睡眠进程正在睡眠,等待资源到来时唤醒也可以通过其他进程信号或时钟中断唤醒进入运行队列
D:不可中断睡眠通常等待一个IO结束,也就是输入输出结束
T:暂停状态程序正在运行时,使用ctrl+z暂停进程不建议使用
t:跟踪状态调试程序时可以看到
X:死亡状态用户看不到,在PCB被内核释放的时候,进程会被置位x,然后进程退出
Z:僵尸状态子进程先于父进程退出,子进程处于僵尸状态。
4.程序计数器和上下文信息
进程在交替运行时会遇到进程没运行完,就要失去cpu的使用权,这种情况需要用到以下属性来保存当前进程的信息。
程序计数器
保存程序下一条指令(汇编指令),在程序切换回来时,用来回复现场,获取下一步要执行的代码。(箭头指向的地方)
ni:按汇编语言单步执行
上下文信息
保存当前寄存器当中的内容,使程序被切换回来执行时,知道之前的数据。
5.内存指针
内存指针指向程序的地址空间
操作系统种有许多进程,每一个进程都是一个task_struct结构体,在各自的结构体中,内存指针指向各自的程序地址空间。
6.IO信息
保存进程打开文件的信息。
每一个进程被创建时,都会默认打开三个文件
0号文件 stdin 标准输入:scanf, getchar
1号文件 stdout 标准输出:printf
2号文件 strerr 标准错误:perror
当一个进程被创建出来之后,操作系统会以进程号命名一个文件夹(/proc:都在此目录下),该文件夹下都是该进程的相关内容。
①首先执行一个程序
②在proc文件下出现一个和进程号相同的文件,进入
③在当前目录下存在一个fd文件,进入后就能查看到。
7.组织
操作系统中的pcb都是使用双向链表连接的
四.创建子进程
fork()函数
fork():创建出一个子进程
头文件:#inclued<unostd.h>
可以使用getppid()方法来获取调用该方法进程的父进程的进程号。
谁在调用fork(),成功之后,谁就会创建出一个子进程,子进程从fork()之后开始执行。
fork()返回值:当fork创建成功时会返回两次,在父进程中返回一次:>0;在被创建的子进程返回一次:返回0;创建失败:返回-1。
我们用代码验证一下
因此,我们可以通过fork()返回值不同的这一机制来使父子进程执行不同的代码。父进程的getppid()返回的是什么?
所有在终端创建出的进程,都是bash创建出的子进程。
在启动程序时 写**./**的目的就是告诉bash我要启动的程序在那里。
原理:
子进程拷贝父进程的PCB。
当进程调用fork(),会根据该进程的PCB,拷贝构造出子进程的PCB。子进程与父进程各自拥有独立的地址空间,在执行时,数据之间互不干扰(都取进程自己内存的数据)—拥有进程独立性。
但因为时拷贝构造,所以代码是一样的。
包括上面提到的程序计数器和上下文信息,子进程也拷贝了父进程的程序计数器和上下文信息,所以子进程会在fork()之后开始执行。
(因为父进程执行到fork(),拷贝构造出子进程,子进程拷贝了父进程的程序计数器和上下文信息,所以子进程认为自己应当从,程序计数器指向的指令继续执行。即fork()的下一条语句)
注意:一旦父进程将子进程创建出来,父子进程间也是抢占式执行,没有先后关系,父进程可能先于子进程子进程退出,使子进程成为孤儿进程,子进程也可能先于父进程退出,使父进程接收不到退出信息导致子进程成为僵尸进程。
僵尸进程与僵尸状态
僵尸进程的出现:子进程先于父进程退出,子进程就会变为僵尸进程。进程状态为Z。
代码模拟这一过程
可以看到父进程持续输出,子进程输出一次后退出。
我们看到子进程的进程号为23850,状态为Z。
+表示该进程为一个前台进程,可以阻塞bash,让bash不能处理其他问题
没有+则表示是一个后台进程,在后台运行,在该进程运行时,用户仍旧可以操作
僵尸状态原因:子进程在退出时,会告知父进程(信号),但父进程忽略处理(收到通知,但不处理),没有接收子进程的退出状态信息。导致子进程的退出状态信息处于无人回收,那操作系统中子进程的PCB就无法释放,从而子进程变为了僵尸进程。
僵尸进程的危害:子进程的PCB没有被释放,导致内存泄漏。
注意:程序员对于僵尸进程,使用kill命令无法清除,但可以kill父进程来清除僵尸进程,但不推荐.
推荐使用进程等待。
孤儿进程
孤儿进程的产生:父进程先于子进程退出,子进程就会变为孤儿进程。
Linux中没有孤儿状态!!!
代码模拟这一过程
我们观察到,当父进程退出后,子进程的父进程pid变为了1,因此当父进程退出后,子进程会被1号进程领养。
所以子进程的退出状态信息会被1号进程接收,这样子进程就不会因父进程没有接收子进程的退出状态信息而变为僵尸进程.
什么是一号进程呢?
操作系统启动的第一个进程,后续许多进程都是由该进程或该进程的子进程创建,负责操作系统的初始化.
原因:父进程先于子进程退出,既定回收子进程的父进程不在了,所以子进程变为孤儿进程。
注意:此时我们使用CTRL + C并不能使进程终止,只能使用对子进程使用kill命令