【编者的话】Linux内核模块作为Linux内核的扩展手段,可以在运行时动态加载和卸载。它是设备和用户应用程序之间的桥梁,可以通过标准系统调用,为应用程序屏蔽设备细节。本文来自Derek Molloy的博客,介绍了内核模块的概念、用途,以及如何构建一个简单的“Hello World”内核模块。 \

前言

\

在这系列文章中,将介绍如何为嵌入式Linux设备编写Linux内核模块。文章将从简单的可加载内核模块(loadable kernel module,LKM)“Hello World!”开始,进而开发通过使用中断请求控制嵌入式Linux设备(如BeagleBone)通用输入输出接口(GPIO)的模块。当我确定合适的应用程序时,我会添加更多的后续文章。 \

内核模块是一个复杂的话题,需要一定的时间来完成。因此,我将内容拆分成几篇文章,每篇提供一个可以实践的示例和结果。这个话题可以写一整本书,因此很难覆盖每一个方面。关于编写内核模块的其他文章也有很多,而本文的示例都在Linux内核3.8.X以上版本构建和测试,以确保这些材料是最新且贴切的。同时,本文主要关注嵌入式系统的硬件接口。在我的书《Exploring BeagleBone》中也有相同的示例,由于本文自身包含了这些代码,读者无须拥有该书的副本。 \

\

图1:内核空间GPIO性能 \

本文集中讨论构建和部署“Hello World!”内核模块所需的系统设置、工具和代码。本系列中的第二篇文章探讨了如何编写字符设备驱动和如何编写用户空间C/C++程序与内核空间模块进行交互。第三篇文章探讨内核空间GPIO库代码的使用,它结合了前两篇文章的内容,开发中断驱动代码,使之能够从Linux用户空间控制。例如,图1展示了示波器捕获的通过中断驱动内核模块处理按钮按下到LED亮起的图形。在常规嵌入式Linux中(即非实时Linux的变体),该代码展示忽略CPU开销后,响应时间大约为20毫秒(±5微秒)。 \

什么是内核模块

\

可加载内核模块(LKM)是Linux内核运行时加载和移除代码的机制。该机制对于设备驱动是理想的,这使得内核可以在不知道硬件如何工作的情况下和硬件进行交互。可加载内核模块的替代是将每个驱动代码构建到Linux内核中。 \

没有模块化能力,Linux内核将会变得非常大,因为它不得不支持BeagleBone开发板上所需的每个驱动。同时,在需要添加新硬件或者升级设备驱动时,必须重新构建内核。可加载内核模块功能的缺点是对于每个设备都必须维护一个驱动文件。可加载内核模块在运行时加载,他们不运行在用户空间,本质上是内核的一部分。\

\

图2:Linux用户空间和内核空间 \

如图2所示,内核模块运行在内核空间,而应用程序运行在用户空间。内核空间和用户空间都有自己独立的内存地址,不会相互重叠。此方法确保了运行在用户空间中的应用程序对于硬件有一致的视图,不用关注硬件平台本身。内核服务通过系统调用以可控的方式提供给用户空间。同时,内核阻止独立的用户空间应用程序之间相互竞争或通过使用保护级别访问受限资源(比如超级用户与普通用户的权限)。 \

为什么编写内核模块

\

在嵌入式Linux中和电子电路交互,你接触到的是系统文件系统,并且使用低级别的文件操作来和电子电路交互。这种方式效率很低(尤其是如果你有传统嵌入式系统开发经验)。然而,对这些文件项进行内存映射后,对于许多应用程序来说性能是足够的。我在书中已经证明,通过在Linux用户空间使用pthread、回调函数和sys/poll.h,在忽略CPU开销下,是可以做到约三分之一毫秒的响应时间。 \

另一个实现是使用内核代码,它支持中断。然而内核代码难以编写和调试。我的建议是优先尝试在Linux用户空间完成任务,除非已确定没有其他可行方法。 \

本次讨论的源码

\

本次讨论的所有代码都在为《Exploring BeagleBone》准备的GitHub仓库上。代码可以在ExploringBB GitHub仓库内核工程目录中公开查看,或者也可以将代码复制到BeagleBone(或者其他Linux设备):

