前言
linux内核将字符设备抽象成一个具体的数据结构,可以理解为字符设备对象,这篇博客就来讲解一下字符设备驱动的关键结构体。
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
目录
- 前言
- 1. 字符设备介绍
- 2. 虚拟文件系统(VFS)
- 3. file_operation结构体
- 3.1 介绍
- 3.2 常用函数
- 3.3 实现模板
- 3. inode结构体
- 3.1 介绍
- 3.2 实际使用
- 4. file结构体
- 4.1 介绍
- 4.2 实际使用
- 参考资料
1. 字符设备介绍
字符设备是Linux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作,读写数据分先后顺序。常见的比如操作IO口输入输出,I2C,SPI,LCD等均为字符设备。
Linux应用程序对驱动程序的调用如下图所示:
应用程序在用户空间,而Linux驱动属于内核的一部分,运行于内核空间,如果想在用户空间对内核空间进行操作,那么就需要用系统调用实现从用户空间陷入到内核空间。例如调用open()函数,其本身是C库的一部分,调用流程如下:
应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比如应用程序中调用了 open
这个函数,那么在驱动程序中也得有一个名为 open
的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件include/linux/fs.h
中有个叫做 file_operations
的结构体,此结构体就是 Linux 内核驱动操作函数集合。
2. 虚拟文件系统(VFS)
众所周知,Linux有一个很厉害的思想叫:“一切皆文件”,即在Linux下,你不管是常规的txt,pdf等用open函数打开,对字符设备(LCD,IO口等)、块设备、网络设备等也当做文件,通过open函数打开,write函数写入指令或数据,read函数读取其中的数据等。
在此基础上,虚拟文件系统(Virtuial File System,VFS)便应运而生了,它是Linux系统中的一个软件抽象层,是实现“一切皆文件”的关键。它为用户空间的程序提供了文件系统接口,同时定义了所有文件系统都支持的基本的、概念上的接口和数据结构。例如,读写普通的文本文件和读写I/O设备的具体实现方法必然是不同的,但VFS提供了统一的接口read和write,开发人员需要编写这些接口的具体的不同的实现,个人感觉有点像面向对象中的多态。因此,在VFS层和内核的其他部分看来,所有文件都只需调用read和write函数就可以完成读写功能,具体的实现过程它们并不关心。
3. file_operation结构体
3.1 介绍
file_operation
是将系统调用和驱动程序关联起来的关键数据结构,其每个成员都对应一个系统调用。读取file_operation
中相应的函数指针,接着把控制权交给函数指针指向的函数,完成Linux设备驱动的工作,其在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 (*iopoll)(struct kiocb *kiocb, bool spin);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);__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 *);unsigned long mmap_supported_flags;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_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,struct file *file_out, loff_t pos_out,loff_t len, unsigned int remap_flags);int (*fadvise)(struct file *, loff_t, loff_t, int);
};
以open
函数为例,不同设备的打开方式不同,因此open函数的实现显然应该是不同的,但从VFS层面上看,都是只需要调用open函数就可以满足打开设备的需求。
3.2 常用函数
常用的函数如下,不一定全部都要实现,但是像open、release、write、read等都是需要实现的:
owner
拥有该结构体的模块的指针,一般设置为 THIS_MODULE
read
函数用于读取设备文件
write
函数用于向设备文件写入(发送)数据
open
函数用于打开设备文件
release
函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
unlocked_ioctl
函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应
compat_ioctl
函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
mmap
函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
llseek
函数用于修改文件当前的读写位置
poll
是个轮询函数,用于查询设备是否可以进行非阻塞的读写
fasync
函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
aio_fsync
函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据
3.3 实现模板
比如一个led的驱动,我们想对led进行控制,那么可以写一个write函数用来设置led对应io口的电平高低:
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{// 设置对应gpio电平的代码return 0;
}
写好后将file_operation
结构体中的write
函数变为led_write
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.write = led_write,
};
不过要注意的是,由于内核空间不能操作用户空间的数据,因此需要用copy_from_user
函数将用户空间的数据拷贝到内核空间中。
3. inode结构体
3.1 介绍
inode
是一种存储文件元信息的区域,包括文件的创建者,文件创建日期,文件大小等(但不包含文件名)。此外,其通常还涉及block
,block
就是存储文件数据的地方。
拓展一下文件的存储:文件是存储在硬盘上的,硬盘的最小存储单位叫做扇区 sector,每个扇区存储512字节。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块block。这种由多个扇区组成的块,是文件存取的最小单位。块的大小,最常见的是4KB,即连续八个sector组成一个block。文件数据存储在块中,那么还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等,这种存储文件元信息的区域就叫做inode。
inode中主要存储以下这些元数据:
- inode编号
- 文件大小
- 占用的块数目与块大小
- 文件类型(普通文件、目录、管道,etc.)
- 存储该文件的设备号
- 链接数目
- 读、写、执行权限
- 拥有者的用户ID和组ID
- 文件的最近访问、数据最近修改时间
- inode最近修改时间
inode编号就相当于你的身份证,是操作系统唯一标识一个文件的手段,通过stat指令可以查看数据信息:
3.2 实际使用
在实际使用中,通常是作为open,release等函数的参数。因为打开和关闭文件的时候,也需要更新inode的信息,比如访问时间,文件大小等等信息:
static int led_open(struct inode *inode, struct file *filp) {}static int led_release(struct inode *inode, struct file *filp) {}
4. file结构体
4.1 介绍
file结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file
。由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。其有两个很重要的字段:文件描述符和缓冲区。
file
结构体依赖于底层的inode
结构体。其于inode的区别在于,inode不跟踪文件的当前位置和当前模式,只是帮助操作系统找到底层文件结构的内容(管道,目录,常规磁盘文件,块/字符设备文件等);file结构体是一个基本结构,但也有一个指向inode的指针,代表打开的文件,并提供一组函数,它们与底层文件结构上执行的方法相关,这些方法包括open、write、read等。
总结下来就是inode代表内核中的文件,file代表实际打开的文件。若一个文件打开多次,会有不同的file,但都指向同一个inode。
4.2 实际使用
在实际使用中,通常是作为open,read,write,release等函数的参数,因为file结构体是代表打开的文件,因此进行操作的时候都需要加上它来修改信息:
static int led_open(struct inode *inode, struct file *filp) {}static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {}static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {}static int led_release(struct inode *inode, struct file *filp) {}
参考资料
[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第四十章
[2] Linux中的File_operations结构体
[3] linux驱动开发(二):Linux字符设备驱动程序(设备号、cdev、设备节点、file_operations)
[4] inode详解
[5] 问:说说inode到底是什么?
[6] Linux file结构体