【进程状态】

计算机的许多程序可以在一段时间内分别执行

当程序运行的时候,加载到内存,操作系统创建进程。linux下把进程task_struct放在某种数据结构进行管理。后续就要等待该进程被调度。但是程序被启动了,进程不会在一定是运行的状态。
在这里插入图片描述
进程要通过scanf函数访问键盘外设,如果不输入数据,进程就会一直在等待用户的输入,直到用户输入了数据,有了键盘资源,才会执行后面的指令和代码数据。在等待键盘资源的期间,进程不是运行的状态,而是等待的状态。所以说进程在创建,被CPU调度,等待被CPU调度,进程退出的状态都是不同的。task_struct这个结构体第一个属性就是进程的状态。

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	//进程的其他属性
}

进程创建后的主要四种状态

在这里插入图片描述
linux系统下进程的各种状态
在这里插入图片描述

就绪状态

一个进程在就绪的状态,是可以随时被CPU调度到的进程,因为一个CPU只能处理一个进程,而且计算机要有并发执行的能力,要运行的进程很多,所以就绪状态的进程会链接到一个双向的运行队列当中,当一个进程在CPU被执行完,进程退出,就可以调度运行队列里的优先级高的进程。只要进程是在运行队列runqueue中,该进程就是就绪状态(“R (running)”)。如果一个进程没有执行完,但分配给该进程的时间片已经用完,该进程就要退出CPU,然后继续到运行队列里重新就绪。如果是进程需要访问某种资源,但进程还没有得到资源,也会从CPU退出,但因为要等待资源的到来,所以该进程会在资源到来期间,不会重新链接到运行队列继续就绪,而是在一个阻塞的队列进行等待。
在这里插入图片描述

阻塞状态

程序如何阻塞的,是需要获取资源才能进行进程下一步的指向,如果资源还没有准备完成,操作系统又要保证计算机的效率,会把该进程从CPU放在等待队列,该进程的状态就会由运行状态变为阻塞的等待的状态。在Linux当中,阻塞状态称为休眠状态(“S (sleeping)”)。这个进程就好像卡住不动了。处于S状态得进程可以直接终止。Linux得D状态也是休眠状态,但该状态得进程是深度休眠得状态,该进程无法直接终止。

如何阻塞

一个运行的进程需要资源,但是资源还没到达,操作系统调用prepare_to_wait() 函数将当前进程放入等待队列并设置为不可中断的状态(TASK_UNINTERRUPTIBLE)。然后调用 schedule() 让出 CPU 让其他进程执行。当条件满足时,使用 finish_wait() 函数从等待队列中移除进程。操作系统可以向进程发送停止信号,让进程停止,暂时不运行,Linux系统下的进程的状态就是T。

在这里插入图片描述

阻塞的进程一直得不到重新调度会挂起

当阻塞的进程过多,占用的内存也就会相应的增大,如果阻塞的进程很多且的无法满足某些条件得到调度,内存已经严重不足了,操作系统为了计算机的效率,会把阻塞一些置换到外设磁盘的一个swap分区。
在这里插入图片描述
在Linux内核当中S,D,T的状态都是让进程等待。 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
当阻塞的进程得到了资源和满足继续运行的条件,等待队列的进程就会重新链接到运行队列,通过之前保存进程的上下文来执行该进程后续的代码和数据。

进程终止执行完任务后的状态

进程的作用就是帮助我们执行任务的。执行完任务就要释放资源,不能一直占据内存资源,所以进程完成任务就要释放资源。

进程结束任务的情况

  1. 进程正常结束,执行的任务的结果正确。
  2. 进程正常结束,执行的任务的结果错误。
  3. 进程异常,直接退出。

进程退出是有退出的信息的。进程是由父进程创建的,父进程管理着子进程,所以子进程退出的信息父进程是要知道的。Linux进程退出时,会把退出信息记录在自己的task_struct当中,进程执行完任务后可以把代码和数据释放,但记录退出信息的task_struct要保留下来让父进程或者操作系在这里插入图片描述
统知道。

僵尸状态

在这里插入图片描述

  1. 如果一个进程退出,没有被父进程获取或者操作系统获取,这个时候就需要OS系统维护这个退出进程的task_struct。该进程的状态会变成("Z"zombie)僵尸状态。
    子进程结束,父进程和操作系统不回收,会处于僵尸状态。即使使用命令kill -9的执行命令也无法杀掉处于一个僵尸的进程。如果僵尸进程过多不回收就会导致内存泄漏,因为进程的task_struct没有被销毁,是要占据内存空间的。只要一个进程处于Z状态,该进程task_struct就要一直存在。当父进程或操作系统把该进程task_struct的状态改为("X"dead)死亡状态,进程才能释放,但用户看不懂X整体。

在这里插入图片描述

  1. 如果是一个进程的父进程退出的话,该进程就无法在被该进程的父进程管理,该进程就会变成孤儿进程,但是要为了管理这个进程,这个进程就要被pid为1的进程管理或者交由操作系统管理,这种情况叫进程的托孤。

进程退出

进程退出是有返回的信息的。比如一个正常结束的进程,退出码是0,也是一个main函数的返回值,在linux系统下可以使用echo $?的指令来获取上一次进程的退出码。如果退出码不为0,说明该进程是异常的的。即使进程main函数是正常退出,返回值为0,但也有可能是进程正常结束,但是结果是不对的。如果进程结束的返回值不为0,不同的返回值会代表不同的错误原因。进程异常就终止的原因就是进程收到了进程异常信号,信号会以数字的形式发送。进程退出的结果就看这数字,进程退出码和信号。
Linux可以输入指令kill -l查看进程退出的信号。可以用函数接口strerror()来退出码对应的字符串信息。一共会有0到133个退出码。

kill - l
//常见的异常有除0的浮点数错误,和对空指针解引用的段错误
for(i = 0;i < 134;i++)
{
     printf("错误码:%d,错误信息: %s\n",i,strerror(i));
}

一个进程的退出就要给父进程返回这两个信息。父进程可以通过进程等待来获取子进程的这两个返回值。

进程等待

进程等待,就是为了父进程可以回收子进程的资源,避免子进程僵尸状态,造成内存泄漏。还有父进程要管理子进程,所以要等待子进程,知道子进程执行任务怎么样。通过子进程退出的两个信息判断。
进程等待可以使用系统调用
pid_t waitpid(pid_t pid, int *status, int options);
pid_t wait(int *status);
这两个系统调用。进程等待是为了回收僵尸状态的子进程的。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdbool.h>
void work()
{
    int cnt = 10;
    while(cnt)
    {
        printf("I am child process: my pid is :% d,ppid is :%d\n",getpid(),getppid());
        sleep(1);
        cnt--;
    }
    printf("child process exit\n");
}
int main()
{
    pid_t pid = fork();
    if(pid == 0)
    {
        work();
        exit(0);
        //子进程退出
    }
    int count = 15;
    while(count)
    {
        printf("I am father process: my pid is :% d,ppid is :%d\n",getpid(),getppid());
        sleep(1);
        count--;
    }
    printf("father process exit\n");
    int statu = 0;
    int n = waitpid(-1,&statu,0);
    if(n == pid)
    {
        printf("wait success\n");
    }
    printf("退出码: %d,退出信息: %s\n",(statu >> 8) & 0xFF,strerror((statu >> 8) & 0xFF));
    printf("退出信号: %d\n",statu&0x7F);
    sleep(5);
    return 0;
}

在这里插入图片描述
当子进程完成了任务,父进程还没等待子进程,子进程就是Z状态,一旦父进程等待子进程成功,子进程就会成功的被操作系统回收,Z状态消失。

进程退出信息

waitpid()是输出形参数,第一个参数传要等待的子进程pid。

1.如果大于0(即子进程pid),即等待和pid相等的子进程
2.如果小于0,即等待任意子进程退出。

第二个参数,是一个整形值的指针,输出形参数,有退出码和退出信号这两个信息。用后16位的bit位分别表示。前8位表示退出码,后8位的后低7位表示的是退出信号。用一共整形的statu来获取。
在这里插入图片描述

如果异常退出,错误码无意义。对应退出码statu右移8位按位&上0xFF,对应退出信号,按位&0x7F即可。
waitpid()函数的返回值如果大于等于0都是等待成功,但等于0,表明是等待成功但子进程还未退出,如果返回值是小于0,表明等待失败。
最后一个参数,是表示父进程以哪种方式对进程进行等待。有阻塞等待和非阻塞等待。
pid_t waitpid(pid_t pid, int *status, 0);阻塞等待
pid_t waitpid(pid_t pid, int *status, WNOHANG);非阻塞等待
非阻塞等待需要在一个轮循的方式等待,即父进程的不能退出,或者父进程要在子进程后面退出,否则,如果父进程先退出,子进程会变成孤儿进程,不在是原来的父进程管理了,由进程pid位1的进程来管理。

  • 34
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值