目录
一、实验环境
二、编译4.19.35版内核
1、下载linux内核源码
2、安装必要的环境工具库
3、一键编译内核
4、获取编译出来的内核相关文件(与makefile文件一致)
三、内核模块代码分析
1、内核模块头文件
2、内核模块打印函数
3、文中语法分析
4、内核模块信息声明
四、Makefile文件分析
五、编译
一、实验环境
- 开发板烧录好debian镜像
- 启动开发板,搭建SSH服务器,挂载共享文件夹(挂不挂载都无所谓的,SSH足够)
- 获取debian镜像的内核源码并编译
二、编译4.19.35版内核
内核模块的功能需要依赖内核提供的各种底层接口
1、下载linux内核源码
github:
git clone -b ebf_4.19.35_imx6ill https://github/Embedfire/ebf_linux_kernel.git
gitee:
git clone https://gitee.com/Embedfire/ebf_linux_kernel_6ull_depth1
2、安装必要的环境工具库
sudo apt install make gcc-arm-linux-gnueabihf gcc bison flex libssl-dev dpkg-dev lzop
3、一键编译内核
sudo ./make_deb.sh
注意:之前使用的是22.04的Ubuntu版本,不知道为什么编译不了,换成Ubuntu18.04版本的就可以编译了
在make_deb.sh脚本中注明了编译好的内核存放位置
4、获取编译出来的内核相关文件(与makefile文件一致)
make_deb.h
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=-carp-imx6"
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
在make_deb.h文件的第四行指定了编译出来的内核相关文件的存放位置,这个需要与内核模块文件夹里的makefile指定编译位置相同,否则会报找不到文件错误。
/home/geralt/linux_driver/kernel/ebf_linux_kernel_6ull_depth1/build_image/build
三、内核模块代码分析
helloworld.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
printk(KERN_EMERG "[ KERN_EMERG ] Hello World Module Init\n");
printk( "[ default ] Hello World Module Init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("[ default ] goodbye\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello world module");
MODULE_ALIAS("test_module");
1、内核模块头文件
- #include<linux/module.h>:包含内核模块声明的相关函数
- #include<linux/init.h>:包含了module.init()和module.exit()函数的声明
- #include<linux/kernel.h>:包含了内核提供的各种函数,如printk
2、内核模块打印函数
- printf:glibc实现的打印函数,工作与用户空间
- printk:内核模块无法使用glibc库函数,内核自身实现的一类printf函数,但是需要指定打印等级
- #define KERN_EMERG "<0>" 通常是系统崩溃前的信息
- #define KERN_ALERT "<1>" 需要立即处理的信息
- #define KERN_CRIT "<2>" 严重情况
- #define KERN_ERR "<3>" 错误情况
- #define KERN_WARNING "<4>" 有问题的情况
- #define KERN_NOTICE "<5>" 注意信息
- #define KERN_INFO "<6>" 普通信息
- #define KERN_DEBUG "<7>" 调试信息
查看当前系统printk打印等级:
cat /proc/sys/kernel/printk
分别对应下面4个等级
- 当前控制台日志级别(小于控制台日志等级的才能被打印出来)
- 默认消息日志级别
- 最小的控制台级别
- 默认控制台日志级别
打印内核所有打印信息:dmesg
- 内核log缓冲区大小有限制,缓冲区数据可能被冲掉
3、文中语法分析
- static:将static修饰的函数限制在本文件之内
- __init中的__:将函数放在指定的段里面
4、内核模块信息声明
- MODULE_LICENSE():表明模块代码接受的软件许可协议,Linux内核遵循GPL_V2开源协议,内核模块与linux模块保持一致即可
- MODULE_AUTHOR:描述模块的作者信息
- MODULE_DESCRIPTION():对模块的简单介绍
- MODULE_ALIAS():给模块设置一个别名
四、Makefile文件分析
Makefile
KERNEL_DIR=/home/geralt/linux_driver/kernel/ebf_linux_kernel_6ull_depth1/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := helloworld.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/geralt/my_dir
- KERNEL_DIR:指定linux内核具体路径
- export:导出变量给子Makefile使用
- obj-m:=<模块名>.o:定义要生成的模块
- $(MAKE):Makefile的默认变量,值为make
- 选项“-C”:让make工具跳转到linux内核目录下读取顶层Makefile
- M=:表示内核模块源码目录
- $(CURDIR):Makefile默认变量,值为当前目录所在路径
- make modules:执行Linux顶层Makefile的伪目标,它实现内核模块的源码读取并编译为.ko文件
五、编译
(1)在part_1文件夹执行make命令,执行结果如下:
(2) 执行make copy命令将生成的ko文件复制到自己Ubuntu的共享文件夹
(3) 将共享文件夹里面的ko文件通过mobaxterm软件和SSH服务器拉到板子里面
(4)进行内核模块加载
- 使用insmod命令加载
- 使用rmmod命令卸载
sudo insmod /home/ubuntu/MyWork/helloworld.ko
(5)使用dmesg命令查看所有的打印信息,可以看到内核模块代码输出的打印信息
(6)可以使用lsmod命令查看所有被加载的模块
(7)最后有一个问题就是,我目前设置里面设置的控制台日志打印等级是4,但是小于该等级的日志信息并没有在我加载内核模块的时候直接打印出来,待解决!!
(8)使用rmmod命令卸载模块的时候,后面只需要加上模块名字即可卸载!