参考:正点原子,驱动开发指南
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的
具体函数,当使用“insmod”命令加载驱动的时候,xxx_init 这个函数就会被调用。module_exit()
函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使
用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。字符设备驱动模块加载和卸载模板如下所示:
1/* 驱动入口函数 */
2 static int __init xxx_init(void)
3 {
4 /* 入口函数具体内容 */
5 return 0;
6 }
7
8 /* 驱动出口函数 */
9 static void __exit xxx_exit(void)
10 {
11 /* 出口函数具体内容 */
12 }
13
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模
块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:
1 static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
2 static inline void unregister_chrdev(unsigned int major, const char *name)
register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块
的出口函数 xxx_exit 中进行
示例代码 40.2.2.1 加入字符设备注册和注销
1 static struct file_operations test_fops;
2
3 /* 驱动入口函数 */
4 static int __init xxx_init(void)
5 {
6 /* 入口函数具体内容 */
7 int retvalue = 0;
8
9 /* 注册字符设备驱动 */
10 retvalue = register_chrdev(200, "chrtest", &test_fops);
11 if(retvalue < 0){
12 /* 字符设备注册失败, 自行处理 */
13 }
14 return 0;
15 }
16
17 /* 驱动出口函数 */
18 static void __exit xxx_exit(void)
19 {
20 /* 注销字符设备驱动 */
21 unregister_chrdev(200, "chrtest");
22 }
23
24 /* 将上面两个函数指定为驱动的入口和出口函数 */
25 module_init(xxx_init);
26 module_exit(xxx_exit);
file_operations 结构体就是设备的具体操作函数,在示例代码 40.2.2.1 中我们定义了
file_operations结构体类型的变量test_fops,但是还没对其进行初始化,也就是初始化其中的open、
release、read 和 write 等具体的设备操作函数。本节小节我们就完成变量 test_fops 的初始化,设
置好针对 chrtest 设备的操作函数。在初始化 test_fops 之前我们要分析一下需求,也就是要对
chrtest 这个设备进行哪些操作,只有确定了需求以后才知道我们应该实现哪些操作函数
struct file_operations {
struct module *owner;
//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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
//compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,
//32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是
//unlocked_ioctl。
int (*mmap) (struct file *, struct vm_area_struct *);
//mmap 函数用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧
//缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应
//用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
int (*open) (struct inode *, struct file *);
//open 函数用于打开设备文件。
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
//release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
//fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
int (*aio_fsync) (struct kiocb *, int datasync);
//aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的
//数据。
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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
/* get_lower_file is for stackable file system */
struct file* (*get_lower_file)(struct file *f);
};
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分
组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux 提供了
一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,定义如下
本小节讲的设备号分配主要是主设备号的分配。前面讲解字符设备驱动的时候说过了,注
册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个
设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉
了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉
的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个
主设备号,使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号。
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
void unregister_chrdev_region(dev_t from, unsigned count)
#include
#include
#include
#include
#include
#include
/***************************************************************
Copyright © XXXXXXX, Ltd. 1998-2029. All rights reserved.
文件名 : chrdevbase.c
作者 :
版本 : V1.0
描述 : chrdevbase驱动文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2020/5/26 仇嘉斌创建
***************************************************************/
#define CHRDEVBASE_MAJOR 200 /* 主设备号 */
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
static char readbuf[100]; /* 读缓冲区 */
static char writebuf[100]; /* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
//printk("chrdevbase open!\r\n");
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue = 0;
/* 向用户空间发送数据 */
memcpy(readbuf, kerneldata, sizeof(kerneldata));
retvalue = copy_to_user(buf, readbuf, cnt);
if(retvalue == 0){
printk("kernel senddata ok!\r\n");
}else{
printk("kernel senddata failed!\r\n");
}
//printk("chrdevbase read!\r\n");
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue = 0;
/* 接收用户空间传递给内核的数据并且打印出来 */
retvalue = copy_from_user(writebuf, buf, cnt);
if(retvalue == 0){
printk("kernel recevdata:%s\r\n", writebuf);
}else{
printk("kernel recevdata failed!\r\n");
}
//printk("chrdevbase write!\r\n");
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
//printk("chrdevbase release!\r\n");
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
if(retvalue < 0){
printk("chrdevbase driver register failed\r\n");
}
printk("chrdevbase init!\r\n");
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit chrdevbase_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
printk("chrdevbase exit!\r\n");
}
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiabinly");