Helloworld 驱动模块加载

语言: CN / TW / HK

介绍

本文引用《linux设备驱动开发》书中部分解释,记录开篇第一章 helloworld 程序

以下内容需要掌握如下基础信息linux模块概念、链接编译、c语言基础

内容

helloworld.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hellowolrd_init(void) {
    pr_info("Hello world!\n");
    return 0;
}

static void __exit hellowolrd_exit(void) {
    pr_info("End of the world\n");
}

module_init(hellowolrd_init);
module_exit(hellowolrd_exit);
MODULE_AUTHOR("John Madieu <[email protected]>");
MODULE_LICENSE("GPL");

运行 make 命令之后,它会生成两个模块

After running make command, there will be two modules:

  • helloworld.ko
  • helloworld-params.ko

第一个模块是基本helloworld驱动程序,第二个也是相同的,但是它接收一些参数,并在内核调试消息中打印这些参数,加载第一个模块后,将在内核中添加两个调试消息,

安装模块

# insmod ./helloworld.ko

卸载模块

# rmmod -f helloworld

内核消息

#dmesg
[...]
[38535.487568] Hello world!
[38542.391099] End of the world

对于第二个模块,可以使用下面方式加载

# insmod  ./helloworld-params.ko

如果未提供任何参数,将使用默认值:

$ dmesg
[...]
[37858.595126] Hello world with parameters!
[37858.595129] The *mystr* parameter: hello
[37858.595130] The *myint* parameter: 1
[37858.595131] The *myarr* parameter: 0, 1, 2
[37887.232643] End of the world

我们传入一些参数之后,将打印出如下消息

# insmod  ./helloworld-params.ko  mystr="packtpub" myint=255 myarr=23,4,7
# dmesg
[...]
[37892.417968] Hello world with parameters!
[37892.417970] The *mystr* parameter: packtpub
[37892.417971] The *myint* parameter: 255
[37892.417972] The *myarr* parameter: 23, 4, 7
[37895.222808] End of the world

模块的入点和出点

​ 内核驱动程序都有入点和出点:前者对应于模块加载时调用的函数(modprobe和insmod,该内容在书中有介绍),后者是模块卸载时执行的函数(在执行rmmod和modprobe -r 时,该内容书中有介绍)。

​ main()函数使用C/C++编写的每个用户空间程序的入点,当这个函数返回时,程序将退出。而对于内核模块,情况就不一样了:入点可以随意命名,它也不像用户空间程序那样在main()返回是退出,其出点在另一个函数中定义,开发人员 要做的就是通知内核把 哪些函数作为入点或出点来执行。实际上,唯一必须要做的是把它们作为参数提供为 module_init()module_exit() 宏,将它们标识为相应的加载和删除函数。

​ 综上所述, module_init() 用于声明模块加载(使用insmod或modprobe)时应该调用的函数。初始化函数中要完成的操作是定义模块的行为。 module_exit() 用于声明模块卸载(使用rmmod)时应该调用的函数。

代码中 __init__exit 属性

__init和__exit实际上是在include/linux/init.h中定义的内核宏,如下所示:

#define __init__section(.init.text)
#define __exir__section(.exit.text)

ELF目标文件

编译文件工作方式,ELF目标文件有不同的命名部分组成,其中一部分是必需的,它们称为ELF标准的基础,但也可以根据自己的需要构建任一部分,并由特殊程序使用,内核就是这样做的。如上所属的内核宏的工作方式,如需要了解它的原理ELF目标文件的可执行和可链接格式说明需要了解

可以通过下列命令打印出指定内核模块module.ko的不同组成部分

~/.../LinuxDeviceDriversDevelopment_Code/Chapter02 >>> objdump -h helloworld.ko                                            

