之前我们说过,Linux设备主要分为三类:字符设备、块设备和网络接口。
字符设备相对于另外两个设备更加容易理解,同时,这类设备也适合大多数简单的硬件设备,因此,接下来我们学习一下字符设备驱动。
字符驱动的学习分为两节:先介绍一下字符驱动驱动中的一些概念,这些概念有些在块设备中也是相同的;然后基于系统的内存创建一个字符设备实例。
本节主要介绍一下驱动中的一些概念,主要学会以下内容:
- 设备号及其相关操作
- 文件操作相关的数据结构
- 文件相关的数据结构
- 节点相关的数据结构
1. 设备号及其相关操作
字符设备通常位于 /dev
目录下,通过设备的名称进行访问,这些名称称为设备文件或节点。在 /dev
目录下,使用 ls -l
命令来查看系统中的设备情况,部分结果如下:
在每行的最前面的有一个字符(c
、d
、l
、b
等),其中,c
开头的设备表示的就是字符设备。(类似的,b
开头的设备表示块设备、d
表示是一个目录、l
表示是一个连接)
截图中的字符设备为 btrfs-control
,具体信息如下:
crw------- 1 root root 10, 234 7月 25 10:45 btrfs-control
其中的:10,234
表示的就是设备的设备号,10
为主设备号(misc
字符设备),234
为次设备号。
主设备号用于表示设备对应的驱动程序。Linux 内核允许多个驱动程序共享主设备号,但许多设备仍然按照一个设备号对应一个驱动程序的原则组织;次设备号由内核使用,用于正确确定设备文件所指的设备,利用次设备号可以获取一个指向内核设备的直接指针,也可以将次设备号当做设备本地数组的索引。
在 <linux/types.h>
头文件中提供了一个 dev_t
类型来保存设备号——在2.6.0内核中,dev_t
类型是32位的,其中的12位用来表示主设备号,剩余20位用来表示次设备号。但我们不要默认永远都是这样实现的,而应该利用 <linux/kdev_t.h>
中定义的两个宏来获取 dev_t
所表示的设备号:
MAJOR(dev_t dev); //返回主设备号
MINOR(dev_t dev); //返回次设备号
同样,当知道主设备号和次设备号时,也不要自己手动去拼接生成 dev_t
变量,而应使用以下宏实现:
MKDEV(int major, int minor); //返回dev_t变量
在驱动程序中,对于设备号的相关操作主要是分配和释放,操作函数都在 <linux/fs.h>
中进行了声明,主要有以下几个函数:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);
对其中的参数进行说明:
-
register_chrdev_region
:first
是要分配的设备编号的起始值;count
是请求的连续设备编号的个数;name
是和该设备号关联的设备名称,它将出现在/proc/devices
和sysfs
中。 -
alloc_chrdev_region
:dev
用于输出分配的设备编号;firstminor
为要是用的被请求的第一个次设备号,通常为0;count
和name
同register_chrdev_region
。 -
unregister_chrdev_region
:first
和count
同register_chrdev_region
。
register_chrdev_region
看名字就知道是注册设备号,这个函数适用于注册所需设备编号已知的情况下;在大多数情况下,我们是不知道设备编号的,可以使用 alloc_chrdev_region
来分配设备编号;unregister_chrdev_region
看名字也知道,是释放注册的设备编号,一般在退出函数中调用。
对于 register_chrdev_region
和 alloc_chrdev_region
,强烈建议使用 alloc_chrdev_region
来分配设备编号,这样可以使得驱动程序适应性更强。
分配设备编号是驱动代码需要完成的第一件事,后续还需要将内部函数和设备编号连接起来。
2. 文件操作相关的数据结构
文件操作相关的数据结构主要是结构体 struct file_operations
,其定义在 <linux/fs.h>
中。
该结构体包含了一组函数指针,用于将内部函数和上面的设备编号连接起来。
该结构体的具体内容请自行查看源码,下面对平常用得比较多的几个函数进行说明:
struct file_operations {
struct module *owner;
......
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
......
__poll_t (*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 (*release) (struct inode *, struct file *);
......
} ;
-
owner
是一个模块指针,指定该结构体的拥有者模块,通常被初始化为THIS_MODULE
; -
read
:从设备中读取数据,返回负值表示读取出错,返回非负值表示读取到了多少字节; -
write
:往设备中写入数据,返回写入成功的字节数,返回负数表示写入出错; -
poll
:是poll
、epoll
、select
这三个系统调用的后端实现,用于实现IO的多路复用; -
unlocked_ioctl
和compat_ioctl
:主要是unlocked_ioctl,用于系统调用ioctl执行设备的特定命令,而compat_ioctl
主要用于32位应用程序在64位的操作系统中时执行,一般不用管; -
mmap
:用于请求将设备内存映射到进程地址空间; -
open
:设备文件执行的第一个操作,定义为NULL
表示打开设备永远成功; -
release
:当file结构体被释放时调用。
目前只要知道有这个结构体即可,可以在后续驱动中需要使用的时候再去深入了解。
3. 文件相关的数据结构
文件相关的数据结构主要是 struct file
结构体,其定义在 <linux/fs.h>
中。
该结构体表示一个打开的文件,它由内核在 open
时创建,因此系统中每个打开的文件、设备都有一个对应的 file
结构体。在文件的所有实例都被关闭后,内核才会释放这个结构体。
具体内容查看源码,下面对比较重要的属性进行说明:
struct file {
......
const struct file_operations *f_op;
......
unsigned int f_flags;
fmode_t f_mode;
......
loff_t f_pos;
......
void *private_data;
......
}
-
f_op
:文件操作相关。内核在执行open
操作的时候对这个指针赋值,以后需要处理这些操作的时候读取这个指针; -
f_flags
:文件标志,如O_RDONLY
、O_NONBLOCK
和O_SYNC
。主要是用来检查O_NONBLOCK
标志,其他标志很少用。检查读写权限不是用这个变量,而是需要使用下面的f_mode
; -
f_mode
:文件模式。用来标识文件是否可读或可写(或可读写); -
f_pos
:表示当前的读写位置,是一个long long
型的变量,可读,但不要去直接去修改它; -
private_data
:是一个void
型的指针,可以忽略,也可以根据需要用于存储需要的数据,有时候非常有用。
同样,这个结构体目前只需要了解即可,后续如果有需要再深入了解。
4. 节点相关的数据结构
上面介绍了 struct file_operations
结构体表示文件操作相关的结构体,struct file
结构体表示打开的文件。下面介绍的 struct inode
结构体表示的是文件(节点)。该结构体同样位于 <linux/fs.h>
中。
struct inode
中包含了大量的有关文件的信息,其中对于驱动程序编写非常重要的主要有以下几个:
struct inode {
......
dev_t i_rdev;
......
union {
......
struct cdev *i_cdev;
......
};
......
};
-
i_rdev
:包含了设备编号; -
i_cdev
:当inode
指向一个字符设备时,该字段包含了指向struct cdev
结构体的指针。
以上是本节的主要内容,下一节将会编写一个字符设备实例。