Linux 情景分析

news2024/11/17 7:35:23

在这里插入图片描述

系列文章目录


Linux 内核设计与实现
深入理解 Linux 内核
Linux 设备驱动程序
Linux设备驱动开发详解
深入理解Linux虚拟内存管理
Linux 情景分析


文章目录

  • 系列文章目录
  • 一、存储管理
    • 1、外部设备存储空间的地址映射
      • (1)ioremap
      • (2)get_vm_area
      • (3)remap_area_pages
      • (3)remap_area_pte
    • 2、系统调用brk()
  • 二、中断、异常和系统调用
  • 符号


一、存储管理

1、外部设备存储空间的地址映射

    任何系统都免不了要有输入/ 输出,所以对外部设备的访问是 CPU 设计中的一个重要问题。一般来说,对外部设备的访问有两种不同的形式,一中叫内存映射式 (memory mapped),另一种叫 I/O 映射式 (I/O mapped)。在采用内存映射方式的 CPU 中,外部设备的存储单元,如控制寄存器、状态寄存器、数据寄存器等等,去作为内存的一部分出现在系统中的。CPU 可以像访问一个内存单元一样地访问外部设备的存储单元,所以不需要专门设立用于外设 I/O 的指令。从前的 PDP-11、后来的 M68K、Power PC 等 CPU 都采用这种方式。而在采用 I/O 映射方式的系统中则不同,外部设备的存储单元与内存分属两个不同的体系。访问内存的指令不能用来访问外部设备的存储单元,所以在 X86 CPU 中设立了专门的 INOUT 指令,但是用于 I/O 指令的 “地址主间” 相对来说是很小的。事实上,现在 X86 的 I/O 地址空间已经非常拥挤。

    但是,随着计算机技术的发展,人们发现单纯的 I/O 映射方式是不能满足要求的。此种方式只适合于早期的计算机技术,那时候一个外设通常都只有几个寄存器,通过这几个寄存器就可以完成对外设的所有操作了。而现在的情况却大不一样。例如,在 PC 机上可以插上一块图像卡,带有 2MB 的存储器,甚至还可能带有一块 ROM,里面装有可执行代码。自从 PCI 总线出现以后,这个问题就更突出了。所以,不管 CPU 的设计采用 I/O 映射或是存储器映射,都必须要有将外设卡上的存储器映射到内存空间,实际上是虚存空间的手段。在 Linux 内核中,这样的映射是通过函数 ioremap() 来建立的。

    对于内存页面的管理,通常我们都是先在虚存空间分配一个虚存区间,然后为此区间分配相应的物理内存页面并建立起映射。而且这样的映射也并不是一次就建立完毕,可以在访问这些虚存页面引起页面异常时逐步地建立。但是,ioremap() 则不同,首先,我们先有一个物理存储区间,其地址就是外设卡上的存储器出现在总线上的地址。这地址未必就是这些存储单元在外设卡上局部的物理地址,而是在总线上由 CPU 所 “看到” 的地址,这中间很可能已经经历了一次地址映射,但这种映射对于 CPU 来说是透明的。所以有时把这种地址称为 “总线地址” 。举例来说,如果有一块 “智能图形卡” ,卡上有个微处理器。对于卡上的微处理器来说,卡上的存储器是从地址 0 开始的,这就是卡上局部的物理地址。但是将这块图形卡插到 PC 的一个 PCI 总线插槽上时,由 PCCPU 所看到的这片物理存储区间的地址可能是从 0x0000 f000 0000 0000 开始的,这中间已经有了一次映射。可是,从系统 (PC) 的 CPU 的角度来说,它只知道这片物理存储区间是从 0x0000 f000 0000 0000 开始的,这就是该区间的物理地址,或者说 “总线地址” 。在 Linux 系统中,CPU 不能按物理地址来访问存储中间,而必须使用虚拟地址,所以必需 “反向” 地从物理地址出发找到一片虚存空间并建立起映射。其次,这样的需求只发生于对外部设备的操作,而这是内核的事,所以相应的虚存区间是在系统空间 (3GB 以上) 。在以前的 Linux 内核版本中,这个函数称为 vremap(),后来改成了 ioremap(),也突出地反映了这一点。还有。这样的页面当然不服从动态的物理内存页面分配,也不服从 kswapd 的换出。

(1)ioremap

// include/asm-i386/io.h
extern inline void * ioremap (unsigned long offset, unsigned long size)
{
	return __ioremap(offset, size, 0);
}

// arch/i386/mm/ioremap.c
/*
 * Remap an arbitrary physical address space into the kernel virtual
 * address space. Needed when the kernel wants to access high addresses
 * directly.
 *
 * NOTE! We need to allow non-page-aligned mappings too: we will obviously
 * have to convert them into an offset in a page-aligned mapping, but the
 * caller shouldn't need to know that small detail.
 */
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
	void * addr;
	struct vm_struct * area;
	unsigned long offset, last_addr;

	/* Don't allow wraparound or zero size */
	last_addr = phys_addr + size - 1;
	if (!size || last_addr < phys_addr)
		return NULL;

	/*
	 * Don't remap the low PCI/ISA area, it's always mapped..
	 */
	if (phys_addr >= 0xA0000 && last_addr < 0x100000)
		return phys_to_virt(phys_addr);

	/*
	 * Don't allow anybody to remap normal RAM that we're using..
	 */
	if (phys_addr < virt_to_phys(high_memory)) {
		char *t_addr, *t_end;
		struct page *page;

		t_addr = __va(phys_addr);
		t_end = t_addr + (size - 1);
	   
		for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
			if(!PageReserved(page))
				return NULL;
	}

	/*
	 * Mappings have to be page-aligned
	 */
	offset = phys_addr & ~PAGE_MASK;
	phys_addr &= PAGE_MASK;
	size = PAGE_ALIGN(last_addr) - phys_addr;

	/*
	 * Ok, go for it..
	 */
	area = get_vm_area(size, VM_IOREMAP);
	if (!area)
		return NULL;
	addr = area->addr;
	if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
		vfree(addr);
		return NULL;
	}
	return (void *) (offset + (char *)addr);
}

    首先是一些例行检查,常常称为 “sanity check”,或者说 “健康检查”、“卫生检查”。其中 109 行检查的是区间的大小既不为 0,也不能太大而越出了 32 位地址空间的限制。物理地址 0xa0000 至 0x100000 用于 VGA 卡和 BIOS,这是在系统初始化时就映射好了的,不能侵犯到这个区间中去。121 行中的 high_memory 是在系统初始化时,根据检测到的物理内存大小设置的物理内存地址的上限 (所对应的虚拟地址)。如果所要求的 phys_addr 小于这个上限的话,就表示与系统的物理内存有冲突了,除非相应的物理页面原来就是保留着的空洞。在通过这些检查以后,还要保证该物理地址是按页面边界对齐的 (136~138 行)。

    完成了这些准备以后,这才 “言归正传” 。首先是要找到一片虚存地址区间。前面讲过,这片区间属于内核,而不属于任何一个特定的进程,所以不是在某个进程的 mm_struct 结构中的虚存区间队列中去寻找,而是从属于内核的虚存区间队列中去寻找。

(2)get_vm_area

// mm/vmalloc.c
struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
{
	unsigned long addr;
	struct vm_struct **p, *tmp, *area;

	area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
	if (!area)
		return NULL;
	size += PAGE_SIZE;
	addr = VMALLOC_START;
	write_lock(&vmlist_lock);
	for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
		if ((size + addr) < addr) {
			write_unlock(&vmlist_lock);
			kfree(area);
			return NULL;
		}
		if (size + addr < (unsigned long) tmp->addr)
			break;
		addr = tmp->size + (unsigned long) tmp->addr;
		if (addr > VMALLOC_END-size) {
			write_unlock(&vmlist_lock);
			kfree(area);
			return NULL;
		}
	}
	area->flags = flags;
	area->addr = (void *)addr;
	area->size = size;
	area->next = *p;
	*p = area;
	write_unlock(&vmlist_lock);
	return area;
}

    内核为自己保持一个虚存区间队列 vmlist,这是由一串 vm_struct 数据结构组成的一个单链队列。这里的 vm_structvmlist 都是由内核专用的。 vm_struct 从概念上说类似于供进程使用的 vm_area_struct,但要简单得多,定义于 include/linux/vmalloc.h 和 mm/vmalloc.c 中:

// include/linux/vmalloc.h
struct vm_struct {
	unsigned long flags;
	void * addr;
	unsigned long size;
	struct vm_struct * next;
};

    以前讲过,内核使用的系统空间虚拟地址与物理地址间存在着一种简单的映射关系,只要在物理地址上加上一个 3GB 的偏移量就得到了内核的虚拟地址。而变量 high_memory 标志着具体物理内存的上限所对应的虚拟地址,这是在系统初始化时设置好的。当内核需要一片虚存地址空间时,就从这个地址以下 8MB 处分配。为此,在 include/asm-i386/pgtable.h 中定义了 VMALLOC_START 等有关的常数:

// include/asm-i386/pgtable.h
/* Just any arbitrary offset to the start of the vmalloc VM area: the
 * current 8MB value just means that there will be a 8MB "hole" after the
 * physical memory until the kernel virtual memory starts.  That means that
 * any out-of-bounds memory accesses will hopefully be caught.
 * The vmalloc() routines leaves a hole of 4kB between each vmalloced
 * area for the same reason. ;)
 */
#define VMALLOC_OFFSET	(8*1024*1024)
#define VMALLOC_START	(((unsigned long) high_memory + 2*VMALLOC_OFFSET-1) & \
						~(VMALLOC_OFFSET-1))
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#define VMALLOC_END	(FIXADDR_START)

    源代码中的注解对于为什么要留一个 8MB 的空洞,以及在每次分配虚存区间时也要留下一个页面的空洞 (见 132 行) 解释得很清楚:是为了便于捕捉可能的越界访问。

    这里读者可能会有个问题,185 行的 if 语句检查的是当前的起始地址加上区间大小须小于下一个区间的起始地址,这是很好理解的。可是176行在区间大小上又加了一个页面作为空洞。这个空洞页面难道不可能与下一个区间的起始地址冲突吗?这里的奥妙在于185行判定的条件是 “<” 而不是 “<=” , 并且 sizeaddr 都是按页面边界对齐的,所以 185 行的条件已经隐含着其中有一个页面的空洞。从 get_vm_area() 成功返回时,就标志着所需要的一片虚存空间已经分配好了,从返回的数据结构可以得到这片空间的起始地址。下面就是建立映射的事了。

    宏定义 VMALLOC_VMADDR 我们已经在前面看到过了,实际上不做什么事情,只是类型转换。 函数 remap_area_pages() 的代码也在 arch/i386/mm/ioremap.c 中:

(3)remap_area_pages

// arch/i386/mm/ioremap.c
static int remap_area_pages(unsigned long address, unsigned long phys_addr,
				 unsigned long size, unsigned long flags)
{
	pgd_t * dir;
	unsigned long end = address + size;

	phys_addr -= address;
	dir = pgd_offset(&init_mm, address);
	flush_cache_all();
	if (address >= end)
		BUG();
	do {
		pmd_t *pmd;
		pmd = pmd_alloc_kernel(dir, address);
		if (!pmd)
			return -ENOMEM;
		if (remap_area_pmd(pmd, address, end - address,
					 phys_addr + address, flags))
			return -ENOMEM;
		address = (address + PGDIR_SIZE) & PGDIR_MASK;
		dir++;
	} while (address && (address < end));
	flush_tlb_all();
	return 0;
}

    我们讲过,每个进程的 task_struct 结构中都有一个指针指向 mm_strcuct 结构,从中可以找到相应的页面目录。但是,内核空间不属于任何一个特定的进程,所以单独设置了一个内核专用的 mm_strcuct , 称为 init_mm。当然,内核也没有代表它的 task_struct 结构,所以69行根据起始地址从 init_mm 中找到所属的目录项,然后就根据区间的大小走遍所有涉及的目录项。这里的68行看似奇怪。从物理地址中减去虚拟地址得出一个负的位移量,这个位移量在78〜79行又与虚拟地址相加,仍旧得到物理地址。 由于在循环中虚拟地址 address 在变 (见81行),物理地址也就相应而变。第75行的 pmd_alloc_kemel() 对于 i386 CPU 就是 pmd_alloc()

// include/asm-i386/pgalloc.h
#define pmd_alloc_kernel	pmd_alloc

// ==============================================================================
// include/asm-i386/pgalloc-2level.h
extern inline pmd_t * pmd_alloc(pgd_t *pgd, unsigned long address)
{
	if (!pgd)
		BUG();
	return (pmd_t *) pgd;
}

    可见,对于i386的二级页式映射,只是把页面目录项当成中间目录项而已,与“分配”实际上毫无关系。即使对于采用了物理地址扩充(PAE)的Pentium CPU,虽然实现三级映射,其作用也只是“找到”中间目录项而已,只有在中间目录项为空时才真的分配一个。

    这样,remap_area_pages() 中从73行开始的do_while循环,对涉及到的每个页面目录表项调用 remap_area_pmd( )。而 remap_area_pmd() 几乎完全一样,对涉及到的每个页面表 (对i386的二级映射, 每个中间目录项实际上就是一个页面表项,也可以理解为中间目录表的大小为1)

(3)remap_area_pte

// arch/i386/mm/ioremap.c
static inline void remap_area_pte(pte_t * pte, unsigned long address, unsigned long size,
	unsigned long phys_addr, unsigned long flags)
{
	unsigned long end;

	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	if (address >= end)
		BUG();
	do {
		if (!pte_none(*pte)) {
			printk("remap_area_pte: page already exists\n");
			BUG();
		}
		set_pte(pte, mk_pte_phys(phys_addr, __pgprot(_PAGE_PRESENT | _PAGE_RW | 
					_PAGE_DIRTY | _PAGE_DIRTY  | flags)));
		address += PAGE_SIZE;
		phys_addr += PAGE_SIZE;
		pte++;
	} while (address && (address < end));
}

    这里只是简单地在循环中设置页面表中所有涉及的页面表项(31行)。每个表项都被预设成 _PAGE_DIRTY 、_PAGE_DIRTY 和 _PAGE_PRESENT。

    在 kswapd 换出页面的情景中,我们已经看到 kswapd 定期地、循环地、依次地从 task 结构队列中找出占用内存页面最多的进程,然后就对该进程调用 swap_out_mm() 换出一些页面。而内核的 mm_struct 结构 init_mm 是单独的,从任何一个进程的 task 结构中都到达不了 init_mm 。 所以,kswapd 根本就看不到 init_mm 中的虚存区间,这些区间的页面就自然不会被换出而长驻于内存。

