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