Chinaunix首页 | 论坛 | 博客
  • 博客访问: 643585
  • 博文数量: 109
  • 博客积分: 1751
  • 博客等级: 上尉
  • 技术积分: 1806
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-31 22:37
文章分类

全部博文(109)

文章存档

2014年(9)

2013年(21)

2012年(48)

2011年(31)

分类: LINUX

2012-02-15 15:38:47

我们知道,系统运行过程中,为了降低功耗,CPU是可以进入各种省电模式(即Cx states)的。什么时候进入省电模式,如何进入省电模式,这是由idle进程来控制的。

I,idle进程的产生
idle进程是进程号为0的进程。我们通过ps可以看到最小进程号的进程是init,那是idle进程的儿子。看代码:
  1. // /init/main.c

  2. asmlinkage void __init start_kernel(void)
  3. {
  4.     ......
  5.     /* Do the rest non-__init'ed, we're now alive */
  6.     rest_init();
  7. }

  8. // /init/main.c

  9. static noinline void __init_refok rest_init(void)
  10. {
  11.     int pid;
  12.     /*
  13.      * We need to spawn init first so that it obtains pid 1, however
  14.      * the init task will end up wanting to create kthreads, which, if
  15.      * we schedule it before we create kthreadd, will OOPS.
  16.      */
  17.     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

  18.     ....
  19.     /* Call into cpu_idle with preempt disabled */
  20.     cpu_idle();
  21. }

  22. // /arch/x86/kernel/process_32.c
  23. /*
  24.  * The idle thread. There's no useful work to be
  25.  * done, so just try to conserve power and have a
  26.  * low exit latency (ie sit in a loop waiting for
  27.  * somebody to say that they'd like to reschedule)
  28.  */

  29. void cpu_idle(void)
  30. {
  31.     /* endless idle loop with no priority at all */
  32.     while (1) {
  33.             stop_critical_timings();
  34.             pm_idle();
  35.             start_critical_timings();
  36.         }
  37. }



II,cpu_idle driver而cpu_idle()函数的核心是pm_idle()函数。
pm_idle()函数是一个全局变量,它首先会在identify_cpu()函数中,即系统识别CPU时被赋值,默认是default_dile()函数。接下来cpu_idle这支driver会根据CPU的类型和内核中存在的idle driver来重新设定pm_idle全局变量。
确切的说,cpu_idle是一个驱动框架,在此框架下,不同的idle驱动可以根据硬件不同和需求不同来编写不同进入和退出Cx的行为。
1,核心数据结构首先是cpuidle_state,
  1. struct cpuidle_state {
  2.     char name[CPUIDLE_NAME_LEN];
  3.     char desc[CPUIDLE_DESC_LEN];
  4.     void *driver_data;
  5.     unsigned int flags;
  6.     unsigned int exit_latency; /* in US */
  7.     unsigned int power_usage; /* in mW */
  8.     unsigned int target_residency; /* in US */
  9.     unsigned long long usage;
  10.     unsigned long long time; /* in US */
  11.     int (*enter) (struct cpuidle_device *dev,
  12.              struct cpuidle_state *state);
  13. };
该结构描述CPU的一个Cx state,最主要的就是enter,即该state的进入函数,另外也有该state的退出延迟,消耗电能等。
然后是cpuidle_device,
  1. struct cpuidle_device {
  2.     unsigned int registered:1;
  3.     unsigned int enabled:1;
  4.     unsigned int power_specified:1;
  5.     unsigned int cpu;
  6.     int last_residency;
  7.     int state_count;
  8.     struct cpuidle_state states[CPUIDLE_STATE_MAX];
  9.     struct cpuidle_state_kobj *kobjs[CPUIDLE_STATE_MAX];
  10.     struct cpuidle_state *last_state;
  11.     struct list_head device_list;
  12.     struct kobject kobj;
  13.     struct completion kobj_unregister;
  14.     void *governor_data;
  15.     struct cpuidle_state *safe_state;
  16.     int (*prepare) (struct cpuidle_device *dev);
  17. };
该结构描述了cpu所具有的Cx states特性,包括其支持的states列表:statas[],上一个state状态:*last_state,最安全的state:*safe_state。
另外,还有一个注册driver用的cpuidle_driver,
  1. /****************************
  2.  * CPUIDLE DRIVER INTERFACE *
  3.  ****************************/
  4. struct cpuidle_driver {
  5.     char name[CPUIDLE_NAME_LEN];
  6.     struct module *owner;
  7. };
