模块的加载过程四

简介: 模块的加载过程四

sys_init_module第二部分由load_module返回的do_init_module实现

static noinline int do_init_module(struct module *mod)
{
  int ret = 0;
  struct mod_initfree *freeinit;
  freeinit = kmalloc(sizeof(*freeinit), GFP_KERNEL);
  if (!freeinit) {
    ret = -ENOMEM;
    goto fail;//HDR视图所占空间的释放
  /*
   *
   */
  }
  freeinit->module_init = mod->module_init;//调用mod中init函数指针
  /*
   * We want to find out whether @mod uses async during init.  Clear
   * PF_USED_ASYNC.  async_schedule*() will set it.
   */
  current->flags &= ~PF_USED_ASYNC;
  do_mod_ctors(mod);
  /* Start the module */
  if (mod->init != NULL)
    ret = do_one_initcall(mod->init);
  if (ret < 0) {
    goto fail_free_freeinit;//放INIT section区域(mod->module_init)
  }
  if (ret > 0) {
    pr_warn("%s: '%s'->init suspiciously returned %d, it should "
      "follow 0/-E convention\n"
      "%s: loading module anyway...\n",
      __func__, mod->name, ret, __func__);
    dump_stack();
  }
  /* Now it's a first class citizen! */
  mod->state = MODULE_STATE_LIVE;//新模块的状态
  blocking_notifier_call_chain(&module_notify_list,
             MODULE_STATE_LIVE, mod);//内核模块的通知链
  /*
   * We need to finish all async code before the module init sequence
   * is done.  This has potential to deadlock.  For example, a newly
   * detected block device can trigger request_module() of the
   * default iosched from async probing task.  Once userland helper
   * reaches here, async_synchronize_full() will wait on the async
   * task waiting on request_module() and deadlock.
   *
   * This deadlock is avoided by perfomring async_synchronize_full()
   * iff module init queued any async jobs.  This isn't a full
   * solution as it will deadlock the same if module loading from
   * async jobs nests more than once; however, due to the various
   * constraints, this hack seems to be the best option for now.
   * Please refer to the following thread for details.
   *
   * http://thread.gmane.org/gmane.linux.kernel/1420814
   */
  if (current->flags & PF_USED_ASYNC)
    async_synchronize_full();
  mutex_lock(&module_mutex);
  /* Drop initial reference. */
  module_put(mod);
  trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
  mod->num_symtab = mod->core_num_syms;
  mod->symtab = mod->core_symtab;
  mod->strtab = mod->core_strtab;
#endif
  unset_module_init_ro_nx(mod);
  module_arch_freeing_init(mod);
  mod->module_init = NULL;
  mod->init_size = 0;
  mod->init_ro_size = 0;
  mod->init_text_size = 0;
  /*
   * We want to free module_init, but be aware that kallsyms may be
   * walking this with preempt disabled.  In all the failure paths,
   * we call synchronize_rcu/synchronize_sched, but we don't want
   * to slow down the success path, so use actual RCU here.
   */
  call_rcu(&freeinit->rcu, do_free_init);
  mutex_unlock(&module_mutex);
  wake_up_all(&module_wq);
  return 0;
fail_free_freeinit:
  kfree(freeinit);
fail:
  /* Try to protect us from buggy refcounters. */
  mod->state = MODULE_STATE_GOING;//新模块的状态为MODULE_STATE_GOING
  synchronize_sched();
  module_put(mod);
  blocking_notifier_call_chain(&module_notify_list,
             MODULE_STATE_GOING, mod);
  free_module(mod);
  wake_up_all(&module_wq);
  return ret;
}

调用模块的初始化函数

mod初始化部分中己经提到了mod中init函数指针是如何指向模块源码中的初始化函数,由do_init_module调用

从以上代码可以看出,内核模块可以不提供模块初始化函数。如果模块提供了初始化函数,那么它将在do_one_initcall函数内部被调用。如果模块初始化函数被成功调用,那么模块就算是被加载进了系统,因此需要更新模块的状态为MODULE_STATE_GOING:

mod->state = MODULE_STATE_GOING;

释放INT section所占用的空间

模块一旦被成功加载,HDR视图和INITsection所占的内存区域将不再会被用到,因此需要释放它们。在sys_init_module函数中,释放INIT section所在内存区域由函数module_free完成,后者调用vfree来释放INIT section区域(mod->module_init):module_free(mod,mod->module_init);

kfree(freeinit);

HDR视图所占空间的释放实际上发生在load_module函数的最后do_init_module部分:

free_module(mod);

模块成功加载进系统之后,正在运行的内核和系统中所有加载的模块关系如图1-10所示:

内核用一全局变量modules链表记录系统中所有已加载的模块。

呼叫模块通知链

Linux内核提供了一个很有趣的特性,也就是所谓的通知链(notifier call charn),这个特性不只是在模块的加载过程中会使用到,在内核系统的其他组件中也常常会使用到。通过通知链,模块或者其他的内核组件可以对向其感兴趣的一些内核事件进行注册,当该事件发生时,这些模块或者组件当初注册的回调函数将会被调用。内核模块机制中实现的模块通知链module_notify_list就是内核中众多通知链中的一条。通知链背后的实现机制其实很简单,通过链表的形式,内核将那些注册进来的关注同类事件的节点构成一个链表,当某一特定的内核事件发生时,事件所属的内核组件负责遍历该通知链上的所有节点,调用节点上的回调函数。所有的通知链头部都是一个struct_blocking_notifier_head类型的变量,该类型的定义为:

struct blocking_notifier_head {
  struct rw_semaphore rwsem;
  struct notifier_block __rcu *head;
};

这里以内核模块的通知链module_notify_list为例,来讨论一下通知链的实现原理。module_notify_list为一全局变量,用来管理所有对内核模块事件感兴趣的通知节点。

static BLOCKING_INIT_NOTIFIER_HEAD(module_notify_list) ;

上述定义其实是定义并初始化了一个类型为struct blocking_notifier_head的对象module_notify_list。如果一个内核模块想了解当前系统中所有与模块相关的事件,可以调用

register_module_notifier向内核注册一个节点对象,该节点对象中包含有一个回调函数。当register_module_notifier函数成功向系统注册了一个回调节点之后,系统中所有那些模块相关的事件发生时都会调用到这个回调函数。为了具体了解幕后的机制,下面先来看看register_module_notifier函数在内核源码中的实现:

int register_module_notifier(struct notifier_block *nb)
{
  return blocking_notifier_chain_register(&module_notify_list, nb);
}

函数的参数是个struct notifier_block型指针,代表一个通知节点对象。struct notifie_rblock的定义为:

struct notifier_block {
  notifier_fn_t notifier_call;
  struct notifier_block __rcu *next;
  int priority;
};

其中,notifier_call就是所谓的通知节点中的回调函数,next用来构成通知链,priority代表

一个通知节点优先级,用来决定通知节点在通知链中的先后顺序,数值越大代表优先级越高。

blocking_notifier_chain_regster最终通过调用notifier_chain_register函数将一个通知节点加入module_notify_list管理的链表。在向一个通知链中加入新节点时,系统会把各节点的priority作为一个排序关键字进行简单排序,其结果是越高优先级的节点越靠近头节点,当有事件发生时,最先被通知。图11展示了多个模块在同一条通知链上调用了

register_module_notifier,由此形成的该调用链结构示意图:

如果要从一条通知链中注销一个通知节点,那么应该使用register_module_notifier

int register_module_notifier(struct notifier_block *nb)
{
  return blocking_notifier_chain_register(&module_notify_list, nb);
}

以上讨论了如何向/从系统中的一条通知链注册/注销一个通知节点,接下来看看当某个特定的内核事件发生时,通知链上各节点的回调函数如何被触发。以内核模块加载过程为例,sys_init_module函数在调用完load_module之后,会通过blocktng_notifier_call_chain函数来通知调用链module_notify_list上的各节点,例如,如果load_module函数成功返回,表明模块加载的大部分工作己经完成,此时sys_init_module会通过调用blocking_notifier_call_chain函数来通知module_notify_list上的节点,一个模块正在被加入系统(MODULE_STATE_COMING):

blocking_notifier_call_chain(&module_notify_list,
             MODULE_STATE_LIVE, mod);//内核模块的通知链

上面代码段中的blockng_notifier_call_chain函数将使通知链module_notify{list上的各节点的回调函数均被调用,其实现原理可以简单概括为遍历module_notify_list上的各节点,依次调用各节点上的notifier_call函数。读者从源码中也可以发现,对于模块加载的其他阶段(MODULE_STATE_LIVE和MODULE_STATE_GOING),内核模块加载器也都会调用blocking_notifier_call_chain函数来通知module_notify_list上的各个通知节点。

模块的卸载

相对于模块的加载,从系统中卸载一个模块的任务则要轻松得多。将一个模块从系统中卸载,使用rmmod命令,比如“rmmod demodev”。rmmod通过系统调用sys_delete_module来完成卸载工作,该函数原型如下:

asmlinkage long sys_delete_module(const char __user *name_user,
        unsigned int flags);
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';
  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)) {
    /* Other modules depend on us: get rid of them first. */
    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 it has an init func, it must have an exit func to unload */
  if (mod->init && !mod->exit) {
    forced = try_force_unload(flags);
    if (!forced) {
      /* This module can't be removed */
      ret = -EBUSY;
      goto out;
    }
  }
  /* Stop the machine so refcounts can't move and disable module. */
  ret = try_stop_module(mod, flags, &forced);
  if (ret != 0)
    goto out;
  mutex_unlock(&module_mutex);
  /* Final destruction now no one is using it. */
  if (mod->exit != NULL)
    mod->exit();
  blocking_notifier_call_chain(&module_notify_list,
             MODULE_STATE_GOING, mod);
  async_synchronize_full();
  /* Store the name of the last unloaded module for diagnostic purposes */
  strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));
  free_module(mod);
  return 0;
out:
  mutex_unlock(&module_mutex);
  return ret;
}

find_module

sys_delete_module函数首先将来自用户空间的欲卸载模块名用strncpy_from_user函数复制到内核空间:

if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
    return -EFAULT;

然后调用find_module函数在内核维护的模块链表modules中利用name来查找要卸载的模块。find_module函数的定义如下:

struct module *find_module(const char *name)
{
  return find_module_all(name, strlen(name), false);
}
/* Search for module by name: must hold module_mutex. */
/*函数通过list_each_entry在modules链表中遍历每一个模块mod,
通过前面的讨论我们知道,全局变量modules用来管理系统中所响己加载的
模块形成的链表。如果查找到指定的模块,则函数返回该模块的mod结构,
否则返回NULL。*/
static struct module *find_module_all(const char *name, size_t len,
              bool even_unformed)
{
  struct module *mod;
  list_for_each_entry(mod, &modules, list) {
    if (!even_unformed && mod->state == MODULE_STATE_UNFORMED)
      continue;
    if (strlen(mod->name) == len && !memcmp(mod->name, name, len))
      return mod;
  }
  return NULL;
}

函数通过list_each_entry在modules链表中遍历每一个模块mod,通过前面的讨论我们知道,全局变量modules用来管理系统中所响己加载的模块形成的链表。如果查找到指定的模块,则函数返回该模块的mod结构,否则返回NULL。

检查依赖关系

如果sys_delete_module函数成功查找到了要卸载的模块,那么接下来就要检查是否有别的模块依赖于当前要卸载的模块,为了系统的稳定,一个有依赖关系的模块不应该从系统中卸载掉。在前面“模块间的依赖关系”部分中己经讨论了内核如何跟踪系统中各模块之间的依赖关系,这里只需检查要卸载模块的source_list链表是否为空,即可判断这种依赖关系,相关代码段为:

if (!list_empty(&mod->source_list)) {
    /* Other modules depend on us: get rid of them first. */
    ret = -EWOULDBLOCK;
    goto out;
  }

free_module

如果一切正常,sys_delete_module函数最后会调用free_module函数来做模块卸载末期的一些清理工作,包括更新模块的状态为MODULE_STATE_GOING,将卸载的模块从modules链表中移除,将模块占用的COREsection空间释放,释放模块从用户空间接收的参数所占的空间等,函数的实现相对比较直白,这里就不再仔细讨论了。

static void free_module(struct module *mod)
{
  trace_module_free(mod);
  mod_sysfs_teardown(mod);
  /* We leave it in list to prevent duplicate loads, but make sure
   * that noone uses it while it's being deconstructed. */
  mutex_lock(&module_mutex);
  mod->state = MODULE_STATE_UNFORMED;
  mutex_unlock(&module_mutex);
  /* Remove dynamic debug info */
  ddebug_remove_module(mod->name);
  /* Arch-specific cleanup. */
  module_arch_cleanup(mod);
  /* Module unload stuff */
  module_unload_free(mod);
  /* Free any allocated parameters. */
  destroy_params(mod->kp, mod->num_kp);
  /* Now we can delete it from the lists */
  mutex_lock(&module_mutex);
  /* Unlink carefully: kallsyms could be walking list. */
  list_del_rcu(&mod->list);
  /* Remove this module from bug list, this uses list_del_rcu */
  module_bug_cleanup(mod);
  /* Wait for RCU synchronizing before releasing mod->list and buglist. */
  synchronize_rcu();
  mutex_unlock(&module_mutex);
  /* This may be NULL, but that's OK */
  unset_module_init_ro_nx(mod);
  module_arch_freeing_init(mod);
  module_memfree(mod->module_init);
  kfree(mod->args);
  percpu_modfree(mod);
  /* Free lock-classes; relies on the preceding sync_rcu(). */
  lockdep_free_key_range(mod->module_core, mod->core_size);
  /* Finally, free the core (containing the module structure) */
  unset_module_core_ro_nx(mod);
  module_memfree(mod->module_core);
#ifdef CONFIG_MPU
  update_protections(current->mm);
#endif
}


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

热门文章

最新文章