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/下的信息进行展示。

  • 相关阅读:
    MongoDB 删除文档
    MongoDB 删除文档
    C#标记 [已弃用] 的方法
    C#标记 [已弃用] 的方法
    MySQL 正则表达式
    MySQL 正则表达式
    SQLcase when then用法
    SQLcase when then用法
    衣服尺码自定义排序sql
    衣服尺码自定义排序sql
  • 原文地址:https://www.cnblogs.com/thammer/p/12775316.html
Copyright © 2011-2022 走看看