第三十四章 linux-模块的加载过程四

news2025/2/5 2:56:56

第三十四章 linux-模块的加载过程四


文章目录

  • 第三十四章 linux-模块的加载过程四
  • 调用模块的初始化函数
  • 释放INT section所占用的空间
  • 呼叫模块通知链
  • 模块的卸载
    • find_module
    • 检查依赖关系
    • free_module


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_LJVE
	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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LCHub:到2023年,全球低代码市场预计达到269亿美元

12月13日,Gartner发布全球低代码市场规模报告。数据显示,到2023年,全球低代码市场规模预计达到269亿美元,同比增长19.6%。 业务技术专家认为,到2026年,超级自动化和业务可组合性将成为加速低代码技术应用的关键驱动力。 Gartner还发布了一项调查数据,到2023年全球超级自…

计算机毕设Python+Vue学习类视频网站(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ STL 之堆栈(后进先出) stack 详解

文章目录Part.I AttentionPart.II FuncitonPart.III CodePart.I Attention stack<T>容器适配器的数据是以 LIFO (Last in First Out, 后进先出) 的方式组织的&#xff0c;可以将它想象成放在餐桌上的一摞盘子。必须要包含头文件#include <stack> Part.II Funcito…

2023年天津农学院专升本专业课考试具体安排及准考证打印时间

天津农学院2023年高职升本科专业课考试相关事宜的通知 一、考试时间及考试地点 1.考试时间&#xff1a;2022年12月31日9:00-11:00 2.考试地点&#xff1a;天津农学院东、西校区&#xff0c;每位考生具体的考试地点、考场号等信息以准考证上标注的为准。 天津农学院东校…

人工智能时代,你应该花4个月时间去学习编程,并找到一份好工作

把现在作为你冒险的开始&#xff0c;你会学到一种由高需求的技能&#xff0c;你会有一段新的经历&#xff0c;你会得到新的机会。试一试吧。 编者按&#xff1a;新的一年&#xff0c;很多人都会想要有一个新的开始。在Andrei Neagoie看来&#xff0c;人们应该选择一个非常有前…

maven—分模块开发与设计

项目与模块下载 任务&#xff1a; 将springmvc_ssm这个ssm整合后的项目拆分成四个模块&#xff0c;如下图&#xff1a; ssm_pojo拆分 新建模块 从原始项目中拷贝实体类&#xff08;User&#xff09;到该模块 ssm_dao拆分 新建模块 从原始项目中拷贝相关内容到该模块 数据…

DEJA_VU3D - Cesium功能集 之 089-台风范围几何绘制

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小130个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(尽可能把代码简洁一些)。博文内容…

学习IB带给了我什么?

在某些层面&#xff0c;IB课程类似于大家更加熟知的AP课程——它是一种让高中学生学习高级、严格课程的体系。然而&#xff0c;有两个主要区别。首先&#xff0c;IB项目在美国高中或者美国大学申请中的受欢迎程度远远低于AP课程&#xff08;个人感觉是这样子滴哈&#xff09;&a…

【复习笔记】嵌入式系统及其原理复习重点

嵌入式系统及其原理复习重点笔记 计算机的发展 第一代——电子管计算机&#xff08;1946—1954年&#xff09; 内 存 延迟线或磁芯外 存 纸带、卡片或磁带工作速度 几千&#xff5e;一万次&#xff0f;秒软 件 机器语言或汇编语言应 用 科学计算代表机型 ENIAC特 点 体积庞大…

【vue】简易封装echarts

将echarts封装成组件&#xff0c;达到只要调用方法&#xff0c;传入数据和相应的参数就能生成图表的效果&#xff0c;避免在项目中编写大量重复和累赘的echarts的配置代码&#xff0c;实现的思路如下&#xff1a; 接口返回的一般是json数据&#xff0c;所以首先要将json数据进…

【LVGL学习笔记】(四)PlatformIO + LVGL8.3配置

LVGL全程LittleVGL&#xff0c;是一个轻量化的&#xff0c;开源的&#xff0c;用于嵌入式GUI设计的图形库。并且配合LVGL模拟器&#xff0c;可以在电脑对界面进行编辑显示&#xff0c;测试通过后再移植进嵌入式设备中&#xff0c;实现高效的项目开发。 LVGL中文教程手册&#…

PicGo+GitHub搭建个人图床用于Markdown、HTML等图片引用

方便程度&#xff1a;★★★★☆ 配置难度&#xff1a;★★☆☆☆ 稳定性&#xff1a;★★★★★ 适用环境&#xff1a;Windows、Mac、Linux 需要工具&#xff1a;GitHub 账号、PicGo 客户端 隐私性&#xff1a;别人可以访问你的图片仓库 GitHub仓库设置 流程&#xff1…

vue后台系统管理项目-商城轮播图管理功能

商城轮播图管理功能 功能介绍&#xff1a; 1.轮播图列表分页功能&#xff1b; 2.轮播图添加功能&#xff1b; 3.轮播图编辑功能&#xff1b; 4.轮播图删除功能&#xff1b; 5.轮播图启用禁用功能&#xff1b; 6.轮播图获取排序号功能&#xff1b; 7.轮播图查看详情功能&#xf…

2.创建自己的Cesiunm地球隐藏控件

目录​​​​​​​ 一、创建地球 引入Cesium.JS和初始页面 修改初始页面 将项目通过tomcat发布 二、去除不需要的控件 一、创建地球 引入Cesium.JS和初始页面 创建一个名为my-cesium的文件夹&#xff0c;将下载好的Cesium中的Build文件夹和Apps中的HelloWorld.html复制到…

基于Android的地铁查询系统app-计算机毕业设计

项目介绍 本软件研究了一个Android平台的地铁查询软件实现方案,从数据库数据保存到地铁数据的提取&#xff0c;再到界面的友好展示,最后到一个成型软件的生成这样一个过程,研究了SQLite数据库在Android平台的应用以及在手机平台的展示等等。 系统提供了地铁线路、站点和换乘的…

3.Entity和CZML添加图形

目录 Entity创建盒子 创建view容器 配置Entity ​编辑CZML数据添加图形 CZML是什么 怎么加载CZML Entity创建盒子 创建view容器 <!DOCTYPE html> <html lang"en"><head><!-- Use correct character set. --><meta charset"ut…

精华推荐 |【深入浅出Sentinel原理及实战】「原理探索专题」完整剖析Alibaba微服务架构体系之轻量级高可用流量控制组件Sentinel(1)

Sentinel是什么&#xff1f;不要概念混淆啊&#xff01; 注意&#xff1a;本Sentinel与Redis服务Sentinel是两回事&#xff0c;压根不是一个概念&#xff0c;请大家不要混肴。 Alibaba的Sentinel Sentinel是由阿里巴巴中间件团队开发的开源项目&#xff0c;是一种面向分布式微…

Vuex的相关知识

「Vuex的相关知识」 ​ vuex是一种对vue 应用中多个组件的共享状态进行集中式的管理(读/写)&#xff1b; vuex的工作原理&#xff1a; https://segmentfault.com/a/1190000021717329 ​ vuex 核心概念和API&#xff1a;state、mutations、actions、getters、modules、向外暴…

green power 设备入网过程

目录 Green Power设备入网流程 单项green power commissioning过程 抓包实例 1. SINK发送Green Power:GP Proxy Commissioning Mode命令​编辑​编辑 2. GPD发送入网命令 3. Proxy和Sink发送Green Power:GP Commissioning Notification 4. …

通过 Request 请求获取真实 IP 地址以及对应省份城市

title: 通过 Request 请求获取真实 IP 地址以及对应省份城市和系统浏览器信息 date: 2022-12-16 16:20:26 tags: GeoIP2UserAgentUtils categories:开发技术及框架 cover: https://cover.png feature: false 1. 获取真实 IP 地址 1.1 代码 代码如下&#xff0c;这里的 Commo…