1、字符设备驱动简介
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节 流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
如果在之前有学习RTOS的相关知识在这方面应该非常好理解,也就多了些IO文件操作而已。
在 Linux
中一切皆为文件,驱动加载成功以后会在“
/dev
”目录下生成一个相应的文件,应
用程序通过对这个名为“
/dev/xxx
”
(xxx
是具体的驱动文件名字
)
的文件进行相应的操作即可实
现对硬件的操作。比如现在有个叫做
/dev/led
的驱动文件,此文件是
led
灯的驱动文件。应用程
序使用
open
函数来打开文件
/dev/led
,使用完成以后使用
close
函数关闭
/dev/led
这个文件。
open 和 close
就是打开和关闭
led
驱动的函数,如果要点亮或关闭
led
,那么就使用
write
函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led
的控制参数。如果要获取
led
灯的状态,就用
read
函数从驱动中读取相应的状态。
应用程序运行在用户空间,而
Linux
驱动属于内核的一部分,因此驱动运行于内核空间。
当我们在用户空间想要实现对内核的操作,比如使用
open
函数打开
/dev/led
这个驱动,因为用
户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空
间“陷入”到内核空间,这样才能实现对底层驱动的操作。
如下是我们在编写字符驱动中的一个
最具有核心的函数,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做
file_operations 的结构体,
此结构体就是 Linux 内核驱动操作函数集合。
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 *);
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 *);
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);
};
简单介绍一下
file_operation
结构体中比较重要的、常用的函数:
owner
拥有该结构体的模块的指针,一般设置为
THIS_MODULE
。
llseek
函数用于修改文件当前的读写位置。
read
函数用于读取设备文件。
write
函数用于向设备文件写入
(
发送
)
数据。
poll
是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
unlocked_ioctl
函数提供对于设备的控制功能,与应用程序中的
ioctl
函数对应。
compat_ioctl
函数与
unlocked_ioctl
函数功能一样,区别在于在
64
位系统上,
32
位的应用程序调用将会使用此函数。在
32
位的系统上运行
32
位的应用程序调用的是
unlocked_ioctl
。
mmap
函数用于将设备的内存映射到进程空间中
(
也就是用户空间
)
,一般帧缓
冲设备会使用此函数,比如
LCD
驱动的显存,将帧缓冲
(LCD
显存
)
映射到用户空间中以后应用
程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
open
函数用于打开设备文件。
release
函数用于释放
(
关闭
)
设备文件,与应用程序中的
close
函数对应。
fasync
函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
aio_fsync
函数与
fasync
函数的功能类似,只是
aio_fsync
是异步刷新待处理的
数据。
在字符设备驱动开发中最常用的就是上面这些函数,关于其他的函数大家可以查阅相关文
档。我们在字符设备驱动开发中最主要的工作就是实现上面这些函数,不一定全部都要实现,
但是像 open、release、write、read 等都是需要实现的,当然了,具体需要实现哪些函数还是要
看具体的驱动要求。
2、字符设备驱动开发基础
直接观察下述代码即可。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
static int chrtest_open (struct inode *inodp, struct file *filp)
{
return 0;
}
static ssize_t chrtest_read (struct file *filp, char __user *buf,
size_t, loff_t *offt)
{
return 0;
}
static ssize_t chrtest_write (struct file *filp, const char __user *buf,
size_t, loff_t *offt)
{
return 0;
}
static int chrtest_release (struct inode *inodp, struct file *filp)
{
return 0;
}
static struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};
static int __init xxx_init(void){
int retvalue = 0;
//设备号 名字 字符模型驱动的一个结构体
retvalue = register_chrdev(200,"chrtest",&test_fops);
if(retvalue < 0){
// exit
}
return 0;
}
static void __exit xxx_exit(void){
unregister_chrdev(200,"chrtest");
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("7yewh");
代码架构很简单,相信都能看懂,就不做具体解析了。