Linux驱动开发:字符设备驱动开发
Posted Top嵌入式
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发:字符设备驱动开发相关的知识,希望对你有一定的参考价值。
文章目录
Linux驱动开发:字符设备驱动开发
一、字符设备简介
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI、LCD 等都是字符设备,这些设备的驱动就叫做字符设备驱动,字符设备的调用按照驱动的调用方式进行
二、驱动调用原理
驱动调用框架如下:
上层应用距离底层硬件间隔了库调用、内核调用、驱动调用三层,因为 Linux 一切皆为文件,对字符串驱动的调用就是以操作文件的形式完成,驱动加载成功以后会在 “/dev” 目录下生成一个相应的文件,对外设的操作最终会调用相关的驱动文件操作操作该文件,以打开文件函数 open 为例子:
应用程序调用 C 库后,会调用系统调用进入内核态,调用底层驱动,底层驱动有一个专门的结构体服务:
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
;
此结构体就是 Linux 内核驱动操作函数集合,几个常见的参数:
- owner 是指向拥有该结构体的模块的指针
- read 函数用于读取设备文件
- write 函数用于向设备文件写入(发送)数据
- open 函数用于打开设备文件
- release 函数用于释放(关闭)设备文件
其他函数使用时再具体查看
三、驱动加载与卸载
Linux 驱动两种运行方式:
- 驱动编译进 Linux 内核中运行
- 驱动编译成模块,驱动编译成模块
一般调试使用模块化的运行方式,模块的加载和卸载需要下面的函数:
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
module_init 函数用来向 Linux 内核注册一个模块加载函数,module_exit 则是卸载,当我们在命令行输入加载和卸载指令(insmod 和 rmmod)时底层调用的就是这两个函数
驱动编译完成以后扩展名为 .ko,insmod 命令用于加载指定的 .ko 模块,但 insmod 命令不能解决模块的依赖关系,而 modprobe 指令会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,卸载则使用 rmmod,对 modprobe 来说,卸载使用 modprobe -r
四、字符设备注册与注销
字符设备的注册和注销函数如下:
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 类型指针,指向设备的操作函数集合变量
五、设备驱动编写流程
上面提到的 file_operations 结构体就是设备的具体操作函数,我们要定义一个结构体用于字符串操作:
static struct file_operations test_fops =
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
;
将结构体的操作函数指针指向对应的底层驱动函数(由我们自己实现),以打开函数为例:
/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
/* 用户实现具体功能 */
return 0;
设置好底层驱动函数后,编写函数驱动进入和退出入口,注册入口函数,以入口函数为例子:
static int __init xxx_init(void)
/* 入口函数具体内容 */
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0)
/* 字符设备注册失败,自行处理 */
return 0;
//注册入口函数
module_init(xxx_init);
驱动代码流程大致如上
六、添加驱动信息
最后需要在驱动中加入 LICENSE 信息和作者信息,函数如下:
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
例如:
MODULE_LICENSE("MIT") //MIT协议
MODULE_AUTHOR("Top嵌入式") //作者信息
其中 LICENSE 是必须添加的,Linux 内核编译时会检测,没有则会报错
以上是关于Linux驱动开发:字符设备驱动开发的主要内容,如果未能解决你的问题,请参考以下文章