内核模块是什么?

Linux下的内核模块类似于Windows下的DLL动态链接库技术,和我们平常所使用的一些动态链接的SDK库一样,只是调用者是内核而已,不是用户态的程序。

内核模块拥有的的权限是和用户态一样吗?

内核模块是活跃在Linux内核中的,它的权限更多,与内核空间下的进程一样的权限,比用户态的权限要高,意味着它拥有对物理设备以及内存中其它空间的访问权限,一般嵌入式开发时内核模块用到的比较多,用于开发LED模块等等。

内核是如何维护模块的?

内核在加载模块时会将模块的符号信息写入一张符号表里,这个表里记录着所有模块的符号地址,用于维护函数之间的调用。

此外内核还会去分析模块之间的依赖性,所以内核还要去维护模块之间的依赖性,如有些模块依赖别的模块的功能,内核加载模块前需要校验当前内核是否有依赖的模块在运行,否则停止加载此模块,不然会产生出乎意料的问题。

内核模块在内核中是如何运行的?

当内核把模块加载到内核空间时,模块就像用户态的程序一样,被加载到内存空间了,只是加载到的空间地址不一样,只是把这一个程序加载到内核空间去了,而不是用户空间,所以内核模块其实运行和用户态的程序没有区别,只是权限不一样。

内核模块有什么优点?

对于Linux内核来说,这样的好处在于可以将一部分功能以模块的方式分割出去,从而节省内核体积,因为有许多功能可能平常内核压根用不到,如果加载到内核空间去,会浪费系统资源,第一占用内存,第二内核态的程序会被单独一个核执行,即便这个内核模块一直执行的是while(0)这样的指令,也会浪费CPU效率,所以这样做法能节省内存资源,以及系统调用。

内核模块有什么缺点?

缺点显而易见,内核模块拥有和内核态一样的权限,如果一些不好的内核模块可能导致电脑死机,或者一些出乎意料的事情发生,让整个系统混乱或者崩溃,访问了一些PC机上规定一些只读区域,或者修改了一些硬件资源,修改GDT与LDT等都有可能导致系统混乱。

其次是内核模块编写时只能使用内核态提供的函数,而且不能去进行浮点数运算,至于为什么可见我写的这一篇文章:https://blog.csdn.net/bjbz_cxy/article/details/106103927

刚刚说内核模块必须使用内核提供的函数,如果内核版本与内核模块版本不一致,就会导致一些奇怪的问题,如内核迭代过程中某些函数被废弃,或者内核的一些特性改了,都会导致内核模块无法正常执行。

内核模块如何编写?

我们可以在任何Linux发行版上进行内核模块开发,内核模块开发流程比较简单,就像我们平时写c语音代码那样就可以了,只是需要符合一些规则,其次不能使用自定编译器编译,我们只能使用makefile进行编译,且makefile需要符合kbuild规则。

什么是kbuild?

kbuild是Linux内核项目里所使用的一种Makefile编译脚本,它基于Makefile,扩展了一些写法,也就是说我们开发的内核模块需要符合Linux内核的代码规则,写法是很统一的,这是Linux为了防止不同开发者有不同的编写风格,为了风格一致而提出的解决方案,不然那么多开发者们所编写代码,岂不是乱糟糟的,我相信这一点对于已经工作的小伙伴们来说非常容易理解,因为公司都有自己的代码风格,Linux内核也是。

内核模块有几种编译方式?

有两种,第一种是基于Linux内核的编译,也就是说把我们的内核模块代码文件,放入到Linux内核项目里,然后在Makefile文件里增加我们的内核模块文件路径,最后在config里配置选上我们的内核模块,然后执行make编译Linux内核,这样我们的内核模块就会被附加到Linux内核里了。

