目录
4.2.2、unregister_chrdev_region()函数
6.2.1、register_chrdev_region()函数
6.2.3、unregister_chrdev_region
1、字符设备驱动
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备是按照字节流进行读写操作的设备。比如灯、按键、 IIC、 SPI等都是字符设备,这些设备的驱动就叫做字符设备驱动。
2、应用程序调用驱动程序流程
应用程序->库函数->内核->驱动程序->硬件。
3、file_operations结构体
file_operations结构体是 Linux 内核驱动操作函数集合(定义在include/linux/fs.h中)。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
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 *);
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 (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
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 (*aio_fsync) (struct kiocb *, 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
};
3.1、常用函数
函数 | 说明 |
owner | 指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在<linux/module.h> 中定义的宏。 |
open | 用于打开设备文件。 |
release | 函数用于释放(关闭)设备文件, 注:与应用程序中的 close 函数对应。 |
read | 用于读取设备文件。 |
write | 用于向设备文件写入(发送)数据。 |
llseek | 用于修改文件当前的读写位置。 注:如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器。 |
mmap | 用于将设备的内存映射到进程空间中 |
poll | poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞。 |
fasync | 用于刷新待处理的数据,将缓冲区中的数据刷新到磁盘中。 |
aio_fsync | 与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。 |
unlocked_ioctl | 提供对于设备的控制功能 注:与应用程序中的 ioctl 函数对应。 |
compat_ioctl | 与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。 |
4、Linux 设备号
Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成。
Linux 提供了一个名为 dev_t 的数据类型表示设备号(定义在include/linux/types.h)。dev_t 是一个 32 位的数据类型(高 12 位为主设备号, 低 20 位为次设备号)。
4.1、静态分配设备号
静态分配设备号需要检查当前系统中所有被使用了的设备号,然后选择一个未被使用的设备号(头文件路径include/linux/kdev_t.h)。
注:有一些常用的设备号已经被 Linux 内核开发者给分配掉了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉的主设备号就不能用了,具体能不能用还得看的硬件平台运行过程中有没有使用这个主设备号!!!
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
宏 | 说明 |
MINORBITS | 次设备号位数 |
MINORMASK | 次设备号掩码 |
MAJOR | 用于从 dev_t 中获取主设备号 |
MINOR | 用于从 dev_t 中获取次设备号 |
MKDEV | 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号 |
4.2、动态分配设备号
静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动分配没有被使用的设备号,这样就避免了冲突。
4.2.1、alloc_chrdev_region()函数
用于动态申请设备号。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
参数 dev:保存申请到的设备号。
参数 baseminor: 次设备号起始地址。alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同。
参数 count: 要申请的设备号数量。
参数 name:设备名字。
返回值:成功返回0。
4.2.2、unregister_chrdev_region()函数
用于释放动态申请的设备号。
void unregister_chrdev_region(dev_t from, unsigned count);
参数 from:要释放的设备号。
参数 count: 表示从 from 开始,要释放的设备号数量。
返回值:成功返回0。
5、旧字符设备
1)当驱动模块加载成功以后需要注册字符设备;
2)当卸载驱动模块的时候需要注销掉字符设备。
5.1、查看当前注册的设备号
cat /proc/devices
5.2、register_chrdev()函数
register_chrdev()函数用于注册字符设备。
注:一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行。
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
参数 major: 主设备号。
参数 name:设备名字。
参数 fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。
返回值:成功返回0(当返回值小于0,表示操作失败)。
5.3、unregister_chrdev()函数
unregister_chrdev()函数用户注销字符设备。
注:字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。
static inline void unregister_chrdev(unsigned int major, const char *name);
参数 major: 要注销的设备对应的主设备号。
参数 name: 要注销的设备对应的设备名。
返回值:成功返回0(当返回值小于0,表示操作失败)。
5.4、创建设备节点
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序通过操作这个设备节点文件来完成对具体设备的操作。
mknod [-m MODE] NAME TYPE [MAJOR MINOR]
6、新字符设备
register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数!!!
6.1、旧字符设备问题
使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题:
1)需要事先确定好哪些主设备号没有使用。
2)会将一个主设备号下的所有次设备号都使用掉。
6.2、设备注册和卸载
6.2.1、register_chrdev_region()函数
如果给定了设备的设备号就使用此函数来注册设备号。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数 from:要申请的起始设备号;
参数 count:要申请的数量;
参数 name:设备名字。
返回值:成功返回0。
6.2.2、alloc_chrdev_region()函数
如果没有指定设备号的话就使用如下函数来申请设备号。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
参数 dev:保存申请到的设备号。
参数 baseminor: 次设备号起始地址。
参数 count: 要申请的设备号数量。
参数 name:设备名字。
返回值:成功返回0(当返回值小于0,表示操作失败)。
6.2.3、unregister_chrdev_region
不 管 是 通 过 alloc_chrdev_region 函 数 还 是register_chrdev_region 函数申请的设备号,统一使用此释放函数。
void unregister_chrdev_region(dev_t from, unsigned count);
参数 from:要释放的设备号。
参数 count: 表示从 from 开始,要释放的设备号数量。
返回值:成功返回0(当返回值小于0,表示操作失败)。
6.3、cdev
6.3.1、cdev 结构体
编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备。
注:头文件路径为<linux/cdev.h>。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
6.3.2、cdev_init()函数
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化。
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
参数 cdev:要初始化的 cdev 结构体变量
参数 fops :字符设备文件操作函数集合。
6.3.3、cdev_add ()函数
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
参数 p:指向要添加的字符设备(cdev 结构体变量);
参数 dev:就是设备所使用的设备号;
参数 count:是要添加的设备数量。
注:alloc_chrdev_region/register_chrdev_region,cdev_init ,cdev_add ,合起来相当于register_chrdev函数。
6.3.4、cdev_del()函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备。
void cdev_del(struct cdev *p);
参数 p:就是要删除的字符设备。
注:cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于unregister_chrdev 函数。
7、自动创建设备节点
在驱动中实现自动创建设备节点的功能以后,加载驱动模块成功的话就会自动在/dev 目录下创建对应的设备文件。
7.1、udev/mdev
udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检
测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用
modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用
rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。
mdev是udev 的简化版本,在嵌入式 Linux 中使用mdev 来实现设备节点文件的自动创建与删除。
7.2、classs
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。
注:头文件路径<linux/device.h>
7.2.1、class_create()函数
类创建函数, class_create 是个宏定义
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
参数 owner:一般为 THIS_MODULE。
参数 name:类名字。
返回值:创建的类。
7.2.2、class_destroy()函数
用于删除类。
void class_destroy(struct class *cls);
参数 cls:要删除的类。
7.3、device
创建好类以后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。
注:头文件路径<linux/device.h>
7.3.1、device_create()函数
用于创建设备。
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
参数 class:要在哪个类下面创建;
参数 parent:父设备,一般为 NULL(没有父设备);
参数 devt:设备号;
参数 drvdata:设备可能会使用的一些数据,一般为 NULL;
参数 fmt:设备名字(如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件)。
返回值:创建的设备。
7.3.2、device_destroy()函数
卸载驱动的时候需要删除掉创建的设备。
void device_destroy(struct class *class, dev_t devt)
参数 class:是要删除的设备所处的类。
参数 devt:是要删除的设备号。