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 部分,雖然核心可以訪問不同的物件部分。