linux字符設備驅動總結之:全自動創建設備及節點

 /***************************************************************************************************
                         linux字符設備驅動總結之:全自動創建設備及節點
看了LDD3,深入淺出LDD,以及各個博文,還是需要總結下的。
張永輝 2012年10月9日
***************************************************************************************************/
概覽:
    第一步:註冊設備號                                              信息#tail -f /var/log/message
        註冊函數:
            register_chrdev_region() 或                             查看#lsmod
            alloc_chrdev_region()    或                             查看#cat /proc/devices
            register_chrdev()
        註銷函數:
            unregist_chrdev_region() 或
            unregister_chrdev()
    
    第二步:初始化cdev並添加到系統
        初始化cdev
            靜態初始化 cdev_init() 或
            動態初始化 cdev_alloc()
        添加到系統函數
            cdev_add()
        從系統刪除函數
            cdev_del()
            
    第三步:創建設備節點
        創建類
            class_create()          將放於/sysfs                    查看#ls /sys/class
        刪除類
            class_destroy()
        
        創建節點
            device_create() 或 class_device_create()  將存放於/dev  查看#ls /dev
        刪除節點
            device_destroy() 或 class_device_destroy()
    
    第四步:簡單示例(待續...)

/***************************************************************************************************
                                   第一步:註冊設備號
***************************************************************************************************/
Linux內核中所有已分配的字符設備編號都記錄在一個名爲 chrdevs 散列表裏。
    該散列表中的每一個元素是一個 char_device_struct 結構,它的定義如下:
    static struct char_device_struct
    {
        struct char_device_struct *next;    // 指向散列衝突鏈表中的下一個元素的指針
        unsigned    int major;              // 主設備號
        unsigned    int baseminor;          // 起始次設備號
        int minorct;                        // 設備編號的範圍大小
        char    name[64];                   // 處理該設備編號範圍內的設備驅動的名稱
        struct file_operations *fops;       // 沒有使用
        struct cdev *cdev;                  // 指向字符設備驅動程序描述符的指針
    }*chrdevs[CHRDEV_MAJOR_HASH_SIZE];

    1 每一個主設備有一個會分配一個此結構,可以有多個次設備號。次設備是依次遞增的。
    2 內核提供了5個函數來來管理字符設備編號。
    
            register_chrdev_region()        指定初始值
            alloc_chrdev_region()           動態分配
            register_chrdev()               指定設備號
        他們都會調用 __register_chrdev_region() 來註冊一組設備編號範圍(一個char_device_struct結構),我們使用其中一個即可。
            
            unregist_chrdev_region()        釋放都用此函數
            unregister_chrdev()             都調用了 __unregister_chrdev_region() 來註銷設備

        註冊:
            register_chrdev_region(dev_t first,unsigned int count,char *name)
                first :要分配的設備編號範圍的初始值(次設備號常設爲0);
                count :連續編號範圍.
                Name  :編號相關聯的設備名稱. (/proc/devices);


            int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
                *dev        :存放返回的設備號
                firstminor  :第一個次設備號的號數,常爲0;

            int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
                major :要註冊的設備號, 若爲0則自動分配一個
                name  :設備名
                *fops :以後再聊

        釋放:
            void unregister_chrdev(unsigned int major, const char *name);
            void unregister_chrdev_region(dev_t from, unsigned count);
    3 示例:略
    4 參考:感謝原著 (有此6個函數的源碼及解說)。
        
http://blog.csdn.net/iLetLet/article/details/6180314

/***************************************************************************************************
                            第二步:初始化 cdev 並添加到系統
***************************************************************************************************/
1.內核中每個字符設備都對應一個 cdev 結構的變量,定義如下:
    linux-2.6.22/include/linux/cdev.h
    struct cdev 
    {
        struct kobject kobj;                //每個 cdev 都是一個 kobject
        struct module *owner;               //指向實現驅動的模塊
        const struct file_operations *ops;  //操縱這個字符設備文件的方法
        struct list_head list;              //與 cdev 對應的字符設備文件的 inode->i_devices 的鏈表頭
        dev_t dev;                          //起始設備編號
        unsigned int count;                 //設備範圍號大小
    };

2. 初始化cdev :有兩種定義初始化方式:
    
    方式1:靜態內存定義初始化:
        struct cdev my_cdev;
        cdev_init(&my_cdev, &fops);
        my_cdev.owner = THIS_MODULE;
        
    方式2:動態內存定義初始化:
        struct cdev *my_cdev = cdev_alloc();
        my_cdev->ops = &fops;
        my_cdev->owner = THIS_MODULE;


    下面是2函數的具體代碼:
        struct cdev *cdev_alloc(void)       //它主要完成了空間的申請和簡單的初始化操作;
        {
            struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
            if (p)
            {
                INIT_LIST_HEAD(&p->list);
                kobject_init(&p->kobj, &ktype_cdev_dynamic);
            }
            return p;
        }
    
        void cdev_init(struct cdev *cdev, const struct file_operations *fops)
        {   
            memset(cdev, 0, sizeof *cdev);  //主要是對空間起到一個清零作用並較之cdev_alloc多了一個ops的賦值操作
            INIT_LIST_HEAD(&cdev->list);
            kobject_init(&cdev->kobj, &ktype_cdev_default);
            cdev->ops = fops;
        }

3. 添加cdev到系統
    爲此可以調用 cdev_add() 函數。傳入cdev結構的指針,起始設備編號,以及設備編號範圍。
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        p->dev = dev;
        p->count = count;
        return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
    }

    釋放時使用 cdev_del()函數來釋放cdev佔用的內存。
    void cdev_del(struct cdev *p)
    {
        cdev_unmap(p->dev, p->count);   //釋放 cdev_map 散列表中的對象
        kobject_put(&p->kobj);          //釋放 cdev 結構本身。
    }

4.關於kobject_init() kobj_map()
    內核中所有都字符設備都會記錄在一個 kobj_map 結構的 cdev_map 變量中。
    這個結構的變量中包含一個散列表用來快速存取所有的對象。
    kobj_map() 函數就是用來把字符設備編號和 cdev 結構變量一起保存到 cdev_map 這個散列表裏。
    當後續要打開一個字符設備文件時,通過調用 kobj_lookup()  函數,根據設備編號就可以找到 cdev 結構變量,從而取出其中的 ops 字段。

/***************************************************************************************************
                            第三步:創建設備節點
***************************************************************************************************/
    方法一:利用mknod命令手動創建設備節點。
    方法二:實際上Linux內核爲我們提供了一組函數,可以在模塊加載的時候在/dev目錄下創建相應設備節點,在卸載時可刪除該節點。

    原理:
        1 內核中定義了struct class結構體,它對應一個類。
        2 先調用class_create()函數,可以用它來創建一個類,這個類將存放於sysfs下面.
        3 再調用device_create()函數,從而在/dev目錄下創建相應的設備節點。
        4 卸載模塊對應的函數是 device_destroy 和 class_destroy()
        注:2.6 以後的版本使用device_create(),之前的版本使用的class_device_create()。

    詳解:
        1:class結構:
            include/linux/device.h
            struct class
            {
                const   char        *name;
                struct module       *owner;
                struct kset         subsys;
                struct list_head    devices;
                struct list_head    interfaces;
                struct kset         class_dirs;
                struct semaphore sem;       /* locks    children, devices, interfaces */
                struct class_attribute  *class_attrs;
                struct device_attribute *dev_attrs;
    
                int    (*dev_uevent)   (struct device *dev,struct kobj_uevent_env *env);
                void   (*class_release)(struct class *class);
                void   (*dev_release)  (struct device   *dev);
                int    (*suspend)      (struct  device *dev, pm_message_t state);
                int    (*resume)       (struct device *dev);
            };

        2:class_create() 
            class_create()在/drivers/base/class.c中實現:
            struct class    *class_create(struct module *owner, //  指定類的所有者是哪個模塊
                                          const char *name)     //  指定類名
            {
                struct class *cls;
                int retval;
                cls =   kzalloc(sizeof(*cls), GFP_KERNEL);
                if (!cls)
                {
                    retval =    -ENOMEM;
                    goto    error;
                }
        
                cls->name   = name;
                cls->owner = owner;
                cls->class_release = class_create_release;
        
                retval = class_register(cls);
                if (retval)
                   goto error;
                return cls;
                error:
                    kfree(cls);
                    return ERR_PTR(retval);
            }
            
        3:device_create()函數在/drivers/base/core.c中實現:
            struct device *device_create(struct class *class,   //指定所要創建的設備所從屬的類
                                        struct devicev *parent, //這個設備的父設備,如果沒有就指定爲NULL
                                        dev_t devt,             //設備號
                                        const char *fmt,        //設備名稱
                                        ...)                    //從設備號
            {
                va_list vargs;
                struct  device *dev;
                va_start(vargs, fmt);
                dev = device_create_vargs(class, parent, devt,  NULL, fmt, vargs);
                va_end(vargs);
                return  dev;
            }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章