molloyd@beaglebone:~$ sudo apt-get install git\molloyd@beaglebone:~$ git clone https://github.com/derekmolloy/exploringBB.git

\

代码中/extras/kernel/hello目录是本文最重要的资源。为这些示例代码自动生成的Doxygen文档有HTML格式和PDF格式。 \

准备构建可加载内核模块的系统

\

为了构建内核代码,需要在设备上安装Linux内核头文件。在典型的Linux桌面机器上,可以使用包管理器来查找和安装正确的包。例如,在64位Debian发行版中,可以这样做:

molloyd@DebianJessieVM:~$ sudo apt-get update\molloyd@DebianJessieVM:~$ apt-cache search linux-headers-$(uname -r)\ linux-headers-3.16.0-4-amd64 - Header files for Linux 3.16.0-4-amd64\ molloyd@DebianJessieVM:~$ sudo apt-get install linux-headers-3.16.0-4-amd64\ molloyd@DebianJessieVM:~$ cd /usr/src/linux-headers-3.16.0-4-amd64/\ molloyd@DebianJessieVM:/usr/src/linux-headers-3.16.0-4-amd64$ ls\ arch  include  Makefile  Module.symvers  scripts

\

本系列的前两篇文章的示例,可以在任何桌面Linux发行版中完成构建。然而,本系列文章中,我将在BeagleBone上直接构建内核模块,这相比于交叉编译可以简化步骤。安装的内核头文件必须和内核构建版本一致。和桌面版安装类似,使用uname命令来识别正确的安装版本。例如:

molloyd@beaglebone:~$ uname -a\Linux beaglebone 3.8.13-bone70 #1 SMP Fri Jan 23 02:15:42 UTC 2015 armv7l GNU/Linux

\

BeagleBone平台的Linux内核头文件可以从Robert Nelson的网站下载。比如在http://rcn-ee.net/deb/precise-armhf/,选择准确的内核构建版本,并且在BeagleBone上下载和安装这些Linux内核头文件。例如:\

molloyd@beaglebone:~/tmp$ wget http://rcn-ee.net/deb/precise-armhf/v3.8.13-bone70\        /linux-headers-3.8.13-bone70_1precise_armhf.deb \100%[===========================\u0026gt;] 8,451,080 2.52M/s in 3.2s\2015-03-17 22:35:45 (2.52 MB/s) - 'linux-headers-3.8.13-bone70_1precise_armhf.deb' saved [8451080/8451080]\molloyd@beaglebone:~/tmp$ sudo dpkg -i ./linux-headers-3.8.13-bone70_1precise_armhf.deb \Selecting previously unselected package linux-headers-3.8.13-bone70

\

然后可以检查头文件是否正确安装:

molloyd@beaglebone:~/tmp$ cd /usr/src/linux-headers-3.8.13-bone70/ \molloyd@beaglebone:/usr/src/linux-headers-3.8.13-bone70$ ls\Documentation Module.symvers  crypto    fs       ipc     mm       scripts   tools\Kconfig       arch            drivers   include  kernel  net      security  usr\Makefile      block           firmware  init     lib     samples  sound     virt

\

给BeagleBone使用的3.8.13-bone47版本内核的Debian发行版中,需要执行一个特殊步骤在/usr/src/linux-headers-3.8.13-bone47/arch/arm/include/mach目录中创建一个空的timex.h文件(即touch timex.h)。bone70构建不需要此步骤。 \

警告

\

编写和测试内核模块时很容易使系统崩溃。系统崩溃可能会损坏文件系统。虽然系统崩溃不太常见,但这是可能发生的。请备份数据或者使用一个嵌入式系统,如BeagleBone,他们能够很方便的被重新刷写。通过执行sudo reboot或者按BeagleBone上的重置按钮,通常能够恢复到正常状态。在写本系列文章过程中,尽管有很多很多次系统崩溃,但BeagleBones并没有损坏过。 \

模块代码

\

传统计算机程序的运行生命周期相当简单。加载器为程序分配内存,然后加载程序和所需要的动态链接库。指令从一些入口开始执行(传统C/C++程序以main()函数作为入口),语句被执行,异常被抛出,动态内存被分配和释放,程序最终运行完成。当程序退出时,操作系统识别任何内存泄露,并释放到内存池。 \

