Skip to main content
 首页 » 操作系统

Linux 调度器之调度相关结构体成员赋值

2022年07月19日27qq78292959

一、task_struct

1. p->wake_cpu

(1) 赋值位置

//kernel/sched/sched.h 
static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu) 
{ 
    set_task_rq(p, cpu); 
#ifdef CONFIG_SMP 
    /* 
     * After ->cpu is set up to a new value, task_rq_lock(p, ...) can be 
     * successfully executed on another CPU. We must ensure that updates of 
     * per-task data have been completed by this moment. 
     */ 
    smp_wmb(); 
#ifdef CONFIG_THREAD_INFO_IN_TASK 
    WRITE_ONCE(p->cpu, cpu); 
#else 
    WRITE_ONCE(task_thread_info(p)->cpu, cpu); 
#endif 
    p->wake_cpu = cpu; //这里赋值 
#endif 
}

(2) p->wake_cpu 的赋值流程:

set_task_cpu(p, new_cpu) //kernel/sched/core.c 
    __set_task_cpu(p, new_cpu);

显式调用set_task_cpu,p->wake_cpu 赋值为指定的cpu.

sched_fork //kernel/sched/core.c 
    __set_task_cpu(p, smp_processor_id());

对于fork的新任务,p->wake_cpu 赋值为当前正在执行的cpu.

wake_up_new_task //kernel/sched/core.c 
    __set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));

对于唤醒的任务,p->wake_cpu 赋值为为其选择的cpu.

    sched_init //kernel/sched/core.c 传参current, smp_processor_id(),也就是将当前线程认为是idle线程,当前cpu当做wake_cpu 
idle_init //将per-cpu的idle线程的wake_cpu赋值为自己的cpu 
    fork_idle(cpu) //fork.c 
        init_idle(idle, cpu) //kernel/sched/core.c 
            __set_task_cpu(idle, cpu);

对于idle线程初始化的时候,其 p->wake_cpu 赋值为为其选择的cpu.

migration_cpu_stop //core.c 若 p->on_rq != TASK_ON_RQ_QUEUED 时赋值 
    p->wake_cpu = arg->dest_cpu; 
__migrate_swap_task //core.c 若 p->on_rq != TASK_ON_RQ_QUEUED 时赋值 
    p->wake_cpu = cpu;

TODO: 待叙

(3) p->wake_cpu 的使用:

try_to_wake_up 
    cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);

在选核时 p->wake_cpu 作为 prev_cpu 来使用,也就是当做任务之前运行的CPU。

(4)  总结
p->wake_cpu 赋值为给任务选定的CPU,在唤醒任务时作为任务之前运行的CPU使用。

2. p->on_cpu

(1) p->on_cpu 的赋值

copy_process //fork.c 
    sched_fork //core.c 
        p->on_cpu = 0; //此时还没有唤醒任务,初始化为0 
 
init_idle //core.c 在执行 __set_task_cpu(idle, cpu) 和 rq->curr = idle 后赋值为1的。 
    idle->on_cpu = 1; 
 
__schedule //core.c 
    context_switch 
        prepare_task_switch //core.c 最先调用 
            prepare_task //core.c 
                WRITE_ONCE(next->on_cpu, 1); //在两个进程切换时才将next任务的on_cpu置位为1 
        finish_task_switch //core.c 最后调用 
            finish_task //core.c 
                smp_store_release(&prev->on_cpu, 0); /将prev任务的on_cpu置位为0

(2) 使用举例:用于判断任务是否正常CPU上运行

static inline int task_running(struct rq *rq, struct task_struct *p) //kernel/sched/sched.h 
{ 
#ifdef CONFIG_SMP 
    return p->on_cpu; 
#else 
    return task_current(rq, p); //return rq->curr == p; 
#endif 
}

(3) 注意:
在给任务选核后放到rq队列时也不会对 p->on_cpu 进行赋值的。
rq->curr == rq->idle 可以用于表示当前正在执行idle任务。

(4) 总结:
判断任务是否是正在运行。若要判断任务是否正常某个CPU上运行可以使用 if(rq->curr == p),也可以通过 p->on_cpu 判断正在运行,然后通过 task_cpu(p) 判断在某个cpu上运行。

3. p->wake_q & task->wake_q_count

(1) 直接看使用,可以参考 __mutex_unlock_slowpath() 

void wake_up_q(struct wake_q_head *head) //core.c 
{ 
    struct wake_q_node *node = head->first; 
 
    while (node != WAKE_Q_TAIL) { 
        struct task_struct *task = container_of(node, struct task_struct, wake_q); 
        node = node->next; 
        task->wake_q.next = NULL; 
        task->wake_q_count = head->count; //标记这次一共要唤醒这个等待队列上的多少个任务,在唤醒期间有值 
        wake_up_process(task); 
        task->wake_q_count = 0; //任务唤醒后请0 
        put_task_struct(task); 
    } 
}

(2) 结论:task->wake_q_count用于标记在同一个wake_q上一起唤醒的一共有多少个任务,只在唤醒过程中 p->wake_q_count 有值。p->wake_q 是要唤醒任务时通过其插入到 struct wake_q_head 单链表中。

4. p->state

1. 赋值路径

(1) 唤醒路径中赋值

try_to_wake_up 
    if (p == current) { 
        trace_sched_waking(p); 
        p->state = TASK_RUNNING; //此时当前线程正在运行,代表running状态 
        trace_sched_wakeup(p); 
    } 
    trace_sched_waking(p); 
    p->state = TASK_WAKING; //此时还没有为这个正在唤醒过程中的任务选核 
        select_task_rq 
        ttwu_queue 
            ttwu_do_activate 
                ttwu_do_wakeup 
                    p->state = TASK_RUNNING; //此时任务要等待被调度,还是runnable状态,调度运行后就表示running状态 
                    trace_sched_wakeup(p); 
 
sched_fork 
    p->state = TASK_NEW; 
    wake_up_new_task 
        p->state = TASK_RUNNING;

(2) 调度切换时赋值

__schedule 
    if (!preempt && prev_state) { 
        if (signal_pending_state(prev_state, prev)) { 
            prev->state = TASK_RUNNING; 
        } 
        ... 
    }

进程切换时,非抢占调度且 prev->state 不是 TASK_RUNNING,且此时有能唤醒它的信号pending,就将 prev 设置为TASK_RUNNING,但是后面仍然会进行切换。

2. 总结

(1) 任务无论是在runnable还是running状态,p->state 都等于 TASK_RUNNING 状态。

(2) 上面列出来的还只是 kernel/sched 目录下的赋值,除此之外还有内核机制和驱动中也会对 p->state 进行赋值。

(3) schedule() 执行路径中,在进行任务切换时,会判断 prev->state 是否等于 TASK_RUNNING,若不是,就会将prev从rq队列中移除,若是则不会被移除。若想让当前任务让出CPU并且有唤醒机制唤醒,使用方式1,若只是单纯的想此次让出cpu或没有唤醒机制,那么使用方式2。

//方式1 
set_current_state(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE); 
schedule(); 
//方式2 
set_current_state(TASK_RUNNING); //或不设置,此时状态就是 TASK_RUNNING 
schedule();

二、struct rq

1. rq->curr 

赋值位置1:

__schedule //core.c 
    RCU_INIT_POINTER(rq->curr, next) //当选出的next不等于prev时赋值

注意:在上面执行 pick_next_task 选核的流程中,rq->curr指向的还是prev任务

赋值位置2:

init_idle //core.c 
    rcu_assign_pointer(rq->curr, idle);

初始化到时init_idle时将rq->curr制定为idle线程。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/15868138.html