作者:李強, 華清遠見嵌入式 學院 講師。
在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);
}
次函數比較簡單,再次就不多說了,請參考前面的實現。