上一篇介绍了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文件。

Makefile模板

ifndef CROSS_COMPILE
export CROSS_COMPILE ?=arm-none-linux-gnueabi-
endifARCH ?= armSRC_DIR := /home/XXX/XXX
OBJ_DIR  := $(SRC_DIR)/obj
PWD := $(shell pwd)LINUX_SRC ?= /home/XXX/kernelCFG_INC = -I$(SRC_DIR) \-I$(DIR_A) \-I$(DIR_B)CFG_FLAGS += -O2
EXTRA_CFLAGS  += $(C_FLAGS) $(CFG_INC) $(CFG_INC)obj-m := mymodule.omymodule-objs := a.o
mymodule-objs += b.o
mymodule-objs += c.omodules:@make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modulesclean:@echo "cleaning..."rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symversrm -f $(mymodule-objs)

以上就是这一篇的内容,下一篇会从简单的字符驱动开始,介绍驱动编写的主要内容。

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

  1. linux设备驱动第一篇:设备驱动程序简介

    为什么80%的码农都做不了架构师?>>>    首先,我们知道驱动是内核的一部分,那么驱动在内核中到底扮演了什么角色呢? 设备驱动程序在内核中的角色:他们是一个个独立的"黑 ...

  2. 君君学Linux设备驱动第二天之硬件基础

    一 处理器 1 通用处理器(GPP)    不针对具体应用领域进行体系结构和指令的优化,具有通用性,以支持复杂的运算和添加新功能. 2 微控制器(MCU):含有CPU和一些基本外设的芯片     微处 ...

  3. linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt

    PPT内容 这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介:建立和运行模块:字符驱动:调试技术:并发和竞争:分配内存:硬件通讯:中断处理:块设备驱动,欢迎点击下载. 嵌入式Lin ...

  4. 【驱动】linux设备驱动·字符设备驱动开发

    Preface 前面对linux设备驱动的相应知识点进行了总结,现在进入实践阶段! <linux设备驱动入门篇>:http://infohacker.blog.51cto.com/6751 ...

  5. Linux 设备驱动篇之I2c设备驱动

    ******************************************************************************************** 装载声明:希望 ...

  6. Linux设备驱动篇——[I2C设备驱动-1]

    Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...

  7. linux设备驱动第五篇:驱动中的并发与竟态

    目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...

  8. RK3568平台开发系列讲解(驱动篇) linux设备驱动模型

    文章目录 linux设备驱动模型 为什么需要设备驱动模型 sysfs概述 设备驱动模型基本元素 驱动模型一 驱动模型二 kobject kset kobj_type linux设备驱动模型 为什么需要 ...

  9. 整理--linux设备驱动模型

    知识整理–linux设备驱动模型 以kobject为底层,组织类class.总线bus.设备device.驱动driver等高级数据结构,同时实现对象引用计数.维护对象链表.对象上锁.对用户空间的表示 ...

最新文章

  1. html dom createevent,js 中 document.createEvent的用法
  2. fuse java_java中的Fuse文件系统 - JVM错误双重免费或损坏
  3. go语言定义二维数组
  4. android 默认开关,android默认设置的开关
  5. 微软Azure storage account的connection string
  6. 数据结构之trie树——First! G,电子字典,Type Printer,Nikitosh and xor
  7. 江西理工大学南昌校区cool code竞赛
  8. 作者:谢华美(1976-),男,就职于中国人民银行征信中心数据部
  9. 批量修改密码脚本--------小练习
  10. 纯 CSS 中的简单响应式汉堡菜单
  11. 图解域名解析成IP的全过程(你浏览器摁下一个网址后发生了啥?)
  12. Command 模式——读书笔记
  13. 图像处理中的 “掩膜” Mask
  14. 深度学习应用于脑电信号分析处理的相关论文,更新了......
  15. 大学四年,如何把自己包装成一个被各大公司竞相争捧的香馍馍
  16. EGit(Git Eclipse Plugin)使用(转载)
  17. 文件上传接受的类型:file的accept属性
  18. 密码学~~~数字信封
  19. 一致性测试--总结_1
  20. 为什么 Vue3 的 ref 让很多大佬操碎了心?

热门文章

  1. 小米8se android q,小米8 SE开始测试安卓Q系统, 国产第一款
  2. 下载!闲鱼最新升级版 Flutter 技术电子书!
  3. C语言实现的 颜色头文件,欢迎使用
  4. 今年,我们在香港支援春运——广深港高铁“列车医生”的工作日记
  5. 识花君:一款利用人工智能识别花草的小程序
  6. nginx : OpenEvent Faied access is denied
  7. java对url参数编码_对URL中的参数编码和解码
  8. [BeiJing2011]元素
  9. 毕业季闲鱼发布租房大数据:700万套房和1800万租客
  10. 网站安全隔离-RBI技术