应用程序使用驱动:
一个设备驱动其实就是一个内核模块。
应用程序使用一个字符设备是通过使用其设备文件来完成的,通过对其设备文件的读写来完成对设备的交互:
那么,想要使用字符设备驱动就首先得有一个字符设备文件,创建一个字符设备文件的方式有两种:
驱动初始化
因为驱动程序是一个内核模块,所以驱动的初始化操作是放在内核模块的初始化函数里:
主要流程如下:
下面展示一段示例代码:
下面来解释一下这段初始化代码中的函数和数据结构:
Struct cdev是用来表示设备的结构体,具体实现如下:
其中比较重要的就是其中特殊颜色的几个属性,设备操作集是指一些操作函数。设备号是用来标示设备的。设备数是用来表示同类型的设备一共有几个,这几个设备的主设备号相同,次设备号不同。比如串口1,串口2,它们的主设备号相同,次设备号不同。
Cdev有两种分配方式:
Det_t类型,指的是设备号。实际为32位的无符号整型。这个32位无符号整型包含了两部分,分别是主设备号,和次设备号,它们相互转化的方式为:
分配一个设备号的方式有两种:
因为设备号是一种资源,所以当驱动退出的时候,应该使用unregister_chrdev_region函数释放掉这个设备号。
Struct file_operations是一个函数指针的集合,定义的是能在设备上进行的操作,这个结构体里就是各种函数指针,你需要重新给这些函数指针赋值,来重定向各种文件操作。下面是给这个结构体赋值的语句。
关于上面那种赋值方式只说明两点:1.无所谓顺序。2.注意下面:
Cedv_init函数使用来初始化cdev结构体的,主要是为了将操作函数集加入到cdev中。
Cdev_add用来向内核注册一个设备:
实现设备操作:
一般来说:常用的设备操作有以下几个:
v int (*open) (struct inode *, struct file *)打开设备,响应open系统
Open设备方法是驱动程序用来为以后的操作完成初始化准备工作的,在绝大部分驱动程序中,open完成如下工作:
1. 标明设备号。
2. 启动设备
解释一下其中的比较重要的结构体:
Struct file,在linux系统中,每一个打开的文件关联一个struct file结构,在内核打开文件的时候创建,在文件关闭后释放。他的重要成员如下:
Struct inode,在linux中,每一个存在于文件系统里的文件都会关联一个inode结构(不论有没有打开),该结构主要记录物理信息,主要数据结构如下:
例子程序如下:
v int (*release) (struct inode *, struct file *);关闭设备,响应close系统调用
Release方法要做的就是关闭设备。
下面是示例代码:
v loff_t (*llseek) (struct file *, loff_t, int)重定位读写指针,响应lseek系统调用
文件当前读写指针重定位:
示例代码:
v ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)从设备读取数据,响应read系统调用
参数意义如下:
Read设备方法通常完成两件事情:
1. 从设备中读取数据(属于硬件访问类操作)。
2. 将读取到的数据返回给应用程序。
示例代码:
用一张图说明read函数的工作流程:
v ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)向设备写入数据,响应write系统调用
Write设备方法通常完成两件事:
1. 从应用程序提供的地址中取出数据,
2. 将数据写入设备(属于硬件访问类操作)。
驱动注销:
从内核中卸载驱动程序的时候,需要使用cdev_del函数来完成字符设备的注销。
示例代码: