linux設備號之操作

作者:李強, 華清遠見嵌入式 學院 講師。

在Linux設備驅動中,設備號設一個很重要的概念和變量。不論是主設備號,還是次設備號,在設備驅動中都佔據了很重要 的地位。那麼他在Kernel中是如何操作的?這個數據結構都是通過那些函數可以很容易的在我們寫Linux設備驅動模塊時被我們所使用呢?

在include/linux/type.h文件中我們能看到一個關於dev_t的定義如下:
        ...
        typedef __u32 __kernel_dev_t;

typedef __kernel_fd_set fd_set;
        typedef __kernel_dev_t dev_t;
        ...

從這個定義中我們能看到dev_t是一個無符號的32位的整型。

首先我們需要說明的是,在linux中主次設備號是放置在一個無符號的32位的整型中,那麼這32位整型對於主次設備號 如何分配呢?

從源代碼中我們可以看到,主設備號佔據12個位,次設備好佔據20位。這在一定的時期內,主次設備號是完全可以滿足系統 需要的。

同時在include/linux/kdev_t.h文件中我們能發現很多函數或者宏定義的操作都是針對dev_t的。

具體可以看到我們經常用到的MAJOR(dev)、MINOR(dev)、MKDEV(ma,mi)。

下面我們就具體分析下這三個我們經常用到的宏定義:

#define MINORBITS 20
        #define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

從這個宏定義中我們可以看到其把無符號的32位的整型做位操作運算:右移20位。

在C語言中如果是右移,那麼左邊補0,這樣在這32位的整型中通過這個操作就只保留了原先第19位到31位的有效值,而 這也正是我們所需要的。

下面我們看下MINOR這個宏定義:

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

要明白這個宏定義的具體是多少,我們需要首先明白宏定義MINORMASK是什麼?

我們從前面的宏定義中,我們看到:

#define MINORMASK ((1U << MINORBITS) - 1)

MINORMASK 是1U也就是1左移位20個字節,二進制的話就是10000000000000000000,也就是1後面帶20個0。

然後在減1呢,就成了二進制11111111111111111111,也就是20個1,十六進制的話是 0xFFFFF。

好現在我們知道MINORMASK是20個1,也就是十六進制0xFFFFF,那麼我們在與dev_t做一個位的與運 算,就把32位中的前12爲置0,保留其後面的20位,也正是我們想要的表是設備次設備號的後20個字節。

好下面我們看下如果我們知道了主設備號、次設備號,我們如何生成一個dev_t的數據結構。

宏定義:
        #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

明白了前面我們所說的,其實這個就比較簡單了,把主設備號左移20位,然後與上次設備號,就是我們所需要的dev_t的 數據結構。

那麼我們前面所說的關於dev_t的操作是新的2.6.x系列中的,在之前的2.4.x系列中,由於對設備號的總共就 16個字節,也就是一個短整型,那麼一個系統中所能擁有的設備號就是及其有限的了。

我們看下在老版本中的內核中他們的表示:

#define MAJOR(dev) ((dev)>>8)
        #define MINOR(dev) ((dev) & 0xff)
        #define MKDEV(ma,mi) ((ma)<<8 | (mi))

從中我們可以看出,他是以8爲爲分界線,高8位爲主設備號,低8位爲次設備號,那麼一個8位所能表示的最多也即是255 個數值,那麼當我們系統中如果擁有的設備大於這個數值的時候,在老版本的內核中就沒有辦法處理了。

在內核實現中還實現了兩個打印的函數,其實也是宏定義:
        #define print_dev_t(buffer, dev)                        /
        sprintf((buffer), "%u:%u/n", MAJOR(dev), MINOR(dev))

#define format_dev_t(buffer, dev)                        /
        ({                                                                                /
                sprintf(buffer, "%u:%u", MAJOR(dev), MINOR(dev));        /
                buffer;                                        /
        })

從代碼中我們可以看出。

第一就是把設備的主設備號和次設備號以字符串的形式存放到buffer中,在使用這個宏定義的時候需要注意的是:

buffer需要提前開闢空間,而且還需要是夠用的空間。

第二所實現的功能和第一個很類似。這兒我們就不具體說明,請參考第一個宏定義的實現。

在這個文件中還有很多的函數,這些函數的主要功能就是和老版本的內核代碼兼容而產生的,比如:

static inline int old_valid_dev(dev_t dev)
        {
                return MAJOR(dev) < 256 && MINOR(dev) < 256;
        }

此函數是判斷一個dev_t是否可以轉換成舊制的dev_t。

static inline u16 old_encode_dev(dev_t dev)
        {
                return (MAJOR(dev) << 8) | MINOR(dev);
        }

把32位的設備號轉換成16位的舊制的設備號。

其中主要操作爲:首先把主設備號左移8位,爲次設備好空出8位的位置,然後與上次設備號。

在使用這個函數的時候需要注意的就是需要首先判斷下32位的設備號是否可以有效的轉換成16位的設備號。

static inline dev_t old_decode_dev(u16 val)
        {
                return MKDEV((val >> 8) & 255, val & 255);
        }

上面函數的反操作。

主設備號右移8位,然後與上255,即8個1。也就是取此變量的低8位,

次設備號與上255,也是取此變量的低8位即可。

static inline u32 new_encode_dev(dev_t dev)
        {
                unsigned major = MAJOR(dev);
                unsigned minor = MINOR(dev);
                return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12);
                              ^^^^^^^^^^^^^
                        次 設備號取其低8位 ^^^^^^^^^^^^^^
                                                        主 設備號左移8位。
                                                                                        ^^^^^^^^^^^^^^^^^^^^^^^
                                                                                        次 設備號低8位清零,左移12位。
        }

static inline dev_t new_decode_dev(u32 dev)
        {
                unsigned major = (dev & 0xfff00) >> 8;
                unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
                return MKDEV(major, minor);
        }

次函數比較簡單,再次就不多說了,請參考前面的實現。

系統分類: 嵌入式    |   用戶分類: Linux    |   來 源: 無分類   |   【推 薦給朋友】    |   【添 加到收藏夾】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章