天天看點

linux cpu運作錯誤的是什麼意思,Linux CPU core的電源管理(1)_概述

Linux CPU core的電源管理(1)_概述

作者:wowo 釋出于:2015-4-30 21:20

分類:電源管理子系統

1. 前言

在SMP(Symmetric Multi-Processing)流行起來之前的很長一段時間,Linux kernel的電源管理工作主要集中在外部裝置上,和CPU core相關的,頂多就是CPU idle。但随着SMP的普及,一個系統中可用的CPU core越來越多,這些core的頻率越來越高,處理能力越來越強,功耗也越來越大。是以,CPU core有關的電源管理,在系統設計中就成為必不可少的一環,與此有關的思考包括: 對消費者(一些專業應用除外)而言,這種暴增的處理能力,是一種極大的浪費,他們很少(或者從不)有如此高的性能需求。但商家對此卻永遠樂此不疲,原因無外乎:

1)硬體成本越來越低。

2)營銷的噱頭。

3)軟體設計者的不思進取(臃腫的Android就是典型的例子),導緻軟體效率低下,硬體資源浪費嚴重。以至于優化幾行代碼的難度,甚至比增加幾個cpu核還困難。

在這種背景下,CPU core的電源管理邏輯,就非常直接了:根據系統的負荷,關閉“多餘的CPU性能”,在滿足使用者需求的前提下,盡可能的降低CPU的功耗。但CPU的控制粒度不可能無限小,目前主要從兩個角度實作CPU core的電源管理功能:

1)在SMP系統中,動态的關閉或者打開CPU core(本文重點介紹的功能)。

2)CPU運作過程中,動态的調整CPU core的電壓和頻率(将在其它文章中單獨分析)。

本文将以ARM64為例,介紹linux kernel CPU core相關的電源管理設計。

2. 功能說明

在linux kernel中,CPU core相關的電源管理實作,并不是單純的電源管理行為,它會涉及到系統初始化、CPU拓撲結構、程序排程、CPU hotplug、memory  hotplug等知識點。總的來說,它主要完成如下功能: 1)系統啟動時,CPU core的初始化、資訊擷取等。

2)系統啟動時,CPU core的啟動(enable)。

3)系統運作過程中,根據目前負荷,動态的enable/disable某些CPU core,以便在性能和功耗之間平衡。

4)CPU core的hotplug支援。所謂的hotplug,是指可以在系統運作的過程中,動态的增加或者減少CPU core(可以是實體上,也可以是邏輯上)。

5)系統運作過程中的CPU idle管理(具體可參考“Linux cpuidle framework系列文章”)。

6)系統運作過程中,根據目前負荷,動态的調整CPU core的電壓和頻率,以便在性能和功耗之間平衡。

3. 軟體架構

為了實作上面的功能,linux kernel抽象出了下面的軟體架構:

linux cpu運作錯誤的是什麼意思,Linux CPU core的電源管理(1)_概述

軟體架構包括arch-dependent和arch-independent兩部分。

對ARM64而言,arch-dependent部分位于“arch\arm64\kernel”,負責提供平台相關的控制操作,包括: CPU資訊的擷取(cpuinfo);

CPU拓撲結構的擷取(cpu topology);

底層的CPU操作(init、disable等)的實作,cpu ops(在ARM32中是以smp ops的形式存在的);

SMP相關的初始化(smp);

等等。

arch-independent負責實作平台無關的抽象,包括: CPU control子產品,屏蔽底層平台相關的實作細節,提供控制CPU(enable、disable等)的統一API,供系統啟動、程序排程等子產品調用;

CPU subsystem driver,向使用者空間提供CPU hotplug有關的功能;

cpuidle,處理CPU idle有關的邏輯,具體可參考“cpuidle framework”相關的文章;

cpufreq,處理CPU frequency調整有關的邏輯,具體可參考後續的文章;

等等。

4. 軟體子產品的功能及API描述

