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

  • 相关阅读:
    how to pass a Javabean to server In Model2 architecture.
    What is the Web Appliation Archive, abbreviation is "WAR"
    Understaning Javascript OO
    Genetic Fraud
    poj 3211 Washing Clothes
    poj 2385 Apple Catching
    Magic Star
    关于memset的用法几点
    c++ 函数
    zoj 2972 Hurdles of 110m
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15415178.html
Copyright © 2011-2022 走看看