驱动程序设计基础教程

为什么要了解驱动设计?
开发新的产品 。若有新的外设,需要开发驱动 。不同的平台需要移植
Android近几年的发展,促使驱动行业的发展
开发难度高,待遇高
什么是外部设备
无论片内片外,CPU核以外的设备都可称为外部设备 , 因为你都需要写程序去操纵 。
关于外部设备控制
嵌入式系统的几乎每种操作,最后都需要映射到实际的物理设备上 。
除处理器、内存和少数其他实体外 , 几乎所有设备的控制操作都由设备相关的驱动程序来实现 。
嵌入式系统开发平台功能成熟度的重要指标:是否具有大量、丰富的设备驱动及BSP(板级支持包)支持 。
设备驱动程序的重要性
设备驱动程序往往工作于系统内核状态 , 因而,其运行性能、、可靠性制约着应用系统的性能和可靠性 。
由于设备驱动错误,导致操作系统崩溃约占30~40%
对于嵌入式Linux
内核发行包一般支持内存、网卡、显卡、硬盘等常规设备
研究范围 , 是指内核发行包一般不支持的外设
移植
新设备
设备与CPU的信息交换
外部设备与CPU的通讯信息 , 主要包括
控制信息:告诉要进行什么处理
状态信息:告诉设备的状态
数据信息:不同的设备,传输数据类型及编码各异 。
输入、输出方式控制
与外部设备的输入/输出的方式,主要包括:
直接I/O方式:通过指令直接对端口进行输入、输出控制 。如X86的in、out指令 。
内存映射方式:可以访问较大的地址空间,实现快速数据交换,如:ISA总线映射的空间为OxC8000~0xEFFF 。
中断方式:采用中断实现数据的输入/输出 。
DMA方式:采用DMA控制器 , 实现数据传输 。
数据量不大的情况下,如字符设备,常采用中断方式;
大量数据传输的I/O设备,如磁盘、网卡等设备,常采用DMA方式,以减少CPU资源的占用 。
什么是驱动程序
每个设备控制器都有一个或多个寄存器用来接收命令或设备的状态,它们通过读写命令按址存取 。(通常有数据寄存器、状态、操作方式寄存器)
不同的设备寄存器的个数和命令含义都不同——设备驱动程序是操作系统中唯一知道控制器有几个寄存器及它们用途的程序 。
驱动程序程序是指挥硬件工作的软件 。它是应用程序与硬件之间的一个中间层 , 为应用程序屏蔽硬件的细节 。
驱动程序的功能是什么?
驱动程序是内核的一部分
对设备的初始化和释放
把数据从内核传到硬件/从硬件读数据到内核
读取应用程序传送给设备文件的数据和回送应用程序请求的数据 。这需要在用户控件 , 内核空间,总线以及外设之间传输数据
检测和处理设备出现的错误
驱动程序与应用程序的区别
Linux中以模块的形式加载各种驱动程序:
主动与被动的区别 。应用程序有一个main函数 , 总是从些函数开始主动执行一个任务,而驱动程序安装以后,便停止工作,并等待被应用程序调用 。
使用的库函数不同 。
程序运行的区域不同 。驱动程序工作在内核状态;应用程序工作在用户态 。
设备分类
Linux支持三类硬件设备
字符设备:无需缓冲直接读写
块设备:通过buffer或cache进行读写
支持块的随机访问
网络设备:通过BSD套接口访问
字符设备
能够像字节流一样被访问的设备,提供数据通道,不允许来回读写,在数据传输以字符为单位进行传输 。
例如:串口、鼠标、终端、打印机
字符设备设备特点:
在数据传输中,可以传输任意大小的字符数据;
可以通过文件方式(/tyCo/0)访问,但只能顺序访问数据通道;
块设备
以数据“块”为单位,对数据进行来回读写/存取的设备 。1个数据块可包括512B、1KB数据 。块设备可以容纳文件系统:
块设备特点:硬盘、U盘、内存、Flash、CD-ROM等 。
块设备特点:
在数据传输中,传输单位是固定大小的数据块 。
块设备的存取时通过buffer、cache来进行 。
可以随机访问块设备中存取的数据块 。
块设备不直接与I/O系统连接 , 而是通过与文件系统关联,提供接口 。
网络接口设备
网络设备 , 又称网络接口,用于网络通信 。通常它们指的是硬件设备,但有时也可以是一个纯软件设备(如回环接口loopback)
与不同I/O设备不同,网络设备没有对应的设备文件,数据通信不是基于的标准的I/O系统接口,而是基于BSD套接口访问,例如:socket、bind、listen、accept、send等系统调用 。
在Linux中,采用给网络接口设备分配一个唯一的名字和方法来访问该设备 。如:eth0 eth1 lo
设备文件和设备号
设备文件
Linux抽象了对硬件设备的访问,可作为普通文件一样访问,使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作 。
每个设备文件都有对应的两个设备号
主设备号 。标识设备的种类,使用的驱动程序
【驱动程序设计基础教程】2.4内核下是8位,2.6及以后内核为12位
次设备号 , 标识使用同一设备驱动程序的不同硬件设备
2.4内核下是8位,2.6及以后内核为20位
设备文件在Linux的什么地方?
Linux应用程序是通过设备文件(又名:设备节点)来使用驱动程序操作字符设备和块设备
/dev是存放设备文件的
字符设备用c表示;块设备用b表示;设备文件一般命名为“设备名+数字或字母”
设备文件、主设备号和驱动程序之间的关系?
应用程序如何使用设备文件?使用文件名
设备文件与驱动程序如何建立联系? 主设备号
(1)安装驱动,注册主设备号
(2)创建设备文件,指向该设备类型,及主次设备号
(3)应用程序,调用设备文件
主设备号+次设备号
主设备号用于标识设备对应的驱动程序,主设备号相同的设备使用相同的设备驱动程序 。一个主设备号可能有多个设备与之对应,这些设备在驱动程序类可以通过次设备号来进一步区分 。
例如,软驱的设备号是2,IDE硬盘的主设备号是3,并口的主设备号是6
代码分布
驱动源代码都放在Driver目录中
block
char
cdrom
pci
scsi
net
sound
Linux设备驱动的特点
核心代码:设备驱动程序是内核的一部分(可靠性)
核心接口:设备驱动程序必须为Linux核心或其从属子系统提供一个标准接口;
核心机制:可以使用标准的核心服务等;
动态可加载:加载请求时进行加载,不使用设备时卸载
可配置:编译内核时,可配置进内核 。
设备文件接口
Linux应用程序可以通过设备文件的一组固定的入口点来访问驱动程序,这组入口点是由每个设备的驱动程序提供的 。
系统试图使它对各类设备的输入、输出看起来就像普通文件一样 。因此,把设备映射为一种特殊恶文件 。
程序:编写应用程序实现向串口发送字符串“ATD2109992”
int main(){ int fd,n; char buf[MAX]="ATD2109992"; fd = open("/dev/ttyS0",O_RDWR); //open入口点,ttyS0是设备文件 if(fd<0){ perror("Unable opne/dev/ttyS0n"); return 1; } n = write(fd,buf,strlen(buf));//write入口点 if(n<0){ print("write() of %d bytes failed!n",strlen()buf); }else{ print("write() of %d bytes ok!n",strlen(buf)); } close(fd); //close入口点}file_operations
struct file_operations{ //指向拥有该结构的模块的指针,一般初始化为THIS_MODULE 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(*aio_read)(struct kiocb*,const struct iovec *,unsinged long.loff_t); //初始化一个异步写入操作 ssize_t(*aio_write)(struct kiocb*,const struct iovec *,unsinged long,loff_t); //用来读取目录 , 对于设备文件 , 该成员应该为NULL int(*readdir)(struct file*.void *,filldir_t); //轮询函数,查询对一个或多个文件描述符的读或写是否会阻塞 unsinged int(*poll)(struct file *,struct poll_table_struct *); //用来执行设备I/O操作命令 int(*ioctl)(struct inode *,struct file*,unsinged int,unsinged long); //不使用BLK文件系统,将使用此函数代替ioctl long(*unlocked_ioctl)(struct file*,unsinged int,unsinged long); //在64位系统上,使用32位的ioctl调用将使用此函数代替 long(*compat_ioctl)(struct file*,unsinged int,unsinged long); //用来将设备内存映射进程的地址空间 int(*mmap)(struct file*,struct vm_area_struct *); //用来打开设备 int(*open)(struct inode *,struct file*); //执行并等待设备的任何未完成操作 int(*flush)(struct file*,struct dentry *,int datasync); //用来关闭设备 int(*release)(struct inode *,struct file *); //fsync的异步版本 int(*aio_fsync)(struct kiocb*,int datasync); //通知设备FASYNC标志的改变 int(*fasync)(int,struct file*,int); //用来实现文件加锁,通常设备文件不需要实现次函数 int(*lock)(struct file*,int struct file_lock *);}}开发驱动时,需要实现的接口:
目前对此结构体采用“标记化”方法进行赋值 , 下面是对此结构体s3c44b0_fops用“标记化”方法进行赋值的实例 。
static struct file_operations s3c440_fops={ owner:THIS_MODULE; open:s3c2410_ts_open; read:s3c2410_ts_read; write:s3c2410_ts_write; release:s3c2410_ts_release;}字符设备驱动框架
字符设备驱动程序是嵌入式Linux最基本、也是最常用的驱动程序
字符设备在Linux内核中使用struct cdev结构来表示
在struct cdev结构中包含着字符设备需要的全部信息(面向对象的思想——基类或容器)
设备号dev_t(linux/type.h)
文件操作file_operations
设备类型和设备号
对字符设备的访问是通过文件系统内的设备文件进行的,或者称为设备节点 , 位于/dev目录
设备类型:字符设备/块设备
主设备号:主设备号用来标识该设备的种类,也标识了该设备所使用的驱动程序
次设备号:次设备号由内核使用,标识使用同一设备驱动程序的不同硬件设备
关键数据结构
file_operations
抽象接口,驱动程序里面去实现的接口
file结构体
打开文件 , 在内核空间创建的文件描述符
inode
文件的索引节点
file结构体
file结构体在<Linux/fs.h>中定义
file结构体代表一个打开的文件 , 系统中每个打开的文件在内核空间都有一个与之关联的struct file,它由内核在打开文件时创建 , 在文件的所有实例都关闭之后,内核将释放这个数据结构 。
inode
inode是内核文件系统索引节点对象,包含内核在操作文件或目录时需要的全部信息
在内核中inode结构体用来表示文件,file是表示打开文件的结构体
重要成员
dev_t i_rdev:对于设备文件而言,此设备成员包含实际的设备号
struct cdev *i_cdev:指向:cdev结构的指针
字符设备的注册和注销
在Linux内核中,使用cdev结构体来描述一个字符设备
struct cdev{ struct kobject kobj; //内嵌的kobject对象 struct module *owner; //所属的模块 const struct file_operations *ops; //文件操作函数 struct list_head list; dev_t dev;unsinged int count;}MAJOR(dev_t dev)根据设备号获得主设备号MINOR(dev_t dev)根据设备号获得次设备号MKDEV(int major,int minor)根据次设备号与主设备号获得设备号cdev结构体操作函数,定义在<linux/cdev.h>
void cdev_init(struct cdev *,const struct file_operations *);用于初始化cdev成员 。
struct cdev *cdev_alloc(void);用于动态申请一块cdev类型内存 。
void cdev_add(struct cdev *,dev_t,unsigned);用于向系统添加一个cdev,完成字符设备的注册
void cdev_del(struct cdev*);用于向系统删除一个cdev完成字符设备的注销
分配和释放设备号
在使用cdev_add()向系统注册字符设备之前应先申请设备号 , 采用如下函数:
已知设备号:int register_chrdev_region(dev_t from,unsigned count,const char *name);
from:设备编号的起始值 , 次设备号为0;count申请分配的连续设备号的个数;name:和设备关联的名称 。
未知设备号:int alloc_chrdev_region(de_t *dev,unsigned baseminor,unsinged count,const char *name);
内核空间与用户空间之间复制数据的方法
在用户空间不能直接访问内核空间恶内存,因此 , 需要借助下面两个函数,分别用来把数据从用户空间拷贝到内核空间,以及把数据从内核空间拷贝到用户空间 。
从用户态拷贝到内核态:
unsigned long copy_from_user(void* to,const void_user * from,unsigned long count);
从内核态拷贝到用户态:
unsinged long copy_to_user(void* _user* to,const void * from ,unsigned long count );
虚拟字符驱动实例
在内核空间申请一块4kb内存用于模拟一个设备,并在驱动中提供对这块设备的读、写、控制和定位函数,以供用户空间的进程能通过Linux系统调用这块内存的内容
虚拟设备驱动vs真实设备驱动
虚拟设备驱动实例