第二种是单独编译,这个也是基于Linux内核,只是我们可以在任意磁盘文件上对这个内核模块进行编译,但是需要自己编写Makefile文件,并且在Makefile文件里需要指定Linux内核源码路径,因为kbuild编译需要加载Linux内核里的一些库,这也是我们上面说过,为什么某些模块只能在对应的内核版本里运行,因为编译依赖,我们我们直接make,就会生成.ko文件,这个是Linux内核模块文件扩展名,这样一个单独编译的内核模块就生成了,可以使用“insmod”命令去加载模块,在任意发行版上加载,需要root权限。

开始编写Linux内核模块

第一步创建.c文件,不能使用c++或者其它编程语言,需要符合Linux内核项目体系,Linux是c语言和少量汇编写的,所以编译器基本上是GCC还有GNU其它的一些项目编译器

创建一个.c文件,文件名可以随便起,这个是没有限制的

这里我创建一个名为“hello”的c语言源文件,并进入编辑模式

vim hello.c

写代码前,先介绍几个宏定义:

当我们编写完模块后使用“modinfo”命令可以查看模块的一些信息,使用这些宏可以定义我们的信息

MODULE_AUTHOR 定义模块作者

MODULE_LICENSE 协议许可证 一般定义为GPL,注意因为是Linux内核上,我们使用的都是GNU的编译器,一般只能是GPL,因为这是GNU组织的规定,如果你使用它们的产品开发软件,许可证上必须是GPL,GPL协议的作用可以互联网搜索一下

MODULE_DESCRIPTION 说明,模块说明

MODULE_ALIAS 模块别名

“MODULE_AUTHOR ”和“MODULE_LICENSE ”是必须要写的,这样才能符合Linux内核模块的标准,“MODULE_DESCRIPTION”和“MODULE_ALIAS ”可选

在介绍两个函数

module_init 定义入口函数,也就是定义当模块加载后执行的第一个函数,启动函数

module_exit 定义退出函数,当模块被卸载后,Linux内核会去调用这个函数

类似Windows上的进入与退出事件

在模块里是没有Main函数的,因为模块是被链接,而非直接编译成elf文件,模块最终会被编译成.o文件,然后在格式化成.ko文件,这些文件是库文件,不允许有主函数,它们只能被Linux内核加载,并调用,这相当于一个主从关系。

其次这里说一下编写init和exit函数时需要在后面加上_init

如hello_init,这样写函数命名符合Linux的一些关键函数规范,当然你也可以不加,这些是Linux内核的代码规范,建议加上

首先第一步我们需要包含三个常用的基本头文件:

#include<linux/module.h>
#include<linux/init.h>
#include<linux/moduleparam.h>

介绍一下三个头文件的作用:

linux/module.h 包含了一些常用的宏函数还有一些基本数据结构,MODULE_AUTHOR 这些宏就包含在这个头文件上

linux/init.h 包含一些常用初始化函数,如module_init这些函数就存在于这个头文件下

linux/moduleparam.h 包含了一些用户态向内核态模块传递参数的函数,当模块被加载到内核态时,我们传递参数的话是不能像用户态那样,必须是使用内核态的一些获取参数方法

具体可以进入到Linux源代码下去看一下这些头文件里的函数定义

这里第一步我们不在定义main函数咯,而是先注册好一些基本信息,这里我就不注册别名之类的了

MODULE_AUTHOR("Kevin Taylor");
MODULE_LICENSE("GPL");

编写一个init函数:

static int __init hello_init(void){}

这里要定义成静态函数,防止被其它源文件调用,__init是Linux内核下的一个宏,注册函数要求前面加上这个

在定义一个退出函数:

static void __exit hello_exit(void){}

我们在使用module_init与module_exit注册一下:

module_init(hello_init);
module_exit(hello_exit);

这样一个基本的Linux模块代码基本架构就完成了。

注意这里不能使用任何用户态的代码,如printf,还有open等等这些函数,这些函数都是基于Linux内核里的函数实现的,所以这里我们需要使用Linux内核态的一些函数,如printk就是打印函数

所以这里我们使用printk进行打印:

先介绍一下printk吧:

printk参数与printf一样,但是printk的打印是有级别的,且打印出的数据是不会出现在终端tty的,而是放入到内核日志缓冲区里,
而printk有打印级别:

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

默认是KERN_WARNING,根据打印级别的不同在日志中显示的格式也不一样,数字越小,优先级越高,如果优先级低于4,那么Linux内核会除了将其输出到内核日志缓冲区里,也会打印到终端里。

这里我们在init里加入一行printk函数:

printk("hello linux kernel");

在退出也加入一行

printk("bye linux kernel");

我们现在无需指定内核的优先级,因为默认是KERN_WARNING,也就是说这种情况下只会输出到内核日志缓冲区里。

那么接下来就要开始写makefile了

vim Makefile

这里先给大家看一段kbuildMakefile代码

ifneq ($(KERNELRELEASE),)obj-m  := hello.o
elseKDIR ?= /lib/modules/`uname -r`/build
all:$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KDIR) M=$(PWD) clean
endif

这里的obj-m是kbuild里的语法表示编译可生成的模块,这里不需要写hello.c,kbuild编译器会自动推到.c文件

如给的是hello.o,最后会寻找hello.c然后编译成hello.o

这里的

ifneq ($(KERNELRELEASE),)

这里是一个条件分支,用来判断KERNELRELEASE变量是否存在

KERNELRELEASE变量是存在于Linux内核目录下顶层目录里Makefile里的一个变量,作用就是用来判断是否为第一次编译

第一次如果没有执行的话,则转入到else里去,else执行完成之后则判断是all还是clean,如果什么都没有直接make则认为是all

那么问题来了,肯定会想,既然obj-m :=hello.o这个没有被执行,那么make是如何知道我们要编译hello.c呢?

答: obj-m属于一个变量,kbuild会读取这个变量里的值的,经过我测试,发现make里声明变量,无论你放在任何位置,make都会首先把变量声明出来,在去执行,也就是说即便obj-m放在ifneq下,即便表达式不成立make也会将其声明出来,上面这段代码可以改成这个样子:

obj-m  := hello.o
ifneq ($(KERNELRELEASE),)elseKDIR ?= /lib/modules/`uname -r`/build
all:$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KDIR) M=$(PWD) clean
endif

如果不是第一次编译则不去执行内核目录下的模块编译make,否则就去执行一下

all和clean就比较容易理解了,就是去执行内核对应路径下的make而已

直接make一下:

编译完成,看一下生成的文件:

其中hello.ko就是我们需要的内核模块了,其它的都是临时文件

加载内核模块

加载命令是:insmod

卸载命令是:rmmod

查看已加载的模块:lsmod

用法:

注意要sudo,内核权限

sudo insmod hello.ko

加载完成后使用dmesg | tail命令查看模块输出信息

可以看到打印出来啦

使用lsmod可以查看已加载的模块,在里面找找我们的模块

格式:lsmod

可以看到模块非常多,我们联合grep来寻找我们的模块:

lsmod | grep hello 注意不需要.ko

找到啦,正在正常的运行

在卸载掉这个模块,然后看一下是否打印出退出的日志信息:

sudo rmmod hello 

看一下日志:

成功~

