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

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.mfbz.cn/a/331733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软件设计师6--流水线技术

软件设计师6--流水线技术 考点1&#xff1a;流水线--概念考点2&#xff1a;流水线--流水线计算考点3&#xff1a;流水线--流水线吞吐率计算例题&#xff1a; 考点1&#xff1a;流水线–概念 相关参数计算&#xff1a; 流水线执行时间计算、流水线吞吐率、流水线加速比、流水线…

如何使用视频下载助手获取公众号视频链接

微信公众号视频链接如何提取呢&#xff1f;今天就来说视频下载助手提取微信公众号的视频链接! 1:公众号获取视频下载助手二维码&#xff0c;并长按添加 2:找到需要下载的公众号带有视频的文章并转发给视频下载助手 3:提取完成后用户可以在视频下载助手中点击返回提取好的视频并…

IO网络5.0

思维导图 使用select实现TCP客户端的并发 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <myhead.h>#define SER_PORT 8888 //服务器端口号 #define SER_IP "192.168.122.61" //服务器客户端//客户端TCP int ma…

解决Microsoft outlook新版本无法支持部分邮件模式问题

问题原因&#xff1a;Outlook16.60及以上 for Mac和Outlook微软商店新版均启用了新界面&#xff0c;暂时不支持POP、Exchange等电子邮件协议。Office里的Outlook客户端不受影响。 注意事项&#xff1a;千万不要使用360软件管家或者腾讯软件管家等卸载微软商店的Outlook应用&am…

Python学习之路——异常捕获

一、什么是异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”&#xff0c;也就是bug 二、异常的捕获方法 当我们的程序遇到bug&#xff0c;那么就下来有两种情况 ①整个程序因为一个…

cpp多线程(二)——对线程的控制和锁的概念

这篇文章是笔者学习cpp多线程操作的第二篇笔记&#xff0c;没有阅读过第一篇的读者可以移步此处&#xff1a; Cpp多线程&#xff08;一&#xff09;-CSDN博客 如果读者发现我的文章里有问题&#xff0c;欢迎交流哈&#xff01; 一、如何控制线程呢&#xff1f; c11在std:…

PBR材质背光面太暗优化

图形学中漫反射光照遵循兰伯特光照模型&#xff0c;它的公式如下 其中&#xff1a; &#xff1a;漫反射光颜色 &#xff1a;入射光颜色 &#xff1a;材质的漫反射系数 &#xff1a;法线方向 &#xff1a;光源方向 由于背光面的法线方向和光源方向的点积为负数&#xff0c;因此…

操作系统课程设计-Windows 线程的互斥和同步

目录 前言 1 实验题目 2 实验目的 3 实验内容 3.1 步骤 3.2 关键代码 3.2.1 创建生产者和消费者进程 3.2.2 生产者和消费者进程 4 实验结果与分析 5 代码 前言 本实验为课设内容&#xff0c;博客内容为部分报告内容&#xff0c;仅为大家提供参考&#xff0c;请勿直接抄…

python PyQt5的学习

一、安装与配置 1、环境&#xff1a; python3.7 2、相关模块 pip install pyqt5 pyqt5-tools pyqt5designer 可以加个镜像 -i https://pypi.tuna.tsinghua.edu.cn/simple3、配置设计器 python的pyqt5提供了一个设计器&#xff0c;便于ui的设计 界面是这样的&#xff1a…

MySQL三大日志

1. redo log 1.1 特点 InnoDB存储引擎独有物理日志&#xff0c;记录在数据页上做的修改让MySQL拥有了崩溃恢复能力&#xff0c;保证事务的持久性 1.2 刷盘时机 事务提交时log buffer 空间使用大约一半时事务日志缓冲区满InnoDB 定期执行检查点Checkpoint后台刷新线程&#…

网工内推 | 运维工程师,最高10K*15薪,思科认证优先

01 乐歌股份 招聘岗位&#xff1a;服务器运维工程师 职责描述&#xff1a; 1、负责公司云上云下所有服务器的日常运维工作&#xff0c;包括应用部署、巡检、备份、日志、监控&#xff0c;故障处理&#xff0c;性能优化等&#xff0c;保障公司相关系统稳定运行。 2、为开发、测…

SMART PLC 模拟量批量转换功能块“Multi_ITR“

模拟量输入转换功能块S_ITR详细公式和代码介绍请查看下面文章链接: https://rxxw-control.blog.csdn.net/article/details/121347697https://rxxw-control.blog.csdn.net/article/details/121347697Smart PLC指针和FOR循环组合应用 https://rxxw-control.blog.csdn.net/arti…

go实现判断20000数据范围内哪些是素数(只能被1和它本身整除的数),采用多协程和管道实现

实现一个并发程序&#xff0c;用于寻找 20000 以内的所有素数。使用了 Goroutines 和 Channels 来分发和处理任务&#xff0c;并通过 WaitGroup&#xff08;实现为 exitChan&#xff09;来同步 Goroutines 的退出。 一.GO代码 package mainimport ("fmt""time…

捍卫中华数学产权系列6.Σ1/n的分级通式与对应的求和公式

调和级数Σ1/n在欧系数学是极为重要的概念&#xff0c;它是纯粹数学的七寸、关乎欧系数学的存废。然而欧洲人对Σ1/n从来没有发现任意一个求和公式。本人以简单而充分的逻辑给Σ1/n进行了多种多样的分级、并给出了各自对应的求和公式&#xff0c;所以&#xff0c;一切关于调和级…

vue+elementui实现12个日历平铺,初始化工作日,并且可点击

<template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" :inline"true"><el-form-item label"年份" prop"holidayYear"><el-date-…

Verilog基础:强度建模(一)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 一、强度建模基础 Verilog HDL提供了针对线网信号0、1、x、z的精准强度建模方式&#xff0c;这样可以允许将两个线网信号进行线与操作从而更加精确地描述出硬件行…

string 模拟实现

string的数据结构 char* _str; size_t _size; size_t _capacity; _str 是用来存储字符串的数组&#xff0c;采用new在堆上开辟空间&#xff1b; _size 是用来表示字符串的长度&#xff0c;数组大小strlen(_str)&#xff1b; _capacity 是用来表示_str的空间大小, _capacity…

使用 Postman 发送 get 请求的简易教程

在API开发与测试的场景中&#xff0c;Postman 是一种普遍应用的工具&#xff0c;它极大地简化了发送和接收HTTP请求的流程。要发出GET请求&#xff0c;用户只需设定正确的参数并点击发送即可。 如何使用 Postman 发送一个GET请求 创建一个新请求并将类型设为 GET 首先&#…

余承东发声!预测:2024年,鸿蒙OS将取代苹果iOS…

半导体行业观察机构Techinsights&#xff0c;1月3日发布报告预测&#xff1b;从2024年起&#xff0c;鸿蒙Harmony OS将取代苹果iOS&#xff0c;成为中国市场上第二大智能手机操作系统。 TechInsights预测&#xff0c;2024年全球智能手机销量将同比反弹3%。华为手机在2024年将坚…

基于Harris角点的多视角图像全景拼接算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Harris角点检测 4.2 图像配准 4.3 图像变换和拼接 4.4 全景图像优化 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 function [ImageB…