Helloworld 驅動模組載入

介紹

本文引用《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部分,雖然內核可以訪問不同的對象部分。