2、系统调用brk()

    尽管“可见度”不高,brk() 也许是最常使用的系统调用了,用户进程通过它向内核申请空间。人们常常并不意识到在调用 brk(),原因在于很少有人会直接使用系统调用 brk() 向系统申请空间,而总 是通过像 malloc() 一类的C语言库函数 (或语言成分,如C++中的new) 间接地用到 brk()。如果把 malloc() 想像成零售,brk() 则是批发。库函数 malloc() 为用户进程 (malloc本身就是该进程的一部分) 维持一个小仓库,当进程需要使用更多的内存空问时就向小仓库要,小仓库中存量不足时就通过 brk() 向内核批发。

    前面讲过,每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G 字节的范围里可以任意使用,因为虚存空间最终得映射到某个物理存储空间 (内存或磁盘空间) ,才真正可以使用,而这种映射的建立和管理则由内核处理。所谓向内核申请一块空间,是指请求内核分配一块虚存区间和相应的若干物理页面,并建立起映射关系。由于每个进程的虚存空间都很大 (3G),而实际需要使用的又很小,内核不可能在创建进程时就为整个虚存空间都分配好相应的物理空间并建立映射, 而只能是需要用多少才“分配”多少。

    那么,内核怎样管理每个进程的3G字节虚存空间呢?粗略地说,用户程序经过编译、连接形成的映象文件中有一个代码段和一个数据段 (包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,包括全局变量和说明为static的局部变量。这些空间是进程所必须的基本要求,所以内核在建立一个进程的运行映象时就分配好这些空间,包括虚存地址区间和物理页面,并建立好二者间的映射。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的(但可以扩充)。所不同的足,堆栈空间安置在虚存空间的顶部,运行时由顶向下延伸;代码段和数据段则在底部 (注意,不耍与X86系统结构中由段寄存器建立的“代码段”及“数据段”相混淆),在运行时并不向上伸展。而从数据段的顶部 end-data 到堆栈段地址的下沿这个中间区域则是一个巨大的空洞,这就是可以在运行时动态分配的空间。最初,这个动态分配空间是从进程的 end_data 开始的,这个地址为内核和进程所共知。以后,每次动态分配一块“内存”,这个边界就往上推进一段 距离,同时内核和进程都要记下当前的边界在哪里。在进程这一边由 malloc() 或类似的库函数管理, 而在内核中则将当前的边界记录在进程的 mm_struct 结构中。具体地说,mm_struct 结构中有一个成分 brk,表示动态分配区当前的底部。当个进程需要分配内存时,将要求的大小与其当前的动态分配区底部边界相加,所得的就是所要求的新边界,也就是 brk() 调用时的参数 brk。当内核能满足要求时, 系统调用 brk() 返回 0 ,此后新旧两个边界之间的虚存地址就都可以使用了。当内核发现无法满足要求 (例如物理空间已经分配完),或者发现新的边界已经过于逼近设于顶部的堆栈时,就拒绝分配而返回 -1 。

    系统调用 brk() 在内核中的实现为 sys_brk(),其代码在 mm/mmap.c 中。这个函数既可以用来分配空间,即把动态分配区底部的边界往上推;也可以用来释放,即归还空间。因此,它的代码也大致上可以分成两部分。

// mm/mmap.c
/*
 *  sys_brk() for the most part doesn't need the global kernel
 *  lock, except when an application is doing something nasty
 *  like trying to un-brk an area that has already been mapped
 *  to a regular file.  in this case, the unmapping will need
 *  to invoke file system routines that need the global lock.
 */
asmlinkage unsigned long sys_brk(unsigned long brk)
{
	unsigned long rlim, retval;
	unsigned long newbrk, oldbrk;
	struct mm_struct *mm = current->mm;

	down(&mm->mmap_sem);

	if (brk < mm->end_code)
		goto out;
	newbrk = PAGE_ALIGN(brk);
	oldbrk = PAGE_ALIGN(mm->brk);
	if (oldbrk == newbrk)
		goto set_brk;

	/* Always allow shrinking brk. */
	if (brk <= mm->brk) {
		if (!do_munmap(mm, newbrk, oldbrk-newbrk))
			goto set_brk;
		goto out;
	}

	/* Check against rlimit.. */
	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
	if (rlim < RLIM_INFINITY && brk - mm->start_data > rlim)
		goto out;

	/* Check against existing mmap mappings. */
	if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
		goto out;

	/* Check if we have enough memory.. */
	if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))
		goto out;

	/* Ok, looks good - let it rip. */
	if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
		goto out;
set_brk:
	mm->brk = brk;
out:
	retval = mm->brk;
	up(&mm->mmap_sem);
	return retval;
}

    参数 brk 表示所要求的新边界,这个边界不能低于代码段的终点,并且必须与页面大小对齐。如 果新边界低于老边界,那就不是申请分配空间,而是释放空间,所以通过 do_munmap() 解除一部分区间的映射。

    首先检查对进程的资源限制,如果所要求的新边界使数据段的大小超过了对当前进程的限制,就拒绝执行。此外,还要通过 find_vma_intersection(),检查所要求的那部分空间是否与已经存在的某一区间相冲突。

二、中断、异常和系统调用

