linux设备驱动第二篇:一个简单hello world驱动如何实现

上一篇介绍了linux驱动的概念,以及linux下设备驱动的基本分类状况及其各个分类的依据和差别,这一篇咱们来描述如何写一个相似hello world的简单测试驱动程序。而这个驱动的惟一功能就是输出hello world。linux

在编写具体的实例以前,咱们先来了解下linux内核下调试程序的一个重要函数printk以及几个重要概念。程序员

printk相似c语言的printf,是内核中输出打印信息的函数。之后驱动调试中的重要性不言而喻,下面先作一个简单介绍。shell

printk的级别编程

日志级别一共有8个级别,printk的日志级别定义以下(在include/linux/kernel.h中):  
#define KERN_EMERG 0/*紧急事件消息,系统崩溃以前提示,表示系统不可用*/  
#define KERN_ALERT 1/*报告消息,表示必须当即采起措施*/  
#define KERN_CRIT 2/*临界条件,一般涉及严重的硬件或软件操做失败*/  
#define KERN_ERR 3/*错误条件,驱动程序经常使用KERN_ERR来报告硬件的错误*/  
#define KERN_WARNING 4/*警告条件,对可能出现问题的状况进行警告*/  
#define KERN_NOTICE 5/*正常但又重要的条件,用于提醒*/  
#define KERN_INFO 6/*提示信息,如驱动程序启动时,打印硬件信息*/  
#define KERN_DEBUG 7/*调试级别的消息*/ubuntu

没有指定日志级别的printk语句默认采用的级别是:DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别通常为<4>,即与KERN_WARNING在一个级别上),其定义在kernel/printk.c中能够找到。在驱动调试过程当中打开全部日志信息可以使用echo 7 > /proc/sys/kernel/printk,相对应关闭日志使用echo 0 > /proc/sys/kernel/printk
微信

下面再来介绍几个重要的概念,这些概念能够先作一个了解,后续的文章中还会提到。数据结构

内核空间和用户空间多线程

linux系统分为两个级别。内核运行在最高级别,能够进行全部的操做。而应用程序运行在最低级别,处理器控制着对硬件的直接访问以及对内存的非受权访问。内核空间和用户空间不只有不一样的优先级等级,并且有不一样的内存映射,有各自的地址空间。详见内存管理。并发

应用程序只能经过系统调用或中断从用户空间切换到内核空间,其中系统调用是软中断(0x80号中断)。执行系统调用的系统代码运行在进程上下文中,它表明调用进程执行操做,所以可以访问进程地址空间的全部数据。而处理硬件中断的内核代码和进程是异步的,与任何一个特定进程无关。微信公众平台

内核中的并发

内核编程区别于常见应用程序编程的地方在于对并发的处理。大部分应用程序除多线程外,一般是顺序执行的,不须要关心因为其余事情的发生而改变它的运行环境。内核代码不是这样,同一时刻,可能有多个进程使用访问同一个模块。

内核编程要考虑并发问题的缘由:1.linux是一般正在运行多个并发进程,而且可能有多个进程同时使用咱们的驱动程序。2.大多数设备可以中断处理器,而中断处理程序异步进行,并且可能在驱动程序正试图处理其它任务时被调用。3.一些相似内核定时器的代码在异步运行。4.运行在对称多处理器上(SMP),不止一个cpu在运行驱动程序。5.内核代码是可抢占的。

当前进程

内核代码可经过访问全局项current来得到当前进程。current指针指向当前正在运行的进程。在open、read、等系统调用的执行过程当中,当前进程指的是调用这些系统调用的进程。内核代码能够经过current指针得到与当前进程相关的信息。

内核中带“__”的函数:内核API函数具备这种名称的,一般都是一些接口的底层函数,应该谨慎使用。实质上,这里的双下划线就是要告诉程序员:谨慎调用,不然后果自负。以__init为例,__init代表该函数仅在初始化期间使用。在模块被装载以后,模块装载器就会将初始化函数扔掉,这样能够将函数占用的内存释放出来,已作它用。注意,不要在结束初始化以后仍要使用的函数(或者数据结构)上使用__init、__initdata标记。这里摘抄网上的一段总结,以下。

__init, __initdata等属性标志,是要把这种属性的代码放入目标文件的.init.text节,数据放入.init.data节──这一过程是经过编译内核时为相关目标平台提供了xxx.lds连接脚原本指导ld完成的。
   对编译成module的代码和数据来讲,当模块加载时,__init属性的函数就被执行;
   对静态编入内核的代码和数据来讲,当内核引导时,do_basic_setup()函数调用do_initcalls()函数,后者负责全部.init节函数的执行。
   在初始化完成后,用这些关键字标识的函数或数据所占的内存会被释放掉。
1) 全部标识为__init的函数在连接的时候都放在.init.text这个区段内,在这个区段中,函数的摆放顺序是和连接的顺序有关的,是不肯定的。 
2) 全部的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会经过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),注意,这些函数在内核初始化过程当中的调用顺序只和这里的函数指针的顺序有关,和1)中所述的这些函数自己在.init.text区段中的顺序无关。 

下面咱们来看一个驱动程序的hello world程序是如何实现的:

[cpp] view plaincopy在CODE上查看代码片派生到个人代码片

  1. #include <linux/init.h>  

  2. #include <linux/module.h>  

  3. MODULE_LICENSE("Dual BSD/GPL");  

  4.   

  5. static int hello_init(void)  

  6. {  

  7.         printk(KERN_ALERT "Hello, world\n");  

  8.         return 0;  

  9. }  

  10. static void hello_exit(void)  

  11. {  

  12.   

  13.         printk(KERN_ALERT "Goodbye, cruel world\n");  

  14. }  

  15.   

  16. module_init(hello_init);  

  17. module_exit(hello_exit);  


内核模块的编译与应用程序的编译有些区别,此hello world模块的编译命令为:

make -C /xxx/xxx/kernel_src/ M=$(PWD) modules

其中/xxx/xxx/kernel_src/ 为已经配置编译过的内核源码路径,ubuntu下通常在/lib/modules/$(shell uname -r)/build目录下。

此函数只有两个函数,一个是hello_init,在insmod的时候执行,这个是模块的初始化函数,另外一个是hello_exit,在rmmod的时候执行,是模块卸载时要执行的函数。此模块的惟一功能就是在insmod的时候输出Hello,world,在rmmod的时候输出Goodbye,cruel world。

在编写应用程序时,咱们通常都是由多个源文件组成的,这个时候编译确定就不能继续使用命令行编译了,就要使用到Makefile。一样,驱动模块的编译也须要使用的makefile,下面就是一个在编译含有多个源码文件的驱动模块时能够参考的Makefile文件。

[cpp] view plaincopy在CODE上查看代码片派生到个人代码片

  1. ifndef CROSS_COMPILE  

  2. export CROSS_COMPILE ?=arm-none-linux-gnueabi-  

  3. endif  

  4.   

  5. ARCH ?= arm  

  6.   

  7. SRC_DIR := /home/XXX/XXX  

  8. OBJ_DIR  := $(SRC_DIR)/obj  

  9. PWD := $(shell pwd)  

  10.   

  11. LINUX_SRC ?= /home/XXX/kernel  

  12.   

  13. CFG_INC = -I$(SRC_DIR) \  

  14.     -I$(DIR_A) \  

  15.     -I$(DIR_B)  

  16.   

  17. CFG_FLAGS += -O2  

  18. EXTRA_CFLAGS  += $(C_FLAGS) $(CFG_INC) $(CFG_INC)  

  19.   

  20. obj-m := mymodule.o  

  21.   

  22. mymodule-objs := a.o  

  23. mymodule-objs += b.o  

  24. mymodule-objs += c.o  

  25.   

  26. modules:  

  27.     @make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modules  

  28.   

  29. clean:  

  30.     @echo "cleaning..."  

  31.     rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symvers  

  32.     rm -f $(mymodule-objs)  


第一时间得到博客更新提醒,以及更多技术信息分享,欢迎关注我的微信公众平台:程序员互动联盟(coder_online)

1.直接帮你解答linux设备驱动疑问点

2.第一时间得到业内十多个领域技术文章

3.针对文章内疑点提出问题,第一时间回复你,帮你耐心解答

4.让你和原创做者成为很好的朋友,拓展本身的人脉资源

扫一扫下方二维码或搜索微信号coder_online便可关注,咱们能够在线交流。

相关文章
相关标签/搜索