【openwrt】【overlayfs】Openwrt系统overlayfs挂载流程

news2025/1/9 16:45:15

overlayfs是一种叠加文件系统,在openwrt和安卓系统中都有很广泛的应用,overlayfs通常用于将只读根文件系统(rootfs)和可写文件系统(jffs2)进行叠加后形成一个新的文件系统,这个新的文件系统“看起来”是可读写的,这种做法的好处是:

  • 对这个新文件系统的修改(删除也属于修改)都只保存在可写文件系统中,只读根文件系统不受任何影响
  • 将可写文件系统格式化后,可以将整个文件系统恢复到初始状态(相当于只有只读根文件系统的状态)
  • 减少flash擦写次数,延长设备使用寿命

下面就开始介绍openwrt系统中的overlayfs是如何挂载的,挂载过程可以分为2个部分:

  • 只读根文件系统(rootfs)挂载过程
  • overlayfs 挂载过程(包括可写文件系统(rootfs_data)挂载过程)

只读根文件系统(rootfs)挂载过程

kernel的启动流程大致如下:
在这里插入图片描述

prepare_namespace

prepare_namespace负责根文件系统的挂载

void __init prepare_namespace(void)
{
	int is_floppy;

	if (root_delay) {
		printk(KERN_INFO "Waiting %d sec before mounting root device...\n",
		       root_delay);
		ssleep(root_delay);
	}
	wait_for_device_probe(); // 等待所有的设备初始化完成

	md_run_setup();

	if (saved_root_name[0]) {
		root_device_name = saved_root_name;
		if (!strncmp(root_device_name, "mtd", 3) ||
		    !strncmp(root_device_name, "ubi", 3)) {
			mount_block_root(root_device_name, root_mountflags);
			goto out;
		}
		ROOT_DEV = name_to_dev_t(root_device_name);
		if (strncmp(root_device_name, "/dev/", 5) == 0)
			root_device_name += 5;
	}

	if (initrd_load())
		goto out;
	/* wait for any asynchronous scanning to complete */
	if ((ROOT_DEV == 0) && root_wait) {
		printk(KERN_INFO "Waiting for root device %s...\n",
			saved_root_name);
		while (driver_probe_done() != 0 ||
			(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
			msleep(5);
		async_synchronize_full();
	}

	is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

	if (is_floppy && rd_doload && rd_load_disk(0))
		ROOT_DEV = Root_RAM0;

	mount_root();
out:
	devtmpfs_mount("dev");
	ksys_mount(".", "/", NULL, MS_MOVE, NULL);
	ksys_chroot(".");
}
  • root_delay:如果cmdline中有“rootdelay=xxx”,则调用ssleep延迟xxx秒,例如“rootdelay=10”,则延时10s
  • wait_for_device_probe()是等待所有的设备probe完成,
  • saved_root_name:如果cmdline中有“root=xxx”,则saved_root_name=xxx。在我当前的系统中 root=/dev/mmcblk1p65,所以saved_root_name=/dev/mmcblk1p65
  • mount_block_root: 如果root_device_name前三个字符是“ubi”或者“mtd”,则调用mount_block_root进行挂载根文件系统,这里主要是针对ubifs这一种文件系统进行特殊处理,因为ubifs根文件系统对应cmdline的参数一般是:ubi.mtd=1 root=ubi0:rootfs rootfstype=ubifs ,而其他文件系统一般是root=/dev/xxx.saved_root_name会被赋值给root_device_name
  • ROOT_DEV:root_device_name设备对应的设备号(/dev/mmcblk1p65设备对应主设备号是259,次设备号是0)
  • while (driver_probe_done() != 0): 等待所有saved_root_name设备probe完成(dev/xxx节点被创建)
  • mount_root:开始挂载根文件系统

mount_root(内核层)

void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
	if (ROOT_DEV == Root_NFS) {
		if (!mount_nfs_root())
			printk(KERN_ERR "VFS: Unable to mount root fs via NFS.\n");
		return;
	}
#endif
#ifdef CONFIG_CIFS_ROOT
	if (ROOT_DEV == Root_CIFS) {
		if (!mount_cifs_root())
			printk(KERN_ERR "VFS: Unable to mount root fs via SMB.\n");
		return;
	}
#endif
#ifdef CONFIG_MTD_ROOTFS_ROOT_DEV
	if (!mount_ubi_rootfs())
		return;
#endif
#ifdef CONFIG_BLOCK
	{
		int err = create_dev("/dev/root", ROOT_DEV);

		if (err < 0)
			pr_emerg("Failed to create /dev/root: %d\n", err);
		mount_block_root("/dev/root", root_mountflags);
	}
#endif
}
  • CONFIG_MTD_ROOTFS_ROOT_DEV 的作用是告诉 Linux 内核在引导过程中从哪个 MTD 设备加载根文件系统,一般支持NANDFlash的设备都会开启这个选项
  • mount_ubi_rootfs() 尝试挂载ubifs,如果挂载成功,则不再继续后续的挂载步骤。
  • CONFIG_BLOCK是开启块设备子系统,对于绝大多数文件系统(EXT4、yaffs2、XFS、FAT等)都需要块设备子系统的支持。
  • create_dev(“/dev/root”, ROOT_DEV) 创建了一个设备节点/dev/root,/dev/root是根文件系统设备的抽象,因为它的设备号也是ROOT_DEV,前面有提到ROOT_DEV就是实际的根文件系统设备节点。这样做的好处是不用关心实际的根文件系统设备是什么,直接对/dev/root进行mount就可以实现根文件系统的挂载。
  • mount_block_root(“/dev/root”, root_mountflags) 挂载/dev/root
void __init mount_block_root(char *name, int flags)
{
	struct page *page = alloc_page(GFP_KERNEL);
	char *fs_names = page_address(page);
	char *p;
	char b[BDEVNAME_SIZE];

	scnprintf(b, BDEVNAME_SIZE, "unknown-block(%u,%u)",
		  MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
	get_fs_names(fs_names);
retry:
	for (p = fs_names; *p; p += strlen(p)+1) {
		int err = do_mount_root(name, p, flags, root_mount_data);
		switch (err) {
			case 0:
				goto out;
			case -EACCES:
			case -EINVAL:
				continue;
		}
	        /*
		 * Allow the user to distinguish between failed sys_open
		 * and bad superblock on root device.
		 * and give them a list of the available devices
		 */
		printk("VFS: Cannot open root device \"%s\" or %s: error %d\n",
				root_device_name, b, err);
		printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");

		printk_all_partitions();
#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
		printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
		       "explicit textual name for \"root=\" boot option.\n");
#endif
		panic("VFS: Unable to mount root fs on %s", b);
	}
	if (!(flags & SB_RDONLY)) {
		flags |= SB_RDONLY;
		goto retry;
	}

	printk("List of all partitions:\n");
	printk_all_partitions();
	printk("No filesystem could mount root, tried: ");
	for (p = fs_names; *p; p += strlen(p)+1)
		printk(" %s", p);
	printk("\n");
	panic("VFS: Unable to mount root fs on %s", b);
out:
	put_page(page);
}
  • get_fs_names(fs_names) 获取根文件系统类型并存入变量 fs_names,fs_names中可能保存了很多个文件系统类型,他们是用,隔开的。
  • for (p = fs_names; *p; p += strlen§+1) 是依次尝试使用fs_names 里面的文件系统类型进行挂载
  • do_mount_root(name, p, flags, root_mount_data) 是具体挂载过程,此处name=/dev/root,一旦挂载成功则退出整个挂载步骤,不会再继续尝试其他文件系统类型。
static int __init do_mount_root(const char *name, const char *fs,
				 const int flags, const void *data)
{
	struct super_block *s;
	struct page *p = NULL;
	char *data_page = NULL;
	int ret;

	if (data) {
		/* init_mount() requires a full page as fifth argument */
		p = alloc_page(GFP_KERNEL);
		if (!p)
			return -ENOMEM;
		data_page = page_address(p);
		/* zero-pad. init_mount() will make sure it's terminated */
		strncpy(data_page, data, PAGE_SIZE);
	}

	ret = init_mount(name, "/root", fs, flags, data_page);
	if (ret)
		goto out;

	init_chdir("/root");
	s = current->fs->pwd.dentry->d_sb;
	ROOT_DEV = s->s_dev;
	printk(KERN_INFO
	       "VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
	       s->s_type->name,
	       sb_rdonly(s) ? " readonly" : "",
	       MAJOR(ROOT_DEV), MINOR(ROOT_DEV));

out:
	if (p)
		put_page(p);
	return ret;
}
  • data 是上一步传入的root_mount_data变量,root_mount_data 是cmdline 中 “rootflags=xxx” 参数=后面的部分,即挂载选项。如果不为空,则需要申请内存保存data
  • init_mount(name, “/root”, fs, flags, data_page) 将/dev/root设备挂载到/root
  • init_chdir(“/root”) 将当前工作路径改为/root
  • s = current->fs->pwd.dentry->d_sb 获取当前文件系统的超级块
  • ROOT_DEV = s->s_dev 将超级块的设备号赋值给 ROOT_DEV
  • 这时候可以看到如下内核打印,说明rootfs已经挂载成功
[    2.337243] VFS: Mounted root (squashfs filesystem) readonly on device 259:0.
# device 259:0 对应设备 
brw-------    1 root     root      259,   0 Jan  1  1970 /dev/mmcblk1p65

overlayfs 挂载过程

在openwrt系统中,overlayfs 挂载需要使用 fstools工具。fstools并不是一个具体的tool,它包含了多个小工具,这些小工具都是运行在应用层的,这也说明了overlayfs是在应用层进行挂载的。

fstools

fstools实际上包括如下小工具:

  • jffs2reset
  • mount_root
  • libfstools.so

除此之外,下列工具也放在fstools的包里面,它们是基于fstools开发出来的拓展工具,而且如果需要安装下面的工具,除了
使能CONFIG_PACKAGE_fstools=y之外,还需要使能对应的配置。

  • block (CONFIG_PACKAGE_block-mount)
  • blockd (CONFIG_PACKAGE_blockd)
  • snapshot_tool (CONFIG_PACKAGE_snapshot-tool)
  • ubi (CONFIG_PACKAGE_ubi-utils)

mount_root(应用层)

mount_root就是用来挂载overlayfs的工具,它支持4种模式:

  • 默认(无参数):挂载overlayfs模式
  • ram :挂载基于ram的overlayfs
  • stop:获取SHUTDOWN环境变量状态
  • done:挂载结束后置文件系统状态位
//fstools-2022-06-02-93369be0/mount_root.c
int main(int argc, char **argv)
{
	if (argc < 2)
		return start(argc, argv);
	if (!strcmp(argv[1], "ram"))
		return ramoverlay();
	if (!strcmp(argv[1], "stop"))
		return stop(argc, argv);
	if (!strcmp(argv[1], "done"))
		return done(argc, argv);
	return -1;
}

在oepnwrt系统运行第一个进程(1号进程)时,mount_root就会被调用。可以看到此时mount_root没有携带任何参数,所以它首先走的是start(argc, argv) 逻辑。

# openwrt/package/base-files/files/lib/preinit/80_mount_root
do_mount_root() {
	mount_root
	boot_run_hook preinit_mount_root
	[ -f /sysupgrade.tgz -o -f /tmp/sysupgrade.tar ] && {
		echo "- config restore -"
		cp /etc/passwd /etc/group /etc/shadow /tmp
		cd /
		[ -f /sysupgrade.tgz ] && tar xzf /sysupgrade.tgz
		[ -f /tmp/sysupgrade.tar ] && tar xf /tmp/sysupgrade.tar
		missing_lines /tmp/passwd /etc/passwd >> /etc/passwd
		missing_lines /tmp/group /etc/group >> /etc/group
		missing_lines /tmp/shadow /etc/shadow >> /etc/shadow
		rm /tmp/passwd /tmp/group /tmp/shadow
		# Prevent configuration corruption on a power loss
		sync
	}
}
mount_root->start
static int
start(int argc, char *argv[1])
{
	struct volume *root;
	struct volume *data = volume_find("rootfs_data");
	struct stat s;

	if (!getenv("PREINIT") && stat("/tmp/.preinit", &s))
		return -1;

	if (!data) {
		root = volume_find("rootfs");
		volume_init(root);
		ULOG_NOTE("mounting /dev/root\n");
		mount("/dev/root", "/", NULL, MS_NOATIME | MS_REMOUNT, 0);
	}

	/* Check for extroot config in rootfs before even trying rootfs_data */
	if (!mount_extroot("")) {
		ULOG_NOTE("switched to extroot\n");
		return 0;
	}

	/* There isn't extroot, so just try to mount "rootfs_data" */
	volume_init(data);
	switch (volume_identify(data)) {
	case FS_NONE:
		ULOG_WARN("no usable overlay filesystem found, using tmpfs overlay\n");
		return ramoverlay();

	case FS_DEADCODE:
		/*
		 * Filesystem isn't ready yet and we are in the preinit, so we
		 * can't afford waiting for it. Use tmpfs for now and handle it
		 * properly in the "done" call.
		 */
		ULOG_NOTE("jffs2 not ready yet, using temporary tmpfs overlay\n");
		return ramoverlay();

	case FS_EXT4:
	case FS_F2FS:
	case FS_JFFS2:
	case FS_UBIFS:
		mount_overlay(data);
		break;

	case FS_SNAPSHOT:
		mount_snapshot(data);
		break;
	}

	return 0;
}
  • volume_find(“rootfs_data”) 查询分区name为rootfs_data的分区,这里最终调用的是volume>-driver->find(),不同类型的文件系统会有不同的实现,find()过程会初始化一个volume对象,但volume对象的成员信息有可能是不完整的,还需要在接下来的init()环节继续填充完整。
  • volume_init(data) 初始化volume对象,继续完善volume成员信息,这里最终调用的是volume>-driver->init()
  • volume_identify(data) 识别volume指向的分区的文件系统类型,如果是FS_EXT4 FS_F2FS FS_JFFS2 FS_UBIFS这4种文件系统之一,接下来就会执行挂载overlayfs流程,这里最终调用的是volume>-driver->identify()
int mount_overlay(struct volume *v)
{
	const char *overlay_mp = "/tmp/overlay";
	char *mp, *fs_name;
	int err;

	if (!v)
		return -1;

	mp = find_mount_point(v->blk, 0);
	if (mp) {
		ULOG_ERR("rootfs_data:%s is already mounted as %s\n", v->blk, mp);
		return -1;
	}

	err = overlay_mount_fs(v, overlay_mp);
	if (err)
		return err;

	/*
	 * Check for extroot config in overlay (rootfs_data) and if present then
	 * prefer it over rootfs_data.
	 */
	if (!mount_extroot(overlay_mp)) {
		ULOG_INFO("switched to extroot\n");
		return 0;
	}

	switch (fs_state_get(overlay_mp)) {
	case FS_STATE_UNKNOWN:
		fs_state_set(overlay_mp, FS_STATE_PENDING);
		if (fs_state_get(overlay_mp) != FS_STATE_PENDING) {
			ULOG_ERR("unable to set filesystem state\n");
			break;
		}
	case FS_STATE_PENDING:
		ULOG_INFO("overlay filesystem has not been fully initialized yet\n");
		overlay_delete(overlay_mp, true);
		break;
	case FS_STATE_READY:
		break;
	}

	fs_name = overlay_fs_name(volume_identify(v));
	ULOG_INFO("switching to %s overlay\n", fs_name);
	if (mount_move("/tmp", "", "/overlay") || fopivot("/overlay", "/rom")) {
		ULOG_ERR("switching to %s failed - fallback to ramoverlay\n", fs_name);
		return ramoverlay();
	}

	return -1;
}
  • const char *overlay_mp = “/tmp/overlay” 定义临时挂载点
  • find_mount_point 查找rootfs_data分区的挂载点,主要目的是为了确认rootfs_data是否已经挂载,如果未挂载,则继续执行
  • overlay_mount_fs 将rootfs_data分区挂载到 /tmp/overlay目录
  • overlay_fs_name(volume_identify(v)) 获取rootfs_data分区文件系统的类型
  • mount_move(“/tmp”, “”, “/overlay”) 将挂载点 /tmp/overlay 迁移至 /overlay,
  • fopivot(“/overlay”, “/rom”) 挂载overlayfs,可写文件系统的挂载点为/overlay/rom此时还只是一个普通文件夹
int fopivot(char *rw_root, char *ro_root)
{
	char overlay[64], mount_options[64], upperdir[64], workdir[64], upgrade[64], upgrade_dest[64];
	struct stat st;

	if (find_filesystem("overlay")) {
		ULOG_ERR("BUG: no suitable fs found\n");
		return -1;
	}

	snprintf(overlay, sizeof(overlay), "overlayfs:%s", rw_root);
	snprintf(upperdir, sizeof(upperdir), "%s/upper", rw_root);
	snprintf(workdir, sizeof(workdir), "%s/work", rw_root);
	snprintf(upgrade, sizeof(upgrade), "%s/sysupgrade.tgz", rw_root);
	snprintf(upgrade_dest, sizeof(upgrade_dest), "%s/sysupgrade.tgz", upperdir);
	snprintf(mount_options, sizeof(mount_options), "lowerdir=/,upperdir=%s,workdir=%s",
		 upperdir, workdir);

	/*
	 * Initialize SELinux security label on newly created overlay
	 * filesystem where /upper doesn't yet exist
	 */
	if (stat(upperdir, &st))
		selinux_restorecon(rw_root);

	/*
	 * Overlay FS v23 and later requires both a upper and
	 * a work directory, both on the same filesystem, but
	 * not part of the same subtree.
	 * We can't really deal with these constraints without
	 * creating two new subdirectories in /overlay.
	 */
	if (mkdir(upperdir, 0755) == -1 && errno != EEXIST)
		return -1;

	if (mkdir(workdir, 0755) == -1 && errno != EEXIST)
		return -1;

	if (stat(upgrade, &st) == 0)
		rename(upgrade, upgrade_dest);

	if (mount(overlay, "/mnt", "overlay", MS_NOATIME, mount_options)) {
		ULOG_ERR("mount failed: %m, options %s\n", mount_options);
		return -1;
	}

	return pivot("/mnt", ro_root);
}
  • find_filesystem(“overlay”) 判断当前内核是否支持overlayfs
  • snprintf(mount_options,xxx) 设置overlayfs的挂载选项
  • mount(overlay, “/mnt”, “overlay”, MS_NOATIME, mount_options) 将overlayfs挂载到 /mnt目录
  • pivot(“/mnt”, ro_root) 这一步操作比较复杂,它主要做了2件事情:1.将当前进程的/ 重新挂载到ro_root目录,也就是/rom 2.将/mnt重新挂载为新的/,因为上一步中overlayfs挂载到 /mnt,所以这一步结果是 overlayfs挂载到 /

最终的效果如下:

$ mount
/dev/root on /rom type squashfs (ro,relatime)  # 只读文件系统
/dev/mmcblk1p66 on /overlay type ext4 (rw,noatime) # 可写文件系统
overlayfs:/overlay on / type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work) #overlayfs

有关volumedriver相关说明如下:

  • volume
    volume 用于描述一个分区(块设备),但与ubifs中的volume不是一个概念,要注意区分。
    这里的volume包含如下信息:
enum {
	UNKNOWN_TYPE,
	NANDFLASH,
	NORFLASH,
	UBIVOLUME,
	BLOCKDEV,
};

struct volume {
	struct driver	*drv;//分区对应driver
	char		*name;//分区名 
	char		*blk;//分区对应的设备节点,dev/xxx

	__u64		size;//分区大小
	__u32		block_size;//块大小
	int		type;//NANDFLASH/NORFLASH/UBIVOLUME
};
  • driver
    driver是操作volume的驱动,包括初始化、查找、读写、擦除等操作,这些操作与具体的文件系统有关,因此不同的文件系统会对应不同的driver。
typedef int (*volume_probe_t)(void);
typedef int (*volume_init_t)(struct volume *v);
typedef void (*volume_stop_t)(struct volume *v);
typedef struct volume *(*volume_find_t)(char *name);
typedef int (*volume_identify_t)(struct volume *v);
typedef int (*volume_read_t)(struct volume *v, void *buf, int offset, int length);
typedef int (*volume_write_t)(struct volume *v, void *buf, int offset, int length);
typedef int (*volume_erase_t)(struct volume *v, int start, int len);
typedef int (*volume_erase_all_t)(struct volume *v);

struct driver {
	struct list_head	list;//用于将多个不同的driver挂接在一起
	char			*name;//驱动名
	volume_probe_t		probe;
	volume_init_t		init;//
	volume_stop_t		stop;
	volume_find_t		find;
	volume_identify_t	identify;
	volume_read_t		read;
	volume_write_t		write;
	volume_erase_t		erase;
	volume_erase_all_t	erase_all;
};
mount_root->ram
int
ramoverlay(void)
{
	mkdir("/tmp/root", 0755);
	mount("tmpfs", "/tmp/root", "tmpfs", MS_NOATIME, "mode=0755");

	return fopivot("/tmp/root", "/rom");
}
  • mount(“tmpfs”, “/tmp/root”, “tmpfs”, MS_NOATIME, “mode=0755”) 挂载tmpfs
  • fopivot(“/tmp/root”, “/rom”) 将当前进程的/ 重新挂载到/rom目录 然后将/tmp/root重新挂载为新的/,这种overlayfs的可写部分是基于RAM的文件系统,所有的修改掉电后会丢失。
mount_root->stop
static int
stop(int argc, char *argv[1])
{
	if (!getenv("SHUTDOWN"))
		return -1;

	return 0;
}
  • getenv(“SHUTDOWN”) 获取SHUTDOWN环境变量并返回结果码
