内核模块详细加载/卸载过程

目录

.ko文件格式

内核模块加载过程

内核模块卸载过程


.ko文件格式

        "K0"文件通常是指Linux内核模块文件,这些文件包含了可以在运行中的Linux内核中加载和卸载的代码。
内核模块允许你在不重新编译整个内核的情况下,向Linux内核添加新功能、驱动程序或其他功能。
K0文件的文件格式实际上就是编译后的二进制文件,其结构和格式取决于所加载的内核模块的编译和源代码。
        一般情况下, Ko文件是一种ELF (Executable and Linkable Format,可执行与可链接格式)文件,这也是Linux 上常见的可执行文件格式之一。

        文件开始处是一个ELF头部(ELF Header ),用来描述整个文件的组织,这些信息独立于处理器,也独立于文件中的其余内容。我们可以使用 readelf 工具查看 elf文件的头部详细信息。
 

下面是KO模块ELF头部信息的一些重要字段和描述:e_ident: ELF文件的标识信息,它包含了文件的一些基本属性,如文件类型、字节序、文件版本等。
e_type:描述ELF文件类型的字段。
对于内核模块来说,这个字段的值通常是 ET_REL,表示可重定位文件。
e_machine:指定了目标机器的体系结构,如x86、 ARM等。
e_version: ELF 版本号。
e_entry:对于可执行文件来说,这是程序的入口点地址。
对于可重定位文件(如内核模块) ,这个字段一般是0。
e_phoff:指定了程序头表(Program Header Table)的偏移量。
内核模块一般没有程序头表。
e-shoff:指定了节头表(Section Header Table)的偏移量。
节头表包含了关于文件中各个节的描述信息。
e_flags:一些标志位,用于指示文件的特性。
e_ehsize: ELF 头部的大小,以字节为单位。
e_phentsize和e_phnum:描述程序头表条目的大小和数量。
内核模块通常没有程序头表。
e_shentsize和e_shnum:描述节头表条目的大小和数量。
e_shstrndx: 指定了包含节名称的字符串表在节头表中的索引。
对于内核模块(KO 文件)来说,节头表中的各个节包含了模块的重要信息,如代码段、数据段、符号表、字符串表等。
KO模块的结构可能因模块的功能和实现方式而有所不同,因此具体的节和字段可能会有所变化。
 

内核模块加载过程 

        在前面我们了解了 ko 内核模块文件的一些格式内容之后, 我们可以知道内核模块其实也是一段经过特殊加工的代码,那么既然是加工过的代码,内核就可以利用到加工时留在内核模块里的信息,对内核模块进行利用。所以我们就可以接着了解内核模块的加载过程了。
        首先 insmod 会通过文件系统将.ko 模块 读到用户空间的一块内存中,然后执行系统调用 sys_init module()解析模组,这时,内核在 vmalloc 区分配与 ko文件大小相同的内存来暂存 ko 文件,暂存好之后解析 ko 文件,将文件中的各个section分配到init段和core段,在modules 区为 init 段和 core段分配内存,并把对应的 sectioncopy 到 modules 区最终的运行地址,经过 relocate 函数地址等操作后,就可以执行ko的 init 操作了,这样一个ko 的加载流程就结束了。同时,init 段会被释放掉,仅留下 core 段来运

​
SYSCALL DEFINE3(init module,void __user *, umod, unsigned long, len, const char,uargs)
{
    int err;
    struct load info info ={};
    err = may init module();if(err)
    return err;
    pr debug("init module: umod=%p,len=%lu, uargs=%p\n",umod,len,uargs);
    err =copy_module_fromuser(umod,len,&info);//<-if(err)
    return err;
    return load module(&info,uargs,0);//
}

​

        第14行:通过vmalloc在vmalloc区分配内存空间,将内核模块copy到此空间,info->hdr 直接指向此空间首地址,也就是ko的elf header 。​

        第18行:然后通过load_module()进行模块加载的核心处理,在这里完成了模块的搬移,重定向等艰苦的过程。​

        下面是load_module()的详细过程,代码已经被我简化,主要包含setup_load_info()和layout_and_allocate()。​

/* 分配并加载模块 */​
static int load_module(struct load_info *info, const char __user *uargs,​
            int flags)​
{​
   struct module *mod;​
   long err = 0;​
   char *after_dashes;​
   ...​
   err = setup_load_info(info, flags);//<---------​
   ...​
   mod = layout_and_allocate(info, flags);//<--------------​
   ...​
}

​        第9行:setup_load_info()加载struct load_info 和 struct module, rewrite_section_headers,将每个section的sh_addr修改为当前镜像所在的内存地址, section 名称字符串表地址的获取方式是从ELF头中的e_shstrndx获取到节区头部字符串表的标号,找到对应section在ELF文件中的偏移,再加上ELF文件起始地址就得到了字符串表在内存中的地址。​

        第11行:在layout_and_allocate()中,layout_sections() 负责将section 归类为core和init这两大类,为ko的第二次搬移做准备。move_module()把ko搬移到最终的运行地址。内核模块加载代码搬运过程到此就结束了。​

        但此时内核模块要工作起来还得进行符号导出。

内核模块卸载过程

        卸载过程相对加载比较简单,我们输入指令rmmod,最终在系统内核中需要调用 sys_delete_module 进行实现。​

        具体过程如下:先从用户空间传入需要卸载的模块名称,根据名称找到要卸载的模块指针, 确保我们要卸载的模块没有被其他模块依赖,然后找到模块本身的exit函数实现卸载。 代码如下。    

SYSCALL_DEFINE2(delete_module, const char __user *, name_user,​
      unsigned int, flags)​
{​
   struct module *mod;​
   char name[MODULE_NAME_LEN];​
   int ret, forced = 0;​
​
   if (!capable(CAP_SYS_MODULE) || modules_disabled)​
      return -EPERM;​
​
   if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)​
      return -EFAULT;​
   name[MODULE_NAME_LEN-1] = '\0';​
​
   audit_log_kern_module(name);​
​
   if (mutex_lock_interruptible(&module_mutex) != 0)​
      return -EINTR;​
​
   mod = find_module(name);​
   if (!mod) {​
      ret = -ENOENT;​
      goto out;​
   }​
​
   if (!list_empty(&mod->source_list)) {​
      ret = -EWOULDBLOCK;​
      goto out;​
   }​
​
   /* Doing init or already dying? */​
   if (mod->state != MODULE_STATE_LIVE) {​
      /* FIXME: if (force), slam module count damn the torpedoes */​
      pr_debug("%s already dying\n", mod->name);​
      ret = -EBUSY;​
      goto out;​
   }​
​
   if (mod->init && !mod->exit) {​
      forced = try_force_unload(flags);​
      if (!forced) {​
         /* This module can't be removed */​
         ret = -EBUSY;​
         goto out;​
   return ret;​
}

        第8行:确保有插入和删除模块不受限制的权利,并且模块没有被禁止插入或删除​

        第11行:获得模块名字​

        第20行:找到要卸载的模块指针​

        第26行:有依赖的模块,需要先卸载它们​

        第39行:检查模块的退出函数​

        第48行:停止机器,使参考计数不能移动并禁用模块​

        第56行:告诉通知链module_notify_list上的监听者,模块状态变                                                                为MODULE_STATE_GOING​。

        第60行:等待所有异步函数调用完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值