>  기사  >  운영 및 유지보수  >  Linux에 모듈 메커니즘을 도입하면 어떤 이점이 있나요?

Linux에 모듈 메커니즘을 도입하면 어떤 이점이 있나요?

青灯夜游
青灯夜游원래의
2023-04-06 15:28:341321검색

Linux에 모듈 메커니즘을 도입하면 다음과 같은 이점이 있습니다. 1. 애플리케이션이 종료되면 리소스 해제 또는 기타 정리 작업을 무시할 수 있지만 모듈의 종료 기능은 초기화 기능에 의해 수행된 모든 작업을 신중하게 취소해야 합니다. 이 메커니즘은 모듈 개발 주기를 단축하는 데 도움이 됩니다. 즉, 등록 및 제거가 유연하고 편리합니다.

Linux에 모듈 메커니즘을 도입하면 어떤 이점이 있나요?

이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.

Linux에 모듈 메커니즘을 도입하면 어떤 이점이 있나요?

먼저 모듈은 향후 요청을 처리하기 위해 자체적으로 사전 등록된 다음 초기화 기능이 즉시 종료됩니다. 즉, 모듈 초기화 함수의 임무는 향후 함수 호출을 미리 준비하는 것입니다.

이점:

  • 1) 애플리케이션이 종료되면 리소스 해제 또는 기타 정리 작업을 무시할 수 있지만 모듈의 종료 기능은 초기화 기능에 의해 수행된 모든 작업을 신중하게 취소해야 합니다.

  • 2) 이 메커니즘은 모듈 개발 주기를 단축하는 데 도움이 됩니다. 즉, 등록 및 제거가 매우 유연하고 편리합니다.

Linux 모듈 메커니즘에 대한 간략한 분석

Linux를 사용하면 사용자가 모듈을 삽입하여 커널에 개입할 수 있습니다. Linux의 모듈 메커니즘은 오랫동안 충분히 명확하지 않았으므로 이 기사에서는 커널 모듈의 로딩 메커니즘을 간략하게 분석합니다.

Hello World 모듈!

간단한 모듈을 만들어 테스트해보겠습니다. 첫 번째는 소스 파일 main.c와 Makefile입니다.

florian@florian-pc:~/module$ cat main.cflorian@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&#39;
  Building modules, stage 2.
  MODPOST 1 modules
make[1]:正在离开目录 `/usr/src/linux-headers-2.6.35-22-generic&#39;

使用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&#39;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);//卸载模块
    ……
}
여기서 init는 모듈 진입 함수로, 모듈이 로드될 때 호출되어 실행되고 종료됩니다. 모듈 종료 함수는 모듈 언로드가 수행될 때 호출됩니다.
🎜🎜🎜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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.