进程基础
什么是进程?
进程的概念:程序的一个执行实例,正在执行的程序。简单来说,比如你打开了一个APP这就是一个进程,在Linux系统下,在命令行一个ls的命令也是一个进程。从内核的角度来说,进程是担当分配系统资源(CPU时间,内存)的实体。
怎么描述进程
进程的所有信息都放在一个叫做进程控制块的数据结构中,称它为PCB。Linux操作系统下的PCB称为task_struct。
每一个进程都有一个task_struct,这个结构体用来描述一个进程,里面存放着进程的各种信息。进程的PCB用一个双向链表连接,当有进程创建,就在链表上添加一个task_struct,同样当一个进程销毁,就删除一个task_struct。那task_struct中的的具体内容又都包含哪些呢?
task_struct
- 标识符:用来描述进程,每一个进程都有一个唯一的标识符,进程pid;
- 状态:任务状态,退出代码,退出信号等。
- 优先级:这么多进程,系统怎么知道先执行哪一个呢?所以当然要有优先级了,就是当前进程相对与其它进程的优先级。
- 程序计数器:程序中即将被执行的下一条指令的地址。
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据:进程执行时处理器的寄存器中的数据。
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的 。
- 记账信息:包括处理器时间的总和,使用得时钟数总和,时间限制,记账号等。
进程调度算法
- 时间片轮转调度算法(RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。
- 先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。
- 优先级调度算法(HPF):在进程等待队列中选择优先级最高的来执行。
- 多级反馈队列调度算法:将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。
- 高响应比优先调度算法:根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。
进程状态
如字面意思,就是进程的某种状态,我们要想了解进程就要知道进程的不同状态。
进程有五种状态:R,S,D,T,t,X,Z,下面具体介绍
1. R运行状态(Running):运行状态不一定就是在运行中,也有可能是在运行队列里。
2. S睡眠状态(Sleeping):进程正在等待事件的完成。也叫做可中断睡眠(interruptible sleep)。也就是说睡面可以在某些特定的情况下终止。
3. D磁盘休眠状态(Disk sleep):有时候也叫做不可中断睡眠状态(uninterruptible sleep),在这个状态下的进程通常都会等待I/O的结束
4. T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止T进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续执行。
5. X死亡状态(dead):这是 内核运行里的do_exit()函数返回的状态。这个状态只是一个返回状态,你不在任务列表里面看到这个状态。
僵尸进程(Zombies)
僵死状态:这是一个人比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵(死)尸进程。僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,你想如果有很多很多僵尸进程的话,那也就意味着这些进程都会一直占用着内存。必定会导致内存泄漏了。所以说只要子进程退出,父进程还在运行,但是父进程没有读取子进程的状态,那么子进程将会进入Z状态。
下面简单实现一下僵尸进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
pid_t pid = getpid();
pid_t id = fork();
if(id<0)
{
perror("fork");
return -1;
}
else if(id>0)
{//father
printf("father pid is:%d,return pid is:%d\n",getpid(),id);
sleep(10);
}
else if(id==0)
{
printf("child pid is:%d\n",getpid());
exit(3);//子进程退出
}
}
运行结果如下图
我们知道进程是由PCB维护的,那如果Z状态一直不退出,PCB一直都要维护。那如果父进程创建了很多个子进程,就是不回收,就会造成资源的浪费。因为数据结构要占用内存。那么该如何避免呢?
解决僵尸进程
孤儿进程
父进程提前退出,子进程就被称为“孤儿进程”。孤儿进程被1号进程领养。父亲不管儿子了,儿子当然就编程孤儿了啊。
代码实现如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
pid_t ret = fork();
if(ret<0)
{
perror("fork");
return -1;
}
if(ret==0)
{
printf("child pid is %d\n",getpid());
sleep(10);
}
if(ret>0)
{
printf("father is %d\n",getpid());
sleep(5);
exit(0);
}
}
运行结果如下