1. 程式人生 > >Linux裝置驅動之字元裝置(一)

Linux裝置驅動之字元裝置(一)

Linux中裝置驅動的分類

這裡寫圖片描述

從上圖可以看到Linux系統將各異的裝置分為三大類:字元裝置,塊裝置和網路裝置。核心針對每一類裝置都提供了對應驅動模型架構,包括基本的核心設施和檔案系統介面。

字元裝置:在傳送過程中以字元為單位,一個位元組一個位元組的讀寫,不能隨機的讀寫資料,因為這類裝置讀寫速度比較緩慢(因而其核心設施中不提供快取機制),常見的字元裝置有鍵盤,滑鼠已印表機裝置等。

塊裝置: 是指可以從任意位置讀取資料的裝置,對這種裝置讀寫是按塊為單位讀寫的。它使用快取區來暫存資料,等待條件成熟後,會一併將資料寫入到裝置或者從裝置一次性讀出到緩衝區。比較U盤,磁碟,SD卡等。

網路裝置: 網路裝置不同於字元裝置和塊裝置,它是面向報文的。同時在/dev目錄下沒有裝置節點這樣一說,在應用層是使用者是通過API的socket函式來使用網路裝置的。比如網絡卡等。

裝置號的構成

  • 主裝置號與次裝置號
    關於裝置號,我們先通過如下的圖來了解一下
    這裡寫圖片描述
    從上圖可以看出,c代表的是字元裝置,d程式碼的是塊裝置。
    對於紅色區域來說,1是主裝置號,11是次裝置號。
    對於綠色區域來說,7是主裝置號,0-7代表是次裝置號。
    主裝置號用來標識對於的裝置驅動程式,而次裝置號則由驅動程式使用,用來標識它所管理的若干同類裝置。
  • 裝置號的表示
    在linux系統中,裝置號用dev_t表示。這是個32位的無符號整數。
<inclue/linux/types.h>
---------------------------
typedef __kernel_dev_t      dev_
t; typedef __u32 __kernel_dev_t;

在核心中,dev_t的低20位用來表示次裝置號,高12位用來表示主裝置號。隨著Linux系統的演變,上述的主次裝置號的分發可能在將來會發生變化,所以裝置驅動程式開發者應該避免直接使用主次裝置號所佔的位寬來獲得對於的主裝置號或次裝置號。為了保證以後主次裝置號所佔的位數發生變化之後,驅動程式依然可以正常工作,核心提供瞭如下了幾個巨集來操作裝置號。

<inclue/linux/kdev_t.h>
-------------------------
#define MINORBITS   20
#define MINORMASK   ((1
U << MINORBITS)
- 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

其中MAJOR巨集用來從一個dev_t型別的裝置號中提取出主裝置號,MINOR巨集從來從一個dev_t型別的裝置號中提取出此裝置號。MKDEV則是將主裝置號ma和次裝置號mi合成一個dev_t型別的裝置號。

假設在核心版本之後對主次裝置號所佔的位數發生了變化,MINORBITS修改為18位,只要驅動是使用MAJOR和MINOR巨集來操作裝置號,就不需要修改驅動程式碼也可以在新核心中使用。

裝置號的分配

在核心原始碼中,裝置號的分配主要有兩個函式:

  • 靜態分配裝置號
<fs/char_dev.c>
----------------------------------------------------------
/**
 * register_chrdev_region() - register a range of device numbers
 * @from: the first in the desired range of device numbers; must include
 *        the major number.
 * @count: the number of consecutive device numbers required
 * @name: the name of the device or driver.
 *
 * Return value is zero on success, a negative error code on failure.
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
    struct char_device_struct *cd;
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        if (next > to)
            next = to;
        cd = __register_chrdev_region(MAJOR(n), MINOR(n),
                   next - n, name);
        if (IS_ERR(cd))
            goto fail;
    }
    return 0;
fail:
    to = n;
    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
    return PTR_ERR(cd);
}

該函式是用來在知道主裝置號的前提下使用,第一個引數form表示一個裝置號,第二個引數count表示次裝置的個數,也就是當前驅動程式所管理的同類裝置的個數,第三個引數name表示裝置或者驅動的名稱。成功返回0,失敗返回負數。

比如核心程式碼中使用register_chrdev_region申請裝置號示例:

    #define INPUT_MAJOR     13
    #define INPUT_MAX_CHAR_DEVICES      1024
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                     INPUT_MAX_CHAR_DEVICES, "input");
    if (err) {
        pr_err("unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

以上程式碼申請了主裝置號為13,總共存在1024個次裝置,裝置的名字為input。

  • 動態分配裝置號
<fs/char_dev.c>
--------------------------------------------------------------------------------------
/**
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 *
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
    *dev = MKDEV(cd->major, cd->baseminor);
    return 0;
}

該函式有系統動態的分配裝置號,是在主裝置號不知情的情況下,讓系統給分配裝置號。第一個引數dev表示是輸出引數,也就是裝置號,第二個引數baseminor表示第一個次裝置號編號,第三個引數count表示次裝置號的個數,第四個引數name也就是裝置或者驅動的名稱。

核心中使用alloc_chrdev_region的示例:

#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
void __init rtc_dev_init(void)
{
    int err;

    err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
    if (err < 0)
        pr_err("failed to allocate char dev region\n");
}

上述示例程式碼是通過alloc_chrdev_region函式申請裝置號,裝置的個數為16個,裝置的名稱為rtc裝置。

裝置號釋放

在驅動程式不使用的時候需要釋放裝置號,因為裝置號也是系統的資源,不用的使用需要及時釋放資源。已供其他裝置使用。

<fs/char_dev.c>
-----------------------------------------------------------------------------------
/**
 * unregister_chrdev_region() - return a range of device numbers
 * @from: the first in the range of numbers to unregister
 * @count: the number of device numbers to unregister
 *
 * This function will unregister a range of @count device numbers,
 * starting with @from.  The caller should normally be the one who
 * allocated those numbers in the first place...
 */
void unregister_chrdev_region(dev_t from, unsigned count)
{
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        if (next > to)
            next = to;
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
}

使用該函式可以使用申請的裝置號,第一個引數from表示要釋放的裝置號,第二個引數count表示要釋放的個數。

核心使用unregister_chrdev_region的示例:

#define INPUT_MAJOR     13
#define INPUT_MAX_CHAR_DEVICES      1024
static void __exit input_exit(void)
{
    unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                 INPUT_MAX_CHAR_DEVICES);
}

從上面程式碼可知,如果呼叫unregister_chrdev_region就會從系統中釋放主裝置號13的裝置。