歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 深入淺出Linux之內核基礎層

深入淺出Linux之內核基礎層

日期:2017/2/28 16:00:07   编辑:Linux教程
前言將內核分成了內核基礎層和內核應用層。記得我們的目標是“快速流暢的閱讀內核代碼”,為了達到這個目標,必須先了解內核的基礎和數據結構。這些知識使用范圍很廣,如果不了解,在內核代碼的理解上就容易出現障礙。

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調用實現進程調度的實例在內核中很多,而且內核中還實現了一系列的函數,簡單介紹一下。

  • wait_event_timeout :和wait_event的區別是有時間限制,如果條件滿足,進程恢復運行,或者時間到達,進程同樣恢復運行
  • wait_event_interruptible:和wait_event類似,不同之處進程處於可打斷的睡眠。而wait_event設置進程處於不可打斷的睡眠。兩者區別何在?可打斷的睡眠進程可以接收到信號,而不可打斷的睡眠進程不能處理信號。
  • wait_event_interruptible_timeout:和wait_event_interruptible相比,多個了時間限制。在規定的時間到達後,進程恢復運行。
  • wait_event_interruptible_exclusive:和wait_event_interruptible區別是排它性的等待。何謂排它性的等待?有一些進程都在等待隊列中,當喚醒的時候,內核是喚醒所有的進程。如果進程設置了排它性等待的標志,那麼喚醒所有非排它性的進程和一個排它性進程。

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創建一個內核線程。

Copyright © Linux教程網 All Rights Reserved