mount_root->done
# openwrt/package/base-files/files/etc/init.d/done
#!/bin/sh /etc/rc.common
# Copyright (C) 2006 OpenWrt.org

START=95
boot() {
	mount_root done
	rm -f /sysupgrade.tgz && sync

	# process user commands
	[ -f /etc/rc.local ] && {
		sh /etc/rc.local
	}

	# set leds to normal state
	. /etc/diag.sh
	set_state done
}

  • mount_root done 会在openwrt所有服务启动的尾声被调用
static int
done(int argc, char *argv[1])
{
	struct volume *v = volume_find("rootfs_data");

	if (!v)
		return -1;

	switch (volume_identify(v)) {
	case FS_NONE:
	case FS_DEADCODE:
		return jffs2_switch(v);

	case FS_EXT4:
	case FS_F2FS:
	case FS_JFFS2:
	case FS_UBIFS:
		fs_state_set("/overlay", FS_STATE_READY);
		break;
	}

	return 0;
}
  • volume_find(“rootfs_data”) 查询分区name为rootfs_data的分区,返回对应的volume对象
  • volume_identify(v) 识别rootfs_data分区的文件系统类型,如果是FS_EXT4 FS_F2FS FS_JFFS2 FS_UBIFS这4种文件系统之一,会设置文件系统状态为FS_STATE_READY,表示overlayfs挂载完成。

参考

核心的進入點: start_kernel()
Linux内核源码分析-安装实际根文件系统- prepare_namespace
内核启动之start_kernel()和rest_init()函数
/dev/root

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

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

相关文章

汽车生产污废水处理需要哪些工艺设备

对于汽车生产过程中产生的污废水处理&#xff0c;需要运用一系列的工艺设备来实现有效的清洁和回收利用。下面让我们一起来探索一下吧&#xff01; 首先&#xff0c;汽车生产工艺设备中最常见的是物理处理设备。物理处理包括沉淀、过滤和吸附等过程。其中&#xff0c;沉淀操作可…

C语言——整数和浮点数在内存中的存储

目录 一、整数在内存中的存储 二、大小端字节序和字节序判断 2.1 什么是大小端&#xff1f; 2.2 为什么有大小端? 2.3 练习 2.3.1 练习1 2.3.2 练习2 三、浮点数在内存中的存储 3.1练习 3.2 浮点数的存储 3.2.1浮点数存的过程 3.2.2浮点数取的过程 3.3 题目解…

js实现iframe内容加载失败自动重新加载功能

最近一个项目上的程序经常出现掉线的情况&#xff0c;经排查是该单位的网络不稳定&#xff0c;存在网络丢包现象。导致有时候程序运行加载页面失败&#xff0c;开机自启动应用时出现请求失败的概率非常大&#xff0c;为了解决这个问题我在网上东找西找也没有找到有效的解决办法…

RocketMQ 源码解析:生产者投递消息 DefaultMQProducer#send(一)

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

区间预测 | Matlab实现GRU-Adaboost-ABKDE的集成门控循环单元自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现GRU-Adaboost-ABKDE的集成门控循环单元自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现GRU-Adaboost-ABKDE的集成门控循环单元自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

Linux shell美化 zsh+oh-my-zsh+power10k

文章目录 安装zsh安装on-my-zsh安装power10k主题安装power10k将oh-my-zsh主题改为power10k字体 设置安装字体配置字体 power10k配置相关插件安装zsh-autosuggestionszsh-syntax-highlighting安装插件完成&#xff0c;重新加载配置文件 美化效果示意&#xff1a; 安装zsh 安装…

连接超时的问题

连接超时的问题 通用第三方工具连接超时 connect timeout 方案一&#xff1a; /etc/ssh/sshd_config node1上操作&#xff0c;图是错的 方案二&#xff1a; windows上Hosts文件域名解析有问题 比如&#xff1a; 192.168.xx.100 node1 192.168.xx.161 node1 两个都解析成node…

Failed to start OpenSSH server daemon-SSH启动失败

一、SSH服务启动失败 或者报错误&#xff1a; journalctl -xe sshd.service 二、查看SSHD的服务状态 3、重新安装openssh [rootzbx ~]# yum -y remove openssh 卸载原来的 [rootzbx ~]# yum -y install openssh openssh-clients openssh-server 重新安装 [rootzbx ~]# system…

阿里云云原生助力安永创新驱动力实践探索

云原生正在成为新质生产力变革的核心要素和企业创新的数字基础设施。2023 年 12 月 1 日&#xff0c;由中国信通院举办的“2023 云原生产业大会”在北京召开。在大会“阿里云云原生”专场&#xff0c;安永科技咨询合伙人王祺分享了对云原生市场的总览及趋势洞见&#xff0c;及安…

代码随想录算法训练营第三十六天 | 435.无重叠区间、763.划分字母区间、56.合并区间

435.无重叠区间 题目链接&#xff1a;435.无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 文章讲解/视频讲解&#xff1a;https://programmercarl.com/0435.%E6%9…

Git学习笔记(第2章):Git安装

官网地址&#xff1a;Githttps://git-scm.com/ Step1&#xff1a;查看Git的GNU协议 → 点击“Next” Step2&#xff1a;设置Git的安装位置(非中文、无空格的目录) → 点击“Next” Step3&#xff1a;选择Git的选项配置(推荐默认设置) → 点击“Next” Step4&#xff1a;设置Git…

What is `addFormattersdoes` in `WebMvcConfigurer` ?

addFormatters 方法在SpringMVC框架中主要用于向Spring容器注册自定义的格式化器&#xff08;Formatter&#xff09; SpringMVC内置了一系列的标准格式化器&#xff0c;用于处理日期、数字和其他常见类型的转换。 开发者也可以通过实现 WebMvcConfigurer 接口&#xff0c;并重写…