内核模块不是应用程序,从一开始就没有main()函数。内核模块和普通应用程序的区别有: \

  • 非顺序执行:内核模块使用初始化函数将自身注册并处理请求,初始化函数运行后就结束了。内核模块处理的请求在模块代码中定义。这和常用于图形用户界面(graphical-user interface,GUI)应用的事件驱动编程模型比较类似。 \
  • 没有自动清理:任何由内核模块申请的内存,必须要模块卸载时手动释放,否则这些内存将无法使用,直到系统重启。 \
  • 不要使用printf()函数:内核代码无法访问为Linux用户空间编写的库。内核模块运行在内核空间,它有自己独立的地址空间。内核空间和用户空间的接口被清晰的定义和控制。内核模块可以通过printk()函数输出信息,这些输出可以在用户空间查看到。 \
  • 会被中断:内核模块一个概念上困难的地方在于他们可能会同时被多个程序/进程使用。构建内核模块时需要小心,以确保在发生中断的时候行为一致和正确。BeagleBone有一个单核处理器(目前为止),但是我们仍然需要考虑多进程同时访问对模块的影响。 \
  • 更高级的执行特权:通常内核模块会比用户空间程序分配更多的CPU周期。这看上去是一个优势,然而需要特别注意内核模块不会影响到系统的综合性能。 \
  • 无浮点支持:对用户空间应用,内核代码使用陷阱(trap)来实现整数到浮点模式的转换。然而在内核空间中这些陷阱难以使用。替代方案是手工保存和恢复浮点运算,这是最好的避免方式,并将处理留给用户空间代码。

以上概念有很多需要消化,重要的是,它们都被解决,但是没有都包含在第一篇文章中。列表1提供了第一个示例内核模块的的代码。当没有提供内核参数时,代码使用printk()函数显示“Hello world!...”,如果提供了参数“Derek”,日志会显示“Hello Derek!...”。列表1中的注释使用Doxygen样式,描述每个语句角色。更多的描述在代码列表下放。

/**\ * @file    hello.c\ * @author  Derek Molloy\ * @date    4 April 2015\ * @version 0.1\ * @brief  入门的可加载内核模块“Hello World!”,当模块加载和移除的时候,会在/var/log/kern.log文件输出消息。\ * 该模块在加载的时候接受一个参数:名字,它将显示在内核日志文件中。\ * @see http://www.derekmolloy.ie/ 查看完整描述和补充描述。\*/\\#include \u0026lt;linux/init.h\u0026gt;             // 用于标记函数的宏,如__init、__exit\#include \u0026lt;linux/module.h\u0026gt;           // 加载内核模块到内核使用的核心头文件\#include \u0026lt;linux/kernel.h\u0026gt;           // 包含内核使用的类型、宏和函数\\MODULE_LICENSE(\"GPL\");              ///\u0026lt; 许可类型,它会影响到运行时行为\MODULE_AUTHOR(\"Derek Molloy\");      ///\u0026lt; 作者,当使用modinfo命令时可见\MODULE_DESCRIPTION(\"A simple Linux driver for the BBB.\");  ///\u0026lt; 模块描述,参见modinfo命令\MODULE_VERSION(\"0.1\");              ///\u0026lt; 模块版本\\static char *name = \"world\";        ///\u0026lt; 可加载内核模块参数示例,这里默认值设置为“world”\module_param(name, charp, S_IRUGO); ///\u0026lt; 参数描述。charp表示字符指针(char ptr),S_IRUGO表示该参数只读,无法修改\MODULE_PARM_DESC(name, \"The name to display in /var/log/kern.log\");  ///\u0026lt; 参数描述\\/** @brief 可加载内核模块初始化函数\ *  static关键字限制了该函数的可见范围为当前C文件。\ *  __init宏表示对于内置驱动(不是可加载内核模块),该函数只在初始化的时候执行,\ *  在此之后,该函数可以废弃,且内存可以被回收。\ *  @return 当执行成功返回0\ */\static int __init helloBBB_init(void){\   printk(KERN_INFO \"EBB: Hello %s from the BBB LKM!\\

