|
一、linux驅(qū)動(dòng)程序的數(shù)據(jù)結(jié)構(gòu)
設(shè)備驅(qū)動(dòng)程序?qū)嵸|(zhì)上是提供一組供應(yīng)用程序操作設(shè)備的接口函數(shù)。
各種設(shè)備由于功能不同,驅(qū)動(dòng)程序提供的函數(shù)接口也不相同,但linux為了能夠統(tǒng)一管理,規(guī)定了linux下設(shè)備驅(qū)動(dòng)程序必須使用統(tǒng)一的接口函數(shù) file_operations 。
所以,一種設(shè)備的驅(qū)動(dòng)程序主要內(nèi)容就是提供這樣的一組file_operations接口函數(shù)。
那么,linux是如何管理種類繁多的設(shè)備驅(qū)動(dòng)程序呢?
linux下設(shè)備大體分為塊設(shè)備和字符設(shè)備兩類。
內(nèi)核中用2個(gè)全局?jǐn)?shù)組存放這2類驅(qū)動(dòng)程序。
#define MAX_CHRDEV 255
#define MAX_BLKDEV 255
struct device_struct {
const char * name;
struct file_operations * fops;
};
static struct device_struct chrdevs[MAX_CHRDEV];
static struct {
const char *name;
struct block_device_operations *bdops;
} blkdevs[MAX_BLKDEV];
//此處說(shuō)明一下,struct block_device_operations是塊設(shè)備驅(qū)動(dòng)程序內(nèi)部的接口函數(shù),上層文件系統(tǒng)還是通過(guò)struct file_operations訪問的。
哈哈,現(xiàn)在明白了吧?你的驅(qū)動(dòng)程序調(diào)用 int register_chrdev(unsigned int major, const char * name, struct file_operations *fops) 就是將你提供的接口函數(shù)fops存放到chrdevs[MAX_CHRDEV]這個(gè)數(shù)組中,數(shù)組下標(biāo)就是你的驅(qū)動(dòng)的主設(shè)備號(hào),數(shù)組內(nèi)容包括驅(qū)動(dòng)名稱和驅(qū)動(dòng)接口函數(shù),這樣,內(nèi)核就能看到你的驅(qū)動(dòng)程序了。BTW,如果你將major設(shè)為0,系統(tǒng)會(huì)自動(dòng)給你分配一個(gè)空閑的主設(shè)備號(hào)。
那么?次設(shè)備號(hào)呢?別急,馬上就出現(xiàn)了:)
二、設(shè)備節(jié)點(diǎn)如何產(chǎn)生?
驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,應(yīng)用程序訪問驅(qū)動(dòng)程序通常是通過(guò)系統(tǒng)調(diào)用文件系統(tǒng)接口函數(shù)的,也就是說(shuō),在linux下,和磁盤文件一樣,設(shè)備也是文件,只是他們的文件屬性不同而已,應(yīng)用程序只能通過(guò)文件名來(lái)訪問設(shè)備的驅(qū)動(dòng)程序。
所以,文件系統(tǒng)中必須要有一個(gè)代表你的設(shè)備的文件,應(yīng)用程序才能訪問你的設(shè)備驅(qū)動(dòng)程序。
為了便于理解,我們可以將設(shè)備文件換個(gè)名字,叫做設(shè)備節(jié)點(diǎn)。
設(shè)備節(jié)點(diǎn)在哪里?設(shè)備節(jié)點(diǎn)存在于你的文件系統(tǒng)中,通常在/dev目錄下,當(dāng)然,你也可以在其它地方創(chuàng)建。一般說(shuō)來(lái),我們?cè)谥谱魑募到y(tǒng)映像時(shí)就已經(jīng)將可能用到的設(shè)備節(jié)點(diǎn)都創(chuàng)建好了。
你可以打開/dev目錄看一下,它下面的設(shè)備節(jié)點(diǎn)的數(shù)量會(huì)讓你吃驚的:)
如何創(chuàng)建設(shè)備節(jié)點(diǎn)?。
你可以用mknod命令。如使用以下命令可以創(chuàng)建一個(gè)mtd4的字符設(shè)備節(jié)點(diǎn)。
Mknod /dev/ mtd4 c MTD_CHAR_MAJOR 4
我們創(chuàng)建一個(gè)普通的磁盤文件,它的內(nèi)容是我們寫入的數(shù)據(jù)。
那么設(shè)備節(jié)點(diǎn)的內(nèi)容是什么?設(shè)備節(jié)點(diǎn)文件沒有數(shù)據(jù),它的文件大小為0,它只有文件屬性,包括設(shè)備類型、主設(shè)備號(hào)、次設(shè)備號(hào)。
沒有別的了?對(duì),就這些,沒別的了。
那設(shè)備節(jié)點(diǎn)和設(shè)備驅(qū)動(dòng)程序是怎么聯(lián)系起來(lái)的啊?
別著急,休息,休息一會(huì)兒:)順便加下我的QQ:2232894713
三、應(yīng)用程序是如何訪問設(shè)備驅(qū)動(dòng)程序的?
舉個(gè)例子:我們要向nor flash的第四個(gè)分區(qū)的起始位置偏移512字節(jié)寫入100字節(jié)的數(shù)據(jù)。
我們是如何做的?主要程序片斷如下:
fd = open(“/dev/mtd4”, O_RDWR);
lseek (fd,512, SEEK_SET);
write (fd , write_buffer, 100);
close(fd);
上面的代碼比較簡(jiǎn)單,但是似乎沒有看到我們的應(yīng)用程序是如何調(diào)用到驅(qū)動(dòng)程序的。
沒關(guān)系,接下來(lái)我將帶領(lǐng)你們走通這條道路。
應(yīng)用程序調(diào)用Open函數(shù),這是個(gè)系統(tǒng)調(diào)用函數(shù),程序會(huì)進(jìn)入內(nèi)核空間調(diào)用sys_open函數(shù)。
在sys_open,首先會(huì)根據(jù)文件路徑“/dev/mtd4”找到這個(gè)文件節(jié)點(diǎn),這部分工作是屬于VFS(虛擬文件系統(tǒng))的。
“/dev/mtd4”的文件屬性是字符設(shè)備,于是sys_open會(huì)調(diào)用函數(shù)chrdev_open(),在這個(gè)函數(shù)里有一句話:
filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
哈!看到了眉目吧!猜也能猜到啊,get_chrfops()里面一定會(huì)返回 chrdevs[major].fops的。
我們終于從文件系統(tǒng)走到驅(qū)動(dòng)程序了,那么,接下來(lái)的事情就是可以理解的了。
Write()最終一定會(huì)調(diào)用到chrdevs[major].fops->write();
Read()最終一定會(huì)調(diào)用到chrdevs[major].fops->read();
各種驅(qū)動(dòng)程序比較特殊的功能函數(shù)都可以通過(guò)ioctl()來(lái)得到調(diào)用。
而次設(shè)備號(hào)也會(huì)作為參數(shù)傳遞給你。
四、為什么要有設(shè)備文件系統(tǒng)?
從前面的章節(jié),我們可以看到以主次設(shè)備號(hào)的形式管理設(shè)備驅(qū)動(dòng)程序存在很大的缺點(diǎn)。
首先,設(shè)備節(jié)點(diǎn)的創(chuàng)建是獨(dú)立于內(nèi)核的,是在建立文件系統(tǒng)時(shí)就把所有要用到的設(shè)備節(jié)點(diǎn)都創(chuàng)建好了的,通常我們不會(huì)去刻意刪除哪些節(jié)點(diǎn),因?yàn)槲覀儾恢老到y(tǒng)將來(lái)會(huì)不會(huì)用到它們。由于每個(gè)設(shè)備節(jié)點(diǎn)代表唯一的主次設(shè)備號(hào),所以每個(gè)可能存在的子設(shè)備都對(duì)應(yīng)一個(gè)設(shè)備節(jié)點(diǎn),可見,這樣的設(shè)備節(jié)點(diǎn)數(shù)量是很大的,這些數(shù)量龐大的設(shè)備節(jié)點(diǎn)都(文件)存在于存儲(chǔ)介質(zhì)中,對(duì)文件系統(tǒng)的效率也是個(gè)影響。
其次,文件系統(tǒng)中存在哪些設(shè)備節(jié)點(diǎn),并不代表內(nèi)核中就有這種設(shè)備的驅(qū)動(dòng)程序,也不代表系統(tǒng)中有這種設(shè)備,因?yàn)樵O(shè)備節(jié)點(diǎn)不是動(dòng)態(tài)創(chuàng)建的,它是制作文件系統(tǒng)時(shí)建立的。因此,/dev目錄下的信息大多對(duì)我們是無(wú)用的,而且那么多的設(shè)備節(jié)點(diǎn)都平鋪在/dev目錄下,閱讀起來(lái)也不直觀。
最后,目前主次設(shè)備號(hào)都是用8位整數(shù)表示的,也就是說(shuō)內(nèi)核最多管理256種字符設(shè)備和256種塊設(shè)備。現(xiàn)在計(jì)算機(jī)外設(shè)種類越來(lái)越多,這樣的限制已經(jīng)不夠了。
由于目前的主次設(shè)備號(hào)的管理形式有以上幾個(gè)缺點(diǎn),linux內(nèi)核小組在2.4版本以后加入了設(shè)備文件系統(tǒng)來(lái)改進(jìn)這些缺點(diǎn)。
設(shè)備文件系統(tǒng)的思想就是想讓設(shè)備節(jié)點(diǎn)可以動(dòng)態(tài)創(chuàng)建、刪除,這樣系統(tǒng)中有哪些設(shè)備驅(qū)動(dòng)程序就可以一目了然;還要能夠把設(shè)備節(jié)點(diǎn)組織成一棵目錄樹,方便閱讀;最后,希望能夠擴(kuò)大主次設(shè)備號(hào)的限制,不再限制在256種設(shè)備以內(nèi)。
五、設(shè)備文件系統(tǒng)如何實(shí)現(xiàn)?
要想在內(nèi)核中方便的做到動(dòng)態(tài)創(chuàng)建、刪除設(shè)備文件(在這里,我們把設(shè)備節(jié)點(diǎn)稱為設(shè)備文件會(huì)更恰當(dāng)些),最自然的做法就是在RAM中創(chuàng)建一個(gè)文件系統(tǒng),內(nèi)核啟動(dòng)時(shí)這個(gè)文件系統(tǒng)是空的,以后每加載一種設(shè)備驅(qū)動(dòng)程序,就在這個(gè)文件系統(tǒng)中創(chuàng)建一個(gè)對(duì)應(yīng)的設(shè)備文件;卸載設(shè)備驅(qū)動(dòng)程序時(shí),再刪除這個(gè)設(shè)備文件。而且,我們可以在這個(gè)文件系統(tǒng)中創(chuàng)建目錄,一類設(shè)備文件放在同一個(gè)目錄中,甚至把一種設(shè)備的多個(gè)子設(shè)備文件放在同一個(gè)目錄下,方便閱讀。
在設(shè)備文件系統(tǒng)中,我們?cè)谧?cè)設(shè)備文件時(shí)可以把設(shè)備驅(qū)動(dòng)程序的ops直接掛到設(shè)備文件的inode中,以后訪問驅(qū)動(dòng)程序就可以擺脫主次設(shè)備號(hào)的限制了,不需要再訪問chrdev[]數(shù)組,這樣就突破了256種設(shè)備的限制。
我們把設(shè)備文件系統(tǒng)mount到/dev目錄下,這樣,看起來(lái)跟以前的方案就很相似了,也方便老的應(yīng)用程序的移植。
六、如何使用設(shè)備文件系統(tǒng)?
以前我們寫驅(qū)動(dòng)程序時(shí)要調(diào)用int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)將你提供的接口函數(shù)fops存放到chrdevs[MAX_CHRDEV]這個(gè)數(shù)組中,然后在文件系統(tǒng)中用mknod創(chuàng)建有相同主設(shè)備號(hào)的設(shè)備節(jié)點(diǎn)就可以了。
那么現(xiàn)在有了設(shè)備文件系統(tǒng),我們的驅(qū)動(dòng)程序該如何寫呢?
很簡(jiǎn)單,follow me!
1、調(diào)用devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info)創(chuàng)建設(shè)備文件所在的目錄。Dir是要?jiǎng)?chuàng)建目錄的父目錄句柄,如為NULL,就是設(shè)備文件系統(tǒng)的根目錄(/dev);最后一個(gè)參數(shù)info通常為NULL。
2、調(diào)用devfs_handle_t devfs_register (devfs_handle_t dir, const char *name,
unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode, void *ops, void *info)
注冊(cè)具體的設(shè)備,并在指定目錄下創(chuàng)建子設(shè)備節(jié)點(diǎn)。
這里的dir目錄名是要?jiǎng)?chuàng)建的設(shè)備文件所在的目錄名,目錄名一般不能為NULL,因?yàn)樽釉O(shè)備文件名name通常是以0、1、2、3等數(shù)字命名的,會(huì)和其它設(shè)備文件沖突。
3、卸載驅(qū)動(dòng)程序時(shí)調(diào)用void devfs_unregister (devfs_handle_t de)刪除創(chuàng)建的目錄和子設(shè)備文件。
七、具體設(shè)備驅(qū)動(dòng)程序分析
這節(jié)以mtdchar設(shè)備驅(qū)動(dòng)程序來(lái)具體分析驅(qū)動(dòng)程序的寫法。
Mtdchar字符設(shè)備是管理flash驅(qū)動(dòng)程序的,是各種flash驅(qū)動(dòng)程序的抽象層。
Mtdchar的主程序是driver/mtd/mtdchar.c;
1、驅(qū)動(dòng)程序初始化時(shí),要注冊(cè)設(shè)備節(jié)點(diǎn),創(chuàng)建子設(shè)備文件
驅(qū)動(dòng)程序?yàn)榱思嫒菀郧暗姆桨福ǔ?huì)既注冊(cè)設(shè)備節(jié)點(diǎn),又創(chuàng)建子設(shè)備文件,這樣不管內(nèi)核支持不支持設(shè)備文件系統(tǒng),驅(qū)動(dòng)程序都可以工作。
static int __init init_mtdchar(void)
{
//為了兼容以前的方案,要注冊(cè)mtdchar的主設(shè)備號(hào)、設(shè)備名以及fops
if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops))
{
printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
MTD_CHAR_MAJOR);
return -EAGAIN;
}
//如果內(nèi)核支持設(shè)備文件系統(tǒng),在這個(gè)函數(shù)里會(huì)創(chuàng)建子設(shè)備文件。
mtdchar_devfs_init();
return 0;
}
static inline void mtdchar_devfs_init(void)
{
//創(chuàng)建設(shè)備節(jié)點(diǎn)的父目錄,/dev/mtd/
devfs_dir_handle = devfs_mk_dir(NULL, "mtd", NULL);
//在這個(gè)函數(shù)里會(huì)調(diào)用devfs_register()創(chuàng)建子設(shè)備
register_mtd_user(¬ifier);
}
notifier定義如下,主要是提供創(chuàng)建和刪除子設(shè)備文件的接口函數(shù)
static struct mtd_notifier notifier = {
.add = mtd_notify_add, //創(chuàng)建一個(gè)子設(shè)備
.remove = mtd_notify_remove, //刪除一個(gè)子設(shè)備
};
static void mtd_notify_add(struct mtd_info* mtd)
{
char name[8];
if (!mtd)
return;
// mtd是一個(gè)子設(shè)備,代表flash上的一個(gè)邏輯分區(qū)
sprintf(name, "%d", mtd->index);
//這里調(diào)用devfs_register創(chuàng)建子設(shè)備文件,如/dev/mtd/0
devfs_rw_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2,
S_IFCHR | S_IRUGO | S_IWUGO,
&mtd_fops, NULL);
//下面注冊(cè)的是只讀子設(shè)備,無(wú)關(guān)緊要。
sprintf(name, "%dro", mtd->index);
//創(chuàng)建只讀子設(shè)備,如 /dev/mtd/0ro
devfs_ro_handle[mtd->index] = devfs_register(devfs_dir_handle, name,
DEVFS_FL_DEFAULT, MTD_CHAR_MAJOR, mtd->index*2+1,
S_IFCHR | S_IRUGO,
&mtd_fops, NULL);
}
static void mtd_notify_remove(struct mtd_info* mtd)
{
if (!mtd)
return;
//刪除mtdchar子設(shè)備文件和mtdchar子設(shè)備文件
devfs_unregister(devfs_rw_handle[mtd->index]);
devfs_unregister(devfs_ro_handle[mtd->index]);
}
mtd驅(qū)動(dòng)程序中會(huì)將一片flash劃分為多個(gè)邏輯分區(qū),這樣的每個(gè)邏輯分區(qū)也可以被看做是一個(gè)子設(shè)備,具體flash驅(qū)動(dòng)程序添加邏輯分區(qū)時(shí)會(huì)在數(shù)組mtd_table[]中記錄分區(qū)的位置和大小。
void register_mtd_user (struct mtd_notifier *new)
{
int i;
down(&mtd_table_mutex);
list_add(&new->list, &mtd_notifiers);
__module_get(THIS_MODULE);
// mtd_table[]是個(gè)全局?jǐn)?shù)組,每個(gè)元素都是一個(gè)邏輯分區(qū),記錄著分區(qū)的起始位置和大小,我們將每個(gè)邏輯分區(qū)創(chuàng)建為一個(gè)單獨(dú)的mtd子設(shè)備文件
for (i=0; i< MAX_MTD_DEVICES; i++)
if (mtd_table)
new->add(mtd_table);
up(&mtd_table_mutex);
}
2、驅(qū)動(dòng)程序卸載時(shí)要注銷設(shè)備節(jié)點(diǎn),刪除設(shè)備文件
static void __exit cleanup_mtdchar(void)
{
mtdchar_devfs_exit();
//注銷chrdevs[major]
unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
}
static inline void mtdchar_devfs_exit(void)
{
//在這個(gè)函數(shù)里會(huì)調(diào)用devfs_unregister()刪除子設(shè)備
unregister_mtd_user(¬ifier);
//刪除父目錄
devfs_unregister(devfs_dir_handle);
}
int unregister_mtd_user (struct mtd_notifier *old)
{
int i;
down(&mtd_table_mutex);
module_put(THIS_MODULE);
for (i=0; i< MAX_MTD_DEVICES; i++)
if (mtd_table)
old->remove(mtd_table);
list_del(&old->list);
up(&mtd_table_mutex);
return 0;
}
|
|