前面对于进程的学习进行了理解,但是没有进行系统的总结
对于用户来说,为什么要创建一个进程?
因为要完成某种任务,而前面又知道,进程=代码和数据+内核数据结构,对于操作系统来说,内核数据结构是利用PCB
来进行控制的,在Linux
中,PCB
具体叫做task_struct
,而前面对于task_struct
的源码也做了部分阅读,但并没有进行其他分析
那么进程的这两部分内容,都会在内存中占用一定的空间,而这些空间并不是被一直占用,当到达某个程度后,进程已经完成了自己的任务,这些内存就会被释放掉,进而操作系统将会管理其他的进程,进而进行控制
**进程的退出:**进程的退出意味着,将要释放掉自己的代码和数据
那么现在的问题是,进程是如何将自己已经完成的目的告诉操作系统?,其实也是借助了内核数据结构的功劳,从中也能看出,操作系统要进行进程的管理,必定是要对进程的数据进行管理,也能看出,管理的本质是管理数据,借助内核数据结构,操作系统可以很轻松的对进程进行管理,至于如何管理后续进行总结
那么现在的问题是,操作系统确实可以在PCB
中做标记,对于已经完成目的的进程,可以进行代码和数据的释放了,因此此时代码和数据就被通过某种手段释放掉了,但是此时的进程还不算被真正退出,因为还有PCB
还留存在内存中,这个PCB
是操作系统需要维护的,原因是这个进程的退出情况还没有被读取,还有存在的意义和价值,因此操作系统必须要对处于这种状态的进程做维护,直到父进程或者是其他控制管理的模块知道,这个进程退出了,不再需要这个进程的任何数据,这个进程才叫做真正的退出,但是如果一直不进行读取呢?
操作系统对于这样的进程,给了它一个状态表示叫做z
状态,也叫做僵尸状态,而处于僵尸状态的进程就叫做僵尸进程,而处于僵尸状态的进程如果一直没有被回收,它会保持这个状态吗,答案是肯定的,操作系统会一直维护这个PCB
结构,直到被读取后才能被真正的释放,PCB
的状态也会随之从z状态改变为x
状态,意味着这个进程真正意义上走到头了,可以被释放了
僵尸进程是有危害的,如果一直不对僵尸进程进行回收,它就会一直占用计算机内存中的存储空间,这样的行为就被叫做内存泄漏
下面对于僵尸进程进行演示,模拟一个会产生僵尸状态的场景
场景的模拟也很简单,创建两个进程,一个进程是另外一个进程的子进程,如果子进程终止了,但是父进程还在运行,此时父进程就不会对子进程进行回收,此时就是僵尸进程
那么进程代码的编写
# 完成Makefile的编写
cc=gcc
src=myproc.c
target=myproc
$(target):$(src)
gcc -o $@ $^
.PHONY:clean
clean:
rm -f $(target)
实现一个程序,完成上面的功能:
#include
#include
#include
int main()
{
pid_t id=fork();
if(id<0)
{
return 1;
}
else if(id>0)
{
// 父进程
while(1)
{
printf("i am father\n");
sleep(1);
}
}
else
{
// 子进程
int cut=5;
while(cut--)
{
printf("i am child:%d\n",cut);
sleep(1);
}
printf("child dead\n");
exit(2);
}
return 0;
}
上面这个程序就是使用了前面的思想,创建一个子进程,如果返回值是负数说明创建失败,如果返回值是0
说明是子进程,如果返回值是其他说明是父进程,此时让子进程死亡,但是父进程还在循环
创建监视脚本:
# 监视带有myproc的进程
while :; do ps ajx |head -1 && ps ajx | grep process|grep -v grep; sleep 1;echo "###################################"; done
此时运行结果:
# 当程序运行5s后的结果
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24154 1126 1126 24154 pts/0 1126 S+ 1003 0:00 ./myproc
1126 1127 1126 24154 pts/0 1126 Z+ 1003 0:00 [myproc] <defunct>
从上面可以看出是符合一开始的与其结果的,子进程编号是1127
,而父进程的编号是1126
,此时子进程死亡后,父进程依旧在运行,没有机会回收子进程,此时子进程所处的状态就是Z
,也就是所谓僵尸进程
前面的代码中可以看出,子进程结束后,父进程没有及时回收就会进入僵尸状态,但如果子进程还没结束,父进程就结束了呢?
#include
#include
#include
int main()
{
pid_t id = fork();
if (id < 0)
{
return 1;
}
else if (id ==0)
{
// 子进程
while (1)
{
printf("i am child\n");
sleep(1);
}
}
else
{
// 父进程
int cut = 5;
while (cut--)
{
printf("i am father:%d\n", cut);
sleep(1);
}
printf("father dead\n");
exit(2);
}
return 0;
}
运行上面的程序,同时监视结果:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24154 16031 16031 24154 pts/0 16031 S+ 1003 0:00 ./myproc
16031 16032 16031 24154 pts/0 16031 S+ 1003 0:00 ./myproc
###################################
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 16032 16031 24154 pts/0 24154 S 1003 0:00 ./myproc
从中可以看出一个现象,当父进程结束后,发生了两件事,父进程被回收了,同时这个父进程的子进程的ppid
变成了1
,同时程序变为后台程序运行
从中就引入了孤儿进程的概念:当子进程的父进程直接退出了,那么子进程就要被领养,变成孤儿进程,这个ppid
为1
的进程被叫做是systemd initd
进程
什么是优先级?
优先级在实际的生产生活中也有具体的体现,比如在排队时,优先级高的可以优先获取到需要的资源,而在操作系统的层次上,对于进程也是如此,优先级高的进程,将会优先得到CPU
分配的资源,而优先级高的进程还能够有优先执行的权利,配置进程的优先权对于Linux
来说是很有用的,可以改善系统的性能
同时,也可以把进程运行到指定的CPU
上,这样就能改善系统的整体性能
在Linux
中,进程的优先级的值是由pri(old)+nice
值来决定的,也就是说,用户只能通过修改nice
值来进行对于优先级值的更变,而不管如何更改,最终程序的优先级的值一定是在一个区间内的,这样是有好处的,后续进行理解
程序的优先级,实际上本质上是因为资源的不足,这是很好理解的,CPU
在同一个时刻只能处理很少的数据,但是在同一时刻会有很多很多的进程等待,因此如何对于这些进程进行调度是很重要的一件事,才有了优先级的概念
进程的优先级本质上就是PCB
中的一个数值,数值的范围是60-99
,默认的进程优先级的值是80
为什么要把优先级设定在一定的范围?
这是由于要让程序被操作系统调度的时候,可以较为均衡的让每一个进程都得到调度,都能获取到资源,而不是优先级特别高的一直获取资源,而优先级较低的长时间都得不到资源,如果进程长时间都得不到资源,会产生一个问题,这个就叫做进程饥饿
竞争性:对于系统重的进程是有很多很多的,而CPU
资源一般只有1个,因此优先级的诞生,就是要高效的完成任务,更加合理的进行相关资源的分配
独立性:进程之间的运行是不会相互干扰的,它们需要进行独立的享受各种资源
并行:对于需要进行比较大的数据处理时,会有多个CPU
同时进行运转,而这个过程就被叫做并行的概念
并发:对于多个进程在一个CPU
上运行的时候,就一定会不断地进行进程切换,而在同一个时间内,让多个进程都得到推进,这个就被叫做并发