4.1 kernel cpu control

kernel cpu control位于“.\kernel\cpu.c”中,是一個承上啟下的子產品,負責屏蔽arch-dependent的實作細節,向上層軟體提供CPU core控制的統一API。主要功能包括:

1)将CPU core抽象為possible、present、online和active四種狀态,并以bitmap的形式,在子產品内部維護所有CPU core的狀态,同時以cpumask的形式向其它子產品提供狀态查詢、狀态修改的API。相關的API如下:

1:

2:

3: #ifdef CONFIG_INIT_ALL_POSSIBLE

4: static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly

5: = CPU_BITS_ALL;

6: #else

7: static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;

8: #endif

9: const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);

10: EXPORT_SYMBOL(cpu_possible_mask);

11:

12: static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;

13: const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);

14: EXPORT_SYMBOL(cpu_online_mask);

15:

16: static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;

17: const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);

18: EXPORT_SYMBOL(cpu_present_mask);

19:

20: static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;

21: const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);

22: EXPORT_SYMBOL(cpu_active_mask);

bitmap的定義是:

#define DECLARE_BITMAP(name,bits)    unsigned long name[BITS_TO_LONGS(bits)]

其本質上是一個long型的數組,數組中每一個bit,代表一個CPU core的狀态。例如long的長度為64位的系統中,如果有8個CPU core,則可以使用長度為1的數組的前8個bit,代表這個8個core的狀态。

這裡一共有4種狀态需要表示:

cpu_possible_bits,系統中包含的所有的可能的CPU core,在系統初始化的時候就已經确定。對于ARM64來說,DTS中所有格式正确的CPU core,都屬于possible的core;

cpu_present_bits,系統中所有可用的CPU core(具備online的條件,具體由底層代碼決定),并不是所有possible的core都是present的。對于支援CPU hotplug的形态,present core可以動态改變;

cpu_online_bits,系統中所有運作狀态的CPU core(後面會詳細說明這個狀态的意義);

cpu_active_bits,有active的程序正在運作的CPU core。

另外,在使用bitmap表示這4種狀态的同時,還提供了4個cpumask,用于對外提供接口。cpumask的本質也是bitmap(多一層封裝而已),隻是kernel提供了一些友善的API,可以以CPU編号為機關,操作cpumask(具體可參考include/linux/cpumask.h)。

注1:這裡有一個關于constant變量的經典例子,大家可以學習一下。

毫無疑問,CPU core的這些狀态,是相當重要的一些狀态,是以kernel希望它們對外(除cpu control外)是read only的,對内又是writeable的。怎麼辦呢?

這裡的設計很巧妙,對内使用4個static的bitmap變量,是以是writeable的。而對外呢,使用4個constant類型的cpumask指針(指針readonly,值readonly),是以是readonly的。

外部子產品read時,通過一層轉換,從static的bitmap中擷取實際的值。是不是很有意思?

下面是這幾個變量有關的操作函數:

1:

2:

3: #define num_online_cpus() cpumask_weight(cpu_online_mask)

4: #define num_possible_cpus() cpumask_weight(cpu_possible_mask)

5: #define num_present_cpus() cpumask_weight(cpu_present_mask)

6: #define num_active_cpus() cpumask_weight(cpu_active_mask)

7: #define cpu_online(cpu) cpumask_test_cpu((cpu), cpu_online_mask)

8: #define cpu_possible(cpu) cpumask_test_cpu((cpu), cpu_possible_mask)

9: #define cpu_present(cpu) cpumask_test_cpu((cpu), cpu_present_mask)

10: #define cpu_active(cpu) cpumask_test_cpu((cpu), cpu_active_mask)

11:

12:

13: #define for_each_possible_cpu(cpu) for_each_cpu((cpu), cpu_possible_mask)

14: #define for_each_online_cpu(cpu) for_each_cpu((cpu), cpu_online_mask)

