模块的加载过程三(下)

简介: 模块的加载过程三(下)

模块传参

#define module_param(name, type, perm)        \
  module_param_named(name, name, type, perm)
#define module_param_named(name, value, type, perm)        \
  param_check_##type(name, &(value));          \
  module_param_cb(name, &param_ops_##type, &value, perm);      \
  __MODULE_PARM_TYPE(name, #type)
#define module_param_cb(name, ops, arg, perm)             \
  __module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)
#define __module_param_call(prefix, name, ops, arg, perm, level, flags) \
  /* Default value instead of permissions? */     \
  static const char __param_str_##name[] = prefix #name; \
  static struct kernel_param __moduleparam_const __param_##name \
  __used                \
    __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
  = { __param_str_##name, ops, VERIFY_OCTAL_PERMISSIONS(perm),  \
      level, flags, { arg } }

可见module_param(a,b,c)在“__param”section中定义了一个类型为struct kernel_param的静态常量。struct kernel_param的定义如下:

struct kernel_param {
  const char *name;//name为参数名
  const struct kernel_param_ops *ops;
  //·perm为对sysfs文件系统中模块参数的访问许可,定义在结构体struct
  //kernel_param_ops对象ops中的成员函数(set和get)用来在模块mod的
  //args成员和模块的参数section间拷贝数据
  u16 perm;
  s8 level;
  u8 flags;
  union {
    void *arg;
    const struct kparam_string *str;
    const struct kparam_array *arr;
  };//指向参数的指针
};

__used和unused主要用来避免编译器产生警告信息,因为此处声明的param_a变量在模块源码的其他部分并不会被使用。

param_check_int宏用来检测变量a在module_param宏之前是否定义,因为struct kernel_param中的union联合体只是用来放置模块使用的参数所在地址,如果之前该参数没有定义,就不可能生成&a的值。所以我们的内核模块源代码hello.c在用module_param声明模块参数之前,要首先定义出这些参数。

在上面的宏展开的实例中,可以看到指向参数的指针值被设定为“{&a}”,在模块静态链接期间,&a指令不可能生成其最终的运行期地址,因此模块参数所在的"__param”section需要有一个对应的relocation section“.rel__param”,用来完成对参数指针的重定位,这样才能把命令行中的参数值正确复制到模块的“__param"section中。

下面讨论“insmod demodev.ko dolphin=10 bobcat=5”中携带的参数值如何为模块所用,不看代码也应该可以猜想出命令行中的参数值应该会被复制到模块的参数中,这样模块在开始使用参数dolphin之前,其值己经被insmod命令行中的实际值所改写。图1一7展示了命令行参数传递到模块的“__param”section的全过程:

在实际的内核源代码中,sys_init_module函数的最后一个参数const char __user *uargs清楚地表明这是由用户空间传递过来的放置模块参数的内存地址,在insmod一个模块时所携带的参数将以字符串的形式向内核空间传递。然后在load_module函数中,通过strndup_user的调用将用户空间的模块参数复制到内核空间。

args=sfrndup_user(uargs,~0UL>>1);

strndup_user函数内部会调用kmalloc为在内核空间保存模块参数字符串分配一段内存区域,然后通过copy_from_user将模块参数从用户空间复制到内核空间。

接着,在HDR视图的section被搬移到CORE和INIT section之后,load_module通过下面的section。函数调用取得“__param",section在内存空间的最终地址,并记录在struct module的struct kernel_param *kp成员变量中。

模块依赖

实际运行的系统中并不是只加载一个模块,模块可以随时添加进系统,也可以随时被卸载。这些内核模块之间并不是完全相对独立的,比如当一个模块引用到另一个模块中导出的符号时,这两个模块间就建立了依赖关系。因此依赖关系只存在于模块与模块之间,模块与内核之间不构成依赖关系,因为在模块生存期间我们不可能去卸载内核,当然内核也不可能引用到模块导出的符号。内核必须能跟踪模块间的这种依赖关系,只有这样,如果由于存在依赖关系卸载一个模块有可能影响到系统的稳定性,内核才可能采取必要的措施防止这种情况发生。

模块的依赖关系的建立最早发生在当前模块对象mod被加载时,模块加载函数调用resolve_symbol函数来解决其中一些“未解决的引用”符号。如果成功地在其他模块导出的符号中找到了指定的符号,那么resolve_symbol函数会将导出这一“未解决的引用"符号的模块记录在一个变量struct module *owner中,然后调用ref_module(mod,0艹er)在模块mod和owner之间建立依赖关系。ref_module函数在做一些必要的安全性检查之后调用

add_module_usage(mod,owner)在mod和owner模块间建立依赖关系,add_module_usage函数的定义如下:

static int add_module_usage(struct module *a, struct module *b)
{
  struct module_use *use;
  pr_debug("Allocating new usage for %s.\n", a->name);
  use = kmalloc(sizeof(*use), GFP_ATOMIC);//kmalloc分配一struct module_use型内存空间us
  if (!use) {
    pr_warn("%s: out of memory loading\n", a->name);
    return -ENOMEM;
  }
  /*将use中的source指向mod模块,target指向owner模块,
   *同时将use的target-list加入mod中的target_list指向的双向链表,
   *将use的sourcelist加入owner中的source_list指向的双向链表。
   */
  use->source = a;
  use->target = b;
  list_add(&use->source_list, &b->source_list);
  list_add(&use->target_list, &a->target_list);
  return 0;
}

函数首先调用kmalloc分配一struct module_use型内存空间use,然后将use中的source指向mod模块,target指向owner模块,同时将use的target-list加入mod中的target_list指向的双向链表,将use的sourcelist加入owner中的source_list指向的双向链表。图1·8展示了通过struct module_use对象在三个模块间建立依赖关系的技术细节:

图1·8中,模块mod_A和mod_B均依赖于模块owner,mod_A和mod_B之间则没有依赖关系。owner模块先加入系统,它导出一个函数为模块mod_A和mod_B所使用。图中显示modA先于modB加入系统,在mod_A加入系统时,模块加载器创建了use_A对象在mod_A和owner间建立依赖关系。随后mod_B加入了系统,因为它和owner模块间存在

依赖关系,加载器同样创建了useB对象在modB和owner间建立关联。从图中可以看到,use_A对象中的source_list成员己不再指向owner->source_list,而是指向了useB->source_list,后者则和owner->source_list建立了直接的链接关系

如此,mod_A和mod_B模块可以通过遍历其target_list成员知道所依赖的所有模块,而owner模块则可以通过遍历其source_list成员知道所有依赖于自己的模块。

当从系统中卸载一个模块时,系统必须确保没有其他模块依赖于该模块,根据上面的讨论,只要模块结构中的sourceJist是一空链表,就表明没有其他模块依赖于它。

版本控制

版本控制主要用来解决内核模块和内核之间的接口一致性问题。所谓内核模块和内核之间的接口,简单地说是指由内核导出并被内核模块调用的那些符号。产生这种问题的根源在于内核模块和内核作为独立实体各自分开编译,实现重要函数如下:

/*
 *在resolve_symbol函数的内部会调用find_symbol来查找该符号。如果成功查找到,
 *则函数接下来会调用check_version对这种接口进行校验码的验证
 */
/* Resolve a symbol for this module.  I.e. if we find one, record usage. */
static const struct kernel_symbol *resolve_symbol(struct module *mod,
              const struct load_info *info,
              const char *name,
              char ownername[])
{
  struct module *owner;
  const struct kernel_symbol *sym;
  const unsigned long *crc;
  int err;
  /*
   * The module_mutex should not be a heavily contended lock;
   * if we get the occasional sleep here, we'll go an extra iteration
   * in the wait_event_interruptible(), which is harmless.
   */
  sched_annotate_sleep();
  mutex_lock(&module_mutex);
  sym = find_symbol(name, &owner, &crc,
        !(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)), true);
  //查找该符号
  if (!sym)
    goto unlock;
  if (!check_version(info->sechdrs, info->index.vers, name, mod, crc,
         owner)) {//对这种接口进行校验码的验证
    sym = ERR_PTR(-EINVAL);
    goto getname;
  }
  err = ref_module(mod, owner);
  if (err) {
    sym = ERR_PTR(err);
    goto getname;
  }
getname:
  /* We must make copy under the lock if we failed to get ref. */
  strncpy(ownername, module_name(owner), MODULE_NAME_LEN);
unlock:
  mutex_unlock(&module_mutex);
  return sym;
}

当模块加载时处理“未解决的引用”符号时是如何对接口一致性进行验证的。这种验证是通过在resolve_symbol函数里调用check_version函数完成的,check_version函数的完整定义如下:

static int check_version(Elf_Shdr *sechdrs,
       unsigned int versindex,
       const char *symname,
       struct module *mod,
       const unsigned long *crc,
       const struct module *crc_owner)
{
  unsigned int i, num_versions;
  struct modversion_info *versions;
  /* Exporting module didn't supply crcs?  OK, we're already tainted. */
  if (!crc)
    return 1;
  /* No versions at all?  modprobe --force does this. */
  if (versindex == 0)
    return try_to_force_load(mod, symname) == 0;
  versions = (void *) sechdrs[versindex].sh_addr;
  num_versions = sechdrs[versindex].sh_size
    / sizeof(struct modversion_info);
  /*
   *在定义了CONFIG_MODVERSIONS的前提下,check_version用一个for循环在
   *“__versions”section中进行遍历,对每一struct modversion_info元素和找
   *到的符号名symname进行匹配,如果匹配成功,再进行接口的校验码比较,
   *如果校验码相等,说明模块所使用的接口和内核导出的接口是一致的,否则
   *产生版本不匹配的错误。
   */
  for (i = 0; i < num_versions; i++) {
    if (strcmp(versions[i].name, symname) != 0)
      continue;
    if (versions[i].crc == maybe_relocated(*crc, crc_owner))
      return 1;
    pr_debug("Found checksum %lX vs module %lX\n",
           maybe_relocated(*crc, crc_owner), versions[i].crc);
    goto bad_version;
  }
  pr_warn("%s: no symbol version for %s\n", mod->name, symname);
  return 0;
bad_version:
  pr_warn("%s: disagrees about version of symbol %s\n",
         mod->name, symname);
  return 0;
}

目录
相关文章
|
1月前
|
缓存 监控 前端开发
如何确保动态导入的模块被正确加载?
通过以上这些方法的综合运用,可以有效地确保动态导入的模块被正确加载,提高应用的稳定性、性能和用户体验。在实际开发过程中,要根据项目的具体情况和需求,灵活运用这些方法,并不断进行测试和优化。
34 4
|
2月前
|
监控 开发者
确保动态导入模块按正确顺序加载
【10月更文挑战第12天】 在复杂应用开发中,确保动态导入模块按正确顺序加载至关重要,直接影响应用性能、功能和稳定性。本文深入探讨了动态模块加载顺序的影响因素、解决方案及实践案例,提供了详细的策略和方法,帮助开发者有效管理模块加载顺序,提升应用质量。
|
4月前
|
数据库连接 数据库
实现加载驱动、得到数据库对象、关闭资源的代码复用,将代码提取到相应的工具包里边。优化程序
该博客文章展示了如何通过创建工具类`Connectiontools`实现数据库连接、语句执行以及资源关闭的代码复用,以优化程序并提高数据库操作的效率和安全性。
|
7月前
|
小程序
小程序的分包加载具体流程
小程序的分包加载具体流程
246 0
|
存储 Linux C语言
模块的加载过程一
模块的加载过程一
169 0
|
Linux 索引
模块的加载过程三
模块的加载过程三
98 0
|
Linux
模块的加载过程四
模块的加载过程四
147 0
|
Linux
模块的加载过程二(上)
模块的加载过程二
106 0
|
程序员 Linux
模块的加载过程二(下)
模块的加载过程二(下)
154 0
|
JavaScript 开发者
Vite 在运行过程中是如何发现新增依赖的?
Vite 在运行过程中是如何发现新增依赖的?
236 0

热门文章

最新文章