嵌入式Linux学习笔记(一) 内核模块编程

白昼怎懂夜的黑 提交于 2020-04-22 14:05:06

1.总结

  从事嵌入式行业多年,虽然因为工作原因接触过嵌入式Linux,也参与过相关产品的底层和应用功能开发,但对于嵌入式Linux的内核,驱动,以及上层开发,仍然停留在看的懂,基本会用的水平,没有过系统深入的去总结,随着工作年限的递增,越来越感受到这种浮躁感带来的技术面瓶颈。既然发现了问题,自然就要去解决,回想起我踏入嵌入式行业来的经历,正是对STM32芯片以及网络部分的学习总结笔记支撑我走到如今的地步,那么沉淀下来,从嵌入式Linux驱动开发开始,对嵌入式Linux进行系统的学习总结也是最符合我目前现状的解决办法,这也是我下定决心放弃日常娱乐,开始本系列的由来。

  嵌入式Linux的掌握学习是很复杂的过程,从最基础的Linux安装,shell指令的学习和应用,交叉编译环境搭建,C语言开发,Linux内核接口,Linux系统接口,在掌握了前面所有知识后,才只是完成了产品开发的基础构建,这些知识不仅对于学习是难点,对于已经掌握的人来用文字描述清楚,特别是系统/软件版本引发的编译,调试问题,在学习以及开发中遇到的问题会抽空整理出来,在本篇中也会涉及部分。如果不理解,要善于使用搜索引擎,嵌入式Linux开发遇到的问题基本都能找到答案,一定要切记。不多说了,下面开始正篇的说明。

2. 内核模块实现

   嵌入式Linux的驱动都是以模块的形式出现,掌握内核模块的实现是必须的。其实简单来说,Linux定义内核模块需要实现的接口,开发者只要按照规则用C语言实现这些需要的接口,在按照一定的规则编译,就可以使用系统指令lsmod/rmmod来说加载和移除自定义的模块,这套规则就是我们掌握内核模块需要学习的知识,按照功能分为以下接口:

必须模块

  模块加载函数:module_init(func)

  模块卸载函数:  module_exit(func)

  模块许可声明:MODULE_LICENSE(“str”)

    支持的许可有: "GPL", "GPL V2", "GPL and additional right", "Dual BSCD/GPL", "DUAL MPL/GPL", "Proprietary"

可选模块

  模块参数 -- 模块加载时传递变量 module_param(name, charp, S_IRUGO);

  模块导出符号 --用于将符号导出,用于其它内核模块使用。

    EXPORT_SYSMBOL(func)/EXPORT_SYSMBOL_GPL(func)

    Linux内核2.6增加了函数校验机制,后续模块需要引入时要在Module.symvers下添加导入函数内核的路径和symbol。

  模块作者 -- MODULE_AUTHOR(“str”)

  模块描述 -- MODULE_DESCRIPTION(“str”)

  模块版本 -- MODULE_VERSION(“str”)

  模块别名 -- MODULE_ALIAS(“str”)

  模块设备表 -- MODULE_DEVICE_TABLE, 对于USB或者PCI设备需要支持,表示支持的设备,目前不在详细描述。

  在了解上述模块的基础上,就可以实现如下的模块代码:

//hello.ko
#include <linux/init.h>
#include <linux/module.h>


//extern int add_integar(int a, int b);
static char *buf = "driver";
module_param(buf, charp, S_IRUGO); //模块参数

static int __init hello_init(void)
{
        int dat = 3; //int dat = add_integar(5, 6);
        printk(KERN_WARNING "hello world enter, %s, %d\n", buf, dat);
        return 0;
}
module_init(hello_init);  //模块加载函数

static void __exit hello_exit(void)
{
    printk(KERN_WARNING "hello world exit\n");
}
module_exit(hello_exit);              //模块卸载函数

MODULE_AUTHOR("ZC");                         //模块作者
MODULE_LICENSE("GPL v2");                     //模块许可协议
MODULE_DESCRIPTION("a simple hello module");  //模块许描述
MODULE_ALIAS("a simplest module");            //模块别名

使用Makefile文件如下:

ifeq ($(KERNELRELEASE),)
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
        $(MAKE) -C $(KDIR) M=$(PWD) modules
modules_install:
        $(MAKE) -C $(KDIR) M=$(PWD) modules_install
clean:
        rm -rf *.o *.ko .depend *.mod.o *.mod.c modules.*
.PHONY:modules modules_install clean
else
obj-m :=hello.o
endif

保存后,使用Make即可编译,如果遇到编译错误,请先查看文章最后的备注,未包含问题请搜索或者留言,编译结果如图所示。

 

之后执行指令modinfo hello.ko即可查看当前的模块信息。

如果无法查看信息,可通过dmesg查看加载信息。

2.内核模块的跨模块调用

  上一节可以解决我们遇到的大部分内核实现问题,但某些时候我们可能需要一些公共内核模块,提供接口给大部分模块使用,这就涉及到内核模块的跨模块调用。

  对于跨核模块调用的实现,对于调用的模块,主要包含2步:

    1、  在代码实现中添加extern int add_integar(int a, int b);

    2、  在编译环境下修改Module.symvers, 添加被链接模块的地址,函数校验值(可通过查看被链接模块编译环境下的Module.symvers内复制即可)

  对于被链接的模块,代码实现如下:

//math.ko
#include <linux/init.h>
#include <linux/module.h>

static int __init math_init(void)
{
    printk(KERN_WARNING "math enter\n");
    return 0;
}
module_init(math_init);

static void __exit math_exit(void)
{
    printk(KERN_WARNING "math exit\n");
}
module_exit(math_exit);

int add_integar(int a, int b)
{
        return a+b;
}
EXPORT_SYMBOL(add_integar);

int sub_integar(int a, int b)
{
        return a-b;
}
EXPORT_SYMBOL(sub_integar);

MODULE_LICENSE("GPL V2");

编译Makefile同上,需要将obj-m :=hello.o修改为obj-m :=math.o

执行make编译完成该文件,并通过insmod加载完模块后,可通过

grep integar /proc/kallsyms 查看加载在内核中的符号,状态如下:

 然后加载insmod hello.ko, 即可跨文件调用该接口。如此,便初步完成对Linux内核模块的学习。

本文参考资料:

 1. 《Linux设备驱动开发详解:基于最新的Linux4.0内核》 第四章

备注:

  下面讲述编译遇到的问题和解决方案。

  1.内核编译名称必须为Makefile,否则编译会出错

    make[2]: *** No rule to make target `/usr/kernel/hello/Makefile'.  Stop.

    make[1]: *** [_module_/usr/kernel/hello] Error 2

    make[1]: Leaving directory `/usr/src/linux-headers-3.5.0-23-generic'

    2.Makefile的内容,如果编译多个文件obj-m :=hello.o test.o

  3.Makefile中,指令必须以Tab对齐,否则编译会异常。

  4.printk不打印,一般来说输出的KERNEL_INFO为超过最大输出值,可直接通过dmesg,在系统信息内查看。

  5.内核跨文件访问接口

  除EXPORT_SYSMBOL外,在编译时Module.symvers需要包含对应函数的校验值,路径

  0x13db98c9      sub_integar     /usr/kernel/math/math   EXPORT_SYMBOL

  0xe1626dee      add_integar     /usr/kernel/math/math   EXPORT_SYMBOL

  否则编译时报警告

    WARNING: "add_integar" [/usr/kernel/hello/hello.ko] undefined!

  安装模块时出错

    [ 9091.025357] hello: no symbol version for add_integar

    [ 9091.025360] hello: Unknown symbol add_integar (err -22)

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!