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

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

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

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

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/*调试级别的消息*/

没有指定日志级别的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程序是如何实现的:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
        printk(KERN_ALERT "Hello, world\n");
        return 0;
}
static void hello_exit(void)
{

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

module_init(hello_init);
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文件。

ifndef CROSS_COMPILE
export CROSS_COMPILE ?=arm-none-linux-gnueabi-
endif

ARCH ?= arm

SRC_DIR := /home/XXX/XXX
OBJ_DIR  := $(SRC_DIR)/obj
PWD := $(shell pwd)

LINUX_SRC ?= /home/XXX/kernel

CFG_INC = -I$(SRC_DIR) 	-I$(DIR_A) 	-I$(DIR_B)

CFG_FLAGS += -O2
EXTRA_CFLAGS  += $(C_FLAGS) $(CFG_INC) $(CFG_INC)

obj-m := mymodule.o

mymodule-objs := a.o
mymodule-objs += b.o
mymodule-objs += c.o

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

clean:
	@echo "cleaning..."
	rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symvers
	rm -f $(mymodule-objs)

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

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

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

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

4.让你和原创作者成为很好的朋友,拓展自己的人脉资源

扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。

时间: 2025-01-13 05:45:06

linux设备驱动第二篇:一个简单hello world驱动如何实现的相关文章

[stm32] 一个简单的stm32vet6驱动2.4寸240X320的8位并口tft屏DEMO

书接上文: 最近在研究用低速.低RAM的单片机来驱动小LCD或TFT彩屏实现动画效果 首先我用一个16MHz晶振的m0内核的8位单片机nRF51822尝试驱动一个1.77寸的4线SPI屏(128X160), 发现,刷一屏大约要0.8s左右的时间, 具体收录在<1.一个简单的nRF51822驱动的天马4线SPI-1.77寸LCD彩屏DEMO>中 觉得,如果用72MHz的STM32也许效果会好很多 于是在stm32上做了个类似的版本, 具体收录在<一个简单的stm32vet6驱动的天马4线S

linux设备驱动第二篇:构造和运行模块

上一篇介绍了linux驱动的概念,以及linux下设备驱动的基本分类情况及其各个分类的依据和差异,这一篇我们来描述如何写一个类似hello world的简单测试驱动程序.而这个驱动的唯一功能就是输出hello world. 在编写具体的实例之前,我们先来了解下linux内核下调试程序的一个重要函数printk以及几个重要概念. printk类似c语言的printf,是内核中输出打印信息的函数.以后驱动调试中的重要性不言而喻,下面先做一个简单介绍. printk的级别 日志级别一共有8个级别,pr

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件.#include <asm/***.h> 是在linux-2.6.29/arch/arm/include/asm下面寻找源文件.#include <mach/***.h> 是在linux-2.6.29/arch/ar

CSDN markdown 编辑器 第二篇 markdown简单使用

第一篇简单介绍了markdown. 第一篇地址 第二篇主要会介绍不熟悉markdown语言的人如何使用CSDN新的编辑器.这里主要介绍图形编辑器.已经掌握的人请挪步. 前面几个都非常简单.例如对字体加粗和斜体. - 或者是对 [帐前卒专栏](http://chillyc.info) 加个链接.用![连接图标](http://img.blog.csdn.net/20150312221433385) - 再者就是加个图片:![图片图标](http://img.blog.csdn.net/201503

Linux内核分析:完成一个简单的时间片轮转多道程序内核代码

PS.贺邦   原创作品转载请注明出处  <Linux内核分析>MOOC课程    http://mooc.study.163.com/course/USTC-1000029000 1.mykernel实验指导(操作系统是如何工作的) 使用实验楼虚拟机打开shell输入下列代码 1 cd LinuxKernel/linux-3.9.4 2 qemu -kernel arch/x86/boot/bzImage 可以看到初始的内核运行情况如下: 内核不停的执行my_start_kernel(),每

IDDD 实现领域驱动设计-一个简单的 CQRS 示例

上一篇:<IDDD 实现领域驱动设计-CQRS(命令查询职责分离)和 EDA(事件驱动架构)> 学习架构知识,需要有一些功底和经验,要不然你会和我一样吃力,CQRS.EDA.ES.Saga 等等,这些是实践 DDD 所必不可少的架构,所以,如果你不懂这些,是很难看懂上篇所提到的 CQRS Journey 和 ENode 项目,那怎么办呢?我们可以从简单的 Demo 一点一滴开始. 代码地址:https://github.com/yuezhongxin/CQRS.Sample 说明:一张很丑陋的

编译原理 龙书 第二章 一个简单的算术式(+,-)翻译器实现

昨天晚上决定正面硬刚神课<编译原理>.硬上龙书. 下面是 一个简单的算术式中缀变后缀的翻译器. 这个也是 龙书中 一个C实现源码 .部分用c++改写. #include <iostream> #include <ctype.h> #include <stdlib.h> #include <stdio.h> using namespace std; int lookahead; void error()//错误处理 { cout<<&q

第二课 一个简单的“引导程序”

上一节中说到BIOS会将MBR中的主引导程序(512字节)加载到内存的0x7c00处,其中这512字节的主引导程序是软件程序,是操作系统的一部分,因此也是由操作系统开发者来编写的,BIOS将其加载到内存后,会自动跳到0x7c00处去执行.接下来我们自己实现一个"主引导程序",功能很简单,就是让它打印一串字符串到屏幕上(真正的主引导程序是加载操作系统内核用的),注意,这段程序现在是独立运行在裸机上的,我们用汇编语言来实现它. 这个过程我们需要调用中断向量表中的"打印函数&quo