zoukankan      html  css  js  c++  java
  • linux内核铁三角-进程(任务)调度

    在linux中,进程是资源分配的基本单位,而线程才是调度的基本单位。
    一个在计算机上运行的程序,需要一些基本的硬件资源才能正常运行起来,包括CPU,内存,存储设备(文件),还有对所有文件进行管理的文件系统。

    在linux里面是用struct task_struct来描述一个进程资源的分配情况(线程也是用这个结构来描述,具体后面介绍)。

    进程生命周期

    在linux中,这几种状态以bit位表示

    TASK_RUNNIG

    正在执行的进程和位于就绪队列上的进程都为TASK_RUNNIG态,通过调度器从就绪队列里面选出一个合适的进程放到CPU上执行。

    TASK_INTERRUPTIBLE & TASK_UNINTERRUPTIBLE

    当这个进程由于执行过程中需要得到某个资源,例如读串口,但是这个时候串口里面没数据,那么这个进程就会被设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE的状态并且调用schedule()触发调度器进行一次调度(由串口驱动做这些动作,可以看看宋宝华驱动书里面globalfifo驱动源码)。

    一段时间后,串口里面有数据来了,在串口收数据那个部分,就会wake_up等待队列上的进程。

    wake_up_interruptible(&dev->r_wait);
    

    TASK_STOPPED

    一个正在执行的进程如果收到信号SIGSTOP(可以通过ctrl+z发出此信号,SIGSTOPSIGKILL一样,不可被捕获也不可被忽略),它会停止执行,直到收到信号SIGCONT。例如有如下程序,一个死循环

    main()
    {
    	while(1);
    }
    

    编译运行后,ps aux查看其运行状态

    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    baohua   20413 26.3  0.0   2024   276 pts/5    R+   18:24   1:52 ./a.out
    

    终端输入CTRL+Z后,a.out停止了运行,并且ps里面它的状态由R+变为了T,T表示进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行。
    发送一个SIGCONT给a.out

    kill -CONT `pidof a.out`
    

    ps aux再次查看其运行状态,变为了R,没了+,+表示进程位于前端的进程组(+ is in the foreground process group)。

    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    baohua   20591 85.1  0.0   2024   276 pts/5    R    18:47   1:43 ./a.out
    

    我在网上查这个状态时,绝大部分都是说+表示位于后台进程组,虽然这个概念无关紧要,但是总觉得怪怪的。前台不是占据着一个终端运行的程序吗?我的a.out最开始在终端上运行,后面SIGCONT唤醒后跑后台运行了。按道理是前台转后台,没+表示后台进程,所以感觉网上一些人写错了。例如
    https://blog.csdn.net/ZWE7616175/article/details/79703903
    https://blog.csdn.net/MaxineZhou/article/details/80468608
    https://zhidao.baidu.com/question/520824519127538445.html
    man ps里面找到了答案

    PROCESS STATE CODES
           Here are the different values that the s, stat and state output
           specifiers (header "STAT" or "S") will display to describe the state of
           a process:
    
                   D    uninterruptible sleep (usually IO)
                   R    running or runnable (on run queue)
                   S    interruptible sleep (waiting for an event to complete)
                   T    stopped, either by a job control signal or because it is
                        being traced
                   W    paging (not valid since the 2.6.xx kernel)
                   X    dead (should never be seen)
                   Z    defunct ("zombie") process, terminated but not reaped by
                        its parent
    
           For BSD formats and when the stat keyword is used, additional
           characters may be displayed:
    
                   <    high-priority (not nice to other users)
                   N    low-priority (nice to other users)
                   L    has pages locked into memory (for real-time and custom IO)
                   s    is a session leader
                   l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads
                        do)
                   +    is in the foreground process group
    

    也可以在a.out启动的那个终端输入fg来把后台进程唤醒,拉到前台来。在用vim编辑文件时经常这样用,在串口终端里面用vim编辑到一半的文件,突然想敲个其他命令。直接CTRL+Z,将vim切至后台,然后敲完命令,fg回到vim。

    TASK_ZOMBIE

    一个进程退出的最后阶段会调用exit()系统调用,在内核态执行的就是do_exit(),它会释放进程描述结构struct task_struct里面指向的各种资源,例如内存,打开的文件等等。但是这个调用结束后,strcut task_struct这个结构本身还有进程pid这种资源是没有释放掉的,这个时候进程就是僵死态了,它不可能被调度,因为分配给它的内存在上一步释放光了,也就无法执行这个进程的任何代码,也就无法做到自己释放自己的struct task_struct,pid等。所以这些未被释放完的资源需要其它人帮它释放,一般是它的父进程,父进程调用waitpid这一系列的函数来阻塞的等待子进程的退出,并回收子进程的尸体struct task_struct, PID这些资源。如果父进程先于子进程死,则找init进程(1号进程)做父。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char** argv)
    {
        pid_t pid;
        
        pid = fork();
        if ( pid < 0 )
        {
            printf("fork error
    ");
            exit(-1);
        }
        else if ( pid > 0)
        {
            printf("i am parent, my child is %d
    ", pid);
            sleep(200);
        }
        else
        {
            printf("i am child
    ");
        }
        
        return 0;
    }
    
    

    父进程阻塞200秒,子进程立即结束,子进程此时就是僵死态。如果父进程一直不死,也不waitpid,并且还一直不断的fork子进程,那么内核里面就一直保存着子进程的struct task_struct结构,pid也被占据着,最终导致pid耗尽。

    baohua   23207  0.0  0.0   2028   276 pts/21   S+   20:37   0:00 ./a.out
    baohua   23208  0.0  0.0      0     0 pts/21   Z+   20:37   0:00 [a.out] <defunct>
    

    僵死态的进程是无法杀死的,因为杀死进程的本质是进程通信,发送kill信号给要杀死的进程。僵死态进程名存实亡,没有进程执行的实体了。ps只不过是拿了/proc/pid/下的信息进行展示。

  • 相关阅读:
    1082 射击比赛 (20 分)
    1091 N-自守数 (15 分)
    1064 朋友数 (20 分)
    1031 查验身份证 (15 分)
    1028 人口普查 (20 分)
    1059 C语言竞赛 (20 分)
    1083 是否存在相等的差 (20 分)
    1077 互评成绩计算 (20 分)
    792. 高精度减法
    791. 高精度加法
  • 原文地址:https://www.cnblogs.com/thammer/p/12775316.html
Copyright © 2011-2022 走看看