Linux设备驱动开发ppt课件.ppt_第1页
Linux设备驱动开发ppt课件.ppt_第2页
Linux设备驱动开发ppt课件.ppt_第3页
Linux设备驱动开发ppt课件.ppt_第4页
Linux设备驱动开发ppt课件.ppt_第5页
已阅读5页,还剩129页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1 Linux设备驱动设计kelvin ema 设备驱动概述 设备由两部分组成 一个是被称为控制器的电器部分 另一个是机械部分 一组寄存器组被赋予到各个控制器 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下设备的属性设备的类型 字符设备 块设备 网络设备主设备号 标识设备对应的驱动程序 一般 一个主设备号对应一个驱动程序 次设备号 每个驱动程序负责管理它所驱动的几个硬件实例 这些硬件实例则由次设备号来表示 同一驱动下的实例编号 用于确定设备文件所指的设备 可通过ls l 设备文件名 命令查看设备的主次设备号 以及设备的类型 11 Linux设备驱动 Linux设备驱动程序是一组由内核中的相关子例程和数据组成的I O设备软件接口 每当用户程序要访问某个设备时 它就通过系统调用 让内核代替它调用相应的驱动例程 这就使得控制从用户进程转移到了驱动例程 当驱动例程完成后 控制又被返回至用户进程 12 一些重要的数据结构 大部分驱动程序涉及三个重要的内核数据结构 文件操作结构体文件对象file结构体索引节点inode结构体 13 一些重要的数据结构 文件操作结构体结构体在头文件linux fs h中定义 用来存储驱动内核模块提供的对设备进行各种操作的函数的指针 结构体的每个域都对应着驱动模块用来处理某个被请求的事务的函数的地址 struct structmodule owner ssize t read structfile char user size t loff t ssize t write structfile constchar user size t loff t 14 一些重要的数据结构 重要的成员Structmodule owner 指向拥有该结构体的模块的指针 内核使用该指针维护模块使用计数 方法llseek用来修改文件的当前读写位置 把新位置作为返回值返回 loff t是在LINUX中定义的长偏移量方法read用来从设备中读取数据 非负返回值表示成功读取的直接数 方法write向设备发送数据 方法ioctl提供一种执行设备特定命令的方法 15 一些重要的数据结构 重要的成员unsignedint poll structfile structpoll table struct 系统调用select和poll的后端实现 用这两个系统调用来查询设备是否可读写 或是否处于某种状态 如果poll为空 则驱动设备会被认为即可读又可写 返回值是一个状态掩码 int mmap structfile structvm area struct 将设备内存映射到进程地址空间 16 一些重要的数据结构 重要的成员驱动内核模块是不需要实现每个函数的 相对应的的项就为NULL Gcc的语法扩展 使得可以定义该结构体 structfops read device read write device write open device open release device release 没有显示声明的结构体成员都被gcc初始化为NULL 17 一些重要的数据结构 重要的成员标准C的标记化结构体的初始化方法 structfops read device read write device write open device open release device release 推荐使用该方法 提高移植性 方法允许对结构体成员进行重新排列 没有显示声明的结构体成员同样都被gcc初始化为NULL 指向结构体的指针通常命名为fops 18 一些重要的数据结构 文件对象file结构体文件对象file代表着一个打开的文件 进程通过文件描述符fd与已打开文件的file结构相联系 进程通过它对文件的线性逻辑空间进行操作 例如 file f op read Structfile在中定义 指向结构体structfile的指针通常命名为filp 或者file 建议使用文件指针filp 19 一些重要的数据结构 文件对象file结构体的成员Struct 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 struct 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 struct fops major是给定的主设备号 为0代表什么 name是驱动的名字 将出现在 proc devices fops是设备驱动的结构 register chrdev将给设备分配0 255的次设备号 并且为每一个建立一个缺省的cdev结构 从系统中卸载字符设备的函数 intunregister chrdev unsignedintmajor constchar name 30 Open方法 编写字符设备驱动的第三步 定义设备驱动与文件系统的接口 结构体的函数定义 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 int 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 信号量的定义 structsemaphore atomic 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 1 SMPCPU 0EIP 0060 NottaintedEFLAGS 00010246 2 6 6 EIPisatoops example write 0 x4 0 x10 oops example eax 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 void local irq disable关闭本地中断递交而不保存状态 104 实现中断处理例程 打开中断 voidlocal irq restore unsignedlongflags 恢复由local irq save存储于flags的状态 而local irq enable无条件打开中断 voidlocal irq enable void 105 顶半部和底半步 中断处理的一个主要问题是如何在处理中进行长时间的任务 响应一次设备中断需要完成一定数量的工作 但是中断处理需要很快完成并且不使中断阻塞太长 Linux把中断处理例程分两部分 顶部分 实际响应中断的例程 底部分 被顶部分调用 通过开中断的方式进行 两种机制实现 Tasklet工作队列workqueue 106 顶半部和底半部 顶半部顶半部的功能是 登记中断 当一个中断发生时 它进行相应地硬件读写后就把中断例程的下半部挂到该设备的底半部执行队列中去 顶半部执行的速度就会很快 可以服务更多的中断请求 底半部仅有 登记中断 是远远不够的 因为中断的事件可能很复杂 Linux引入了一个底半部 来完成中断事件的绝大多数使命 底半部和顶半部最大的不同是底半部是可中断的 而顶半部是不可中断的 底半部几乎做了中断处理程序所有的事情 而且可以被新的中断打断 底半部则相对来说并不是非常紧急的 通常还是比较耗时的 因此由系统自行安排运行时机 不在中断服务上下文中执行 107 小任务机制tasklet 内核在BH机制的基础上进行了扩展 实现 软中断请求 softirq 机制 利用软中断代替bottomhalfhandler的处理 tasklet机制正是利用软中断来完成对驱动bottomhalf的处理 tasklet会让内核选择某个合适的时间来执行给定的小任务 tasklet所对应的处理函数就是tasklet action 这个处理函数在系统启动时

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论