一、杂项设备

在上次的实验中编写了一个内核模块,是使用内核提供的API实现了一个简单的虚拟设备的驱动,其中字符设备是使用函数alloc_chrdev_region -> cdev_alloc-> cdev_init-> cdev_add来注册和初始化的,而字符设备驱动也可以使用MISC机制来进行注册。
Linux把不符合预先确定的字符设备范畴称为杂项设备-MISC。
Linux的驱动设计是趋向于分层的,大多数设备都有自己归属的类型,例如按键、触摸屏属于输入设备,对于他们有一个input子系统框架。但是对于随机数发生器、时钟发生器等设备,无法明确其属于什么类型,对于这种设备统一使用misc驱动框架编写驱动程序。
MISC设备也是一个字符设备,主设备号是10,不同的杂项设备通过次设备号进行区分。
定义在major.h中,语句如下:

#define MISC_MAJOR 10

MISC设备通过结构体struct miscdevice来存储,源码如下:

struct miscdevice  {int minor;  //指定次设备号const char *name;   //名字const struct file_operations *fops; //文件操作struct list_head list;struct device *parent;struct device *this_device;const struct attribute_group **groups;const char *nodename;umode_t mode;
};

minor字段相当于dev_t设备号,但是由于设定上MISC设备的主设备号是固定的,所以只需要一个int型的字段来区分次设备号就可以了

MISC的相关函数定义在drivers\char\misc.c文件中,接下来对几个重要函数进行详细分析。

二、入口函数

驱动的入口首先创建misc类,这将给misc设备实例生成设备节点时使用,然后定义文件操作集合misc_fops。
源码如下:

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

可以看到misc_fops中只实现了open函数,此函数是用于转发系统调用到具体的misc设备实例中,是misc驱动框架的核心。
源码如下:

static int misc_open(struct inode *inode, struct file *file)
{int minor = iminor(inode);//找到inode对应的设备号,再用MINOR宏求出次设备号后赋给minor变量struct miscdevice *c;int err = -ENODEV;const struct file_operations *new_fops = NULL;mutex_lock(&misc_mtx);  //互斥锁//在misc设备双链表中找minor对应的misc设备list_for_each_entry(c, &misc_list, list) {if (c->minor == minor) {new_fops = fops_get(c->fops);//获得它的文件操作break;}}//如果new_fops为空进行的纠正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;}/** Place the miscdevice in the file's* private_data so it can be used by the* file operations, including f_op->open below*/file->private_data = c;err = 0;replace_fops(file, new_fops);//replace_fops只能用用在open()实例中,new_fops必须非空,结果是用new_fops的内容填充了open后生成的file结构体中的>f_op字段if (file->f_op->open)err = file->f_op->open(inode, file);
fail:mutex_unlock(&misc_mtx);return err;
}

可以看出这个函数和普通字符设备chrdev_open的原理基本一致,文件操作替换过程示意图如下:

三、misc_init

misc_init函数完成misc子系统在内核中的初始化,主要包括创建文件、创建misc类、注册字符设备等一系列操作,最后注册到内核中。
源码如下:

static int __init misc_init(void)
{int err;struct proc_dir_entry *ret;//如果使用proc文件系统,则创建misc项ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);misc_class = class_create(THIS_MODULE, "misc");//在/sys/class/目录下创建一个名为misc的类err = PTR_ERR(misc_class);if (IS_ERR(misc_class))goto fail_remove;err = -EIO;//注册misc类的设备驱动,最终调用内部函数__register_chrdev_region来注册一个字符设备if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))goto fail_printk;misc_class->devnode = misc_devnode;return 0;fail_printk:pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);class_destroy(misc_class);
fail_remove:if (ret)remove_proc_entry("misc", NULL);return err;
}
subsys_initcall(misc_init);

这里需要注意的一点是创建类的函数class_create,它最终调用内部函数__class_create。
源码如下:

struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
{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, key);if (retval)goto error;return cls;error:kfree(cls);return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(__class_create);

但是创建类有什么用呢?
在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可。
简单理解就是:只要有了misc_class对象,所有misc设备就有地方放了。

且最后的语句:

subsys_initcall(misc_init);

subsys_initcall是子系统的初始化函数,表示向内核注册misc子系统。

四、misc_register

int misc_register(struct miscdevice *misc)
{dev_t dev;int err = 0;bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);//MISC_DYNAMIC_MINOR的取指是255,次设备号设置为255时表示由内核来分配设备号INIT_LIST_HEAD(&misc->list);//初始化链表头mutex_lock(&misc_mtx);//自动分配方法if (is_dynamic) {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;//非自动分配的方法//遍历链表,看次设备号minor是否冲突,是则出错返回list_for_each_entry(c, &misc_list, list) {if (c->minor == misc->minor) {err = -EBUSY;goto out;}}}//用MKDEV宏加工成常规格式的设备号,MISC_MAJOR为10dev = MKDEV(MISC_MAJOR, misc->minor);//生成设备节点 misc->this_device =device_create_with_groups(misc_class, misc->parent, dev,misc, misc->groups, "%s", misc->name);if (IS_ERR(misc->this_device)) {if (is_dynamic) {int i = DYNAMIC_MINORS - misc->minor - 1;if (i < DYNAMIC_MINORS && i >= 0)clear_bit(i, misc_minors);misc->minor = MISC_DYNAMIC_MINOR;}err = PTR_ERR(misc->this_device);goto out;}/** Add it to the front, so that later devices can "override"* earlier defaults*///将设备添加到misc设备链表中list_add(&misc->list, &misc_list);out:mutex_unlock(&misc_mtx);return err;
}

可以看到加工完设备号之后,便为misc结构体构造this device字段,即创建设备节点,相当于手动创建命令mknod。
使用的是函数device_create_with_groups,这个函数通之前版本中的device_create是一样的作用,它们最终都是device_create_groups_vargs()函数。
源码如下:

static struct device *
device_create_groups_vargs(struct class *class, struct device *parent,dev_t devt, void *drvdata,const struct attribute_group **groups,const char *fmt, va_list args)
{struct device *dev = NULL;//申请一个空的设备结构体int retval = -ENODEV;if (class == NULL || IS_ERR(class))goto error;dev = kzalloc(sizeof(*dev), GFP_KERNEL);//为设备申请内存if (!dev) {retval = -ENOMEM;goto error;}device_initialize(dev);//设备节点初始化dev->devt = devt;//填充设备结构体dev->class = class;//class字段的创建是在misc_init中完成的dev->parent = parent;dev->groups = groups;dev->release = device_create_release;dev_set_drvdata(dev, drvdata);retval = kobject_set_name_vargs(&dev->kobj, fmt, args); if (retval)goto error;//device_add把设备加入到liux设备驱动模型中,添加到模型之后方便让总线为其找到适配的Driver,实现比较复杂retval = device_add(dev); if (retval)goto error;return dev;error:put_device(dev);return ERR_PTR(retval);
}

到这一步,设备注册、设备号获得、设备创建就全部完成了。

值得注意的是在下方还有一段很相似的代码:

int misc_register(miscdevice *misc)
{return 0;
}

可以看到参数类型有些差异,不明白这段代码的含义。 -_-||

五、misc_deregister

杂项设备的注销过程,其实就是注册过程的逆向过程,即注册的顺序是:申请设备号->创建设备->添加到设备链表,注销便是:从设备链表删除->销毁设备->释放设备号。
源码如下:

void misc_deregister(struct miscdevice *misc)
{int i = DYNAMIC_MINORS - misc->minor - 1;if (WARN_ON(list_empty(&misc->list)))return;mutex_lock(&misc_mtx);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);
}

这里着重看一下设备的销毁函数device_destroy,函数位于drivers/base/core.c中。
源码如下:

void device_destroy(struct class *class, dev_t devt)
{struct device *dev;dev = class_find_device(class, NULL, &devt, __match_devt);if (dev) {put_device(dev);//在linux内核中,get_device()和put_device()是一对相反的操作函数。get_device()是对设备引用数量加一,put_device()是对设备引用减一device_unregister(dev);}
}
EXPORT_SYMBOL_GPL(device_destroy);

可以看出代码非常简明,继续展开看到调用逻辑为device_unregister->device_del,查看device_del的代码为:

void device_del(struct device *dev)
{struct device *parent = dev->parent;struct kobject *glue_dir = NULL;struct class_interface *class_intf;/* Notify clients of device removal.  This call must come* before dpm_sysfs_remove().*/if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DEL_DEVICE, dev);dpm_sysfs_remove(dev);if (parent)klist_del(&dev->p->knode_parent);if (MAJOR(dev->devt)) {devtmpfs_delete_node(dev);device_remove_sys_dev_entry(dev);device_remove_file(dev, &dev_attr_dev);}if (dev->class) {device_remove_class_symlinks(dev);mutex_lock(&dev->class->p->mutex);/* notify any interfaces that the device is now gone */list_for_each_entry(class_intf,&dev->class->p->interfaces, node)if (class_intf->remove_dev)class_intf->remove_dev(dev, class_intf);/* remove the device from the class list */klist_del(&dev->knode_class);mutex_unlock(&dev->class->p->mutex);}device_remove_file(dev, &dev_attr_uevent);device_remove_attrs(dev);bus_remove_device(dev);device_pm_remove(dev);driver_deferred_probe_del(dev);device_remove_properties(dev);device_links_purge(dev);/* Notify the platform of the removal, in case they* need to do anything...*/if (platform_notify_remove)platform_notify_remove(dev);if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_REMOVED_DEVICE, dev);kobject_uevent(&dev->kobj, KOBJ_REMOVE);glue_dir = get_glue_dir(dev);kobject_del(&dev->kobj);cleanup_glue_dir(dev, glue_dir);put_device(parent);
}
EXPORT_SYMBOL_GPL(device_del);

device_del所做的操作就是:从控制列表中删除设备,将其从其他驱动程序模型子系统中删除,并将其从kobject层次结构中删除。

六、实例及测试

实现思路为:
定义打开以及读写方法->定义file_operations->定义misc设备结构体->注册设备->释放设备

实现代码如下:

# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/miscdevice.h>
# include <linux/kfifo.h>DEFINE_KFIFO(mydemo_fifo, char, 64);# define DEMO_NAME "my_misc_dev"
static struct device *miscde_demo;static int my_misc_open(struct inode *inode, struct file *file)
{int major = MAJOR(inode->i_rdev);int minor = MINOR(inode->i_rdev);printk("打开MISC设备%s\n", DEMO_NAME);printk("Method--%s: Major=%d, Minor=%d\n",__func__,major,minor);return 0;
}static ssize_t my_misc_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{int actual_readed;int ret;ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed);if(ret)return -EIO;printk("Method--%s: actual_readed=%d, ppos=%lld\n",__func__,actual_readed,*ppos);return actual_readed;
}static ssize_t my_misc_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos)
{unsigned int actual_write;int ret;ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write);if(ret)return -EIO;printk("Method--%s: actual_write=%d, ppos=%lld\n",__func__,actual_write,*ppos);return actual_write;
}static const struct file_operations my_misc_fops = {.owner = THIS_MODULE,.open = my_misc_open,.read = my_misc_read,.write = my_misc_write,
};static struct miscdevice my_miscde = {.minor = MISC_DYNAMIC_MINOR,.name = DEMO_NAME,.fops = &my_misc_fops,
};static int __init simple_misc_init(void)
{int ret;ret = misc_register(&my_miscde);if(ret){printk("Register misc device failed!\n");return ret;}miscde_demo = my_miscde.this_device;printk("Register misc device successed : %s\n",DEMO_NAME);return 0;
} static void __exit simple_misc_exit(void)
{misc_deregister(&my_miscde);printk("Remove device successed.\n");
}module_init(simple_misc_init);
module_exit(simple_misc_exit);MODULE_LICENSE("GPL");

用户态测试程序如下:

# include <stdio.h>
# include <fcntl.h>
# include <unistd.h>
# include <malloc.h>
# include <string.h># define DEMO_DEV_NAME "/dev/my_misc_dev"int main()
{char buffer[64];int fd;int ret;size_t len;char message[] = "Hello MISC!";char *read_buffer;len = sizeof(message);fd = open(DEMO_DEV_NAME,O_RDWR);if(fd<0) {printf("Open device %s failed\n",DEMO_DEV_NAME);return -1;}//向设备写数据ret = write(fd,message,len);if(ret != len){printf("Cannot write to device %d, ret=%d\n",fd,ret);return -1;}read_buffer = malloc(2*len);memset(read_buffer,0,2*len);//关闭设备ret = read(fd,read_buffer,2*len);printf("Read %d bytes. \nRead buffer=%s\n", ret, read_buffer);close(fd);return 0;
}

插入内核模块:

root@ubuntu:/kernel_sample/misc_drive# insmod misc_drive.ko

查看内核消息

root@ubuntu:~# dmesg

运行用户态测试程序:

root@ubuntu:/kernel_sample/misc_drive# gcc test.c
root@ubuntu:/kernel_sample/misc_drive# ./a.out

查看内核消息

root@ubuntu:~# dmesg



