设备驱动程序基础
驱动程序是专用于控制和管理特定硬件设备的软件,因此也被称作设备驱动程序。从操作系统的角度来看,它可以位于内核空间(以特权模式运行),也可以位于用户空间(具有较低的权限)。在编写设备驱动程序之前,应该了解一些概念。C语言编程技巧是必需的,至少需要熟悉指针,并熟悉一些处理函数和必要的硬件知识。
- 模块的构建过程及其加载和卸载
- 驱动程序框架以及调试消息管理
- 驱动程序中的错误处理
1. 内核空间和用户空间
内核空间:内存驻留和运行的地址空间。内核内存是由内核拥有的内存范围,受访问标志保护,防止任何用户应用程序有意或无意间与内核搞混。在系统上以更高的优先级运行。
用户空间:正常程序被限制运行的地址(位置)空间。可以将其视为沙盒或监狱,以便用户程序不能混用其他程序拥有的内存或任何其他资源。在用户模式下,CPU只能访问标有用户空间访问权限的内存。

2.模块的相关概念
模块对于Linux而言就像插件和用户软件一样,模块动态扩展了内核的功能。大多数情况下,内核模块是即插即用的。内核中的模块可以提供函数或变量,在内核构建过程中运行depmod工具可以生成模块依赖文件。它通过读取/lib/modules/<kernel_release>/中的每个模块来确定它应该导出哪些符号以及它需要什么符号。处理得到的结果写入文件modules.dep.
【模块加载】模块运行需要先加载到内核,可以使用insmod 或modprobe 来实现,前者需要指定模块路径作为参数,作为开发期间的首选;后者更加智能化,是生产系统中的首选。
-
手动加载
手动加载需要用户的干预,该用户应该拥有root访问权限。具体方法如下:
1.使用insmod来加载模块,并给出所加载模块的路径:
2.insmod这种模块加载形式低级,相反,系统管理员或在生产系统中常用的modprobe.modprobe更加智能,它会加载指定的模块之前解析文件modules.dep,以便首先加载依赖关系。
-
自动加载
depmod实用程序的作用不只是构建modules.dep 和modules.dep.bin 文件。内核开发人员实际编写驱动程序时已经明确知道该驱动程序将要支持的硬件。将驱动程序支持的所有设备的产品和厂商ID提供给该驱动程序。depmod还处理模块文件来提取和收集该信息,并生成modules.alias文件,该文件将设备映射到其对应的驱动程序。
-
【模块卸载】常用的模块卸载命令是rmmod,使用这个命令来卸载insmod命令加载的模块。
而另一个更高级的模块卸载命令是modprobe -r ,它会自动卸载未使用的相关依赖模块:
在开发中还有一个常用的命令lsmod,可以用来检查模块是否已加载:
3.驱动程序框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init helloworld_init(void)
{
pr_info("Hello world!\n");
return 0;
}
static void __exit helloworld_exit(void)
{
pr_info("End of the world\n");
}
/* 模块的入点和出点 */
module_init(helloworld_init);
module_init(helloworld_exit);
/* 模块相关信息 */
MODULE_AUTHOR("***** <*****@mail.com>");
MODULE_DESCRIPTION("Hello world! Module");
/* 许可 */
MODULE_LICENSE("GPL");
4.错误和消息打印
错误代码有内核空间应用程序(error变量)解释。错误处理在软件开发中非常重要,而不仅仅实在内核开发中。在内核当中提供了几种错误,几乎涵盖了可能出现的错误,有时需要将它们打印出来以帮助调试。
4.1 错误处理
由于给定的错误返回错误代码会导致内核或用户空间应用产生不必要的行为,从而做出错误的决定。为了方便处理这些错误,内核树中预定义的错误几乎涵盖了可能遇到的情况。它们被定义在路径为 include/upia/asm-generic/errno-base.h 的头文件中 ,下面是定义的所有错误定义代码:
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif
4.2 消息打印
printk()是在内核空间中使用的,其作用和在用户空间使用printf()一样。根据所打印消息的重要性不同,可以选用include/linux/kern_levels.h中定义的八个级别的消息日志。下面是所列出的内核日志级别,每个级别对应一个字符串格式的数字,其优先级与该数字的值成反比。
0具有较高的优先级,以此类推,7的优先级最低。
#define KERN_SOH "\001" /* ASCII Start Of Header */
#define KERN_SOH_ASCII '\001'
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
通过下列代码打印内核消息和日志级别:
printk(KERN_ERR "This is an error\n");
通过打印错误消息,检查日志级别参数,方便开发人员更快速的调试驱动程序。
/* integer equivalents of KERN_<LEVEL> */
#define LOGLEVEL_SCHED -2 /* Deferred messages from sched code
* are set to this special level */
#define LOGLEVEL_DEFAULT -1 /* default (or last) loglevel */
#define LOGLEVEL_EMERG 0 /* system is unusable */
#define LOGLEVEL_ALERT 1 /* action must be taken immediately */
#define LOGLEVEL_CRIT 2 /* critical conditions */
#define LOGLEVEL_ERR 3 /* error conditions */
#define LOGLEVEL_WARNING 4 /* warning conditions */
#define LOGLEVEL_NOTICE 5 /* normal but significant condition */
#define LOGLEVEL_INFO 6 /* informational */
#define LOGLEVEL_DEBUG 7 /* debug-level messages */