http://blog.chinaunix.net/uid-24148050-id-300576.html
分類: LINUX
一、簡介
2.6核心上一個新的特性就是per-CPU變數。顧名思義,就是每個處理器上有此變數的一個副本。
per-CPU的最大優點就是,對它的訪問幾乎不需要鎖,因為每個CPU都在自己的副本上工作。
tasklet、timer_list等機制都使用了per-CPU技術。
二、API使用
注意,2.6核心是搶佔式的。
所以在訪問per-CPU變數時,應使用特定的API來避免搶佔,即避免它被切換到另一個CPU上被處理。
per-CPU變數可以在編譯時間聲明,也可以在系統運行時動態產生
執行個體一:
編譯期間建立一個per-CPU變數:
DEFINE_PER_CPU(int,my_percpu); //聲明一個變數
DEFINE_PER_CPU(int[3],my_percpu_array); //聲明一個數組
使用編譯時間產生的per-CPU變數:
ptr = get_cpu_var(my_percpu); // 使用ptr
put_cpu_var(my_percpu); //
當然,也可以使用下列宏來訪問特定CPU上的per-CPU變數
per_cpu(my_percpu, cpu_id); //
per-CPU變數匯出,供模組使用:
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
執行個體二:
動態分配per-CPU變數:
void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);
使用動態產生的per-CPU變數:
int cpu;
cpu = get_cpu();
ptr = per_cpu_ptr(my_percpu);
//使用ptr
put_cpu();
三、實現
使用上面的API為什麼就能避免搶佔問題呢,看看代碼實現就知道了
#define get_cpu_var(var) (*({ \
extern int simple_identifier_##var(void); \
preempt_disable(); \
&__get_cpu_var(var); }))
#define put_cpu_var(var) preempt_enable()
#define get_cpu() ({ preempt_disable(); smp_processor_id(); }) #define put_cpu() preempt_enable()
關鍵就在於 preempt_disable 和 preempt_enable 兩個調用,分別是禁止搶佔和開啟搶佔
搶佔相關的東東以後再看
per-cpu 變數的引入有效解決了SMP系統中處理器對鎖得競爭,每個cpu只需訪問自己的本地變數。本文闡述了per-cpu變數在2.6核心上的實現和相關操作。 在系統編譯階段我們就手工的定義了一份所有的per-cpu變數,這些變數的定義是通過宏DEFINE_PER_CPU實現的: 11 #define DEFINE_PER_CPU(type, name) \
12 __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
從上面的代碼我們可以看出,手工定義的所有per-cpu變數都是放在.data.percpu段的。注意上面的宏只是在SMP體繫結構下才如此定義。如果不是SMP結構的電腦那麼只是簡單的把所有的per-cpu變數放到全域變數應該放到的地方。
單CPU的per-cpu變數定義: 27 #else /* ! SMP */
28
29 #define DEFINE_PER_CPU(type, name) \
30 __typeof__(type) per_cpu__##name
在瞭解了上述代碼後,我們還必須弄清楚一點:單CPU的電腦中使用的per-cpu變數就是通過上述宏定義的放在全域資料區的per-cpu變 量。而在SMP體繫結構中,我們使用卻不是放在.data.percpu段的變數,設想一下如果使用這個變數,那麼應該哪個CPU使用呢。事實上,SMP 下,每個cpu使用的都是在.data.percpu段中的這些per-cpu變數的副本,有幾個cpu就建立幾個這樣的副本。
在系統初始化期 間,start_kernel()函數中調用setup_per_cpu_areas()函數,用於為每個cpu的per-cpu變數副本分配空間,注意 這時alloc記憶體 Clerk還沒建立起來,該函數調用alloc_bootmem函數為初始化期間的這些變數副本分配物理空間。 332 static void __init setup_per_cpu_areas(void)
/* */
333 {
334 unsigned long size, i;
335 char *ptr;
336
337 /* Copy section for each CPU (we discard the original) */
338 size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
339 #ifdef CONFIG_MODULES
340 if (size < PERCPU_ENOUGH_ROOM)
341 size = PERCPU_ENOUGH_ROOM;
342 #endif
343
344 ptr = alloc_bootmem(size * NR_CPUS);
345
346 for (i = 0; i < NR_CPUS; i++, ptr += size) {
347 __per_cpu_offset[i] = ptr - __per_cpu_start;
348 memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
349 }
350 }
351 #endif /* !__GENERIC_PER_CPU */
上述函數,在分配好每個cpu的per-cpu變數副本所佔用的物理空間的同時,也對__per_cpu_offset[NR_CPUS]數組進行了初始化用於以後找到指定CPU的這些per-cpu變數副本。
15 #define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
16 #define __get_cpu_var(var) per_cpu(var, smp_processor_id()) 這兩個宏一個用於獲得指定cpu的per-cpu變數,另一個用於獲的本地cpu的per-cpu變數,可以自己分析一下。