Linux CPU core的電源管理(5)_cpu control及cpu hotplug

1. 前言

由“Linux CPU core的電源管理(1)_概述”的描述可知,kernel cpu control位於“.\kernel\cpu.c”中,是一個承上啓下的模塊,負責屏蔽arch-dependent的實現細節,向上層軟件提供控制CPU core的統一API(主要包括cpu_up/cpu_down等接口的實現)。本文將基於這些API,從上到下,分析CPU core從啓動到關閉的整個過程(主要是CPU hotplug),進一步理解系統運行過程中CPU core電源管理相關的行爲。

注1:其實這一部分已經不屬於電源管理的範疇了,而是系統級的軟件行爲(boot、調度、電源管理等等),之所以放到這裏講述,主要原因是,這些複雜行爲的背後,目的只有一個----節電。因此,本文只會focus在CPU core power狀態切換的過程上,涉及到得其它知識,如進程調度,只會一筆帶過。

2. possible/present/active/online cpus

前面文章提到過,kernel使用4個bitmap,來保存分別處於4種狀態的CPU core:possible、present、active和online。這四個狀態的意義到底是什麼?下面我們根據相關的代碼邏輯,來解答這個問題。

開始之前,先看一下kernel中對他們的註釋:

  1: /* include/linux/cpumask.h */
  2: 
  3: 
  4: /*
  5:  * The following particular system cpumasks and operations manage
  6:  * possible, present, active and online cpus.
  7:  *
  8:  *     cpu_possible_mask- has bit 'cpu' set iff cpu is populatable
  9:  *     cpu_present_mask - has bit 'cpu' set iff cpu is populated
 10:  *     cpu_online_mask  - has bit 'cpu' set iff cpu available to scheduler
 11:  *     cpu_active_mask  - has bit 'cpu' set iff cpu available to migration
 12:  *
 13:  *  If !CONFIG_HOTPLUG_CPU, present == possible, and active == online.
 14:  *
 15:  *  The cpu_possible_mask is fixed at boot time, as the set of CPU id's
 16:  *  that it is possible might ever be plugged in at anytime during the
 17:  *  life of that system boot.  The cpu_present_mask is dynamic(*),
 18:  *  representing which CPUs are currently plugged in.  And
 19:  *  cpu_online_mask is the dynamic subset of cpu_present_mask,
 20:  *  indicating those CPUs available for scheduling.
 21:  *
 22:  *  If HOTPLUG is enabled, then cpu_possible_mask is forced to have
 23:  *  all NR_CPUS bits set, otherwise it is just the set of CPUs that
 24:  *  ACPI reports present at boot.
 25:  *
 26:  *  If HOTPLUG is enabled, then cpu_present_mask varies dynamically,
 27:  *  depending on what ACPI reports as currently plugged in, otherwise
 28:  *  cpu_present_mask is just a copy of cpu_possible_mask.
 29:  */
 30: 

大意是這樣的:

possible狀態的CPU意味着是“populatable(覺得這個單詞還沒有possible易懂)”的,可理解爲存在這個CPU資源,但還沒有納入Kernel的管理範圍;

present狀態的CPU,是已經“populated”的CPU,可理解爲已經被kernel接管;

online狀態的CPU,表示可以被調度器使用;

active狀態的CPU,表示可以被migrate(什麼意思?);

如果系統沒有使能CPU Hotplug功能,則present等於possible,active等於online。

還真不是很容易理解,不急,我們一個一個分析。

2.1 possible CPU

possible的CPUs,代表了系統中可被使用的所有的CPU,在boot階段確定之後,就不會再修改。以ARM64爲例,其初始化的過程如下。

1)系統上電後,boot CPU啓動,執行start_kernel(init/main.c),並分別調用boot_cpu_init和setup_arch兩個接口,進行possible CPU相關的初始化。

