【linux内核】do_idle

linux5.16.2内核源码do_idle函数

/*
 * Generic idle loop implementation
 *
 * Called with polling cleared.
 */
static void do_idle(void)
{
	int cpu = smp_processor_id();

	/*
	 * Check if we need to update blocked load
	 */
	nohz_run_idle_balance(cpu);

	/*
	 * If the arch has a polling bit, we maintain an invariant:
	 *
	 * Our polling bit is clear if we're not scheduled (i.e. if rq->curr !=
	 * rq->idle). This means that, if rq->idle has the polling bit set,
	 * then setting need_resched is guaranteed to cause the CPU to
	 * reschedule.
	 */

	__current_set_polling();
	tick_nohz_idle_enter();

	while (!need_resched()) {
		rmb();

		local_irq_disable();

		if (cpu_is_offline(cpu)) {
			tick_nohz_idle_stop_tick();
			cpuhp_report_idle_dead();
			arch_cpu_idle_dead();
		}

		arch_cpu_idle_enter();
		rcu_nocb_flush_deferred_wakeup();

		/*
		 * In poll mode we reenable interrupts and spin. Also if we
		 * detected in the wakeup from idle path that the tick
		 * broadcast device expired for us, we don't want to go deep
		 * idle as we know that the IPI is going to arrive right away.
		 */
		if (cpu_idle_force_poll || tick_check_broadcast_expired()) {
			tick_nohz_idle_restart_tick();
			cpu_idle_poll();
		} else {
			cpuidle_idle_call();
		}
		arch_cpu_idle_exit();
	}

	/*
	 * Since we fell out of the loop above, we know TIF_NEED_RESCHED must
	 * be set, propagate it into PREEMPT_NEED_RESCHED.
	 *
	 * This is required because for polling idle loops we will not have had
	 * an IPI to fold the state for us.
	 */
	preempt_set_need_resched();
	tick_nohz_idle_exit();
	__current_clr_polling();

	/*
	 * We promise to call sched_ttwu_pending() and reschedule if
	 * need_resched() is set while polling is set. That means that clearing
	 * polling needs to be visible before doing these things.
	 */
	smp_mb__after_atomic();

	/*
	 * RCU relies on this call to be done outside of an RCU read-side
	 * critical section.
	 */
	flush_smp_call_function_from_idle();
	schedule_idle();

	if (unlikely(klp_patch_pending(current)))
		klp_update_patch_state(current);
}

AI的回答

这段代码是Linux内核中的`do_idle`函数,它是CPU在空闲时执行的主要循环。以下是对这段代码的一些基本解释:
1. int cpu = smp_processor_id();:获取当前处理器的ID。
2. nohz_run_idle_balance(cpu);:检查是否需要更新阻塞负载。
3. __current_set_polling();和`tick_nohz_idle_enter();`:设置当前CPU为轮询状态,并进入无Hz空闲状态。
4. while (!need_resched()) {...}:这是一个循环,只要当前CPU不需要重新调度,就会一直执行。在这个循环中,它会执行一些操作,如禁用本地中断,检查CPU是否离线,进入CPU空闲状态,刷新RCU无回调的延迟唤醒等。
5. if (cpu_idle_force_poll || tick_check_broadcast_expired()) {...}:如果CPU被强制轮询或者检查到广播时钟已过期,那么就重新启动无Hz空闲时钟并进行CPU空闲轮询。否则,调用`cpuidle_idle_call()`进入CPU空闲状态。
6. preempt_set_need_resched();:设置需要重新调度的抢占标志。
7. __current_clr_polling();:清除当前CPU的轮询状态。
8. smp_mb__after_atomic();:在原子操作后插入内存屏障,确保清除轮询状态的操作在执行后续操作之前对所有处理器可见。
9. flush_smp_call_function_from_idle();和`schedule_idle();`:从空闲状态刷新SMP调用函数,并调度下一个空闲任务。
10. if (unlikely(klp_patch_pending(current))) klp_update_patch_state(current);:如果当前任务有待处理的内核实时补丁,那么就更新补丁状态。
这只是对代码的基本解释,具体的行为可能会根据内核配置和运行时环境有所不同。
这段代码是Linux内核中的`do_idle`函数的实现。该函数是系统的空闲循环,用于在处理器空闲时执行一些操作。
以下是对代码的解释:
1. 首先,获取当前CPU的编号。
2. 调用`nohz_run_idle_balance`函数来检查是否需要更新阻塞负载。
3. 如果架构支持轮询位(polling bit),则维护一个不变性:当当前进程不是空闲进程(即`rq->curr != rq->idle`)时,轮询位被清除。这意味着,如果`rq->idle`的轮询位被设置,那么设置`need_resched`将保证导致CPU重新调度。
4. 调用`__current_set_polling`函数来设置当前进程的轮询位。
5. 调用`tick_nohz_idle_enter`函数,进入无高精度事件计时器(tick)的空闲模式。
6. 在一个循环中,检查是否需要重新调度(即`need_resched()`是否为真)。如果不需要重新调度,则执行以下操作:
   - 执行读内存栅栏(rmb)操作,确保内存读取顺序的一致性。
   - 禁用本地中断。
   - 如果当前CPU已离线,则停止无高精度事件计时器的计时,并报告CPU离线的事件。
   - 执行特定架构的空闲进入操作。
   - 执行RCU(Read-Copy-Update)机制的延迟唤醒刷新操作。
   - 根据条件执行不同的操作:如果`cpu_idle_force_poll`为真或者检测到从空闲状态唤醒时的tick广播设备已过期,则重新启动无高精度事件计时器并进行轮询;否则,调用cpuidle_idle_call函数进入节能模式。
   - 执行特定架构的空闲退出操作。
