Linux设备驱动概述
- 操作系统内核是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备。
- 设备驱动程序是操作系统内核和机器硬件之间的接口,系统调用是操作系统内核和应用程序之间的接口。
- 在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作.
Linux下设备可以分为三种:
- 字符设备:数据的传输是以字节流的形式传输,如键盘、鼠标、触摸屏、摄像头,LCD显示屏等等。
- 块设备:数据是以块为单位传输的。如硬盘、U盘等存储设备。
- 网络设备:网络是linux内核的一大功能模块,网络设备在内核总独立成为一类设备。提供专用API(socket编程)。
Linux系统中,应用程序访问外设是通过文件的形式来进行的,Linux将所有的外设都看做文件,统一存放在/dev目录下。
应用程序使用内核提供的标准系统调用来与内核中的驱动程序进行通讯,这些系统调用有:
open(), read(), write(), ioctl(), close() 等等。
linux如何管理文件
- Linux把设备纳入文件系统的范畴来管理。
- 每个设备在Linux系统上看起来都像一个文件,它们存放在/dev目录中,称为"设备节点"。
-
每当用户程序要访问某个设备时,通过系统调用,内核根据设备结点的信息调用相应的驱动程序。当驱动程序执行完后,又返回至用户进程。
Linux下设备的属性
- 设备的类型:字符设备、块设备、网络设备;
- 主设备号:标识设备对应的驱动程序。一般“一个主设备号对应一个驱动程序”
- 次设备号:每个驱动程序负责管理它所驱动的几个硬件实例,这些硬件实例则由次设备号来表示。同一驱动下的实例编号,用于确定设备文件所指的设备。
- 文件名:设备文件名字。
设备号
- 设备文件的主、次设备号用于表示一个硬件设备;
- 对于每个类型的设备,其主、次设备号都有相关的规定说明;
- 在内核源码里面的Documentation\devices.txt说明文件中,就规定了各种设备的主、次设备号。
一些重要的数据结构
- 大部分驱动程序涉及三个重要的内核数据结构:
文件操作file_operations结构体
- 结构体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 *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
......
}
file_operations重要的成员
owner : 指向拥有该结构体的模块的指针,内核使用该指针维护模块使用计数。
llseek : 用来修改文件的当前读写位置,把新位置作为返回值返回,loff_t是在LINUX中定义的长偏移量 .
read : 用来从设备中读取数据。非负返回值表示成功读取的直接数。
write : 向设备发送数据。
ioctl : 提供一种执行设备特定命令的方法。
-
unsigned int (*poll) (struct file *, struct poll_table_struct *);
- 系统调用select和poll的后端实现,用这两个系统调用来查询 设备是否可读写,或是否处于某种状态。如果poll为空,则驱动设备会被认为即可读又可写,返回值是一个状态掩码。
-
int (*mmap) (struct file *, struct vm_area_struct *);�将设备内存映射到进程地址空间
文件对象file结构体
文件对象file代表着一个打开的文件。进程通过文件描述符fd与已打开文件的file结构相联系。
struct file 在<linux/fs.h>中定义。
指向结构体struct file的指针通常命名为filp,或者file。建议使用文件指针filp。
-
文件对象file结构体的成员
- struct file_operations *f_op;
与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件操作结构体。用C语言实现面向对象编程的方法重载。 -
其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体 file,只是使用file中的数据。
索引节点inode结构体
- struct file_operations *f_op;
文件打开,在内存建立副本后,由唯一的索引节点inode描述。
-
与file结构不同。
- file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。
- inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。一个文件在内存里面只有一个inode结构对应。
inode结构包含大量描述文件信息的成员变量。
但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。
dev_t i_rdev; 包含真正的设备号。
struct file_operations *i_fop;在生成设备文件的时候,这个文件操作成员被赋予一个默认值;
-
从inode中获得主设备号和次设备号的宏:
- unsigned int iminor(struct inode *inode);
- unsigned int imajor(struct inode *inode);
struct inode 结构代表一个实实在在文件,每个文件只对应一个inode;
struct file 结构代表一个打开的文件,同一个文件可以对应多个file结构;
struct file_operations结构代表底层操作硬件函数的集合
怎么注册一个字符设备
注册一个字符设备的早期方法:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major 是给定的主设备号。为0代表自动分配设备号
name 是驱动的名字(将出现在 /proc/devices),
fops 是设备驱动的file_operations 结构。
register_chrdev 将给设备分配 0 - 255 的次设备号, 并且为每一个建立一个缺省的 cdev 结构。从系统中卸载字符设备的函数:
int unregister_chrdev(unsigned int major, const char *name);-
驱动程序是以内核模块的形式表现的,linux内核的模块机制是:在插入模块时,执行模块初始化函数;在卸载模块时,执行模块卸载函数。
- 驱动程序就是利用这种机制,在模块初始化函数中,进行设备的设置、注册等。
- 在模块卸载函数进行设备的注销工作。
举个简单的字符设备例子
-
编写底层操作函数--open、release方法:
-
编写底层操作函数--read、write方法:
-
将底层操作函数设置到一个file_operations结构体变量中
创建一个结构体变量fops,这个变量的成员open,release,read,write分别指向对应的函数。
-
模块初始化函数注册设备;卸载函数注销设备
- 编译模块;
- 在ARM板上插入模块
# insmod first_drv.ko
- 插入模块之后,可以通过文件/proc/devices 查看设备信息
# cat /proc/devices
找到 first_drv的主设备号是249,如下图
- 建立设备文件
# mknod /dev/first_drv c 249 0
- 建立好设备文件之后,应用程序就可以通过设备文件来访问驱动程序了。
-
应用测试程序如图:
-
在开发板上执行测试程序的效果如下
简单的总结一下驱动开发的流程
- 字符设备驱动程序的编写框架是:
- 编写底层硬件的操作函数,将这些函数集合在一个file_operations结构中;
- 在模块的入口函数中,申请设备号,初始化并注册一个cdev结构;
- 在模块的出口函数中,注销cdev结构,注销设备号;
- 可以通过文件 /proc/devices 查看设备信息,找到动态分配的主设备号
- 手动建立设备文件通过mknod命令
进阶字符设备写法请看下一篇文章