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