以下内容参考: http://loda.hala01.com/2011/08/android-筆記-linux-kernel-smp-symmetric-multi-processors-開機流程解析-part3-linux-多核心啟動流/
1,cpu hotplug机制
Linux Kernel支援CPU hotplug机制,并可透过全域变数cpu_hotplug_disabled决定处理器Hot Plug机制的致能与否.参考档案 kernel/cpu.c,如果全域变数cpu_hotplug_disabled被设定为1,cpu_up与cpu_down机制就会失效(返回-EBUSY). 在实作上,可以呼叫函式enable_nonboot_cpus致能CPU Up/Down机制,或呼叫函式disable_nonboot_cpus关闭CPU Up/Down机制.
2,cpu down的流程
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 设定Mutex Lock “cpu_add_remove_lock” 2,确认cpu_hotplug_disabled是否有被设定 3,呼叫 _cpu_down(cpu, 0) 4,呼叫cpu_maps_update_done解开Mutex Lock “cpu_add_remove_lock”
|
2,”_cpu_down” (in kernel/cpu.c) |
1,呼叫函式num_online_cpus,确认如果目前Online的处理器只有一个,会直接返回错误 (mmmm,如果这个也Down下去就没有处理器可用了…) 2,呼叫函式cpu_online,如果该CPU并非Online状态,就返回错误 3,呼叫cpu_hotplug_begin,取得 Mutex Lock “cpu_hotplug.lock”. 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 中的state为STOPMACHINE_PREPARE 5.b,透过函式stop_cpus,停止指定的处理器. 会在关闭Preemption的状态下,透过cpu_stop_queue_work,让要被停止的处理器执行函式take_cpu_down. 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中 (in kernel/sched.c),并透过函式migrate_tasks把要Offline处理器的Tasks转移到其它处理器上. (在这之后,处理器就只剩下Idle Task.). 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,就会呼叫 cpu_relax (对应的实作为ARM的Memory Barrier),直到确认要Offline的处理器是处于Idle Task中. 8,呼叫 __cpu_die(cpu) 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 透过函式 complete 设定“Completion”给 cpu_died物件,如果cpu_died物件有设定完成或是TimeOut就会继续往后执行.有关cpu_die函式的执行,是在处理器初始化到最后时,会透过函式rest_init呼叫到函式cpu_idle (in arch/arm/kernel/process.c)中,由cpu_idle执行处理器的IDLE流程. 而在cpu_idle中,在有支援CPU HotPlug的组态下,会去确认处理器是否被Offiline,若是就会执行 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 Idle Task在执行cpu_die后,就会进入函式platform_cpu_die (in arch/arm/mach-tegra/hotplug.c),并透过platform_do_lowpower,让处理器处于WFI Low Power的状态,等待下一次的唤醒. 4,若处理器重新被唤醒,就会执行函式secondary_start_kernel (in arch/arm/kernel/smp.c),重新执行初始化流程.
|
3,cpu_idle()的流程
实作在档案arch/arm/kernel/process.c中,当处理器没有其它Task执行时,就会在这函式中运作,Linux Kernel支援 Tickless System (设定路径在Kernel Features —>Tickless System (Dynamic Ticks)),如果有选择这能力,会在.config中设定CONFIG_NO_HZ=y. 当处理器执行IDLE Task时,可以停止系统的Scheduling Tick,也就是说进入pm_idle时,可以减少Timer中断触发(例如HZ=100,每秒就有一百次Tick Timer中断触发),减少系统醒来的机会.
有关CPU Idle的说明,也可以参考Linux Kernel文件”Documentation/scheduler/sched-arch.txt”,如下简述 CPU Idle的行为
1,Enable FIQ.
2,进入 while(1) 的 Loop
2-1,呼叫tick_nohz_stop_sched_tick,停止Scheduling Tick,包括会透过get_next_timer_interrupt取得下一次Timer中断,跟现在的时间计算Delta delta_jiffies,如果delta_jiffies过小(=1),也就是说下个Timer中断很快就会被触发,就会直接结束这函式. (间隔过短,关闭Scheduling Tick所产生的效益就降低了.),由于系统时间所依赖的jiffies就是透过Scheduling Tick更新,因此在这函式中也会针对暂停Scheduling Tick引起的时间差,预备修正的机制.
2-2,进入while (!need_resched()) Loop ,执行need_resched可以知道是否需要重新排程,或是要准备进入IDLE的省电机制中. Idle 流程不会Set或Clear 函式need_resched所参考的TIF_NEED_RESCHED,当Idle流程触发排程时,才会Clear该状态
2-2-1,如果目前的处理器已经被Offline,则呼叫cpu_die
2-2-2,Disable IRQ
2-2-3,如果hlt_counter不为0,就会Enable IRQ,执行cpu_relax. 如果有呼叫platform_suspend_ops中的 end 函式指标,就会呼叫enable_hlt,让hlt_counter不为0,反之如果呼叫begin函式指标,就会透过disable_hlt,让htl_counter减为0. (并非所有ARM方案都有设定Platform Suspend Operation的机制,例如TI OMAP有,而Nvidia Tegra则无).
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..等,让系统可以进入更深度的省电模式. 执行完毕pm_idle后,就会Enable IRQ,往后继续执行.
2-3,呼叫tick_nohz_restart_sched_tick,恢复Scheduling Tick,并更新jiffies
2-4,执行preempt_enable_no_resched,致能Preemptive排程 (Call dec_preempt_count())
2-5,进行Kernel Scheduling
2-6,执行preempt_disable,关闭Preemptive排程 (Call inc_preempt_count()),在cpu_idle中,除了要触发排程外,多数的情况下Preemptive排程会被关闭,直到要重新触发Linux Kernel排程才会致能Preemptive机制.
4,cpu_up()的流程
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 设定Mutex Lock “cpu_add_remove_lock” 3,确认cpu_hotplug_disabled是否有被设定 (也就是不允许动态的CPU Online动作) 4,呼叫 _cpu_up(cpu, 0) 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 Write为目前的Process (cpu_hotplug.active_writer = current),取得 Mutex Lock “cpu_hotplug.lock”. 2.b,如果 cpu_hotplug.refcount为0,表示目前没有其它Reader,因此,可以结束函式cpu_hotplug_begin,让_cpu_up后续工作继续 2.c,反之,如果cpu_hotplug.不为0,就会把行程设定为TASK_UNINTERRUPTIBLE,并解开Mutex Lock “cpu_hotplug.lock”,触发排程,让其它的Reader把工作结束 (必须让Write取得Mutex Lock “cpu_hotplug.lock”以及cpu_hotplug.refcount为0,才能让函式cpu_hotplug_begin结束). 3,呼叫__cpu_notify,透过函式__raw_notifier_call_chain,通知CPU Chain中的处理器,目前正在进行Online动作的处理器状态为”CPU_UP_PREPARE”. 4,呼叫 __cpu_up(cpu) 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, “__cpu_up”
(in arch/arm/kernel/smp.c) |
1,呼叫per_cpu取得要Online处理器的cpuinfo_arm结构 (in arch/arm/include/asm/cpu.h),如下所示 struct cpuinfo_arm { struct cpu cpu; #ifdef CONFIG_SMP struct task_struct *idle; //会指向目前这个处理器的IDLE Task. unsigned int loops_per_jiffy; #endif }; 2,如果目前处理器没有指定Idle Task,就透过函式fork_idle (in kernel/fork.c)产生Idle Task后指定给这个处理器. 函式fork_idle会呼叫copy_process複製一个PID = init_struct_pid的行程,并执行函式init_idle_pids与init_idle把这Idle Task指定给要Online的处理器. 3,反之,如果这处理器已经有Idle Task,就呼叫函式init_idle (in kernel/sched.c)指定Idle Task给目前进行Online的处理器. 4,呼叫函式pgd_alloc (in arch/arm/mm/pgd.c),会以1MB TLB Settings (约需要16kbytes 记忆体),产生TLB Level 1的Page Table. 5,如果PHYS_OFFSET != PAGE_OFFSET (PHYS_OFFSET为Kernel Image在实体记忆体中的Offset,PAGE_OFFSET为Kernel Image在虚拟记忆体中的Offset,一般而言为0x8000),就会透过函式identity_mapping_add (in arch/arm/mm/idmap.c),把Linux Kernel Image 程式区段 (_stext 到_etext)与资料区段(_sdata到_edata)的记忆体分页以1MB TLB与AP (Access Permission)为PMD_SECT_AP_WRITE (1 << 10) 属性设定到TLB分页中.有关记忆体分页的属性与对应的Bits如下图所示,以现有程式码的配置来说,对Linux Kernel 的程式与资料区段是特权等级Privileged permissions为Read/Write,而一般应用程式User permissions为No Access.
至此就完成对新增处理器的初步MMU记忆体分页与安全性的配置动作. 6,设定要Online处理器的Stack (secondary_data.stack)与Page Table(secondary_data.pgdir)位址到全域变数struct secondary_data. 7,呼叫函式__cpuc_flush_dcache_area进行 DCache Flush (范围是全域变数struct secondary_data在记忆体的起点与大小) 8,呼叫函式outer_clean_range把Clean L2 Cache (范围是全域变数struct secondary_data在记忆体的起点与大小) 9,透过函式boot_secondary (in arch/arm/mach-tegra/platsmp.c)带起Online处理器. 10,secondary_data.stack = NULL;与secondary_data.pgdir = 0; 11, 如果PHYS_OFFSET != PAGE_OFFSET,就会透过函式identity_mapping_del把之前配置的Linux Kernel Image 程式区段 (_stext 到_etext)与资料区段(_sdata到_edata)记忆体分页删除. 12, 呼叫函式pgd_free释放Page Table.
|