符号

   
⇐ ⇒ ⇔ ⇆ ⇒ ⟺
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜㉝㉞㉟㊱㊲㊳㊴㊵㊶㊷㊸㊹㊺㊻㊼㊽㊾㊿
⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑿⒀⒁⒂⒃⒄⒅⒆⒇
➊➋➌➍➎➏➐➑➒➓⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴
⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵
ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ
ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ
🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩

123

y = x 2 + z 3 y = x^2 + z_3 y=x2+z3

y = x 2 + z 3 + a b + b a y = x^2 + z_3 + \frac {a}{b} + \sqrt[a]{b} y=x2+z3+ba+ab

y = x 2 + z 3 (1) y = x^2 + z^3 \tag{1} y=x2+z3(1)

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

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

相关文章

力扣 513. 找树左下角的值

题目来源&#xff1a;https://leetcode.cn/problems/find-bottom-left-tree-value/description/ C题解1&#xff1a;是寻找最底层最左边的节点&#xff0c;不是最底层的左子树节点&#xff01; &#xff01; 使用层序遍历&#xff0c;判断左右子树是不是叶子节点&#xff0c;进…

snmp_exporter监控交换机网络流量

一.背景与需求 最近收到机房账单多出了将近70M下行带宽&#xff0c;多交了8K多的费用&#xff0c;很是蛋疼。IDC机房使用每月保底带宽模式, 例如保底100M带宽/月&#xff0c;如果利用955计费方式&#xff0c;没超出100M则只收机柜和保底带宽的费用&#xff0c;如果超出1M则按照…

资本和技术的两个死结

技术与资本的两个死结 资本从一开始就俘获了技术 没有资本主义 就没有科学和技术 以前肯定是科技的崇拜者 现在多了一个视角 趣讲大白话&#xff1a;自己给自己挖坟 【趣讲信息科技210期】 **************************** 搞科学的看不起搞技术的 科学和技术确实是两码事 但最后…

LabVIEW里计算当前时间起前几个月的时间段

在查询数据时&#xff0c;可能会用到从某个时间开始&#xff0c;前几个月的数据&#xff0c;这时就需要计算出查询的历史时间&#xff0c;该如何计算呢&#xff1f;如图&#xff1a; 注意点&#xff1a; 1、日应该是当前日期的后一天。 2、当前的月数与历史的月数相等时的处置…

使用shiro框架进行认证拦截

01.需要添加依赖 在springboot项目中&#xff0c;使用shiro框架需要在pom.xml文件中去添加依赖&#xff1a; org.apache.shiro shiro-spring 1.4.1 02.shiro核心对象配置 这里需要配置一个配置类&#xff0c;使用的注解是Configuration&#xff0c;这表示修饰的类会有多个注…

Nacos集群版本升级2.2.3

官方传送 官方文档传送门 记录日期 2023-06-28 背景简介 当前Nacos版本为2.1.0&#xff0c;集群部署与官方文档一致&#xff0c;通过阿里云负载均衡SLB服务挂载3台ECS提供服务&#xff0c;如图&#xff1a; 近期有漏洞Nacos 内网集群Raft 反序列化漏洞披露。 影响版本 …

leecode-寻找重复数字

题目 题目 分析 哇哦&#xff0c;原来vector可以这样初始化&#xff0c;学到了&#xff01;&#xff01; 不初始化会直接报错嘞&#xff01; 代码 class Solution { public:int findDuplicate(vector<int>& nums) {vector<bool> v(nums.size(),false);//…

解决vue3+vite项目中引入mockjs失败的问题--无法找到模块“mockjs”的声明文件

看到上面报错&#xff0c;根据提示 修改声明方式 declare module mockjs 我们修改一下引入的声明&#xff0c;发现修改之后仍然报错&#xff1b; 解决方法&#xff1a; 需要在vite-env.d.ts文件中&#xff0c;添加 declare module mockjs&#xff0c;保存即可 然后就可以正常使…

CI/CD持续测试的未来...

如果您想知道为什么持续测试如此重要&#xff0c;请考虑以下几点&#xff1a;在过去&#xff0c;软件测试通常是在编写代码并发送给 QA 部门进行独立测试之后进行的。当发现错误时&#xff0c;代码将返回给开发人员进行更正。虽然这种测试方法有效&#xff0c;但非常耗时。如今…

Java的||或者是什么意思?

1.|| a||b 短路或 ab 全为 false 时&#xff0c;计算结果为 false&#xff0c;否则为 true。 2<1||3>4 false 具体解释就是&#xff1a;||表示或&#xff0c;只要有一个为true&#xff0c;结果就为true&#xff0c;两个为false结果才为false 2.&& a&&b 短…

【算法题】动态规划基础阶段之三步问题、 连续数列、按摩师

动态规划基础阶段 前言一、三步问题1.1、思路1.2、代码实现 二、 连续数列2.1、思路2.2、代码实现 三、按摩师3.1、思路3.2、代码实现 总结 前言 动态规划&#xff08;Dynamic Programming&#xff0c;简称 DP&#xff09;是一种解决多阶段决策过程最优化问题的方法。它是一种…

python spider 爬虫 之 scrapy框架 企业级

Scrapy定义 它是一个为了爬取网站的数据&#xff0c;提取结构性数据而编写的应用框架。可以应用在包括数据挖掘信息处理或存储历史数据等一系列的程序中 Scrapy 安装 pip install scrapy 安装过程中可能出现的错误&#xff1a; 报错1&#xff1a;building ‘twisted.test.r…

如何将银行卡拍照转为excel?

如果您手头上有很多的银行卡&#xff0c;并且需要录入到电脑&#xff0c;怎么办&#xff1f;手工一张一张、一个数字一个数字地敲键盘打字&#xff1f;太麻烦了&#xff0c;效率低&#xff0c;而且银行卡上的卡号数字可不短&#xff0c;传统的人工打字录入很容易出错&#xff0…

基于模糊控制算法的水位控制研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Qt 动态手势识别“手掌随动”+“握拳选择”

系列文章目录 通过Qt实现手势识别控制软件操作相关系列技术方案 &#xff08;一&#xff09;Qt 将某控件、图案绘制在最前面的方法&#xff0c;通过QGraphicsScene模块实现 &#xff08;二&#xff09;Qt QGraphicsScene模块实现圆点绘制在所有窗体的最前方&#xff0c;实现圆…

leetcode583. 两个字符串的删除操作(java-动态规划)

两个字符串的删除操作 leetcode583. 两个字符串的删除操作题目描述解题思路解法一 递归加缓存动态规划代码演示 动态规划专题 leetcode583. 两个字符串的删除操作 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/delete-…

算法基础修炼

算法基础 数据结构与算法 字符串匹配 KMP算法&#xff1a; 字符串算法之KMP&#xff08;字符串匹配&#xff09;kmp字符串匹配算法青萍之末的博客-CSDN博客 各大排序算法&#xff1a; 冒泡排序&#xff1a; 选择排序&#xff1a; 类似于冒泡算法&#xff0c;不断找到乱…

Android 编译Android7.0版本源码

编译Android7.0版本源码 前言正文一、前置条件二、编译准备三、下载编译源码1. 下载Repo2. 初始化仓库3. 同步远程代码4. 编译源码5. 启动模拟器 四、编译中的问题① Jack-Server② 本地化设置③ Communication error with Jack server (35), try jack-diagnose or see Jack se…

Pycharm中运行Allure报错不是内部或外部命令的解决方案

问题现象&#xff1a; Allure已安装并配置好系统环境变量&#xff1a; 1、Allure的下载地址&#xff1a;https://github.com/allure-framework/allure2/releases 2、下载后解压&#xff0c;将解压后的bin路径配置到环境变量中&#xff1a; allure-pytest插件也已安装好&#x…

Thinkphp5分页后携带参数进行跳转传递

问题&#xff1a; 我在tp框架中写了一个图书详情分页的&#xff0c;代码如下&#xff1a; public function verify_details(){$sell_order_numinput(sell_order_num);$resDB::table(verif)->where(["sell_order_num">$sell_order_num])->paginate(10);// 模…