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

news2024/11/15 17:32:40

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


文章目录

  • 第三十一章 linux-模块的加载过程一
  • sys_init_module
  • struct module
  • load_module
    • 模块ELF静态的内存视图
    • 字符串表(string Table)
    • HDR视图的第一次改写
    • find_sec函数


ps:kernel symbol内核符号表,就是在内核的内部函数或变量中,可供外部引用的函数和变量的符号表。. 其实说白了就是一个索引文件,它存在的目的就是让外部软件可以知道kernel文件内部实际分配的位置。

在用户空间,用insmod这样的命令来向内核空间安装一个内核模块,本节将详细讨论模块加载时的内核行为。当调用“insmod demodev.ko”来安装demodev.ko这样的内核模块时,insmod会首先利用文件系统的接口将其数据读取到用户空间的一段内存中,然后通过系统调用sys_init_module让内核去处理模块加载的整个过程。

sys_init_module

原型:

asmlinkage long sys_init_module(void __user *umod, unsigned long len,
				const char __user *uargs);

其中,第一参数umod是指向用户空间demodev.ko文件映像数据的内存地址,第二参数len是该文件的数据大小,第三参数是传给模块的参数在用户空间下的内存地址。

sys_init_module实现
SYSCALL_DEFINE3宏定义

#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...)					\
	asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))	\
		__attribute__((alias(__stringify(SyS##name))));		\
	static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));	\
	asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__));	\
	asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
	{								\
		long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));	\
		__MAP(x,__SC_TEST,__VA_ARGS__);				\
		__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));	\
		return ret;						\
	}								\
	static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, 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_from_user(umod, len, &info);
	if (err)
		return err;

	return load_module(&info, uargs, 0);
}

在sys_init_module函数中,加载模块的任务主要是通过调load_module函数来完成的,该函数的定义为:

/* Allocate and load the module: note that size of section 0 is always
   zero, and we rely on this for optional sections. */
//二进制数据使用load_module传输到内核地址空间中,锁有需要的重定位都会完成,所有iny都会解决
//在load_module中创建module实例已近添加到全局的modues链表后,内核内需要调用模块的初始化函数并释放
//初始化数据占用的内存空间

static int load_module(struct load_info *info, const char __user *uargs,
		       int flags)
{
	struct module *mod;
	long err;
	char *after_dashes;

	err = module_sig_check(info);//module_sig_check () 需要开启 
	//CONFIG_MODULE_SIG 才会起作用
	if (err)
		goto free_copy;

	err = elf_header_check(info);//主要是检查一下 ELF 文件头信息,别忘了模块文
	//件.ko 是 ELF relocatable 文件,所以需要检查一下是否是正确的 ELF 文件格
	//式。
	if (err)
		goto free_copy;

	/* Figure out module layout, and allocate all the memory. */
	mod = layout_and_allocate(info, flags);//获取模块的布局,重新申请一次内存
	//,并对相关section对内容进行修改
	if (IS_ERR(mod)) {
		err = PTR_ERR(mod);
		goto free_copy;
	}

	/* Reserve our place in the list. */
	err = add_unformed_module(mod);//将模块加入到内核的模块链表中。
	if (err)
		goto free_module;

#ifdef CONFIG_MODULE_SIG
	mod->sig_ok = info->sig_ok;
	if (!mod->sig_ok) {
		pr_notice_once("%s: module verification failed: signature "
			       "and/or required key missing - tainting "
			       "kernel\n", mod->name);
		add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
	}
#endif

	/* To avoid stressing percpu allocator, do this once we're unique. */
	err = percpu_modalloc(mod, info);//对模块里面的percpu进行特殊的内存申请,
	//这个percpu变量比较特殊,在每个CPU上都存在一份,所以用特殊的申请内存函数
	//去处理
	if (err)
		goto unlink_mod;

	/* Now module is in final location, initialize linked lists, etc. */
	err = module_unload_init(mod);//为后续执行rmmod卸载命令时初始化一些引用变
	//量以及链表等。
	if (err)
		goto unlink_mod;

	/* Now we've got everything in the final locations, we can
	 * find optional sections. */
	err = find_module_sections(mod, info);//找模块里面的一些section,
	//比如参数的__param节,内核符号表__ksymtab节,GPL范围协议的符号
	//__ksymtab_gpl节,ftrace增加的_ftrace_events节等待。
	if (err)
		goto free_unload;

	err = check_module_license_and_versions(mod);
	//主要是检查模块的license是否存在污染内核的可能
	if (err)
		goto free_unload;

	/* Set up MODINFO_ATTR fields */
	setup_modinfo(mod, info);//对sys属性进行一些预先setup处理。

	/* Fix up syms, so that st_value is a pointer to location. */
	err = simplify_symbols(mod, info);//修复加载模块的符号,使符号指向内核正确的运行地址。
	if (err < 0)
		goto free_modinfo;

	err = apply_relocations(mod, info);//是对.rel节和.rela节进行重定位的一个过程。
	if (err < 0)
		goto free_modinfo;

	err = post_relocation(mod, info);//对重定位后的percpu变量重新赋值,并将即
	//将加载完成的模块的符号加入到内核模块的符号链表中,如果成功加载此模块且
	//内核配置了CONFIG_KALLSYMS,那么在/proc/kallsyms下可以看到此模块的符号


	if (err < 0)
		goto free_modinfo;

	flush_module_icache(mod);//执行刷新模块的init_layout和core_layout的cache。

	/* Now copy in args */
	mod->args = strndup_user(uargs, ~0UL >> 1);//复制一下模块的参数
	if (IS_ERR(mod->args)) {
		err = PTR_ERR(mod->args);
		goto free_arch_cleanup;
	}

	dynamic_debug_setup(info->debug, info->num_debug);//需要开启内核CONFIG_DYNAMIC_DEBUG才会启用

	/* Ftrace init must be called in the MODULE_STATE_UNFORMED state */
	ftrace_module_init(mod);//需要开启相关的ftrace配置

	/* Finally it's fully formed, ready to start executing. */
	err = complete_formation(mod, info);//对模块的内存属性进行修改,比如.text的读+执行,.data的读写属性。
	if (err)
		goto ddebug_cleanup;

	/* Module is ready to execute: parsing args may do that. */
	after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
				  -32768, 32767, unknown_module_param_cb);//解析模块参数
	if (IS_ERR(after_dashes)) {
		err = PTR_ERR(after_dashes);
		goto bug_cleanup;
	} else if (after_dashes) {
		pr_warn("%s: parameters '%s' after `--' ignored\n",
		       mod->name, after_dashes);
	}

	/* Link in to syfs. */
	err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);//对sys进行创建的过程
	if (err < 0)
		goto bug_cleanup;

	/* Get rid of temporary copy. */
	free_copy(info);//释放最初内核申请的用于保存模块原文件信息的内存

	/* Done! */
	trace_module_load(mod);//trace相关的

	return do_init_module(mod);//最后模块执行init函数的过程

 bug_cleanup:
	/* module_bug_cleanup needs module_mutex protection */
	mutex_lock(&module_mutex);
	module_bug_cleanup(mod);
	mutex_unlock(&module_mutex);

	blocking_notifier_call_chain(&module_notify_list,
				     MODULE_STATE_GOING, mod);

	/* we can't deallocate the module until we clear memory protection */
	unset_module_init_ro_nx(mod);
	unset_module_core_ro_nx(mod);

 ddebug_cleanup:
	dynamic_debug_remove(info->debug);
	synchronize_sched();
	kfree(mod->args);
 free_arch_cleanup:
	module_arch_cleanup(mod);
 free_modinfo:
	free_modinfo(mod);
 free_unload:
	module_unload_free(mod);
 unlink_mod:
	mutex_lock(&module_mutex);
	/* Unlink carefully: kallsyms could be walking list. */
	list_del_rcu(&mod->list);
	wake_up_all(&module_wq);
	/* Wait for RCU synchronizing before releasing mod->list. */
	synchronize_rcu();
	mutex_unlock(&module_mutex);
 free_module:
	/* Free lock-classes; relies on the preceding sync_rcu() */
	lockdep_free_key_range(mod->module_core, mod->core_size);

	module_deallocate(mod, info);
 free_copy:
	free_copy(info);
	return err;
}

所有参数同sys_init_module函数中的完全一样,实际上在sys_init_module函数的一开始便会调用该函数,调用时传入的实参完全来自于sys_init_module函数,没有经过任何的处理或者修改。

为了更清楚地解释模块加载时的内核行为,我们把sys_init_module分为两个部分:第一部分是调用load_module,完成模块加载最核心的任务:第二部分是在模块被成功加载到系统之后的后续处理。我们将在讨论完load_module部分之后再继续讨论sys_init_module的第二部分。不过,在继续load_module话题之前,先要看一个内核中非常重要的数据结构—------struct module。

struct module

load module函数的返回值是一个struct module类型的指针,struct module是内核用来管理系统中加载的模块时使用的一个非常重要的数据结构,一个struct module对象代表着现实中一个内核模块在Linux系统中的抽象,该结构的定义如下〈删除了一些trace和unused symbol相关的部分):

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. */
	//导出符号
	struct module_kobject mkobj;
	struct module_attribute *modinfo_attrs;
	const char *version;
	const char *srcversion;
	struct kobject *holders_dir;

	/* Exported symbols */
	const struct kernel_symbol *syms;
	const unsigned long *crcs;//内核模块导出符号的校验码所在起始地址。
	unsigned int num_syms;

	/* Kernel parameters. */
	struct kernel_param *kp;//内核模块参数所在的起始地址。
	unsigned int num_kp;

	/* GPL-only exported symbols. */
	//只适用于GPL的导出符号
	unsigned int num_gpl_syms;
	const struct kernel_symbol *gpl_syms;
	const unsigned long *gpl_crcs;

#ifdef CONFIG_UNUSED_SYMBOLS
	/* unused exported symbols. */
	const struct kernel_symbol *unused_syms;
	const unsigned long *unused_crcs;
	unsigned int num_unused_syms;

	/* GPL-only, unused exported symbols. */
	unsigned int num_unused_gpl_syms;
	const struct kernel_symbol *unused_gpl_syms;
	const unsigned long *unused_gpl_crcs;
#endif

#ifdef CONFIG_MODULE_SIG
	/* Signature was verified. */
	bool sig_ok;
#endif

	/* symbols that will be GPL-only in the near future. */
	const struct kernel_symbol *gpl_future_syms;
	const unsigned long *gpl_future_crcs;
	unsigned int num_gpl_future_syms;

	/* Exception table */
	//异常表
	unsigned int num_exentries;
	struct exception_table_entry *extable;

	/* Startup function. */
	//初始化函数
	int (*init)(void);//init是一个指针,指向一个在模块初始化时调用的函数
	//指向内核模块初始化函数的指针,在内核模块源码中由module init宏指定。
	/* If this is non-NULL, vfree after init() returns */
	void *module_init;

	/* Here is the actual code + data, vfree'd on unload. */
	void *module_core;

	/* Here are the sizes of the init and core sections */
	unsigned int init_size, core_size;

	/* The size of the executable code in each section.  */
	unsigned int init_text_size, core_text_size;

	/* Size of RO sections of the module (text+rodata) */
	unsigned int init_ro_size, core_ro_size;

	/* Arch-specific module values */
	struct mod_arch_specific arch;

	unsigned int taints;	/* same bits as kernel:tainted */

#ifdef CONFIG_GENERIC_BUG
	/* Support for BUG */
	unsigned num_bugs;
	struct list_head bug_list;
	struct bug_entry *bug_table;
#endif

#ifdef CONFIG_KALLSYMS
	/*
	 * We keep the symbol and string tables for kallsyms.
	 * The core_* fields below are temporary, loader-only (they
	 * could really be discarded after module init).
	 */
	Elf_Sym *symtab, *core_symtab;
	unsigned int num_symtab, core_num_syms;
	char *strtab, *core_strtab;

	/* Section attributes */
	struct module_sect_attrs *sect_attrs;

	/* Notes attributes */
	struct module_notes_attrs *notes_attrs;
#endif

	/* The command line arguments (may be mangled).  People like
	   keeping pointers to this stuff */
	char *args;

#ifdef CONFIG_SMP
	/* Per-cpu data. */
	void __percpu *percpu;
	unsigned int percpu_size;
#endif

#ifdef CONFIG_TRACEPOINTS
	unsigned int num_tracepoints;
	struct tracepoint * const *tracepoints_ptrs;
#endif
#ifdef HAVE_JUMP_LABEL
	struct jump_entry *jump_entries;
	unsigned int num_jump_entries;
#endif
#ifdef CONFIG_TRACING
	unsigned int num_trace_bprintk_fmt;
	const char **trace_bprintk_fmt_start;
#endif
#ifdef CONFIG_EVENT_TRACING
	struct ftrace_event_call **trace_events;
	unsigned int num_trace_events;
	struct trace_enum_map **trace_enums;
	unsigned int num_trace_enums;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
	unsigned int num_ftrace_callsites;
	unsigned long *ftrace_callsites;
#endif

#ifdef CONFIG_LIVEPATCH
	bool klp_alive;
#endif

#ifdef CONFIG_MODULE_UNLOAD
//用来在内核模块间建立依赖关系。
	/* What modules depend on me? */
	struct list_head source_list;
	/* What modules do I depend on? */
	struct list_head target_list;

	/* Destruction function. */
	void (*exit)(void);

	atomic_t refcnt;
#endif

#ifdef CONFIG_CONSTRUCTORS
	/* Constructor functions. */
	ctor_fn_t *ctors;
	unsigned int num_ctors;
#endif
};

load_module

作为内核模块加载器中最核心的函数,load_module负责最艰苦的模块加载全过程。我们将仔细讨论该函数,因为除了可以了解内核模块加载的幕后机制之外,还能了解到一些非常有趣的特性,诸如内核模块如何调用内核代码导出的函数,被加载的模块如何向系统中其他的模块导出自己的符号,以及模块如何接收外部的参数等。在介绍这部分内容时,如果完全按照内核代码的顺序依序进行的话,逻辑上可能会显得比较凌乱。所以此处文字组织的基本思路是:将load_module函数按照各主要功能分成若干部分,各部分在下文中的出现顺序尽可能维持在代码中的出现顺序:如果某些功能之间存在着某种依赖关系,比如有A和B两个功能,A功能的叙述需要用到B功能中提供的机制,则先介绍B功能:独立于功能模块之外的一些基础设施,比如某些功能性函数,则尽量往前放。

模块ELF静态的内存视图

用户空间程序insmod首先通过文件系统接口读取内核模块demodev.ko的文件数据,将其放在一块用户空间的存储区域中(图中void *umod所示)。然后通过系统调用sys_init_module进入到内核态,同时将umod指针作为参数传递过去(同时传入的还有umod所指向的空间大小len和存放有模块参数的地址空间指针uargs)。

sys_init_module调用load_module,后者将在内核空间利用vmalloc分配一块大小同样为len的地址空间,如图1·2中Elf_Ehdr *hdr所示。然后通过copy_from_user函数的调用将用户空间的文件数据复制到内核空间中,从而在内核空间构造出demodev.ko的一个ELF静态的内存视图。接下来的操作都将以此视图为基础,为使叙述简单起见,我们称该视图为HDR视图(图1·2下方点画线椭圆部分)。HDR视图所占用的内存空间在load_module结束时通过vfree予以释放。
在这里插入图片描述

字符串表(string Table)

字符串表是ELF文件中的一个section,用来保存ELF文件中各个section的名称或符号名,这些名称以字符串的形式存在。图1·3给出了一个具体的字符串表实例:
在这里插入图片描述
由图1·3可见,字符串表中各个字符串的构成和c语言中的字符串完全一样,都以’\0’作为一个字符串的结束标记。由index指向的字符串是从字符串表第index个字符开始,直到遇到一个’\0’标记,如果index处恰好是,那么index指向的就是个空串(null stnng)。

在驱动模块所在的ELF文件中,一般有两个这样的字符串表section,一个用来保存各section名称的字符串,另一个用来保存符号表中每个符号名称的字符串。虽然同样都是字符串表section,但是得到这两个section的基地址的方法并不一样。

section名称字符串表的基地址为char *secstrings=(char *)hdr+entry[hdr->e_shstrndx].sh_offset。而获得符号名称字符串表的基地址则有点绕:首先要遍历Section
header table中所有的entry,去找一个entry[i].sh_type=SHT_SYMTAB的entry,SHT_SYMTAB表明这个entry所对应的section是一符号表。这种情况下,entry[i].sh_link是符号名称字符串表section在Section header table中的索引值,换句话说,符号名称字符串表所在section的基地址为char *strtab=(char *)hdr+entry[entry[i].shlink].sh_offset。

如此,若想获得某一section的名称(假设该section在section header table中的索引值是i),那么用secstrings+entry[i].sh_name即可。

至此,load_module函数通过以上计算获得了section名称字符串表的基地址secstrings和符号名称字符串表的基地址stb,留作将来使用。

HDR视图的第一次改写

在获得了section名称字符串表的基地址secstrings和符号名称字符串表的基地址strtab之后,函数开始第一次遍历section header table中的所有entry,将每个entry中的sh_addr改写为entry[i].sh_addr=(size_t)hdr+entry[i].offset,这样entry[i].sh_addr将指向该entry所对应的section在HDR视图中的实际存储地址。

在遍历过程中,如果发现CONFIG_MODULE_UNLOAD宏没有定义,表明系统不支持动态卸载一个模块,这样,对于名称为".exit”的section,将来就没有必要把它加载到内存中,内核代码于是清除对应entry中sh_flags里面的SHF_ALLOC标志位。

相对于刚复制到内核空间的HDR视图,HDR视图的第一次改写只是在自身基础上修改了Section header table中的某些字段,其他方面没有任何变化。接下来在“HDR视图的第2次改写”一节中将会看到改写后的HDR视图会再次被改写,在那里,HDR视图中的绝大部分会被搬移到一个新的内存空间中,那也是它们最终的内存位置。

find_sec函数

内核用find_sec来寻找section再Section header table中的索引值,load_module->find_module_sections->find_sec

static unsigned int find_sec(const struct load_info *info, const char *name)
{
	unsigned int i;

	for (i = 1; i < info->hdr->e_shnum; i++) {
		Elf_Shdr *shdr = &info->sechdrs[i];
		/* Alloc bit cleared means "ignore it." */
		if ((shdr->sh_flags & SHF_ALLOC)
		    && strcmp(info->secstrings + shdr->sh_name, name) == 0)
			return i;
	}
	return 0;
}

函数返回该的索引值,如果没有找到对应的section,则返回0。该函数的前两个参数分别是ELF文件的ELF header和section header。因为函数要查找的是某一section的name,所以第三个参数就是前面提到的secstrings,第四个参数则是安查找的section的name。函数的具体实现过程非常简单:遍历section header table中所有的entry(忽略没有SHF_ALLOC标志的section,因为这样的section最终不占有实际内存地址),对每一个entry,先找到其所对应的section name,然后和第四个参数进行比较,如果相等,就找到对应的section,返回该section在Section header table中的索引值。

在对HDR视图进行第一次改写之后,内核通过调用find_sec,分别查找以下名称的section:".gnu.linkonce.this_module”,“__versions”和“.modinfo,。查找的索引值分别保存在变量modindex、versindex和infoindex中,以备将来使用。

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

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

相关文章

opencv图像去畸变

图像去畸变的思路 对于目标图像(无畸变图像)上的每个像素点&#xff0c;转换到normalize平面&#xff0c;再进行畸变变换&#xff0c;进行投影&#xff0c;得到这个像素点畸变后的位置&#xff0c;然后将这个位置的源图像&#xff08;畸变图像&#xff09;的像素值作为目标图像…

Visual Studio 2022安装与编译简单c语言以及C#语言(番外)

文章目录1 软件下载网站2 下载与安装3 创建并学习C语言4 创建并学习C#语言1 软件下载网站 Visual Studio官网 2 下载与安装 1、下载社区版即可。 2、下载得到安装文件&#xff0c;右键以管理员方式运行安装文件。 3、点击继续。 4、等待下载完成。 5、这里学习C选择使用…

SpringBoot文件上传同时,接收复杂参数

目录 环境信息 问题描述 错误分析 解决方法 简单参数 总结 环境信息 Spring Boot&#xff1a;2.0.8.RELEASE Spring Boot内置的tomcat&#xff1a;tomcat-embed-core 8.5.37 问题描述 收到文件上传的开发工作&#xff0c;要求能适配各种场景&#xff0c;并且各场景的请求…

C语言——操作符详解(上)

C语言——操作符详解&#xff08;上&#xff09; 操作符的分类 C语言中的操作符主要分为算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。我将分成三篇文章为大家详细介绍以上所…

[附源码]Python计算机毕业设计Django网约车智能接单规划小程序

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

[附源码]Python计算机毕业设计华夏商场红酒管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等…

AI绘画火爆,以昆仑万维AIGC为例,揭秘AI绘画背后的模型算法

AI绘画火爆&#xff0c;以昆仑万维AIGC为例&#xff0c;揭秘AI绘画背后的模型算法 一、前言 最近AI绘画让人工智能再次走进大众视野。在人工智能发展早起&#xff0c;一直认为人工智能能实现的功能非常有限。通常都是些死板的东西&#xff0c;像是下棋、问答之类的&#xff0…

mysql锁范围(一)表级锁变行级锁

文章目录行级锁1. 用两个连接connection登陆mysql2. 测试无索引情况1&#xff09;机器1开启事务&#xff0c;执行更新北京仓数据sql&#xff0c;不提交事务2&#xff09;机器2开启事务&#xff0c;先查询北京仓3&#xff09;机器2开始更新上海仓数据4&#xff09;机器1事务回滚…

【Spring Cloud】Nacos服务分级存储模型与负载均衡原理与实战

本期目录1. 服务分级模型介绍2. 服务分级模型的必要性3. 配置集群属性4. NacosRule负载均衡4.1 背景描述4.2 配置Nacos负载均衡策略4.3 根据权重负载均衡1. 服务分级模型介绍 为了提升整个系统的容灾性&#xff0c;Nacos 引入了地域 (Zone) 的概念&#xff0c;如上图中的北京、…

Reactor 和 Proactor 区别

Reactor 和 Proactor 区别 同步异步、阻塞非阻塞组合 同步 以read()函数为例&#xff0c;int n read(fd, buf. sz) 当采用同步的方式和阻塞io的方式时&#xff0c;buf就是从内核拷贝的数据&#xff0c;函数返回则可以马上知道 buf 中的数据。当采用同步的方式和非阻塞io的方式…

关于rabbitmq消息推送的小demo

目录 一.前言 1.1场景 1.2消息交换机三种形式 二.建设demo工程 2.1 依赖 2.2yml文件指定rabbitmq连接信息 2.3直连型消息链接 一.前言 1.1场景 在我们实际开发中到一个特定的时候是比如工作流到某个状态时, 我们会向某某单位发送消息, 这时就会用到我们的消息推送---ra…

javaee之Mybatis2

一、保存操作 在做这个方法之前&#xff0c;我们先把之前做的那个MybatisTest里面的每一个方法做成一个Test方法&#xff0c;也就是标注Test这个注解 这样便于我们测试接下来的每一个方法。仔细分析一下上面的代码&#xff0c;会发现&#xff0c;可重复性的地方太多。比如我们…

两台linux服务器rsync自动备份文件

检查rsycn是否安装 检查方法&#xff1a;rpm -qa rsync 出现rsync 包名就是安装了 安装rsycn rsync的安装可以使用yum直接安装&#xff1a;yum install rsync rsycn的服务端/文件接收端配置 1、先创建备份目录 mkdir /data/xsbak2、服务端需要开启rsyncd服务&#xff0c;添加…

接口测试(九)—— Git代码托管、jenkins 的持续集成

目录 一、持续集成 二、git 1、简介和安装 2、Gitee 2.1 git 和 gitee 管理代码工作原理 2.2 PyCharm 配置 Gitee 插件 3、PyCharm 与 Gitee 相关操作 3.1 将 Gitee的项目 Checkout到 Pycharm中 3.2 推送 PyCharm 新项目到 Gitee远程仓库 3.3 将 Pycharm代码 push到 …

React基础知识(组件实例三大核心属性state、props、refs)(二)

系列文章目录 第一章&#xff1a;React基础知识&#xff08;React基本使用、JSX语法、React模块化与组件化&#xff09;&#xff08;一&#xff09; 文章目录系列文章目录一、State1.1. state基本使用1.2 state的简写形式二、Props2.1 props的基本使用2.2 props属性值限制2.3 …

精品基于SSM的小学生课程资源网络云平台

《基于SSM的小学生课程资源网络云平台》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 使用技术&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据…

redux

文章目录redux是什么redux使用情况redux工作原理redux安装redux的3个核心概念Action——形成动作Reducers——操作状态store——存储状态APIredux的响应式redux实现加减案例不使用ActionCreators实现使用ActionCreators实现异步Actionreact-redux使用容器组件创建修改App.jsx在…

干电池升压IC3.3V的电源芯片fs2111/FS2112

干电池升压3.3V的电源芯片FS2111/FS2112 FS2111/FS2112适用于一节干电池升压到3.3V&#xff0c;两节干电池升压3.3V的升压电路&#xff0c;FS2111/FS2112干电池升压IC。 FS2111/FS2112 干电池1.5V和两节干电池3V升压到3.3V的测试数据 两节干电池输出500MA测试&#xff1a; F…

CubeMax添加Rtthread操作系统 组件STM32F103

CubeMax添加Rtthread操作系统 组件STM32F103 本篇主要介绍&#xff0c;怎么使用STM32CubeMx工具&#xff0c;添加RT-Thread操作系统组件&#xff0c;码代码的IDE是keil。快速移植RT-Thread实时操作系统&#xff0c;所用的IDE可自行官网下载最新版。 CubeMax官网下载链接 RTthre…

Ajax(六)

1. XMLHttpRequest的基本使用——URL编码与解码 1.1 什么是URL编码 1.2 如何对URL进行编码与解码 <body><script>var str 黑马程序员//对str编码var str2 encodeURI(str)console.log(str2)//一个汉字对应三个百分号&#xff0c;反解码从console里头复制console.…