深入浅出linux设备驱动编程(宋宝华老师)

上传人:子 文档编号:42623435 上传时间:2018-06-02 格式:DOCX 页数:106 大小:75.19KB
返回 下载 相关 举报
深入浅出linux设备驱动编程(宋宝华老师)_第1页
第1页 / 共106页
深入浅出linux设备驱动编程(宋宝华老师)_第2页
第2页 / 共106页
深入浅出linux设备驱动编程(宋宝华老师)_第3页
第3页 / 共106页
深入浅出linux设备驱动编程(宋宝华老师)_第4页
第4页 / 共106页
深入浅出linux设备驱动编程(宋宝华老师)_第5页
第5页 / 共106页
点击查看更多>>
资源描述

《深入浅出linux设备驱动编程(宋宝华老师)》由会员分享,可在线阅读,更多相关《深入浅出linux设备驱动编程(宋宝华老师)(106页珍藏版)》请在金锄头文库上搜索。

1、Linux 设备驱动编程宋宝华 1.引言 目前,Linux 软件工程师大致可分为两个层次:(1)Linux 应用软件工程师(Application Software Engineer):主要利用 C 库函数和 Linux API 进行应用软件的编写; (2)Linux 固件工程师(Firmware Engineer):主要进行 Bootloader、Linux 的移植及 Linux 设备驱动程序的设计。 一般而言,固件工程师的要求要高于应用软件工程师的层次,而其中的 Linux 设备驱动 编程又是 Linux 程序设计中比较复杂的部分,究其原因,主要包括如下几个方面:(1)设备驱动属于 Li

2、nux 内核的部分,编写 Linux 设备驱动需要有一定的 Linux 操作 系统内核基础; (2)编写 Linux 设备驱动需要对硬件的原理有相当的了解,大多数情况下我们是针对 一个特定的嵌入式硬件平台编写驱动的; (3)Linux 设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现 bug; (4)由于属于内核的一部分, Linux 设备驱动的调试也相当复杂。 目前,市面上的 Linux 设备驱动程序参考书籍非常稀缺,少有的经典是由 Linux 社区的 三位领导者 Jonathan Corbet、Alessandro Rubini、Greg Kroah-Hartman 编写的Lin

3、ux Device Drivers (目前该书已经出版到第 3 版,中文译本由中国电力出版社出版) 。该书将 Linux 设 备驱动编写技术进行了较系统的展现,但是该书所列举实例的背景过于复杂,使得读者需要 将过多的精力投放于对例子背景的理解上,很难完全集中精力于 Linux 驱动程序本身。往往 需要将此书翻来覆去地研读许多遍,才能有较深的体会。(Linux Device Drivers中英文版封面)本文将仍然秉承 Linux Device Drivers一书以实例为主的风格,但是实例的背景将非 常简单,以求使读者能将集中精力于 Linux 设备驱动本身,理解 Linux 内核模块、Linux

4、 设 备驱动的结构、Linux 设备驱动中的并发控制等内容。另外,与 Linux Device Drivers所 不同的是,针对设备驱动的实例,本文还给出了用户态的程序来访问该设备,展现设备驱动 的运行情况及用户态和内核态的交互。相信阅读完本文将为您领悟Linux Device Drivers 一书中的内容打下很好的基础。本文中的例程除引用的以外皆由笔者亲自调试通过,主要基于的内核版本为 Linux 2.4, 例子要在其他内核上运行只需要做少量的修改。构建本文例程运行平台的一个较好方法是:在 Windows 平台上安装 VMWare 虚拟机, 并在 VMWare 虚拟机上安装 Red Hat。

5、注意安装的过程中应该选中“开发工具”和“内核开 发”二项(如果本文的例程要在特定的嵌入式系统中运行,还应安装相应的交叉编译器,并包含相应的 Linux 源代码) ,如下图: 2.Linux 内核模块 Linux 设备驱动属于内核的一部分, Linux 内核的一个模块可以以两种方式被编译和加 载:(1)直接编译进 Linux 内核,随同 Linux 启动时加载; (2)编译成一个可加载和删除的模块,使用 insmod 加载( modprobe 和 insmod 命令类 似,但依赖于相关的配置文件) ,rmmod 删除。这种方式控制了内核的大小,而模块一旦被 插入内核,它就和内核其他部分一样。 下

6、面我们给出一个内核模块的例子: #include /所有模块都需要的头文件 #include / init static int _init hello_init (void) printk(“Hello module initn“); return 0; static void _exit hello_exit (void) printk(“Hello module exitn“); module_init(hello_init); module_exit(hello_exit); 分析上述程序,发现一个 Linux 内核模块需包含模块初始化和模块卸载函数,前者在 insmod 的时候运行,

7、后者在 rmmod 的时候运行。初始化与卸载函数必须在宏 module_init 和 module_exit 使用前定义,否则会出现编译错误。程序中的 MODULE_LICENSE(“GPL“)用于声明模块的许可证。如果要把上述程序编译为一个运行时加载和删除的模块,则编译命令为: gcc D_KERNEL_ -DMODULE DLINUX I /usr/local/src/linux2.4/include -c o hello.o hello.c 由此可见,Linux 内核模块的编译需要给 gcc 指示 D_KERNEL_ -DMODULE DLINUX 参数。 -I 选项跟着 Linux 内