2)boot_cpu_init負責將當前的boot CPU放到possible CPU的bitmap中,同理,boot CPU也是present、oneline、active CPU(因此,後續的描述,都是針對非boot CPU的)。如下:

  1: /* init/main.c */
  2: 
  3: static void __init boot_cpu_init(void)
  4: {
  5:         int cpu = smp_processor_id();
  6:         /* Mark the boot cpu "present", "online" etc for SMP and UP case */
  7:         set_cpu_online(cpu, true);
  8:         set_cpu_active(cpu, true);
  9:         set_cpu_present(cpu, true);
 10:         set_cpu_possible(cpu, true);
 11: }

smp_processor_id用於獲取當前的CPU id;

set_cpu_xxx接口,可以將指定的CPU設置爲(或者清除)指定的狀態。

3)setup_arch負責根據MPIDR寄存器,以及DTS配置,解析並設置其它的possible CPU,如下:

  1: /* arch/arm64/kernel/setup.c */
  2: 
  3: void __init setup_arch(char **cmdline_p)
  4: {
  5:         ...
  6:         cpu_logical_map(0) = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
  7:         cpu_read_bootcpu_ops();
  8: #ifdef CONFIG_SMP
  9:         smp_init_cpus();
 10:         smp_build_mpidr_hash();
 11: #endif
 12:         ...
 13: }
 14: 

3a)cpu_logical_map數組

kernel使用一個整形數組(cpu_logical_map,定義如下),保存物理CPU(由ID標示)和邏輯CPU(數組的index)之間的映射,該數組的長度由NR_CPUS決定。

  1: /* arch/arm64/include/asm/smp_plat.h */
  2: 
  3: /*
  4:  * Logical CPU mapping.
  5:  */
  6: extern u64 __cpu_logical_map[NR_CPUS];
  7: #define cpu_logical_map(cpu)    __cpu_logical_map[cpu]

上面setup_arch代碼的第六行,通過read_cpuid_mpidr接口,讀取當前CPU(boot CPU)的ID(物理ID),並保存在map表的第一個位置。

3b)smp_init_cpus

如果使能了SMP,則調用smp_init_cpus接口,完成如下事情:

從DTS中解析其它CPU的HW ID(通過‘reg’關鍵字,如下),並保存在cpu_logical_map數組中;

對所有cpu_logical_map數組中的CPU,執行set_cpu_possible操作,將它們設置爲possible狀態。

  1: {
  2:        ...
  3:        cpus {
  4:                 #address-cells = <2>;
  5:                 #size-cells = <0>;
  6: 
  7:                 cpu@0 {
  8:                         device_type = "cpu";
  9:                         compatible = "arm,cortex-a53", "arm,armv8";
 10:                         reg = <0x0 0x0>;
 11:                         enable-method = "psci";
 12:                         cpu-idle-states = <&CPU_SLEEP_0 &CPU_SLEEP_1>;
 13:                 };
 14: 
 15:                 cpu@1 {
 16:                         device_type = "cpu";
 17:                         compatible = "arm,cortex-a53", "arm,armv8";
 18:                         reg = <0x0 0x1>;
 19:                         enable-method = "psci";
 20:                         cpu-idle-states = <&CPU_SLEEP_0 &CPU_SLEEP_1>;
 21:                 };
 22:                 ...
 23:        };
 24:        ...
 25: }
 26: 

CPU DTS文件示例。 

4)總結

對ARM64來說,possible的CPU,就是在DTS中指定了的,物理存在的CPU core。

2.2 present CPU

還是以ARM64爲例,“start_kernel—>setup_arch”成功執行之後,繼續執行“start_kernel—>rest_init—>kernel_init(pid 1,init task)—>kernel_init_freeable”,在kernel_init_freeable中會調用arch-dependent的接口:smp_prepare_cpus,該接口主要的主要功能有兩個:

1)構建系統中CPU的拓撲結構,具體可參考“Linux CPU core的電源管理(2)_cpu topology”。

2)拓撲結構構建完成後,根據CPU的拓撲,初始化系統的present CPU mask,代碼如下:

  1: void __init smp_prepare_cpus(unsigned int max_cpus)
  2: {
  3:         ...
  4:         /* Don't bother if we're effectively UP */
  5:         if (max_cpus <= 1)
  6:                 return;
  7: 
  8:         /*
  9:          * Initialise the present map (which describes the set of CPUs
 10:          * actually populated at the present time) and release the
 11:          * secondaries from the bootloader.
 12:          *
 13:          * Make sure we online at most (max_cpus - 1) additional CPUs.
 14:          */
 15:         max_cpus--;
 16:         for_each_possible_cpu(cpu) {
 17:                 if (max_cpus == 0)
 18:                         break;
 19: 
 20:                 if (cpu == smp_processor_id())
 21:                         continue;
 22: 
 23:                 if (!cpu_ops[cpu])
 24:                         continue;
 25: 
 26:                 err = cpu_ops[cpu]->cpu_prepare(cpu);
 27:                 if (err)
 28:                         continue;
 29: 
 30:                 set_cpu_present(cpu, true);
 31:                 max_cpus--;
 32:         }
 33: }

4~6行:當然,如果CPU個數不大於1,則不是SMP系統,就沒有後續的概念,直接返回。

16~32行,輪詢所有的possible CPU,如果某個CPU core滿足一些條件,則調用set_cpu_present,將其設置爲present CPU,滿足的條件包括:具備相應的cpu_ops指針(有關cpu ops請參考“Linux CPU core的電源管理(3)_cpu ops”);cpu ops的.cpu_prepare回調成功執行。

由“Linux CPU core的電源管理(3)_cpu ops”中有關CPU ops的解釋可知,.cpu_prepare回調主要用於檢查某個CPU是否具備執行的條件。如果.cpu_prepare執行成功,則說明該CPU是可以啓動的。因此,present CPU的意義是:

該CPU已經被kernel識別到,並且具備執行代碼的條件,後續可以在需要的時候(如hotpulg的時候),啓動該CPU。

2.3 online CPU

由前面present CPU可知,如果某個CPU是present的,則說明該CPU具備boot的條件,但是否已經boot還是未知數。

由“Linux CPU core的電源管理(3)_cpu ops”的介紹可知,所謂的CPU boot,就是讓CPU執行(取指譯碼/執行)代碼(這裏爲linux kernel)。而CPU是否boot,則反映到online mask上,已經boot的CPU,會在secondary_start_kernel中,調用set_cpu_online接口,將其設置爲online狀態。反之,會在__cpu_disable中將其從online mask中清除。

有關CPU boot的流程,請參考下面的介紹。

2.4 active CPU

在單核時代,調度器(scheduler)的職責很單純:主要負責管理、調教一幫調皮搗蛋的task,儘量以“公平公正”的原則,爲它們分配有限的CPU資源。

但在SMP系統中,特別是支持CPU hotplug的系統中,調度器需要多操一份心,即:

CPU資源可以在任何時候增加或者刪除。增加的時候,需要將新增的資源分配給等待的task。刪除的時候,需要將那些運行在這些CPU上的task,轉移到其它尚存的CPU上(這個過程稱作migration)。

要達到上面的目的,調度器需要監視CPU hotplug有關的每一個風吹草動。由於調度器和CPU控制兩個獨立的模塊,kernel通過notifier機制(“Linux CPU core的電源管理(1)_概述”中有提及,但沒有過多介紹)實現這一功能。

簡言之,每當系統的CPU資源有任何變動,kernel CPU control模塊就會通知調度器,調度器根據相應的event(CPU_DOWN_FAILED、CPU_DOWN_PREPARE等),調用set_cpu_active接口,將某個CPU添加到active mask或者移出active mask。這就是active CPU的意義:

從調度器的角度,CPU的狀態,即是否對調度器可見,或者說,調度器是否可以把task分配到這個CPU上運行。

注2:由此可知,active狀態,只是爲了方便調度器操作,抽象出的狀態,和CPU電源管理之間沒有耦合,後面就不在涉及這部分內容。

3. CPU的控制流程

CPU的控制流程,可以總結爲up和down兩種行爲(和“.\kernel\cpu.c”中的cpu_up、cpu_down兩個接口對應),up指CPU的啓動過程,down指相反地過程。

根據CPU的發展過程,up和down的行爲又可以分爲三類:單核CPU的up/down;多核CPU的up/down;hotplugable CPU的up/down。下面讓我們對這幾種情況做一下簡單的介紹。

3.1 單核CPU的控制流程

單核時代,只有一個CPU core,因此CPU的up/down,就是軟件的整個生命週期(也就無所謂up/down了),如下:

1)系統上電,CPU從ROM代碼執行,經bootloader(非必須),將控制權交給linux kernel。這就是cpu up的過程

2)系統運行(一大堆省略號)。

3)由linux kernel及其進程調度算法所決定,不允許系統在沒有CPU資源的情況下運行(這也是boot CPU的由來),所以系統的整個運行過程中,CPU都是up狀態。

4)系統關閉,cpu down

3.2 多核CPU的控制流程

linux kernel對待SMP系統的基本策略是:指定一個boot CPU,完成系統的初始化,然後再啓動其它CPU。過程如下:

1)boot CPU啓動,其up/down的控制流程和生命週期,和單核CPU一樣。

2)boot CPU啓動的過程中,調用cpu_up接口,啓動其它CPU(稱作secondary CPUs),使它們變成online狀態(具體可參考“Linux CPU core的電源管理(3)_cpu ops”)。這就是secondary CPUs的up過程

3)由於CPU不支持hotplug功能,因此所有CPU只能up,不能down。直到系統關閉,纔是cpu down

3.3 CPU hotplug的控制流程

對於支持CPU hotplug功能的平臺來說,可以在系統啓動後的任意時刻,關閉任意一個secondary CPU(對ARM平臺來說,CPU0或者說boot CPU,是不可以被關閉的),並在需要的時候,再次打開它。因此,相應的CPU控制流程如下:

1)boot CPU啓動,其up/down的控制流程和生命週期,和單核CPU一樣。

2)boot CPU啓動的過程中,調用cpu_up接口,啓動secondary CPU,使它們變成online狀態,這是secondary CPUs的up過程的一種。

3)在系統負荷較低、或者不需要使用的時候,調用cpu_down接口,關閉不需要使用的secondary CPU,這是secondary CPUs的down過程。

4)在需要的時候,再次調用cpu_up接口,啓動處於down狀態的CPU,這是secondary CPUs的up過程的另一種。

有關CPU hotplug的具體說明,可參考後面描述。

4. CPU hotplug

4.1 CPU hotplug的時機

在kernel/cpu.c中,cpu_up接口,只會在使能了CONFIG_SMP配置項(意味着是SMP系統)後纔會提供。而cpu_down接口,則只會在使能了CONFIG_HOTPLUG_CPU配置項(意味着支持CPU hotplug)後纔會提供。

在當前kernel實現中,只支持通過sysfs的形式,關閉或打開CPU(當然,如果需要可以自定義一些方法,實現動態開關核的功能,本文就不在描述了),例如:

echo 0 > /sys/devices/system/cpu/cpuX/online      # 關閉CPU

echo 1 > /sys/devices/system/cpu/cpuX/online      # 打開CPU

另外,CPU hotplug還受“maxcpus”命令行參數影響:

系統啓動的時候,可以通過命令行參數“maxcpus”,告知kernel本次啓動所使用的CPU個數,該個數可以小於等於possible CPU的個數。系統初始化時,只會把“maxcpus”所指定個數的CPU置爲present狀態,具體可參考上面2.2小節所描述的smp_prepare_cpus的代碼實現。

因此,CPU hotplug只能管理“maxcpus”所指定個數的CPU,具體可參考後面_cpu_up的流程分析。

注3:蝸蝸對這部分的理解,和“Documentation\cpu-hotplug.txt”中的描述有出入,文檔是這樣描述的:

maxcpus=n    Restrict boot time cpus to n. Say if you have 4 cpus, using 
             maxcpus=2 will only boot 2. You can choose to bring the 
             other cpus later online, read FAQ's for more info.

它說其它CPU可以在後邊被online,但從代碼邏輯來說,沒有機會online啊!先存疑吧!!

4.2 CPU hotplug的過程分析

CPU online的軟件流程如下:

echo 0 > /sys/devices/system/cpu/cpuX/online 
        online_store(drivers/base/core.c) 
                device_online(drivers/base/core.c) 
                        cpu_subsys_online(drivers/base/cpu.c) 
                                cpu_up(kernel/cpu.c) 
                                        _cpu_up(kernel/cpu.c) 

CPU offline的流程和online類似,不再詳細介紹。這兩個操作,最終是由cpu_up/cpu_down(也即_cpu_up/_cpu_down)兩個接口實現的,下面我們重點分析這兩個接口。

注4:內核中經常有這樣的函數,xxx、_xxx或者__xxx,區別是一個或者兩個下劃線,其中的含義是:

xxx接口,通常需要由某個鎖保護,一般提供給其它模塊調用。它會直接調用_xxx接口;

_xxx接口,則不需要保護,一般由模塊內部在確保安全的情況下調用。有時,外部模塊確信可行(不需要保護),也可能會直接調用;

__xxx接口,一般提供給arch-dependent的軟件層實現,比如這裏的arch/arm64/kernel/xxx.c。

理解這些含義後,會加快我們閱讀代碼的速度,另外,如果直接寫代碼,也儘量遵守這樣的原則,以便使自己的代碼更規範、更通用。

4.3 cpu_up流程分析

cpu_up的基本流程如下所示:

cpu_up_overview

其要點包括:

1)up前後,發送PREPARE、ONLINE、STARTING等notify,以便讓關心者作出相應的動作,例如調度器、RCU、workqueue等模塊,都需要關注CPU的hotplug動作,以便進行任務的重新分配等操作。

2)執行Arch-specific相關的boot操作,將CPU boot起來,最終通過secondary_start_kernel接口,停留在per-CPU的idle線程上。

下面我們結合代碼,對上述過程做一個簡單的分析。

4.3.1 per-CPU的idle線程

我們在“linux cpuidle framework”的系列文章中,已經分析過linux cpuidle有關的工作原理,但卻沒有提及cpuidle的源頭,這裏我們補充回來。

首先,boot CPU在執行初始化動作的時候,會通過“smp_init—>idle_threads_init—>idle_init”的調用,爲每個CPU創建一個idle線程,如下:

  1: /* kernel/smpboot.c */
  2: static inline void idle_init(unsigned int cpu)
  3: {
  4:         struct task_struct *tsk = per_cpu(idle_threads, cpu);
  5: 
  6:         if (!tsk) {
  7:                 tsk = fork_idle(cpu);
  8:                 if (IS_ERR(tsk))
  9:                         pr_err("SMP: fork_idle() failed for CPU %u\n", cpu);
 10:                 else
 11:                         per_cpu(idle_threads, cpu) = tsk;
 12:         }
 13: }

該接口的本質是,爲每個CPU fork一個idle thread(由struct task_struct結構表示),並保存在一個per-CPU的全局變量(idle_threads)中。

此時,idle thread只是一個task結構,並沒有執行。那它最終怎麼執行的呢?我們繼續往後面看。

4.3.2 arch-specific CPU boot

_cpu_up接口會在完成一些準備動作之後,調用平臺相關的__cpu_up接口,由平臺代碼完成具體的up操作,如下:

  1: static int _cpu_up(unsigned int cpu, int tasks_frozen)
  2: {
  3:         int ret, nr_calls = 0;
  4:         void *hcpu = (void *)(long)cpu;
  5:         unsigned long mod = tasks_frozen ? CPU_TASKS_FROZEN : 0;
  6:         struct task_struct *idle;
  7: 
  8:         cpu_hotplug_begin();
  9: 
 10:         if (cpu_online(cpu) || !cpu_present(cpu)) {
 11:                 ret = -EINVAL;
 12:                 goto out;
 13:         }
 14: 
 15:         idle = idle_thread_get(cpu);
 16:         if (IS_ERR(idle)) {
 17:                 ret = PTR_ERR(idle);
 18:                 goto out;
 19:         }
 20: 
 21:         ret = smpboot_create_threads(cpu);
 22:         if (ret)
 23:                 goto out;
 24: 
 25:         ret = __cpu_notify(CPU_UP_PREPARE | mod, hcpu, -1, &nr_calls);
 26:         if (ret) {
 27:                 nr_calls--;
 28:                 pr_warn("%s: attempt to bring up CPU %u failed\n",
 29:                         __func__, cpu);
 30:                 goto out_notify;
 31:         }
 32: 
 33:         /* Arch-specific enabling code. */
 34:         ret = __cpu_up(cpu, idle);
 35:         if (ret != 0)
 36:                 goto out_notify;
 37:         BUG_ON(!cpu_online(cpu));
 38: 
 39:         /* Wake the per cpu threads */
 40:         smpboot_unpark_threads(cpu);
 41: 
 42:         /* Now call notifier in preparation. */
 43:         cpu_notify(CPU_ONLINE | mod, hcpu);
 44: 
 45: out_notify:
 46:         if (ret != 0)
 47:                 __cpu_notify(CPU_UP_CANCELED | mod, hcpu, nr_calls, NULL);
 48: out:
 49:         cpu_hotplug_done();
 50: 
 51:         return ret;
 52: }

準備動作包括:

1)獲取idle thread的task指針,該指針最終會以參數的形式傳遞給arch-specific代碼。

2)創建一個用於管理CPU hotplug動作的線程(smpboot_create_threads),該線程的具體意義,後面會再說明。

3)發送CPU_UP_PREPARE notify。

以ARM64爲例,__cpu_up的內部實現如下:

  1: /* arch/arm64/kernel/smp.c */
  2: int __cpu_up(unsigned int cpu, struct task_struct *idle)
  3: {
  4:         int ret;
  5: 
  6:         /*
  7:          * We need to tell the secondary core where to find its stack and the
  8:          * page tables.
  9:          */
 10:         secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
 11:         __flush_dcache_area(&secondary_data, sizeof(secondary_data));
 12: 
 13:         /*
 14:          * Now bring the CPU into our world.
 15:          */
 16:         ret = boot_secondary(cpu, idle);
 17:         if (ret == 0) {
 18:                 /*
 19:                  * CPU was successfully started, wait for it to come online or
 20:                  * time out.
 21:                  */
 22:                 wait_for_completion_timeout(&cpu_running,
 23:                                             msecs_to_jiffies(1000));
 24: 
 25:                 if (!cpu_online(cpu)) {
 26:                         pr_crit("CPU%u: failed to come online\n", cpu);
 27:                         ret = -EIO;
 28:                 }
 29:         } else {
 30:                 pr_err("CPU%u: failed to boot: %d\n", cpu, ret);
 31:         }
 32: 
 33:         secondary_data.stack = NULL;
 34: 
 35:         return ret;
 36: }

該接口以idle thread的task指針爲參數,完成如下動作:

1)將idle線程的堆棧,保存在一個名稱爲secondary_data的全局變量中(這地方很重要,後面再介紹其中的奧妙)。

2)執行boot_secondary接口,boot CPU,具體的流程,可參考“Linux CPU core的電源管理(3)_cpu ops”中的描述。

3)boot_secondary返回後,等待對應的CPU切換爲online狀態。

