Linux终极整理笔记之进程(一)
注:参考书籍:《Unix/Linux编程实践教程》,目前已经绝版,淘宝卖的都是打印版
相关知识:操作系统
操作系统:任何一个计算机都包含一个程序的集合,成为操作系统(OS)
操作系统主要包括内核(进程管理,内存管理,文件管理,驱动管理)和其他程序(例如库函数,shell程序等等)
操作系统的功能/定位:操作系统本身是一个软件,其功能就是为了更好的管理计算机上的软硬件,同时为上层软件提供良好的执行环境
1.进程概念
- 进程概念
进程就是正在运行的程序,属于对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发,在操作系统的角度,PCB是用来描述进程,所以创建一个进程就是创建一个PCB
进程分类:用户级进程and内核级进程
用户态进程:当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。用户态不可以访问内核空间定义的对象。
内核态进程:是担当系统资源调用的实体,当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
-
查看进程信息
/proc 系统中正在运行的进程信息都保存在这个目录下
ps -la :列出所有进程及详细信息
ps -ef :通过命令查看进程的相关信息
ps aux | grep *** | grep -v grep: 查看root用户文件信息:
getpid(): 在代码中获取当前调用子进程的pid
getppid():在代码中获取当前调用父进程的ppid
查看当前用户进程状态:
fork():
通过复制调用进程来创建一个新的进程
这里说复制,就是复制调用进程的pcb
我们亲切的把创建出来的进程叫做子进程,调用进程为父进程 -
进程状态
进程大体可分为四个状态:就绪态,运行态,阻塞态,挂起态- 运行态(就绪和运行):R
- 可中断睡眠态:S
- 磁盘睡眠态(不可中断睡眠态):D
- 停止态:T
- 死亡态:X
- 追踪态:t
- 僵尸态:Z(重要)—>造成僵尸进程
-
进程的描述-----PCB
- 进程的描述信息都被放在进程控制块的结构体中,被视为进程属性的集合,也就是PCB,在操作系统中,在Linux下描述进程的结构体叫task_struct
- 这个结构体中包含了:进程标识(pid),内存指针(指向了进程在内存中代码和数据的地址),进程状态,文件状态信息,记账信息(在CPU上运行多长时间),程序计数器,上下文数据,优先级。
- 进程优先级
基本概念:在操作系统中,cpu分配资源的先后顺序,就是进程优先级
ps -l: 查看进程优先级
PRI:代表优先级权限,数值越小越先执行
NI:代表进程的nice值,表示进程可以被执行的优先级修正数值
NI&PRI的关系:PRI(new)=PRI(old)+nice
所以调整进程的优先级就是调整nice值,nice值取值范围是-20~19,一共四十个等级
其它属性:
- 竞争性:系统进程数目众多,但是cpu只有少数,甚至只有一个,要合理分配资源,便具有了进程优先级
- 独立性:多进程运行,需要独享各种资源,多运行期间各不打扰
- 并行:多个进程在多个cpu下同时进程
- 并发:多个进程在一个cpu下采用进程切换的方式轮询进行
- 程序地址空间
注:从下到上,低地址到高地址
程序是我们写的代码文件,是在硬盘上的,并没有内存文件,只有一个程序运行起来成为进程,这时候进程才会占有内存,才有进程的地址空间。
每个进程都有自己的一个进程地址空间:我们的程序一旦生成完毕,那么他的地址就固定了,运行时候就需要一块完整连续的内存,但是如果程序使用连续的物理内存,内存的利用率太低了,因此:操作系统就为每个进程画了一个区域,也就是说为每个进程都虚拟了一个完整的连续的地址空间,但是这些地址都是虚拟的,并不是物理内存的地址
而虚拟地址经过页表映射之后他们每个变量的真正的物理内存地址有可能不连续的,通过这种方式来充分利用我们的物理内存。
页表: 操作系统为每个进程都会创造一个页表来映射虚拟地址与物理地址的转换关系,属于一个中介,同时页表还有一个重要的功能:内存访问控制
2.进程控制
- 进程的创建
- fork():创建一个新的进程(子进程),通过复制调用进程(父进程)
子进程复制了父进程的pcb,也就意味他们的数据和代码都是“完全相同”,少量信息不一样 - fork有两个返回值:
对父进程来说:fork的返回值大于0,是子进程的pid
对子进程来说:fork的返回值等于0
父子进程,代码共享,数据独有
代码共享:因为子进程复制了父进程的pcb,因此他们的内存指针都是一样的,程序计数器也是一样的,因此他们运行的代码也一样,并且子进程也是从父进程创建成功之后开始运行。
父进程创建了一个子进程,也就以为子进程干的任务跟父进程完全一样,在很多情况,因为我们更希望子进程能够干其他的事情而不是跟父进程干相同的任务。
僵尸进程&孤儿进程
僵尸进程:
父进程创建了子进程,假如子进程挂了,操作系统检测到有一个进程退出了,因此操作系统并不会让这个进程直接退出,释放全部资源,而是让退出的这个进程释放一部分资源,剩下的资源要保存这个进程的退出状态,要通知这个进程的家属(父进程)说你的子进程退出了,你去看一下进程退出原因是什么,如果父进程(用wait或者waitpid查看子进程状态)看完了,这个时候就需要告诉操作系统看完了,允许释放资源了,这时候子进程的资源才会被完全释放。
假如父进程接收到了通知, 子进程退出了, 但是他没有关心子进程的退出(没有理会操作系统的通知),这时候操作系统一直得不到允许释放资源的回应,因此子进程就一直不会完全退出(占据着一一部分资源不释放),这种进程处于一个状态:僵尸态。
**危害**:资源泄露和占用进程号
孤儿进程::
父进程先于子进程退出,子进程就成了孤儿进程。运行在后台。
孤儿进程退出并不会成为僵尸进程,因为init进程很负责任,会负责接收通知,允许操作系统释放资源
僵尸进程的父进程如果退出了,那么也就意味着僵尸进程保存的退出原因没有意义了,因此僵尸进程也就被释放资源了。
僵尸进程和孤儿进程的区别:
- 僵尸进程是子进程先行退出;孤儿进程是父进程先行退出
- 僵尸进程不会释放资源;孤儿进程会释放资源
- 僵尸进程有可能造成信息泄露和占用进程号;孤儿进程没有任何危害
-
进程终止
进程终止场景:正常退出 运行完毕,结果符合预期; 运行完毕,结果不符合预期; 异常退出 崩溃,退出码就不能作为评判标准 进程的终止方式: 在main函数中return exit (int)退出码
进程退出:
1.库函数:exit,退出时会刷新缓冲区,关闭文件 2.系统调用接口:_exit,暴力退出,直接释放资源 同理于:main函数中的return 进程的退出码系统只用了一个字节来保存,返回码尽量不要大于255
-
进程等待
1.概念:进程等待就是父进程等待子进程的退出 2.目的:获取退出码,防止产生僵尸进程
1.wait :
等待任意一个子进程退出,获取子进程的退出码,返回退出的子进程pid
假如没有子进程退出,就一直等待。
阻塞:为了完成某个功能, 但是如果条件不具备就一直等待,直到具备条件,完成功能再返回
非阻塞:为了完成某个功能,但是如果条件不具备直接报错返回
2.waitpid :
等待可以指定子进程,也可等待任意子进程退出
pid_t waitpid(pid_t pid, int *status, int options);
返回值:>0 : 退出的子进程的pid
-1 : 出错
=0: 当前没有子进程退出
3.进程的退出码:
statu : 不仅仅包含子进程的退出码
statu : 只使用了低16位,这16位中,高八位保存退出码,低8位中的低7位存异常退出信号值,高1位中存储core dump标志。
WIFEXITED:通过statu判断子进程是否正常退出
WEXITSTATUS : 获取statu中的子进程退出码: