进程
进程控制块PCB。回顾:命令行上启动的进程,一般它的父进程都是bash(特殊情况除外)。
fork()之后,会有父进程和子进程两个进程在执行后续代码,即fork之后的代码被父子进程共享。
函数会返回0给子进程,返回子进程ID给父进程,创建失败则返回-1,故使用fork通常会加上if判断。
进程具有独立性、竞争性、并行、并发。并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为
并发。
进程状态/任务状态
一些状态如运行、挂起、停止等是进程的内部属性,都在PCB里(task_struct里面),与进程队列里 进程的状态不同。
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
各种进程状态,比如
运行、新建、就绪、挂起、阻塞、等待、停止、挂机、死亡...
普遍的操作系统层面如何理解上述状态?
运行状态
1、**所谓进程的不同状态,本质是 进程在不同的队列中,等待某种资源。**一般把在等待puc资源的进程队列称为运行队列,等待外设的称为阻塞队列。
2、因为cpu只有1个,但是有很多进程在等待执行,故有一个进程队列/运行队列,(1个cpu1个运行队列),
3、让线程进入队列,本质是将该进程的PCB结构体对象(task_struct)放入到运行队列,让PCB去进入队列去排队(不是让程序去排队)。
4、在运行队列runqueue里的进程,就是运行状态,R,表达的不是该进程是否正在运行(因为cpu执行速度很快,这个运行时间很短,对此衡量是无意义的)。运行状态与 有没有在cpu里跑 无关,不是说这个进程正在运行才是运行状态。
5、CPU执行速度很快,硬件执行速度比较慢,但是进程或多或少都要访问硬件。如果该硬件还没准备好,那么当有很多进程时,进程可能在等待cpu资源,也有可能在等待外设资源。
阻塞状态
该进程不能直接被cpu调用,因为该进程还在等待其他资源(比如外设资源),在等待外设资源的队列称为阻塞队列。阻塞状态都是cpu把PCB进程块对象放到不同的队列中(阻塞队列,等待队列等),在阻塞状态的进程不能被立即调度;
挂起状态
与阻塞状态相区分,假如很多进程都在阻塞状态下,意味着不会被cpu执行,而这些程序的代码和数据短期内不会使用,假设此时内存被占满了,如果此时想运行其他程序,操作系统就会保留对应的PCB,但把那些程序的代码和数据暂时保存到磁盘上,变成挂起状态。进程从挂起状态-运行状态,将进程相关的数据加载或保存到磁盘,这个过程称为内存数据的换入换出。
即阻塞不一定挂起,挂起一定阻塞。只要不是运行状态,其他状态均有可能被挂起。
linux下的状态
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
sleep–S
sleep浅度睡眠 是阻塞状态的一种。当运行test程序while(1){printf("%d", 1);}
时,使用ps axj
查看进程状态。【IO密集型程序】
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ ps axj | head -1 && ps axj | grep testPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND6106 6447 6447 6106 pts/0 6447 S+ 1001 0:00 ./test6447 6448 6447 6106 pts/0 6447 S+ 1001 0:00 ./test6357 6615 6614 6357 pts/1 6614 S+ 1001 0:00 grep --color=auto test
这个程序一直在运行,为什么它的状态是S(sleep)+呢?+表示前台进程,可以用ctrl+c
终止,或者用信号kill -9 进程ID
来终止。
因为printf是显示在显示器上,等显示器就绪要的时间比较长(IO比cpu慢)【CPU编译仅占1%时间,99%是被IO在占用】,所以如果程序有访问外设的行为时,查看进程状态大概率看到的状态都是S。
当test.c的代码只有while(1);
时,可以看到R+状态。【计算密集型程序】
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ ps ajx | head -1 && ps ajx | grep testPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND6106 10956 10956 6106 pts/0 10956 R+ 1001 0:22 ./test
10860 11023 11022 10860 pts/1 11022 R+ 1001 0:00 grep --color=auto test
stop–T
stop暂停 也算阻塞状态的一种。
kill -19 进程ID
此条命令是让程序暂停,状态会变成T
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ kill -19 10956
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ ./test[1]+ Stopped ./test
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ ps ajx | head -1 && ps ajx | grep testPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND6106 10956 10956 6106 pts/0 6106 T 1001 2:45 ./test
10860 11518 11517 10860 pts/1 11517 S+ 1001 0:00 grep --color=auto test
kill -18 进程ID
此条命令是让进程继续执行,保存R,没有+,表示后台运行,即命令行此时也可以接受其他命令输入,该进程只能用信号kill -9 进程ID
来终止。
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ kill -18 10956
[yyq@VM-8-13-centos 2023_01_02_ProcessState]$ ps ajx | head -1 && ps ajx | grep testPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND6106 10956 10956 6106 pts/0 6106 R 1001 2:57 ./test
10860 14798 14797 10860 pts/1 14797 R+ 1001 0:00 grep --color=auto test
disk sleep–D
disk sleep深度睡眠是阻塞状态的一种,无法被OS杀掉,只能通过断电、重启或者进程自己醒来解决。sleep和stop是浅度睡眠,可以被终止。(在高IO的情况下,可能看到D状态的进程。)
tracing stop–t
表示该进程正在被追踪,比如用gdb调试的程序。也是阻塞状态的一种。
dead-X
这个状态只是一个返回状态,一个进程一旦死亡,在任务列表是看不到这个状态的。
zombie–Z
正常的进程退出时,不能立即释放该进程对应的资源!要保存一段时间,让父进程或者OS来进行读取。
僵尸状态就是一个进程从退出的那一刻开始,直到被父进程读取到返回代码(让上层知道是因为什么原因退出的)的这一段时间所处的状态。这是一个问题状态。
//平时的查进程代码
//grep -v grep就可以不显示grep这个进程
ps ajx | head -1 && ps ajx | grep zombie | grep -v grep
//监控进程脚本
while :; do ps ajx | head -1 && ps ajx | grep zombie | grep -v grep; sleep 1; done
//zombie.c
//进程退出了,但是没有被回收,所处的状态就是僵尸状态
//让OS或父进程创建子进程
//父进程不退出也不读取子进程返回代码也不回收资源
//让子进程正常退出
#include
#include
#include
int main()
{pid_t id = fork();//创建子进程//不考虑创建失败的情况if(id == 0){//子进程printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());sleep(5);//睡5sexit(-1);//让子进程直接中止}else{//父进程while(1){printf("我是父进程,pid:%d, ppid:%d", getpid(), getppid());sleep(1);}}return 0;
}
运行上述两个代码,可以观察到如下结果
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
19148 6470 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombie6470 6471 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombiePPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
19148 6470 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombie6470 6471 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombie
-------出现Z状态 进程对应的名字变了+defunct失效的-----PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
19148 6470 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombie6470 6471 6470 19148 pts/0 6470 Z+ 1001 0:00 [zombie] PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
19148 6470 6470 19148 pts/0 6470 S+ 1001 0:00 ./zombie6470 6471 6470 19148 pts/0 6470 Z+ 1001 0:00 [zombie]
僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
僵尸状态会导致内存泄漏!!
孤儿进程:如果父进程先退出,子进程就是孤儿进程。会被1号init(即操作系统)进程领养,由init进程来释放资源。PID为1的进程是systemd或者initd(不同操作系统底层实现不同),I号就是操作系统。如果是前台进程创建的子进程变成孤儿了,会自动变成后台进程。被领养以后,该进程就会变成后台进程(+
不见了)。
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND1615 3964 3964 1615 pts/0 3964 S+ 1001 0:00 ./fatherless3964 3965 3964 1615 pts/0 3964 S+ 1001 0:00 ./fatherless----- 执行 kill -9 3964 -----PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND1 3965 3964 1615 pts/0 1615 S 1001 0:00 ./fatherlessPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND1 3965 3964 1615 pts/0 1615 S 1001 0:00 ./fatherless
----- 父进程变成1,+消失 -----
如果孤儿进程不由操作系统领养,那么该进程的资源就没法释放。
进程优先级
优先级priority,进程先或者后获得资源。是cpu资源分配的先后顺序。本质就是PCB里面定义的一个或几个整数。
linux支持进程在运行过程中修改优先级,通过修改NI值完成。在调整时,PRI(old)就是80为基准。
为什么存在优先级?因为资源太少了。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
//执行ps -l命令,可以看到
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行。80为基准来调整
NI :代表这个进程的nice值,表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)(即80)+nice
。这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19,一共40个级别。
注意:进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
如何修改优先级?执行top命令后,输入r(r表示renice),输入进程的PID,再输入NI值即可。
进程切换
cpu里面有寄存器,当程序要被加载到内存里,cpu通过pc(eip)指令计数器去找PCB,PCB里面有代码地址,把代码加载到内存中分析并执行。
cpu一直在做三件事:取指令;分析指令;执行指令。
当程序在运行的时候,一定会产生非常多的临时数据,这份数据属于当前进程。CPU内部虽然只有一套寄存器硬件,但是寄存器里保存的数据是属于当前进程的。寄存器硬件 不等于 寄存器内的数据。
进程在运行的时候,占有CPU,但不是一直占用到进程结束。比如死循环时,CPU也能运行别的进程。因为进程在运行的时候,都有自己的时间片(假设时间片10ms,跑完了最好,变成Z状态就不再调度了,没跑完就等下一个时间片)。
**当一个进程没跑完就被替换了,它的临时数据(上下文数据)存在哪里?**进程离开cpu,保留临时数据,称为“上下文保护”,回到cpu,恢复临时数据,称为“上下文恢复”。这两个概念就是进程切换的必经动作。
注意:寄存器不是这个进程的上下文,寄存器内的数据才是上下文。上下文保护的是数据。
寄存器被所有进程共享,但是寄存器内的数据是每个进程私有的,称为上下文数据。