找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2691|回復: 0
收起左側(cè)

linux驅(qū)動程序的數(shù)據(jù)結(jié)構(gòu)

[復制鏈接]
ID:195628 發(fā)表于 2017-5-4 11:08 | 顯示全部樓層 |閱讀模式
一、linux驅(qū)動程序的數(shù)據(jù)結(jié)構(gòu)

設(shè)備驅(qū)動程序?qū)嵸|(zhì)上是提供一組供應(yīng)用程序操作設(shè)備的接口函數(shù)。
各種設(shè)備由于功能不同,驅(qū)動程序提供的函數(shù)接口也不相同,但linux為了能夠統(tǒng)一管理,規(guī)定了linux下設(shè)備驅(qū)動程序必須使用統(tǒng)一的接口函數(shù) file_operations 。
所以,一種設(shè)備的驅(qū)動程序主要內(nèi)容就是提供這樣的一組file_operations接口函數(shù)。
那么,linux是如何管理種類繁多的設(shè)備驅(qū)動程序呢?

linux下設(shè)備大體分為塊設(shè)備和字符設(shè)備兩類。
內(nèi)核中用2個全局數(shù)組存放這2類驅(qū)動程序。
#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是塊設(shè)備驅(qū)動程序內(nèi)部的接口函數(shù),上層文件系統(tǒng)還是通過struct file_operations訪問的。

哈哈,現(xiàn)在明白了吧?你的驅(qū)動程序調(diào)用 int register_chrdev(unsigned int major, const char * name, struct file_operations *fops) 就是將你提供的接口函數(shù)fops存放到chrdevs[MAX_CHRDEV]這個數(shù)組中,數(shù)組下標就是你的驅(qū)動的主設(shè)備號,數(shù)組內(nèi)容包括驅(qū)動名稱和驅(qū)動接口函數(shù),這樣,內(nèi)核就能看到你的驅(qū)動程序了。BTW,如果你將major設(shè)為0,系統(tǒng)會自動給你分配一個空閑的主設(shè)備號。
那么?次設(shè)備號呢?別急,馬上就出現(xiàn)了:)
二、設(shè)備節(jié)點如何產(chǎn)生?

    驅(qū)動程序運行在內(nèi)核空間,應(yīng)用程序訪問驅(qū)動程序通常是通過系統(tǒng)調(diào)用文件系統(tǒng)接口函數(shù)的,也就是說,在linux下,和磁盤文件一樣,設(shè)備也是文件,只是他們的文件屬性不同而已,應(yīng)用程序只能通過文件名來訪問設(shè)備的驅(qū)動程序。
所以,文件系統(tǒng)中必須要有一個代表你的設(shè)備的文件,應(yīng)用程序才能訪問你的設(shè)備驅(qū)動程序。
    為了便于理解,我們可以將設(shè)備文件換個名字,叫做設(shè)備節(jié)點。
    設(shè)備節(jié)點在哪里?設(shè)備節(jié)點存在于你的文件系統(tǒng)中,通常在/dev目錄下,當然,你也可以在其它地方創(chuàng)建。一般說來,我們在制作文件系統(tǒng)映像時就已經(jīng)將可能用到的設(shè)備節(jié)點都創(chuàng)建好了。
    你可以打開/dev目錄看一下,它下面的設(shè)備節(jié)點的數(shù)量會讓你吃驚的:)

       如何創(chuàng)建設(shè)備節(jié)點?。
你可以用mknod命令。如使用以下命令可以創(chuàng)建一個mtd4的字符設(shè)備節(jié)點。
Mknod  /dev/ mtd4  c MTD_CHAR_MAJOR  4

我們創(chuàng)建一個普通的磁盤文件,它的內(nèi)容是我們寫入的數(shù)據(jù)。
那么設(shè)備節(jié)點的內(nèi)容是什么?設(shè)備節(jié)點文件沒有數(shù)據(jù),它的文件大小為0,它只有文件屬性,包括設(shè)備類型、主設(shè)備號、次設(shè)備號。
沒有別的了?對,就這些,沒別的了。

那設(shè)備節(jié)點和設(shè)備驅(qū)動程序是怎么聯(lián)系起來的啊?

三、應(yīng)用程序是如何訪問設(shè)備驅(qū)動程序的?

舉個例子:我們要向nor flash的第四個分區(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);
上面的代碼比較簡單,但是似乎沒有看到我們的應(yīng)用程序是如何調(diào)用到驅(qū)動程序的。
沒關(guān)系,接下來我將帶領(lǐng)你們走通這條道路。