Linux内核开发_内核模块相关推荐

  1. Linux内核开发_将Linux内核打包成img文件

    接着之前两个文章我们已经编译了Linux内核和制作了一个文件系统 这一步我们将它们打包成一个img软盘文件 1.使用DD命令创建一个镜像的img文件 sudo dd if=/dev/zero of=m ...

  2. Linux内核开发:内核模块参数

    目录 使用参数加载模块 module_param宏 读取和更改参数值 验证参数值 声明模块参数数组 有关模块参数的一些说明 在本文中,我们将向模块添加参数.使用参数,您可以在加载模块时访问模块全局变量 ...

  3. linux内核驱动ldd3_走进linux 驱动开发 之 内核模块

    一.Linux内核简介 1.宏内核与微内核 内核分为四大类:单内核(宏内核):微内核:混合内核:外内核. 宏内核(Monolithickernel)是将内核从整体上作为一个大过程来实现,所有的内核服务 ...

  4. 嵌入式系统Linux内核开发工程师必须掌握的三十道题

    嵌入式系统Linux内核开发工程师必须掌握的三十道题 如果你能正确回答以下问题并理解相关知识点原理,那么你就可以算得上是基本合格的Linux内核开发工程师,试试看! 1) Linux中主要有哪几种内核 ...

  5. 何为 Linux 内核开发,怎么学好 Linux 内核?

    此文包含 Linux 系统的学习路径和书籍推荐. 我觉得学习 Linux 系统,内核驱动时,最开始只需要 'Know what, not know how '. 不用去探究细节,只需要知道整体的框架, ...

  6. Linux驱动开发_设备文件系统详解

    目录 何为设备管理器? Linux下dev的作用 Devfs sysfs kobject udev proc 何为设备管理器? 设备管理器就是负责管理这台电脑上的外设,当我们通过电脑提供的USB口插入 ...

  7. linux内核开发入门二(内核KO模块介绍、开发流程以及注意事项)

    linux内核开发入门二(内核KO模块介绍.开发流程以及注意事项) 一.什么是内核模块 内核模块:ko模块(Kernel Object Module)是Linux内核中的可加载模块,它可以动态地向内核 ...

  8. Linux 内核开发特点

    Linux 内核 开发的特点 无 libc 库抑或无标准头文件库 GUN C 没有内存保护机制 不要再内核中轻易使用浮点数 容积小而且固定的栈 同步和并发 可移植性的重要性 参考文献 开发的特点 相对 ...

  9. Linux内核开发工作方向

    Linux内核开发工作方向

最新文章

  1. 3Dmax+V-Ray学习建筑可视化教程
  2. 在linux中安装Qt4.8,在linux 如何安装qt 4.8.1
  3. 当集合a为空集时a的取值范围_高中数学必修一第一章集合分节练习和章末测试题含答案[1] 2...
  4. 中ridge_10种线性代数在数据科学中的强大应用(内附多种资源)
  5. Git 别名(分布式版本控制系统)
  6. 将本地代码上传到github中
  7. 微信小程序连接本地接口(转)
  8. 如何学好C语言程序设计?
  9. 深入学习jquery源码之show()和hide()
  10. PCWorld:HTML5会终结移动应用程序吗?
  11. 未能找到主机服务器是什么鬼,未能找到指定主机服务器是什么意思
  12. Oracle JDE R23更新快报
  13. JSON 在线格式化工具感觉挺好用的
  14. 【分享贴】PCB设计思路
  15. html生日快乐源代码
  16. STM32实现的语音识别的智能垃圾桶
  17. 增加点赞手势图及提交按钮图标
  18. 基于图像识别的跌倒检测
  19. 嵌入式系统设计---实时系统与嵌入式操作系统
  20. Hive启动报错 java.lang.RuntimeException: org.apache.hadoop.hive.ql.metadata.HiveException: java.lang

热门文章

  1. python selenium安装失败_python:学习selenium遇到的坑
  2. python正则匹配中文_python 正则表达式匹配中文-阿里云开发者社区
  3. Arcgis10.2安装及LicensenManager10.2启动失败解决方法
  4. git学习(八)pull,fetch,merge
  5. dsp c语言程序设计,DSP C语言程序设计.pdf
  6. 数据库修改后 前台同步更新 php,PHP实现前台页面与MySQL的数据绑定、同步更新...
  7. Duplicate keys detected: ‘checks‘. This may cause an update error. found in
  8. tp5--权限操作(auth类)基本使用
  9. idea debug异常关闭 Error running 'Tomcat8': Unable to open debugger port (127.0.0.1:50168): java.net.Soc
  10. Chrome浏览器提示您的连接不是私密连接解决办法