作者:李強,華清遠(yuǎn)見嵌入式學(xué)院講師。
在Linux設(shè)備驅(qū)動中,設(shè)備號設(shè)一個很重要的概念和變量。不論是主設(shè)備號,還是次設(shè)備號,在設(shè)備驅(qū)動中都占據(jù)了很重要的地位。那么他在Kernel中是如何操作的?這個數(shù)據(jù)結(jié)構(gòu)都是通過那些函數(shù)可以很容易的在我們寫Linux設(shè)備驅(qū)動模塊時被我們所使用呢?
在include/linux/type.h文件中我們能看到一個關(guān)于dev_t的定義如下:
...
typedef __u32 __kernel_dev_t;
typedef __kernel_fd_set fd_set;
typedef __kernel_dev_t dev_t;
...
從這個定義中我們能看到dev_t是一個無符號的32位的整型。
首先我們需要說明的是,在linux中主次設(shè)備號是放置在一個無符號的32位的整型中,那么這32位整型對于主次設(shè)備號如何分配呢?
從源代碼中我們可以看到,主設(shè)備號占據(jù)12個位,次設(shè)備好占據(jù)20位。這在一定的時期內(nèi),主次設(shè)備號是完全可以滿足系統(tǒng)需要的。
同時在include/linux/kdev_t.h文件中我們能發(fā)現(xiàn)很多函數(shù)或者宏定義的操作都是針對dev_t的。
具體可以看到我們經(jīng)常用到的MAJOR(dev)、MINOR(dev)、MKDEV(ma,mi)。
下面我們就具體分析下這三個我們經(jīng)常用到的宏定義:
#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個字節(jié),二進(jìn)制的話就是10000000000000000000,也就是1后面帶20個0。
然后在減1呢,就成了二進(jìn)制11111111111111111111,也就是20個1,十六進(jìn)制的話是0xFFFFF。
好現(xiàn)在我們知道MINORMASK是20個1,也就是十六進(jìn)制0xFFFFF,那么我們在與dev_t做一個位的與運算,就把32位中的前12為置0,保留其后面的20位,也正是我們想要的表是設(shè)備次設(shè)備號的后20個字節(jié)。
好下面我們看下如果我們知道了主設(shè)備號、次設(shè)備號,我們?nèi)绾紊梢粋€dev_t的數(shù)據(jù)結(jié)構(gòu)。
宏定義:
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
明白了前面我們所說的,其實這個就比較簡單了,把主設(shè)備號左移20位,然后與上次設(shè)備號,就是我們所需要的dev_t的數(shù)據(jù)結(jié)構(gòu)。
那么我們前面所說的關(guān)于dev_t的操作是新的2.6.x系列中的,在之前的2.4.x系列中,由于對設(shè)備號的總共就16個字節(jié),也就是一個短整型,那么一個系統(tǒng)中所能擁有的設(shè)備號就是及其有限的了。
我們看下在老版本中的內(nèi)核中他們的表示:
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
從中我們可以看出,他是以8為為分界線,高8位為主設(shè)備號,低8位為次設(shè)備號,那么一個8位所能表示的最多也即是255個數(shù)值,那么當(dāng)我們系統(tǒng)中如果擁有的設(shè)備大于這個數(shù)值的時候,在老版本的內(nèi)核中就沒有辦法處理了。
在內(nèi)核實現(xiàn)中還實現(xiàn)了兩個打印的函數(shù),其實也是宏定義:
#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; \
})
從代碼中我們可以看出。
第一就是把設(shè)備的主設(shè)備號和次設(shè)備號以字符串的形式存放到buffer中,在使用這個宏定義的時候需要注意的是:
buffer需要提前開辟空間,而且還需要是夠用的空間。
第二所實現(xiàn)的功能和第一個很類似。這兒我們就不具體說明,請參考第一個宏定義的實現(xiàn)。
在這個文件中還有很多的函數(shù),這些函數(shù)的主要功能就是和老版本的內(nèi)核代碼兼容而產(chǎn)生的,比如:
static inline int old_valid_dev(dev_t dev)
{
return MAJOR(dev) < 256 && MINOR(dev) < 256;
}
此函數(shù)是判斷一個dev_t是否可以轉(zhuǎn)換成舊制的dev_t。
static inline u16 old_encode_dev(dev_t dev)
{
return (MAJOR(dev) << 8) | MINOR(dev);
}
把32位的設(shè)備號轉(zhuǎn)換成16位的舊制的設(shè)備號。
其中主要操作為:首先把主設(shè)備號左移8位,為次設(shè)備好空出8位的位置,然后與上次設(shè)備號。
在使用這個函數(shù)的時候需要注意的就是需要首先判斷下32位的設(shè)備號是否可以有效的轉(zhuǎn)換成16位的設(shè)備號。
static inline dev_t old_decode_dev(u16 val)
{
return MKDEV((val >> 8) & 255, val & 255);
}
上面函數(shù)的反操作。
主設(shè)備號右移8位,然后與上255,即8個1。也就是取此變量的低8位,
次設(shè)備號與上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);
^^^^^^^^^^^^^
次設(shè)備號取其低8位 ^^^^^^^^^^^^^^
主設(shè)備號左移8位。
^^^^^^^^^^^^^^^^^^^^^^^
次設(shè)備號低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);
}
次函數(shù)比較簡單,再次就不多說了,請參考前面的實現(xiàn)。
“本文由華清遠(yuǎn)見http://www.embedu.org/index.htm提供”
華清遠(yuǎn)見