编写Linux设备驱动程序的注意事项

简介: 编写Linux设备驱动程序的注意事项

编写设备驱动程序的注意事项

应用程序开发与驱动程序开发的差异

在Linux上的程序开发一般分为两种,一种是内核及驱动程序开发,另一种是应用程序开发。这两种开发种类对应Linux的两种状态,分别是内核态和用户态。内核态用来管理用户态的程序,完成用户态请求的工作;用户态处理上层的软件工作。驱动程序与底层的硬件交互,所以工作在内核态。

大多数程序员致力于应用程序的开发,少数程序员则致力于内核及驱动程序的开发。相对于应用程序的开发,内核及驱动程序的开发有很大的不同。最重要的差异包括以下几点:

  • 内核及驱动程序开发时不能访问C库,因为C库是使用内核中的系统调用来实现的,而且是在用户空间实现的。
  • 内核及驱动程序开发时必须使用GNU C,因为Linux操作系统从一开始就使用的是GNU C,虽然也可以使用其他的编译工具,但是需要对以前的代码做大量的修改。
  • 内核支持异步终端、抢占和SMP,,因此内核及驱动程序开发时必须时刻注意同步和并发。
  • 内核只有一个很小的定长堆栈。
  • 内核及驱动程序开发时缺乏像用户空间那样的内存保护机制。
  • 内核及驱动程序开发时浮点数很难使用,应该使用整型数。
  • 内核及驱动程序开发要考虑可移植性,因为对于不同的平台,驱动程序是不兼容的。

GUN C开发驱动程序

GUN C语言最早起源于一个GUN计划,GUN的意思是“GUN is not UNIX”。GUN计划开始于1984年,这个计划的目的是开发一个类似UNIX并且软件自由的完整操作系统。这个计划一直在进行,直到Linus开发Linux操作系统时,GNU计划已经开发出来了很多高质量的自由软件,其中就包括著名的GCC编译器,GCC编译器能够编译GUN C语言。Linus考虑到GUN计划的自由和免费,所以选择了GCC编译器来编写内核代码,之后的很多开发者也使用这个编译器,所以直到现在,驱动开发人员也使用GUN C语言来开发驱动程序。

不能使用C库开发驱动程序

与用户空间的应用程序不同,内核不能调用标准的C函数库,主要的原因在于对于内核来说完整的C库太大了。一个编译的内核大小可以是1MB左右,而一个标准的C语言库大小可能操作5MB。这对于存储容量较小的嵌入式设备来说,是不实用的。缺少标志C语言库,并不是说驱动程序就只能做很好的事情了。

大部分常用的C库函数在内核中都已经实现了。比如操作字符串的函数组就位于内核文件lib/string.c中。只要包含<linux/string.h>,就可以使用它们。又如内存分配的函数也已经包含在include/linux/slab_def.h中实现了。注意:内核程序中包含的头文件是指内核代码树中的内核头文件,不是指开发应用程序时的外部头文件。在内核中实现的库函数中的打印函数printk(),它是C库函数printf()的内核版本。printk()函数和printf()函数有基本相同的用法和功能。

没有内存保护机制

当一个用户应用程序由于编程错误,试图访问一个非法的内存空间,那么操作系统内核会结束这个进程,并返回错误码。应用程序可以在操作系统内核的帮助下恢复过来,而且应用程序并不会对操作系统内核有太大的影响。但是如果操作系统内核访问了一个非法的内存,那么就有可能破坏内核的代码或者数据。这将导致内核处于未知的状态,内核会通过oops错误给用户一些提示,但是这些提示都是不支持、难以分析的。

在内核编程中,不应该访问非法内存,特别是空指针,否则,内核会忽然死掉,没有任何机会给用户提示。对于不好的驱动程序,引起系统崩溃是很常见的事情,所以对于驱动开发人员来说,应该非常重视对内存的正确访问。一个好的建议是,当申请内存后,应该对返回的地址进行检测。

小内核栈

用户空间的程序可以从栈上分配大量的空间存放变量,甚至用栈存放巨大的数据结构或者数组都没问题。之所以能这样做是因为应用程序是非常驻内存的,它们可以动态地申请和释放所有可用的内存空间。内核要求使用固定常驻的内存空间,因此要求尽量少地占用常驻内存,而尽量多地留出内存提供给用户程序使用。因此内核栈的长度是固定大小的,不可动态增长的32位机的内核栈是8KB;64位机的内核栈是16KB。

由于内核栈比较小,所以编写程序时,应该充分考虑小内核栈问题。尽量不要使用递归调用,在应用程序中,递归调用4000多次就有可能溢出,在内核中,递归调用的次数非常少,几乎不能完成程序的功能。另外按使用完内存空间后,应该尽快地释放内存,以防止资源泄漏,引起内核崩溃。

重视可移植性

对于用户空间的应用程序来说,可移植性一直是一个重要的问题。一般可移植性通过两种方式来实现。一种方式是定义一套可移植的API,然后对这套API在这两个需要移植的平台上分别实现。应用程序开发人员,只要使用这套可移植的API,就可以写出可移植的程序。在嵌入式领域,比较常见的API套件是QT。另一种方式是使用类似Java、Actionscript等可移植到很多操作系统上的语言。这些语言一般通过虚拟机执行,所以可以移植到很多平台上。

对于驱动程序来说,可移植性需要注意以下几个问题:

  • 考虑字节顺序,一些设备使用大端字节序,一些设备使用小端字节序。Linux内核提供了大小端字节节序转换的函数。
#define cpu_to_le16(v16) (v16)
#define cpu_to_le32(v32) (v32)
#define cpu_to_le64(v64) (v64)
#define le16_to_cpu(v16) (v16)
#define le32_to_cpu(v32) (v32)
#define le64_to_cpu(v64) (v64)
  • 即使是同一种设备的驱动程序,如果使用的芯片不同,也应该写不同的驱动程序,但是应该给用户提供一个统一的编程接口。
  • 尽量使用宏代替设备端口的物理地址,并且可以使用ifdefine宏确定版本等信息。
  • 针对不同的处理器,应该使用相关处理器的函数。
相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
2月前
|
Linux 程序员 编译器
Linux内核驱动程序接口 【ChatGPT】
Linux内核驱动程序接口 【ChatGPT】
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
41 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
46 5
|
3月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
35 3
|
3月前
|
缓存 安全 Linux
Linux 设备驱动程序(一)((下)
Linux 设备驱动程序(一)
28 3
|
3月前
|
安全 数据管理 Linux
Linux 设备驱动程序(一)(中)
Linux 设备驱动程序(一)
23 2
|
3月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
19 1
|
3月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
31 1
|
3月前
|
存储 前端开发 大数据
Linux 设备驱动程序(二)(中)
Linux 设备驱动程序(二)
26 1
|
3月前
|
缓存 安全 Linux
Linux 设备驱动程序(二)(上)
Linux 设备驱动程序(二)
31 1