Linux设备驱动开发

上传人:tia****g98 文档编号:244830052 上传时间:2024-10-06 格式:PPT 页数:133 大小:565KB
返回 下载 相关 举报
Linux设备驱动开发_第1页
第1页 / 共133页
Linux设备驱动开发_第2页
第2页 / 共133页
Linux设备驱动开发_第3页
第3页 / 共133页
点击查看更多>>
资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,Linux,设备驱动设计,梁红波,1,设备驱动概述,设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。,一组寄存器组被赋予到各个控制器。I/O端口包含4组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。,状态寄存器拥有可以被CPU读取的(状态)位,用来 指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。,控制寄存器则用于启动一条命令(指令)或者改变设备的(工作)模式。,数据输入寄存器用于获取输入的数据。,数据输出寄存器则向CPU发送结果。,设备驱动概述,操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备。,设备驱动程序是操作系统内核和机器硬件之间的接口,系统调用是操作系统内核和应用程序之间的接口。,在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作.,设备驱动概述,驱动完成以下的功能:,对设备初始化和释放.,把数据从内核传送到硬件和从硬件读取数据.,读取应,用,程序传送给设备文件的数据和回送应,用,程序请求的数据.,检测和处理设备出现的错误.,4,设备驱动概述,无操作系统的设备驱动,有操作系统的设备驱动,Application,Driver,Hardware,Application,Lib API,System call,Embedded OS,Hardware,不带操作系统软件结构 带操作系统软件结构,Driver,5,Linux设备驱动,6,Linux设备驱动,用户级的程序使用内核提供的标准系统调用来与内核通讯,这些系统调用有:open(), read(), write(), ioctl(), close() 等等。,Linux的内核是映射到每一个进程的高1G空间。每一个用户进程运行时都好像有一份内核的拷贝,每当用户进程使用系统调用时,都自动地将运行模式从用户级转为内核级,此时进程在内核的地址空间中运行。,7,Linux设备驱动,Linux内核使用“设备无关”的I/O子系统来为所有的设备服务。,每个设备都提供标准接口给内核,尽可能地隐藏了自己的特性。,用户程序使用一些基本的系统调用从设备读取数据并且将它们存入缓冲的例子。我们可以看到,每当一个系统调用被使用时,内核就转到相应的设备驱动例程来操纵硬件。,8,Linux设备驱动,Linux操作系统把设备,纳入文件系统的范畴,来管理。,每个设备在Linux系统上看起来都像一个文件,它们存放在/dev目录中,称为设备节点。,对文件操作的系统调用大都适用于设备文件。,Linux设备驱动,Linux下设备的属性,设备的类型:字符设备、块设备、网络设备,主设备号:标识设备对应的驱动程序。一般“一个主设备号对应一个驱动程序”,次设备号:,每个驱动程序负责管理它所驱动的几个硬件实例,这些硬件实例则由次设备号来表示。同一驱动下的实例编号,用于确定设备文件所指的设备。,可通过ls l “设备文件名”命令查看设备的主次设备号,以及设备的类型。,10,Linux设备驱动,Linux,设备驱动程序是一组由内核中的相关子例程和数据组成的I/O设备软件接口。,每当用户程序要访问某个设备时,它就通过系统调用,让内核代替它调用相应的驱动例程。这就使得控制从用户进程转移到了驱动例程,当驱动例程完成后,控制又被返回至用户进程。,11,一些重要的数据结构,大部分驱动程序涉及三个重要的内核数据结构:,文件操作,file_operations结构体,文件对象file结构体,索引节点inode结构体,12,一些重要的数据结构,文件操作结构体,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 *);,。,13,一些重要的数据结构,file_operations重要的成员,Struct module *owner,,指向拥有该结构体的模块的指针。内核使用该指针维护模块使用计数。,方法,llseek,用来修改文件的当前读写位置,把新位置作为返回值返回。loff_t是在LINUX中定义的长偏移量,方法,read,用来从设备中读取数据。非负返回值表示成功读取的直接数。,方法,write,向设备发送数据。,方法,ioctl,提供一种执行设备特定命令的方法。,14,一些重要的数据结构,file_operations重要的成员,unsigned int (*poll) (struct,file,*, struct poll_table_struct *);,系统调用select和poll的后端实现,用这两个系统调用来查询,设备是否可读写,或是否处于某种状态。如果poll为空,则驱动设备会被认为即可读又可写,返回值是一个状态掩码。,int (*mmap) (struct,file,*, struct vm_area_struct *);将设备内存映射到进程地址空间,15,一些重要的数据结构,file_operations重要的成员,驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为,NULL,。,Gcc的语法扩展,使得可以定义该结构体:,struct file_operations fops = ,read: device_read,write: device_write,open: device_open,release: device_release,;,没有显示声明的结构体成员都被gcc初始化为,NULL,。,16,一些重要的数据结构,file_operations重要的成员,标准C的标记化结构体的初始化方法:,struct file_operations,fops,= ,.read = device_read,.write = device_write,.open = device_open,.release = device_release,;,推荐使用该方法,提高移植性,方法允许对结构体成员进行重新排列。,没有显示声明的结构体成员同样都被gcc初始化为,NULL。,指向结构体file_operations的指针通常命名为fops。,17,一些重要的数据结构,文件对象file结构体,文件对象file代表着一个打开的文件,。,进程通过文件描述符fd与已打开文件的file结构相联系。进程通过它对文件的线性逻辑空间进行操作。例如:file-f_op-read();,Struct file 在中定义。,指向结构体,struct file,的指针通常命名为filp,或者file。建议使用文件指针filp。,18,一些重要的数据结构,文件对象file结构体的成员,Struct file_operations *f_op;,与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件操作结构体。用C语言实现面向对象编程的方法重载。,其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体,file,,只是使用,file,中的数据。,19,一些重要的数据结构,索引节点inode结构,文件打开,在内存建立副本后,由,唯一的索引节点inode,描述。,与file结构不同。,file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。,Inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。,一个文件在内存里面只有一个inode结构对应。,20,一些重要的数据结构,索引节点inode结构,Inode结构包含大量描述文件信息的成员变量。,但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。,Dev_t i_rdev,; 包含真正的设备编号。,Struct cdev *i_cdev,; 指向cdev结构体的指针。cdev是表示字符设备的内核数据结构。,从inode中获得主设备号和次设备号的宏:,Unsigned int,iminor,(struct inode *inode);,Unsigned int,imajor,(struct inode *inode);,21,Linux设备驱动,主设备号和次设备号的内部表达:,Dev_t类型用于保存设备号,称为设备编号。/linux/types.h文件中定义。,目前设备编号dev_t是一个32位的整数,其中12位表示主设备号,20位表示次设备号。,通过设备编号获取主次设备号:,MAJOR(dev_t dev);,MINOR(dev_t dev);,通过主次设备号合成设备编号:,MKDEV(int major, int minor);,Dev_t格式以后可能会发生变化,但只要使用这些宏,就可保证设备驱动程序的正确性。,22,分配和释放字符设备号,编写驱动程序要做的第一件事,为字符设备获取一个设备号。,事先知道所需要的设备编号(主设备号)的情况:,int register_chrdev_region(dev_t first, unsigned count, const char *name),first是要分配的起始设备编号值。 first的次设备号通常设置为0。,Count 所请求的连续设备编号的个数。,Name设备名称,指和该编号范围建立关系的设备。,分配成功返回0。,23,分配和释放字符设备号,动态分配设备编号(主要是主设备号),int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name),dev 是一个仅用于输出的参数, 它在函数成功完成时保存已分配范围的第一个编号。,baseminor 应当是请求的第一个要用的次设备号,它常常是 0.,count 和 name 参数跟request_chrdev_region 的一样.,24,分配和释放字符设备号,不再使用时,释放这些设备编号。使用以下函数:,void unregister_chrdev_region(dev_t from, unsigned count),在模块的卸载函数中调用该函数。,25,分配和释放字符设备号,新驱动程序,建议使用动态分配机制获取主设备号,也就是使用alloc_chrdev_region()。,动态分配导致无法预先创建设备节点。,可在分配设备号后,从/proc/devices文件中获取。,为了加载后自动创建设备文件,可以通过编写内核模块加载脚本实现。,26,字符设备的注册,内核内部使用struct cdev结构表示字符设备。,编写设备驱动的第二步就是注册该设备。,包含头文件。,获取一个独立的cdev结构:,struct cdev *my_cdev = cdev_alloc();,调用cdev_init初始化cdev结构体,void cdev_init(struct cdev *cdev, struct file_operations *fops);,初始化该设备的所有者字段:,dev-cdev.owner = THIS_MODULE;,初始化该设备的可用操作集:,dev-cdev.ops = ,27,字符设备的注册,编写设备驱动的第二步就是注册该设备。,cdev 结构已建立和初始化, 最后通过cdev_add函数把它告诉内核:,int cdev_add(struct cdev *dev, dev_t num, unsigned int count);,dev 是要添加的设备的 cdev 结构,num 是这个设备对应的第一个设备编号,count 是应当关联到设备的设备号的数目.,卸载字符设备时,调用相反的动作函数:,void cdev_del(struct cdev *dev);,28,设备的注册,早期方法:,内核中仍有许多字符驱动不使用刚刚描述过的cdev 接口。没有更新到 2.6 内核接口的老代码。,注册一个字符设备的早期方法:,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);,29,Open方法,编写字符设备驱动的第三步:定义设备驱动与文件系统的接口,file_operation结构体的函数定义。,open 方法,int (*open)(struct inode *inode, struct file *filp);,驱动程序提供open 方法,让用户进程使用设备之前,进行一些初始化的工作。,检查设备特定的错误。,如果第一次打开设备, 则初始化设备。,如果需要, 更新 f_op 指针,更换操作方法集。,分配并填充,要放进 filp-private_data 的任何数据结构。,30,Open方法,对于设备文件,inode 参数只有两个参数对设备驱动有用的。,Dev_t i_rdev; 包含真正的设备编号。,Struct cdev *i_cdev; 指向cdev结构体的指针。,i_cdev里面包含我们之前建立的 cdev 结构。但是有时候,我们需要的是包含 cdev 结构的描述设备的结构。,使用通过成员地址获取结构体地址的宏,container_of,,在 中定义:,container_of(pointer, container_type, container_field);,这个宏使用一个指向 container_field 类型的成员的指针, 它在一个 container_type 类型的结构中,宏通过分析他们关系,返回指向包含该成员的结构体指针.,31,Open方法,在 myscull_open, 这个宏用来找到适当的设备结构:,dev = container_of(inode-i_cdev, struct scull_dev, cdev);,找到 myscull_dev 结构后, scull 在filp-private_data 中存储其指针, 为以后存取使用.,filp-private_data = dev;,32,release 方法,release 方法做open相反的工作,释放 open 分配给filp-private_data的内存空间。,在最后一次的关闭操作时,关闭设备。,不是每个 close 系统调用引起调用 release 方法。,33,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 char _user *buff, size_t count, loff_t *offp);,filp,是文件对象指针,count,是请求的传输数据大小.,buff,参数对write来说是指向持有被写入数据的缓存, 对read则是放入新数据的空缓存.,offp,是指向一个“long offset type”的指针, 它指出用户正在存取的文件位置.,返回值,是“signed size type”类型;,34,Read和Write方法,read 和 write 方法的,buff 参数是用户空间指针,,不能被内核代码直接解引用。,_user,字符串只是形式上的说明,表明是用户空间地址。,驱动必须能够存取用户空间缓存以完成它的工作。内核如何解决这个问题?,为安全起见,内核提供专用的函数来完成对用户空间的存取。这些专用函数在,中声明。,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);,大多数读写函数都会调用这两个函数,用于跟应用程序空间交流信息。,35,Read和Write方法,典型的Read函数对参数的使用。,36,llseek函数,llseek函数用于对设备文件访问定位。,驱动接口,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、SEEK_CUR 和 SEEK_END 是 System V 引入的,是 0、1 和 2。,37,ioctl,进行超出简单的数据传输之外的操作,进行各种硬件控制操作. ioctl 方法和用户空间版本不同的原型:,int (*,ioctl,) (struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg),不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsigned long的形式传递。,返回值,POSIX 标准规定:如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为“不合适的设备 ioctl。,-EINVAL也是相当普遍的。,38,结构化设备驱动程序,设备结构体,把与某设备相关的所有内容定义为一个设备结构体,其中包括设备驱动涉及的硬件资源、全局软件资源、控制(自旋锁、互斥锁、等待队列、定时器等),在涉及设备的操作时,就仅仅操作这个结构体,39,Linux设备驱动的并发控制,40,设备驱动的并发控制,在驱动程序中,当多个线程同时访问相同的资源时,可能会引发“竞态”,必须对共享资源进行并发控制。,并发和竞态广泛存在。,并发控制的目的:,使得线程访问共享资源的操作是原子操作。,原子操作:,在执行过程中不会被别的代码路径所中断的操作。,驱动程序中的全局变量是一种典型的共享资源。,41,考虑一个非常简单的共享资源的例子:一个全局整型变量和一个简单的临界区,其中的操作仅仅是将整型变量的值增加,1,:,i+,该操作可以转化成下面三条机器指令序列:,得到当前变量,i,的值并拷贝到一个寄存器中,将寄存器中的值加,1,把,i,的新值写回到内存中,原子操作,42,原子操作,内核任务,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) -,可能的实际执行结果:,期望的结果,43,Linux内核的并发控制,在内核空间的内核任务需要考虑同步,内核空间中的共享数据对内核中的所有任务可见,所以当在内核中访问数据时,就必须考虑是否会有其他内核任务并发访问的可能、是否会产生竞争条件、是否需要对数据同步。,44,确定保护对象,找出哪些数据需要保护是关键所在,内核任务的局部数据仅仅被它本身访问,显然不需要保护。,如果数据只会被特定的进程访问,也不需加锁,大多数内核数据结构都需要加锁,:若有其它内核任务可以访问这些数据,那么就给这些数据加上某种形式的锁;若任何其它东西能看到它,那么就要锁住它。,Linux内核的并发控制,45,Linux内核的并发控制,并发控制的机制,中断屏蔽,原子数操作,自旋锁和信号量都是解决并发问题的机制。,中断屏蔽很少被单独使用,原子操作只能针对整数来进行。因此自旋锁和信号量应用最为广泛。,46,中断屏蔽,单CPU系统中,避免竟态的一种简单方式,保证正在执行的内核执行路径不被中断处理程序所抢占,防止竟态条件的发生。,Local_irq_disable()/关中断,Critical section /临界区,Local_irq_enable() /开中断,中断对内核非常重要,长时间屏蔽中断非常危险!,只适合短时间的关闭,对SMP多CPU引发的竟态无效,47,锁机制可以避免竞争状态正如门锁和门一样,门后的房间可想象成一个临界区。,在一段时间内,房间里只能有一个内核任务存在,当一个任务进入房间后,它会锁住身后的房门;当它结束对共享数据的操作后,就会走出房间,打开门锁。如果另一个任务在房门上锁时来了,那么它就必须等待房间内的任务出来并打开门锁后,才能进入房间。,加锁机制,48,任何要访问临界资源的代码首先都需要占住相应的锁,这样该锁就能阻止来自其它内核任务的并发访问,:,任务 1,试图锁定队列,成功:获得锁,访问队列,为队列解除锁,任务,2,试图锁定队列,失败:等待,等待,等待,成功:获得锁,访问队列,为队列解除锁,加锁机制,49,原子数操作,整型原子数操作,原子变量初始化,atomic_t test = ATOMIC_INIT(i);,设置原子变量的值,void atomic_set(atomic_t *v, int i),获得原子变量的值,atomic_read(v),原子变量加,void atomic_add(int i, atomic_t *v),原子变量减,void atomic_sub(int i, atomic_t *v),50,原子数操作,整型原子数操作,原子变量的自增操作,void atomic_inc(atomic_t *v),原子变量的自减操作,void atomic_dec(atomic_t *v),操作并测试 (测试其是否为0,0为true,否为false),atomic_inc_and_test(atomic_t *v),atomic_dec_and_test(atomic_t *v),int atomic_sub_and_test(int i, atomic_t *v),操作并返回 (返回新值),int atomic_add_return(int i, atomic_t *v),int atomic_sub_return(int i, atomic_t *v),51,原子数操作,原子位操作,设置位,void set_bit(int nr, volatile unsigned long * addr),清除位,void clear_bit(int nr, volatile unsigned long * addr),改变位,change_bit(nr,p),测试位,test_bit(int nr, const volatile unsigned long * p),测试并操作位,test_and_set_bit(nr,p),52,自旋锁,自旋锁是专为,防止多处理器并发,而引入的一种锁,它在内核中大量应用于中断处理等部分。而对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。,自旋锁最多只能被一个内核任务持有,若一个内核任务试图请求一个已被持有的自旋锁,那么这个任务就会一直进行,忙循环,也就是旋转,等待锁重新可用。,自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。,53,自旋锁,自旋锁的初衷就是:,在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(,特别浪费处理器时间,),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。,54,自旋锁,自旋锁防止在不同CPU上的执行单元对共享资源的同时访问,以及不同进程上下文互相抢占导致的对共享资源的非同步访问。,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。,自旋锁,不允许任务睡眠,。,55,自旋锁,自旋锁的基本形式如下:,spin_lock(,/*临界区*/,spin_unlock(,56,自旋锁,自旋锁原语要求包含文件是 . 锁的类型是 spinlock_t.,锁的两种初始化方法:,spinlock_t my_lock = SPIN_LOCK_UNLOCKED;,void spin_lock_init(spinlock_t *lock);,进入一个临界区前, 必须获得需要的 lock。,void spin_lock(spinlock_t *lock);,自旋锁等待是不可中断的。一旦你调用spin_lock, 将自旋直到锁变为可用。,释放一个锁:,void spin_unlock(spinlock_t *lock);,57,自旋锁,关中断的自旋锁,Spin_lock_irq( ),Spin_unlock_irq( ),Spin_lock_irqsave ( ),Spin_unlock_irqrestore ( ),58,信号量,Linux中的信号量是一种睡眠锁。,如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。,当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。,信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;,信号量的操作,信号量支持两个原子操作,P(),和,V(),,前者做测试操作,后者叫做增加操作。,Linux中分别叫做,down(),和,up()。,59,信号量,down(),和,up()。,down(),操作通过对信号量计数减,1,来请求获得一个信号量。,如果结果是,0,或大于,0,,信号量锁被获得,任务就可以进入临界区了。,如果结果是负数,任务会被放入等待队列,处理器执行其它任务。,相反,当临界区中的操作完成后,,up(),操作用来释放信号量,增加信号量的计数值,。,如果在该信号量上的等待队列不为空,处于队列中等待的任务在被唤醒的同时会获得该信号量。,60,信号量,61,信号量,62,Linux信号量的实现,内核代码必须包含 ,才能使用信号量。,相关的类型是 struct semaphore,信号量的定义,struct semaphore atomic_t count; int sleepers; wait_queue_head_t wait; ,63,Linux信号量的实现,信号量的声明和初始化,直接创建一个信号量 struct semaphore * sem;,接着使用 sema_init 来初始化这个信号量:,void sema_init(struct semaphore *sem, int val);,互斥模式的信号量声明,内核提供宏定义.,DECLARE_MUTEX(name);,信号量初始化为 1,DECLARE_MUTEX_LOCKED(name);,信号量初始化为0,64,Linux信号量的实现,动态分配的互斥信号量声明,void init_MUTEX(struct semaphore *sem);,信号量初始化为 1,void init_MUTEX_LOCKED(struct semaphore *sem);,信号量初始化为0,65,Linux信号量的实现,信号量的P操作,void down(struct semaphore *sem);,down减小信号量的值,并根据信号量的值决定是否等待。不可中断的等待。,int down_interruptible(struct semaphore *sem);,操作是可中断的。,int down_trylock(struct semaphore *sem);,信号量在调用时不可用, down_trylock 立刻返回一个非零值.,66,Linux信号量的实现,信号量的V操作,void up(struct semaphore *sem);,通过down操作进入临界区的进程,再退出的时候都需要调用一个up操作,释放信号量。,67,Linux信号量的实现,信号量基本使用形式为:,static DECLARE_MUTEX(mr_sem);/声明互斥信号量,if(down_interruptible(&mr_sem),/*可被中断的睡眠,当信号来到,睡眠的任务被唤醒 */,/*临界区*/,up(,操作配套使用,68,Linux,设备驱动调试,69,内核调试选项,内核开发者在内核自身中构建了多个调试特性。这些特性会产生额外的输出并降低性能,Linux,发行版的内核为了提高性能,去除这些调试特性。,用来开发的内核应当激活的调试配置选项,是在,“,kernel hacking,”,菜单中。,70,通过打印调试,Printk,printk 通过附加不同的消息优先级在消息上,对消息的严重程度进行分类。在 定义了8个loglevel。,DEFAULT_MESSAGE_LOGLEVEL为默认级别(printk.c ),当消息优先级小于console_loglevel,信息才能显示出来。而console_loglevel的初值为DEFAULT_CONSOLE_LOGLEVEL。,通过对/proc/sys/kernel/printk的访问来改变console_loglevel的值。该文件包含四个数字:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。,echo 1 /proc/sys/kernel/printk,echo 8 /proc/sys/kernel/printk,71,通过打印调试,打开和关闭消息,通过封装printk函数,快速打开调试信息或者关闭调试信息。,# define,PDEBUG(fmt, args.),printk( KERN_DEBUG “myscull: fmt, # args),通过在Makefile里面定义调试开关变量去决定调试信息是否打开。,72,通过查询调试,获取相关信息的最好方法:在需要的时候才去查询系统信息,而不是持续不断地产生数据。,/proc文件系统是一种特殊的、由软件创建的文件系统,内核使用他向外界导出信息。,/proc下面的每个文件都,绑定于一个内核函数,,用户读取其中的文件时,该函数动态的生成文件的内容。 例如/proc/devices,73,通过查询调试,包含,在驱动中定义跟proc文件绑定的内核函数,read_proc,,在函数里面定义要输出的信息。,在初始化函数中调用,creat_proc_read_entry,函数将/proc入口文件和read_proc函数联系起来。,卸载模块时调用,remove_proc_entry,撤销proc入口。,74,通过查询调试,read_proc函数,int (*,read_proc,)(char *page, char *start, off_t offset, int count, int *eof, void *data);,page 是输出数据的缓存内存页。进程读取/proc文件时,内核会分配一个内存页,read_proc将数据通过这个内存页返回到用户空间。,start 是这个函数用来说有关的数据写在页中哪里,eof,当没有数据可返回时,驱动设置这个参数。,Data是提供给驱动的专用数据指针。,75,通过查询调试,int sprintf (char *buf, const char *fmt, .),将数据打包成字符流的形式。,内核很多象printk函数一样,通过库函数的形式提供给内核开发者的函数,以满足内核开发中的一些简单的需要。,void *memset (void *s, char c, size_t count);,void *memcpy (void *dest, const void *src, size_t count);,76,通过查询调试,creat_proc_read_entry函数,struct proc_dir_entry *,create_proc_read_entry,(const char *,name,mode_t,mode, struct proc_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).,77,查看Oops信息,大多数bug通常是因为废弃了一个NULL指针或者使用了错误的指针值。这类bug导致的结果通常是一条oops消息。,一条oops消息能够显示发生故障时CPU的状态,以及CPU寄存器的内容和其他看似难以理解的信息。,78,查看Oops信息,例如访问一个NULL指针。因为NULL不是一个可访问的指针值,所以会引发一个错误,内核会简单地将其转换为oops消息并显示。然后其调用进程会被杀死。,Unable to handle kernel NULL pointer dereference at virtual address 00000000,printing eip: d083a064 Oops: 0002 #1 SMP CPU: 0 EIP: 0060: Not tainted EFLAGS: 00010246 (2.6.6),EIP is at oops_example _write+0x4/0x10 oops_example,eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 00000000,79,通过监视调试,strace命令可以显示由用户空间程序所发出的所有系统调用。,还以符号形式显示调用的参数和返回值。当一个系统调用失败, 错误的符号值(例如, ENOMEM)和对应的字串(Out of memory) 都显示.,strace 有很多命令行选项;,-t 来显示每个调用执行的时间,-T 来显示调用中花费的时间,-e 来限制被跟踪调用的类型,-o 来重定向输出到一个文件. 缺省地, strace 打印调用信息到 stderr.,80,Linux,的内存分配,81,kmalloc函数,void *,kmalloc,(size_t size,int flags);,所分配到的内存在物理内存中连续且保持原有的数据(不清零)。,size,是要分配的块的大小。Linux 创建一系列内存对象slab,每个slab内的内存块大小是固定。处理分配请求时,就直接在包含有足够大内存块的slab中分配一个整块给请求者。,内核只能分配一些预定义的、固定大小的字节数组。kmalloc 能够处理的最小内存块是 32 或 64 字节(体系结构依赖),而内存块大小的上限随着体系和内核配置而变化。,不应分配大于 128 KB的内存。若需多于几个 KB的内存块,最好使用其他方法。,82,kmalloc函数,void *kmalloc(size_t size,int flags);,Flags 分配标志,表示如何分配空间 。所有标志都定义在 ,GFP_KERNEL,内存分配,是代表运行在,内核,空间的进程执行的,并且在空闲内存较少时把当前进程转入休眠以等待一个页面。,GFP_ATOMIC,内核通常会为原子性的分配预留一些空闲页。当当前进程不能被置为睡眠时,应使用 GFP_ATOMIC,这样kmalloc 甚至能够使用最后一个空闲页。如果连这最后一个空闲页也不存在,则分配返回失败。常用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠。,GFP_DMA,要求分配可用于DMA的内存。,83,后备高速缓存,内核为驱动程序常常需要反复分配许多相同大小内存块的情况,增加了一些特殊的内存池,称为后备高速缓存(lookaside cache)。 设备驱动程序通常不会涉及后备高速缓存,但是也有例外:在 Linux 2.6 中 USB 和 SCSI 驱动。,84,后备高速缓存,创建一个新的后备高速缓存 kmem_cache_create,name: name和这个后备高速缓存相关联;通常设置为被缓存的结构类型的名字,不能包含空格。,参数size:每个内存区域的大小。,offset:页内第一个对象的偏移量;用来确保被分配对象的特殊对齐,0 表示缺省值。,flags:控制分配方式的位掩码:,SLAB_NO_REAP 保护缓存在系统查找内存时不被削减,不推荐。,SLAB_HWCACHE_ALIGN 所有数据对象跟高速缓存行对齐,平台依赖,可能浪费内存。,SLAB_CACHE_DMA 每个数据对象在 DMA 内存区段分配.,85,后备高速缓存,创建一个新的后备高速缓存 kmem_cache_create,参数constructor 和 destructor 是可选函数,用来初始化新分配的对象和在内存被作为整体释放给系统之前“清理”对象。,86,后备高速缓存,kmem_cache_alloc 从已创建的后备高速缓存中分配对象:,void *kmem_cache_alloc(kmem_cache_t *cache, int flags);,flags 和kmalloc 的flags相同,使用 kmem_cache_free释放一个对象:,void kmem_cache_free(kmem_cache_t *cache, const void *obj);,当驱动用完这个后备高速缓存(通常在当模块被卸载时),释放缓存:,int kmem_cache_destroy(kmem_cache_t *cache);,87,get_free_page相关函数,如果一个模块需要分配大块的内存,最好使用面向页的分配技术。,_get_free_page(unsigned int flags);,返回一个指向新页的指针, 未清零该页,get_zeroed_page(unsigned int flags); 类似于_get_free_page,但用零填充该页,_get_free_pages(unsigned int flags, unsigned int order);,分配是若干(物理连续的)页面并返回指向该内存区域的第一个字节的指针,该内存区域未清零,flags 与 kmalloc 的用法相同,order 是请求或释放的页数以 2 为底的对数。若其值过大(没有这么大的连续区可用), 则分配失败,88,get_free_page相关函数,当程序不需要页面时,用下列函数之一来释放,void free_page(unsigned long addr);void free_pages(unsigned long addr, unsigned long order);,89,Linux,的中断处理,90,为什么会有中断,中断最初是为克服对I/O接口控制采用程序查询所带来的处理器低效率而产生的。,处理器速度一般比外设快很多,用轮询的方式来查询设备的状态,CPU效率不高,CPU和外设不能并行工作。,中断机制让CPU启动设备后,就去处理其他任务,只有当外设真正完成数据传输的准备,请求CPU服务的时候,CPU才转过来处理外设的请求。,91,中断和异常,外部中断:,外部设备所发出的,I/O,请求。,随着计算机系统结构的不断改进以及应用技术的日益提高,中断的适用范围也随之扩大,出现了所谓的内部中断(或叫异常)。,异常:,为解决机器运行时所出现的某些随机事件及编程方便而出现的。,92,I/O中断处理,为了保证系统对外部的响应,一个中断处理程序必须被尽快的完成。因此,把所有的操作都放在中断处理程序中并不合适,Linux中把紧随中断要执行的操作分为三类,紧急的(critical)一般关中断运行。诸如对PIC应答中断,对PIC或是硬件控制器重新编程,或者修改由设备和处理器同时访问的数据,非紧急的(noncritical)如修改那些只有处理器才会访问的数据结构(例如按下一个键后读扫描码),这些也要很快完成,因此由中断处理程序立即执行,不过一般在开中断的情况下,93,I/O中断处理,Linux中把紧随中断要执行的操作分为三类,非紧急可延迟的(noncritical deferrable),这些操作可以被延迟较长的时间间隔而不影响内核操作,有兴趣的进程将会等待数据。内核用下半部分这样一个机制来在一个更为合适的时机用独立的函数来执行这些操作。,如把缓冲区内容拷贝到某个进程的地址空间(例如把键盘缓冲区内容发送到终端处理程序进程)。,94,S3c2410的中断,中断发生时,需要知道中断的来源。,芯片的引线有限,很难提供很多条中断请求引线。使用中断控制器管理中断请求线。,S3c2410将中断控制器集成在CPU芯片中,“复用”GPIO端口引脚,具有作为中断请求线的功能。,SRCPND寄存器32位中的每一位对应着一个中断源,每一位被设置为1,则相应的中断源产生中断请求并且等待中断被服务。,95,注册中断服务例程,中断号是一个宝贵且常常有限的资源。内核维护一个中断号的注册表。,要使用中断,就要进行中断号的申请,也就是,IRQ(Interrupt ReQuirement),。,只有当设备需要中断的时候才申请占用一个,IRQ,,或者是在申请,IRQ,时采用共享中断的方式,让更多的设备使用中断。,96,注册中断服务例程,在 实现中断注册接口:,int,request_irq,(unsigned int,irq,irqreturn_t (*,handler,)(int, void *, struct pt_regs *),unsigned long,flags,const char *,dev_name,void *,dev_id,);,void,free_irq,(unsigned int,irq, void *,dev_id,);,request_irq 的返回值是 0 指示申请成功,为负值时表示错误码。函数返回 -EBUSY 表示已经有另一个驱动占用了所要申请的中断线。,97,注册中断服务例程,request_irq的参数说明:,unsigned int,irq,要申请的中断号。,irqreturn_t (*,handler,)(int, void *, struct pt_regs *),要安装的中断处理函数指针。,const char *,dev_name,用在 /proc/interrupts 中显示中断的拥有者。,98,注册中断服务例程,request_irq的参数说明:,unsigned long,flags,与中断管理相关的位掩码选项。,Flags的每个位有不同含义,SA_INTERRUPT 当该位被设置时, 表示这是一个“快速”中断。快速中断处理例程运行时,屏蔽中断。,SA_SHIRQ 这个位表示中断可以在设备间共享。,void *,dev_id,这个指针用于共享的中断号。做为驱动程序的私有数据区(可用来识别那个设备产生的中断)。不使用共享中断线方式时,可设置为NULL。,99,proc 文件系统中的中断信息,/proc/interrupts反映系统的中断信息,第一列是 IRQ 号,给出每个中断线发生中断的次数。,给出处理中断的可编程中断控制器。,给出在该中断号上注册中断处理例程的设备名称。,100,实现中断处理例程,中断处理例程特别之处:,在中断时间内运行,不能向用户空间发送或者接收数据。,不能做任何导致休眠的操作。,不能调用schedule函数。,无论快速还是慢速中断处理例程,都应该设计成执行时间尽可能短。,101,实现中断处理例程,中断处理
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!