通知cpu重调度一定要发IPI吗?
有时候我们需要让当前cpu通知其他某个cpu做重调度。比如当前cpu将一个进程enqueue到另一个cpu的rq上,这个时候我们需要通知对端的cpu这里有个任务,请做一下调度,执行新加入的任务。但是通知对端cpu重调度是否一定要发IPI?
一般而言,cpu只能管理自己内部的事务,各个cpu之间互相不干涉对方。但是有时候我们需要跟某个或者所有其他cpu说上话,告诉对方需要做些什么事情,这个时候就需要IPI来做cpu间通信。比如我们现在要讨论的RES(重调度)
在内核中smp_send_reschedule负责发送RES IPI。resched_curr会尝试让某个cpu发起重调度,里面会调用smp_send_reschedule。
void resched_curr(struct rq *rq) { struct task_struct *curr = rq->curr; int cpu; lockdep_assert_rq_held(rq); if (test_tsk_need_resched(curr)) return; cpu = cpu_of(rq); if (cpu == smp_processor_id()) { set_tsk_need_resched(curr); set_preempt_need_resched(); return; } if (set_nr_and_not_polling(curr)) smp_send_reschedule(cpu); else trace_sched_wake_idle_without_ipi(cpu); }
可以看到,smp_send_reschedule并不是一定会调用的,set_nr_and_not_polling会首先做一个判断。我们看看这里在做什么。
static inline bool set_nr_and_not_polling(struct task_struct *p) { struct thread_info *ti = task_thread_info(p); return !(fetch_or(&ti->flags, _TIF_NEED_RESCHED) & _TIF_POLLING_NRFLAG); }
set_nr_and_not_polling会设置TIF标志,然后检查_TIF_POLLING_NRFLAG是否被设置,如果没有被设置才会返回true,这时候才会调用smp_send_reschedule发送IPI,也就是当进程设置了_TIF_POLLING_NRFLAG这个标志后就不需要发送IPI。
那么_TIF_POLLING_NRFLAG这个标志是在那里设置的,为什么设置它会取消IPI发送?
搜索代码可以发现,这个标志是由__current_set_polling()函数设置的,调用它的地方跟do_idle有关。
static __always_inline void __current_set_polling(void) { arch_set_bit(TIF_POLLING_NRFLAG, (unsigned long *)(¤t_thread_info()->flags)); }
在do_idle时调用__current_set_polling,此时的current_thread_info指向swapper线程的thread_info。设置这一项只是名义上表示该进程会做监控,但是没有实际的行动。真正的行动体现在mwait中。下面是一个调用栈。

实现idle的方式有多种,mwait是目前机器的首选。mwait_idle实现如下:
static __cpuidle void mwait_idle(void) { if (need_resched()) return; x86_idle_clear_cpu_buffers(); if (!current_set_polling_and_test()) { const void *addr = ¤t_thread_info()->flags; alternative_input("", "clflush (%[addr])", X86_BUG_CLFLUSH_MONITOR, [addr] "a" (addr)); __monitor(addr, 0, 0); if (need_resched()) goto out; __sti_mwait(0, 0); raw_local_irq_disable(); } out: __current_clr_polling(); }
可以看到mwait真正监控了进程栈的flags标志所在的地址,一旦有写入操作,cpu会从mwait中唤醒。这就是无需IPI,只需通过写内存就达到通知目的的机制。但是监控操作并非由mwait指令完成,而是由mnitor指令来做。
static __always_inline void __monitor(const void *eax, unsigned long ecx, unsigned long edx) { /* "monitor %eax, %ecx, %edx;" */ asm volatile(".byte 0x0f, 0x01, 0xc8;" :: "a" (eax), "c" (ecx), "d"(edx)); }
monitor指令会监控一个地址的写操作,一旦有写到来,就会从监控pending状态退出,mwait会捕获到这个状态的改变,从而从睡眠中醒来。下面是AMD文档中monitor指令的说明。
Establishes a linear address range of memory for hardware to monitor and puts the processor in the monitor event pending state. When in the monitor event pending state, the monitoring hardware detects stores to the specified linear address range and causes the processor to exit the monitor event pending state. The MWAIT and MWAITX instructions use the state of the monitor hardware.
到这里我们可以做一个总结:通知对端cpu做重调度可以不发IPI中断,如果系统支持mwait,并且设置了_TIF_POLLING_NRFLAG,首先通过写flags来通知,节省IPI带来的开销。
浙公网安备 33010602011771号