8、核源代码中 Include 目录的路径。下列命令将可加载 hello 模块: insmod ./hello.o 下列命令完成相反过程: rmmod hello如果要将其直接编译入 Linux 内核,则需要将源代码文件拷贝入 Linux 内核源代码的相 应路径里,并修改 Makefile。我们有必要补充一下 Linux 内核编程的一些基本知识:内存在 Linux 内核模式下,我们不能使用用户态的 malloc()和 free()函数申请和释放内存。进 行内核编程时,最常用的内存申请和释放函数为在 include/linux/kernel.h 文件中声明的 kmalloc()和 kfree(),其

9、原型为: void *kmalloc(unsigned int len, int priority); void kfree(void *_ptr); kmalloc 的 priority 参数通常设置为 GFP_KERNEL,如果在中断服务程序里申请内存则 要用 GFP_ATOMIC 参数,因为使用 GFP_KERNEL 参数可能会引起睡眠,不能用于非进程 上下文中(在中断中是不允许睡眠的) 。由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而 应该使用 Linux 中的用户和内核态内存交互函数(这些函数在 include/asm/uaccess.h 中被声 明):

10、 unsigned long copy_from_user(void *to, const void *from, unsigned long n); unsigned long copy_to_user (void * to, void * from, unsigned long len); copy_from_user、copy_to_user 函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为 0。 include/asm/uaccess.h 中定义的 put_user 和 get_user 用于内核空间和用户空间的单值交互 (如 char、int、long) 。这里给出的仅仅

11、是关于内核中内存管理的皮毛,关于 Linux 内存管理的更多细节知识, 我们会在本文第 9 节内存与 I/O 操作进行更加深入地介绍。输出在内核编程中,我们不能使用用户态 C 库函数中的 printf()函数输出信息,而只能使用 printk()。但是,内核中 printk()函数的设计目的并不是为了和用户交流,它实际上是内核的 一种日志机制,用来记录下日志信息或者给出警告提示。每个 printk 都会有个优先级,内核一共有 8 个优先级,它们都有对应的宏定义。如果未 指定优先级,内核会选择默认的优先级 DEFAULT_MESSAGE_LOGLEVEL。如果优先级数 字比 int consol

12、e_loglevel 变量小的话,消息就会打印到控制台上。如果 syslogd 和 klogd 守护 进程在运行的话,则不管是否向控制台输出,消息都会被追加进/var/log/messages 文件。klogd 只处理内核消息,syslogd 处理其他系统消息,比如应用程序。模块参数 2.4 内核下,include/linux/module.h 中定义的宏 MODULE_PARM(var,type) 用于向模块 传递命令行参数。 var 为接受参数值的变量名, type 为采取如下格式的字符串 min-maxb,h,i,l,s。min 及 max 用于表示当参数为数组类型时,允许输入的数组元素

13、的个 数范围;b:byte;h:short;i:int;l:long;s:string。 在装载内核模块时,用户可以向模块传递一些参数: insmod modname var=value 如果用户未指定参数,var 将使用模块内定义的缺省值。 3.字符设备驱动程序 Linux 下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得 Windows 的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程 序可以象操作普通文件一样对硬件设备进行操作,如 open ()、close ()、read ()、write () 等。 Linux 主要将设备分为二类:字符设

14、备和块设备。字符设备是指设备发送和接收数据以 字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个 4 个字节的全局变 量 int global_var,而这个设备的名字叫做“ gobalvar” 。对“gobalvar”设备的读写等操作即 是对其中全局变量 global_var 的操作。驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对 所控设备的初始化工作,并调用 register_chrdev() 函数注册字符设备: static int _init gobalvar_in

15、it(void) if (register_chrdev(MAJOR_NUM, “ gobalvar “, 其中参数 inode 为设备特殊文件的 inode (索引结点) 结构的指针,参数 file 是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法 性(次设备号可以用 MINOR(inode- i - rdev) 取得)、控制使用设备的进程数、根据执行 情况 返回状态码(0 表示成功,负数表示存在错误 ) 等; release()函数当最后一个打开设备的用户进程执行 close ()系统调用时,内核将调用驱动程序的 release () 函数:

16、 void (*release) (struct inode * ,struct file *) ; release 函数的主要任务是清理未结束的输入 /输出操作、释放资源、用户自定义排他标志的复位等。 read()函数当对设备特殊文件进行 read() 系统调用时,将调用驱动程序 read() 函数: ssize_t (*read) (struct file *, char *, size_t, loff_t *); 用来从设备中读取数据。当该函数指针被赋为 NULL 值时,将导致 read 系统调用出错并返回-EINVAL(“Invalid argument,非法参数” ) 。函数返回非负值表示成功读取的字节 数(返回值为“signed size”数据类

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 生活休闲 > 科普知识

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号