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);
然后,简单的介绍一下主要的数据结构:
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