應(yīng)用程序調(diào)用Open函數(shù),這是個系統(tǒng)調(diào)用函數(shù),程序會進入內(nèi)核空間調(diào)用sys_open函數(shù)。
在sys_open,首先會根據(jù)文件路徑“/dev/mtd4”找到這個文件節(jié)點,這部分工作是屬于VFS(虛擬文件系統(tǒng))的。
“/dev/mtd4”的文件屬性是字符設(shè)備,于是sys_open會調(diào)用函數(shù)chrdev_open(),在這個函數(shù)里有一句話:
filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
哈!看到了眉目吧!猜也能猜到啊,get_chrfops()里面一定會返回 chrdevs[major].fops的。
我們終于從文件系統(tǒng)走到驅(qū)動程序了,那么,接下來的事情就是可以理解的了。
Write()最終一定會調(diào)用到chrdevs[major].fops->write();
Read()最終一定會調(diào)用到chrdevs[major].fops->read();
各種驅(qū)動程序比較特殊的功能函數(shù)都可以通過ioctl()來得到調(diào)用。
而次設(shè)備號也會作為參數(shù)傳遞給你。
四、為什么要有設(shè)備文件系統(tǒng)?

從前面的章節(jié),我們可以看到以主次設(shè)備號的形式管理設(shè)備驅(qū)動程序存在很大的缺點。
首先,設(shè)備節(jié)點的創(chuàng)建是獨立于內(nèi)核的,是在建立文件系統(tǒng)時就把所有要用到的設(shè)備節(jié)點都創(chuàng)建好了的,通常我們不會去刻意刪除哪些節(jié)點,因為我們不知道系統(tǒng)將來會不會用到它們。由于每個設(shè)備節(jié)點代表唯一的主次設(shè)備號,所以每個可能存在的子設(shè)備都對應(yīng)一個設(shè)備節(jié)點,可見,這樣的設(shè)備節(jié)點數(shù)量是很大的,這些數(shù)量龐大的設(shè)備節(jié)點都(文件)存在于存儲介質(zhì)中,對文件系統(tǒng)的效率也是個影響。
其次,文件系統(tǒng)中存在哪些設(shè)備節(jié)點,并不代表內(nèi)核中就有這種設(shè)備的驅(qū)動程序,也不代表系統(tǒng)中有這種設(shè)備,因為設(shè)備節(jié)點不是動態(tài)創(chuàng)建的,它是制作文件系統(tǒng)時建立的。因此,/dev目錄下的信息大多對我們是無用的,而且那么多的設(shè)備節(jié)點都平鋪在/dev目錄下,閱讀起來也不直觀。
最后,目前主次設(shè)備號都是用8位整數(shù)表示的,也就是說內(nèi)核最多管理256種字符設(shè)備和256種塊設(shè)備。現(xiàn)在計算機外設(shè)種類越來越多,這樣的限制已經(jīng)不夠了。

由于目前的主次設(shè)備號的管理形式有以上幾個缺點,linux內(nèi)核小組在2.4版本以后加入了設(shè)備文件系統(tǒng)來改進這些缺點。
設(shè)備文件系統(tǒng)的思想就是想讓設(shè)備節(jié)點可以動態(tài)創(chuàng)建、刪除,這樣系統(tǒng)中有哪些設(shè)備驅(qū)動程序就可以一目了然;還要能夠把設(shè)備節(jié)點組織成一棵目錄樹,方便閱讀;最后,希望能夠擴大主次設(shè)備號的限制,不再限制在256種設(shè)備以內(nèi)。
五、設(shè)備文件系統(tǒng)如何實現(xiàn)?

       要想在內(nèi)核中方便的做到動態(tài)創(chuàng)建、刪除設(shè)備文件(在這里,我們把設(shè)備節(jié)點稱為設(shè)備文件會更恰當些),最自然的做法就是在RAM中創(chuàng)建一個文件系統(tǒng),內(nèi)核啟動時這個文件系統(tǒng)是空的,以后每加載一種設(shè)備驅(qū)動程序,就在這個文件系統(tǒng)中創(chuàng)建一個對應(yīng)的設(shè)備文件;卸載設(shè)備驅(qū)動程序時,再刪除這個設(shè)備文件。而且,我們可以在這個文件系統(tǒng)中創(chuàng)建目錄,一類設(shè)備文件放在同一個目錄中,甚至把一種設(shè)備的多個子設(shè)備文件放在同一個目錄下,方便閱讀。
    在設(shè)備文件系統(tǒng)中,我們在注冊設(shè)備文件時可以把設(shè)備驅(qū)動程序的ops直接掛到設(shè)備文件的inode中,以后訪問驅(qū)動程序就可以擺脫主次設(shè)備號的限制了,不需要再訪問chrdev[]數(shù)組,這樣就突破了256種設(shè)備的限制。
    我們把設(shè)備文件系統(tǒng)mount到/dev目錄下,這樣,看起來跟以前的方案就很相似了,也方便老的應(yīng)用程序的移植。

