調度器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\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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!\n");
        return -ENOMEM;
    }

    ret = sysfs_create_group(test.kobj, &mytest_attr_group);
    if (ret) {
        pr_info("sysfs_create_group failed!\n");
        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\n", 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\n");
}

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM