Linux内核 / 基础组件 / 模块机制快速入门(2)

哈喽,我是 Jack 吴,继续记录我的学习心得。

一、关于读书

  • 读书不是集邮,数量不是关键,读完也不是目的。坚持每天读书,但是不需要坚持读完每一本书。

  • 任何阅读都是功利的,即便是消遣也是一种目的。记住读书的目的,然后用不同的方法和速度,对待不同的书以及书中的不同段落。

  • 有目的地读书和漫游式读书,都是必要的。前者是先有菜谱后去采购相应食材,后者是直接去菜市场大量收集新鲜素材。二者结合使用,会做到更高效的输入和输出。

  • 不要限制自己的读书领域,在学有余力的情况下,多进程跨学科跨领域的阅读,会让思维变得活跃起来。

  • 读书是需要技巧的,效率也有高低之分,不要忌讳方法论。小时候每个人都是天生的学习家,是后天的环境让我们丢失了学习的能力。


二、模块机制快速入门 (2)

目的:

  • 探索一下模块加载机制。

目录:

  1. 将用户空间的模块 ELF 数据拷贝到内核空间

  2. 内核模块是如何组织在一起的?

环境:

  • 基于 Linux-4.14 + Arm-v7。

1. 将用户空间的模块 ELF 数据拷贝到内核空间

insmod 命令会利用文件系统的接口将其数据读取到用户空间的一段内存中,然后通过系统调用 init_module() 让内核去处理模块加载的核心过程。

点击查看大图

1) 系统调用 init_module():

$ man 2 init_module
SYNOPSIS
  int init_module(void *module_image, unsigned long len,
                  const char *param_values);

DESCRIPTION
       init_module() loads an ELF image into kernel space, performs any  necessary  symbol  relocations, initializes module  parameters  to  values  provided by the caller, and then runs the module's init function.  This system call requires privilege.
      [...]
  • module_image 是用户空间下内核模块文件映像的内存地址;

  • len 是模块文件的数据大小;

  • param_values 是用户空间下传给模块的参数在的内存地址;

2) 内核空间的 init_module():

// kernel/module.c

SYSCALL_DEFINE3(init_module, void __user *, umod,
  unsigned long, len, const char __user *, uargs)

    struct load_info info = { };

    copy_module_from_user(umod, len, &info);

    return load_module(&info, uargs, 0);
  • copy_module_from_user() 负责将用户空间的模块 ELF 数据拷贝到内核空间。

  • load_module() 负责模块的核心加载工作

3) struct load_info info:

每一个内核模块在被加载时,都会创建一个 struct load_info info,用于保存模块加载过程中需要用到的信息。它会贯穿整个模块加载的过程,其中比较重要的成员如下:

// kernel/module.c

struct load_info {
 const char *name;
 Elf_Ehdr *hdr;      // ELF header 的首地址,其实也是 ELF 的首地址
 unsigned long len;
 Elf_Shdr *sechdrs;  // ELF p header table 的地址
 char *secstrings, *strtab;  // 重要,暂时不用理会
 [...]
 struct {
  unsigned int sym, str, mod, vers, info, pcpu;
 } index;
};

4) copy_module_from_user() 用于将用户空间的 elf 数据拷贝到内核空间:

// kernel/module.c

int copy_module_from_user(...) {

  // 分配空间, 长度为整个 ELF 文件的长度
  info->hdr = __vmalloc(info->len)

  // 拷贝 ELF 数据到内核空间
  copy_chunked_from_user(info->hdr, umod, info->len); 
}

到此,模块 ELF 数据已经复制到内核空间中,接下来的核心工作是什么?

内核模块既不是可执行文件,也不是库文件。但其基本结构是基于可执行文件和库文件所采用的同样方案。file 命令的输出表明模块文件是可重定位 (relocatable) 的:

$ file hello.ko 
hello.ko: ELF 32-bit LSB relocatable ...

可重定位文件的函数不会引用绝对地址,而只是指向代码中的相对地址,因此可以在内存的任意偏移地址加载。应用程序的 ELF 文件会由动态链接器 ld.so 进行重定位,而内核模块则由内核自身进行重定位。

重定位和解决模块中未定义的引用是模块加载的核心,这部分工作将会在 load_module() 中完成。慢慢来,在这之前,有必要先了解一下模块在内核里的抽象,以及内核是如何对其进行组织管理的。

2. 内核模块是如何组织在一起的?

2.1 对内核模块的抽象:struct module

struct module 是内核用来管理系统中已加载的模块的数据结构,一个 struct module 对象代表着一个内核模块在Linux 系统中的抽象。

struct module 里跟本文比较相关的成员:

struct module {
 enum module_state state;

 /* Member of list of modules */
 struct list_head list;

 /* Unique handle for this module */
 char name[MODULE_NAME_LEN];

 /* Sysfs stuff. */
 [...]

 /* Exported symbols */
 const struct kernel_symbol *syms;
 const s32 *crcs;
 unsigned int num_syms;

 /* Kernel parameters. */
 [...]

 /* GPL-only exported symbols. */
 [...]

 /* Startup function. */
 int (*init)(void);

 /* Core layout: rbtree is accessed frequently, so keep together. */
 struct module_layout core_layout __module_layout_align;
 struct module_layout init_layout;

 [...]
};

2.2 struct module 是如何组织的?

下面将通过阅读少量的关键代码快速地了解 struct module 组织方式:

点击查看大图

3) load_module() 的里关于模块组织的核心流程:

点击查看大图
  • 每一个 struct module 都是在编译模块时由内核为模块自动创建的,定义在 [module_name].mod.c:

// hello.mod.c 

struct module __this_module __attribute__((p(".gnu.linkonce.this_module"))) = {
  .name = KBUILD_MODNAME,
  .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
  .exit = cleanup_module,
#endif
  .arch = MODULE_ARCH_INIT,
};
  • struct module 被放置在模块 elf 文件的 ".gnu.linkonce.this_module" p 中。

  • 内核用链表管理着所有内核模块,节点为 struct module,链表头为 kernel/module.c/LIST_HEAD(modules);

4) 如何确定 ".gnu.linkonce.this_module" p (即struct module) 的地址?

  • 1> 执行 insmod 命令时,模块的 elf 文件的数据会从用户空间拷贝到内核空间,然后加载器会第一次时间将所有的 p header->sh_addr (p 的地址) 修正为正确的内核地址。

  • 2> 使用 find_sec(const struct load_info *info, const char *name) 找到 p 在 ELF p header 表中的下标。find_sec() 通过查找参数 name 在 .shstrtab p (p header 字符串表) 中的位置来确定下标:

// kernel/module.c

setup_load_info()
  info->index.mod = find_sec()
    for (i = 1; i < info->hdr->e_shnum; i++)
      if (strcmp(info->secstrings + shdr->sh_name, name) == 0)
        return i;
  • 3> 从 ".gnu.linkonce.this_module" 的 ELF p header 中取出 p 在内核空间中的地址:

// kernel/module.c

setup_load_info()
  struct module mod = (void *)info->sechdrs[info->index.mod].sh_addr;
  • 4> 在 find_sec() 里添加打印信息以验证:

$ readelf -t hello.ko | grep this_module
[27] .gnu.linkonce.this_module

$ insmod hello.ko 
find_sec i=27 .gnu.linkonce.this_module

到此,关于模块组织相关的分析就结束了,鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容将放在后面的文章里。建议大家可以先自行阅读相关书籍,不是自己理解到的东西是消化不了的。

3. 相关参考

  • Linux 设备驱动开发详解,第 4 章节

  • 深入 Linux 设备驱动程序内核机制,第 1 章节

  • 深入理解 Linux 内核,第 20 章节、附录2

  • 深入 Linux 内核架构,第 7 章节、附录E

4. 更多值得关注的知识点

  • 模块加载/重定位和符号引用

  • 模块的参数传递机制

  • 模块之间的依赖关系

  • 模块中的版本控制机制

  • ...


三、思考技术,也思考人生

要学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,关注公众号:嵌入式Hacker

觉得文章对你有价值,不妨点个 在看和赞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值