




已阅读5页,还剩128页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
.,1,Linux设备驱动设计梁红elvinema-,.,设备驱动概述,设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。一组寄存器组被赋予到各个控制器。I/O端口包含4组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。状态寄存器拥有可以被CPU读取的(状态)位,用来指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。控制寄存器则用于启动一条命令(指令)或者改变设备的(工作)模式。数据输入寄存器用于获取输入的数据。数据输出寄存器则向CPU发送结果。,.,设备驱动概述,操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备。设备驱动程序是操作系统内核和机器硬件之间的接口,系统调用是操作系统内核和应用程序之间的接口。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作.,.,4,设备驱动概述,驱动完成以下的功能:对设备初始化和释放.把数据从内核传送到硬件和从硬件读取数据.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.检测和处理设备出现的错误.,.,5,设备驱动概述,无操作系统的设备驱动有操作系统的设备驱动,EmbeddedOS,Hardware,不带操作系统软件结构带操作系统软件结构,Driver,.,6,Linux设备驱动,.,7,Linux设备驱动,用户级的程序使用内核提供的标准系统调用来与内核通讯,这些系统调用有:open(),read(),write(),ioctl(),close()等等。Linux的内核是映射到每一个进程的高1G空间。每一个用户进程运行时都好像有一份内核的拷贝,每当用户进程使用系统调用时,都自动地将运行模式从用户级转为内核级,此时进程在内核的地址空间中运行。,.,8,Linux设备驱动,Linux内核使用“设备无关”的I/O子系统来为所有的设备服务。每个设备都提供标准接口给内核,尽可能地隐藏了自己的特性。用户程序使用一些基本的系统调用从设备读取数据并且将它们存入缓冲的例子。我们可以看到,每当一个系统调用被使用时,内核就转到相应的设备驱动例程来操纵硬件。,.,Linux设备驱动,Linux操作系统把设备纳入文件系统的范畴来管理。每个设备在Linux系统上看起来都像一个文件,它们存放在/dev目录中,称为设备节点。对文件操作的系统调用大都适用于设备文件。,.,10,Linux设备驱动,Linux下设备的属性设备的类型:字符设备、块设备、网络设备主设备号:标识设备对应的驱动程序。一般“一个主设备号对应一个驱动程序”次设备号:每个驱动程序负责管理它所驱动的几个硬件实例,这些硬件实例则由次设备号来表示。同一驱动下的实例编号,用于确定设备文件所指的设备。可通过lsl“设备文件名”命令查看设备的主次设备号,以及设备的类型。,.,11,Linux设备驱动,Linux设备驱动程序是一组由内核中的相关子例程和数据组成的I/O设备软件接口。每当用户程序要访问某个设备时,它就通过系统调用,让内核代替它调用相应的驱动例程。这就使得控制从用户进程转移到了驱动例程,当驱动例程完成后,控制又被返回至用户进程。,.,12,一些重要的数据结构,大部分驱动程序涉及三个重要的内核数据结构:文件操作file_operations结构体文件对象file结构体索引节点inode结构体,.,13,一些重要的数据结构,文件操作结构体file_operations结构体file_operations在头文件linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。结构体的每个域都对应着驱动模块用来处理某个被请求的事务的函数的地址。structfile_operationsstructmodule*owner;ssize_t(*read)(structfile*,char_user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar_user*,size_t,loff_t*);。,.,14,一些重要的数据结构,file_operations重要的成员Structmodule*owner,指向拥有该结构体的模块的指针。内核使用该指针维护模块使用计数。方法llseek用来修改文件的当前读写位置,把新位置作为返回值返回。loff_t是在LINUX中定义的长偏移量方法read用来从设备中读取数据。非负返回值表示成功读取的直接数。方法write向设备发送数据。方法ioctl提供一种执行设备特定命令的方法。,.,15,一些重要的数据结构,file_operations重要的成员unsignedint(*poll)(structfile*,structpoll_table_struct*);系统调用select和poll的后端实现,用这两个系统调用来查询设备是否可读写,或是否处于某种状态。如果poll为空,则驱动设备会被认为即可读又可写,返回值是一个状态掩码。int(*mmap)(structfile*,structvm_area_struct*);将设备内存映射到进程地址空间,.,16,一些重要的数据结构,file_operations重要的成员驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为NULL。Gcc的语法扩展,使得可以定义该结构体:structfile_operationsfops=read:device_read,write:device_write,open:device_open,release:device_release;没有显示声明的结构体成员都被gcc初始化为NULL。,.,17,一些重要的数据结构,file_operations重要的成员标准C的标记化结构体的初始化方法:structfile_operationsfops=.read=device_read,.write=device_write,.open=device_open,.release=device_release;推荐使用该方法,提高移植性,方法允许对结构体成员进行重新排列。没有显示声明的结构体成员同样都被gcc初始化为NULL。指向结构体file_operations的指针通常命名为fops。,.,18,一些重要的数据结构,文件对象file结构体文件对象file代表着一个打开的文件。进程通过文件描述符fd与已打开文件的file结构相联系。进程通过它对文件的线性逻辑空间进行操作。例如:file-f_op-read();Structfile在中定义。指向结构体structfile的指针通常命名为filp,或者file。建议使用文件指针filp。,.,19,一些重要的数据结构,文件对象file结构体的成员Structfile_operations*f_op;与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件操作结构体。用C语言实现面向对象编程的方法重载。其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体file,只是使用file中的数据。,.,20,一些重要的数据结构,索引节点inode结构文件打开,在内存建立副本后,由唯一的索引节点inode描述。与file结构不同。file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。Inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。一个文件在内存里面只有一个inode结构对应。,.,21,一些重要的数据结构,索引节点inode结构Inode结构包含大量描述文件信息的成员变量。但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。Dev_ti_rdev;包含真正的设备编号。Structcdev*i_cdev;指向cdev结构体的指针。cdev是表示字符设备的内核数据结构。从inode中获得主设备号和次设备号的宏:Unsignedintiminor(structinode*inode);Unsignedintimajor(structinode*inode);,.,22,Linux设备驱动,主设备号和次设备号的内部表达:Dev_t类型用于保存设备号,称为设备编号。/linux/types.h文件中定义。目前设备编号dev_t是一个32位的整数,其中12位表示主设备号,20位表示次设备号。通过设备编号获取主次设备号:MAJOR(dev_tdev);MINOR(dev_tdev);通过主次设备号合成设备编号:MKDEV(intmajor,intminor);Dev_t格式以后可能会发生变化,但只要使用这些宏,就可保证设备驱动程序的正确性。,.,23,分配和释放字符设备号,编写驱动程序要做的第一件事,为字符设备获取一个设备号。事先知道所需要的设备编号(主设备号)的情况:intregister_chrdev_region(dev_tfirst,unsignedcount,constchar*name)first是要分配的起始设备编号值。first的次设备号通常设置为0。Count所请求的连续设备编号的个数。Name设备名称,指和该编号范围建立关系的设备。分配成功返回0。,.,24,分配和释放字符设备号,动态分配设备编号(主要是主设备号)intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)dev是一个仅用于输出的参数,它在函数成功完成时保存已分配范围的第一个编号。baseminor应当是请求的第一个要用的次设备号,它常常是0.count和name参数跟request_chrdev_region的一样.,.,25,分配和释放字符设备号,不再使用时,释放这些设备编号。使用以下函数:voidunregister_chrdev_region(dev_tfrom,unsignedcount)在模块的卸载函数中调用该函数。,.,26,分配和释放字符设备号,新驱动程序,建议使用动态分配机制获取主设备号,也就是使用alloc_chrdev_region()。动态分配导致无法预先创建设备节点。可在分配设备号后,从/proc/devices文件中获取。为了加载后自动创建设备文件,可以通过编写内核模块加载脚本实现。,.,27,字符设备的注册,内核内部使用structcdev结构表示字符设备。编写设备驱动的第二步就是注册该设备。包含头文件。获取一个独立的cdev结构:structcdev*my_cdev=cdev_alloc();调用cdev_init初始化cdev结构体voidcdev_init(structcdev*cdev,structfile_operations*fops);初始化该设备的所有者字段:dev-cdev.owner=THIS_MODULE;初始化该设备的可用操作集:dev-cdev.ops=,.,28,字符设备的注册,编写设备驱动的第二步就是注册该设备。cdev结构已建立和初始化,最后通过cdev_add函数把它告诉内核:intcdev_add(structcdev*dev,dev_tnum,unsignedintcount);dev是要添加的设备的cdev结构,num是这个设备对应的第一个设备编号,count是应当关联到设备的设备号的数目.卸载字符设备时,调用相反的动作函数:voidcdev_del(structcdev*dev);,.,29,设备的注册,早期方法:内核中仍有许多字符驱动不使用刚刚描述过的cdev接口。没有更新到2.6内核接口的老代码。注册一个字符设备的早期方法:intregister_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops);major是给定的主设备号。为0代表什么?name是驱动的名字(将出现在/proc/devices),fops是设备驱动的file_operations结构。register_chrdev将给设备分配0-255的次设备号,并且为每一个建立一个缺省的cdev结构。从系统中卸载字符设备的函数:intunregister_chrdev(unsignedintmajor,constchar*name);,.,30,Open方法,编写字符设备驱动的第三步:定义设备驱动与文件系统的接口,file_operation结构体的函数定义。open方法int(*open)(structinode*inode,structfile*filp);驱动程序提供open方法,让用户进程使用设备之前,进行一些初始化的工作。检查设备特定的错误。如果第一次打开设备,则初始化设备。如果需要,更新f_op指针,更换操作方法集。分配并填充要放进filp-private_data的任何数据结构。,.,31,Open方法,对于设备文件,inode参数只有两个参数对设备驱动有用的。Dev_ti_rdev;包含真正的设备编号。Structcdev*i_cdev;指向cdev结构体的指针。i_cdev里面包含我们之前建立的cdev结构。但是有时候,我们需要的是包含cdev结构的描述设备的结构。使用通过成员地址获取结构体地址的宏container_of,在中定义:container_of(pointer,container_type,container_field);这个宏使用一个指向container_field类型的成员的指针,它在一个container_type类型的结构中,宏通过分析他们关系,返回指向包含该成员的结构体指针.,.,32,Open方法,在myscull_open,这个宏用来找到适当的设备结构:dev=container_of(inode-i_cdev,structscull_dev,cdev);找到myscull_dev结构后,scull在filp-private_data中存储其指针,为以后存取使用.filp-private_data=dev;,.,33,release方法,release方法做open相反的工作释放open分配给filp-private_data的内存空间。在最后一次的关闭操作时,关闭设备。不是每个close系统调用引起调用release方法。,.,34,Read和Write方法,Read的任务,就是从设备拷贝数据到用户空间。Write的任务,则从用户空间拷贝数据到设备。ssize_tread(structfile*filp,char_user*buff,size_tcount,loff_t*offp);ssize_twrite(structfile*filp,constchar_user*buff,size_tcount,loff_t*offp);filp是文件对象指针,count是请求的传输数据大小.buff参数对write来说是指向持有被写入数据的缓存,对read则是放入新数据的空缓存.offp是指向一个“longoffsettype”的指针,它指出用户正在存取的文件位置.返回值是“signedsizetype”类型;,.,35,Read和Write方法,read和write方法的buff参数是用户空间指针,不能被内核代码直接解引用。_user字符串只是形式上的说明,表明是用户空间地址。驱动必须能够存取用户空间缓存以完成它的工作。内核如何解决这个问题?为安全起见,内核提供专用的函数来完成对用户空间的存取。这些专用函数在中声明。unsignedlongcopy_to_user(void_user*to,constvoid*from,unsignedlongcount);unsignedlongcopy_from_user(void*to,constvoid_user*from,unsignedlongcount);大多数读写函数都会调用这两个函数,用于跟应用程序空间交流信息。,.,36,Read和Write方法,典型的Read函数对参数的使用。,.,37,llseek函数,llseek函数用于对设备文件访问定位。驱动接口loff_t(*llseek)(structfile*,loff_t,int);库函数off_tlseek(intfiledes,off_toffset,intwhence);参数offset的含义取决于参数whence:如果whence是SEEK_SET,文件偏移量将被设置为offset。如果whence是SEEK_CUR,文件偏移量将被设置为cfo加上offset,offset可以为正也可以为负。如果whence是SEEK_END,文件偏移量将被设置为文件长度加上offset,offset可以为正也可以为负。SEEK_SET、SEEK_CUR和SEEK_END是SystemV引入的,是0、1和2。,.,38,ioctl,进行超出简单的数据传输之外的操作,进行各种硬件控制操作.ioctl方法和用户空间版本不同的原型:int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsignedlong的形式传递。返回值POSIX标准规定:如果使用了不合适的ioctl命令号,应当返回-ENOTTY。这个错误码被C库解释为“不合适的设备ioctl。-EINVAL也是相当普遍的。,.,39,结构化设备驱动程序,设备结构体把与某设备相关的所有内容定义为一个设备结构体其中包括设备驱动涉及的硬件资源、全局软件资源、控制(自旋锁、互斥锁、等待队列、定时器等)在涉及设备的操作时,就仅仅操作这个结构体,.,40,Linux设备驱动的并发控制,.,41,设备驱动的并发控制,在驱动程序中,当多个线程同时访问相同的资源时,可能会引发“竞态”,必须对共享资源进行并发控制。并发和竞态广泛存在。并发控制的目的:使得线程访问共享资源的操作是原子操作。原子操作:在执行过程中不会被别的代码路径所中断的操作。驱动程序中的全局变量是一种典型的共享资源。,.,42,考虑一个非常简单的共享资源的例子:一个全局整型变量和一个简单的临界区,其中的操作仅仅是将整型变量的值增加1:i+该操作可以转化成下面三条机器指令序列:得到当前变量i的值并拷贝到一个寄存器中将寄存器中的值加1把i的新值写回到内存中,原子操作,.,43,原子操作,内核任务1内核任务2获得i(1)-增加i(1-2)-写回i(2)-获得i(2)增加i(2-3)写回i(3),内核任务1内核任务2获得i(1)-增加i(1-2)-获得i(1)-增加i(1-2)-写回i(2)写回i(2)-,可能的实际执行结果:,期望的结果,.,44,Linux内核的并发控制,在内核空间的内核任务需要考虑同步内核空间中的共享数据对内核中的所有任务可见,所以当在内核中访问数据时,就必须考虑是否会有其他内核任务并发访问的可能、是否会产生竞争条件、是否需要对数据同步。,.,45,确定保护对象找出哪些数据需要保护是关键所在内核任务的局部数据仅仅被它本身访问,显然不需要保护。如果数据只会被特定的进程访问,也不需加锁大多数内核数据结构都需要加锁:若有其它内核任务可以访问这些数据,那么就给这些数据加上某种形式的锁;若任何其它东西能看到它,那么就要锁住它。,Linux内核的并发控制,.,46,Linux内核的并发控制,并发控制的机制中断屏蔽,原子数操作,自旋锁和信号量都是解决并发问题的机制。中断屏蔽很少被单独使用,原子操作只能针对整数来进行。因此自旋锁和信号量应用最为广泛。,.,47,中断屏蔽,单CPU系统中,避免竟态的一种简单方式保证正在执行的内核执行路径不被中断处理程序所抢占,防止竟态条件的发生。Local_irq_disable()/关中断Criticalsection/临界区Local_irq_enable()/开中断中断对内核非常重要,长时间屏蔽中断非常危险!只适合短时间的关闭对SMP多CPU引发的竟态无效,.,48,锁机制可以避免竞争状态正如门锁和门一样,门后的房间可想象成一个临界区。在一段时间内,房间里只能有一个内核任务存在,当一个任务进入房间后,它会锁住身后的房门;当它结束对共享数据的操作后,就会走出房间,打开门锁。如果另一个任务在房门上锁时来了,那么它就必须等待房间内的任务出来并打开门锁后,才能进入房间。,加锁机制,.,49,任何要访问临界资源的代码首先都需要占住相应的锁,这样该锁就能阻止来自其它内核任务的并发访问:,加锁机制,.,50,原子数操作,整型原子数操作原子变量初始化atomic_ttest=ATOMIC_INIT(i);设置原子变量的值voidatomic_set(atomic_t*v,inti)获得原子变量的值atomic_read(v)原子变量加voidatomic_add(inti,atomic_t*v)原子变量减voidatomic_sub(inti,atomic_t*v),.,51,原子数操作,整型原子数操作原子变量的自增操作voidatomic_inc(atomic_t*v)原子变量的自减操作voidatomic_dec(atomic_t*v)操作并测试(测试其是否为0,0为true,否为false)atomic_inc_and_test(atomic_t*v)atomic_dec_and_test(atomic_t*v)intatomic_sub_and_test(inti,atomic_t*v)操作并返回(返回新值)intatomic_add_return(inti,atomic_t*v)intatomic_sub_return(inti,atomic_t*v),.,52,原子数操作,原子位操作设置位voidset_bit(intnr,volatileunsignedlong*addr)清除位voidclear_bit(intnr,volatileunsignedlong*addr)改变位change_bit(nr,p)测试位test_bit(intnr,constvolatileunsignedlong*p)测试并操作位test_and_set_bit(nr,p),.,53,自旋锁,自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分。而对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。自旋锁最多只能被一个内核任务持有,若一个内核任务试图请求一个已被持有的自旋锁,那么这个任务就会一直进行忙循环,也就是旋转,等待锁重新可用。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。,.,54,自旋锁,自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话,最好使用信号量。,.,55,自旋锁,自旋锁防止在不同CPU上的执行单元对共享资源的同时访问,以及不同进程上下文互相抢占导致的对共享资源的非同步访问。在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。自旋锁不允许任务睡眠。,.,56,自旋锁,自旋锁的基本形式如下:spin_lock(,.,57,自旋锁,自旋锁原语要求包含文件是.锁的类型是spinlock_t.锁的两种初始化方法:spinlock_tmy_lock=SPIN_LOCK_UNLOCKED;voidspin_lock_init(spinlock_t*lock);进入一个临界区前,必须获得需要的lock。voidspin_lock(spinlock_t*lock);自旋锁等待是不可中断的。一旦你调用spin_lock,将自旋直到锁变为可用。释放一个锁:voidspin_unlock(spinlock_t*lock);,.,58,自旋锁,关中断的自旋锁Spin_lock_irq()Spin_unlock_irq()Spin_lock_irqsave()Spin_unlock_irqrestore(),.,59,信号量,Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;信号量的操作信号量支持两个原子操作P()和V(),前者做测试操作,后者叫做增加操作。Linux中分别叫做down()和up()。,.,60,信号量,down()和up()。down()操作通过对信号量计数减1来请求获得一个信号量。如果结果是0或大于0,信号量锁被获得,任务就可以进入临界区了。如果结果是负数,任务会被放入等待队列,处理器执行其它任务。相反,当临界区中的操作完成后,up()操作用来释放信号量,增加信号量的计数值。如果在该信号量上的等待队列不为空,处于队列中等待的任务在被唤醒的同时会获得该信号量。,.,61,信号量,.,62,信号量,.,63,Linux信号量的实现,内核代码必须包含,才能使用信号量。相关的类型是structsemaphore,信号量的定义,structsemaphoreatomic_tcount;intsleepers;wait_queue_head_twait;,.,64,Linux信号量的实现,信号量的声明和初始化直接创建一个信号量structsemaphore*sem;接着使用sema_init来初始化这个信号量:voidsema_init(structsemaphore*sem,intval);互斥模式的信号量声明,内核提供宏定义.DECLARE_MUTEX(name);信号量初始化为1DECLARE_MUTEX_LOCKED(name);信号量初始化为0,.,65,Linux信号量的实现,动态分配的互斥信号量声明voidinit_MUTEX(structsemaphore*sem);信号量初始化为1voidinit_MUTEX_LOCKED(structsemaphore*sem);信号量初始化为0,.,66,Linux信号量的实现,信号量的P操作voiddown(structsemaphore*sem);down减小信号量的值,并根据信号量的值决定是否等待。不可中断的等待。intdown_interruptible(structsemaphore*sem);操作是可中断的。intdown_trylock(structsemaphore*sem);信号量在调用时不可用,down_trylock立刻返回一个非零值.,.,67,Linux信号量的实现,信号量的V操作voidup(structsemaphore*sem);通过down操作进入临界区的进程,再退出的时候都需要调用一个up操作,释放信号量。,.,68,Linux信号量的实现,信号量基本使用形式为:staticDECLARE_MUTEX(mr_sem);/声明互斥信号量if(down_interruptible(操作配套使用,.,69,Linux设备驱动调试,.,70,内核调试选项,内核开发者在内核自身中构建了多个调试特性。这些特性会产生额外的输出并降低性能,Linux发行版的内核为了提高性能,去除这些调试特性。用来开发的内核应当激活的调试配置选项,是在“kernelhacking”菜单中。,.,71,通过打印调试,Printkprintk通过附加不同的消息优先级在消息上,对消息的严重程度进行分类。在定义了8个loglevel。DEFAULT_MESSAGE_LOGLEVEL为默认级别(printk.c)当消息优先级小于console_loglevel,信息才能显示出来。而console_loglevel的初值为DEFAULT_CONSOLE_LOGLEVEL。通过对/proc/sys/kernel/printk的访问来改变console_loglevel的值。该文件包含四个数字:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。echo1/proc/sys/kernel/printkecho8/proc/sys/kernel/printk,.,72,通过打印调试,打开和关闭消息通过封装printk函数,快速打开调试信息或者关闭调试信息。#definePDEBUG(fmt,args.)printk(KERN_DEBUG“myscull:fmt,#args)通过在Makefile里面定义调试开关变量去决定调试信息是否打开。,.,73,通过查询调试,获取相关信息的最好方法:在需要的时候才去查询系统信息,而不是持续不断地产生数据。/proc文件系统是一种特殊的、由软件创建的文件系统,内核使用他向外界导出信息。/proc下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态的生成文件的内容。例如/proc/devices,.,74,通过查询调试,包含在驱动中定义跟proc文件绑定的内核函数read_proc,在函数里面定义要输出的信息。在初始化函数中调用creat_proc_read_entry函数将/proc入口文件和read_proc函数联系起来。卸载模块时调用remove_proc_entry撤销proc入口。,.,75,通过查询调试,read_proc函数int(*read_proc)(char*page,char*start,off_toffset,intcount,int*eof,void*data);page是输出数据的缓存内存页。进程读取/proc文件时,内核会分配一个内存页,read_proc将数据通过这个内存页返回到用户空间。start是这个函数用来说有关的数据写在页中哪里eof,当没有数据可返回时,驱动设置这个参数。Data是提供给驱动的专用数据指针。,.,76,通过查询调试,intsprintf(char*buf,constchar*fmt,.)将数据打包成字符流的形式。内核很多象printk函数一样,通过库函数的形式提供给内核开发者的函数,以满足内核开发中的一些简单的需要。void*memset(void*s,charc,size_tcount);void*memcpy(void*dest,constvoid*src,size_tcount);,.,77,通过查询调试,creat_proc_read_entry函数structproc_dir_entry*create_proc_read_entry(constchar*name,mode_tmode,structproc_dir_entry*base,read_proc_t*read_proc,void*data);name是要创建的proc文件名,mod是文件的访问掩码(缺省0),base指出要创建的文件的目录;如果base是NULL,文件在/proc根下创建。read_proc是实现文件内容的read_proc函数,data被内核忽略(传递给read_proc).,.,78,查看Oops信息,大多数bug通常是因为废弃了一个NULL指针或者使用了错误的指针值。这类bug导致的结果通常是一条oops消息。一条oops消息能够显示发生故障时CPU的状态,以及CPU寄存器的内容和其他看似难以理解的信息。,.,79,查看Oops信息,例如访问一个NULL指针。因为NULL不是一个可访问的指针值,所以会引发一个错误,内核会简单地将其转换为oops消息并显示。然后其调用进程会被杀死。UnabletohandlekernelNULLpointerdereferenceatvirtualaddress00000000printingeip:d083a064Oops:0002#1SMPCPU:0EIP:0060:NottaintedEFLAGS:00010246(2.6.6)EIPisatoops_example_write+0 x4/0 x10oops_exampleeax:00000000ebx:00000000ecx:00000000edx:00000000,.,80,通过监视调试,strace命令可以显示由用户空间程序所发出的所有系统调用。还以符号形式显示调用的参数和返回值。当一个系统调用失败,错误的符号值(例如,ENOMEM)和对应的字串(Outofmemory)都显示.strace有很多命令行选项;-t来显示每个调用执行的时间,-T来显示调用中花费的时间,-e来限制被跟踪调用的类型,-o来重定向输出到一个文件.缺省地,strace打印调用信息到stderr.,.,81,Linux的内存分配,.,82,kmalloc函数,void*kmalloc(size_tsize,intflags);所分配到的内存在物理内存中连续且保持原有的数据(不清零)。size是要分配的块的大小。Linux创建一系列内存对象slab,每个slab内的内存块大小是固定。处理分配请求时,就直接在包含有足够大内存块的slab中分配一个整块给请求者。内核只能分配一些预定义的、固定大小的字节数组。kmalloc能够处理的最小内存块是32或64字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。不应分配大于128KB的内存。若需多于几个KB的内存块,最好使用其他方法。,.,83,kmalloc函数,void*kmalloc(size_tsize,intflags);Flags分配标志,表示如何分配空间。所有标志都定义在GFP_KERNEL内存分配是代表运行在内核空间的进程执行的,并且在空闲内存较少时把当前进程转入休眠以等待一个页面。GFP_ATOMIC内核通常会为原子性的分配预留一些空闲页。当当前进程不能被置为睡眠时,应使用GFP_ATOMIC,这样kmalloc甚至能够使用最后一个空闲页。如果连这最后一个空闲页也不存在,则分配返回失败。常用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠。GFP_DMA要求分配可用于DMA的内存。,.,84,后备高速缓存,内核为驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookasidecache)。设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在Linux2.6中USB和SCSI驱动。,.,85,后备高速缓存,创建一个新的后备高速缓存kmem_cache_createname:name和这个后备高速缓存相关联;通常设置为被缓存的结构类型的名字,不能包含空格。参数size:每个内存区域的大小。offset:页内第一个对象的偏移量;用来确保被分配对象的特殊对齐,0表示缺省值。flags:控制分配方式的位掩码:SLAB_NO_REAP保护缓存在系统查找内存时不被削减,不推荐。SLAB_HWCACHE_ALIGN所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。SLAB_CACHE_DMA每个数据对象在DMA内存区段分配.,.,86,后备高速缓存,创建一个新的后备高速缓存kmem_cache_create参数constructor和destructor是可选函数,用来初始化新分配的对象和在内存被作为整体释放给系统之前“清理”对象。,.,87,后备高速缓存,kmem_cache_alloc从已创建的后备高速缓存中分配对象:void*kmem_cache_alloc(kmem_cache_t*cache,intflags);flags和kmalloc的flags相同使用kmem_cache_free释放一个对象:voidkmem_cache_free(kmem_cache_t*cache,constvoid*obj);当驱动用完这个后备高速缓存(通常在当模块被卸载时),释放缓存:intkmem_cache_destroy(kmem_cache_t*cache);,.,88,get_free_page相关函数,如果一个模块需要分配大块的内存,最好使用面向页的分配技术。_get_free_page(unsignedintflags);返回一个指向新页的指针,未清零该页get_zeroed_page(unsignedintflags);类似于_get_free_page,但用零填充该页_get_free_pages(unsignedintflags,unsignedintorder);分配是若干(物理连续的)页面并返回指向该内存区域的第一个字节的指针,该内存区域未清零flags与kmalloc的用法相同order是请求或释放的页数以2为底的对数。若其值过大(没有这么大的连续区可用),则分配失败,.,89,get_free_page相关函数,当程序不需要页面时,用下列函数之一来释放voidfree_page(unsignedlongaddr);voidfree_pages(unsignedlongaddr,unsignedlongorder);,.,90,Linux的中断处理,.,91,为什么会有中断,中断最初是为克服对I/O接口控制采用程序查询所带来的处理器低效率而产生的。处理器速度一般比外设快很多用轮询的方式来查询设备的状态,CPU效率不高,CPU和外设不能并行工作。中断机制让CPU启动设备后,就去处理其他任务,只有当外设真正完成数据传输的准备,请求CPU服务的时候,CPU才转过来处理外设的请求。,.,92,中断和异常,外部中断:外部设备所发出的I/O请求。随着计算机系统结构的不断改进以及应用技术的日益提高,中断的适用范围也随之扩大,出现了所谓的内部中断(或叫异常)。异常:为解决机器运行时所出现的某些随机事件及编程方便而出现的。,.,93,I/O中断处理,为了保证系统对外部的响应,一个中断处理程序必须被尽快的完成。因此,把所有的操作都放在中断处理程序中并不合适Linux中把紧随中断要执行的操作分为三类紧急的(critical)一般关中断运行。诸如对PIC应答中断,对PIC或是硬件控制器重新编程,或者修改由设备和处理器同时访问的数据非紧急的(noncritical)如修改那些只有处理器才会访问的数据结构(例如按下一个键后读扫描码),这些也要很快完成,因此由中断处理程序立即执行,不过一般在开中断的情况下,.,94,I/O中断处理,Linux中把紧随中断要执行的操作分为三类非紧急可延迟的(noncriticaldeferrable)这些操作可以被延迟较长的时间间隔而不影响内核操作,有兴趣的进程将会等待数据。内核用下半部分这样一个机制来在一个更为合适的时机用独立的函数来执行这些操作。如把缓冲区内容拷贝到某个进程的地址空间(例如把键盘缓冲区内容发送到终端处理程序进程)。,.,95,S3c2410的中断,中断发生时,需要知道中断的来源。芯片的引线有限,很难提供很多条中断请求引线。使用中断控制器管理中断请求线。S3c2410将中断控制器集成在CPU芯片中,“复用”GPIO端口引脚,具有作为中断请求线的功能。SRCPND寄存器32位中的每一位对应着一个中断源,每一位被设置为1,则相应的中断源产生中断请求并且等待中断被服务。,.,96,注册中断服务例程,中断号是一个宝贵且常常有限的资源。内核维护一个中断号的注册表。要使用中断,就要进行中断号的申请,也就是IRQ(InterruptReQuirement)。只有当设备需要中断的时候才申请占用一个IRQ,或者是在申请IRQ时采用共享中断的方式,让更多的设备使用中断。,.,97,注册中断服务例程,在实现中断注册接口:intrequest_irq(unsignedintirq,irqreturn_t(*handler)(int,void*,structpt_regs*),unsignedlongflags,constchar*dev_name,void*dev_id);voidfree_irq(unsignedintirq,void*dev_id);request_irq的返回值是0指示申请成功,为负值时表示错误码。函数返回-EBUSY表示已经有另一个驱动占用了所要申请的中断线。,.,98,注册中断服务例程,request_irq的参数说明:unsignedintirq,要申请的中断号。irqreturn_t(*handler)(int,void*,structpt_regs*),要安装的中断处理函数指针。constchar*dev_name,用在/proc/interrupts中显示中断的拥有者。,.,99,注册中断服务例程,request_irq的参数说明:unsignedlongflags,与中断管理相关的位掩码选项。Flags的每个位有不同含义SA_INTERRUPT当该位被设置时,表示这是一个“快速”中断。快速中断处理例程运行时,屏蔽中断。SA_SHIRQ这个位表示中断可以在设备间共享。void*dev_id这个指针用于共享的中断号。做为驱动程序的私有数据区(可用来识别那个设备产生的中断)。不使用共享中断线方式时,可设置为NULL。,.,100,proc文件系统中的中断信息,/proc/interrupts反映系统的中断信息第一列是IRQ号给出每个中断线发生中断的次数。给出处理中断的可编程中断控制器。给出在该中断号上注册中断处理例程的设备名称。,.,101,实现中断处理例程,中断处理例程特别之处:在中断时间内运行,不能向用户空间发送或者接收数据。不能做任何导致休眠的操作。不能调用schedule函数。无论快速还是慢速中断处理例程,都应该设计成执行时间尽可能短。,.,102,实现中断处理例程,中断处理函数的参数和返回值irqreturn_t(*handler)(intirq,void*dev_id,structpt_regs*regs)Irq中断号Dev_id驱动程序可用的数据区,通常可传递指向描述设备的数据结构指针。structpt_regs*regs,保存了处理器进入中断代码之前的cpu寄存器的值。一般驱动可不要。,.,103,实现中断处理例程,启动和禁用中断驱动禁止特定中断线的中断:#include.voiddisable_irq(intirq);voidenable_irq(intirq);禁止所有中断voidlocal_irq_save(unsignedlongflags);local_irq_save在当前处理器上禁止中断递交,在保存当前中断状态到flags。voidlocal_irq_disable(
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年重庆八中宏帆中学小升初自主招生数学试题(含答案详解)
- 2025年事业单位工勤技能-湖南-湖南家禽饲养员三级(高级工)历年参考题库典型考点含答案解析
- 2025年事业单位工勤技能-湖南-湖南动物检疫员四级(中级工)历年参考题库典型考点含答案解析
- 2025年事业单位工勤技能-湖北-湖北水生产处理工二级(技师)历年参考题库典型考点含答案解析
- 2025年事业单位工勤技能-湖北-湖北林木种苗工一级(高级技师)历年参考题库典型考点含答案解析
- 2025年建筑行业新型保温材料市场趋势与技术创新报告
- 2025年事业单位工勤技能-海南-海南水文勘测工二级(技师)历年参考题库含答案解析
- 2025年事业单位工勤技能-海南-海南印刷工一级(高级技师)历年参考题库含答案解析
- 2025年事业单位工勤技能-浙江-浙江土建施工人员二级(技师)历年参考题库含答案解析(5套)
- 2025-2030中国立体护围卫生巾行业竞争优势及投资战略研究报告
- 2025年匹克球裁判试题及答案
- 2025秋苏教版科学三年级上册教学设计(附目录)
- 2025国家能源投资集团有限责任公司审计中心社会招聘12人笔试参考题库附带答案详解(10套)
- 《初中必读名著导读:《水浒传》核心知识点与深度解读》
- 深圳微利房管理办法
- 诊断学血管检查
- 大连市甘井子区社区工作者招聘笔试真题2024
- 生产安全会议纪要
- 护理文书书写PDCA案例
- 哪个团队收益大+课件2025-2026学年+北师大版(2024)八年级数学上册
- 制作瓷器培训课件
评论
0/150
提交评论