4.3.3 secondary_startup

Linux CPU core的電源管理(3)_cpu ops” 4.1小節,分析了使用SPIN TABLE cpu ops的情況下,boot_secondary到secondary_startup的流程(其它cpu ops類似),本文繼續secondary_startup的分析。

該接口位於arch/arm64/kernel/head.S中,負責secondary CPU啓動後的後期操作,如下:

  1: ENTRY(secondary_startup)
  2:         /*
  3:          * Common entry point for secondary CPUs.
  4:          */
  5:         mrs     x22, midr_el1                   // x22=cpuid
  6:         mov     x0, x22
  7:         bl      lookup_processor_type
  8:         mov     x23, x0                         // x23=current cpu_table
  9:         cbz     x23, __error_p                  // invalid processor (x23=0)?
 10: 
 11:         pgtbl   x25, x26, x28                   // x25=TTBR0, x26=TTBR1
 12:         ldr     x12, [x23, #CPU_INFO_SETUP]
 13:         add     x12, x12, x28                   // __virt_to_phys
 14:         blr     x12                             // initialise processor
 15: 
 16:         ldr     x21, =secondary_data
 17:         ldr     x27, =__secondary_switched      // address to jump to after enabling the MMU
 18:         b       __enable_mmu
 19: ENDPROC(secondary_startup)
 20: 
 21: ENTRY(__secondary_switched)
 22:         ldr     x0, [x21]                       // get secondary_data.stack
 23:         mov     sp, x0
 24:         mov     x29, #0
 25:         b       secondary_start_kernel
 26: ENDPROC(__secondary_switched)

我們重點關注上面16~17行,以及21~26行的__secondary_switched,__secondary_switched會將保存在secondary_data全局變量中的堆棧取出,保存在該CPU的SP中,並跳轉到secondary_start_kernel繼續執行。思考一下其中的意義:

我們都知道,CPU啓動後,需要先配置好堆棧,才能進行後續的函數調用,這裏使用的是該CPU idle thread的堆棧。就這麼簡單嗎?當然不是,看一下kernel中“current”指針(獲取當前task結構的宏定義)的實現方法:

  1: #define current get_current()
  2: #define get_current() (current_thread_info()->task)
  3: 
  4: static inline struct thread_info *current_thread_info(void)
  5: {
  6:         register unsigned long sp asm ("sp");
  7:         return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
  8: }
  9: 

有沒有豁然開朗的感覺?通過CPU的SP指針,是可以獲得CPU的當前task的(這和linux kernel進程管理的實現有關,我們不深究)。也就是說,當CPU SP被賦值爲idle thread的堆棧的那一瞬間,當前的上下文已經是idle thread了!!

至於後面的secondary_start_kernel,就比較簡單了,使能GIC、Timers,設置CPU爲online狀態,使能本地IRQ中斷。等等。最後,調用cpu_startup_entry,進入cpuidle的loop中,已經和“Linux cpuidle framework(1)_概述和軟件架構”中描述接上了,自此,CPU UP完成。

4.4 cpu_down流程

cpu_down是cpu_up的反過程,用於將某個CPU從系統中移出。從表面上看,它和cpu_up的過程應該類似,但實際上它的處理過程卻異常繁瑣、複雜,同時牽涉到非常多的進程調度的知識,鑑於篇幅,本文就不再繼續分析了。如果有機會,後面再專門用一篇文章分析這個過程。

另外,前面提到的smpboot有關的內容,也和cpu_down的過程有關,也就不再介紹了。

5. 小結

由本文的分析可知,cpu control有關的過程,其本身的邏輯比較簡單,複雜之處在於:與此相關的系統服務(任務、中斷、timer等等)的遷移。如果要理解這個過程,就必須有深厚的進程調度、中斷管理等背景知識作支撐。不着急,來日方長,有機會我們繼續分析。

發佈了35 篇原創文章 · 獲贊 46 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章