helloworld.ko:     文件格式 elf64-x86-64

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000000  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .init.text    00000015  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  2 .exit.text    0000000c  0000000000000000  0000000000000000  00000055  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  3 .rodata.str1.1 00000024  0000000000000000  0000000000000000  00000061  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 __mcount_loc  00000008  0000000000000000  0000000000000000  00000085  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .rodata       0000008c  0000000000000000  0000000000000000  000000a0  2**5
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  6 .rodata.str1.8 00000058  0000000000000000  0000000000000000  00000130  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .modinfo      000000af  0000000000000000  0000000000000000  00000188  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .orc_unwind   0000001e  0000000000000000  0000000000000000  00000237  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .orc_unwind_ip 00000014  0000000000000000  0000000000000000  00000255  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 10 .note.gnu.property 00000030  0000000000000000  0000000000000000  00000270  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .note.gnu.build-id 00000024  0000000000000000  0000000000000000  000002a0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 12 .note.Linux   00000030  0000000000000000  0000000000000000  000002c4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 13 .data         00000000  0000000000000000  0000000000000000  000002f4  2**0
                  CONTENTS, ALLOC, LOAD, DATA
 14 .printk_index 00000010  0000000000000000  0000000000000000  000002f8  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
 15 .gnu.linkonce.this_module 000003c0  0000000000000000  0000000000000000  00000340  2**6
                  CONTENTS, ALLOC, LOAD, RELOC, DATA, LINK_ONCE_DISCARD
 16 .bss          00000000  0000000000000000  0000000000000000  00000700  2**0
                  ALLOC
 17 .debug_info   000038ec  0000000000000000  0000000000000000  00000700  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 18 .debug_abbrev 00000609  0000000000000000  0000000000000000  00003fec  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 19 .debug_aranges 00000060  0000000000000000  0000000000000000  000045f5  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 20 .debug_ranges 00000030  0000000000000000  0000000000000000  00004655  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 21 .debug_line   000005a7  0000000000000000  0000000000000000  00004685  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 22 .debug_str    00003206  0000000000000000  0000000000000000  00004c2c  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 23 .comment      00000026  0000000000000000  0000000000000000  00007e32  2**0
                  CONTENTS, READONLY
 24 .note.GNU-stack 00000000  0000000000000000  0000000000000000  00007e58  2**0
                  CONTENTS, READONLY
 25 .debug_frame  00000048  0000000000000000  0000000000000000  00007e58  2**3
                  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 26 .BTF          00000050  0000000000000000  0000000000000000  00007ea0  2**0
                  CONTENTS, READONLY

如上只有少部分属于ELF标准

  • .text: 包含程序代码,也称为代码。
  • .data: 包含初始化数据,也称为数据段。
  • .rodata: 用于只读数据。
  • .comment: 注释。
  • 未初始化化的数据段,也称为有符号开始的块(block started by symbol,bss)。

ELF链接器

​ 对于代码程序中内核添加的其他部分,讨论为时过早,我们首先要讨论链接器是如何将目标程序文件排列,并将相关 .init.text 链接到程序中 hellowolrd_init.exit.text 链接到程序中 hellowolrd_exit

​ 要解释这一行为,首先我们要了解一个叫做链接器(Linux系统上的ld)程序,该程序负责将符号(数据、代码等)放置到生成的二进制文件中的适当部分,以便在程序执行时可以被加载器处理。二进制文件中的这些部分可以自定义、更改它们的默认位置,甚至可以通过提供链接器脚本[称为链接器定义文件( LDF )或链接器定义脚本( LDS )]来添加其他部分。要实现这些操作只需通过编译器指令把符号的位置告知链接器即可, GUN C 编译器为此提供了一些属性。Linux内核提供了一个自定义 LDS 文件,它位于 arch/<arch>/kernel/vmlinux.lds,.s 中。对于要放置在内核LDS文件所映射的专用部分中的符号,使用 __init__exit 进行标记。

​ 总之, __init__exit 是Linux指令(实际上是宏),他们使用C编译器属性指定符号的位置。这些指令指示编译器将以它们为前缀的代码分别放在 .init.text.exit.text 部分,虽然内核可以访问不同的对象部分。