Linux字符设备驱动框架
1、驱动模块需要实现的函数
在Linux系统中,一切皆文件。应用层通过open,read,write,close实现对驱动的调用。
每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中
有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,如下所示:
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
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 (*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);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (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 *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
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 **);
};
因此我们在驱动程序里面实现file_operations的各个函数,就可以在应用层实现调用。当然也不是每一个驱动都需要实现file_operations的全部接口。
实现字符驱动步骤:
1、实现 open,read,write,release函数
/* 打开设备 */
static int Demo_open(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
return 0;
}
/* 从设备读取 */
static ssize_t Demo_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
/* 用户实现具体功能 */
return 0;
}
/* 向设备写数据 */
static ssize_t Demo_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
/* 用户实现具体功能 */
return 0;
}
/* 关闭/释放设备 */
static int Demo_release(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
return 0;
}
2、定义自己的file_operations结构体
static struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = Demo_open,
.read = Demo_read,
.write = Demo_write,
.release = Demo_release,
};
3、字符设备的注册与注销
我们有自己的file_operations结构体以后,需要注册到内核里面去,才能被应用层调用。
字符设备的注册和注销函数原型如下所示:
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
参数含义:
major: 主设备号, Linux 下每个设备都有一个设备号
name:设备名字,指向一串字符串。
fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。
/* 驱动入口函数 */
static int __init demo_init(void)
{
/* 入口函数具体内容 */
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(0, "chrdev_demo", &demo_fops);
if(retvalue < 0){
/* 字符设备注册失败,自行处理 */
}
return 0;
}
/* 驱动出口函数 */
static void __exit demo_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(0, "chrdev_demo");
}
4、实现驱动指定的入口和出口函数
我们实现了驱动模块的入口函数与出口函数,但是Linux怎么才能知道这个是一个驱动的入口函数与出口函数呢?
使用module_init与module_exit函数告诉编译器。
module_init(demo_init); //注册模块加载函数
module_exit(demo_exit); //注册模块卸载函数
5、添加license信息
GPL 协议的传染性,LICENSE 必须采用 GPL 协议,不然不能使用
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wpgulang");
6、使用Makefile编译成.ko文件
KERNELDIR := /home/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,根据自己的实际情况填写即可。
CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取当前所处路
径。
obj-m 表示将 chrdevdemo.c 这个文件编译为 chrdevdemo.ko 模块。
7、驱动模块的加载和卸载
使用命令 insmod加载驱动,使用rmmod卸载驱动。
insmod chrdevdemo.ko
rm chrdevdemo.ko