第三章 OS Environment包裝Linux驅動程序的方法分析
1. Linux內核為驅動程序提供的支持
為了讓驅動程序能夠正常的工作,操作系統內核必須要為驅動程序提供一系列的支持,這些支持包括許多方面。舉例來說,驅動程序需要像內核申請使用系統內存,驅動程序需要向內核使用系統硬件資源(如端口、DMA控制器),驅動程序需要向內核注冊自己,尤其是那些占用中斷資源的驅動程序。
要將其它的系統的驅動程序包裝進來,就首先要了解其它的系統的內核是如何為自己系統的驅動程序提供支持的,然後通過OS Environment為這些驅動提供支持就可以了。由於我們是在Linux上使用OSKit的,所以就順便選擇了分析一下Linux大概通過哪些函數為內核的驅動程序提供支持。我們並沒有將所有的支持函數都找出來,只是找了一些比較重要比較典型的,為的是學習OSKit中包裝別人的驅動程序的方法。
Linux中這些函數很多在kernel目錄下,在這個目錄下的dma.c、resource.c、printk.c中,分配內存的函數在mm目錄下。下面我們大概看一下Linux中的情況。
1.1 內存分配函數
在Linux中,驅動程序通常使用kmalloc函數申請內存,當然,也可以使用其它的函數申請。但因為kmalloc函數使用的比較多,我們重點說一下它。
void *kmalloc(size_t size, int flags)
kmalloc函數和malloc函數相似,它有兩個參數,一個參數是size,即申請內存塊的大小,這個參數比較簡單,就像malloc中的參數一樣。第二個參數是一個標志,在裡面可以指定優先權之類的信息。在Linux中,有以下的一些優先權:
GFP_KERNEL,它的意思是該內存分配是由運行在內核模式的進程調用的,即當內存低於min_free_pages的時候可以讓該進程進入睡眠;
GFP_ATOMIC,原子性的內存分配允許在實際內存低於min_free_pages時繼續分配內存給進程。
GFP_DMA:此標志位需要和GFP_KERNEL、GFP_ATOMIC等一起使用,用來申請用於直接內存訪問的內存頁。
1.2 DMA
Linux的設備驅動程序在使用DMA通道以前必須申請,在使用之後必須要釋放,這樣從理論上講,就允許幾個設備同時使用一個DMA通道了,當然,很多設備是無法這樣使用的。
Linux中申請和釋放DMA通道的函數是request_dma( )和free_dma( )。
1.3 I/O端口
Linux中,系統維護著一個輸入輸出端口的設備注冊表,設備驅動程序在使用輸入輸出端口前,也必須要進行申請。只有申請成功,才能夠使用相應的端口。Linux內核提供了三個函數用於申請、釋放端口及檢查端口是否被占用。這三個函數是:
void request_region(unsigned long from,
unsigned long num, const char *name)
這個函數用來申請一塊輸入輸出區域。
void release_region(unsigned long from, unsigned long num)
這個函數用來釋放一塊輸入輸出區域。
int check_region(unsigned long from, unsigned long num)
這個函數用來檢測一塊輸入輸出區域是否被占用。
1.4 中斷號的申請
中斷是非常寶貴的系統資源,在Linux中,系統維護著一個象輸入輸出設備注冊表一樣的中斷資源注冊表。任何驅動程序在使用一個中斷,即作為中斷處理程序之前,要申請一個中斷通道,並且,在處理完成之後,可以釋放它。在Linux中,對於i386平台,這些函數定義在arch/i386/kernel/irq.c中,申請和釋放一個中斷通道的函數是:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
void free_irq(unsigned int irq, void *dev_id)
其中各個參數的含義如下:
irq: 要申請的中斷資源的中斷號。
handler: 指向要安裝的中斷處理函數的指針。
irqflags: 這是一個與中斷管理有關的各種選項的字節掩碼。此項可選值如下:SA_INTERRUPT、SA_SHIRQ、SA_SAMPLE_RANDOM。如果不置SA_INTERRUPT,則標明這個中斷處理程序時一個"慢速"的中斷處理程序,如果置了這一位,這個處理程序就是一個"快速"的中斷處理程序。
devname:傳遞給request_irq的字符串,可以通過Linux的proc文件系統顯示中斷地擁有者的名字。
dev_id:這個參數用於共享中斷號,驅動程序可以自由的任意的使用dev_id,除非強制使用中斷共享,dev_id通常被置為NULL。
1.5 打印函數
內核程序為了輸出一些信息,就需要一個內核打印函數,Linux中的在內核中使用的打印函數是printk,這個函數的參數同普通的printf是一樣的。
2. OSKit替換Linux內核-驅動界面的方法
大致了解了Linux內核為其驅動程序提供的支持之後,我們就可以來看一看OSKit究竟是采用什麼方法來使用Linux的驅動程序的源碼的。OSKit使用Linux驅動程序的方法,並不是去修改Linux驅動程序的源代碼。
看了上面Linux內核所提供的部分支持,再看看前面OS Environment所提供的支持,不難發現它們有著很多的相似之處。Linux的驅動程序要使用內核提供的支持,就必須要包含內核的頭文件,因此,只要根據OSKit的接口所提供的功能,重寫一下Linux驅動程序需要的那些應當由內核提供的支持函數,就可以讓Linux的驅動程序作為OSKit的一部分進行編譯了。
當然,僅僅能夠編譯還是不夠的,還必須要讓驅動程序能夠正常的工作,這一方面要在包裝原有的代碼上面下功夫,比如要能夠正確的處理函數傳遞過來的各種參數,尤其是各種標志掩碼。另一方面,還要為驅動程序建立一個可以運行的環境,這一部分內容就是OSKit整體設計的問題了,在前面的OSKit介紹中已經大概講過了。當真正要設計一個目標系統的時候,這一點就是必須要考慮得非常仔細了。
3. OSKit包裝Linux驅動程序的實際例子
上面的內容,完全是對OSKit包裝Linux驅動程序的方法的理論上的敘述,下面我們具體看兩個OSKit中的例子,這樣就可以對這種方法有更深刻的了解。
3.1 DMA
OSKit為Linux的驅動程序提供了支持,提供了Linux驅動程序需要的兩個函數,request_dma和free_dma,下面是這兩個函數在OSKit中的實現。此段代碼在linux/dev/dma.c中。
/*
* Allocate a DMA channel.
*/
int
request_dma(unsigned int drq, const char *name)
{
struct task_struct *cur = current;
int chan;
chan = osenv_isadma_alloc(drq);
current = cur;
return chan;
}
/*
* Free a DMA channel.
*/
void
free_dma(unsigned int drq)
{
struct task_struct *cur = current;
osenv_isadma_free(drq);
current = cur;
}
從上面的兩個程序,我們可以看出,OSKit所提供的request_dma( )函數實際上是調用了OS Environment所提供的osenv_isadma_alloc( )函數,而free_dma( )則是調用了osenv_isadma_free( )函數。
DMA的例子時非常簡單的,下面我們看一個比較復雜一點的例子。
3.2 內存管理
void *
kmalloc(unsigned int size, int priority)
{
int flags;
void *p;
flags = KMALLOC_FLAGS;
if (priority & GFP_ATOMIC)
flags |= OSENV_NONBLOCKING;
if (priority & GFP_DMA)
flags |= OSENV_ISADMA_MEM;
p = oskit_linux_mem_alloc(size, flags, 0);
return (p);
}
上面的程序段,就是OSKit提供的由Linux驅動程序使用的用來進行內存分配的函數。這個函數並沒有直接調用OS Environment中的osenv_mem_alloc函數,這是有原因的,我們會在後面介紹。
這裡值得注意的是OSKit用自己的標志代替了Linux驅動程序所使用的標志。比如,如果Linux驅動程序給出了標志GFP_ATOMIC,則用相應的OSENV_NONBLOCKING來代替,而GFP_DMA則用相應的OSENV_ISADMA_MEM來代替。
下面我們來看一下上面的程序段中用到的oskit_linux_mem_alloc函數。
void *
oskit_linux_mem_alloc(oskit_size_t size, osenv_memflags_t flags, unsigned align)
{
struct task_struct *cur = current;
void *mem;
mem = osenv_mem_alloc(size, flags, align);
current = cur;
return mem;
}
這個函數,實際調用了OSKit所提供的內存分配函數,在調用osenv_mem_alloc之前,保存了當前進程的進程控制塊的指針,在內存申請之後再恢復,這是因為內存申請的請求有可能被阻塞。
另外還有一個原因,就是Linux實際提供的內存分配函數並不止kmalloc一個,還有get_free_pages和vmalloc等多個,然而在OSKit的缺省的實現中,只是通過osenv_mem_alloc來進行內存分配的。
OSKit為Linux所提供的內存分配函數是在linux/share/kmem.c中定義的。
4. 小結
通過上面的兩個例子,不難看出OSKit包裝Linux驅動程序的基本方法,輸入輸出端口、中斷和內核打印輸出等部分和這些部分實現的基本原理是相同的,當然隨著支持的不同以及OSKit同Linux所提供的底層支持之間的差異,程序的寫法是不同的。我們所分析的這幾個例子是比較簡單的,同時也並沒有把Linux的所有函數都列出來,我們分析這些例子,目的是學到一種方法及思想。
OSKit除了使用了Linux的設備驅動程序,還是用了FreeBSD的設備驅動程序、Linux的網絡設備驅動程序等等很多其它操作系統中的模塊,我們選擇Linux的設備驅動程序進行分析是因為這一段的程序代碼相對比較清晰簡單,那些部分同這個部分所使用的思想和方法是大致相同的,只是接口不同、數據結構不同而已。