分类: LINUX
2010-12-24 19:26:46
杂项设备的原理就是注册一个主设备号,将各种杂类设备都归属于该主设备号之下。
杂项设备本质上就是字符设备。
static int __init misc_init(void)
{
int err;
#ifdef CONFIG_PROC_FS
//在proc文件系统下创建一个"misc"目录。 misc_proc_fops是该文件系统下文件的操作函数集。
proc_create("misc", 0, NULL, &misc_proc_fops);
#endif//创建一个杂项设备类名为"misc"
misc_class = class_create(THIS_MODULE, "misc");
err = PTR_ERR(misc_class);
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
//注册一个主设备号为MISC_MAJOR(10)的字符设备,设备操作函数集为misc_fops。
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
goto fail_printk;
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);//作为子系统添加到内核
/*****************************************************************************************************************************/
//注册字符设备的具体实现如下
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
//注册一个主设备号(major)和在该主设备号下的256个次设备号。
cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
//为字符设备结构体分配内存空间
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;//指向该字符设备操作函数
kobject_set_name(&cdev->kobj, "%s", name);
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
//将该字符设备添加到内核。
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
/*****************************************************************************************************************************/
/*****************************************************************************************************************************/
/*****************************************************************************************************************************/
/*****************************************************************************************************************************/
现在我们已经将主设备号为10的字符设备添加到了内核,并注册了该主设备号下的256个次设备号。
那么是如何将这些次设备号分配给各杂项设备并为它们创建设备节点的呢?让我们来看一个看门狗驱动的例子。
每一个杂项设备都对应一个杂项设备结构体:
struct miscdevice {
int minor;//次设备号
const char *name;//设备名
const struct file_operations *fops;//杂项设备也是字符设备,因此也有自己的操作函数集
struct list_head list;//用于挂接到杂项设备链表misc_list上。
struct device *parent;//指向父设备
struct device *this_device;//在创建设备节点时指向函数device_create()返回的设备结构
};
在看门狗驱动中申明并初始化了一个杂项设备结构体(在文件linux/drivers/char/watchdog/s3c2410_wdt.c中)
static struct miscdevice s3c2410wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &s3c2410wdt_fops,//看门狗设备操作函数集
};
操作函数集 s3c2410wdt_fops在文件s3c2410_wdt.c中申明并实现:
static const struct file_operations s3c2410wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = s3c2410wdt_write,
.unlocked_ioctl = s3c2410wdt_ioctl,
.open = s3c2410wdt_open,
.release = s3c2410wdt_release,
};
该看门狗设备不仅作为杂项设备还作为平台设备而存在。
以下是平台设备驱动探测函数:
static int s3c2410wdt_probe(struct platform_device *pdev)
{
。
。
。
//看门狗作为杂项设备被添加到内核并创建设备节点
ret = misc_register(&s3c2410wdt_miscdev);
if (ret) {
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
goto err_clk;
}
。
。
。
return ret;
}
/*****************************************************************************************************************************/
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;
INIT_LIST_HEAD(&misc->list);//初始化misc->list,将用于将结构体misc挂接到链表misc_list
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {//遍历链表misc_list,看将要注册的杂项设备的设备号是否已经被占用。
mutex_unlock(&misc_mtx);
return -EBUSY;
}
}
//如果杂项设备的设备号被置为MISC_DYNAMIC_MINOR,则表明该设备的设备号要从新自动分配。
if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = DYNAMIC_MINORS;
while (--i >= 0)//在该位图中找一个未使用的位置。
if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)//misc_minors是杂项设备位图,总共有64个位,表示可以注册64个杂项设备。
break;
if (i<0) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = i;//将在位图中找到的位置的索引号作为该设备的次设备号。
}
if (misc->minor < DYNAMIC_MINORS)
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);//将次设备号对应为置1表明该设备号已被占用。
//计算设备号主设备号为MISC_MAJOR(10),即是我们先前注册的字符设备的主设备号
dev = MKDEV(MISC_MAJOR, misc->minor);
misc->this_device = device_create(misc_class, misc->parent, dev, NULL,//创建设备节点设备号为dev
"%s", misc->name);
if (IS_ERR(misc->this_device)) {
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);//将该杂项设备添加到杂项设备链表。
out:
mutex_unlock(&misc_mtx);
return err;
}
到此一个杂项设备就被添加到了内核,并为之创建了设备节点。
所有的杂项设备都共用一个主设备号,但各有各的操作函数集,各杂项设备被是如何找到自己的操作函数集的呢?
先前我们注册了一个字符设备,设备名为"misc",主设备号为MISC_MAJOR,操作函数集为misc_fops。
该字符设备管理着主设备号MISC_MAJOR下的所有杂项设备。
操作函数集misc_fops在文件linux/drivers/char/misc.c中申明并初始化。
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
//该操作函数集只有一个打开函数,让我们看看在设备打开时做了哪些工作。
//用户空间的文件操作函数直接调用的是主设备号对应的那个设备的操作函数集中的函数。
//所以用户空间打开一个杂项设备时直接调用的打开函数必是此处的misc_open()而不是
//各杂项设备自己的打开函数。
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;
lock_kernel();
mutex_lock(&misc_mtx);
//遍历杂项设备链表,找出次设备号为minor的杂项设备结构体
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops); //找到后获取该杂项设备的操作函数集。
break;
}
}
//如果操作函数集不存在,说明该杂项设备模块没有被加载,则请求加载该模块request_module()
//并重新遍历链表misc_list找出杂项设备结构体并获取它的操作函数集。
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;
old_fops = file->f_op;//保存旧的操作函数集,即是操作函数集misc_fops。
file->f_op = new_fops;//让file->f_op指向新的操作函数集。
if (file->f_op->open) {
err=file->f_op->open(inode,file);//实现对应杂项设备的真正打开。
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);//增加old_fops的引用计数
fail:
mutex_unlock(&misc_mtx);
unlock_kernel();
return err;
}
//杂项设备文件打开的主要工作就是寻找次设备号对应杂项设备,并获取该杂项设备操作函数集,用新的操作函数集中的
//打开函数实现设备的真正打开。
转自:http://hi.baidu.com/396954504/blog/item/b5075254006ddb113b29357e.html