六、如何使用設(shè)備文件系統(tǒng)?

       以前我們寫驅(qū)動程序時要調(diào)用int register_chrdev(unsigned int major, const char * name, struct file_operations *fops)將你提供的接口函數(shù)fops存放到chrdevs[MAX_CHRDEV]這個數(shù)組中,然后在文件系統(tǒng)中用mknod創(chuàng)建有相同主設(shè)備號的設(shè)備節(jié)點就可以了。
    那么現(xiàn)在有了設(shè)備文件系統(tǒng),我們的驅(qū)動程序該如何寫呢?
    很簡單,follow me!

1、調(diào)用devfs_handle_t devfs_mk_dir (devfs_handle_t dir, const char *name, void *info)創(chuàng)建設(shè)備文件所在的目錄。Dir是要創(chuàng)建目錄的父目錄句柄,如為NULL,就是設(shè)備文件系統(tǒng)的根目錄(/dev);最后一個參數(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)
注冊具體的設(shè)備,并在指定目錄下創(chuàng)建子設(shè)備節(jié)點。
這里的dir目錄名是要創(chuàng)建的設(shè)備文件所在的目錄名,目錄名一般不能為NULL,因為子設(shè)備文件名name通常是以0、1、2、3等數(shù)字命名的,會和其它設(shè)備文件沖突。
    3、卸載驅(qū)動程序時調(diào)用void devfs_unregister (devfs_handle_t de)刪除創(chuàng)建的目錄和子設(shè)備文件。

七、具體設(shè)備驅(qū)動程序分析

       這節(jié)以mtdchar設(shè)備驅(qū)動程序來具體分析驅(qū)動程序的寫法。
    Mtdchar字符設(shè)備是管理flash驅(qū)動程序的,是各種flash驅(qū)動程序的抽象層。
    Mtdchar的主程序是driver/mtd/mtdchar.c;

1、驅(qū)動程序初始化時,要注冊設(shè)備節(jié)點,創(chuàng)建子設(shè)備文件

驅(qū)動程序為了兼容以前的方案,通常會既注冊設(shè)備節(jié)點,又創(chuàng)建子設(shè)備文件,這樣不管內(nèi)核支持不支持設(shè)備文件系統(tǒng),驅(qū)動程序都可以工作。

static int __init init_mtdchar(void)
{
    //為了兼容以前的方案,要注冊mtdchar的主設(shè)備號、設(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),在這個函數(shù)里會創(chuàng)建子設(shè)備文件。
    mtdchar_devfs_init();
    return 0;
}

static inline void mtdchar_devfs_init(void)
{
    //創(chuàng)建設(shè)備節(jié)點的父目錄,/dev/mtd/
    devfs_dir_handle = devfs_mk_dir(NULL, "mtd", NULL);
    //在這個函數(shù)里會調(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)建一個子設(shè)備
    .remove    = mtd_notify_remove, //刪除一個子設(shè)備
};
static void mtd_notify_add(struct mtd_info* mtd)
{
    char name[8];

    if (!mtd)
       return;
    // mtd是一個子設(shè)備,代表flash上的一個邏輯分區(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);

    //下面注冊的是只讀子設(shè)備,無關(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ū)動程序中會將一片flash劃分為多個邏輯分區(qū),這樣的每個邏輯分區(qū)也可以被看做是一個子設(shè)備,具體flash驅(qū)動程序添加邏輯分區(qū)時會在數(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[]是個全局數(shù)組,每個元素都是一個邏輯分區(qū),記錄著分區(qū)的起始位置和大小,我們將每個邏輯分區(qū)創(chuàng)建為一個單獨的mtd子設(shè)備文件
    for (i=0; i< MAX_MTD_DEVICES; i++)
       if (mtd_table)
           new->add(mtd_table);

    up(&mtd_table_mutex);
}

2、驅(qū)動程序卸載時要注銷設(shè)備節(jié)點,刪除設(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)
{
    //在這個函數(shù)里會調(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;
}
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復 返回頂部 返回列表