linux驅動開發 --miscdevice

前言

在linux驅動中,需要提供主設備號和次設備號號,通常使用的主設備號是從
0到 255之間的數,僅僅使用主設備號,還是不叫緊張的,因此需要利用次設備號。

linux中,提供了miscdevice這種雜項設備,指定主設備號爲10,次設備號可以設置爲
系統動態分配。

在具體分析miscdevice之前,先給出miscdevice的核心設計思想。

應用層,打開/dev/xxx 節點,會得到 文件描述符 fd,通過fd,可以執行 read()、
wriete()、ioctl()、mmap()等對這個文件的操作。

應用層調用 open()函數,經過系統調用後,會調用驅動程序的 open()函數。 在前邊分析
調用過程時,分析過這個open()的調用過程。

驅動程序中,調用file_opeartions的open()的時候,此時,調用到另外一個 file_operations的
open(),此時應用層會得到這個open()時的 文件描述符fd,此時應用層 read()、write()等操作,
就調用到了這個file_operations。故在cdev_init時的 fops 充當一箇中轉的作用,轉到具體的
fops 來操作。

給出在linux驅動開發之字符設備–自動創建設備節點
代碼上的一個 diff

@@ -134,6 +135,19 @@ static struct file_operations fops ={
     .unlocked_ioctl = cdev_demo_ioctl,
 };

+static int misc_open(struct inode * inode, struct file * file)
+{
+    printk("%s,%d\n",__func__,__LINE__);
+   
+   file->f_op = &fops;
+   return file->f_op->open(inode,file) ;
+}
+
+static struct file_operations misc_fops = {
+   .owner = THIS_MODULE;
+   .open = misc_open,
+};
+
 char cdev_buf[2] = "a";

 static ssize_t cdev_demo_show(struct device *dev,struct device_attribute *attr,    char *buf)  
@@ -171,7 +185,7 @@ static int __init cdev_demo_init(void)
         return -ENOMEM;
     }

-    cdev_init(pdev,&fops);
+    cdev_init(pdev,&misc_fops);

     ret = alloc_chrdev_region(&dev,minor,count,DEVICE_NAME);
     if(ret){

關鍵的代碼是

    file->f_op = &fops;
    return file->f_op->open(inode,file) ;

將變量 fops 賦值給了 file 的 f_op (const struct file_operations *f_op;)

在返回的時候,調用這個file->f_op-open,其實質是調用到 fops的open()。

這樣的一種方式,實現了 中轉的作用。 利用這種中轉,可以進行代碼的分層,在通用層中
實現抽象的open(),在具體的設備中,實現具體的open()、read()、write()。

miscdevice分析

miscdevice 結構體

struct miscdevice  {
    int minor;            //次設備號  通常爲MISC_DYNAMIC_MINOR  動態分配
    const char *name;     //設備爲的名字
    const struct file_operations *fops;//函數操作集
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    umode_t mode;
};

通常 miscdevice的 minor 、name 和 fops是需要實現的。

int misc_register(struct miscdevice * misc)

misc 註冊函數,成功返回0 ,失敗返回負數

int misc_deregister(struct miscdevice *misc)

misc 註銷函數,成功返回0 ,失敗返回負數

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
static int misc_open(struct inode * inode, struct file * file)
{
    //取出次設備號
    int minor = iminor(inode);
    struct miscdevice *c;
    int err = -ENODEV;
    const struct file_operations *old_fops, *new_fops = NULL;

    mutex_lock(&misc_mtx);
    //misc_list中,取出具體設備的misc_device來,得到fops
    list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);       
            break;
        }
    }

    if (!new_fops) {
        mutex_unlock(&misc_mtx);
        request_module("char-major-%d-%d", MISC_MAJOR, minor);
        mutex_lock(&misc_mtx);

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == minor) {    
                new_fops = fops_get(c->fops);
                break;
            }
        }
        if (!new_fops)
            goto fail;
    }

    err = 0;
    //保存原有的fops
    old_fops = file->f_op;
    //賦值新的fops
    file->f_op = new_fops;
    //如果設備實現了open函數
    if (file->f_op->open) {


        /*
         * FIXME: this is a workaround for pmem
         * PMEM need private_data to be NULL for an unmapped file.
         * The private_data is used to store PMEM internal data strucutre.
         * Leakage may happen if we just override this field in pmem_open function
         */
        if (strcmp(c->name, "pmem_multimedia") != 0 && strcmp(c->name, "vmem_multimedia") != 0)
            file->private_data = c;

        //調用open函數
        err=file->f_op->open(inode,file);
        if (err) {
            //調用失敗,恢復fops爲保存old_fops
            fops_put(file->f_op);
            file->f_op = fops_get(old_fops);
        }
    }
    fops_put(old_fops);
fail:
    mutex_unlock(&misc_mtx);
    return err;
}

static struct class *misc_class;

static const struct file_operations misc_fops = {
    .owner      = THIS_MODULE,
    .open       = misc_open,
    .llseek     = noop_llseek,
};

/**
 *  misc_register   -   register a miscellaneous device
 *  @misc: device structure
 *  
 *  Register a miscellaneous device with the kernel. If the minor
 *  number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *  and placed in the minor field of the structure. For other cases
 *  the minor number requested is used.
 *
 *  The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered.
 *
 *  A zero is returned on success and a negative errno code for
 *  failure.
 */

int misc_register(struct miscdevice * misc)
{
    dev_t dev;
    int err = 0;

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);
    //動態分配次設備號
    if (misc->minor == MISC_DYNAMIC_MINOR) {
        //從位圖中,從 misc_minors 開始,查找最小的 zero位,做多查找64次
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
        if (i >= DYNAMIC_MINORS) {
            mutex_unlock(&misc_mtx);
            //沒有次設備號,可以分配
            return -EBUSY;
        }
        //賦值次設備號 
        misc->minor = DYNAMIC_MINORS - i - 1;
        //設置位圖
        set_bit(i, misc_minors);
    } else {
        //指定次設備號
        struct miscdevice *c;
        //從 misc_list中,查看是否制定的這個 minor是否已經註冊 
        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                mutex_unlock(&misc_mtx);
                return -EBUSY;
            }
        }
    }
    //使用MKDEV創建設備號
    dev = MKDEV(MISC_MAJOR, misc->minor);
    //創建設備
    misc->this_device = device_create(misc_class, misc->parent, dev,
                      misc, "%s", misc->name);
    //判斷設備是否創建成功,不成功,則銷燬 次設備號
    if (IS_ERR(misc->this_device)) {
        int i = DYNAMIC_MINORS - misc->minor - 1;
        if (i < DYNAMIC_MINORS && i >= 0)
            clear_bit(i, misc_minors);
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
     //將設備添加到misc_list的頭部
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}

/**
 *  misc_deregister - unregister a miscellaneous device
 *  @misc: device to unregister
 *
 *  Unregister a miscellaneous device that was previously
 *  successfully registered with misc_register(). Success
 *  is indicated by a zero return, a negative errno code
 *  indicates an error.
 */

int misc_deregister(struct miscdevice *misc)
{
    int i = DYNAMIC_MINORS - misc->minor - 1;

    if (WARN_ON(list_empty(&misc->list)))
        return -EINVAL;

    mutex_lock(&misc_mtx);
    //刪除鏈表 misc->list,相當於同時在misc_list 中刪除了這個 misc
    list_del(&misc->list);
    //銷燬設備
    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
    //清理次設備號
    if (i < DYNAMIC_MINORS && i >= 0)
        clear_bit(i, misc_minors);
    mutex_unlock(&misc_mtx);
    return 0;
}

EXPORT_SYMBOL(misc_register);
EXPORT_SYMBOL(misc_deregister);

static char *misc_devnode(struct device *dev, umode_t *mode)
{
    struct miscdevice *c = dev_get_drvdata(dev);

    if (mode && c->mode)
        *mode = c->mode;
    if (c->nodename)
        return kstrdup(c->nodename, GFP_KERNEL);
    return NULL;
}

static int __init misc_init(void)
{
    int err;
//創建proc 文件系統
#ifdef CONFIG_PROC_FS
    proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    printk("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    remove_proc_entry("misc", NULL);
    return err;
}
subsys_initcall(misc_init);

1.利用鏈表,進行存儲具體的設備
2.使用open時的中轉功能
3.通用層: miscdevice,提供結構體、創建類,提供創建設備的核心函數
具體設備層: 填充結構題,實現具體功能; 利用提供的核心函數實現註冊設備 。

參考文獻

linux驅動開發之字符設備框架 -調用過程分析

發佈了84 篇原創文章 · 獲贊 25 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章