2,暴露给idle driver的接口2.1 cpuidle_register_device(struct cpuidle_device *dev)idle driver使用该接口将struct cpuidle_device注册到cpu_idle框架中。如果注册成功,在代码的最后还会调用cpuidle_install_idle_handler()函数,将上面提到的pm_idle全局变量赋为cpuidle_idle_call()。值得注意的是,cpuidle中存在一个全局变量pm_idle_old,保存之前pm_idle值,当idle driver被卸载后,pm_idle会被回复成原来的值。
2.2 cpuidle_register_driver(struct cpuidle_driver *drv)idle driver使用该接口将struct cpuidle_driver注册到cpu_idle框架中。查看cpuidle_register_driver的代码可知,cpuidle不允许多支idle driver同时运行。
3,核心流程上面提到pm_idle全局量在cpuidle_register_device()函数中被赋为cpuidle_idle_call(),那么每次idle被调度(准确的说idle进程并不参与调度,而是当可运行进程列表为空时被运行),都会进入cpuidle_idle_call()函数。
  1. /**
  2.  * cpuidle_idle_call - the main idle loop
  3.  *
  4.  * NOTE: no locks or semaphores should be used here
  5.  */
  6. static void cpuidle_idle_call(void)
  7. {
  8.     struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices);
  9.     struct cpuidle_state *target_state;
  10.     int next_state;
  11.     /* check if the device is ready */
  12.     if (!dev || !dev->enabled) {
  13.         if (pm_idle_old)
  14.             pm_idle_old();
  15.         else
  16. #if defined(CONFIG_ARCH_HAS_DEFAULT_IDLE)
  17.             default_idle();
  18. #else
  19.             local_irq_enable();
  20. #endif
  21.         return;
  22.     }
  23.     if (dev->prepare)
  24.         dev->prepare(dev);

  25.     /* ask the governor for the next state */
  26.     next_state = cpuidle_curr_governor->select(dev);
  27.     if (need_resched()) {
  28.         local_irq_enable();
  29.         return;
  30.     }
  31.     target_state = &dev->states[next_state];
  32.     /* enter the state and update stats */
  33.     dev->last_state = target_state;
  34.     trace_power_start(POWER_CSTATE, next_state, dev->cpu);
  35.     trace_cpu_idle(next_state, dev->cpu);
  36.     dev->last_residency = target_state->enter(dev, target_state);
  37.     trace_power_end(dev->cpu);
  38.     trace_cpu_idle(PWR_EVENT_EXIT, dev->cpu);
  39.     if (dev->last_state)
  40.         target_state = dev->last_state;
  41.     target_state->time += (unsigned long long)dev->last_residency;
  42.     target_state->usage++;
  43.     /* give the governor an opportunity to reflect on the outcome */
  44.     if (cpuidle_curr_governor->reflect)
  45.         cpuidle_curr_governor->reflect(dev);
  46. }
函数首先会检查cpuidle_device是否存在和使能,而后调用cpuidle_device->prepare()函数进行进行Cx模式的准备,然后通过cpuidle_curr_governor->select()函数来选择应进入的Cx级别。而后通过得到的target_state的enter函数进入sleep模式。当从Cx模式返回时,将统计数据写入target_state中,以便用户获知CPU在每个状态下所占用的时间。
4,其它idle进程在每个CPU核中都有一个。事实上cpu_idle()函数除了在start_kernel()中出现外,另一个出现的地方就是smpboot.c-start_secondary()函数末尾。而我们通过上面的代码也可以看到,函数使用了__this_cpu_read()来获取当前CPU的cpuidle_devices变量。
IV,idle driver前面说过,cpuidle只是一个框架,需要更底层idle driver来执行实际的硬件操作。在目前的kernel中,存在两支idle driver:ACPI_idle和intel_idle。
ACPI_idle driver是支持ACPI系统中默认的idle driver,按照ACPI spec实现。intel_idle driver则是为Intel的一些较新的架构(如Atom, Nehalem(i5,i7))所写的idle driver,因为Intel的CPU在电源管理部分存在很多ACPI spec之外的东西,如C3之后的state 扩展,如更高效的进入和离开Cx state的方法。
我们简单了解一下其具体实现。1,intel idle driver在nehalm平台下的实现
  1. static struct cpuidle_state nehalem_cstates[MWAIT_MAX_NUM_CSTATES] = {
  2.     { /* MWAIT C0 */ },
  3.     { /* MWAIT C1 */
  4.         .name = "C1-NHM",
  5.         .desc = "MWAIT 0x00",
  6.         .driver_data = (void *) 0x00,
  7.         .flags = CPUIDLE_FLAG_TIME_VALID,
  8.         .exit_latency = 3,
  9.         .target_residency = 6,
  10.         .enter = &intel_idle },
  11.     { /* MWAIT C2 */
  12.         .name = "C3-NHM",
  13.         .desc = "MWAIT 0x10",
  14.         .driver_data = (void *) 0x10,
  15.         .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED,
  16.         .exit_latency = 20,
  17.         .target_residency = 80,
  18.         .enter = &intel_idle },
  19.     { /* MWAIT C3 */
  20.         .name = "C6-NHM",
  21.         .desc = "MWAIT 0x20",
  22.         .driver_data = (void *) 0x20,
  23.         .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED,
  24.         .exit_latency = 200,
  25.         .target_residency = 800,
  26.         .enter = &intel_idle },
  27. };