15: #define for_each_present_cpu(cpu) for_each_cpu((cpu), cpu_present_mask)

16:

17:

18: void set_cpu_possible(unsigned int cpu, bool possible);

19: void set_cpu_present(unsigned int cpu, bool present);

20: void set_cpu_online(unsigned int cpu, bool online);

21: void set_cpu_active(unsigned int cpu, bool active);

22: void init_cpu_present(const struct cpumask *src);

23: void init_cpu_possible(const struct cpumask *src);

24: void init_cpu_online(const struct cpumask *src);

2)提供CPU core的up/down操作,以及up/down時的notifier機制

通俗地講,所謂的CPU core up,就是将某一個CPU core“運作”起來。何為運作呢?回憶一下單核CPU的啟動,就是讓該CPU core在指定的memory位址處取指執行。是以該功能隻對SMP系統有效(使能了CONFIG_SMP)。而CPU core down,就是讓CPU core儲存現場(後面可以繼續執行)後,停止取指,隻有在CPU hotplug功能使能(CONFIG_HOTPLUG_CPU)時有效。這兩個功能對應的API為:

1:

2:

3: int cpu_up(unsigned int cpu);

4:

5: int cpu_down(unsigned int cpu);

同時,提供了CPU up/down時的通知API,具體請參考“include/linux/cpu.h"。

3)提供SMP PM有關的操作

系統休眠過程中,将noboot的CPU禁用,并在系統恢複時恢複(可參考“Linux電源管理(6)_Generic PM之Suspend功能”中的有關描述)。

1: #ifdef CONFIG_PM_SLEEP_SMP

2: extern int disable_nonboot_cpus(void);

3: extern void enable_nonboot_cpus(void);

4: #else

5: static inline int disable_nonboot_cpus(void) { return 0; }

6: static inline void enable_nonboot_cpus(void) {}

7: #endif

4.2 cpu subsystem driver

cpu subsystem driver位于“drivers/base/cpu.c”中,從裝置模型的角度,抽象CPU core裝置,并通過sysfs提供CPU core狀态查詢、hotplug控制等接口。具體如下:

1)注冊一個名稱為“bus”的subsystem(在sysfs中目錄為“/sys/devices/system/cpu/”,有關subsystem的描述,可參考“Linux裝置模型(6)_Bus”)。

2)使用struct cpu抽象CPU core device(見“include/linux/cpu.h”)。

4)從裝置模型的角度,提供CPU core device的register/unregister等接口,并在系統初始化的時候根據CPU core的個數,将這些device注冊到kernel中。同時根據kernel配置,注冊相關的CPU attribute。

1: extern int register_cpu(struct cpu *cpu, int num);

2: extern struct device *get_cpu_device(unsigned cpu);

3: extern bool cpu_is_hotpluggable(unsigned cpu);

4: extern bool arch_match_cpu_phys_id(int cpu, u64 phys_id);

5: extern bool arch_find_n_match_cpu_physical_id(struct device_node *cpun,

6: int cpu, unsigned int *thread);

7:

8: extern int cpu_add_dev_attr(struct device_attribute *attr);

9: extern void cpu_remove_dev_attr(struct device_attribute *attr);

10:

11: extern int cpu_add_dev_attr_group(struct attribute_group *attrs);

12: extern void cpu_remove_dev_attr_group(struct attribute_group *attrs);

13:

14: #ifdef CONFIG_HOTPLUG_CPU

15: extern void unregister_cpu(struct cpu *cpu);

16: extern ssize_t arch_cpu_probe(const char *, size_t);

17: extern ssize_t arch_cpu_release(const char *, size_t);

18: #endif

最終在sysfs中的目錄結構如下:

# ls /sys/devices/system/cpu/

autoplug/  cpu2/      cpuidle/   offline    power/

cpu0/      cpu3/      kernel_max online     present

具體會在後續的文章中詳細說明。

4.3 smp

