001_Linux内核驱动之杂项设备(miscellaneous device)的misc.c源码解析

0、写在开头
    在Linux系统中,存在一类字符设备,他们共享一个主设备号(10),但次设备号不同,我们称这类设备为杂项设备(miscdeivce)。
    查看/proc/device中可以看到一个名为misc的主设备号为10.所有的杂项设备形成一个链表,对设备访问时内核根据次设备号找到对应的miscdevice设备。
(1)linux 内核使用struct miscdeivce描述一个杂项设备:
    a)linux源码中位置:/kernel-4.14/include/linux/miscdevice.h
    b)详细描述

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;
 };

 c) 这个结构体是misc设备基本的结构体,在注册misc设备的时候必须要声明并初始化一个这样的结构体,但其中一般只需填充name minor fops字段即可。 例如:

    static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &dev_fops,
};

(2)杂项设备作为字符设备的封装,为字符设备提供的简单的编程接口,如果编写新的字符驱动,可以考虑使用杂项设备接口,方便简单,只需要初始化一个miscdevice的结构,调用misc_register就可以了。
    系统最多有255个杂项设备,因为杂项设备模块自己占用了一个次设备号。

1、内核源码中的路径
    kernel-4.14\drivers\char\misc.c
    kernel4.14\include\linux\miscdevice.h

2、static int __init misc_init(void)

static int __init misc_init(void){

	//关键代码1:创建一个proc入口项
	ret = proc_create("misc", 0, NULL, &misc_proc_fops);
	//关键代码2:在/sys/class/目录下创建一个名为misc的类
	misc_class = class_create(THIS_MODULE, "misc");
	//关键代码3:
	if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
		goto fail_printk;
	//关键代码4:
	misc_class->devnode = misc_devnode;
	return 0;
	//关键代码5:
	class_destroy(misc_class);
	//关键代码6:
	remove_proc_entry("misc", NULL);
}

2.1、proc_create()函数
(1)此函数定义中/kernel-4.14/fs/proc/generic.c中,函数原型为:
     struct proc_dir_entry *proc_create(const char *name, umode_t mode,
        struct proc_dir_entry *parent,
        const struct file_operations *proc_fops);

(2)此函数内部调用 proc_create_data(name, mode, parent, proc_fops, NULL)函数。
        此函数底层继续调用 __proc_create()函数。
(3)此函数功能:创建特定的proc。

2.2、class_create()
(1)这个实际是一个宏,其定义位于:/kernel-4.14/include/linux/device.h 文件中。
 #define class_create(owner, name)        \
 ({                        \
     static struct lock_class_key __key;    \
     __class_create(owner, name, &__key);    \
 })
 (2)实际调用的是 __class_create()函数
    create a struct class structure
    struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key);
    @owenr:pointer to the module that is to "own" this struct class
    @name: pointer to a string for the name of this class
    @key: the lock_class_key for this class; used by mutex lock debugging
    这个函数的主要功能是创建一个类指针以便在dev_create()中使用。
    此函数的定义:/kernel-4.14/drivers/base/class.c
(3)在/sys/class/目录下创建一个名为misc的类
    
2.3、register_chrdev()函数
(1)函数内部调用 int __register_chrdev(unsigned int major, 
                unsigned int baseminor,
                unsigned int count,
                const char *name,
                const struct file_operations *fops)
(2)create and register a cdev occupying a range of minors
(3)形参说明
    @major: major device number or 0 for dynamic allocation
    @baseminor: first of the requested range of minor numbers
    @count: the number of minor numbers required
    @name: name of this range of devices
    @fops: file operations associated with this devices
    如果major==0,那么将会动态分配一个主设备号并将此号返回。
    如果major>0,那么此函数将分配一个指定的主设备号。
(4)此函数定义:/kernel-4.14/fs/char_dev.c
(5)内部使用 struct file_operations misc_fops。
    static const struct file_operations misc_fops = {
     .owner        = THIS_MODULE,
     .open        = misc_open,
     .llseek        = noop_llseek,
 };

2.4、class_destroy()
(1)此函数用来销毁一个class struct。
(2)函数定义:/kernel-4.14/drivers/base/class.c
     void class_destroy(struct class *cls)
    {
        if ((cls == NULL) || (IS_ERR(cls)))
            return;
        class_unregister(cls);
    }
(3)The pointer to be destroyed must have been created with a call to class_create().

2.5、remove_proc_entry()
(1)函数定义位于/kernel-4.14/fs/proc/generic.c文件中
    Remove a /proc entry and free it if it's not currently in use.
(2)函数内部详细说明参加此函数的定义,后续再说明。

2.6、subsys_initcall(misc_init);
linux内核中的subsys_initcall的实现细节:
    misc_init 作为一个子系统被注册到linux内核中
    (1)内置模块中的定义:/kernel-4.14/include/linux/init.h
  

  #define subsys_initcall(fn)        __define_initcall(fn, 4)
    
  #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
    
  #define ___define_initcall(fn, id, __sec) __lto_initcall(__COUNTER__, __LINE__, fn, id, __sec)
    
  #define __lto_initcall(c, l, fn, id, __sec) ___lto_initcall(c, l, fn, id, __sec)
    
  #define ___lto_initcall(c, l, fn, id, __sec) \
        static initcall_t __initcall_##c##_##l##_##fn##id __used \
            __attribute__((__section__( #__sec \
                __stringify(.init..##c##_##l##_##fn)))) = fn;

    (2)可加载模块中的定义:/kernel-4.19/include/linux/module.h
    #define subsys_initcall(fn)        module_init(fn)
    #define module_init(x)    __initcall(x);
    
    module_init():这个函数用于创建一个驱动模块的入口,在内核启动或模块被加载时调用。
    与之对应的是:#define module_exit(x)    __exitcall(x);
    a)driver exit entry point
    b)当模块从内核中移除时调用。

3、misc_register()函数
3.1、函数原型:int misc_register(struct miscdevice *misc);
    (1)misc指针指向一个创建的杂项设备(字符设备)
    (2)这个函数用来向内核注册一个杂项设备。如果misc->minor是MISC_DYNAMIC_MINOR,那么将动态分配一个次设备号。
        通过形参传入的杂项设备必须一致存在,直到调用unregister()函数。
3.2、函数定义

int misc_register(struct miscdevice *misc)
 {
	//字符设备号
 	dev_t dev;
 	int err = 0;
	//判断是否需要动态分配次设备号
 	bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
	//设备链表misc_list的初始化
 	INIT_LIST_HEAD(&misc->list);
 	mutex_lock(&misc_mtx);
 
 	if (is_dynamic) {
		//动态分配次设备号,查询可用的设备号
		//#define DYNAMIC_MINORS 128 /* like dynamic majors */---在misc.c头部定义
 		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;
 			}
 		}
 	}
	//根据分配号的主设备号和从设备号创建杂项设备完整的设备号
	//字符设备号一共32bit,高12bit是主设备号,低20bit是次设备号
 	dev = 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
 	 */
	//将这个miscdevice添加到misc_list链表中
 	list_add(&misc->list, &misc_list);
  out:
 	mutex_unlock(&misc_mtx);
 	return err;
 }

4、misc_deregister()函数
4.1、函数原型:void misc_deregister(struct miscdevice *misc);
    删除一个杂项设备
    misc--指向要删除的杂项设备
4.2、函数定义

 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);
	//从misc_list链表中删除miscdevice设备
 	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);
 }

5、misc_open()函数
5.1、函数原型声明:static int misc_open(struct inode *inode, struct file *file);
5.2、函数定义

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);
	//找到次设备号对应的操作函数集合,让new_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);
		//重新遍历misc_list链表,如果没有找到就退出,否则让new_fops指向这个具体设备的操作函数集合
 		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);
 	if (file->f_op->open)
 		err = file->f_op->open(inode, file);
 fail:
 	mutex_unlock(&misc_mtx);
 	return err;
 }

