Android
hlchou@mail2000.com.tw
by
承襲之前的內容,本文會先把resr_init到kernel_init的流程做一個說明.
Linux Kernel針對CPU Idle與對應的省電機制,已經有滿不錯的框架,只要處理器平台開發者,根據自己的處理器架構,Power
Android除了TV產品外,多數的應用都是屬於移動裝置,而這些裝置對於省電需求,是非常殷切的.
由於筆者時間受限,本系列文章會分次刊登,還請見諒.
rest_init
這函式是Kernel初始化的最後一棒,負責的Bootstrap處理器,也會在這之後進入CPU Idle狀態,並讓系統行程Scheduling正常運作,也是我們了解Kernel時,值得清楚掌握的部份.
rest_init | 說明 |
rcu_scheduler_starting | 實作在檔案kernel/rcutree.c中, 啟動Read-Copy Update,會呼叫num_online_cpus確認目前只有bootstrap處理器在運作,以及呼叫nr_context_switches確認在啟動RCU前,沒有進行過Contex-Switch,最後就是設定rcu_scheduler_active=1啟動RCU機制. RCU在多核心架構下,不同的行程要讀取同一筆資料內容/結構,可以提供高效率的同步與正確性. 在這之後就可以使用 |
產生Kernel Thread kernel_init | Kernel Thread函式 init 產生後,會停在函式呼叫wait_for_completion中,等待kthreadd_done Signal,以便往後繼續執行下去. |
產生Kernel Thread kthreadd | Kernel Thread函式 kthreadd |
find_task_by_pid_ns | 實作在檔案kernel/pid.c中, 呼叫函式find_task_by_pid_ns,並帶入參數 |
complete | 實作在檔案kernel/sched.c中, 會送出kthreadd_done Signal,讓 |
init_idle_bootup_task | 實作在檔案kernel/sched.c中, 設定目前啟動的Task為IDLE Task. (idle->sched_class = &idle_sched_class), 在Linux下IDLE Task並不佔PID(也可以把它當作是PID 0),每個處理器都會有這樣的IDLE Task,用來在沒有行程排成時,讓處理器掉入執行的.而最基礎的省電機制,也可透過IDLE Task來進行. (包括讓系統可以關閉必要的周邊電源與Clock Gating). |
schedule(); | 實作在檔案kernel/sched.c中, //preempt_enable_no_resched/preempt_disable(); 啟動Linux Kernel Process的排成Context-Switch機制. |
cpu_idle(); | 實作在檔案arch/arm/kernel/process.c中, 這是處理器IDLE Task的主函式,我們會在稍後進一步說明,走到這,屬於Booting到IDLE Task的流程就算是初步結束了. |
kernel_init
實作在檔案
kernel_init 初始化函式的流程 | 說明 |
wait_for_completion | 實作在檔案kernel/sched.c中, 會呼叫函式wait_for_completion,等待Kernel Thread kthreadd (PID=2)產生完畢. |
init can run on any cpu | |
set_cpus_allowed_ptr | 實作在檔案kernel/sched.c中, 透過這函式可以設定CPU bitmask,限定Task只能在特定的處理器上運作.而在init中,會設定為cpu_all_mask (= to_cpumask(cpu_all_bits)), |
cad_pid = task_pid(current); | 呼叫task_pid (以inline實作在檔案include/linux/sched.h中),設定目前current的init Task PID給cad_pid (也就是要用來接收”ctrl-alt-del” Reboot Signal的Process ID, 最後會呼叫函式ctrl_alt_del (in kernel/sys.c),並確認C_A_D是否為1,以便執行kernel_restart流程. |
smp_prepare_cpus | 實作在檔案arch/arm/kernel/smp.c中, 呼叫函式smp_prepare_cpus時,會以全域變數setup_max_cpus為函式參數max_cpus,用以表示在編譯核心時,設定支援的最大CPU數量. 首先,會透過函式num_possible_cpus(=cpumask_weight(cpu_possible_mask) ,in include/linux/cpumask.h)取得目前系統存在的處理器數量, 而Kernel中的全域變數setup_max_cpus初始值為NR_CPUS (參考檔案include/linux/threads.h 呼叫函式percpu_timer_setup,設定目前處理器的Local Timer. 呼叫函式platform_smp_prepare_cpus (in arch/arm/mach-tegra/platsmp.c),依據max_cpus結果,透過函式set_cpu_present (in kernel/cpu.c), |
do_pre_smp_initcalls | 實作在檔案init/main.c中, 會透過函式do_one_initcall,執行介於Symbol 如下為arch/arm/kernel/vmlinux.lds中的Symbol區間內容, __initcall_start 以筆者編譯的結果來說,會執行有透過early_initcall spawn_ksoftirqd init_workqueues (in kernel/workqueue.c), init_call_single_data (in kernel/smp.c), cpu_stop_init (in kernel/stop_machine.c), ….etc |
smp_init | 實作在檔案kernel/smp.c中, 這函式主要是由Bootstrap處理器,進行Active多核心架構下其它的處理器. 如果發生Online的處理器個數(from num_online_cpus)超過在核心編譯時,所設定的最大處理器個數 如果該處理器目前屬於Present |
sched_init_smp | 實作在檔案kernel/sched.c中, 1,呼叫get_online_cpus,如果目前CPU Hotplug Active Write行程是自己,就直接返回.反之就把 2,取得Mutex Lock “sched_domains_mutex” 3,呼叫arch_init_sched_domains,設定scheduler domains與groups,參考Linux Documentation/scheduler/sched-domains.txt文件,一個 4,釋放Mutex Lock “sched_domains_mutex” 5,呼叫put_online_cpus,如果目前CPU Hotplug Active Writer行程是自己,就直接返回.反之就把 6,註冊CPU Notifier cpuset_cpu_active/cpuset_cpu_inactive/update_runtime 7,呼叫set_cpus_allowed_ptr,透過這函式可以設定CPU bitmask,限定Task只能在特定的處理器上運作.在這會用參數”non_isolated_cpus”,也就是會把 8,呼叫sched_init_granularity,透過函式update_sysctl,讓sysctl_sched_min_granularity=normalized_sysctl_sched_min_granularity,sysctl_sched_latency=normalized_sysctl_sched_latency, |
do_basic_setup | 實作在檔案init/main.c中, 1,呼叫usermodehelper_init (in kernel/kmod.c),產生khelper workqueue. 2,呼叫init_tmpfs (in mm/shmem.c),對VFS註冊Temp FileSystem. 3,呼叫driver_init (in drivers/base/init.c),初始化Linux Kernel Driver System Model. 4,呼叫init_irq_proc(in kernel/irq/proc.c),初始化 5,呼叫do_ctors (in init/main.c),執行位於Symbol __ctors_start 6,透過函式do_initcalls,執行介於Symbol 如下為arch/arm/kernel/vmlinux.lds中的Symbol區間內容, __early_initcall_end 以筆者編譯的結果來說,會執行有透過__initcall (0….added by pure_initcall….) init_atomic64_lock (1….added by core_initcall….) ptrace_break_init consistent_init v6_userpage_init alloc_frozen_cpus sysctl_init ksysfs_init init_jiffies_clocksource pm_init init_zero_pfn fsnotify_init filelock_init init_script_binfmt nit_elf_binfmt random32_init (2….added by postcore_initcall….) tegra_gpio_init tegra_dma_init bdi_class_init tty_class_init vtconsole_class_init wakeup_sources_debugfs_init (3….added by arch_initcall….) customize_machine exceptions_init (4….added by subsys_initcall….) topology_init4 param_sysfs_init4 default_bdi_init4 init_bio4 fsnotify_notification_init4 blk_settings_init4 blk_ioc_init4 blk_softirq_init4 blk_iopoll_setup4 genhd_device_init4 misc_init4 serio_init4 input_init4 hwmon_init4 (5….added by fs_initcall….) proc_cpu_init5 dma_debug_do_init5 alignment_init5 clocksource_done_booting5 init_pipe_fs5 eventpoll_init5 anon_inode_init5 blk_scsi_ioctl_init5 chr_dev_init5 firmware_class_init5 (rootfs….added by rootfs_initcall….) default_rootfs (6….added by device_initcall….) timer_init_sysfs6 register_pmu_driver6 proc_execdomains_init6 ioresources_init6 uid_cache_init6 init_posix_timers6 init_posix_cpu_timers6 nsproxy_cache_init6 timekeeping_init_ops6 init_clocksource_sysfs6 init_timer_list_procfs6 futex_init6 kallsyms_init6 user_namespaces_init6 pid_namespaces_init6 utsname_sysctl_init6 init_per_zone_wmark_min6 kswapd_init6 setup_vmstat6 mm_sysfs_init6 proc_vmalloc_init6 procswaps_init6 slab_proc_init6 slab_sysfs_init6 fcntl_init6 proc_filesystems_init6 fsnotify_mark_init6 dnotify_init6 inotify_user_setup6 aio_setup6 proc_locks_init6 proc_cmdline_init6 proc_consoles_init6 proc_cpuinfo_init6 proc_devices_init6 proc_interrupts_init6 proc_loadavg_init6 proc_meminfo_init6 proc_stat_init6 proc_uptime_init6 proc_version_init6 proc_softirqs_init6 proc_kmsg_init6 proc_page_init6 init_devpts_fs6 init_ramfs_fs6 proc_genhd_init6 bsg_init6 noop_init6 deadline_init6 cfq_init6 percpu_counter_startup6 pty_init6 rand_initialize6 topology_sysfs_init6 fusb300_udc_init6 init6 serport_init6 mousedev_init6 atkbd_init6 psmouse_init6 hid_init6 (7….added by late_initcall….) init_oops_id7 printk_late_init7 pm_qos_power_init7 max_swapfiles_check7 random32_reseed7 seqgen_init7 |
sys_open | 透過sys_open以Read/Write模式開啟Console "/dev/console", 由於系統在此時沒有任何檔案開啟,這個返回一定是 |
sys_dup(0) | 實作在檔案fs/fcntl.c中, 宣告為"SYSCALL_DEFINE1(dup, unsigned int, fildes)", 在這會連續執行兩次sys_dup,複製兩個sys_open開啟/dev/console所產生的檔案描述0 (也就是會多生出兩個1與2),只是都對應到"/dev/console", 0:Standard input (stdin) 1:Standard output (stdout) 2:Standard error (stderr) (為方便大家參考,附上Wiki URL |
ramdisk_execute_command與prepare_namespace | 1,如果ramdisk_execute_command為0,就設定ramdisk_execute_command = "/init" 2,如果sys_access確認檔案ramdisk_execute_command |
init_post | 實作在檔案kernel/main.c中 1,呼叫async_synchronize_full (in kernel/async.c),用以同步所有非同步函式呼叫的執行,在這函式中會等待List async_running與async_pending都 2,呼叫free_initmem (in arch/arm/mm/init.c),釋放Linux Kernel介於__init_begin到 3,設定system_state 4,產生第一個User 4.a,如果ramdisk_execute_command不為0,就執行該命令成為init User Process. 4.b,如果execute_command不為0,就執行該命令成為init User Process. 4.c,如果上述都不成立,就依序執行如下指令 run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); 也就是說會試著從/sbin/init, /etc/init, /bin/init 5,如果都找不到可以執行的 panic("No init found. "See Linux Documentation/init.txt for guidance."); |
有關IDLE Task, init Task
CPU Mask
Linux Kernel針對多核心的需求,提供了對應處理器狀態的CPU
const
static
static
static
static
有關CPU狀態的CPU Mask會包含以下五種屬性
(也可以參考Linux Documentation/cpu-hotplug.txt文件)
1,cpu_all_bits:
2,cpu_possible_bits:這表示系統實際Run-Time時,存在的處理器個數(cpu_all_bits是編譯時期指定的最大處理器個數),在系統初始化的過程中,會根據偵測到的處理器數量透過函式set_cpu_possible設定哪些Bits要為1. (如果有兩個處理器就是0×00000003). (set_cpu_possible會再透過
3,cpu_online_bits:用以表示哪個處理器目前是Online(也就是可以正常使用並參與排程的與處理中斷).當處理器進行
4,cpu_present_bits:
5,cpu_active_bits:用以表示目前有哪些處於Online,且為Active的處理器可供scheduler
相關巨集的定義,可以在include/linux/types.h中,看到如下宣告
#define DECLARE_BITMAP(name,bits) \
unsigned
與在include/linux/bitmap.h中,如下的宣告
#define BITMAP_LAST_WORD_MASK(nbits)
(
((nbits) % BITS_PER_LONG) ?
(1UL<<((nbits) % BITS_PER_LONG))-1 :
)
與在include/linux/cpumask.h中,如下的宣告
#define CPU_MASK_LAST_WORD BITMAP_LAST_WORD_MASK(NR_CPUS)
#if NR_CPUS <= BITS_PER_LONG
#define CPU_BITS_ALL
{
[BITS_TO_LONGS(NR_CPUS)-1] = CPU_MASK_LAST_WORD \
}
#else
#define CPU_BITS_ALL
{
[0 ... BITS_TO_LONGS(NR_CPUS)-2] = ~0UL,
[BITS_TO_LONGS(NR_CPUS)-1] = CPU_MASK_LAST_WORD
}
#endif
以筆者所在的環境來說,NR_CPUS設定為4,則
多核心架構下,跟CPU個數與狀態有關的基礎函式
全域變數setup_max_cpus
取得目前系統在對應狀況(Online,Possible,Present或Active)的處理器個數
#define num_online_cpus()
#define num_possible_cpus()
#define num_present_cpus()
#define num_active_cpus()
確認對應處理器目前的狀態
#define cpu_online(cpu)
#define cpu_possible(cpu)
#define cpu_present(cpu)
#define cpu_active(cpu)
initcall
Linux Kernel有提供initcall機制,讓每個核心模塊的設計者,可以在系統init初始的不同階段,執行對應的初始化動作.參考檔案include/linux/init.h,Linux Kernel有提供__define_initcall巨集,用以根據輸入的Level產生對應的
#define __define_initcall(level,fn,id) \
static
__attribute__((__section__(".initcall" level ".init"))) = fn
如果使用者希望在初始SMP前(也就是呼叫smp_init前),
#define early_initcall(fn)
以筆者所編譯的ARM Tegra環境來說,在init執行到尾聲時,會呼叫do_initcalls,進行執行使用者模式init前的最後核心裝置與檔案系統…等的initcall呼叫,在筆者的環境中主要會產生以下的Level “0”,“1”,”2”,”3”,”4”,”5”,”rootfs”,”6”與
對應的宣告如下所示
#define pure_initcall(fn)
#define core_initcall(fn)
#define postcore_initcall(fn)
#define arch_initcall(fn)
#define subsys_initcall(fn)
#define fs_initcall(fn)
#define rootfs_initcall(fn)
#define device_initcall(fn)
#define late_initcall(fn)
在實際的程式碼中,可以看到例如在檔案kernel/softirq.c中,函式static __init int spawn_ksoftirqd(void),會執行
或像是在檔案arch/arm/kernel/setup.c中,函式static int __init topology_init(void)
在檔案kernel/posix-timers.c中,函式static __init int init_posix_timers(void)會執行__initcall(init_posix_timers)把函式
以及在檔案drivers/char/random.c中,函式static __init int seqgen_init(void)回執行late_initcall(seqgen_init)把函式
與平台相關的函式boot_secondary與Secondary處理器啟動流程
要了解不同處理器平台(例如Nvidia Tegra (in arch/arm/mach-tegra/platsmp.c), TI OMAP (in arch/arm/mach-omap2/omap-smp.c)或Qualcomm MSM (in arch/arm/mach-msm/platsmp.c)對多核心啟動的實作差異,從這個函式就可以揭露出概略的框架,例如
處理器 | 說明 |
NVIDIA Tegra | 參考arch/arm/mach-tegra/platsmp.c中的實作, 1,透過virt_to_phys(tegra_secondary_startup)取得函式tegra_secondary_startup的Physical Adress 2,讀取並暫存HW Register EVP_CPU_RESET_VECTOR 3,致能Secondary處理器的Clock 4,執行smp_wmb (=barrier),透過Memory Barrier執行ARM dmb sy指令(Data Memory Barrier)與Flush Cache,讓處理器重新去外部記憶體讀取資料到Register與Cache中. 5,設定HW Register TEGRA_FLOW_CTRL_BASE的值(Unhalt the Online CPU),讓處理器可以開始運作 6,HW 7,還原原本 至此,就完成Tegra雙核心架構下,由Bootstrap處理器把SMP的Secondary處理器帶起來的動作. 參考資訊: #define EVP_CPU_RESET_VECTOR \ (IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0×100) TEGRA_EXCEPTION_VECTORS_BASE =0x6000F000 |
Qualcomm MSM | 參考 1,呼叫prepare_cold_cpu,透過函式scm_set_boot_addr利用Qualcomm的SCM(Secure Channel Manager)介面,把函式msm_secondary_startup的Physical Address傳給Online處理器. 2,把全域變數pen_release設定為Secondary處理器CPU ID(沒特別的意義,只是要在後面利用確保這個值不為-1來確認Secondary處理器是不是被啟動了). 3,Flush 4,呼叫smp_cross_call,觸發Software Interrupt給Secondary處理器,讓Secondary處理器開始啟動流程.(透過 5,接下來會確任全域變數pen_release是否為-1,若是就表示Secondary處理器順利被啟動了. |
TI OMAP | 參考 1, 2,直到Bootstrap處理器透過omap_modify_auxcoreboot0(0×200, 0xfffffdff);更新AuxCoreBoot0內容.讓函式omap_secondary_startup離開hold的迴圈. 3,Bootstrap處理器會透過smp_cross_call(cpumask_of(cpu), 1)觸發IPI (Inter-Processor Interrupt)給Secondary處理器. 4,此時Secondary處理器往後進入函式secondary_startup中執行後續流程. |
以上,筆者把目前常見的Dual-Core平台,如何把Secondary處理器帶起的機制做一個簡要說明,我們可以看到每一個方案的作法都有些許的差異,但其實主要還是在於Reset/Halt機制或是透過
cpu_up與cpu_down
Linux Kernel支援CPU hotplug機制,並可透過全域變數cpu_hotplug_disabled決定處理器Hot Plug機制的致能與否.
參考檔案
CPU Up呼叫流程為
cpu_up (in kernel/cpu.c) -> _cpu_up (in kernel/cpu.c) → __cpu_up (in arch/arm/kernel/smp.c)
如下簡述這三個流程的動作.
CPU UP | 說明 |
1, “cpu_up” (in kernel/cpu.c) | 1,呼叫cpu_possible確認目前要Online的處理器是否可被啟用. 2,呼叫cpu_maps_update_begin 3,確認cpu_hotplug_disabled是否有被設定 4,呼叫 5,呼叫cpu_maps_update_done解開Mutex Lock “cpu_add_remove_lock” |
2, “_cpu_up” (in kernel/cpu.c) | 1,如果該CPU已經Online或是非Present的狀態,就返回錯誤 2,呼叫cpu_hotplug_begin, 2.a,設定Active 2.b,如果 2.c,反之,如果cpu_hotplug.不為0,就會把行程設定為TASK_UNINTERRUPTIBLE,並解開Mutex Lock “cpu_hotplug.lock”,觸發排程,讓其它的Reader把工作結束 3,呼叫__cpu_notify,透過函式__raw_notifier_call_chain,通知CPU Chain中的處理器,目前正在進行Online動作的處理器狀態為”CPU_UP_PREPARE”. 4,呼叫 5,呼叫cpu_notify,透過函式__raw_notifier_call_chain,通知CPU Chain中的處理器,目前完成Online動作的處理器狀態為”CPU_ONLINE”. 6,呼叫cpu_hotplug_done,設定Active Write為NULL (cpu_hotplug.active_writer = NULL),解開Mutex Lock “cpu_hotplug.lock”. |
3, (in arch/arm/kernel/smp.c) | 1,呼叫per_cpu取得要Online處理器的cpuinfo_arm結構 struct cpuinfo_arm { struct cpu #ifdef CONFIG_SMP struct unsigned int #endif }; 2,如果目前處理器沒有指定Idle 3,反之,如果這處理器已經有Idle 4,呼叫函式pgd_alloc (in arch/arm/mm/pgd.c),會以1MB TLB Settings (約需要16kbytes 5,如果PHYS_OFFSET != PAGE_OFFSET 至此就完成對新增處理器的初步MMU記憶體分頁與安全性的配置動作. 6,設定要Online處理器的Stack 7,呼叫函式__cpuc_flush_dcache_area進行 8,呼叫函式outer_clean_range把Clean L2 Cache (範圍是全域變數struct secondary_data在記憶體的起點與大小) 9,透過函式boot_secondary 10,secondary_data.stack = NULL;與secondary_data.pgdir = 0; 11, 12, |
執行完畢__cpu_up,就完成了新增處理器的流程.
CPU Down呼叫流程為
cpu_down (in kernel/cpu.c) -> _cpu_down (in kernel/cpu.c) → __cpu_die (in arch/arm/kernel/smp.c)
如下簡述這三個流程的動作.
CPU DOWN流程 | 說明 |
1,”cpu_down” (in kernel/cpu.c) | 1,呼叫cpu_maps_update_begin 2,確認cpu_hotplug_disabled是否有被設定 3,呼叫 4,呼叫cpu_maps_update_done解開Mutex Lock “cpu_add_remove_lock” |
2,”_cpu_down” (in kernel/cpu.c) | 1,呼叫函式num_online_cpus,確認如果目前Online的處理器只有一個,會直接返回錯誤 2,呼叫函式cpu_online,如果該CPU並非Online狀態,就返回錯誤 3,呼叫cpu_hotplug_begin,取得 4,呼叫__cpu_notify,透過函式__raw_notifier_call_chain,通知CPU Chain中的處理器,目前正在進行Online動作的處理器狀態為”CPU_DOWN_PREPARE”. 5,呼叫函式__stop_machine (in ) 5.a,透過函式set_state設定struct stop_machine_data smdata 5.b,透過函式stop_cpus,停止指定的處理器. 5.c,由於是由要被停止的處理器執行函式take_cpu_down,在這函式的實作中,會呼叫__cpu_disable(in arch/arm/kernel/smp.c),把自己Offline,Migrate IRQ給其他處理器,停止Local Timer,Flush Cache/TLB,透過cpumask_clear_cpu把自己從Memory Management CPU Mask中移除,最後透過cpu_notify通知自己處於CPU_DYING. 5.d,進入Callback函式migration_call中 6,透過BUG_ON(cpu_online(cpu)),確認要停止的處理器,是否已經處於Offline的狀態. (若還是在Online狀態就會導致Kernel Panic) 7,呼叫函式idle_cpu (in kernel/shced.c),確認要Offline處理器是否正在執行idle task.(前面的migrate_tasks已經把要Offline處理器的所有Tasks都轉到其它處理器上了).若該處理器不是正在執行Idle Task,就會呼叫 8,呼叫 9,呼叫cpu_notify_nofail,通知完成Offline動作的處理器狀態為”CPU_DEAD”. 10,呼叫check_for_tasks,確認目前是否還有Tasks在被停止的處理器上,若有就會Printk出警告訊息…(ㄟ….就算有在這階段也來不及做啥了……@_@) 11,呼叫cpu_hotplug_done,設定Active Write為NULL,解開Mutex Lock “cpu_hotplug.lock”. |
3,”__cpu_die” (in arch/arm/kernel/smp.c) | 1,會執行函式wait_for_completion_timeout,等待函式cpu_die #ifdef CONFIG_HOTPLUG_CPU if (cpu_is_offline(smp_processor_id())) cpu_die(); #endif 2,呼叫platform_cpu_kill (in arch/arm/mach-tegra/hotplug.c),以Tegra方案來說,這函式為空函式. 3,而CPU 4,若處理器重新被喚醒,就會執行函式secondary_start_kernel (in arch/arm/kernel/smp.c),重新執行初始化流程. |
執行完畢__cpu_die,就完成了卸載處理器的流程.
cpu_idle
實作在檔案arch/arm/kernel/process.c中,當處理器沒有其它Task執行時,就會在這函式中運作.
Linux Kernel支援
有關CPU Idle的說明,也可以參考Linux Kernel文件”Documentation/scheduler/sched-arch.txt”,如下簡述
1,Enable
2,進入
2-1,呼叫tick_nohz_stop_sched_tick,停止Scheduling Tick,包括會透過get_next_timer_interrupt取得下一次Timer中斷,跟現在的時間計算Delta delta_jiffies,如果delta_jiffies過小(=1),也就是說下個Timer中斷很快就會被觸發,就會直接結束這函式. (間隔過短,關閉Scheduling
2-2,進入while
2-2-1,如果目前的處理器已經被Offline,則呼叫cpu_die
2-2-2,Disable
2-2-3,如果hlt_counter不為0,就會Enable IRQ,執行cpu_relax.
2-2-4,若hlt_counter為0,就會進入每個平台差異化的pm_idle實作,在這個函式指標中,會依據每個平台的實作不同,在系統初始化時,設定給不同的Power Management函式.例如:omap2_pm_idle,omap3_pm_idle或s5p64x0_idle,在這就可以根據每個平台的差異,優化跟平台有關的Power Saving機制,包括關閉PMU Power Group(LDO)或是停止PLL..等,讓系統可以進入更深度的省電模式.
2-3,呼叫tick_nohz_restart_sched_tick,恢復Scheduling Tick,並更新jiffies
2-4,執行preempt_enable_no_resched,致能Preemptive排程
2-5,進行Kernel
2-6,執行preempt_disable,關閉Preemptive排程
基於上述的說明,筆者把CPU Idle主要的行為,用如下的圖示表示
結語
希望透過本文的介紹,各位可以對Linux Kernel與Idle機制有清楚的藍圖,進而在ARM MPCore產品,有更好的掌握度.