smp位于“arch/arm64/kernel/smp.c”,在arch-dependent代碼中,承擔承上啟下的角色,主要提供兩類功能:

1)arch-dependent的SMP初始化、CPU core控制等操作(本文需要關注的功能)。

2)IPI(Inter-Processor Interrupts)相關的支援(具體可參考本站“中斷子系統”有關的文章)。

SMP初始化操作,主要負責從DTS中解析CPU core資訊,并擷取必要的資訊以及操作函數集,由smp_init_cpus接口實作,并在setup_arch(arch\arm64\kernel\setup.c)中調用。

CPU core控制相關的接口包括:

1:

2:

3:

6: asmlinkage void secondary_start_kernel(void);

7:

8:

11: struct secondary_data {

12: void *stack;

13: };

14: extern struct secondary_data secondary_data;

15: extern void secondary_entry(void);

16:

17: extern void arch_send_call_function_single_ipi(int cpu);

18: extern void arch_send_call_function_ipi_mask(const struct cpumask *mask);

19:

20: extern int __cpu_disable(void);

21:

22: extern void __cpu_die(unsigned int cpu);

23: extern void cpu_die(void);

secondary_start_kernel、secondary_entry,是那些 noboot CPU的入口,具體後面再詳細介紹;

__cpu_disable、__cpu_die、cpu_die等函數負責disable某個CPU core,它們不會直接操作硬體,而是通過下層的cpu_ops控制具體的CPU core,具體請參考4.4小節的說明。

4.4 cpu ops

由于SMP架構比較複雜,特别是對ARM64而言,又涉及到虛拟化等安全特性,ARM便将CPU core的up/down等電源管理操作,封裝起來(例如封裝到secure mode下,特權級别的OS代碼通過一些指令碼與其互動,具體請參考後續的文章)。在ARM64中,這種封裝便是通過cpu os結構(struct cpu_operations)展現的,如下:

1:

2:

3:

28: struct cpu_operations {

29: const char *name;

30: int (*cpu_init)(struct device_node *, unsigned int);

31: int (*cpu_init_idle)(struct device_node *, unsigned int);

32: int (*cpu_prepare)(unsigned int);

33: int (*cpu_boot)(unsigned int);

34: void (*cpu_postboot)(void);

35: #ifdef CONFIG_HOTPLUG_CPU

36: int (*cpu_disable)(unsigned int cpu);

37: void (*cpu_die)(unsigned int cpu);

38: int (*cpu_kill)(unsigned int cpu);

39: #endif

40: #ifdef CONFIG_ARM64_CPU_SUSPEND

41: int (*cpu_suspend)(unsigned long);

42: #endif

43: };

ARM architecture提供多種可選的cpu ops實作,如spin-table、PSCI(Power State Coordination Interface)等(具體會在後續的文章中較長的描述),開發者可以根據需求,選擇一種。4.3節所描述的smp初始化時,會解析DTS,并填充cpu ops變量。

這裡以PSCI為例,簡單了解一下這些接口的含義(具體說明請參考後續的文章)。

cpu_boot:   Boots a cpu into the kernel.  其實就是将啟動函數(secondary_entry)的實體位址,給到CPU core執行,具體要看spec

cpu_disable: Prepares a cpu to die.

cpu_die: Makes a cpu leave the kernel.

cpu_suspend: Suspends a cpu and saves the required context.

4.5 cpu topology

本文提到了很多諸如SMP、CPU core之類的字眼,相應讀者可能看的不太明白,這和CPU的拓撲結構有關。程序排程、cpufreq等子產品,可能需要根據具體的拓撲結構,制定相應的政策。

ARM64的topology在“./arch/arm64/include/asm/topology.h”中定義,如下:

1: struct cpu_topology {

2: int thread_id;

3: int core_id;

4: int cluster_id;

5: cpumask_t thread_sibling;

6: cpumask_t core_sibling;

7: };

8:

9: extern struct cpu_topology cpu_topology[NR_CPUS];

10:

11: #define topology_physical_package_id(cpu) (cpu_topology[cpu].cluster_id)

12: #define topology_core_id(cpu) (cpu_topology[cpu].core_id)

13: #define topology_core_cpumask(cpu) (&cpu_topology[cpu].core_sibling)

14: #define topology_thread_cpumask(cpu) (&cpu_topology[cpu].thread_sibling)

15:

16: void init_cpu_topology(void);

17: void store_cpu_topology(unsigned int cpuid);

18: const struct cpumask *cpu_coregroup_mask(int cpu);

topology的實作位于“arch/arm64/kernel/topology.c ”中,負責在系統初始化的時候,由boot cpu讀取DTS,填充每個CPU core的struct cpu_topology變量。同時,該檔案提供一些通用的宏定義,用于擷取執行CPU core的資訊,例如該CPU core的package id、core id等。

topology的具體描述,請參考下一篇文章。

4.6 cpu info及其它

cpu info位于“arch/arm64/kernel/cpuinfo.c”,負責在初始化的時候将ARM CPU core有關的資訊,從寄存器中讀出,緩存在struct cpuinfo_arm64類型的變量中,以便後面使用。具體不再較長的描述。

5. 結束語

本文簡單的介紹了CPU core電源管理相關的軟體組成,并認識了cpu ops、cpu topology等概念,後續将通過以下的文章進行更為詳細的分析:

Linux CPU core的電源管理(2)_cpu topology,認識并了解ARM處理器的拓撲結構,以及cluster(socket)、core、thread等處理器結構相關的概念;

Linux CPU core的電源管理(4)_PSCI,分析ARM PSCI(Power State Coordination Interface)接口;

Linux CPU core的電源管理(5)_cpu control,從系統的角度,分析系統初始化、程序排程、CPU hotplug等場景下,CPU up/down等操作的流程;

Linux cpufreq framework系列文章,分析CPU動态頻率/電壓調整相關的實作。

原創文章,轉發請注明出處。蝸窩科技,www.wowotech.net。

linux cpu運作錯誤的是什麼意思,Linux CPU core的電源管理(1)_概述

評論:

2016-05-27 09:13

hi,wowo,

我們經常所說的DVFS,狹隘地是指interactive這個governor嗎?還有其他地方有涉及嗎?

啥時候有空把interactive governor分析一下啊?

謝謝

2016-05-27 10:55

@koala:可以這麼了解,不過你也可以設計自己的政策。

有時間可以分析一下interactive governor,多謝關注~

2016-01-27 19:56

寫的很好,才發現有這麼好的一個地方,謝謝! 希望能快點學習,以後我也能在這裡寫一篇文章。 :)

2016-01-28 08:57

@archer:希望不止有一篇哈,等着你:-)

YANG23

2015-09-17 15:35

非常期待 電源管理PSCI相關的文章,希望作者盡快發表啊,另外能不能對比一下 PSCI和SMP SPIN TABLE優劣主要對功耗的影響呢  期待作者

2015-09-18 12:20

@YANG23:多謝關注,其實一般情況下,不會深入到PSCI的,會用就可以了。

PSCI主要的特點,就是支援CPU hotplug。對功耗而言,PSCI的ops可能會單獨core的power,因而肯定比SPIN table好。

2015-08-30 20:25

非常期待 Linux CPU core的電源管理(5)_cpu control

這篇文章.

現在移植一個cpuidle功能到高版本核心, bootloader一走完, 就hang住了.

我猜和PM與SMP初始化哪裡出錯了. 很多代碼不是我寫的, 現在正在看.

請教lz, PM和smp初始化相關的代碼有哪些呢?

2015-08-31 09:05

@firo:解決問題的時候,看代碼不是最有效的方法,你可以用一些收到,查一查hang在哪裡了。

另外,這個時候和PM沒有關系吧?SMP的話,可以去“arch/xxx/kernel/smp.c”看看。

