嵌入式 Linux 下 USB 驱动程序的研 究与开发 指导老师 郭卫锋 03 级软件 2 班 何瓛
一 本课题设计(研究)的目的: 源代码开发的 Linux 以其自身的优势,吸引了众 多开发商的目光,成为嵌入式操作系统的新宠。 由于其源代码的开放,驱动的添加一般不用开 发者从头做起,主要是修改并编译相关驱动文 件,以适合自己需要。
二 目前设计(研究)中公认的重点与难点: 设备驱动属于 Linux 内核的部分,编写 Linux 设备驱动 需要有一定的 Linux 操作系统内核基础; 编写 Linux 设备驱动需要对硬件的原理有相当的了解, 大多数情况下是针对一个特定的嵌入式硬件平台编写 驱动的; Linux 设备驱动中广泛涉及到多进程并发的同步、互 斥等控制,容易出现 bug ; 由于属于内核的一部分, Linux 设备驱动的调试也相 当复杂。
设计(研究)中我所遇到的的重点与难点: 1. 尚且不太 熟悉嵌入式系统进行应用程序的开 发的工具与环境. 2. 开发者需要对嵌入式即插即用硬件设备有基 本的了解.
3 :若 USB 设备可以在任何时间点从系统中取 走(如 U 盘、 USB 读卡器、 MP3 或数码相机), 即使程序目前正在访问它。 USB 驱动程序必须 要能够很好地处理解决此问题,它需要能够切 断任何当前的读写,同时通知用户空间程序: USB 设备已经被取走。
三 Linux 上搭建 C/C++IDE 开发环境 1. 进行开发之前, 首先要有合适的开发环境. 而首先必须充 分地熟悉了 Redhat linux 操作系统. 2. 开发者可以使用 GLADE 做界面,用 emacs 或 vi 编辑 器 等等编辑程序,再用终端模拟器编辑开发项目。 GLADE 仍然不能算是一个 “ 集成开发环境 ”( 相比 VS STUDIO 来说 ) 。 而 Anjuta ,一种最近新兴的 Linux 开发环境, 可以将所有这些繁杂零散 的任务都在一个统一的、集成的、自然而然的环境下完成, 是 Linux 下 第一个正式的 IDE, 而非传统的记事本编辑器.
四 LINUX 下最基本的编译命令 运行 gcc Linux 中最重要的软件开发工具是 GCC 。 GCC 是 GNU 的 C 和 C++ 编译器。实际上, GCC 能够编译三种语言: C 、 C++ 和 ObjectC ( C 语 言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。 gcc 的主要选项 -ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些 特色,例如 asm 或 typeof 关键词。 -c 只编译并生成目标文件。 -DMACRO 以字符串 “1” 定义 MACRO 宏。 -DMACRO=DEFN 以字符串 “DEFN” 定义 MACRO 宏。 -E 只运行 C 预编译器。 -g 生成调试信息。 GNU 调试器可利用该信息。
五 流行的即插即用设备与硬件 PDIUSBD12 是一款性价比很高的 USB 器件, 虽然只支持 USB1.1 规 范, 但具备本地 DMA 直接传输能力, 可以将 uClinux 的驱动编译进系 统内核. 整个硬件框架如下 :
六 Linux 设备驱动之字符设备驱动程序 Linux 下的设备驱动程序被组织为一组完成不同任务的函 数的集合,通过这些函数使得 Windows 的设备操作犹如文 件一般。在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作, 如 open () 、 close () 、 read () 、 write () 等。 Linux 主要将设备分为二类:字符设备和块设备。字符设 备是指设备发送和接收数据以字符的形式进行;而块设备 则以整个数据缓冲区的形式进行。字符设备的驱动相对比 较简单。
六 Linux 设备驱动之字符设备驱动程序 驱动程序是内核的一部分,因此我们需要给其添加模块初始化函 数,该函数用来完成对所控设备的初始化工作,并调用 register_chrdev() 函数注册字符设备: static int __init gobalvar_init(void) { if (register_chrdev(MAJOR_NUM, " gobalvar ", &gobalvar_fops)) { //… 注册失败 } else { //… 注册成功 } }
六 Linux 设备驱动之字符设备驱动程序 与模块初始化函数对应的就是模块卸载函数,需要调用 register_chrdev() 的 " 反函数 " unregister_chrdev() : static void __exit gobalvar_exit(void) { if (unregister_chrdev(MAJOR_NUM, " gobalvar ")) { //… 卸载失败 } else { //… 卸载成功 } }
六 Linux 设备驱动编程中的阻塞与非阻塞 阻塞操作是指,在执行设备操作时,若不能获得资源,则 进程挂起直到满足可操作的条件再进行操作。非阻塞操作 的进程在不能进行设备操作时,并不挂起。被挂起的进程 进入 sleep 状态,被从调度器的运行队列移走,直到等待 的条件被满足。 在 Linux 驱动程序中,我们可以使用等待队列( wait queue ) 来实现阻塞操作。 wait queue 很早就作为一个基本的功能 单位出现在 Linux 内核里了,它以队列为基础数据结构, 与进程调度机制紧密结合,能够用于实现核心的异步事件 通知机制。等待队列可以用来同步对系统资源的访问.
六 设备驱动编程中的内存与 I/O 操作 每一种外设都是通过读写设备上的寄存器来进行的,通常 包括控制寄存器、状态寄存器和数据寄存器三大类,外设 的寄存器通常被连续地编址。根据 CPU 体系结构的不同, CPU 对 IO 端口的编址方式有两种: I/O 映射方式( I/O-mapped ) 内存映射方式( Memory-mapped )
六 Linux 设备驱动之并发控制 在驱动程序中,当多个线程同时访问相同 的资源时(驱动程序中的全局变量是一种 典型的共享资源),可能会引发 “ 竞态 ” ,因 此我们必须对共享资源进行并发控制。 Linux 内核中解决并发控制的最常用方法是 信号量
六 Linux 设备驱动之并发控制 与信号量相关的 API 主要有: 定义信号量 struct semaphore sem; 初始化信号量 void sema_init (struct semaphore *sem, int val); 该函数初始化信号量,并设置信号量 sem 的值为 val void init_MUTEX (struct semaphore *sem); 该函数用于初始化一个互斥锁,即它把信号量 sem 的值设置为 1 ,等同于 sema_init (struct semaphore *sem, 1) ; void init_MUTEX_LOCKED (struct semaphore *sem); 该函数也用于初始化一个互斥锁,但它把信号量 sem 的值设置为 0 ,等同于 sema_init (struct semaphore *sem, 0) ; 获得信号量 void down(struct semaphore * sem); 该函数用于获得信号量 sem ,它会导致睡眠,因此不能在中断上下文使用; int down_trylock(struct semaphore * sem); 该函数尝试获得信号量 sem ,如果能够立刻获得,它就获得该信号量并返回 0 ,否则, 返回非 0 值。它不会导致调用者睡眠,可以在中断上下文使用。 释放信号量 void up(struct semaphore * sem); 该函数释放信号量 sem ,唤醒等待者。
六 异步通知 结合阻塞与非阻塞访问可以较好地解决设备的读 写,但是如果有了异步通知就更方便了。异步通 知的意思是:一旦设备就绪,则主动通知应用程 序,这样应用程序根本就不需要查询设备状态 驱动程序中调用 fasync() 函数,并在 write() 函数中 当数据被写入时,调用 kill_fasync() 函数激发一个 通知信号
七 定时器 Linux 内核中定义了一个 timer_list 结构: struct timer_list { struct list_head list; unsigned long expires; // 定时器到期时间 unsigned long data; // 作为参数被传入定时器处理函数 void (*function)(unsigned long); }; 那么就能在驱动开发中加以利用 增加定时器 void add_timer(struct timer_list * timer); 删除定时器 int del_timer(struct timer_list * timer);
谢谢老师