linux中引入模块机制的好处是什么

69次阅读
没有评论

共计 5007 个字符,预计需要花费 13 分钟才能阅读完成。

本文丸趣 TV 小编为大家详细介绍“linux 中引入模块机制的好处是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“linux 中引入模块机制的好处是什么”文章能帮助大家解决疑惑,下面跟着丸趣 TV 小编的思路慢慢深入,一起来学习新知识吧。

linux 中引入模块机制的好处:1、应用程序在退出时,可以不管资源的释放或者其他的清除工作,但是模块的退出函数却必须仔细此撤销初始化函数所作的一切;2、该机制有助于缩短模块的开发周期,即注册和卸载都很灵活方便。

Linux 中引入模块机制有什么好处?

首先,模块是预先注册自己以便服务于将来的某个请求,然后他的初始化函数就立即结束。换句话说,模块初始化函数的任务就是为以后调用函数预先作准备。

好处:

1) 应用程序在退出时,可以不管资源的释放或者其他的清除工作,但是模块的退出函数却必须仔细此撤销初始化函数所作的一切。

2) 该机制有助于缩短模块的开发周期。即:注册和卸载都很灵活方便。

Linux 模块机制浅析

Linux 允许用户通过插入模块,实现干预内核的目的。一直以来,对 linux 的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析。

模块的 Hello World!

我们通过创建一个简单的模块进行测试。首先是源文件 main.c 和 Makefile。

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);// 卸载模块
   ……
}

通过回调 exit 完成模块的出口函数功能,最后调用 free_module 将模块卸载。

读到这里,这篇“linux 中引入模块机制的好处是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注丸趣 TV 行业资讯频道。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-12发表,共计5007字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)