注:用户打开miscdevice设备是通过主设备号对应的打开函数,在这个函数中找到次设备号对应的相应的具体设备的open函数。

6、特殊信息专项说明
6.1、杂项设备的注册及卸载简要流程
注册:
    调用misc_register:匹配次设备号->找到一个没有占用的次设备号(如果需要动态分配的话)->计算设号->创建设备文件-miscdevice结构体添加到misc_list链表中
卸载:
    调用misc_deregister:从mist_list中删除miscdevice->删除设备文件->释放占用的设备号资源

6.2、struct file_operations结构体
(1)此结构体在linux源码中的位置:/kernel-4.14/include/linux/fs.h
(2)结构体成员详细定义

struct file_operations {
 	struct module *owner;//THIS_MODULE,常用
 	loff_t (*llseek) (struct file *, loff_t, int);//应用层llseek时对应的驱动操作
 	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//应用层read时对应的驱动操作,常用
 	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//应用层write时对应的驱动操作,常用
 	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
 	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
 	int (*iterate) (struct file *, struct dir_context *);
 	int (*iterate_shared) (struct file *, struct dir_context *);
 	unsigned int (*poll) (struct file *, struct poll_table_struct *);
 	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
 	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
 	int (*mmap) (struct file *, struct vm_area_struct *);
 	int (*open) (struct inode *, struct file *);//应用open设备节点时对应的驱动动作,常用
 	int (*flush) (struct file *, fl_owner_t id);
 	int (*release) (struct inode *, struct file *);
 	int (*fsync) (struct file *, loff_t, loff_t, int datasync);//异步通知
 	int (*fasync) (int, struct file *, int);
 	int (*lock) (struct file *, int, struct file_lock *);
 	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
 	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
 	int (*check_flags)(int);
 	int (*flock) (struct file *, int, struct file_lock *);
 	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
 	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
 	int (*setlease)(struct file *, long, struct file_lock **, void **);
 	long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
 	void (*show_fdinfo)(struct seq_file *m, struct file *f);
 #ifndef CONFIG_MMU
 	unsigned (*mmap_capabilities)(struct file *);
 #endif
 	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);
 	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);
 	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
 } __randomize_layout;

6.3、由于杂项设备实际上也是一种字符设备,只不发对其做了特殊封装。因此本文也简单说明一下字符设备注册的操作,详细说明待后续单独写文档说明。
(1)字符类设备号
    dev_t 高12位为主设备号,低20位为次设备号
    创建设备号:
    dev_t dev = MKDEV(major,minor)
    提取:
    MAJOR(dev)MINOR(dev)
(2)字符类设备号注册
    a)注册多个不同的主设备号的字符设备,次设备号为0
    int register_chrdev_region(dev_t from, unsigned count, const char *name);
    b)注册同一主设备号的不同设备,必须使用次设备号区分
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
(3)字符类设备号注销
    void unregister_chrdev_region(dev_t from, unsigned count);
(4)字符类设备结构体
        字符类结构体:
    struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
(5)注册与注销常用的两个函数
    int cdev_add(struct cdev *p, dev_t dev, unsigned count);
    void cdev_del(struct cdev *p);
(6)生成设备节点(两种方法)
    a)内核函数生成
    //先创建class结构体
    struct class *class_create(struct module *owner,const char *name);
    //class_create本身为一个宏定义实质调用__class_create。
    对应void class_destroy(struct class *cls);
    //创建设备节点注册到文件系统
    struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
    //调用device_create(class,NULL,设备号,NULL,设备名)设备名可以为“name.%d"类似printf
    对应注销void device_destroy(struct class *class, dev_t devt);
    
    b)mknod命令
    mknod 设备名 设备类型(字符:c,块:b) 主设备号 从设备号

注:

7、参考文献连接:

https://blog.csdn.net/nanhangfengshuai/article/details/50533230

https://www.cnblogs.com/haimeng2010/p/3582403.html

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只特立独行的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值