Les avantages de l'introduction du mécanisme de module sous Linux : 1. Lorsque l'application se termine, elle peut ignorer la libération de ressources ou d'autres travaux de nettoyage, mais la fonction de sortie du module doit soigneusement annuler tout ce qui est fait par la fonction d'initialisation ; Ce mécanisme permet de raccourcir le cycle de développement du module, c'est-à-dire que l'enregistrement et la désinstallation sont flexibles et pratiques.
L'environnement d'exploitation de ce tutoriel : système linux7.3, ordinateur Dell G3.
Tout d'abord, le module se pré-enregistre pour servir une future requête, puis sa fonction d'initialisation se termine immédiatement. En d'autres termes, la tâche de la fonction d'initialisation du module est de préparer à l'avance les futurs appels de fonction.
Avantages :
1) Lorsque l'application se termine, elle peut ignorer la libération de ressources ou tout autre travail de nettoyage, mais la fonction de sortie du module doit soigneusement annuler tout ce qui est fait par la fonction d'initialisation.
2) Ce mécanisme permet de raccourcir le cycle de développement des modules. Autrement dit : l’enregistrement et la désinstallation sont très flexibles et pratiques.
Linux permet aux utilisateurs d'intervenir dans le noyau en insérant des modules. Le mécanisme des modules de Linux n'a pas été suffisamment clair depuis longtemps, c'est pourquoi cet article analyse brièvement le mécanisme de chargement du module du noyau.
Module Bonjour tout le monde !
Nous testons en créant un module simple. Le premier est le fichier source main.c et 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🎜Parmi eux, obj-m précise le nom du fichier cible, et le nom du fichier doit être le identique au nom du fichier source (nom d'extension) afin que make puisse le déduire automatiquement. 🎜🎜🎜Utilisez ensuite la commande make pour compiler le module et obtenir le fichier du module main.ko. 🎜🎜🎜florian@florian-pc:~/module$ make
🎜🎜🎜rrreee🎜🎜Utilisez les commandes insmod et rmmod pour charger et décharger le module, et utilisez dmesg pour imprimer le journal du noyau. 🎜🎜
🎜🎜rrreee🎜🎜rrreee🎜🎜Grâce aux informations du journal du noyau, on peut voir que les fonctions d'entrée et de sortie du module sont correctement appelées et exécutées. 🎜🎜🎜🎜Fichier module🎜🎜🎜🎜Utilisez la commande readelf pour vérifier les informations du fichier module main.ko. 🎜🎜🎜florian@florian-pc:~/module$ readelf -h main.ko
🎜🎜🎜rrreee🎜Nous avons constaté que le type de fichier main.ko est un fichier cible déplaçable, ce qui est différent du général Il n'y a aucune différence dans le format de fichier cible. Nous savons que le fichier cible ne peut pas être exécuté directement. Il doit passer par le processus d'allocation d'espace d'adressage, de résolution de symboles et de déplacement de l'éditeur de liens et être converti en fichier exécutable avant de pouvoir être exécuté. 🎜🎜🎜Donc, une fois que le noyau a chargé main.ko, est-ce qu'il le lie ? 🎜🎜🎜🎜Structure des données du module🎜🎜🎜🎜Tout d'abord, comprenons la structure des données du noyau du module. 🎜🎜🎜linux3.5.2/kernel/module.h:220
🎜🎜🎜rrreee🎜🎜Les pointeurs de fonction d'initialisation et de sortie de la structure de données du module enregistrent les fonctions d'entrée et de sortie du module que nous avons définies. 🎜🎜🎜🎜Chargement du module🎜🎜🎜🎜Le chargement du module est complété par l'appel système du noyau init_module. 🎜🎜🎜linux3.5.2/kernel/module.c:3009
🎜🎜🎜rrreee🎜🎜L'appel système init_module est implémenté par SYSCALL_DEFINE3 (init_module...), qui a deux appels de fonction clés. load_module est utilisé pour le chargement du module et do_one_initcall est utilisé pour rappeler la fonction init du module. 🎜🎜L'implémentation de la fonction load_module est. 🎜🎜🎜linux3.5.2/kernel/module.c:2864
🎜🎜🎜rrreee🎜🎜Il y a quatre appels de fonction clés dans la fonction load_module. copy_and_check copie le module de l'espace utilisateur vers l'espace noyau, layout_and_allocate alloue un espace d'adressage pour le module, simplifie_symbols effectue la résolution des symboles pour le module et apply_relocations effectue la relocalisation du module. 🎜🎜On peut voir que lorsque le module est chargé, le noyau effectue un processus de liaison pour le fichier du module main.ko ! 🎜🎜Quant à l'implémentation de la fonction do_one_initcall, elle est relativement simple. 🎜🎜🎜linux3.5.2/kernel/init.c:673
🎜🎜🎜rrreee🎜🎜C'est-à-dire que la fonction d'entrée du module init est appelée. 🎜🎜🎜🎜Désinstallation du module🎜🎜🎜🎜La désinstallation du module est complétée par l'appel système du noyau delete_module. 🎜🎜🎜linux3.5.2/kernel/module.c:768
🎜🎜🎜rrreee🎜Remplissez la fonction d'exportation du module via la sortie de rappel, et enfin appelez free_module pour désinstaller le module.
Conclusion
Il semble que le module noyau ne soit pas mystérieux. Les programmes utilisateur traditionnels doivent être compilés en programmes exécutables avant de pouvoir être exécutés, tandis que les programmes de module doivent uniquement être compilés en fichiers objets puis chargés dans le noyau. Le noyau implémente le lien vers le module et le convertit en code exécutable. Dans le même temps, pendant le processus de chargement et de déchargement du noyau, la fonction d'entrée de module définie par l'utilisateur et la fonction de sortie de module seront rappelées via des fonctions pour implémenter les fonctions correspondantes.
Recommandations associées : "Tutoriel vidéo Linux"
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!