Linux设备驱动开发.ppt
《Linux设备驱动开发.ppt》由会员分享,可在线阅读,更多相关《Linux设备驱动开发.ppt(133页珍藏版)》请在三一办公上搜索。
1、1,Linux 设备驱动设计 梁红波,设备驱动概述,设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。一组寄存器组被赋予到各个控制器。I/O端口包含4组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。状态寄存器拥有可以被CPU读取的(状态)位,用来 指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。控制寄存器则用于启动一条命令(指令)或者改变设备的(工作)模式。数据输入寄存器用于获取输入的数据。数据输出寄存器则向CPU发送结果。,设备驱动概述,操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备。设备驱动程序是操作系统
2、内核和机器硬件之间的接口,系统调用是操作系统内核和应用程序之间的接口。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作.,4,设备驱动概述,驱动完成以下的功能:对设备初始化和释放.把数据从内核传送到硬件和从硬件读取数据.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.检测和处理设备出现的错误.,5,设备驱动概述,无操作系统的设备驱动有操作系统的设备驱动,Embedded OS,Hardware,不带操作系统软件结构 带操作系统软件结构,Driver,6,Linux设备驱动,7,Linux设备驱动,用户级的程序使用内核提供的标准系统调用来与内核
3、通讯,这些系统调用有:open(),read(),write(),ioctl(),close()等等。Linux的内核是映射到每一个进程的高1G空间。每一个用户进程运行时都好像有一份内核的拷贝,每当用户进程使用系统调用时,都自动地将运行模式从用户级转为内核级,此时进程在内核的地址空间中运行。,8,Linux设备驱动,Linux内核使用“设备无关”的I/O子系统来为所有的设备服务。每个设备都提供标准接口给内核,尽可能地隐藏了自己的特性。用户程序使用一些基本的系统调用从设备读取数据并且将它们存入缓冲的例子。我们可以看到,每当一个系统调用被使用时,内核就转到相应的设备驱动例程来操纵硬件。,Linux
4、设备驱动,Linux操作系统把设备纳入文件系统的范畴来管理。每个设备在Linux系统上看起来都像一个文件,它们存放在/dev目录中,称为设备节点。对文件操作的系统调用大都适用于设备文件。,10,Linux设备驱动,Linux下设备的属性设备的类型:字符设备、块设备、网络设备主设备号:标识设备对应的驱动程序。一般“一个主设备号对应一个驱动程序”次设备号:每个驱动程序负责管理它所驱动的几个硬件实例,这些硬件实例则由次设备号来表示。同一驱动下的实例编号,用于确定设备文件所指的设备。可通过ls l“设备文件名”命令查看设备的主次设备号,以及设备的类型。,11,Linux设备驱动,Linux设备驱动程序
5、是一组由内核中的相关子例程和数据组成的I/O设备软件接口。每当用户程序要访问某个设备时,它就通过系统调用,让内核代替它调用相应的驱动例程。这就使得控制从用户进程转移到了驱动例程,当驱动例程完成后,控制又被返回至用户进程。,12,一些重要的数据结构,大部分驱动程序涉及三个重要的内核数据结构:文件操作file_operations结构体文件对象file结构体索引节点inode结构体,13,一些重要的数据结构,文件操作结构体file_operations结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。结构体的每个域都
6、对应着驱动模块用来处理某个被请求的事务的函数的地址。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*);。,14,一些重要的数据结构,file_operations重要的成员Struct module*owner,指向拥有该结构体的模块的指针。内核使用该指针维护模块使用计数。方法llseek用来修改文件的当前读写位置,把新位置
7、作为返回值返回。loff_t是在LINUX中定义的长偏移量 方法read用来从设备中读取数据。非负返回值表示成功读取的直接数。方法write向设备发送数据。方法ioctl提供一种执行设备特定命令的方法。,15,一些重要的数据结构,file_operations重要的成员unsigned int(*poll)(struct file*,struct poll_table_struct*);系统调用select和poll的后端实现,用这两个系统调用来查询 设备是否可读写,或是否处于某种状态。如果poll为空,则驱动设备会被认为即可读又可写,返回值是一个状态掩码。int(*mmap)(struct
8、file*,struct vm_area_struct*);将设备内存映射到进程地址空间,16,一些重要的数据结构,file_operations重要的成员驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为 NULL。Gcc的语法扩展,使得可以定义该结构体:struct file_operations fops=read:device_read,write:device_write,open:device_open,release:device_release;没有显示声明的结构体成员都被gcc初始化为NULL。,17,一些重要的数据结构,file_operat
9、ions重要的成员标准C的标记化结构体的初始化方法:struct file_operations fops=.read=device_read,.write=device_write,.open=device_open,.release=device_release;推荐使用该方法,提高移植性,方法允许对结构体成员进行重新排列。没有显示声明的结构体成员同样都被gcc初始化为NULL。指向结构体file_operations的指针通常命名为fops。,18,一些重要的数据结构,文件对象file结构体文件对象file代表着一个打开的文件。进程通过文件描述符fd与已打开文件的file结构相联系。进程
10、通过它对文件的线性逻辑空间进行操作。例如:file-f_op-read();Struct file 在中定义。指向结构体struct file的指针通常命名为filp,或者file。建议使用文件指针filp。,19,一些重要的数据结构,文件对象file结构体的成员Struct file_operations*f_op;与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件操作结构体。用C语言实现面向对象编程的方法重载。其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体 file,只是使用fil
11、e中的数据。,20,一些重要的数据结构,索引节点inode结构文件打开,在内存建立副本后,由唯一的索引节点inode描述。与file结构不同。file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。Inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。一个文件在内存里面只有一个inode结构对应。,21,一些重要的数据结构,索引节点inode结构Inode结构包含大量描述文件信息的成员变量。但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。Dev_t i_rdev;包含真正的设备编号
12、。Struct cdev*i_cdev;指向cdev结构体的指针。cdev是表示字符设备的内核数据结构。从inode中获得主设备号和次设备号的宏:Unsigned int iminor(struct inode*inode);Unsigned int imajor(struct inode*inode);,22,Linux设备驱动,主设备号和次设备号的内部表达:Dev_t类型用于保存设备号,称为设备编号。/linux/types.h文件中定义。目前设备编号dev_t是一个32位的整数,其中12位表示主设备号,20位表示次设备号。通过设备编号获取主次设备号:MAJOR(dev_t dev);MI
13、NOR(dev_t dev);通过主次设备号合成设备编号:MKDEV(int major,int minor);Dev_t格式以后可能会发生变化,但只要使用这些宏,就可保证设备驱动程序的正确性。,23,分配和释放字符设备号,编写驱动程序要做的第一件事,为字符设备获取一个设备号。事先知道所需要的设备编号(主设备号)的情况:int register_chrdev_region(dev_t first,unsigned count,const char*name)first是要分配的起始设备编号值。first的次设备号通常设置为0。Count 所请求的连续设备编号的个数。Name设备名称,指和该编号
14、范围建立关系的设备。分配成功返回0。,24,分配和释放字符设备号,动态分配设备编号(主要是主设备号)int alloc_chrdev_region(dev_t*dev,unsigned baseminor,unsigned count,const char*name)dev 是一个仅用于输出的参数,它在函数成功完成时保存已分配范围的第一个编号。baseminor 应当是请求的第一个要用的次设备号,它常常是 0.count 和 name 参数跟request_chrdev_region 的一样.,25,分配和释放字符设备号,不再使用时,释放这些设备编号。使用以下函数:void unregiste
15、r_chrdev_region(dev_t from,unsigned count)在模块的卸载函数中调用该函数。,26,分配和释放字符设备号,新驱动程序,建议使用动态分配机制获取主设备号,也就是使用alloc_chrdev_region()。动态分配导致无法预先创建设备节点。可在分配设备号后,从/proc/devices文件中获取。为了加载后自动创建设备文件,可以通过编写内核模块加载脚本实现。,27,字符设备的注册,内核内部使用struct cdev结构表示字符设备。编写设备驱动的第二步就是注册该设备。包含头文件。获取一个独立的cdev结构:struct cdev*my_cdev=cdev_
16、alloc();调用cdev_init初始化cdev结构体void cdev_init(struct cdev*cdev,struct file_operations*fops);初始化该设备的所有者字段:dev-cdev.owner=THIS_MODULE;初始化该设备的可用操作集:dev-cdev.ops=,28,字符设备的注册,编写设备驱动的第二步就是注册该设备。cdev 结构已建立和初始化,最后通过cdev_add函数把它告诉内核:int cdev_add(struct cdev*dev,dev_t num,unsigned int count);dev 是要添加的设备的 cdev 结
17、构,num 是这个设备对应的第一个设备编号,count 是应当关联到设备的设备号的数目.卸载字符设备时,调用相反的动作函数:void cdev_del(struct cdev*dev);,29,设备的注册,早期方法:内核中仍有许多字符驱动不使用刚刚描述过的cdev 接口。没有更新到 2.6 内核接口的老代码。注册一个字符设备的早期方法:int register_chrdev(unsigned int major,const char*name,struct file_operations*fops);major 是给定的主设备号。为0代表什么?name 是驱动的名字(将出现在/proc/dev
18、ices),fops 是设备驱动的file_operations 结构。register_chrdev 将给设备分配 0-255 的次设备号,并且为每一个建立一个缺省的 cdev 结构。从系统中卸载字符设备的函数:int unregister_chrdev(unsigned int major,const char*name);,30,Open方法,编写字符设备驱动的第三步:定义设备驱动与文件系统的接口,file_operation结构体的函数定义。open 方法int(*open)(struct inode*inode,struct file*filp);驱动程序提供open 方法,让用户进
19、程使用设备之前,进行一些初始化的工作。检查设备特定的错误。如果第一次打开设备,则初始化设备。如果需要,更新 f_op 指针,更换操作方法集。分配并填充要放进 filp-private_data 的任何数据结构。,31,Open方法,对于设备文件,inode 参数只有两个参数对设备驱动有用的。Dev_t i_rdev;包含真正的设备编号。Struct cdev*i_cdev;指向cdev结构体的指针。i_cdev里面包含我们之前建立的 cdev 结构。但是有时候,我们需要的是包含 cdev 结构的描述设备的结构。使用通过成员地址获取结构体地址的宏container_of,在 中定义:contai
20、ner_of(pointer,container_type,container_field);这个宏使用一个指向 container_field 类型的成员的指针,它在一个 container_type 类型的结构中,宏通过分析他们关系,返回指向包含该成员的结构体指针.,32,Open方法,在 myscull_open,这个宏用来找到适当的设备结构:dev=container_of(inode-i_cdev,struct scull_dev,cdev);找到 myscull_dev 结构后,scull 在filp-private_data 中存储其指针,为以后存取使用.filp-private
21、_data=dev;,33,release 方法,release 方法做open相反的工作释放 open 分配给filp-private_data的内存空间。在最后一次的关闭操作时,关闭设备。不是每个 close 系统调用引起调用 release 方法。,34,Read和Write方法,Read的任务,就是从设备拷贝数据到用户空间。Write的任务,则从用户空间拷贝数据到设备。ssize_t read(struct file*filp,char _user*buff,size_t count,loff_t*offp);ssize_t write(struct file*filp,const c
22、har _user*buff,size_t count,loff_t*offp);filp 是文件对象指针,count 是请求的传输数据大小.buff 参数对write来说是指向持有被写入数据的缓存,对read则是放入新数据的空缓存.offp 是指向一个“long offset type”的指针,它指出用户正在存取的文件位置.返回值是“signed size type”类型;,35,Read和Write方法,read 和 write 方法的 buff 参数是用户空间指针,不能被内核代码直接解引用。_user字符串只是形式上的说明,表明是用户空间地址。驱动必须能够存取用户空间缓存以完成它的工作。
23、内核如何解决这个问题?为安全起见,内核提供专用的函数来完成对用户空间的存取。这些专用函数在中声明。unsigned long copy_to_user(void _user*to,const void*from,unsigned long count);unsigned long copy_from_user(void*to,const void _user*from,unsigned long count);大多数读写函数都会调用这两个函数,用于跟应用程序空间交流信息。,36,Read和Write方法,典型的Read函数对参数的使用。,37,llseek函数,llseek函数用于对设备文件访
24、问定位。驱动接口loff_t(*llseek)(struct file*,loff_t,int);库函数off_t lseek(int filedes,off_t offset,int whence);参数 offset 的含义取决于参数 whence:如果 whence 是 SEEK_SET,文件偏移量将被设置为 offset。如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。SEEK_SET、
25、SEEK_CUR 和 SEEK_END 是 System V 引入的,是 0、1 和 2。,38,ioctl,进行超出简单的数据传输之外的操作,进行各种硬件控制操作.ioctl 方法和用户空间版本不同的原型:int(*ioctl)(struct inode*inode,struct file*filp,unsigned int cmd,unsigned long arg)不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsigned long的形式传递。返回值POSIX 标准规定:如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY。这个错误码被 C 库解释为“不
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 设备 驱动 开发
链接地址:https://www.31ppt.com/p-5438159.html