编写Linux内核模块——第一部分:前言相关推荐

  1. 编写Linux内核模块——第三部分:按键和发光二极管

    [编者的话]了解了基本的内核模块开发.内核空间和用户空间交互之后,终于要开始和硬件设备直接交互了.Linux内核提供了对通用输入输出接口.中断请求等的封装,让驱动开发者可以利用中断来控制硬件线路上的设 ...

  2. linux内核模块编写,Linux内核模块编程

    1 总体设计思路 Linux内核是单体式结构,相对于微内核结构而言,其运行效率高,但是系统的可维护性和可扩展性较差.为此,Linux提供了内核模块(module)机制,它不仅可以弥补单体式内核相对于微 ...

  3. Linux内核模块的概念和基本的编程方法

    Linux内核模块的概念和基本的编程方法 标签: Linux内核模块 2013-06-14 18:29 1864人阅读 评论(0) 收藏 举报 分类: linux内核(34) 版权声明:本文为博主原创 ...

  4. Linux内核模块学习笔记(转载)

    Linux内核模块    Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件. 1.1linux内核模块简介 Linux内核的整体结构非常庞 ...

  5. 编写函数实现员工信息录入和输出_编写我的第一个Linux 内核模块“hello_module”...

    前言: Linux 内 核 模 块 全 称 为 " 动 态 可 加 载 内 核 模 块 (Loadable Kernel Module,LKM)",是系统内核向外部提供的功能插口. ...

  6. Linux内核模块编写详解

    内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统,本文给大家介 ...

  7. 编写一个 Linux 内核模块

    编写一个 Linux 内核模块 作者:解琛 时间:2020 年 8 月 16 日 编写一个 Linux 内核模块 一.实验环境 二.Linux 内核模块相关命令 三.程序架构 四.编写一个内核模块 4 ...

  8. Linux内核模块编程系列1-极简内核模块编写

    1.准备工作 使用如下命令查看自己Linux的内核版本 uname -a 结果如下: Linux VM-73-203-debian 4.9.0-6-amd64 #1 SMP Debian 4.9.88 ...

  9. 调试linux内核模块

    1:前言: 最近几天学习Linux-2.6平台上的设备驱动,所以要建立内核及内核模块的调试平台.虽然网上有很多相关教程,但多是基于2.6.26以前的通过补丁安装的,过程非常复杂,而且问题比较多.lin ...

最新文章

  1. 如何使用Fiddler抓包操作?
  2. DXperience_v9.15简体中文
  3. android 悬浮窗权限,Android 悬浮窗权限校验
  4. linux命令之nc,emacs,go run,查看文件行数等
  5. mac 苹果多版本jdk自由切换
  6. sqlserver附加数据库错误823的解决方案
  7. 解决sublime text3安装Package Control问题
  8. ROS学习之包的概念
  9. Visual Studio 2010软件安装教程
  10. JSP简单练习-省略显示长字符串
  11. libc.so.6linux查找,Linux中提示:/lib64/libc.so.6: version `GLIBC_2.17' not found 的解决办法...
  12. linux php任务计划,linux系统怎么添加计划任务执行php文件
  13. ZZ:SDNLAB技术分享(一):ODL的SFC入门和Demo
  14. imutils.path
  15. bzoj千题计划240:bzoj3900: 交换茸角
  16. java语言实现常用算法(排序和查找)
  17. sklearn逻辑回归参数设置_【机器学习笔记】:逻辑回归实战练习(二)
  18. [双指针|模拟] leetcode 15 三数之和
  19. Linux多进程编程(1)
  20. springboot全局异常处理_SpringBoot:如何优雅地处理全局异常

热门文章

  1. FFT和Matlab中操作FFT
  2. NOIP2012开车旅行 【倍增】
  3. 滑动窗口——TCP可靠传输的实现[转]
  4. SQL Server 安装好后 Always On群组配置
  5. select resharper shortcuts scheme
  6. flex+hibernate 中java的pojo与as的pojo的映射问题
  7. 这里天刚黑,而家里都已经后半夜了
  8. centos7输入shell找不到命令_反弹shell原理与实现
  9. 单链表删除所有值为x的元素_C/C++编程笔记:如何使用C++实现单链表?单链表的基本定义...
  10. mnist 0与mnist x 相互衰变半衰期汇总