【linux内核】cpu_idle_loop或者do_idle

linux3.16:

/*
 * Generic idle loop implementation
 *
 * Called with polling cleared.
 */
static void cpu_idle_loop(void)
{
	while (1) {
		/*
		 * 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()) {
			check_pgt_cache();
			rmb();

			if (cpu_is_offline(smp_processor_id()))
				arch_cpu_idle_dead();

			local_irq_disable();
			arch_cpu_idle_enter();

			/*
			 * 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())
				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();

		sched_ttwu_pending();
		schedule_preempt_disabled();
	}
}

这个函数实现了CPU的空闲循环逻辑,其主要步骤如下:
1. 调用__current_set_polling()设置current任务的polling标志。
2. 调用tick_nohz_idle_enter()通知tickless(无节拍)空闲进入。
3. 在need_resched为false时循环:
   - 调用check_pgt_cache()检查页面表缓存。
   - 调用rmb()进行读内存屏障。
   - 检查CPU是否offline,如果是则调用arch_cpu_idle_dead()。
   - 禁用本地中断。
   - 调用arch_cpu_idle_enter()进入架构专属的空闲状态。
   - 根据情况选择cpu_idle_poll()自旋或cpuidle_idle_call()进入深度空闲状态。
   - 调用arch_cpu_idle_exit()退出空闲状态。
4. 空闲退出后,通知need_resched和preempt_need_resched。
5. 调用tick_nohz_idle_exit()通知tickless空闲退出。
6. 清除当前任务polling标志。
7. 发出调度信号,调用schedule_preempt_disabled()调度。
该函数反复进入空闲和退出调度的循环,实现CPU的调度功能,是CPU空闲状态的核心逻辑。
这段代码定义了一个名为`cpu_idle_loop`的函数,它是一个无限循环函数,用于在CPU空闲时执行一些操作。

函数的具体步骤如下:
1. 设置当前CPU的轮询位为非零值(即处于轮询状态)。
2. 进入非同步延时空闲模式。
3. 在一个while循环中,检查是否需要重新调度(`need_resched()`)。如果不需要重新调度,则执行以下操作:
   - 检查页表缓存(`check_pgt_cache()`)。
   - 将CPU标记为不可中断(`local_irq_disable()`)。
   - 进入CPU空闲模式(`arch_cpu_idle_enter()`)。
   - 如果需要强制轮询或者广播设备过期,则进行轮询操作(`cpu_idle_poll()`);否则调用`cpuidle_idle_call()`函数。
   - 退出CPU空闲模式(`arch_cpu_idle_exit()`)。
4. 因为跳出了上面的循环,所以知道`TIF_NEED_RESCHED`标志必须被设置,将其传播到`PREEMPT_NEED_RESCHED`。这是必要的,因为在轮询空闲循环中,我们不会收到IPI来折叠状态。
5. 设置抢占需要的重新调度标志(`preempt_set_need_resched()`)。
6. 退出非同步延时空闲模式(`tick_nohz_idle_exit()`)。
7. 清除当前CPU的轮询位(`__current_clr_polling()`)。
8. 在SMP(对称多处理)环境中,使用原子操作保证以上修改对其他CPU可见。
9. 调用`sched_ttwu_pending()`函数,表示有挂起的任务需要重新调度。
10. 进入禁用抢占的模式(`schedule_preempt_disabled()`)。
11. 继续回到循环的开头,重复上述步骤。

这段代码的目的是在CPU空闲时执行一些操作,例如检查页表缓存、处理中断等。通过轮询机制和禁用抢占模式,可以实现对CPU的精确控制和任务调度。
这段代码是CPU空闲循环的函数实现,通常在系统没有任务需要执行时运行。这个函数会一直运行,直到系统需要CPU执行任务。

这个函数主要做以下几件事情:

设置当前CPU的polling状态,并进入nohz idle状态。
在没有需要重新调度的情况下,执行一些检查和操作,例如检查页表缓存(check_pgt_cache()),然后进入CPU空闲状态,执行arch_cpu_idle_enter()。
如果处于强制轮询模式或者tick广播设备已经过期,那么会进入cpu_idle_poll()进行轮询。否则,会进入cpuidle_idle_call()进入深度空闲状态。
当需要重新调度时(need_resched()为真),退出循环,并设置need_resched为真,以在下一次循环中重新调度。
退出nohz idle状态,并清除当前CPU的polling状态。
保证在设置need_resched之前清除polling状态,然后调用sched_ttwu_pending()进行调度,并重新调度。
这个函数的目标是在没有任务需要执行时,尽可能地让CPU保持空闲状态,以减少能源消耗和提升系统的总体性能。

linux5.16.2内核:

/*
 * 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);
}

do_idle()函数是Linux内核中CPU进入空闲状态的具体实现,其主要步骤如下:
1. 调用nohz_run_idle_balance()平衡nohz模式下的负载。
2. 设置current任务的polling标志。
3. 进入tickless模式空闲。
4. 在need_resched为false时循环:
   - 读内存屏障。
   - 检查CPU状态,若offline则停止tick并报告idle dead。
   - 通过arch_cpu_idle_enter()进入架构专属的空闲状态。
   - 根据情况选择自旋或深度休眠。
   - 通过arch_cpu_idle_exit()退出空闲。
5. 空闲退出后设置need_resched标志。
6. 退出tickless空闲模式。
7. 清除polling标志,发出调度信号。
8. 调用smp_call_function_from_idle()刷新跨CPU调用。
9. 调用schedule_idle()进行调度。
10. 检查是否有内核补丁待更新。
该函数实现了通用的CPU空闲处理逻辑,通过调用架构相关的函数完成实际进入/退出低功耗状态的操作。它维护了空闲状态与系统调度之间的桥接。
这段代码定义了一个名为do_idle的函数,用于在CPU空闲时执行一些操作。

函数的具体步骤如下:

获取当前CPU的编号。
调用nohz_run_idle_balance(cpu)函数来检查是否需要更新阻塞负载。
如果架构支持轮询机制,则设置轮询位,并进入非同步延时空闲模式。
在一个while循环中,检查是否需要重新调度。如果不需要重新调度,则执行以下操作:
屏蔽中断。
检查当前CPU是否处于离线状态。如果是,则停止tick广播,报告CPU空闲死状态,并退出arch_cpu_idle_enter()函数。
进入arch_cpu_idle_enter()函数,清除RCU中的延迟唤醒事件。
如果需要强制轮询或者tick广播已过期,则重启tick广播,并进行轮询操作;否则调用cpuidle_idle_call()函数。
退出arch_cpu_idle_exit()函数。
如果跳出了上面的循环,说明需要设置TIF_NEED_RESCHED标志,将其传播到PREEMPT_NEED_RESCHED。这是必要的,因为在轮询空闲循环中,我们不会收到IPI来折叠状态。
设置抢占需要的重新调度标志,并退出非同步延时空闲模式。
清除轮询位。
保证在RCU读端临界区之外调用flush_smp_call_function_from_idle()函数。
调用schedule_idle()函数进行调度。
如果存在klp补丁待处理,则更新补丁状态。
继续回到循环的开头,重复上述步骤。
这段代码的目的是在CPU空闲时执行一些操作,例如检查负载均衡、处理中断等。通过轮询机制和禁用抢占模式,可以实现对CPU的精确控制和任务调度。
这段代码是do_idle函数,它负责在系统没有任务需要执行时,让CPU进入空闲状态。

以下是该函数的主要步骤:

获取当前CPU的ID。
检查是否需要更新被阻塞的负载。
设置当前CPU的polling状态,并进入nohz idle状态。
在没有需要重新调度的情况下,执行一些操作,例如更新内存屏障,检查CPU是否处于offline状态。
如果处于强制轮询模式或者tick广播设备已经过期,那么会进入cpu_idle_poll()进行轮询。否则,会进入cpuidle_idle_call()进入深度空闲状态。
当需要重新调度时(need_resched()为真),退出循环,并设置need_resched为真,以在下一次循环中重新调度。
退出nohz idle状态,并清除当前CPU的polling状态。
保证在设置need_resched之前清除polling状态,然后调用sched_ttwu_pending()进行调度,并重新调度。
调用flush_smp_call_function_from_idle(),确保从idle状态发出的smp call完成。
调用schedule_idle()进入idle task。
如果当前线程有未处理的klp(Kernel Live Patch)补丁,则更新补丁状态。
这个函数的目标是在没有任务需要执行时,尽可能地让CPU保持空闲状态,以减少能源消耗和提升系统的总体性能。
Called with polling cleared.
这行注释表示调用do_idle()函数时,current任务的polling标志应该被清除。
在Linux内核中,polling标志用于表示一个任务正在自旋等待事件而不是进入睡眠状态。它通常用于在自旋锁或等待进程间通知等情况下避免任务进入睡眠。
do_idle()函数的作用是让CPU进入低功耗的空闲状态。在调用该函数前,需要确保clear当前任务的polling标志,否则CPU会持续自旋浪费能量。
调用do_idle()的地方主要有:
- cpu_idle()函数在初始化polling标志后调用do_idle()。
- idle线程的线程函数idle()会先clear polling标志,再调用do_idle()。
- 其他一些地方在通知CPU空闲前也会clear polling标志。
所以这个注释的意思是,调用do_idle()让CPU进入空闲状态前,必须确保当前任务polling标志被清除,不会发生自旋的情况,这样才能真正使CPU进入低功耗状态。
This message indicates that the polling mechanism is not active or has been cleared. It means that there is no need to check for scheduled tasks or interrupts, and the CPU can operate freely without any constraints.
Poll-based idle loops are used in some operating systems to periodically check for tasks that need to be executed, such as waiting for a packet to arrive or handling user input. By clearing the polling bit, the CPU can enter a state of idleness, free from any pending tasks or interrupts.
In summary, "Called with polling cleared" indicates that the CPU is currently in an idle state, free from any tasks or interrupts that may have been waiting to be processed.

这句话表示调用被清除了轮询标志。它的意思是轮询机制不处于活动状态或已被清除。这意味着不需要检查计划的任务或中断,并且CPU可以在没有任何约束的情况下自由操作。
基于轮询的空闲循环在一些操作系统中使用,以定期检查需要执行的任务,例如等待数据包到达或处理用户输入。通过清除轮询位,CPU可以进入空闲状态,没有任何待处理任务或中断。
总之,“Called with polling cleared”表示CPU当前处于空闲状态,没有任何等待处理的任务或中断。

猜你喜欢

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