第四十三讲 写一个内核模块
一、基础知识
1、头文件
头文件 | 说明 |
---|---|
linux/module.h | 包含内核模块信息声明的相关函数 |
linux/init.h | 包含了 module_init() 和 module_exit() 函数声明 |
linux/kernel.h | 包含内核提供的各种函数 |
2、打印信息
前面编写程序都是使用 printf 进行打印消息的,但是在内核不能使用 printf 进行打印消息。因为 printf 是 glibc 提供的打印函数,工作与用户空间。内核模块无法使用 glibc 函数。但是内核提供了一个 printk 函数,能让我们输出打印信息。
-
printf
说明:类似 printf 函数,但是需要指定打印等级
用法:
等级 说明 KERN_EMERG "<0>"通常是系统崩溃前的消息 KERN_ALERT "<1>"需要立即处理的消息 KERN_CRIT "<2>"严重情况 KERN_ERR "<3>"错误情况 KERN_WARING "<4>"警告 KERN_NOTICE "<5>"需要注意 KERN_INFO "<6>"普通消息 KERN_DEBUG "<7>"调试信息 -
查看 printk 打印等级
注意:仅当小于日志等级消息才会被打印
cat /proc/sys/kernel/printk
4 4 1 7
:这是上面命令的返回,分别代表了 4:当前控制台日志级别
4:默认消息日志级别
1:最小控制台日志级别
7:默认控制台日志级别
二、准备工作
-
准备好开发板(我使用的是野火pro开发板)一个并且烧录好 debian 镜像(如果你的没有烧录,可以参考镜像烧录)
-
启动开发板并且搭建好 nfs 环境(参考:nfs)
-
下载 debain 内核源码([源码](git clone https://gitee.com/Embedfire/ebf-buster-linux.git))
注意:这里下载有可能会失败的,可以参考这篇文章(下载失败解决)
错误代码
error: invalid path 'drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c' fatal: unable to checkout working tree warning: Clone succeeded, but checkout failed. You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'
要在代码文件夹内执行命令
git reset git config core.protectNTFS false git checkout
-
安装工具以及需要的库(命令:sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop)
三、编译内核源码
将源码下载下来后,放入Ubuntu中。进入源码根目录,然后执行命令sudo ./make_deb.sh
进行源码编译。这个过程可能会很长,当然,如果你的电脑性能比较优异的话,可以多线程编译。这样可以加快速度哦!
这里介绍个小知识,打开内核源码根路径,里面有一个文件名为make_deb.sh
的脚本文件。打开这个文件make_deb.sh
。
文件内容是
deb_distro=bionic
DISTRO=stable
build_opts="-j 6"
build_opts="${build_opts} O=build_image/build"
build_opts="${build_opts} ARCH=arm"
build_opts="${build_opts} KBUILD_DEBARCH=${DEBARCH}"
build_opts="${build_opts} LOCALVERSION=-imx-r1"
build_opts="${build_opts} KDEB_CHANGELOG_DIST=${deb_distro}"
build_opts="${build_opts} KDEB_PKGVERSION=1${DISTRO}"
build_opts="${build_opts} CROSS_COMPILE=arm-linux-gnueabihf-"
build_opts="${build_opts} KDEB_SOURCENAME=linux-upstream"
make ${build_opts} npi_v7_defconfig
make ${build_opts}
make ${build_opts} bindeb-pkg
前面说的加快速度可以修改这里build_opts="-j 6"
。例如修改为 build_opts="-j 12"
。另外还有就是我们编译出来的东西在哪呢?答案是build_opts="${build_opts} O=build_image/build"
。也就是在源码根路径下的build_image/build
里面,如果你想放在其他路径下直接修改路径即可。这个地方划重点,后面要用的。
三、内核模块实验
-
第一步先创建我们实验的文件夹(以下仅是示例)
mkdir 001kernel_hello
-
实验代码
#include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> /*Init functions*/ static int __init helloInit(void) { printk(KERN_ERR"Enter hello init functions\r\n"); return 0; } static void __exit helloExit(void) { printk(KERN_ERR"Exit hello init functions\r\n"); return 0; } /*声明函数*/ module_init(helloInit); module_exit(helloExit); /**/ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yeyu"); MODULE_DESCRIPTION("My first module!"); MODULE_ALIAS("hello_module");
-
makefile
KERNEL_DIR=../../ebf_linux_kernel/build_image/build/ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := hello.o all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules .PHONE:clean copy clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean copy: sudo cp *.ko /home/dragon/nfsshare
-
这样完成后直接
make
就能编译出ko文件啦 -
将.ko文件放入nfs共享文件夹(仅仅只是示例)
cp hello.ko ../../../nfsshare/
-
打开开发板
-
登陆开发板
-
连接nfs(参考)
-
开发板进入共享文件夹,查看.ko文件是否存在(如果不存在的话请将ko文件复制到共享文件夹里面)
-
开发板加载内核模块
sudo insmod /mnt/hello.ko
-
实验结果
[ 669.212442] hello: loading out-of-tree module taints kernel. [ 669.232127] Enter hello init functions
-
注意事项:
这个实验我做了三天,这是因为什么原因呢?我按照视频教程的一步一步跟着做,左后卡在加载模块了。模块始终编译不进去。最后查找到原因是因为我开发板的内核版本跟克隆下来的内核版本不一致。后面你们做这个实验的时候很可能也会遇到这个问题。解决方案就是下载正确的内核版本,然后使用这个内核编译成ko文件。这里贴上下载地址。还有个问题是不要在windows下克隆代码,直接去Ubuntu下克隆。否则会下载出错。具体原因据说是因为一些文件不支持。我也没有深究。
四、感想
做完这个实验,我发现视频教程里面的一些不足之处,当然,后面的教程我也会接着做。但是强烈建议大家多看看也或提供的pdf教程。视频教程里面有些地方描述的还不是很准确。当然,从另外一方面也教会了大家发现问题,解决问题的能力。一起加油吧!!!这个实验做的比较久,可能里面存在着一些不足,欢迎大家跟我一起讨论、一起交流!!!