2015-08-31 10:42

@wowo:感謝.

核心什麼資訊都沒列印出來,就hang了.

因為我是隻把上個版本cpuidle & suspend相關的代碼和進來.系統就不能啟動了. 是以和PM關系非常大.

2015-09-01 09:45

@firo:可以把選項CONFIG_DEBUG_LL(kernel low-level debugging functions)打開,即使console還沒有初始化,也可以用printascii等函數加入列印資訊,這樣就可以定位具體挂在哪裡了?這個功能非常實用。

2015-09-01 11:13

@snail:感謝!

昨天用printascii看到4個core都走到了cpu_startup_entry.看上去系統正常啟動了.

1. 可能是console沒有正常啟動是以看不到printk之類的資訊, 機率較小, 因為支部分代碼沒動過.

2. wowo知道用什麼方法可以, 把printk在系統啟動時打到console裡面的資訊用printascii 或者其他方法輸出出來, 看看使用者态是否起來了? 排除是否隻是console有問題.

3. 還有一個問題, arm多核cpu_do_idle是用宏定義成processor._do_idle(), 我沒有找到這個_do_idle是在哪裡初始化的.

在setup_processor隻看到了processor的整體指派語句

list = lookup_processor_type(read_cpuid_id());

...

processor = *list->proc;

我昨天用printascii列印的時候隻列印到

static void default_idle(void)

{

if (arm_pm_idle)

arm_pm_idle();

else

cpu_do_idle(); //這是個宏

local_irq_enable();

}接下來不知道怎麼走了.

2015-09-01 12:26

@firo:2. 很久以前我們做過,方法有多種,比如重寫printk,調用printascii 輸出。

3. processor的_do_idle,一般都是發指令到CPU core中了,不同平台不一樣。一般情況下,跟到這裡面,就沒有方法接着跟了。

2015-08-20 14:51

cpu_online_bits,系統中所有運作狀态的CPU core(後面會詳細說明這個狀态的意義);

系統是如果确定運作狀态呢?

cpu_active_bits,有active的程序正在運作的CPU core。

這個online 與active 如何差別呢?

2015-08-20 17:11

@tigger:不好意思,一直空接着寫這一塊的東西:

cpu_online_bits比較容易了解,CPU啟動的話(也即CPU運作了kernel代碼,secondary_start_kernel),就認為是online的,會通過set_cpu_online接口設定這個bit mask。

而cpu_active_bits,則表示有任務在這個CPU上排程,由kenrel sched子產品,調用set_cpu_active設定。

online和active的差別是,隻要active,一定是online,是以online更多的代碼了CPU的“實體狀态”,而active,更多的是OS排程層面的抽象,CPU本身沒有這一個狀态。

buhui

2015-08-08 15:19

好文章,讀這樣的文章,就像饑餓的人撲到了面包上。比陳浩強的文章有天壤之别。

2015-05-10 14:57

高端手機平台現在都是cpuidle + scheduler(HMP) 做省功耗方案。hotplug隻在thermal控制中使用了。

2015-05-11 10:29

@schedule:多謝分享,第一次聽說HMP的概念,要了解了解。

2015-05-24 22:42

@wowo:1、是的,64bit 4核以上arm cpu方案基本都采用HMP(GTS) + Interactive(DVFS)組合,但功耗調試需要花時間。

2015-05-25 11:05

@zzq57683968:原來HMP就是big·little模式的multi-processor,我對HMP這個詞不太了解。big·little是ARM為了性能和功耗平衡搞出來的一個東西,和CPU topology有關。

DVFS還沒有深入了解,就是我們項目加上去過一段時間,後來又去掉了,據說不太穩定,呵呵。

2015-05-10 14:38

CPU/GPU 絕對耗電大戶啊

發表評論:

昵稱

郵件位址 (選填)

個人首頁 (選填)

linux cpu運作錯誤的是什麼意思,Linux CPU core的電源管理(1)_概述