linux 混雜設備miscdevice

#miscdevice混雜設備

         miscdevice混雜設備是字符設備的一種,它們共享一個主設備號(10),但次設備號不同,所有混雜設備形成一個鏈表,對設備發給你問時內核根據次設備號查找到相應的miscdevice設備。這樣做的好處,節約主設備號,將某些設備用鏈表的形式鏈接在一起,最後通過查找次設備區分。
miscdevice混雜設備用主設備號無法匹配出設備驅動,只能找到鏈表,再通過次設備號,才能找到設備驅動,而一般字符設備,通過主設備號,就能找到設備驅動了。

         通過代碼,我們簡單瞭解一下miscdevice混雜設備(只截取有關內容)

include/linux/miscdevice.h:

#define MISC_DYNAMIC_MINOR 255

struct device;

struct miscdevice  {
int minor; //次設備號
const char *name;//設備名稱,即/dev創建的設備文件名稱
const struct file_operations *fops;//對應設備文件的一組操作
struct list_head list;//最終會鏈接到內核維持的misc_list鏈表中,方便查找。
struct device *parent;
struct device *this_device;//指向當前創建的設備文件
const char *nodename;
umode_t mode;
};

extern int misc_register(struct miscdevice * misc);//混雜設備註冊
extern int misc_deregister(struct miscdevice *misc);//混雜設備註銷


drivers/char/misc.c

static int misc_open(struct inode * inode, struct file * file)
{
	int minor = iminor(inode);
	struct miscdevice *c;
	int err = -ENODEV;
	const struct file_operations *new_fops = NULL;

	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) {
	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;
	replace_fops(file, new_fops);
	if (file->f_op->open) {
	/* 
     * file->private_data 在混雜設備中 保存的是混雜設備的數據結構。
     * 在一般字符設備中,保存的是 自定義的包含struct cdev結構的數據結構
     */ 
		file->private_data = c;
		err = file->f_op->open(inode,file);
	}
fail:
	mutex_unlock(&misc_mtx);
	return err;
}

/* 包含一組設備驅動操作的結構體 */
static const struct file_operations misc_proc_fops = {
	.owner	 = THIS_MODULE,
	.open    = misc_seq_open, //在之後的定義操作,無須對open賦值,而一般字符設備必須初始化open操作
	.read    = seq_read,
	.llseek  = seq_lseek,
	.release = seq_release,
};

/* 混雜設備註冊 */
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) {
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
		if (i >= DYNAMIC_MINORS) {
			err = -EBUSY;
			goto out;
		}
		misc->minor = DYNAMIC_MINORS - i - 1;
		set_bit(i, misc_minors);
	} else {
		struct miscdevice *c;

		list_for_each_entry(c, &misc_list, list) {
			if (c->minor == misc->minor) {
				err = -EBUSY;
				goto out;
			}
		}
	}


	dev = MKDEV(MISC_MAJOR, misc->minor);
	/*
     * device_create() : 在/dev/目錄下建立設備文件
     * 所以在混雜設備中不需要用mknod命令顯式建立設備文件
     * 但device_create() 依賴 /sys/class 下的文件,因此在此之前得調用class_create()
     */
	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;
}


/* 混雜設備卸載 */
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);
	list_del(&misc->list);
    /*
     * device_destroy : 會刪除原先在/dev目錄下創建的設備文件。
     * 在一般字符設備卸載時,並沒有刪除在/dev目錄下創建的設備文件。
     */
	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 int __init misc_init(void)
{
	int err;

	#ifdef CONFIG_PROC_FS
	proc_create("misc", 0, NULL, &misc_proc_fops);
	#endif
    /*
     * class_create() : 在/sys/class/目錄下 創建 misc 目錄
     * /sys/class 包含了在內核中已經註冊的設備類,每個設備類描述了一種設備的作用類型
     */
	misc_class = class_create(THIS_MODULE, "misc");
	err = PTR_ERR(misc_class);
	if (IS_ERR(misc_class))
		goto fail_remove;

	err = -EIO;
    /*
     * register_chrdev() : 註冊一個字符設備驅動
     */
	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);

