介紹
本文引用《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 <john.madieu@gmail.com>");
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
部分,雖然核心可以訪問不同的物件部分。