1. 程式人生 > >linux 裝置驅動程式設計

linux 裝置驅動程式設計

驅動

目 錄


驅動

    Linux系統支援三種類型的硬體裝置:字元裝置、塊設
<script type="text/javascript"><!--google_ad_client = "pub-2299987709779770";google_ad_width = 300;google_ad_height = 250;google_ad_format = "300x250_as";google_ad_type = "text_image";google_ad_channel ="";google_color_border = "FFFFFF";google_color_bg = "FFFFFF";google_color_link = "0000FF";google_color_url = "008000";google_color_text = "000000";//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
備和網路裝置。字元裝置是直接讀取的,不必使用緩衝區。例如,系統的序列口/dev/cua0和/dev/cua1。塊裝置每次只能讀取一定大小的塊的倍數,通常一塊是512或者1024位元組。塊裝置通過緩衝區讀寫,並且可以隨機地讀寫。塊裝置可以通過它們的裝置檔案存取,但通常是通過檔案系統存取。只有塊裝置支援掛接的檔案系統。網路裝置是通過BSD套接字介面存取的。

    Linux系統支援多種裝置,這些裝置的驅動程式之間有一些共同的特點:
    * 核心程式碼:裝置驅動程式是系統核心的一部分,所以如果驅動程式出現錯誤的話,將可能嚴重地破壞整個系統。
    * 核心介面:裝置驅動程式必須為系統核心或者它們的子系統提供一個標準的介面。例如,一個終端驅動程式必須為Linux核心提供一個檔案I/O介面;一個SCSI裝置驅動程式應該為SCSI子系統提供一個SCSI裝置介面,同時SCSI子系統也應為系統核心提供檔案I/O和緩衝區。
    * 核心機制和服務:裝置驅動程式利用一些標準的核心服務,例如記憶體分配等。
    * 可裝入:大多數的Linux裝置驅動程式都可以在需要時裝入核心,在不需要時解除安裝。
    * 可設定:Linux系統裝置驅動程式可以整合為系統核心的一部分,至於哪一部分需要整合到核心中,可以在系統編譯時設定。

[目錄]

I/O埠

  關鍵詞:裝置管理、驅動程式、I/O埠、資源

  申明:這份文件是按照自由軟體開放原始碼的精神釋出的,任何人可以免費獲得、使用和重新發布,但是你沒有限制別人重新發布你釋出內容的權利。釋出本文的目的是希望它能對讀者有用,但沒有任何擔保,甚至沒有適合特定目的的隱含的擔保。更詳細的情況請參閱GNU通用公共許可證(GPL),以及GNU自由文件協議(GFDL)。

  幾乎每一種外設都是通過讀寫裝置上的暫存器來進行的。外設暫存器也稱為“I/O埠”,通常包括:控制暫存器、狀態暫存器和資料暫存器三大類,而且一個外設的暫存器通常被連續地編址。CPU對外設IO埠實體地址的編址方式有兩種:一種是I/O對映方式(I/O-mapped),另一種是記憶體對映方式(Memory-mapped)。而具體採用哪一種則取決於CPU的體系結構。

  有些體系結構的CPU(如,PowerPC、m68k等)通常只實現一個實體地址空間(RAM)。在這種情況下,外設I/O埠的實體地址就被對映到CPU的單一實體地址空間中,而成為記憶體的一部分。此時,CPU可以象訪問一個記憶體單元那樣訪問外設I/O埠,而不需要設立專門的外設I/O指令。這就是所謂的“記憶體對映方式”(Memory-mapped)。

  而另外一些體系結構的CPU(典型地如X86)則為外設專門實現了一個單獨地地址空間,稱為“I/O地址空間”或者“I/O埠空間”。這是一個與CPU地RAM實體地址空間不同的地址空間,所有外設的I/O埠均在這一空間中進行編址。CPU通過設立專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元(也即I/O埠)。這就是所謂的“I/O對映方式”(I/O-mapped)。與RAM實體地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O對映方式”的一個主要缺點。

  Linux將基於I/O對映方式的或記憶體對映方式的I/O埠通稱為“I/O區域”(I/O region)。在討論對I/O區域的管理之前,我們首先來分析一下Linux是如何實現“I/O資源”這一抽象概念的。

3.1 Linux對I/O資源的描述

  Linux設計了一個通用的資料結構resource來描述各種I/O資源(如:I/O埠、外設記憶體、DMA和IRQ等)。該結構定義在include/linux/ioport.h標頭檔案中:


  struct resource {
        const char *name;
        unsigned long start, end;
        unsigned long flags;
        struct resource *parent, *sibling, *child;
  };

  各成員的含義如下:

  1. name指標:指向此資源的名稱。
  2. start和end:表示資源的起始實體地址和終止實體地址。它們確定了資源的範圍,也即是一個閉區間[start,end]。
  3. flags:描述此資源屬性的標誌(見下面)。
  4. 指標parent、sibling和child:分別為指向父親、兄弟和子資源的指標。

  屬性flags是一個unsigned long型別的32位標誌值,用以描述資源的屬性。比如:資源的型別、是否只讀、是否可快取,以及是否已被佔用等。下面是一部分常用屬性標誌位的定義(ioport.h):


/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS                0x000000ff        /* Bus-specific bits */

#define IORESOURCE_IO                0x00000100        /* Resource type */
#define IORESOURCE_MEM                0x00000200
#define IORESOURCE_IRQ                0x00000400
#define IORESOURCE_DMA                0x00000800

#define IORESOURCE_PREFETCH        0x00001000        /* No side effects */
#define IORESOURCE_READONLY        0x00002000
#define IORESOURCE_CACHEABLE        0x00004000
#define IORESOURCE_RANGELENGTH        0x00008000
#define IORESOURCE_SHADOWABLE        0x00010000
#define IORESOURCE_BUS_HAS_VGA        0x00080000

#define IORESOURCE_UNSET        0x20000000
#define IORESOURCE_AUTO                0x40000000
#define IORESOURCE_BUSY                0x80000000
        /* Driver has marked this resource busy */

  指標parent、sibling和child的設定是為了以一種樹的形式來管理各種I/O資源。

3.2 Linux對I/O資源的管理

  Linux是以一種倒置的樹形結構來管理每一類I/O資源(如:I/O埠、外設記憶體、DMA和IRQ)的。每一類I/O資源都對應有一顆倒置的資源樹,樹中的每一個節點都是一個resource結構,而樹的根結點root則描述了該類資源的整個資源空間。

  基於上述這個思想,Linux在kernel/Resource.c檔案中實現了對資源的申請、釋放及查詢等操作。

  3.2.1 I/O資源的申請

  假設某類資源有如下這樣一顆資源樹:

  節點root、r1、r2和r3實際上都是一個resource結構型別。子資源r1、r2和r3通過sibling指標連結成一條單向非迴圈連結串列,其表頭由root節點中的child指標定義,因此也稱為父資源的子資源連結串列。r1、r2和r3的parent指標均指向他們的父資源節點,在這裡也就是圖中的root節點。

  假設想在root節點中分配一段I/O資源(由圖中的陰影區域表示)。函式request_resource()實現這一功能。它有兩個引數:①root指標,表示要在哪個資源根節點中進行分配;②new指標,指向描述所要分配的資源(即圖中的陰影區域)的resource結構。該函式的原始碼如下(kernel/resource.c):


  int request_resource(struct resource *root, struct resource *new)
  {
        struct resource *conflict;

        write_lock(&resource_lock);
        conflict = __request_resource(root, new);
        write_unlock(&resource_lock);
        return conflict ? -EBUSY : 0;
  }

  對上述函式的NOTE如下:

  ①資源鎖resource_lock對所有資源樹進行讀防寫,任何程式碼段在訪問某一顆資源樹之前都必須先持有該鎖。其定義如下(kernel/Resource.c):

  static rwlock_t resource_lock = RW_LOCK_UNLOCKED;

  ②可以看出,函式實際上是通過呼叫內部靜態函式__request_resource()來完成實際的資源分配工作。如果該函式返回非空指標,則表示有資源衝突;否則,返回NULL就表示分配成功。

  ③最後,如果conflict指標為NULL,則request_resource()函式返回返回值0,表示成功;否則返回-EBUSY表示想要分配的資源已被佔用。

  函式__request_resource()完成實際的資源分配工作。如果引數new所描述的資源中的一部分或全部已經被其它節點所佔用,則函式返回與new相沖突的resource結構的指標。否則就返回NULL。該函式的原始碼如下


(kernel/Resource.c):
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource
  (struct resource *root, struct resource *new)
{
        unsigned long start = new->start;
        unsigned long end = new->end;
        struct resource *tmp, **p;

        if (end < start)
                return root;
        if (start < root->start)
                return root;
        if (end > root->end)
                return root;
        p = &root->child;
        for (;;) {
                tmp = *p;
                if (!tmp || tmp->start > end) {
                        new->sibling = tmp;
                        *p = new;
                        new->parent = root;
                        return NULL;
                }
                p = &tmp->sibling;
                if (tmp->end < start)
                        continue;
                return tmp;
        }
}

  對函式的NOTE:

  ①前三個if語句判斷new所描述的資源範圍是否被包含在root內,以及是否是一段有效的資源(因為end必須大於start)。否則就返回root指標,表示與根結點相沖突。

  ②接下來用一個for迴圈遍歷根節點root的child連結串列,以便檢查是否有資源衝突,並將new插入到child連結串列中的合適位置(child連結串列是以I/O資源實體地址從低到高的順序排列的)。為此,它用tmp指標指向當前正被掃描的resource結構,用指標p指向前一個resource結構的sibling指標成員變數,p的初始值為指向root->sibling。For迴圈體的執行步驟如下:

  l 讓tmp指向當前正被掃描的resource結構(tmp=*p)。

  l 判斷tmp指標是否為空(tmp指標為空說明已經遍歷完整個child連結串列),或者當前被掃描節點的起始位置start是否比new的結束位置end還要大。只要這兩個條件之一成立的話,就說明沒有資源衝突,於是就可以把new鏈入child連結串列中:①設定new的sibling指標指向當前正被掃描的節點tmp(new->sibling=tmp);②當前節點tmp的前一個兄弟節點的sibling指標被修改為指向new這個節點(*p=new);③將new的parent指標設定為指向root。然後函式就可以返回了(返回值NULL表示沒有資源衝突)。

  l 如果上述兩個條件都不成立,這說明當前被掃描節點的資源域有可能與new相沖突(實際上就是兩個閉區間有交集),因此需要進一步判斷。為此它首先修改指標p,讓它指向tmp->sibling,以便於繼續掃描child連結串列。然後,判斷tmp->end是否小於new->start,如果小於,則說明當前節點tmp和new沒有資源衝突,因此執行continue語句,繼續向下掃描child連結串列。否則,如果tmp->end大於或等於new->start,則說明tmp->[start,end]和new->[start,end]之間有交集。所以返回當前節點的指標tmp,表示發生資源衝突。

  3.2.2 資源的釋放

  函式release_resource()用於實現I/O資源的釋放。該函式只有一個引數——即指標old,它指向所要釋放的資源。起原始碼如下:


int release_resource(struct resource *old)
{
        int retval;

        write_lock(&resource_lock);
        retval = __release_resource(old);
        write_unlock(&resource_lock);
        return retval;
}

  可以看出,它實際上通過呼叫__release_resource()這個內部靜態函式來完成實際的資源釋放工作。函式__release_resource()的主要任務就是將資源區域old(如果已經存在的話)從其父資源的child連結串列重摘除,它的原始碼如下:


static int __release_resource(struct resource *old)
{
        struct resource *tmp, **p;

        p = &old->parent->child;
        for (;;) {
                tmp = *p;
                if (!tmp)
                        break;
                if (tmp == old) {
                        *p = tmp->sibling;
                        old->parent = NULL;
                        return 0;
                }
                p = &tmp->sibling;
        }
        return -EINVAL;
}

  對上述函式程式碼的NOTE如下:

  同函式__request_resource()相類似,該函式也是通過一個for迴圈來遍歷父資源的child連結串列。為此,它讓tmp指標指向當前被掃描的資源,而指標p則指向當前節點的前一個節點的sibling成員(p的初始值為指向父資源的child指標)。迴圈體的步驟如下:

  ①首先,讓tmp指標指向當前被掃描的節點(tmp=*p)。

  ②如果tmp指標為空,說明已經遍歷完整個child連結串列,因此執行break語句推出for迴圈。由於在遍歷過程中沒有在child連結串列中找到引數old所指定的資源節點,因此最後返回錯誤值-EINVAL,表示引數old是一個無效的值。

  ③接下來,判斷當前被掃描節點是否就是引數old所指定的資源節點。如果是,那就將old從child連結串列中去除,也即讓當前結點tmp的前一個兄弟節點的sibling指標指向tmp的下一個節點,然後將old->parent指標設定為NULL。最後返回0值表示執行成功。

  ④如果當前被掃描節點不是資源old,那就繼續掃描child連結串列中的下一個元素。因此將指標p指向tmp->sibling成員。

  3.2.3 檢查資源是否已被佔用,

  函式check_resource()用於實現檢查某一段I/O資源是否已被佔用。其原始碼如下:


int check_resource(struct resource *root, unsigned long start, unsigned long len)
{
        struct resource *conflict, tmp;

        tmp.start = start;
        tmp.end = start + len - 1;
        write_lock(&resource_lock);
        conflict = __request_resource(root, &tmp);
        if (!conflict)
                __release_resource(&tmp);
        write_unlock(&resource_lock);
        return conflict ? -EBUSY : 0;
}

  對該函式的NOTE如下:

  ①構造一個臨時資源tmp,表示所要檢查的資源[start,start+end-1]。

  ②呼叫__request_resource()函式在根節點root申請tmp所表示的資源。如果tmp所描述的資源還被人使用,則該函式返回NULL,否則返回非空指標。因此接下來在conflict為NULL的情況下,呼叫__release_resource()將剛剛申請的資源釋放掉。

  ③最後根據conflict是否為NULL,返回-EBUSY或0值。

  3.2.4 尋找可用資源

  函式find_resource()用於在一顆資源樹中尋找未被使用的、且滿足給定條件的(也即資源長度大小為size,且在[min,max]區間內)的資源。其函式原始碼如下:


/*
* Find empty slot in the resource tree given range and alignment.
*/
static int find_resource(struct resource *root, struct resource *new,
                  unsigned long size,
                  unsigned long min, unsigned long max,
                  unsigned long align,
                  void (*alignf)(void *, struct resource *, unsigned long),
                  void *alignf_data)
{
        struct resource *this = root->child;

        new->start = root->start;
        for(;;) {
                if (this)
                        new->end = this->start;
                else
                        new->end = root->end;
                if (new->start < min)
                        new->start = min;
                if (new->end > max)
                        new->end = max;
                new->start = (new->start + align - 1) & ~(align - 1);
                if (alignf)
                        alignf(alignf_data, new, size);
                if (new->start < new->end && new->end - new->start + 1 >= size)
                  {
                        new->end = new->start + size - 1;
                        return 0;
                }
                if (!this)
                        break;
                new->start = this->end + 1;
                this = this->sibling;
        }
        return -EBUSY;
}

  對該函式的NOTE如下:

  同樣,該函式也要遍歷root的child連結串列,以尋找未被使用的資源空洞。為此,它讓this指標表示當前正被掃描的子資源節點,其初始值等於root->child,即指向child連結串列中的第一個節點,並讓new->start的初始值等於root->start,然後用一個for迴圈開始掃描child連結串列,對於每一個被掃描的節點,迴圈體執行如下操作:

  ①首先,判斷this指標是否為NULL。如果不為空,就讓new->end等於this->start,也即讓資源new表示當前資源節點this前面那一段未使用的資源區間。

  ②如果this指標為空,那就讓new->end等於root->end。這有兩層意思:第一種情況就是根結點的child指標為NULL(即根節點沒有任何子資源)。因此此時先暫時將new->end放到最大。第二種情況就是已經遍歷完整個child連結串列,所以此時就讓new表示最後一個子資源後面那一段未使用的資源區間。

  ③根據引數min和max修正new->[start,end]的值,以使資源new被包含在[min,max]區域內。

  ④接下來進行對齊操作。

  ⑤然後,判斷經過上述這些步驟所形成的資源區域new是否是一段有效的資源(end必須大於或等於start),而且資源區域的長度滿足size引數的要求(end-start+1>=size)。如果這兩個條件均滿足,則說明我們已經找到了一段滿足條件的資源空洞。因此在對new->end的值進行修正後,然後就可以返回了(返回值0表示成功)。

  ⑥如果上述兩條件不能同時滿足,則說明還沒有找到,因此要繼續掃描連結串列。在繼續掃描之前,我們還是要判斷一下this指標是否為空。如果為空,說明已經掃描完整個child連結串列,因此就可以推出for迴圈了。否則就將new->start的值修改為this->end+1,並讓this指向下一個兄弟資源節點,從而繼續掃描連結串列中的下一個子資源節點。

  3.2.5 分配介面allocate_resource()

  在find_resource()函式的基礎上,函式allocate_resource()實現:在一顆資源樹中分配一條指定大小的、且包含在指定區域[min,max]中的、未使用資源區域。其原始碼如下:


/*
* Allocate empty slot in the resource tree given range and alignment.
*/
int allocate_resource(struct resource *root, struct resource *new,
                      unsigned long size,
                      unsigned long min, unsigned long max,
                      unsigned long align,
                      void (*alignf)(void *, struct resource *, unsigned long),
                      void *alignf_data)
{
    int err;

    write_lock(&resource_lock);
    err = find_resource(root, new, size, min, max, align, alignf, alignf_data);
    if (err >= 0 && __request_resource(root, new))
        err = -EBUSY;
    write_unlock(&resource_lock);
    return err;
}

  3.2.6 獲取資源的名稱列表

  函式get_resource_list()用於獲取根節點root的子資源名字列表。該函式主要用來支援/proc/檔案系統(比如實現proc/ioports檔案和/proc/iomem檔案)。其原始碼如下:


int get_resource_list(struct resource *root, char *buf, int size)
{
        char *fmt;
        int retval;

        fmt = "        %08lx-%08lx : %s
";
        if (root->end < 0x10000)
                fmt = "        %04lx-%04lx : %s
";
        read_lock(&resource_lock);
        retval = do_resource_list(root->child, fmt, 8, buf, buf + size) - buf;
        read_unlock(&resource_lock);
        return retval;
}

  可以看出,該函式主要通過呼叫內部靜態函式do_resource_list()來實現其功能,其原始碼如下:


/*
* This generates reports for /proc/ioports and /proc/iomem
*/
static char * do_resource_list(struct resource *entry, const char *fmt,
  int offset, char *buf, char *end)
{
        if (offset < 0)
                offset = 0;

        while (entry) {
                const char *name = entry->name;
                unsigned long from, to;

                if ((int) (end-buf) < 80)
                        return buf;

                from = entry->start;
                to = entry->end;
                if (!name)
                        name = "";

                buf += sprintf(buf, fmt + offset, from, to, name);
                if (entry->child)
                   buf = do_resource_list(entry->child, fmt, offset-2, buf, end);
                entry = entry->sibling;
        }

        return buf;
}

  函式do_resource_list()主要通過一個while{}迴圈以及遞迴巢狀呼叫來實現,較為簡單,這裡就不在詳細解釋了。

3.3 管理I/O Region資源

  Linux將基於I/O對映方式的I/O埠和基於記憶體對映方式的I/O埠資源統稱為“I/O區域”(I/O Region)。I/O Region仍然是一種I/O資源,因此它仍然可以用resource結構型別來描述。下面我們就來看看Linux是如何管理I/O Region的。

  3.3.1 I/O Region的分配

  在函式__request_resource()的基礎上,Linux實現了用於分配I/O區域的函式__request_region(),如下:


struct resource * __request_region(struct resource *parent,
  unsigned long start, unsigned long n, const char *name)
{
        struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

        if (res) {
                memset(res, 0, sizeof(*res));
                res->name = name;
                res->start = start;
                res->end = start + n - 1;
                res->flags = IORESOURCE_BUSY;

                write_lock(&resource_lock);

                for (;;) {
                        struct resource *conflict;

                        conflict = __request_resource(parent, res);
                        if (!conflict)
                                break;
                        if (conflict != parent) {
                                parent = conflict;
                                if (!(conflict->flags & IORESOURCE_BUSY))
                                        continue;
                        }

                        /* Uhhuh, that didn't work out.. */
                        kfree(res);
                        res = NULL;
                        break;
                }
                write_unlock(&resource_lock);
        }
        return res;
}

NOTE:

  ①首先,呼叫kmalloc()函式在SLAB分配器快取中分配一個resource結構。

  ②然後,相應的根據引數值初始化所分配的resource結構。注意!flags成員被初始化為IORESOURCE_BUSY。

  ③接下來,用一個for迴圈開始進行資源分配,迴圈體的步驟如下:

  l 首先,呼叫__request_resource()函式進行資源分配。如果返回NULL,說明分配成功,因此就執行break語句推出for迴圈,返回所分配的resource結構的指標,函式成功地結束。

  l 如果__request_resource()函式分配不成功,則進一步判斷所返回的衝突資源節點是否就是父資源節點parent。如果不是,則將分配行為下降一個層次,即試圖在當前衝突的資源節點中進行分配(只有在衝突的資源節點沒有設定IORESOURCE_BUSY的情況下才可以),於是讓parent指標等於conflict,並在conflict->flags&IORESOURCE_BUSY為0的情況下執行continue語句繼續for迴圈。

  l 否則如果相沖突的資源節點就是父節點parent,或者相沖突資源節點設定了IORESOURCE_BUSY標誌位,則宣告分配失敗。於是呼叫kfree()函式釋放所分配的resource結構,並將res指標置為NULL,最後用break語句推出for迴圈。

  ④最後,返回所分配的resource結構的指標。

  3.3.2 I/O Region的釋放

  函式__release_region()實現在一個父資源節點parent中釋放給定範圍的I/O Region。實際上該函式的實現思想與__release_resource()相類似。其原始碼如下:


void __release_region(struct resource *parent,
    unsigned long start, unsigned long n)
{
        struct resource **p;
        unsigned long end;

        p = &parent->child;
        end = start + n - 1;

        for (;;) {
                struct resource *res = *p;

                if (!res)
                        break;
                if (res->start <= start && res->end >= end) {
                        if (!(res->flags & IORESOURCE_BUSY)) {
                                p = &res->child;
                                continue;
                        }
                        if (res->start != start'  'res->end != end)
                                break;
                        *p = res->sibling;
                        kfree(res);
                        return;
                }
                p = &res->sibling;
        }
        printk("Trying to free nonexistent resource <%08lx-%08lx>
", start, end);
}

  類似地,該函式也是通過一個for迴圈來遍歷父資源parent的child連結串列。為此,它讓指標res指向當前正被掃描的子資源節點,指標p指向前一個子資源節點的sibling成員變數,p的初始值為指向parent->child。For迴圈體的步驟如下:

  ①讓res指標指向當前被掃描的子資源節點(res=*p)。

  ②如果res指標為NULL,說明已經掃描完整個child連結串列,所以退出for迴圈。

  ③如果res指標不為NULL,則繼續看看所指定的I/O區域範圍是否完全包含在當前資源節點中,也即看看[start,start+n-1]是否包含在res->[start,end]中。如果不屬於,則讓p指向當前資源節點的sibling成員,然後繼續for迴圈。如果屬於,則執行下列步驟:

  l 先看看當前資源節點是否設定了IORESOURCE_BUSY標誌位。如果沒有設定該標誌位,則說明該資源節點下面可能還會有子節點,因此將掃描過程下降一個層次,於是修改p指標,使它指向res->child,然後執行continue語句繼續for迴圈。

  l 如果設定了IORESOURCE_BUSY標誌位。則一定要確保當前資源節點就是所指定的I/O區域,然後將當前資源節點從其父資源的child連結串列中去除。這可以通過讓前一個兄弟資源節點的sibling指標指向當前資源節點的下一個兄弟資源節點來實現(即讓*p=res->sibling),最後呼叫kfree()函式釋放當前資源節點的resource結構。然後函式就可以成功返回了。

  3.3.3 檢查指定的I/O Region是否已被佔用

  函式__check_region()檢查指定的I/O Region是否已被佔用。其原始碼如下:


int __check_region(struct resource *parent, unsigned long start, unsigned long n)
{
        struct resource * res;

        res = __request_region(parent, start, n, "check-region");
        if (!res)
                return -EBUSY;

        release_resource(res);
        kfree(res);
        return 0;
}

  該函式的實現與__check_resource()的實現思想類似。首先,它通過呼叫__request_region()函式試圖在父資源parent中分配指定的I/O Region。如果分配不成功,將返回NULL,因此此時函式返回錯誤值-EBUSY表示所指定的I/O Region已被佔用。如果res指標不為空則說明所指定的I/O Region沒有被佔用。於是呼叫__release_resource()函式將剛剛分配的資源釋放掉(實際上是將res結構從parent的child連結串列去除),然後呼叫kfree()函式釋放res結構所佔用的記憶體。最後,返回0值表示指定的I/O Region沒有被佔用。

3.4 管理I/O埠資源

  我們都知道,採用I/O對映方式的X86處理器為外設實現了一個單獨的地址空間,也即“I/O空間”(I/O Space)或稱為“I/O埠空間”,其大小是64KB(0x0000-0xffff)。Linux在其所支援的所有平臺上都實現了“I/O埠空間”這一概念。

  由於I/O空間非常小,因此即使外設匯流排有一個單獨的I/O埠空間,卻也不是所有的外設都將其I/O埠(指暫存器)對映到“I/O埠空間”中。比如,大多數PCI卡都通過記憶體對映方式來將其I/O埠或外設記憶體對映到CPU的RAM實體地址空間中。而老式的ISA卡通常將其I/O埠對映到I/O埠空間中。

  Linux是基於“I/O Region”這一概念來實現對I/O埠資源(I/O-mapped 或 Memory-mapped)的管理的。

  3.4.1 資源根節點的定義

  Linux在kernel/Resource.c檔案中定義了全域性變數ioport_resource和iomem_resource,來分別描述基於I/O對映方式的整個I/O埠空間和基於記憶體對映方式的I/O記憶體資源空間(包括I/O埠和外設記憶體)。其定義如下:


struct resource ioport_resource =
    { "PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO };
struct resource iomem_resource =
    { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };

  其中,巨集IO_SPACE_LIMIT表示整個I/O空間的大小,對於X86平臺而言,它是0xffff(定義在include/asm-i386/io.h標頭檔案中)。顯然,I/O記憶體空間的大小是4GB。

  3.4.2 對I/O埠空間的操作

  基於I/O Region的操作函式__XXX_region(),Linux在標頭檔案include/linux/ioport.h中定義了三個對I/O埠空間進行操作的巨集:①request_region()巨集,請求在I/O埠空間中分配指定範圍的I/O埠資源。②check_region()巨集,檢查I/O埠空間中的指定I/O埠資源是否已被佔用。③release_region()巨集,釋放I/O埠空間中的指定I/O埠資源。這三個巨集的定義如下:


#define request_region(start,n,name)
        __request_region(&ioport_resource, (start), (n), (name))
#define check_region(start,n)
        __check_region(&ioport_resource, (start), (n))
#define release_region(start,n)
        __release_region(&ioport_resource, (start), (n))

  其中,巨集引數start指定I/O埠資源的起始實體地址(是I/O埠空間中的實體地址),巨集引數n指定I/O埠資源的大小。

  3.4.3 對I/O記憶體資源的操作

  基於I/O Region的操作函式__XXX_region(),Linux在標頭檔案include/linux/ioport.h中定義了三個對I/O記憶體資源進行操作的巨集:①request_mem_region()巨集,請求分配指定的I/O記憶體資源。②check_ mem_region()巨集,檢查指定的I/O記憶體資源是否已被佔用。③release_ mem_region()巨集,釋放指定的I/O記憶體資源。這三個巨集的定義如下:


#define request_mem_region(start,n,name)
  __request_region(&iomem_resource, (start), (n), (name))
#define check_mem_region(start,n)
        __check_region(&iomem_resource, (start), (n))
#define release_mem_region(start,n)
        __release_region(&iomem_resource, (start), (n))

  其中,引數start是I/O記憶體資源的起始實體地址(是CPU的RAM實體地址空間中的實體地址),引數n指定I/O記憶體資源的大小。

  3.4.4 對/proc/ioports和/proc/iomem的支援

  Linux在ioport.h標頭檔案中定義了兩個巨集:

  get_ioport_list()和get_iomem_list(),分別用來實現/proc/ioports檔案和/proc/iomem檔案。其定義如下:


#define get_ioport_list(buf) get_resource_list(&ioport_resource, buf, PAGE_SIZE)
#define get_mem_list(buf)        get_resource_list(&iomem_resource, buf, PAGE_SIZE)

3.5 訪問I/O埠空間

  在驅動程式請求了I/O埠空間中的埠資源後,它就可以通過CPU的IO指定來讀寫這些I/O埠了。在讀寫I/O埠時要注意的一點就是,大多數平臺都區分8位、16位和32位的埠,也即要注意I/O埠的寬度。

  Linux在include/asm/io.h標頭檔案(對於i386平臺就是include/asm-i386/io.h)中定義了一系列讀寫不同寬度I/O埠的巨集函式。如下所示:

  ⑴讀寫8位寬的I/O埠


  unsigned char inb(unsigned port);
  void outb(unsigned char value,unsigned port);

  其中,port引數指定I/O埠空間中的埠地址。在大多數平臺上(如x86)它都是unsigned short型別的,其它的一些平臺上則是unsigned int型別的。顯然,埠地址的型別是由I/O埠空間的大小來決定的。

  ⑵讀寫16位寬的I/O埠


  unsigned short inw(unsigned port);
  void outw(unsigned short value,unsigned port);

  ⑶讀寫32位寬的I/O埠


  unsigned int inl(unsigned port);
  void outl(unsigned int value,unsigned port);

  3.5.1 對I/O埠的字串操作

  除了上述這些“單發”(single-shot)的I/O操作外,某些CPU也支援對某個I/O埠進行連續的讀寫操作,也即對單個I/O埠讀或寫一系列位元組、字或32位整數,這就是所謂的“字串I/O指令”(String Instruction)。這種指令在速度上顯然要比用迴圈來實現同樣的功能要快得多。

  Linux同樣在io.h檔案中定義了字串I/O讀寫函式:

  ⑴8位寬的字串I/O操作


  void insb(unsigned port,void * addr,unsigned long count);
  void outsb(unsigned port ,void * addr,unsigned long count);

  ⑵16位寬的字串I/O操作


  void insw(unsigned port,void * addr,unsigned long count);
  void outsw(unsigned port ,void * addr,unsigned long count);

  ⑶32位寬的字串I/O操作


  void insl(unsigned port,void * addr,unsigned long count);
  void outsl(unsigned port ,void * addr,unsigned long count);

  3.5.2 Pausing I/O


  在一些平臺上(典型地如X86),對於老式匯流排(如ISA)上的慢速外設來說,如果CPU讀寫其I/O埠的速度太快,那就可能會發生丟失資料的現象。對於這個問題的解決方法就是在兩次連續的I/O操作之間插入一段微小的時延,以便等待慢速外設。這就是所謂的“Pausing I/O”。

  對於Pausing I/O,Linux也在io.h標頭檔案中定義了它的I/O讀寫函式,而且都以XXX_p命名,比如:inb_p()、outb_p()等等。下面我們就以out_p()為例進行分析。

  將io.h中的巨集定義__OUT(b,”b”char)展開後可得如下定義:


extern inline void outb(unsigned char value, unsigned short port) {
        __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
                                : : "a" (value), "Nd" (port));
}

extern inline void outb_p(unsigned char value, unsigned short port) {
        __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
                                __FULL_SLOW_DOWN_IO
                                : : "a" (value), "Nd" (port));
}

  可以看出,outb_p()函式的實現中被插入了巨集__FULL_SLOWN_DOWN_IO,以實現微小的延時。巨集__FULL_SLOWN_DOWN_IO在標頭檔案io.h中一開始就被定義:


#ifdef SLOW_IO_BY_JUMPING
#define __SLOW_DOWN_IO "
jmp 1f
1:        jmp 1f
1:"
#else
#define __SLOW_DOWN_IO "
outb %%al,$0x80"
#endif

#ifdef REALLY_SLOW_IO
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
  __SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
#else
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
#endif

  顯然,__FULL_SLOW_DOWN_IO就是一個或四個__SLOW_DOWN_IO(根據是否定義了巨集REALLY_SLOW_IO來決定),而巨集__SLOW_DOWN_IO則被定義成毫無意義的跳轉語句或寫埠0x80的操作(根據是否定義了巨集SLOW_IO_BY_JUMPING來決定)。