7. 当跳出上述循环时,说明`TIF_NEED_RESCHED`必定被设置,将其传播到`PREEMPT_NEED_RESCHED`。
8. 调用`tick_nohz_idle_exit`函数,退出无高精度事件计时器的空闲模式。
9. 调用`__current_clr_polling`函数,清除当前进程的轮询位。
10. 在执行以下操作之前,通过`smp_mb__after_atomic`函数确保清除轮询位的可见性:
    - 调用`sched_ttwu_pending`函数,唤醒等待任务的进程,并进行重新调度。
    - 调用`flush_smp_call_function_from_idle`函数,刷新在空闲状态下排队的SMP(Symmetric Multi-Processing)函数调用。
    - 调用`schedule_idle`函数,进行空闲调度。
11. 如果当前进程存在未决的内核补丁,则调用`klp_update_patch_state`函数更新补丁状态。
总体上,这段代码实现了一个空闲循环,通过检查是否需要重新调度来决定执行相应的操作。它还涉及到了轮询位、节能模式和RCU机制等概念,并确保了操作的顺序和一致性。
该函数是Linux内核中用于实现空闲循环的通用实现。它的主要作用是在空闲状态下执行一些需要执行的任务,并在需要时进行调度。
首先,函数会获取当前CPU的编号,并检查是否需要更新阻塞负载。
接下来,函数会设置当前CPU的轮询位。如果架构中有一个轮询位,在调度程序没有将当前CPU调度的情况下,轮询位将被清除。这意味着,如果当前CPU的轮询位被设置,然后设置need_resched,CPU将被保证重新调度。
然后,函数会进入"finally"代码块中的循环,该循环会一直运行直到need_resched被设置。
在循环中,函数首先禁用本地中断并检查CPU是否已离线。如果CPU已离线,则会执行与离线相关的操作,并退出循环。
接下来,函数会执行一些与CPU空闲状态相关的操作,例如执行架构相关的CPU空闲操作、刷新延迟唤醒、检查是否需要进行轮询或进入深度空闲状态。
如果需要进行轮询,则重启定时器,并执行cpu_idle_poll()函数进行轮询。否则,执行cpuidle_idle_call()函数进行深度空闲状态。
当循环结束时,函数会将TIF_NEED_RESCHED设置为PREEMPT_NEED_RESCHED,然后退出空闲状态并清除轮询位。
随后,函数会执行一些调度相关的操作,如调用flush_smp_call_function_from_idle()函数来刷新SMP调用。
最后,函数会调用schedule_idle()函数进行调度,并检查当前进程是否有待处理的补丁。
do_idle()函数是Linux内核的空闲循环(idle loop)的一个通用实现。简单来说,它的主要功能是:
1. 当CPU进入空闲状态时调用,通过while循环让CPU进入低功耗状态。
2. 在循环中,调用arch_cpu_idle_enter()让CPU进入架构相关的最低功耗状态。
3. 通过need_resched()变量判断是否需要重新调度,如果需要则跳出循环。
4. 跳出循环后,通过preempt_set_need_resched()来通知可能的抢占。
5. 调用schedule_idle()进行调度。
6. 处理无盘碎片(RCU)的需求。
7. 确保在下次循环前,恢复必要的时钟中断等。
主要的功能是让CPU进入低功耗状态从而节省功耗,同时也要正确交出CPU控制权给需要运行的任务。
这是一个通用的do_idle实现,不同架构会有自己定制的arch_cpu_idle_enter()函数。该函数配合tickless idle机制可以实现更高效的CPUIDLE管理。
这段代码是Linux内核中的通用空闲循环实现。它在调用时会先清除轮询标志。
以下是该函数的主要步骤:
1. 首先,检查是否需要更新阻塞负载。
2. 接下来,如果架构支持轮询标志,这里维护了一个不变式:如果当前CPU没有被调度(即rq->curr != rq->idle),则我们的轮询标志是清除的。这意味着,如果rq->idle的轮询标志被设置,那么设置need_resched变量将确保CPU重新调度。
3. 调用tick_nohz_idle_enter()函数进入无时钟中断的空闲模式。
4. 进入一个循环,直到需要调度时退出循环。在循环中,首先执行一些必要的准备工作,如禁用本地硬件中断、检查CPU是否下线等。
5. 根据条件判断是否使用轮询模式。如果cpu_idle_force_poll变量为true或者tick_check_broadcast_expired()函数检测到时钟广播设备已过期,则进入轮询模式,并调用cpu_idle_poll()函数进行忙等待。否则,调用cpuidle_idle_call()函数进入深度空闲模式。
6. 在退出循环后,设置预期调度标志(preempt_need_resched),并调用tick_nohz_idle_exit()函数退出无时钟中断的空闲模式,并清除轮询标志。
7. 在执行调度之前,需要等待清除轮询标志的写操作完成,可以通过smp_mb__after_atomic()来实现。
8. 最后,调用flush_smp_call_function_from_idle()函数刷新空闲状态下的SMP调用函数,并调用schedule_idle()函数进行空转等待。
总体而言,这段代码实现了通用的空闲循环逻辑,根据不同情况选择进入轮询模式或深度空闲模式,同时保证调度和其他必要的操作的执行。 
这是Linux 5.16.2内核源码中的do_idle函数,用于实现一般的空闲循环。在调用该函数时,中断是被清除的。函数首先检查是否需要更新阻塞负载。然后,如果架构有一个轮询位,那么我们维护一个不变量:如果rq->curr不等于rq->idle,则我们的轮询位应该是清除的。这意味着,如果rq->idle有轮询位设置,则设置need_resched肯定会导致CPU重新调度。然后,函数设置轮询位并进入tick_nohz_idle_enter()函数。在while循环中,函数检查是否需要重新调度,然后执行一些处理,如处理硬件平台特定的空闲死亡状态。如果需要重新调度,则函数将清除轮询位并退出while循环。最后,函数调用schedule_idle()函数来调度下一个任务。
这段代码是Linux内核中用于实现CPU空闲状态的do_idle函数。当CPU处于空闲状态时,该函数会被调用以处理中断和调度任务。以下是代码的主要功能:

1. 获取当前CPU的ID。
2. 调用nohz_run_idle_balance函数来平衡阻塞负载。
3. 设置当前线程的轮询位。
4. 进入tick domain,表示当前线程正在执行一个tick事件。
5. 在没有需要重新调度的情况下,循环执行以下操作:
   a. 保存当前的上下文并禁用本地中断。
   b. 如果当前CPU处于离线状态,停止tick,报告CPU空闲,并设置CPU为空闲状态。
   c. 进入CPU空闲状态,清除RCU延迟唤醒队列。
   d. 如果需要强制轮询或者广播设备过期,重启tick并执行轮询调度;否则,执行idle call。
   e. 退出CPU空闲状态。
6. 由于跳出了循环,我们知道TIF_NEED_RESCHED必须被设置,将其传播到PREEMPT_NEED_RESCHED。
7. 设置PREEMPT_NEED_RESCHED标志。
8. 清除轮询位。
9. 调用smp_mb__after_atomic()函数,确保在原子操作之后执行此函数。
10. 调用flush_smp_call_function_from_idle()函数,将SMP调用函数从空闲状态清除。
11. 调用schedule_idle()函数进行调度。
12. 如果klp_patch_pending(current)返回真值,更新补丁状态。

猜你喜欢

转载自blog.csdn.net/eidolon_foot/article/details/133032164
今日推荐