zoukankan      html  css  js  c++  java
  • Compile-kernel-module

    Compile-kernel-module

    1. 内核模块编程
    1.1 简介
    1.2 加载内核模块
    1.3 最简单的模块
    1.4 模块必要信息
    1.4.1 内核模块必须至少包含的头文件:
    1.4.2 内核模块必须至少有两个功能:
    1.4.3 printk()日志记录
    1.4.4 优先级
    1.4.5 许可和模块文档
    1.5 编译内核模块
    1.6 实战
    1.6.1 源代码文件: hello.c
    1.6.2 Makefile文件: Makefile
    1.6.3 使用make编译
    1.6.4 使用modinfo查看新的模块文件
    1,6.5 使用insmod加载内核模块
    1.6.6 使用lsmod查看已加载的模块
    1.6.7 使用rmmod卸载模块
    1.6.8 使用dmesg查看日志
    1.6.9 练习
    1.6.10 其他信息
    1.7 更多基础示例链接
    1.7.1 将命令行参数传递给模块
    1.7.2 跨越多个文件的模块
    1.7.3 构建预编译内核的模块
    1.7.4 与应用程序交互
    2 make工具
    2.1 DESCRIPTION
    2.2 退出状态
    2.3 选项
    3. 其他内核编译方法及相关连接
    3.1 编译Arch的内核模块
    3.2 Arch构建系统
    3.3 内核/传统编译
    3.4 修补包
    3.5 模块与程序

    1. 内核模块编程

    http://www.tldp.org/LDP/lkmpg/2.6/html/
    Linux内核模块编程指南 2007-05-18见2.6.4

    1.1 简介

    什么是内核模块?模块是可以根据需要加载和卸载到内核中的代码片段。它们扩展了内核的功能,而无需重启系统。
    例如,一种类型的模块是设备驱动程序,它允许内核访问连接到系统的硬件。还有很多的文件系统等。

    1.2 加载内核模块

    您可以通过运行lsmod来查看已经加载到内核中的模块,lsmod通过读取文件/proc/modules来获取其信息。
    执行modprobe来加载模块.modprobe以两种形式之一传递一个字符串:
      > 模块名称,如softdog或ppp。
      > 一个更通用的标识符,如char-major-10-30。
    如果modprobe被赋予通用标识符,它首先在文件/etc/modprobe.conf中查找该字符串。如果找到如下的别名行:alias char-major-10-30 softdog
    它知道通用标识符引用模块softdog.ko。
    然后,modprobe查看文件/lib/modules/version/modules.dep,查看是否必须加载其他模块才能加载所请求的模块。该文件由depmod -a创建,包含模块依赖项。
          例如,msdos.ko要求fat.ko模块已经加载到内核中。
    最后,modprobe使用insmod首先将任何必备模块加载到内核中,然后加载所请求的模块。
    modprobe将insmod指向/lib/modules/'uname -r'/, 模块的标准目录。

    insmod对于模块的位置是相当愚蠢的,而modprobe知道模块的默认位置,知道如何找出依赖关系并以正确的顺序加载模块。
    因此,例如,如果要加载msdos模块,则必须运行:
    $ insmod /lib/modules/2.6.11/kernel/fs/fat/fat.ko
    $ insmod /lib/modules/2.6.11/kernel/fs/msdos/msdos.ko

    要么:
    $ modprobe msdos
    所以,insmod要求你传递完整的路径名并以正确的顺序插入模块,而modprobe只取名字,没有任何扩展名,并通过解析/lib找出它需要知道的所有内容/modules/version/modules.dep。

    1.3 最简单的模块

    http://www.tldp.org/LDP/lkmpg/2.6/html/x121.html
    Example 2-1. hello-1.c
    #include <linux/module.h> /* Needed by all modules */
    #include <linux/kernel.h> /* Needed for KERN_INFO */
    int init_module(void)
    {
    printk(KERN_INFO "Hello world 1. ");
    return 0;
    }
    void cleanup_module(void)
    {
    printk(KERN_INFO "Goodbye world 1. ");
    }

    1.4 模块必要信息

    1.4.1 内核模块必须至少包含的头文件:

    #include <linux/module.h> /* module_init() module_exit() 函数来注册模块入口和退出处理。
    #include <linux/kernel.h> /* Needed for KERN_INFO ,仅用于printk()日志级别的KERN_ALERT的宏扩展 */
    #include <linux/init.h> /* Needed for the macros */

    1.4.2 内核模块必须至少有两个功能:

    • 一个“开始”(初始化)功能调用 的init_module()当模块insmoded到内核被称为,
    • 以及“结束”(清理)函数调用在cleanup_module()这是刚刚称为在它被破坏之前。
    从Linux 2.4开始,您可以重命名模块的init和cleanup功能; 它们不再需要分别被称为 init_module()和cleanup_module()。
    这是通过 module_init()和module_exit()宏完成的。这些宏在linux / init.h中定义。
    唯一需要注意的是,必须在调用宏之前定义init和cleanup函数,否则会出现编译错误。

    1.4.3 printk()日志记录

    由于代码运行在内核空间里面,不能直接使用用户空间的 print 函数,而要使用内核中的 printk 函数.
    作为日志记录机制,用于记录信息或发出警告。
    每个printk() 语句都带有一个优先级,即您看到的<1>和KERN_ALERT。
    有8个优先级,内核有宏,所以你不必使用神秘的数字,你可以在linux/kernel.h中查看它们(及其含义)。
    如果未指定优先级,则将使用默认优先级DEFAULT_MESSAGE_LOGLEVEL。

    花点时间阅读优先级宏。头文件还描述了每个优先级的含义。
    在实践中,不要使用数字,如<4>。始终使用宏,如 KERN_WARNING。

    如果优先级低于int console_loglevel,则会在当前终端上打印消息。
    如果syslogd和klogd都在运行,那么该消息也将附加到/var/log/messages,无论它是否打印到控制台。
    我们使用高优先级(如KERN_ALERT)来确保将printk()消息打印到控制台而不是仅记录到日志文件中。
    编写实际模块时,您需要使用对当前情况有意义的优先级。

    1.4.4 优先级

    https://szosoft.blogspot.com/2019/06/linux-journal.html#1021
    cat  /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/kernel.h
    cat  /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/printk.h
    内核模块printk no (Key)journal
    KERN_EMERG 0 Emergency 紧急
    KERN_ALERT 1 Alert 警报
    KERN_CRIT 2 Critical 危急
    KERN_ERR 3 Error 错误
    KERN_WARNING 4 Warning 警告
    KERN_NOTICE 5 Notice 注意
    KERN_INFO 6 Informational 信息
    KERN_DEBUG 7 Debug 调试

    1.4.5 许可和模块文档

    MODULE_LICENSE("GPL");  /* Get rid of taint message by declaring code as GPL.  */
    MODULE_AUTHOR("Tom"); /* Who wrote this module? */
    MODULE_DESCRIPTION("Test"); /* What does this module do */
    MODULE_VERSION("0.0.1");
    MODULE_SUPPORTED_DEVICE("testdevice");   /* 声明模块支持哪些类型的设备。 */
    在内核2.4及更高版本中,设计了一种机制来识别在GPL(和朋友)下许可的代码,以便可以警告人们代码是非开源的。
    这是通过MODULE_LICENSE()宏实现的。通过将许可证设置为GPL,可以防止打印警告。
    此许可证机制在linux/module.h中定义并记录:
    $ cat  /usr/lib/modules/5.1.15-arch1-1-ARCH/build/include/linux/module.h |grep MODULE_

    /*
     * 目前接受以下许可证标识为免费软件模块
     * "GPL" [GNU Public License v2 or later]
     * "GPL v2" [GNU Public License v2]
     * "GPL and additional rights" [GNU Public License v2 rights and more]
     * "Dual BSD/GPL" [GNU Public License v2 or BSD license choice]
     * "Dual MIT/GPL" [GNU Public License v2 or MIT license choice]
     * "Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice]
     *
     * 以下其他标识可供选择
     * "Proprietary" [Non free products]
     */

    1.5 编译内核模块

    http://www.tldp.org/LDP/lkmpg/2.6/html/x181.html
    内核模块的编译需要与常规用户空间应用程序略有不同。
    以前的内核版本要求我们关注这些设置,这些设置通常存储在Makefile中。虽然按层次结构组织,但许多冗余设置在次级Makefile中累积并使它们变大并且难以维护。
    幸运的是,有一种新方法可以做这些事情,称为kbuild,外部可加载模块的构建过程现在完全集成到标准内核构建机制中。
    要了解有关如何编译不属于官方内核的模块的更多信息(例如本指南中的所有示例),请参阅文件 linux/Documentation/kbuild/modules.txt.

    编译时通过一个 Makefile 文件进行,把这个 Makefile 文件置于 hello.c 同一目录下.
    Makefile对格式有要求。每一行文本除非顶头开始,如果需要格式编排,不能使用空格键来控制文本行缩进,必须使用Tab键

    1.6 实战

    1.6.1 源代码文件: hello.c

    /*  hello.c - Demonstrates module documentation. */
    #include <linux/module.h> /* Needed by all modules */
    #include <linux/kernel.h> /* Needed for KERN_INFO */
    #include <linux/init.h> /* Needed for the macros */
    #define DRIVER_AUTHOR "Peter Jay Salzman <p@dirac.org>"
    #define DRIVER_DESC   "A sample driver"

    static int __init init_hello(void)
    {
    printk(KERN_INFO "HelloWorld ");
    return 0;
    }

    static void __exit cleanup_hello(void)
    {
    printk(KERN_INFO "GoodbyeWorld ");
    }

    module_init(init_hello);
    module_exit(cleanup_hello);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module? */
    MODULE_DESCRIPTION(DRIVER_DESC); /* What does this module do */
    MODULE_SUPPORTED_DEVICE("testdevice");

    1.6.2 Makefile文件: Makefile

    obj-m += hello.o

    all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

    clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

    从技术角度来看,第一行确实是必要的,为了方便起见,添加了“全部”和“清洁”目标。
    现在您可以通过发出命令make来编译模块。您应该获得类似于以下内容的输出:

    1.6.3 使用make编译

    $ make
    make -C /lib/modules/5.1.15-arch1-1-ARCH/build M=/home/toma/ko modules
    make[1]: Entering directory '/usr/lib/modules/5.1.15-arch1-1-ARCH/build'
      CC [M]  /home/toma/ko/hello.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/toma/ko/hello.mod.o
      LD [M]  /home/toma/ko/hello.ko
    make[1]: Leaving directory '/usr/lib/modules/5.1.15-arch1-1-ARCH/build'

    请注意,内核2.6引入了一种新的文件命名约定:内核模块现在具有.ko 扩展名(代替旧的.o扩展名),可以轻松地将它们与传统的目标文件区分开来。
    原因是它们包含一个额外的.modinfo部分,其中保留了有关该模块的其他信息。我们很快就会看到这些信息有什么用处。
    有关内核模块的Makefile的更多详细信息,请参见 linux/Documentation/kbuild/makefiles.txt .

    $ ls -l   //编译后查看文件列表
    name size
    Makefile 154   //make文件
    hello.c 786      //源文件
    hello.ko 4528   //内核文件
    hello.mod.c 646
    hello.mod.o 2712
    hello.o 2664   //目标文件
    modules.order 30
    Module.symvers 0

    1.6.4 使用modinfo查看新的模块文件

    $ modinfo hello.ko
    filename:       /home/toma/ko/hello.ko
    version:        0.0.1
    description:    Test
    author:         Tom
    license:        GPL
    srcversion:     BC3C3A49026E0297D738AE7
    depends:      
    retpoline:      Y
    name:           hello
    vermagic:       5.1.15-arch1-1-ARCH SMP preempt mod_unload

    1,6.5 使用insmod加载内核模块

    $ sudo insmod ./hello.ko

    1.6.6 使用lsmod查看已加载的模块

    $ lsmod |grep hello
    Module                  Size  Used by
    hello                  16384  0

    1.6.7 使用rmmod卸载模块

    $ sudo rmmod hello
    $ lsmod |grep hello

    1.6.8 使用dmesg查看日志

    $ dmesg |tail
    ...
    [562050.818179] HelloWorld
    [562458.113633] GoodbyeWorld

    1.6.9 练习

    练习1
    请参阅init_module()中 return语句上方的注释 ?将返回值更改为负值,重新编译并再次加载模块。怎么了?
    $ sudo insmod ./hello-2.ko
    insmod: ERROR: could not insert module ./hello-2.ko: Operation not permitted
    $ dmesg |tail
    ...
    [546135.110381] HelloWorld2.

    练习2
    将代码中许可的部分删除,再编译看看。
    $ make
    make -C /lib/modules/5.1.15-arch1-1-ARCH/build M=/home/toma/ko modules
    make[1]: Entering directory '/usr/lib/modules/5.1.15-arch1-1-ARCH/build'
      CC [M]  /home/toma/ko/hello-1.o
      Building modules, stage 2.
      MODPOST 1 modules
    style="color: red; font-size: xx-small;">WARNING: modpost: missing MODULE_LICENSE() in /home/toma/ko/hello-1.o
    see include/linux/module.h for more information
      CC      /home/toma/ko/hello-1.mod.o
      LD [M]  /home/toma/ko/hello-1.ko
    make[1]: Leaving directory '/usr/lib/modules/5.1.15-arch1-1-ARCH/build'

    1.6.10 其他信息

    现在看一下linux/drivers/char/Makefile的真实示例。
    正如你所看到的,有些东西被硬件连接到内核(obj-y)但是那些obj-m去了哪里?
    那些熟悉shell脚本的人很容易发现它们。
    对于那些没有的,你看到的obj - $(CONFIG_FOO)条目会扩展为obj-y或obj-m,具体取决于CONFIG_FOO变量是否已设置为y或m。
    虽然我们在这里,但那些正是你在linux/.config文件中设置的那种变量,最后一次你说make menuconfig 或类似的东西。

    1.7 更多基础示例链接

    1.7.1 将命令行参数传递给模块

    http://www.tldp.org/LDP/lkmpg/2.6/html/x323.html

    1.7.2 跨越多个文件的模块

    http://www.tldp.org/LDP/lkmpg/2.6/html/x351.html

    1.7.3 构建预编译内核的模块

    http://www.tldp.org/LDP/lkmpg/2.6/html/x380.html

    1.7.4 与应用程序交互

    https://www.oschina.net/translate/writing-a-simple-linux-kernel-module

    2 make工具

    2.1 DESCRIPTION

    make实用程序将自动确定需要重新编译大型程序的哪些部分,并发出命令以重新编译它们。
    我们的示例显示了C程序,因为它们非常常见,但您可以使用make与任何编译语言,其编译器可以使用shell命令运行。
    实际上,make并不仅限于程序。
    您可以使用它来描述任何一些任务,其中某些文件必须在其他文件更改时自动从其他文件更新。

    要准备使用make,您必须编写一个名为makefile的文件,该文件描述程序中文件之间的关系,以及用于更新每个文件的命令的状态。
    在程序中,通常从目标文件更新可执行文件,而目标文件又通过编译源文件来完成。

    一旦存在合适的makefile,每次更改一些源文件时,这个简单的shell命令:
    make
    足以执行所有必要的重新编译。
    make程序使用makefile描述和文件的最后修改时间来决定需要更新哪些文件。
    对于每个文件,它会发出makefile中记录的命令。

    make执行makefile中的命令以更新一个或多个目标名称,其中name通常是程序。
    如果不存在-f选项,make将按顺序查找 makefiles GNUmakefile, makefile, and Makefile, in that order.

    (我们建议使用Makefile,因为它突出显示在目录列表的开头附近,紧邻其他重要文件,如README。)
    检查的第一个名称,建议不要将GNUmakefile用于大多数makefile。
    如果您具有特定于GNU make的makefile,则应使用此名称,并且其他版本的make不会理解该名称。
    如果makefile为' - ',则读取标准输入。

    2.2 退出状态

    如果所有的makefile都被成功解析并且没有构建的目标失败,则GNU make退出状态为零。
    如果使用-q标志并且make确定需要重建目标,则将返回状态1。
    如果遇到任何错误,将返回状态2。

    2.3 选项

    -b, -m Ignored for compatibility. 忽略兼容性.
    -B, --always-make Unconditionally make all targets. 无条件地制定所有目标.
    -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before doing anything. 在做任何事之前改为DIRECTORY.
    -d Print lots of debugging information. 打印大量调试信息.
    --debug[=FLAGS] Print various types of debugging information.  FLAGS可以用于所有调试输出(与使用-d相同),
    b用于基本调试,v用于更详细的基本调试,i用于显示隐式规则,
    j用于调用命令的详细信息,m用于在重新生成makefile时进行调试.
     使用n禁用所有先前的调试标志.
    -e, --environment-overrides Environment variables override makefiles. 环境变量覆盖makefile.
    --eval=STRING Evaluate STRING as a makefile statement. 将STRING评估为makefile语句.
    -f FILE, --file=FILE, --makefile=FILE Read FILE as a makefile. 将FILE作为makefile读取.
    -h, --help Print this message and exit. 打印此消息并退出.
    -i, --ignore-errors Ignore errors from recipes.  忽略为重制文件而执行的命令中的所有错误.
    -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included makefiles. 搜索DIRECTORY以获取包含的makefile.
    -j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg. 一次允许N个工作;没有arg的无限工作.
    -k, --keep-going Keep going when some targets can't be made. 当一些目标无法制作时继续前进.
    -l [N], --load-average[=N],--max-load[=N] Don't start multiple jobs unless load is below N. 除非负载低于N,否则不要启动多个作业.
    -L, --check-symlink-times Use the latest mtime between symlinks and target. 使用符号链接和目标之间的最新mtime.
    -n, --just-print, --dry-run, --recon Don't actually run any recipe; just print them. 实际上不要运行任何配方;只需打印它们.
    -o FILE, --old-file=FILE, --assume-old=FILE Consider FILE to be very old and don't remake it. 即使FILE很老,也不要重新生成它.
    -O[TYPE], --output-sync[=TYPE] Synchronize output of parallel jobs by TYPE. 按TYPE同步并行作业的输出.
     当与-j并行运行多个作业时,请确保将每个作业的输出收集在一起,而不是穿插其他作业的输出.
     如果未指定type或是target,则将每个目标的整个配方的输出组合在一起.
     如果type为line,则配方中每个命令行的输出将组合在一起.
     如果type是recurse,则整个递归make的输出被组合在一起.
    如果type为none,则禁用输出同步.
    -p, --print-data-base Print make's internal database.  打印通过读取makefile产生的数据库(规则和变量值);
    然后像往常一样或以其他方式指定执行.
    要打印数据库而不尝试重新创建任何文件,请使用: make -p -f/dev/null.
    -q, --question Run no recipe; exit status says if up to date. ``问题模式''.不要运行任何命令,也不要打印任何东西;如果指定的目标已经是最新的,则返回退出状态为零,否则返回非零值.
    -r, --no-builtin-rules Disable the built-in implicit rules. 禁用内置隐式规则.
    -R, --no-builtin-variables Disable the built-in variable settings. 禁用内置变量设置.
    -s, --silent, --quiet Don't echo recipes.  无声操作;不要在执行时打印命令.
    -S, --no-keep-going, --stop Turns off -k.  取消-k选项的效果.这是永远不必要的,
    除了在递归make中,-k可能通过MAKEFLAGS从顶级make继承,
    或者如果你在环境中的MAKEFLAGS中设置-k.
    -t, --touch Touch targets instead of remaking them. 触摸目标而不是重新制作它们.
     这用于假装命令已完成,以欺骗未来的make调用.
    --trace Print tracing information. 打印跟踪信息.
    -v, --version Print the version number of make and exit. 打印make和exit的版本号.
    -w, --print-directory Print the current directory. 打印当前目录.
    --no-print-directory Turn off -w, even if it was turned on implicitly. 关闭-w,即使它是隐式打开的.
    -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE Consider FILE to be infinitely new. 认为FILE是无限新的.
     没有-n,它几乎与在运行make之前在给定文件上运行touch命令相同,只是修改时间仅在make的想象中改变.
    --warn-undefined-variables Warn when an undefined variable is referenced. 引用未定义的变量时发出警告.

    3. 其他内核编译方法及相关连接

    3.1 编译Arch的内核模块

    https://wiki.archlinux.org/index.php/Compile_kernel_module
    首先,您需要安装构建依赖项,例如compiler(base-devel)和linux-headers。

    3.2 Arch构建系统

    https://wiki.archlinux.org/index.php/Kernel/Arch_Build_System
    https://www.kernel.org/doc/Documentation/kbuild/kconfig.txt
    内核/Arch构建系统
    该拱门构建系统可以用来构建基于官方的自定义内核的Linux软件包。
    这种编译方法可以自动化整个过程,并且基于经过良好测试的软件包。
    您可以编辑PKGBUILD以使用自定义内核配置或添加其他修补程序。
    $ pacman -Ss asp
    extra/asp 5-1    Arch Linux build source file management tool
    安装的ASP封装和基devel的包组。
    您需要一个干净的内核来开始自定义。通过运行以下命令从ABS获取最新的内核包文件到您的构建目录:
    $ asp update linux
    $ asp checkout linux
    然后,从各自的源获取您需要的任何其他文件(例如,自定义配置文件,修补程序等)。

    3.3 内核/传统编译

    https://wiki.archlinux.org/index.php/Kernel/Traditional_compilation#Download_the_kernel_source
    安装核心包
    安装base-devel软件包组,其中包含必要的软件包,例如make和gcc。
    还建议安装以下软件包,如默认的Arch内核PKGBUILD中所列:xmlto,kmod,inetutils,bc,libelf,git

    3.4 修补包

    https://wiki.archlinux.org/index.php/Patching_packages#Applying_patches
    本文介绍如何创建以及如何在Arch Build System(ABS)中将补丁应用于包。
    一个补丁描述了一组针对一个或多个文件线路的变化。补丁通常用于自动更改源代码。

    3.5 模块与程序

    http://www.tldp.org/LDP/lkmpg/2.6/html/x427.html
    程序 模块
    用户空间      内核空间
    printf() printk()
    main()开始 init_module或 通过module_init调用指定的函数 开始
      cleanup_module或 使用module_exit调用指定的函数 结束
  • 相关阅读:
    【洛谷P1005】矩阵取数游戏
    【洛谷P1966】火柴排队
    【题解】洛谷P1731 [NOI1999] 生日蛋糕(搜索+剪枝)
    【题解】洛谷P3627 [APIO2009]抢掠计划(缩点+SPFA)
    【题解】洛谷P1262 间谍网络 (强连通分量缩点)
    【题解】洛谷P3200 [HNOI2009] 有趣的数列(卡特兰数+质因数分解)
    点双连通分量 [HNOI2012]矿场搭建
    分块 公主的朋友
    [置顶] 数学的坑,一点点来填
    大暴搜 [NOIP2009]靶形数独
  • 原文地址:https://www.cnblogs.com/sztom/p/11129737.html
Copyright © 2011-2022 走看看