zoukankan      html  css  js  c++  java
  • 调度器7—TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE

    一、D状态简介

    1. D状态的由来

    __schedule(bool preempt) {
        ...
        if (prev != next) {
            trace_sched_switch(preempt, prev, next);
        }
        ...
    }

    trace_sched_switch() 中若 prev->state 为 TASK_UNINTERRUPTIBLE,在解析后的 trace 上就显示为 D 状态。

    只要将进程状态设置为 TASK_UNINTERRUPTIBLE,然后触发任务切换将当前任务切走,此时在解析后的trace上看prev线程就是D状态的,若是 TASK_INTERRUPTIBLE,trace上看就是sleep状态。UNINTERRUPTIBLE 的意思是不被信号唤醒。

    2. 使用逻辑

    (1) 和 schedule_timeout 配合使用,延时到期后由定时器到期后由 process_timeout 函数调用 wake_up_process(timeout->task) 唤醒自己,唤醒函数中会将任务状态设置为 TASK_RUNNING。

    static int sdias_sclp_send(struct sclp_req *req) //sclp_sdias.c
    {
        for (...) {
            set_current_state(TASK_INTERRUPTIBLE);
            schedule_timeout(msecs_to_jiffies(500));
        }
    }

    (2) 和 hrtime 配合使用

    和 schedule_timeout 搭配使用的时间精度是 jiffify,精度太低。可以使用高精度定时器,定时器到期后使用 hrtimer_wakeup 来唤醒任务。

    int jbd2_journal_stop(handle_t *handle) //transaction.c
    {
        ...
        ktime_t expires = ktime_add_ns(ktime_get(), commit_time);
        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule_hrtimeout(&expires, HRTIMER_MODE_ABS);
        ....
    }

    (3) 和等待队列配合使用,当条件满足时唤醒自己

    init_waitqueue_head(&pp->wait);
    
    static int smu_release(struct inode *inode, struct file *file) //smu.c
    {
        ...
        DECLARE_WAITQUEUE(wait, current);
        add_wait_queue(&pp->wait, &wait);
    
        for (;;) {
            set_current_state(TASK_UNINTERRUPTIBLE);
            schedule();
            if (pp->cmd.status != 1)
                break;
            }
        }
        remove_wait_queue(&pp->wait, &wait);
        ...
    }
    
    wake_up_all(&pp->wait);

    先定义一个全局等待队列头 wait_queue_head_t 结构,然后再定义一个 wait_queue_entry 结构来保存需要唤醒的任务和指定唤醒函数 default_wake_function(默认),然后将 wait_queue_entry 挂在全局链表 wait_queue_head_t 上,当条件满足时调用 wake_up_all 相关函数唤醒全局链表上的任务,任务唤醒后判断条件是否满足,满足就退出,不满足就切出任务继续休眠。
    注意这里的 wait_queue_entry wait 是一个局部变量,保存在栈中,由于进程休眠后此函数没有退出,没有退栈,因此是没有问题的。

    3. 可以指定唤醒何种状态的任务

    int wake_up_state(struct task_struct *p, unsigned int state);
    int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags, int sibling_count_hint);
    /* 常用的 wake_up_q 只用户唤醒 interrupt 和 uninterruptable 类型的任务 */
    void wake_up_q(struct wake_q_head *head) {
        try_to_wake_up(task, TASK_NORMAL, 0, 1); //TASK_NORMAL == (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
    }

    这里有个参数 state,是个掩码,只唤醒此时是这个掩码包含状态的任务,与它交集为空的任务不唤醒。

    二、D状态的使用机制

    1. 大量驱动中进行自定义使用

    就是上面三种使用方式,先 set_current_state(TASK_UNINTERRUPTIBLE) 然后再将任务切走,并等待唤醒。

    2. swait/swakeup机制

    __swait_XXX 函数进入等待,swake_up_XXX 唤醒,就是对上面机制的简单封转,见 swait.c/swait.h

    3. wait/wakeup机制

    wait_event_XXX 函数进入等待,__wake_up_XXX 唤醒,就是对上面机制的简单封转,见 wait.c/wait.h

    4. wait_on_bit/wake_up_bit

    wait_on_bit_XXX 函数进入等待,wake_up_bit 等函数唤醒,就是对上面机制的简单封转,见 wait_bit.c/wait_bit.h

    5. semaphore

    /* 使用的是 TASK_UNINTERRUPTIBLE */
    extern void down(struct semaphore *sem);
    extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
    
    /* 使用的是 TASK_INTERRUPTIBLE */
    extern int __must_check down_interruptible(struct semaphore *sem);
    
    /* 使用的是 TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE) */
    extern int __must_check down_killable(struct semaphore *sem);
    
    /* 只对  sem->count - 1 进行判断 */
    extern int __must_check down_trylock(struct semaphore *sem);
    /* 使用 list_first_entry(&sem->wait_list, ...) 只唤醒wait链表上的首个任务 */
    extern void up(struct semaphore *sem);

    6. rwsem

    /* 使用的是 TASK_UNINTERRUPTIBLE */
    void __sched down_read(struct rw_semaphore *sem);
    void __sched down_write(struct rw_semaphore *sem);
    
    /* 使用的是 TASK_KILLABLE */
    int __sched down_read_killable(struct rw_semaphore *sem);
    int __sched down_write_killable(struct rw_semaphore *sem);

    读写信号量导出的函数中只使用了 TASK_UNINTERRUPTIBLE,没有使用 TASK_INTERRUPTIBLE,实现见 rwsem.c

    7. mutex

    /* 使用的是 TASK_UNINTERRUPTIBLE */
    void __sched mutex_lock(struct mutex *lock);
    
    /* 使用的是 TASK_INTERRUPTIBLE */
    int __sched mutex_lock_interruptible(struct mutex *lock)
    
    /* 使用的是 TASK_KILLABLE */
    int __sched mutex_lock_killable(struct mutex *lock)

    8. rtmutex

    /* 使用的是 TASK_UNINTERRUPTIBLE */
    void __sched rt_mutex_lock(struct rt_mutex *lock)
    
    /* 使用的是 TASK_INTERRUPTIBLE */
    int __sched rt_mutex_lock_interruptible(struct rt_mutex *lock)
    int rt_mutex_timed_lock(struct rt_mutex *lock, struct hrtimer_sleeper *timeout)

    9. completion

    /* 使用的是 TASK_UNINTERRUPTIBLE */
    void __sched wait_for_completion(struct completion *x)
    unsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout);
    void __sched wait_for_completion_io(struct completion *x)
    unsigned long __sched wait_for_completion_io_timeout(struct completion *x, unsigned long timeout)
    
    /* 使用的是 TASK_INTERRUPTIBLE */
    int __sched wait_for_completion_interruptible(struct completion *x)
    
    /* 使用的是 TASK_KILLABLE */
    int __sched wait_for_completion_killable(struct completion *x)
    long __sched wait_for_completion_killable_timeout(struct completion *x, unsigned long timeout)

    10. futex 用户空间锁

    /* 使用的是 TASK_INTERRUPTIBLE,然后使用 wake_up_q 唤醒 */
    void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q, struct hrtimer_sleeper *timeout) //futex.c

    注:以上是在 5.4 内核中检索 TASK_UNINTERRUPTIBLE,然后删除重复项得出来的,应该是比较全面。


    三、测试例子

     kernel_uninter.c:

    #define pr_fmt(fmt) "mytest: " fmt
    
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/sysfs.h>
    #include <linux/string.h>
    #include <linux/wait.h>
    #include <linux/sched.h>
    
    
    #define mytest_attr(_name) 
    static struct kobj_attribute _name##_attr = {    
        .attr    = {                
            .name = __stringify(_name),    
            .mode = 0644,            
        },                    
        .show    = _name##_show,            
        .store    = _name##_store,        
    }
    
    #define mytest_attr_ro(_name) 
    static struct kobj_attribute _name##_attr = {    
        .attr    = {                
            .name = __stringify(_name),    
            .mode = S_IRUGO,        
        },                    
        .show    = _name##_show,            
    }
    
    #define mytest_attr_wo(_name) 
    static struct kobj_attribute _name##_attr = {    
        .attr    = {                
            .name = __stringify(_name),    
            .mode = S_IWUGO,        
        },                    
        .store    = _name##_store,        
    }
    
    
    struct mytest {
        int tri_value;
        struct kobject *kobj;
        wait_queue_head_t uninter_wait;
        wait_queue_head_t inter_wait;
        wait_queue_head_t killable_wait;
    };
    
    struct mytest test;
    
    //works ok
    ssize_t uninter_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
        if (test.tri_value != 1) {
            DECLARE_WAITQUEUE(wait, current);
            add_wait_queue(&test.uninter_wait, &wait);
            for (;;) {
                set_current_state(TASK_UNINTERRUPTIBLE);
                schedule();
                pr_info("uninter pid=%d %d was waken up! state=0x%lx
    ", current->pid,
                    ((struct task_struct *)wait.private)->pid, ((struct task_struct *)wait.private)->state);
                if (test.tri_value == 1) {
                    break;
                }
            }
            remove_wait_queue(&test.uninter_wait, &wait);
        }
        return sprintf(buf, "%d
    ", test.tri_value);
    }
    mytest_attr_ro(uninter);
    
    //works bad
    ssize_t inter_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
        if (test.tri_value != 2) {
            DECLARE_WAITQUEUE(wait, current);
            add_wait_queue(&test.inter_wait, &wait);
            for (;;) {
                set_current_state(TASK_INTERRUPTIBLE);
                schedule();
                pr_info("inter pid=%d %d was waken up! state=0x%lx
    ", current->pid,
                    ((struct task_struct *)wait.private)->pid, ((struct task_struct *)wait.private)->state);
                if (test.tri_value == 2) { //process signal
                    break;
                }
            }
            remove_wait_queue(&test.inter_wait, &wait);
        }
        return sprintf(buf, "%d
    ", test.tri_value);
    }
    mytest_attr_ro(inter);
    
    //works bad
    ssize_t killable_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
        if (test.tri_value != 3) {
            DECLARE_WAITQUEUE(wait, current);
            add_wait_queue(&test.killable_wait, &wait);
            for (;;) {
                set_current_state(TASK_KILLABLE);
                schedule();
                pr_info("killable pid=%d %d was waken up! state=0x%lx
    ", current->pid,
                        ((struct task_struct *)wait.private)->pid, ((struct task_struct *)wait.private)->state);
                if (test.tri_value == 3) {
                    break;
                }
            }
            remove_wait_queue(&test.killable_wait, &wait);
        }
        return sprintf(buf, "%d
    ", test.tri_value);
    }
    mytest_attr_ro(killable);
    
    
    ssize_t trigger_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) {
        int val;
    
        if (sscanf(buf, "%d", &val) != 1) {
            return -EINVAL;
        }
        test.tri_value = val;
    
        switch(test.tri_value) {
        case 1:
            wake_up_all(&test.uninter_wait);
            break;
        case 2:
            wake_up_all(&test.inter_wait);
            break;
        case 3:
            wake_up_all(&test.killable_wait);
            break;
        default:
            break;
        }
    
        return count;
    }
    
    ssize_t trigger_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) {
        return sprintf(buf, "%d
    ", test.tri_value);
    }
    
    mytest_attr(trigger);
    
    static struct attribute *mytest_attrs[] = {
        &uninter_attr.attr,
        &inter_attr.attr,
        &killable_attr.attr,
        &trigger_attr.attr,
        NULL,
    };
    
    static struct attribute_group mytest_attr_group = {
        .name = "mytest",
        .attrs = mytest_attrs,
    };
    
    
    static int mytest_device_file_init(void) {
        int ret = 0;
    
        test.kobj = kobject_create_and_add("test", NULL);
        if (!test.kobj) {
            pr_info("kobject_create_and_add failed!
    ");
            return -ENOMEM;
        }
    
        ret = sysfs_create_group(test.kobj, &mytest_attr_group);
        if (ret) {
            pr_info("sysfs_create_group failed!
    ");
            return ret;
        }
    
        return ret;
    }
    
    static int __init mytest_init(void)
    {
        int ret;
    
        init_waitqueue_head(&test.uninter_wait);
        init_waitqueue_head(&test.inter_wait);
        init_waitqueue_head(&test.killable_wait);
    
        ret = mytest_device_file_init();
    
        pr_info("mytest_init probed! ret=%d
    ", ret);
    
        return ret;
    }
    
    static void __exit mytest_exit(void)
    {
        sysfs_remove_group(test.kobj, &mytest_attr_group);
        kobject_put(test.kobj);
        pr_info("mytest_exit removed
    ");
    }
    
    module_init(mytest_init);
    module_exit(mytest_exit);
    
    MODULE_LICENSE("GPL");

    Makefile:

    obj-m += kernel_uninter.o
    
    all:
        make -C /lib/modules/`uname -r`/build M=$(PWD)
        rm -rf *.o *.o.cmd *.ko.cmd *.order *.symvers *.mod.c .tmp_versions
    
    clean:
        rm -rf *.ko

    测试结果:

    # cat /sys/test/mytest/killable
    # kill -17 > <pid_killable>  //对SIGCHLD没有任何反应
    # kill -9 > <pid_killable> //SIGKILL信号,疯狂打印模块中加的pid等于 pid_killable 的唤醒信息
    # kill -14 > <pid_killable> //SIGALRM信号,疯狂打印模块中加的pid等于 pid_killable 的唤醒信息
    # kill -29 > <pid_killable> //SIGIO信号,疯狂打印模块中加的pid等于 pid_killable 的唤醒信息
    # echo 3 > trigger //只有这样才能救

    killable 类型的休眠,不仅对 kill 信号,而且对非 kill 信号也响应,而且若是没有返回用户空间会一直响应

    # /sys/test/mytest/uninter
    # ^C //不响应,dmesg看log无打印
    # kill -9 > <pid_uninter> //SIGKILL信号不响应

    uninterruptable 类型的休眠对任何信号都不响应,对SIGKILL、SIGBUS都不响应,任何信号都无法唤醒它

    针对 inter 和 killable 被信号持续唤醒修正:

    /* 在 break 的判断条件里面加上对信号pending的判断  */
    signal_pending(current)

    在 inter 和 killable 的唤醒位置加或上 signal_pending(current) 的判断修正后:

    # while true; do cat /sys/test/mytest/killable; done
    # kill -17 <pid_killable> //另一个终端执行,对SIGCHLD不响应。
    # kill -7 <pid_killable> //另一个终端执行,对SIGBUS不响应。
    # kill -9 <pid_killable> //另一个终端执行,对SIGKILL响应,用户空间命令只是打印“Killed”,但是并没有停止运行,且PID变更了。
    # kill -9 <pid_killable> //另一个终端执行,每次对SIGKILL的响应PID都会变更。############
    # kill -2 <pid_killable> //另一个终端执行,对SIGINT响应,用户空间也直接退出
    
    # while true; do cat /sys/test/mytest/inter; done
    # kill -9 <pid_inter> //另一个终端执行,对SIGKILL响应,用户空间命令只是打印“Killed”,但是并没有停止运行,且PID变更了。
    # kill -2 <pid_inter> //另一个终端执行,对SIGINT响应,用户空间也直接退出
    
    # cat /sys/test/mytest/uninter
    # kill -9 <pid_inter> //另一个终端执行,无响应
    # kill -2 <pid_inter> //另一个终端执行,无响应

    试验结论:

    1. 对于 TASK_INTERRUPTIBLE 和 TASK_KILLABLE 类型的信号若是不处理,信号就会一直存在,一直持续唤醒任务,拉高系统负载,把系统搞死。它需要在返回用户空间时执行TASK_UNINTERRUPTIBLE 类型的睡眠任何信号都唤醒不了,无需对是否有pending信号进行判断

    2. 用户空间的程序一直执行,每次接收到SIGKILL信号,其PID还可以一直变,但是任务一直运行不退出。

    3. 三种休眠对 SIGCHILD 都不响应。

    4. wake_up_all() 会唤醒所有等待的任务。

    四、结论

    1. 大多数机制都是支持 interrupt 和 uninterrupt 的两种进入等待方式的。内核中的锁相关机制若无特殊标识,一般是使用 TASK_UNINTERRUPTIBLE而用户空间锁机制,在内核中使用的是TASK_INTERRUPTIBLE

  • 相关阅读:
    es6异步编程 Promise 讲解 --------各个优点缺点总结
    js重新讲解继承,es5的一些继承,es6继承的改变 ----------由浅入深
    node.js里的buffer常见操作,copy,concat等实例讲解
    node.js 写流 createWriteStream----由浅入深
    node.js 读取文件--createReadStream
    Java的位运算符—— 与(&)、非(~)、或(|)、异或(^)
    XML的特殊字符处理
    mysql语句收藏
    MYSQL学习
    利用HTML5 LocalStorage实现跨页面通信channel
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15415178.html
Copyright © 2011-2022 走看看