参考资料:
https://blog.csdn.net/weixin_42462202/article/details/100039448
https://blog.csdn.net/armwind/article/details/52166139
https://www.cnblogs.com/deng-tao/p/6042965.html
https://blog.csdn.net/weixin_42462202/article/details/100039448

MISC机制编写字符驱动程序相关推荐

  1. linux文件控制驱动程序,Linux设备驱动程序学习(6)-高级字符驱动程序操作[(3)设备文件的访问控制]...

    Linux设备驱动程序学习(6) -高级字符驱动程序操作[(3)设备文件的访问控制] 提供访问控制对于一个设备节点来的可靠性来说有时是至关重要的.这部分的内容只是在open和release方法上做些修 ...

  2. Linux设备驱动程序学习(4) -高级字符驱动程序操作[(1)ioctl and llseek]

    今天进入<Linux设备驱动程序(第3版)>第六章高级字符驱动程序操作的学习. 一.ioctl 大部分设备除了读写能力,还可进行超出简单的数据传输之外的操作,所以设备驱动也必须具备进行各种 ...

  3. 【网站推荐】Solaris 平台编写设备驱动程序

    Documentation Home  > 编写设备驱动程序 Book Information 索引 前言 第 1 部分 针对 Solaris 平台设计设备驱动程序 第 1 章 Solaris ...

  4. Linux USB 驱动开发(三)—— 编写USB 驱动程序

    前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再 ...

  5. 一个简单的字符驱动程序

    一.背景 为了了解设备驱动程序的框架,在此编写一个简单的字符驱动程序,以此来对驱动程序的框架进行一个简单的了解. 二.设备驱动程序 所谓设备驱动程序,其实就是计算机硬件与外部设备进行通信的接口.由于硬 ...

  6. 小白学python系列————【Day15】垃圾回收机制及字符编码简史

    今日内容概要 垃圾回收机制(理论) 字符编码概述(理论) 字符编码相关操作 代码操作文件 垃圾回收机制 python会自动帮你申请和释放内存空间 1.引用计数 概念: 当数据值身上的引用计数不为0即变 ...

  7. 在Android内核源代码工程中编写硬件驱动程序(1)

    在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了.据统计,截止2011年5月,AppStore的应用软 ...

  8. Linux驱动开发(从零开始编写一个驱动程序)

    1.系统整体工作原理 (1)应用层->API->设备驱动->硬件 (2)API:open.read.write.close等 (3)驱动源码中提供真正的open.read.write ...

  9. 在ubuntu上为android系统编写Linux驱动程序【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/6568411 在智能手机时代,每个品牌的手机都有自己的个性特点.正是依靠这种与众不同的 ...

最新文章

  1. RHEL5 Silent方式安装Oracle 11gR2指南
  2. VS2017增加数据库连接串
  3. python十三:函数
  4. 站长就是个太监^_^
  5. 基于Spark的Als算法+自迭代+Spark2.0新写法
  6. Java ResourceBundle getLocale()方法与示例
  7. 3月15日之前的FreeEIM
  8. Sublime 安装包时出现的 There are no packages available for installation
  9. leetcode - 617. 合并二叉树
  10. JS 前端排序 数组指定项移动到最后
  11. plt的纵坐标的百分号显示
  12. Ruby 学习笔记6
  13. Android ProGuard 还原堆栈
  14. VC6.0用file-open后,出现“MSDEV.EXE-应用程序错误 遇到问题需要关闭“
  15. 人工智能肉搏战:商汤和旷世们的商业化征途
  16. 点云数据文件常用格式及PCL中点云数据类型
  17. CAD迷你画图2019破解补丁|CAD迷你画图2019r3注册机下载
  18. flash 在谷歌 不能使用
  19. Node.js meitulu图片批量下载爬虫 1.05版(Final最终版)
  20. css水波纹-雷达扩散效果

热门文章

  1. 【Aladdin-Unity3D-Shader编程】之六-模型实时阴影
  2. 教你快速爬取哔哩哔哩整部番剧的视频弹幕
  3. 数字电路反相器符号_反相器
  4. 姑苏城内的老黄,金鸡湖边的GTC,你get到几个点?
  5. RSSI in wifi
  6. php验证码无法验证问题,php无法显示验证码
  7. Asterisk支持通话录音前语音提示
  8. 网页中title乱码问题解决方案
  9. mysql获取年月日周季度
  10. 用R对Twitter用户的编程语言语义分析