Die Vorteile der Einführung des Modulmechanismus in Linux: 1. Wenn die Anwendung beendet wird, kann sie die Freigabe von Ressourcen oder andere Bereinigungsarbeiten ignorieren, aber die Exit-Funktion des Moduls muss sorgfältig alles rückgängig machen, was von der Initialisierungsfunktion durchgeführt wurde. 2. Dieser Mechanismus hat dazu beigetragen, den Modulentwicklungszyklus zu verkürzen, d. h. die Registrierung und Deinstallation sind flexibel und bequem.
Die Betriebsumgebung dieses Tutorials: Linux7.3-System, Dell G3-Computer.
Zuerst registriert sich das Modul vorab, um eine zukünftige Anfrage zu bedienen, und dann endet seine Initialisierungsfunktion sofort. Mit anderen Worten besteht die Aufgabe der Modulinitialisierungsfunktion darin, sich im Voraus auf zukünftige Funktionsaufrufe vorzubereiten.
Vorteile:
1) Wenn die Anwendung beendet wird, kann sie die Freigabe von Ressourcen oder andere Aufräumarbeiten ignorieren, aber die Exit-Funktion des Moduls muss sorgfältig alles rückgängig machen, was von der Initialisierungsfunktion durchgeführt wurde.
2) Dieser Mechanismus trägt dazu bei, den Modulentwicklungszyklus zu verkürzen. Das heißt: Registrierung und Deinstallation sind sehr flexibel und komfortabel.
Linux ermöglicht es Benutzern, durch das Einfügen von Modulen in den Kernel einzugreifen. Der Modulmechanismus von Linux war lange Zeit nicht klar genug, daher wird in diesem Artikel kurz der Lademechanismus des Kernelmoduls analysiert.
Hallo Weltmodul!
Wir testen, indem wir ein einfaches Modul erstellen. Die erste ist die Quelldatei main.c und Makefile.
florian@florian-pc:~/module$ cat main.c
florian@florian-pc:~/module$ cat main.c
#include<linux/module.h> #include<linux/init.h> static int __init init(void) { printk("Hi module!\n"); return 0; } static void __exit exit(void) { printk("Bye module!\n"); } module_init(init); module_exit(exit);
其中init为模块入口函数,在模块加载时被调用执行,exit为模块出口函数,在模块卸载被调用执行。
florian@florian-pc:~/module$ cat Makefile
obj-m += main.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie object all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #clean clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
其中,obj-m指定了目标文件的名称,文件名需要和源文件名相同(扩展名除外),以便于make自动推导。
然后使用make命令编译模块,得到模块文件main.ko。
florian@florian-pc:~/module$ make
make -C /usr/src/linux-headers-2.6.35-22-generic M=/home/florian/module modules make[1]: 正在进入目录 `/usr/src/linux-headers-2.6.35-22-generic' Building modules, stage 2. MODPOST 1 modules make[1]:正在离开目录 `/usr/src/linux-headers-2.6.35-22-generic'
使用insmod和rmmod命令对模块进行加载和卸载操作,并使用dmesg打印内核日志。
florian@florian-pc:~/module$ sudo insmod main.ko;dmesg | tail -1 [31077.810049] Hi module!
florian@florian-pc:~/module$ sudo rmmod main.ko;dmesg | tail -1 [31078.960442] Bye module!
通过内核日志信息,可以看出模块的入口函数和出口函数都被正确调用执行。
模块文件
使用readelf命令查看一下模块文件main.ko的信息。
florian@florian-pc:~/module$ readelf -h main.ko
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 1120 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 19 Section header string table index: 16
我们发现main.ko的文件类型为可重定位目标文件,这和一般的目标文件格式没有任何区别。我们知道,目标文件是不能直接执行的,它需要经过链接器的地址空间分配、符号解析和重定位的过程,转化为可执行文件才能执行。
那么,内核将main.ko加载后,是否对其进行了链接呢?
模块数据结构
首先,我们了解一下模块的内核数据结构。
linux3.5.2/kernel/module.h:220
struct module { …… /* Startup function. */ int (*init)(void); …… /* Destruction function. */ void (*exit)(void); …… };
模块数据结构的init和exit函数指针记录了我们定义的模块入口函数和出口函数。
模块加载
模块加载由内核的系统调用init_module完成。
linux3.5.2/kernel/module.c:3009
/* This is where the real work happens */ SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs) { struct module *mod; int ret = 0; …… /* Do all the hard work */ mod = load_module(umod, len, uargs);//模块加载 …… /* Start the module */ if (mod->init != NULL) ret = do_one_initcall(mod->init);//模块init函数调用 …… return 0; }
系统调用init_module由SYSCALL_DEFINE3(init_module...)实现,其中有两个关键的函数调用。load_module用于模块加载,do_one_initcall用于回调模块的init函数。
函数load_module的实现为。
linux3.5.2/kernel/module.c:2864
/* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */ static struct module *load_module(void __user *umod, unsigned long len, const char __user *uargs) { struct load_info info = { NULL, }; struct module *mod; long err; …… /* Copy in the blobs from userspace, check they are vaguely sane. */ err = copy_and_check(&info, umod, len, uargs);//拷贝到内核 if (err) return ERR_PTR(err); /* Figure out module layout, and allocate all the memory. */ mod = layout_and_allocate(&info);//地址空间分配 if (IS_ERR(mod)) { err = PTR_ERR(mod); goto free_copy; } …… /* Fix up syms, so that st_value is a pointer to location. */ err = simplify_symbols(mod, &info);//符号解析 if (err < 0) goto free_modinfo; err = apply_relocations(mod, &info);//重定位 if (err < 0) goto free_modinfo; …… }
函数load_module内有四个关键的函数调用。copy_and_check将模块从用户空间拷贝到内核空间,layout_and_allocate为模块进行地址空间分配,simplify_symbols为模块进行符号解析,apply_relocations为模块进行重定位。
由此可见,模块加载时,内核为模块文件main.ko进行了链接的过程!
至于函数do_one_initcall的实现就比较简单了。
linux3.5.2/kernel/init.c:673
int __init_or_module do_one_initcall(initcall_t fn) { int count = preempt_count(); int ret; if (initcall_debug) ret = do_one_initcall_debug(fn); else ret = fn();//调用init module …… return ret; }
即调用了模块的入口函数init。
模块卸载
模块卸载由内核的系统调用delete_module完成。
linux3.5.2/kernel/module.c:768
SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags) { struct module *mod; char name[MODULE_NAME_LEN]; int ret, forced = 0; …… /* Final destruction now no one is using it. */ if (mod->exit != NULL) mod->exit();//调用exit module …… free_module(mod);//卸载模块 …… }
florian@florian-pc:~/module$ cat Makefile
🎜🎜🎜rrreee🎜Unter diesen gibt obj-m den Namen der Zieldatei an, und der Dateiname muss der sein identisch mit dem Namen der Quelldatei (Erweiterungsname), sodass make ihn automatisch ableiten kann. 🎜🎜🎜Verwenden Sie dann den Befehl make, um das Modul zu kompilieren und die Moduldatei main.ko abzurufen. 🎜🎜🎜florian@florian-pc:~/module$ make
🎜🎜🎜rrreee🎜🎜Verwenden Sie die Befehle insmod und rmmod, um das Modul zu laden und zu entladen, und verwenden Sie dmesg, um das Kernel-Protokoll zu drucken. 🎜🎜
🎜🎜rrreee🎜🎜rrreee🎜🎜Anhand der Kernel-Protokollinformationen ist ersichtlich, dass die Ein- und Ausstiegsfunktionen des Moduls korrekt aufgerufen und ausgeführt werden. 🎜🎜🎜🎜Moduldatei🎜🎜🎜🎜Verwenden Sie den Befehl readelf, um die Informationen der Moduldatei main.ko zu überprüfen. 🎜🎜🎜florian@florian-pc:~/module$ readelf -h main.ko
🎜🎜🎜rrreee🎜Wir haben festgestellt, dass der Dateityp von main.ko eine verschiebbare Zieldatei ist, was anders ist Vom allgemeinen gibt es keinen Unterschied im Zieldateiformat. Wir wissen, dass die Zieldatei nicht direkt ausgeführt werden kann. Sie muss den Prozess der Adressraumzuweisung, Symbolauflösung und Verlagerung des Linkers durchlaufen und in eine ausführbare Datei konvertiert werden, bevor sie ausgeführt werden kann. 🎜🎜🎜Verknüpft der Kernel also main.ko, nachdem er es geladen hat? 🎜🎜🎜🎜Moduldatenstruktur🎜🎜🎜🎜Lassen Sie uns zunächst die Kernel-Datenstruktur des Moduls verstehen. 🎜🎜🎜linux3.5.2/kernel/module.h:220
🎜🎜🎜rrreee🎜🎜Die Init- und Exit-Funktionszeiger der Moduldatenstruktur zeichnen die von uns definierten Moduleinstiegs- und -exitfunktionen auf. 🎜🎜🎜🎜Laden des Moduls🎜🎜🎜🎜Das Laden des Moduls wird durch den Kernel-Systemaufruf init_module abgeschlossen. 🎜🎜🎜linux3.5.2/kernel/module.c:3009
🎜🎜🎜rrreee🎜🎜Der Systemaufruf init_module wird durch SYSCALL_DEFINE3 (init_module...) implementiert, das über zwei wichtige Funktionsaufrufe verfügt. Load_module wird zum Laden des Moduls verwendet und do_one_initcall wird zum Zurückrufen der Init-Funktion des Moduls verwendet. 🎜🎜Die Implementierung der Funktion load_module ist. 🎜🎜🎜linux3.5.2/kernel/module.c:2864
🎜🎜🎜rrreee🎜🎜Es gibt vier wichtige Funktionsaufrufe in der Funktion „load_module“. copy_and_check kopiert das Modul vom Benutzerraum in den Kernelraum, layout_and_allocate weist Adressraum für das Modul zu, simplifizieren_symbols führt die Symbolauflösung für das Modul durch und apply_relocations führt eine Verschiebung für das Modul durch. 🎜🎜Es ist ersichtlich, dass der Kernel beim Laden des Moduls einen Verknüpfungsprozess für die Moduldatei main.ko durchführt! 🎜🎜Die Implementierung der Funktion do_one_initcall ist relativ einfach. 🎜🎜🎜linux3.5.2/kernel/init.c:673
🎜🎜🎜rrreee🎜🎜Das heißt, die Einstiegsfunktion init des Moduls wird aufgerufen. 🎜🎜🎜🎜Moduldeinstallation🎜🎜🎜🎜Die Moduldeinstallation wird durch den Kernel-Systemaufruf delete_module abgeschlossen. 🎜🎜🎜linux3.5.2/kernel/module.c:768
🎜🎜🎜rrreee🎜Vervollständigen Sie die Exportfunktion des Moduls über den Rückruf-Exit und rufen Sie schließlich free_module auf, um das Modul zu deinstallieren.
Fazit
Es scheint, dass das Kernelmodul kein Geheimnis ist. Herkömmliche Benutzerprogramme müssen vor der Ausführung in ausführbare Programme kompiliert werden, während Modulprogramme nur in Objektdateien kompiliert und dann in den Kernel geladen werden müssen. Der Kernel implementiert die Verknüpfung zum Modul und konvertiert sie in ausführbaren Code. Gleichzeitig werden während des Ladens und Entladens des Kernels die benutzerdefinierten Moduleinstiegsfunktionen und Modulausstiegsfunktionen über Funktionen zurückgerufen, um entsprechende Funktionen zu implementieren.
Verwandte Empfehlungen: „Linux-Video-Tutorial“
Das obige ist der detaillierte Inhalt vonWelche Vorteile bietet die Einführung eines Modulmechanismus unter Linux?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!