3.6 訪問I/O記憶體資源

  儘管I/O埠空間曾一度在x86平臺上被廣泛使用,但是由於它非常小,因此大多數現代匯流排的裝置都以記憶體對映方式(Memory-mapped)來對映它的I/O埠(指I/O暫存器)和外設記憶體。基於記憶體對映方式的I/O埠(指I/O暫存器)和外設記憶體可以通稱為“I/O記憶體”資源(I/O Memory)。因為這兩者在硬體實現上的差異對於軟體來說是完全透明的,所以驅動程式開發人員可以將記憶體對映方式的I/O埠和外設記憶體統一看作是“I/O記憶體”資源。

  從前幾節的闡述我們知道,I/O記憶體資源是在CPU的單一記憶體實體地址空間內進行編址的,也即它和系統RAM同處在一個實體地址空間內。因此通過CPU的訪內指令就可以訪問I/O記憶體資源。

  一般來說,在系統執行時,外設的I/O記憶體資源的實體地址是已知的,這可以通過系統韌體(如BIOS)在啟動時分配得到,或者通過裝置的硬連線(hardwired)得到。比如,PCI卡的I/O記憶體資源的實體地址就是在系統啟動時由PCI BIOS分配並寫到PCI卡的配置空間中的BAR中的。而ISA卡的I/O記憶體資源的實體地址則是通過裝置硬連線對映到640KB-1MB範圍之內的。但是CPU通常並沒有為這些已知的外設I/O記憶體資源的實體地址預定義虛擬地址範圍,因為它們是在系統啟動後才已知的(某種意義上講是動態的),所以驅動程式並不能直接通過實體地址訪問I/O記憶體資源,而必須將它們對映到核心虛地址空間內(通過頁表),然後才能根據對映所得到的核心虛地址範圍,通過訪內指令訪問這些I/O記憶體資源。

  3.6.1 對映I/O記憶體資源

  Linux在io.h標頭檔案中聲明瞭函式ioremap(),用來將I/O記憶體資源的實體地址對映到核心虛地址空間(3GB-4GB)中,如下:


void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
void iounmap(void * addr);

  函式用於取消ioremap()所做的對映,引數addr是指向核心虛地址的指標。這兩個函式都是實現在mm/ioremap.c檔案中。具體實現可參考《情景分析》一書。

  3.6.2 讀寫I/O記憶體資源

  在將I/O記憶體資源的實體地址對映成核心虛地址後,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O記憶體資源了。但是,由於在某些平臺上,對I/O記憶體和系統記憶體有不同的訪問處理,因此為了確保跨平臺的相容性,Linux實現了一系列讀寫I/O記憶體資源的函式,這些函式在不同的平臺上有不同的實現。但在x86平臺上,讀寫I/O記憶體與讀寫RAM無任何差別。如下所示(include/asm-i386/io.h):


#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

#define memset_io(a,b,c)        memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c)        memcpy(__io_virt(a),(b),(c))

  上述定義中的巨集__io_virt()僅僅檢查虛地址addr是否是核心空間中的虛地址。該巨集在核心2.4.0中的實現是臨時性的。具體的實現函式在arch/i386/lib/Iodebug.c檔案。

  顯然,在x86平臺上訪問I/O記憶體資源與訪問系統主存RAM是無差別的。但是為了保證驅動程式的跨平臺的可移植性,我們應該使用上面的函式來訪問I/O記憶體資源,而不應該通過指向核心虛地址的指標來訪問。

[目錄]

from smth

[目錄]

基本結構

1.UNIX下裝置驅動程式的基本結構
    在UNIX系統裡,對使用者程式而言,裝置驅動程式隱藏了裝置的具體細節,對各種不同裝置提供了一致的介面,一般來說是把裝置對映為一個特殊的裝置檔案,使用者程式可以象對其它檔案一樣對此裝置檔案進行操作。UNIX對硬體裝置支援兩個標準介面:塊特別裝置檔案和字元特別裝置檔案,通過塊(字元)特別 裝置檔案存取的裝置稱為塊(字元)裝置或具有塊(字元)裝置介面。 塊裝置介面僅支援面向塊的I/O操作,所有I/O操作都通過在核心地址空間中的I/O緩衝區進行,它可以支援幾乎任意長度和任意位置上的I/O請求,即提供隨機存取的功能。

    字元裝置介面支援面向字元的I/O操作,它不經過系統的快速快取,所以它們負責管理自己的緩衝區結構。字元裝置介面只支援順序存取的功能,一般不能進行任意長度的I/O請求,而是限制I/O請求的長度必須是裝置要求的基本塊長的倍數。顯然,本程式所驅動的序列卡只能提供順序存取的功能,屬於是字元裝置,因此後面的討論在兩種裝置有所區別時都只涉及字元型裝置介面。裝置由一個主裝置號和一個次裝置號標識。主裝置號唯一標識了裝置型別,即裝置驅動程式型別,它是塊裝置表或字元裝置表中裝置表項的索引。次裝置號僅由裝置驅動程式解釋,一般用於識別在若干可能的硬體裝置中,I/O請求所涉及到的那個裝置。

裝置驅動程式可以分為三個主要組成部分:

    (1) 自動配置和初始化子程式,負責檢測所要驅動的硬體裝置是否存在和是否能正常工作。如果該裝置正常,則對這個裝置及其相關的、裝置驅動程式需要的軟體狀態進行初始化。這部分驅動程式僅在初始化的時候被呼叫一次。
    (2) 服務於I/O請求的子程式,又稱為驅動程式的上半部分。呼叫這部分是由於系統呼叫的結果。這部分程式在執行的時候,系統仍認為是和進行呼叫的程序屬於同一個程序,只是由使用者態變成了核心態,具有進行此係統呼叫的使用者程式的執行環境,因此可以在其中呼叫sleep()等與程序執行環境有關的函式。
    (3) 中斷服務子程式,又稱為驅動程式的下半部分。在UNIX系統中,並不是直接從中斷向量表中呼叫裝置驅動程式的中斷服務子程式,而是由UNIX系統來接收硬體中斷,再由系統呼叫中斷服務子程式。中斷可以產生在任何一個程序執行的時候,因此在中斷服務程式被呼叫的時候,不能依賴於任何程序的狀態,也就不能呼叫任何與程序執行環境有關的函式。因為裝置驅動程式一般支援同一型別的若干裝置,所以一般在系統呼叫中斷服務子程式的時候,都帶有一個或多個引數,以唯一標識請求服務的裝置。

    在系統內部,I/O裝置的存取通過一組固定的入口點來進行,這組入口點是由每個裝置的裝置驅動程式提供的。一般來說,字元型裝置驅動程式能夠提供如下幾個入口點:
(1) open入口點。開啟裝置準備I/O操作。對字元特別裝置檔案進行開啟操作,都會呼叫裝置的open入口點。open子程式必須對將要進行的I/O操作做好必要的準備工作,如清除緩衝區等。如果裝置是獨佔的,即同一時刻只能有一個程式訪問此裝置,則open子程式必須設定一些標誌以表示裝置處於忙狀態。
(2) close入口點。關閉一個裝置。當最後一次使用裝置終結後,呼叫close子程式。獨佔裝置必須標記裝置可再次使用。
(3) read入口點。從裝置上讀資料。對於有緩衝區的I/O操作,一般是從緩衝區裡讀資料。對字元特別裝置檔案進行讀操作將呼叫read子程式。
(4) write入口點。往裝置上寫資料。對於有緩衝區的I/O操作,一般是把資料寫入緩衝區裡。對字元特別裝置檔案進行寫操作將呼叫write子程式。
(5) ioctl入口點。執行讀、寫之外的操作。
(6) select入口點。檢查裝置,看資料是否可讀或裝置是否可用於寫資料。select系統呼叫在檢查與裝置特別檔案相關的檔案描述符時使用select入口點。如果裝置驅動程式沒有提供上述入口點中的某一個,系統會用預設的子程式來代替。對於不同的系統,也還有一些其它的入口點。

[目錄]

驅動程式

2.LINUX系統下的裝置驅動程式
    具體到LINUX系統裡,裝置驅動程式所提供的這組入口點由一個結構來向系統進行說明,此結構定義為:

#include <linux/fs.h>
struct file_operations {
        int (*lseek)(struct inode *inode,struct file *filp,
                off_t off,int pos);
        int (*read)(struct inode *inode,struct file *filp,
                char *buf, int count);
        int (*write)(struct inode *inode,struct file *filp,
                char *buf,int count);
        int (*readdir)(struct inode *inode,struct file *filp,
                struct dirent *dirent,int count);
        int (*select)(struct inode *inode,struct file *filp,
                int sel_type,select_table *wait);
        int (*ioctl) (struct inode *inode,struct file *filp,
                unsigned int cmd,unsigned int arg);
        int (*mmap) (void);

        int (*open) (struct inode *inode, struct file *filp);
        void (*release) (struct inode *inode, struct file *filp);
        int (*fsync) (struct inode *inode, struct file *filp);
};

其中,struct inode提供了關於特別裝置檔案/dev/driver(假設此裝置名為driver)的資訊,它的定義為:

#include <linux/fs.h>
struct inode {
        dev_t           i_dev;
        unsigned long    i_ino;  /* Inode number */
        umode_t        i_mode; /* Mode of the file */
        nlink_t          i_nlink;
        uid_t           i_uid;
        gid_t           i_gid;
        dev_t           i_rdev;  /* Device major and minor numbers*/
        off_t            i_size;
        time_t          i_atime;
        time_t          i_mtime;
        time_t          i_ctime;
        unsigned long   i_blksize;
        unsigned long   i_blocks;
        struct inode_operations * i_op;
      struct super_block * i_sb;
        struct wait_queue * i_wait;
        struct file_lock * i_flock;
        struct vm_area_struct * i_mmap;
        struct inode * i_next, * i_prev;
        struct inode * i_hash_next, * i_hash_prev;
        struct inode * i_bound_to, * i_bound_by;
        unsigned short i_count;
        unsigned short i_flags;  /* Mount flags (see fs.h) */
        unsigned char i_lock;
        unsigned char i_dirt;
        unsigned char i_pipe;
        unsigned char i_mount;
        unsigned char i_seek;
        unsigned char i_update;
        union {
                struct pipe_inode_info pipe_i;
                struct minix_inode_info minix_i;
                struct ext_inode_info ext_i;
                struct msdos_inode_info msdos_i;
                struct iso_inode_info isofs_i;
                struct nfs_inode_info nfs_i;
        } u;
};

struct file主要用於與檔案系統對應的裝置驅動程式使用。當然,其它裝置驅動程式也可以使用它。它提供關於被開啟的檔案的資訊,定義為:#include <linux/fs.h>
struct file {
        mode_t f_mode;
        dev_t f_rdev;             /* needed for /dev/tty */
        off_t f_pos;              /* Curr. posn in file */
        unsigned short f_flags;   /* The flags arg passed to open */
        unsigned short f_count;   /* Number of opens on this file */
        unsigned short f_reada;
        struct inode *f_inode;    /* pointer to the inode struct */
        struct file_operations *f_op;/* pointer to the fops struct*/
};

    在結構file_operations裡,指出了裝置驅動程式所提供的入口點位置,分別是
(1) lseek,移動檔案指標的位置,顯然只能用於可以隨機存取的裝置。
(2) read,進行讀操作,引數buf為存放讀取結果的緩衝區,count為所要讀取的資料長度。返回值為負表示讀取操作發生錯誤,否則返回實際讀取的位元組數。對於字元型,要求讀取的位元組數和返回的實際讀取位元組數都必須是inode->i_blksize的的倍數。
(3) write,進行寫操作,與read類似。
(4) readdir,取得下一個目錄入口點,只有與檔案系統相關的裝置驅動程式才使用。
(5) selec,進行選擇操作,如果驅動程式沒有提供select入口,select操作將會認為裝置已經準備好進行任何的I/O操作。
(6) ioctl,進行讀、寫以外的其它操作,引數cmd為自定義的的命令。
(7) mmap,用於把裝置的內容對映到地址空間,一般只有塊裝置驅動程式使用。
(8) open,開啟裝置準備進行I/O操作。返回0表示開啟成功,返回負數表示失敗。如果驅動程式沒有提供open入口,則只要/dev/driver檔案存在就認為開啟成功。
(9) release,即close操作。
    裝置驅動程式所提供的入口點,在裝置驅動程式初始化的時候向系統進行登記,以便系統在適當的時候呼叫。LINUX系統裡,通過呼叫register_chrdev向系統註冊字元型裝置驅動程式。register_chrdev定義為:

#include <linux/fs.h>
#include <linux/errno.h>
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

    其中,major是為裝置驅動程式向系統申請的主裝置號,如果為0則系統為此驅動程式動態地分配一個主裝置號。name是裝置名。fops就是前面所說的對各個呼叫的入口點的說明。此函式返回0表示成功。返回-EINVAL表示申請的主裝置號非法,一般來說是主裝置號大於系統所允許的最大裝置號。返回-EBUSY表示所申請的主裝置號正在被其它裝置驅動程式使用。如果是動態分配主裝置號成功,此函式將返回所分配的主裝置號。如果register_chrdev操作成功,裝置名就會出現在/proc/devices檔案裡。
    初始化部分一般還負責給裝置驅動程式申請系統資源,包括記憶體、中斷、時鐘、I/O埠等,這些資源也可以在open子程式或別的地方申請。在這些資源不用的時候,應該釋放它們,以利於資源的共享。在UNIX系統裡,對中斷的處理是屬於系統核心的部分,因此如果裝置與系統之間以中斷方式進行資料交換的話,就必須把該裝置的驅動程式作為系統核心的一部分。裝置驅動程式通過呼叫request_irq函式來申請中斷,通過free_irq來釋放中斷。它們的定義為:

#include <linux/sched.h>
int request_irq(unsigned int irq,
            void (*handler)(int irq,void dev_id,struct pt_regs *regs),
            unsigned long flags,
            const char *device,
            void *dev_id);
void free_irq(unsigned int irq, void *dev_id);

    引數irq表示所要申請的硬體中斷號。handler為向系統登記的中斷處理子程式,中斷產生時由系統來呼叫,呼叫時所帶引數irq為中斷號,dev_id為申請時告訴系統的裝置標識,regs為中斷髮生時暫存器內容。device為裝置名,將會出現在/proc/interrupts檔案裡。flag是申請時的選項,它決定中斷處理程式的一些特性,其中最重要的是中斷處理程式是快速處理程式(flag裡設定了SA_INTERRUPT)還是慢速處理程式(不設定SA_INTERRUPT),快速處理程式執行時,所有中斷都被遮蔽,而慢速處理程式執行時,除了正在處理的中斷外,其它中斷都沒有被遮蔽。

    在LINUX系統中,中斷可以被不同的中斷處理程式共享,這要求每一個共享此中斷的處理程式在申請中斷時在flags裡設定SA_SHIRQ,這些處理程式之間以dev_id來區分。如果中斷由某個處理程式獨佔,則dev_id可以為NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或handler==NULL,返回-EBUSY表示中斷已經被佔用且不能共享。作為系統核心的一部分,裝置驅動程式在申請和釋放記憶體時不是呼叫malloc和free,而代之以呼叫kmalloc和kfree,它們被定義為:

#include <linux/kernel.h>
void * kmalloc(unsigned int len, int priority);
void kfree(void * obj);

    引數len為希望申請的位元組數,obj為要釋放的記憶體指標。priority為分配記憶體操作的優先順序,即在沒有足夠空閒記憶體時如何操作,一般用GFP_KERNEL。與中斷和記憶體不同,使用一個沒有申請的I/O埠不會使CPU產生異常,也就不會導致諸如“segmentation fault"一類的錯誤發生。任何程序都可以訪問任何一個I/O埠。此時系統無法保證對I/O埠的操作不會發生衝突,甚至會因此而使系統崩潰。因此,在使用I/O埠前,也應該檢查此I/O埠是否已有別的程式在使用,若沒有,再把此埠標記為正在使用,在使用完以後釋放它。這樣需要用到如下幾個函式:

int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent, const char *name);
void release_region(unsigned int from, unsigned int extent);

    呼叫這些函式時的引數為:from表示所申請的I/O埠的起始地址;extent為所要申請的從from開始的埠數;name為裝置名,將會出現在/proc/ioports檔案裡。check_region返回0表示I/O埠空閒,否則為正在被使用。
在申請了I/O埠之後,就可以如下幾個函式來訪問I/O埠:

#include <asm/io.h>
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);

    其中inb_p和outb_p插入了一定的延時以適應某些慢的I/O埠。在裝置驅動程式裡,一般都需要用到計時機制。在LINUX系統中,時鐘是由系統接管,裝置驅動程式可以向系統申請時鐘。與時鐘有關的系統呼叫有:

#include <asm/param.h>
#include <linux/timer.h>
void add_timer(struct timer_list * timer);
int  del_timer(struct timer_list * timer);
inline void init_timer(struct timer_list * timer);

struct timer_list的定義為:

struct timer_list {
               struct timer_list *next;
               struct timer_list *prev;
               unsigned long expires;
               unsigned long data;
               void (*function)(unsigned long d);
       };

    其中expires是要執行function的時間。系統核心有一個全域性變數JIFFIES表示當前時間,一般在呼叫add_timer時jiffies=JIFFIES+num,表示在num個系統最小時間間隔後執行function。系統最小時間間隔與所用的硬體平臺有關,在核心裡定義了常數HZ表示一秒內最小時間間隔的數目,則num*HZ表示num秒。系統計時到預定時間就呼叫function,並把此子程式從定時佇列裡刪除,因此如果想要每隔一定時間間隔執行一次的話,就必須在function裡再一次呼叫add_timer。function的引數d即為timer裡面的data項。在裝置驅動程式裡,還可能會用到如下的一些系統函式:

#include <asm/system.h>
#define cli() __asm__ __volatile__ ("cli"::)
#define sti() __asm__ __volatile__ ("sti"::)

這兩個函式負責開啟和關閉中斷允許。

#include <asm/segment.h>
void memcpy_fromfs(void * to,const void * from,unsigned long n);
void memcpy_tofs(void * to,const void * from,unsigned long n);

    在使用者程式呼叫read 、write時,因為程序的執行狀態由使用者態變為核心態,地址空間也變為核心地址空間。而read、write中引數buf是指向使用者程式的私有地址空間的,所以不能直接訪問,必須通過上述兩個系統函式來訪問使用者程式的私有地址空間。memcpy_fromfs由使用者程式地址空間往核心地址空間複製,memcpy_tofs則反之。引數to為複製的目的指標,from為源指標,n為要複製的位元組數。在裝置驅動程式裡,可以呼叫printk來列印一些除錯資訊,用法與printf類似。printk列印的資訊不僅出現在螢幕上,同時還記錄在檔案syslog裡。

[目錄]

具體實現

3.LINUX系統下的具體實現
    在LINUX裡,除了直接修改系統核心的原始碼,把裝置驅動程式加進核心裡以外,還可以把裝置驅動程式作為可載入的模組,由系統管理員動態地載入它,使之成為核心地一部分。也可以由系統管理員把已載入地模組動態地解除安裝下來。

    LINUX中,模組可以用C語言編寫,用gcc編譯成目標檔案(不進行連結,作為*.o檔案存在),為此需要在gcc命令列里加上-c的引數。在編譯時,還應該在gcc的命令列里加上這樣的引數:-D__KERNEL__ -DMODULE。由於在不連結時,gcc只允許一個輸入檔案,因此一個模組的所有部分都必須在一個檔案裡實現。編譯好的模組*.o放在/lib/modules/xxxx/misc下(xxxx表示核心版本,如在核心版本為2.0.30時應該為/lib/modules/2.0.30/misc),然後用depmod -a使此模組成為可載入