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

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

 大部分程序员都比较熟悉应用程序的编写,但是对于驱动程序的编写可能不是很熟悉。关于应用程序的很多编程经验不能直接应用到驱动程序的编写中。下面给出编写驱动程序的一些注意事项,希望引起读者注意

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

  在linux上的开发一般分为两种,一种是内核及驱动程序开发,另一种是应用程序许的开发。这两种开发种类对应linux的两种状态,分别是内核态和用户态。内核态用来管理用户态的程序,完成用户态请求的工作;用户态处理上层的软件工作。驱动程序与底层的硬件交互,所以工作在内核态。
  大多数程序员致力于应用程序的开发,少数程序员则致力于内核及驱动程序的开发。相对于应用程序的开发,内核及驱动程序的开发有很大的不同。最重要的差异包括以下几点:

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

2.GNU C开发驱动程序

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

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

  与用户空间的程序不同,内核不能调用标准C函数库,主要的原因在于对于内核来说完整的C库太大了。一个编译完整的内核大小可以使1MB左右,而一个标准的C语言库大小可能是操作5MB。这对于存储容量较小的嵌入式设备来说,是不实用的。缺少标志C语言库,并不是说驱动程序就只能做很好的事情了。
  大部分常用的C库函数在内核中都已经实现了。比如操作字符串的函数组就位于内核文件lib/string.c中。只要包含<linux/string.h>,就可以使用它们。又如内存分配的函数也已经包含在include/linux/slab_def.h中实现了。

注意:内核程序中包含的头文件是指内核代码树种的内核头文件,不是指开发应用程序时的外部头文件。在内核中实现的库函数中的打印函数printk(),它是C库函数printf()的内核版本。printk()函数和printf()函数有基本相同的用法和功能。

4.没有内存保护机制

  当一个用户程序由于编程错误,试图访问非法内存空间,那么操作系统内核会结束这个进程,并返回错误码。应用程序可以在操作系统内核的帮助下恢复过来,而且应用程序并不会对操作系统内核有太大的影响。但是如果操作系统内核访问了一个非法的内存,那么就有可能破坏内核的代码或者数据。这将导致内核处于未知的状态,内核会通过oops错误给用户一些提示,但是这些提示都是不支持、难以分析的。
  在内核和编程中,不应该访问非法内存,特别是空指针,否则,内核会突然死掉,没有任何机会给用户提示。对于不好的驱动程序,引起系统崩溃时很常见的事情,所以对于驱动开发人员来说,应该非常重视对内存的正确访问。一个好的建议是,当申请内存后,应该对返回的地址进行检测。

5.小内核栈

  用户空间的程序可以从栈上分配大量的空间存放变量,甚至存放巨大的数据结构或者数组都没问题。之所以能这样做事因为应用程序是非常驻内存,他们可以动态地申请和释放所有可用的内存空间。内核要求使用固定常驻的内存空间,因此要求尽量少地占用常驻内存,而尽量多地留出内存提供给用户程序使用。因此内核栈的长度是固定大小的,不可动态增长的32位机的内核栈是8KB;64位机的内核栈是16KB。
  由于内核栈比较小,所以编写程序时,应该充分考虑小内核栈的问题。尽量不要使用递归调用,在应用程序中,递归调用4000多次就有可能溢出,在内核中,递归调用的次数非常少,几乎不能完成程序的功能。另外按使用完内存空间后,应尽快地释放内存,以防止资源泄露,引起内核崩溃。

6.重视可移植性

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

  • 字节序,当然除了一些网络设备使用大端序,一般都是用小端序。内核中提供了大小端字节序转换函数。

    
     #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宏确定版本等信息。
  • 针对不同的处理器,应该使用相关处理器的函数。

猜你喜欢

转载自blog.csdn.net/sinat_36544290/article/details/80746484