笔记本电脑如何连接显示屏?

目录 1.按下快捷键 winP,选择扩展 2.连接显示器&#xff0c;连好接线 3.笔记本驱动有问题&#xff0c;显示错误如下&#xff1a; 4.驱动已经下载完成&#xff0c; 按下快捷键&#xff0c;还是显示第3步中的错误 5.驱动已经下载完成&#xff0c; 按下快捷键&#xff0c;参照…

Page268~270 11.3.4 wxWidgets项目配置

项目w28_gui的项目配置&#xff1a; 一&#xff0c;编译选项&#xff0c; -pipe -mthreads [[if (GetCompilerFactory().GetCompilerVersionString(_T("gcc")) > _T("4.8.0")) print(_T("-Wno-unused-local-typedefs"));]] 1, -pipe&#…

京东获得JD商品详情 API (jd.item_get):电商发展中的中重要性

数据整合与同步&#xff1a;对于许多电商企业来说&#xff0c;商品数据的管理是一个重要的环节。通过JD商品详情API&#xff0c;商家可以方便地获取京东平台上的商品详情&#xff0c;实现数据的整合与同步。这有助于确保商品信息的准确性&#xff0c;提高库存管理和订单处理的效…

前端react入门day04-useEffect与Hook函数

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 useEffect 的使用 useEffect 的概念理解 useEffect 依赖项参数说明 useEffect — 清除副作用 自定义Ho…

新数智空间:阿里云边缘云持续保持中国公有云市场第一

全球领先的 IT 市场研究和咨询公司 IDC 发布 《中国边缘云市场解读&#xff08;2023H1&#xff09;》报告 中国边缘公有云服务市场 阿里云持续第一 稳居市场第一&#xff0c;“边缘”逆势生长 近日&#xff0c;全球领先的 IT 市场研究和咨询公司 IDC 最新发布《中国边缘云市…

通信入门系列——信号的频谱分析

一、信号频谱 信号的频谱&#xff0c;指的是一段频率范围内的情况&#xff0c;信号的幅度和相位的情况。 以一个频率为1Hz的余弦电压信号进行说明&#xff0c;这个信号的傅里叶变换为X(ω)πδ(ω-2π)πδ(ω2π)&#xff0c;也就是所谓的频谱密度&#xff0c;单位为V/(rad/…

Manjora 中使用idm,linux通用

说明 在Mnajora中的idm需要在wine中运行&#xff0c;idm是一款很不错的下载工具&#xff0c;但是在linux不能直接使用&#xff0c;借助wine也无法使用浏览器的集成插件&#xff0c;在网上偶然发现一款第三方插件能够在linux的浏览器中将链接捕捉到idm中&#xff0c;虽然使用起…

Java的便捷输入方法及解析

在 Java 中&#xff0c;有多种便捷的输入方法可以从用户那里获取输入。下面是一些常见的便捷输入方法及解析&#xff1a; 使用 Scanner 类&#xff1a;在上述示例中&#xff0c;首先导入了 java.util.Scanner 类&#xff0c;创建了一个 Scanner 对象&#xff0c;并使用 System…