00-一个helloworld级别的内核模块
本文针对嵌入式设备
0.准备工作
- 安装并配置好交叉编译工具链
- 交叉编译linux内核,为编译内核模块提供编译环境
1.内核模块的入口和出口
linux内核提供了一对注册宏module_init(initfn)
和module_exit(exitfn)
,它俩用于向内核注入内核模块的入口函数和出口函数。分别对应的声明为:
int init_module(void);
void cleanup_module(void);
- 入口函数在模块加载时做一些初始化动作,出口函数在模块卸载时做一些清理动作。
完整示例为:
#include <linux/module.h>
//加载内核模块后的入口函数
static int __init simplest_drv_init(void)
{
printk(KERN_INFO "Simplest driver init\n");
return 0;
}
//卸载内核模块后的清理函数
static void __exit simplest_drv_exit(void)
{
printk(KERN_INFO "Simplest driver exit\n");
}
module_init(simplest_drv_init);
module_exit(simplest_drv_exit);
- 内核模块里面的函数如果不需要导出给外部使用(使用
EXPORT_SYMBOL
宏来实现),通常声明为static,主要是为了避免符号冲突,起到隔离作用。__init
和__exit
是一个编译器扩展标志的宏,内核源码里面注释了它的作用:标记函数或者初始化的变量(不要对未初始化的数据使用),在初始化动作完成后释放相关内存资源。
2.外部模块的Makefile
需要一个Makefile来方便编译:
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
KERN_DIR=/mnt/2Thdd/wds/100ask_imx6ull-sdk/Linux-4.9.88/
obj-m += simplest_drv.o
all:
make -C ${KERN_DIR} M=$(shell pwd) modules
clean:
make -C ${KERN_DIR} M=$(shell pwd) clean
嗯,没错我用的是韦东山的imx6ull pro开发板,9年前入门也是看他的2440视频,但是后面因为工作原因去搞应用开发了。
makefile稍微解释下:
- 开头的两个export是指shell里面导出环境变量,kernel里面的Makefile会推导使用它们,好奇的可以看看。
obj-m += simplest_drv.o
,内核编译框架下指定将当前文件下的simplest_drv.c(makefile的自动推导,由.o推导出.c)编译为内核模块。还有一个是obj-y
表示直接编译进内核,这里不发散。- ‘-C’进到指定目录执行make命令,这里就是内核编译目录。
- 后面的
M=xxx
是内核编译框架下指定要编译的外部内核模块的源码目录,modules
和clean
是makefile的两个目标而已。
以下是拷贝自内核顶层Makefile里面针对外部模块的注释
###
# External module support.
# When building external modules the kernel used as basis is considered
# read-only, and no consistency checks are made and the make
# system is not used on the basis kernel. If updates are required
# in the basis kernel ordinary make commands (without M=...) must
# be used.
#
# The following are the only valid targets when building external
# modules.
# make M=dir clean Delete all automatically generated files
# make M=dir modules Make all modules in specified dir
# make M=dir Same as 'make M=dir modules'
# make M=dir modules_install
# Install the modules built in the module directory
# Assumes install directory is already created
3.编译并测试
加载内核模块通过insmod
命令,卸载通过rmmod
,查看当前已加载的内核模块则通过lsmod
。
通过scp把编译出来的simplest_drv.ko
搞进开发板,并加载模块,然后查看:
[root@100ask:~]# insmod simplest_drv.ko
[43988.862630] Simplest driver init
[root@100ask:~]# lsmod
Module Size Used by
simplest_drv 991 0
[root@100ask:~]# rmmod simplest_drv.ko
[43994.023260] Simplest driver exit
[root@100ask:~]# lsmod
Module Size Used by
insmod
会导致sysfs
的挂载目录sys
下的module里面新增一个目录:
[root@100ask:/sys/module]# ls -d simplest_drv
simplest_drv
这个内核模块啥都没干,就是注册并跑了下入口函数和出口函数,所谓hello world
级别。