參考源碼:linux-4.4
         然後,簡單的介紹一下主要的數據結構:

  1. struct file_operations : 採用面向對象的思想,將一組系統調用封裝到一個結構體中。在寫設備驅動時,將自定義的操作賦值相應的系統調用。當對該設備文件操作時,內核自動選擇相應的系統調用。
  2. struct inode : 在內部表示文件。在設備驅動中,一般只使用dev_t i_rdev和struct cdev *i_cdev。
  3. struct file : 表示打開的文件。它是由內核open時創建,並傳遞給在該文件上進行操作的所有函數,直到最後的close函數。

         各自成員請參考《linux設備驅動程序》第三章。

         接下來,比較混雜設備和一般字符設備

  1. 數據結構:在混雜設備中自定義的數據結構不必包含miscdevice結構體,而一般字符設備中自定義的數據結構得包含cdev結構體。
  2. 設備編號: 在混雜設備中主設備編號恆爲10,次設備編號在調用misc_register()時分配。而在一般字符設備提供了兩種分配設備編號的方法,一是當明確知道所需要的設備編號時,調用register_chrdev_region()分配次設備號,二是當不明確主設備號時,則採用alloc_chrdev_region()動態主設備號。
  3. 註冊字符設備:在混雜字符設備中僅需調用misc_register()即可註冊,而一般字符設備,則需要通過cdev_init()初始化包含struct cdev的數據結構和cdev_add()通知內核字符設備的信息。
  4. 自定義系統調用:混雜設備中無需自定義open系統調用,而一般字符設備中,則需要自定義open系統調用。
  5. 設備與操作數據結構相關聯:在混雜設備中只需在初始化miscdevice結構賦值即可,而字符設備則需要cdev_init()相關聯並且顯式賦值cdev結構體中ops成員。
  6. 卸載設備: 在混雜字符設備中,僅需調用misc_deregister()即可,而在一般字符設備中,需要通過cdev_del()移除字符設備和通過unregister_chrdev_region釋放已分配的設備號。
  7. 實際的設備文件:在混雜設備中,自動創建實際的設備文件;卸載混雜設備模塊時,自動刪除實際設備文件,原因請看上述源代碼。在一般字符設備中,這需要通過/proc/devices文件顯式創建實際的設備文件;卸載一般字符設備模塊之後,並沒有刪除實際的設備文件,需顯式刪除設備文件。

最後另附很小miscdevice驅動測試代碼:

mydev.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>


MODULE_LICENSE("GPL");

#define MCDEV_NAME "mcdev"

DEFINE_MUTEX(mcdev_mutex);

struct struct_mcdev_buf{
	char buf[1000];
	int size;
};

/* remember requesting space for a point */
static struct struct_mcdev_buf *mcdev_buf;

ssize_t mcdev_read(struct file *filp, char __user *to_userbuf, size_t count, loff_t *ppos)
{
	ssize_t res = 0;
 	mutex_lock(&mcdev_mutex);

	printk(KERN_NOTICE "reading.....\n");
	if(count > 999)
    	count = 999;
	if(mcdev_buf->size < *ppos + count)
    	count = mcdev_buf->size - *ppos;

	if(copy_to_user(to_userbuf, mcdev_buf->buf, count)){
    	res = -EINVAL;
    	goto out;
	}
	*ppos += count;
	res = count;

 	out:
	mutex_unlock(&mcdev_mutex);
	return res;
}

ssize_t mcdev_write(struct file *filp, const char __user *from_userbuf, size_t count, loff_t *ppos)
{
 	ssize_t res = 0;
	mutex_lock(&mcdev_mutex);
 	printk(KERN_NOTICE "writing.......\n");

	if(count > 999)
    	count = 999;
	if(mcdev_buf->size < count + *ppos)
    	count = mcdev_buf->size - *ppos;

	if(copy_from_user(mcdev_buf->buf, from_userbuf, count)){
    	res = -EFAULT;
    	goto out;
	}

	*ppos += count;

	out:
	mutex_unlock(&mcdev_mutex);
	return count;
}

/* .open is initialized in driver/char/misc.c */
static struct file_operations ops = {
	.read = mcdev_read,
	.write = mcdev_write,
};

/* 
* initialization for struct miscdevice 
* but we initialize three member for it.
* 
* minor, name, fops 
*/
static struct miscdevice mcdev ={
	.minor = MISC_DYNAMIC_MINOR, 
	.name = MCDEV_NAME,
	.fops = &ops,
};

static int __init register_mcdev(void)
{
	int ret;

	printk(KERN_NOTICE "register_mcdev......\n");

	ret = misc_register(&mcdev);

	mcdev_buf = kmalloc(sizeof(struct struct_mcdev_buf), GFP_KERNEL);
	memset(mcdev_buf->buf, 0, sizeof(mcdev_buf->buf));
	mcdev_buf->size = 1000;

	printk(KERN_NOTICE "minor : %d\n", mcdev.minor);    

	if(ret)
    printk(KERN_WARNING "register fail!\n");

	return ret;
}

static void __exit deregister_mcdev(void)
{
	int ret;

	kfree(mcdev_buf);
	ret = misc_deregister(&mcdev);


	if(!ret)
	printk(KERN_NOTICE "deregister success\n");
    
}

module_init(register_mcdev);
module_exit(deregister_mcdev);

MODULE_AUTHOR("[email protected]");

Makefile文件如下:

all: module

ifndef KERNEL_DIR
	KERNEL_DIR = /lib/modules/`uname -r`/build
endif

obj-m := mydev.o

.PHONY: module
module:
	make -C $(KERNEL_DIR) M=$(PWD) modules

.PHONY: clean
clean:
	make -C $(KERNEL_DIR) M=$(PWD) clean
	rm *~ > /dev/null 2>&1

.PHONY: install
install:
	sudo insmod mydev.ko

.PHONY: uninstall
uninstall:
	sudo rmmod mydev.ko

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