1.內核對內存的使用
簡單說,內核提供了兩個層次的內存分配接口。一個是從伙伴系統分配,一個是從slab分配。關於伙伴系統和slab機制,在後面的章節再分析。這裡只需要了解,伙伴系統是最底層的內存管理機制,提供基於基於頁式的內存管理,而slab是伙伴系統之上的內存管理,提供基於對象的內存管理。
從伙伴系統分配內存的調用是alloc_pages,注意此時得到的頁面的地址,如果要獲得能使用的內存地址,還需要page_address調用來獲得內存地址。
如果要直接獲得內存地址,需要使用__get_free_pages。__get_free_pages其實封裝了alloc_pages和page_address兩個函數。
alloc_pages申請的內存是以頁為單元的,最少也要一個頁。如果只是一小塊內存,一個頁就浪費了,而且內核中很多應用也希望一種對象化的內存管理,希望內存管理能自動的構造和析構對象,這都很接近面向對象的思路了。這就是slab內存管理。
要從slab申請內存,則需要創建一個slab對象,使用kmem_cache_create創建slab對象,kmem_cache_create可以提供對象的名字和大小,構造函數和析構函數等。然後通過kmem_cache_alloc和kmem_cache_free來申請和釋放內存。
內核中常用的kmalloc其實也是slab管理。只不過內核已經創建好了一些固定大小的對象,用戶通過kmalloc申請的時候,就使用了這些對象。
提供一個內核的例子:
======================================================================
創建slab對象:
bh_cachep = kmem_cache_create("buffer_head",
sizeof(struct buffer_head), 0,
(SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
SLAB_MEM_SPREAD),
init_buffer_head,
NULL);
創建了一個名為“buffer_head"的對象,對象的初始化函數為init_buffer_head
申請slab對象:
struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags);
釋放slab對象:
kmem_cache_free(bh_cachep, bh);
內核中還有一個內存分配調用:vmalloc。Vmalloc涉及到高端內存和建立頁表映射的概念,作為內核基礎的本節就不分析了。 理解了上面的幾個函數調用,閱讀內核代碼的時候,就可以清晰內核中對內存的使用。至於內存管理的結構和細節,在後面我們再討論。
2.內核使用的進程調度
內核中經常需要使用進程的調度。首先看一個例子。
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
上文定義了一個wait對象,然後設置進程睡眠。如果有其它進程喚醒這個進程後,判斷條件是否滿足,如果滿足則刪除wait對象,否則進程繼續睡眠。
這是一個很常見的例子,使用wait_event調用實現進程調度的實例在內核中很多,而且內核中還實現了一系列的函數,簡單介紹一下。
3.內核的軟中斷和tasklet
linux內核把中斷的執行分拆成兩部分。和硬件關系緊密,必須關中斷執行的操作放在中斷上下文中執行,而可以開中斷執行的操作則放在軟中斷上下文執行。
為此目的,linux內核定義了幾個缺省的軟中斷,網絡設備有自己的發送和接收軟中斷,塊設備也有自己的軟中斷。為了方便使用,內核還定義了一個TASKLET軟中斷。TASKLET是一種特殊的軟中斷,一個TASKLET只能由一個CPU 執行,同一刻,不同的TASKLET可以在不同的CPU上執行,而同樣的TASKLET只能有一個在執行。這個和軟中斷不同,軟中斷同一刻可以在不同的CPU並行執行,因此軟中斷必須考慮重入的問題。
內核中很多地方使用了tasklet。先分析一個例子:
======================================================================
DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0);
tasklet_schedule(&hil_mlcs_tasklet);
上面的例子首先定義了一個tasklet,它的執行函數是hil_mlcs_process。當程序中調用
tasklet_schedule,會把要執行的結構插入到一個tasklet鏈表。然後觸發一個TASKLET軟
中斷。每個CPU都有自己的tasklet鏈表,內核會根據情況,確定在何時執行tasklet。
可以看到,TASKLET使用起來很簡單。本節只需要了解在內核如何使用即可。
4.工作隊列
工作隊列和tasklet相似,都是一種延緩執行的機制。不同之處是工作隊列有自己的進程上下文,所以工作隊列可以睡眠,可以被調度。而tasklet一般要在軟中斷上下文中執行。
看一個工作隊列的例子:
======================================================================
INIT_WORK(&ioc->sas_persist_task,
mptsas_persist_clear_table,
(void *)ioc);
schedule_work(&ioc->sas_persist_task);
使用工作隊列很簡單,schedule_work就把用戶定義的work_struct加入系統的隊列中,並喚醒系統線程去執行。那麼是那一個系統線程執行用戶的work_struct?實際上,內核初始化的時候,就要創建一個工作隊列keventd_wq,同時為這個工作隊列創建系統線程(缺省是為每個CPU創建一個系統線程)。
內核同時還提供了create_workqueue和create_singlethread_workqueue函數,這樣用戶可以創建自己的工作隊列和執行線程,而不用內核提供的工作隊列。看內核的例子:
======================================================================
kblockd_workqueue = create_workqueue("kblockd");
int kblockd_schedule_work(struct work_struct *work)
{
return queue_work(kblockd_workqueue, work);
}
kblockd_workqueue是內核通用塊層提供的工作隊列,需要由kblockd_workqueue執行的工作,就要調用kblockd_schedule_work,其實就是調用queue_work把work加入到kblockd_workqueued工作隊列的任務鏈表。
create_singlethread_workqueue和create_workqueue類似,不同之處像名字揭示的一樣,create_singlethread_workqueue只創建一個內核線程,而不是為每個CPU創建一個內核線程。