从上面的代码可以看出,intel idle driver实际上是将APCI spec中的C1,C2,C3与nehalem架构下的C1,C3,C6对应起来,这也与我在i7z(查看i7/i5 cpu所处Cx state的开源工具)看到的一致。intel idle的主要流程在intel_idle函数中,如下:
  1. static int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
  2. {
  3.     unsigned long ecx = 1; /* break on interrupt flag */
  4.     unsigned long eax = (unsigned long)cpuidle_get_statedata(state);
  5.     unsigned int cstate;
  6.     ktime_t kt_before, kt_after;
  7.     s64 usec_delta;
  8.     int cpu = smp_processor_id();

  9.     cstate = (((eax) >> MWAIT_SUBSTATE_SIZE) & MWAIT_CSTATE_MASK) + 1;

  10.     local_irq_disable();
  11.     /*
  12.      * leave_mm() to avoid costly and often unnecessary wakeups
  13.      * for flushing the user TLB's associated with the active mm.
  14.      */
  15.     if (state->flags & CPUIDLE_FLAG_TLB_FLUSHED)
  16.         leave_mm(cpu);

  17.     if (!(lapic_timer_reliable_states & (1 << (cstate))))
  18.         clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu);

  19.     kt_before = ktime_get_real();
  20.     stop_critical_timings();
  21.     if (!need_resched()) {

  22.         __monitor((void *)&current_thread_info()->flags, 0, 0);
  23.         smp_mb();
  24.         if (!need_resched())
  25.             __mwait(eax, ecx);
  26.     }
  27.     start_critical_timings();
  28.     kt_after = ktime_get_real();
  29.     usec_delta = ktime_to_us(ktime_sub(kt_after, kt_before));

  30.     local_irq_enable();
  31.     if (!(lapic_timer_reliable_states & (1 << (cstate))))
  32.         clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu);

  33.     return usec_delta;
  34. }
代码中进行硬件操作的语句主要是__monitor()和__mwait()两个内联汇编。这两个实际上是SSE指令集中的指令,按照指令的描述,MONITOR指令告诉硬件检测某一段地址空间,MWAIT指令则使CPU进入Cx state,直到有数据向这一段地址空间写入。Cx的级别由MWAIT的参数来指定,有固定的格式。参考网址:http://siyobik.info/main/reference/instruction/MONITOR

http://siyobik.info/main/reference/instruction/MWAIT

按照oracal这这篇文章来看,这种进入Cx的方式有较低的唤醒延迟。

http://docs.oracle.com/cd/E19082-01/820-2604/ggdqv/index.html

值得注意的是,如果内核使用intel_idle,即使你通过BIOS或者内核参数关闭ACPI,或者禁用了某个Cx state,

intel_idle一样会工作。如果要禁用intel_idle,可使用内核参数"intel_idle.max_cstate=0"

网上有人碰到这样的问题:

机器响应很迟钝,打开ACPI时,发现是acpi_idle_enter_bm()吃掉了大多数时间,关闭ACPI,发现是mwait_idle()吃掉了大多数时间。

http://www.spinics.net/lists/linux-acpi/msg24058.html


2,ACPI idle driver待续
阅读(9292) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册