一、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