Linux에 모듈 메커니즘을 도입하면 다음과 같은 이점이 있습니다. 1. 애플리케이션이 종료되면 리소스 해제 또는 기타 정리 작업을 무시할 수 있지만 모듈의 종료 기능은 초기화 기능에 의해 수행된 모든 작업을 신중하게 취소해야 합니다. 이 메커니즘은 모듈 개발 주기를 단축하는 데 도움이 됩니다. 즉, 등록 및 제거가 유연하고 편리합니다.
이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.
먼저 모듈은 향후 요청을 처리하기 위해 자체적으로 사전 등록된 다음 초기화 기능이 즉시 종료됩니다. 즉, 모듈 초기화 함수의 임무는 향후 함수 호출을 미리 준비하는 것입니다.
이점:
1) 애플리케이션이 종료되면 리소스 해제 또는 기타 정리 작업을 무시할 수 있지만 모듈의 종료 기능은 초기화 기능에 의해 수행된 모든 작업을 신중하게 취소해야 합니다.
2) 이 메커니즘은 모듈 개발 주기를 단축하는 데 도움이 됩니다. 즉, 등록 및 제거가 매우 유연하고 편리합니다.
Linux를 사용하면 사용자가 모듈을 삽입하여 커널에 개입할 수 있습니다. Linux의 모듈 메커니즘은 오랫동안 충분히 명확하지 않았으므로 이 기사에서는 커널 모듈의 로딩 메커니즘을 간략하게 분석합니다.
Hello World 모듈!
간단한 모듈을 만들어 테스트해보겠습니다. 첫 번째는 소스 파일 main.c와 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🎜이 중 obj-m은 대상 파일의 이름을 지정하며, 파일 이름은 다음과 같아야 합니다. make가 자동으로 추론할 수 있도록 소스 파일 이름(확장자 이름)과 동일합니다. 🎜🎜🎜그런 다음 make 명령을 사용하여 모듈을 컴파일하고 모듈 파일 main.ko를 가져옵니다. 🎜🎜🎜florian@florian-pc:~/module$ make
🎜🎜🎜rrreee🎜🎜 insmod 및 rmmod 명령을 사용하여 모듈을 로드 및 언로드하고 dmesg를 사용하여 커널 로그를 인쇄합니다. 🎜🎜
🎜🎜rrreee🎜🎜rrreee🎜🎜커널 로그 정보를 통해 모듈의 진입 및 퇴장 기능이 올바르게 호출 및 실행되는 것을 확인할 수 있습니다. 🎜🎜🎜🎜모듈 파일🎜🎜🎜🎜readelf 명령을 사용하여 모듈 파일 main.ko의 정보를 확인하세요. 🎜🎜🎜florian@florian-pc:~/module$ readelf -h main.ko
🎜🎜🎜rrreee🎜main.ko 파일 형식이 재배치 가능한 대상 파일인 것을 확인했습니다. 일반에서 대상 파일 형식에는 차이가 없습니다. 대상 파일은 직접 실행될 수 없다는 것을 알고 있습니다. 주소 공간 할당, 기호 확인, 링커 재배치 과정을 거쳐 실행 파일로 변환되어야 실행됩니다. 🎜🎜🎜그럼 커널이 main.ko를 로드한 후 이를 링크하나요? 🎜🎜🎜🎜모듈 데이터 구조🎜🎜🎜🎜먼저 모듈의 커널 데이터 구조를 이해해 봅시다. 🎜🎜🎜linux3.5.2/kernel/module.h:220
🎜🎜🎜rrreee🎜🎜모듈 데이터 구조의 초기화 및 종료 함수 포인터는 우리가 정의한 모듈 시작 및 종료 기능을 기록합니다. 🎜🎜🎜🎜모듈 로딩🎜🎜🎜🎜모듈 로딩은 커널 시스템 호출 init_module에 의해 완료됩니다. 🎜🎜🎜linux3.5.2/kernel/module.c:3009
🎜🎜🎜rrreee🎜🎜 시스템 호출 init_module은 두 개의 주요 함수 호출이 있는 SYSCALL_DEFINE3(init_module...)에 의해 구현됩니다. load_module은 모듈 로딩에 사용되고 do_one_initcall은 모듈의 init 함수를 콜백하는 데 사용됩니다. 🎜🎜load_module 함수의 구현은 다음과 같습니다. 🎜🎜🎜linux3.5.2/kernel/module.c:2864
🎜🎜🎜rrreee🎜🎜load_module 함수에는 4개의 주요 함수 호출이 있습니다. copy_and_check는 사용자 공간에서 커널 공간으로 모듈을 복사하고,layout_and_allocate는 모듈에 주소 공간을 할당하고, 단순화_심볼은 모듈에 대한 기호 확인을 수행하고, Apply_relocations는 모듈에 대한 재배치를 수행합니다. 🎜🎜모듈이 로드되면 커널은 모듈 파일인 main.ko에 대한 연결 과정을 수행하는 것을 볼 수 있습니다! 🎜🎜 do_one_initcall 함수의 구현은 비교적 간단합니다. 🎜🎜🎜linux3.5.2/kernel/init.c:673
🎜🎜🎜rrreee🎜🎜즉, 모듈의 입력 함수 init가 호출됩니다. 🎜🎜🎜🎜모듈 제거🎜🎜🎜🎜모듈 제거는 커널 시스템 호출 delete_module에 의해 완료됩니다. 🎜🎜🎜linux3.5.2/kernel/module.c:768
🎜🎜🎜rrreee🎜콜백 종료를 통해 모듈의 내보내기 기능 기능을 완료하고 마지막으로 free_module을 호출하여 모듈을 제거합니다.
결론
커널 모듈이 신비롭지 않은 것 같습니다. 기존 사용자 프로그램은 실행되기 전에 실행 가능한 프로그램으로 컴파일되어야 하는 반면, 모듈 프로그램은 커널에 로드되기 전에 객체 파일로 컴파일하면 됩니다. 커널은 모듈에 대한 링크를 구현하고 이를 실행 가능한 코드로 변환합니다. . 동시에 커널 로드 및 언로드 과정에서 사용자 정의 모듈 진입 함수와 모듈 종료 함수는 함수를 통해 다시 호출되어 해당 기능을 구현합니다.
관련 추천: "Linux 비디오 튜토리얼"
위 내용은 Linux에 모듈 메커니즘을 도입하면 어떤 이점이 있나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!