【Linux进程篇】进程概念(2)
目录
- 【Linux进程篇】进程概念(2)
- 进程状态
- Linux对进程的说法
- linux中的信号
- 进程状态查看
- Z(zombie)——僵尸进程
- 僵尸进程的危害
- 孤儿进程
- 进程优先级
- 基本概念
- 查看系统进程
- PRI (优先级priority)和 NI(优先级的修正数据nice)
- PRI 和 NI 的比较
- 查看进程优先级的命令
- 用top命令更改已存在进程的nice(更改优先级需要使用root用户):
- 其他概念
作者:爱写代码的刚子
时间:2023.8.7
前言:这篇博客将继续介绍进程部分的知识——进程状态。
进程状态
Linux对进程的说法
- 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
三大进程状态(不同的操作系统有不同的实现方案):
- 运行状态:并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- 阻塞状态:当一个进程需要从设备中读取数据时,但是该设备没有就绪状态是不可读的,该进程就自动将该进程PCB链入对应设备的等待队列(处于等待队列就叫做阻塞状态)(每一个设备都有等待队列),如果已经可读了,该进程就会将自己放入运行队列。进程唤醒就是将该进程的阻塞状态改为R状态,然后将该进程放入运行队列。
- 挂起状态:如果一个进程一直在等待队列中,操作系统内部的内存资源严重不足了,保证正常的情况下,要省出来内存资源,操作系统就会将该进程的PCB保留,然后将该进程的代码和数据交换到外设当中。(换出),当该进程运行时需要从磁盘中读取数据。(换入 )(现在介绍的挂起状态全称其实就是阻塞挂起状态)
下面的状态在kernel源代码里定义:
- R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠,浅度睡眠(interruptible sleep))(阻塞状态)。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态,深度睡眠(uninterruptible sleep),在这个状态的进程通常会等待IO的结束,不响应操作系统的任何请求。(进程在等待磁盘写入完毕期间,这个进程不能被任何人杀掉,当写入完毕时该进程由D状态变为R状态。当系统长时间出现D状态时,说明磁盘的压力非常大了,系统快崩溃了)(也是阻塞状态的一种)(可以使用dd命令模拟高IO的情况往磁盘中写入,这样就可以查看D状态)
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。(等待某种资源时可以理解为阻塞状态)
- t追踪状态(tracing stop):“跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作,比如在gdb中对被跟踪的进程添加一个断点。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
不同的进程状态决定了进程要做什么事情
只要PCB还在,不管代码数据在不在,该进程依旧存在
linux中的信号
- kill -l查看所有信号
进程状态查看
-
ps aux / ps axj 指令
-
前台运行
-
后台运行
-
如过要杀死后台运行的程序ctrl + c是不起效果的,需要输入kill -9 该进程PID
Z(zombie)——僵尸进程
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
也就是说:进程一般退出的时候,如果父进程没有主动回收子进程的信息,子进程会一直让自己处于Z状态(占用资源),进程的相关资源尤其是task_struct结构体不能被释放!
ptrace系统调用追踪进程运行
僵尸进程的危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),父进程如果一直不读取,那子进程就一直处于Z状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。
- 父进程如果创建了很多子进程,如果不回收,就会造成内存资源的浪费(内存泄漏)。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。
- 造成内存泄漏。
孤儿进程
-
父进程先退出,子进程就称之为“孤儿进程”
-
孤儿进程被1号init(systemd)进程领养,由init(systemd)进程回收。
-
父进程先退出示例:
-
杀死父进程后子进程变成了后台进程,子进程的PPID变为了1(即操作系统)
-
查看PID为1的进程:
-
为什么bash进程不能回收孙子进程,因为孙子进程不是由bash创建的,没有回收孙子进程的逻辑,而操作系统在内核方面就能回收。
进程优先级
补充:Linux中使用了大量的强转使不同类型的节点也能链接并访问其成员
例:用start指针找到该节点的初始地址来访问其他成员:
如果想要将该节点放入不同的数据结构中,只需要添加相应的指针即可。
基本概念
-
cpu资源分配的先后顺序,就是指进程的优先权(priority)。
-
优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可能改善系统性能。
-
还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
-
- 因为资源是有限的,进程是多个的,注定了进程之间是竞争关系,操作系统必须保证大家良性竞争所以需要确认优先级。——竞争性
-
- 如果我们进程长时间得不到CPU资源,该进程的代码长时间无法得到推进——该进程的饥饿问题
查看系统进程
在Linux或者unix系统中,用ps -l命令则会类似输出以下几个内容:
我们很容易注意到其中的几个重要信息,有下:
-
- UID : 代表执行者的身份
- UID : 代表执行者的身份
-
- PID : 代表这个进程的代号
-
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
-
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
-
- NI :代表这个进程的nice值
PRI (优先级priority)和 NI(优先级的修正数据nice)
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
- 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)(默认都为80)+nice
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值
- nice其取值范围是-20至19,一共40个级别。
【问题】那我们能不能随意修改nice值来改变我们进程的优先级呢?
技术上可以做到,但是Linux不想过多的让用户参与优先级的调整,调度器需要保证每个进程相对公平地利用CPU的资源,所以只让我们在对应的范围内做优先级的调整。nice:[-20,19] ; PRI:[60,99]
PRI 和 NI 的比较
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
- 可以理解nice值是进程优先级的修正修正数据
查看进程优先级的命令
用top命令更改已存在进程的nice(更改优先级需要使用root用户):
- top
- 进入top后按“r”->输入进程PID->输入nice值
当我们输入的NI值过大或者过小时,NI只会取其最大或者最小值,并不会让用户过分的调整。
【问题】:操作系统是如何根据优先级开展的调度呢?
通过位图结构:
(之后C++内容会提到)
【问题】如果running队列里的进程处理完了,我们也需要waiting队列里的进程运行,如何做到?
runqueue里面还维护了task_struct **run;和task_struct **run;两个指针,利用这两个指针可以将running和waiting进行交换,也就是进程调度( swap(&run , &wait); )。
所以判断队列是否为空,只能遍历数组(hash表),所以该struct runqueue还维护了bitmap isempty,定义了一个char bit[5];(含40个bit位 )所以我们可以用每一个bit位的位置来表示对应的队列是否为空。(近乎大O(1)的时间复杂度,从众多进程中挑选一个来调度,称之为Linux内核2.6的O(1)调度算法)
其他概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高
效完成任务,更合理竞争相关资源,便具有了优先级 - 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行(计算机配置高才能并行)
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间(时间片)之内,让多个进程都得以推进,称之为并发(基于进程切换基于时间片轮转的调度算法)用两个队列来保证进程就算被切下来也保证不同优先级之间的调度顺序不变。(运行完的进程将排在另一个队列中,防止该进程因为中断而导致不同优先级之间进程的相对顺序改变)
附:CPU中的寄存器:
return a -> mov eax 10
程序计数器(pc指针,eip):记录当前进程正在执行指令的下一行指令的地址(与选择、循环、跳转语句有关)
通用寄存器:eax,ebx,ecx,edx
栈帧寄存器:ebp,esp,eip
状态寄存器:status
- 为了提高效率,将进程的高频数据放入寄存器中,CPU内的寄存器保存的是进程相关的(临时)数据——进程的上下文数据。
- 进程在被切换的时候有两个动作(1. 保存上下文 2. 恢复上下文 )(这两种动作软硬件方面都有相应的处理,可以当做扩展(全局段描述符、局部段描述符等一些硬件方面的概念))
对Linux系统熟悉并不是对自己的代码数据更熟悉(C++),而是对进程相关的数据结构更熟悉。
推荐一个搜书的网站:
《深入理解Linux内核》(推荐水平高了再研究)