211.xv6——3(page tables)
在本实验室中,您将探索页表并对其进行修改,以简化将数据从用户空间复制到内核空间的函数。
开始编码之前,请阅读xv6手册的第3章和相关文件:
- kernel/memlayout.h,它捕获了内存的布局。
- kernel/vm.c,其中包含大多数虚拟内存(VM)代码。
- kernel/kalloc.c,它包含分配和释放物理内存的代码。
1.kernel/memlayout.h
这段代码和注释描述了QEMU虚拟化环境中的物理内存布局,特别是RISC-V架构下的内存布局。它定义了各种硬件设备和内存区域的物理地址,以及内核如何使用这些内存区域。
// Physical memory layout// qemu -machine virt is set up like this, // based on qemu's hw/riscv/virt.c: // // 00001000 -- boot ROM, provided by qemu // 02000000 -- CLINT // 0C000000 -- PLIC // 10000000 -- uart0 // 10001000 -- virtio disk // 80000000 -- boot ROM jumps here in machine mode // -kernel loads the kernel here // unused RAM after 80000000.// the kernel uses physical memory thus: // 80000000 -- entry.S, then kernel text and data // end -- start of kernel page allocation area // PHYSTOP -- end RAM used by the kernel// qemu puts UART registers here in physical memory. #define UART0 0x10000000L #define UART0_IRQ 10// virtio mmio interface #define VIRTIO0 0x10001000 #define VIRTIO0_IRQ 1// local interrupt controller, which contains the timer. #define CLINT 0x2000000L #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid)) #define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.// qemu puts programmable interrupt controller here. #define PLIC 0x0c000000L #define PLIC_PRIORITY (PLIC + 0x0) #define PLIC_PENDING (PLIC + 0x1000) #define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100) #define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100) #define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000) #define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000) #define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000) #define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)// the kernel expects there to be RAM // for use by the kernel and user pages // from physical address 0x80000000 to PHYSTOP. #define KERNBASE 0x80000000L #define PHYSTOP (KERNBASE + 128*1024*1024)// map the trampoline page to the highest address, // in both user and kernel space. #define TRAMPOLINE (MAXVA - PGSIZE)// map kernel stacks beneath the trampoline, // each surrounded by invalid guard pages. #define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE)// User memory layout. // Address zero first: // text // original data and bss // fixed-size stack // expandable heap // ... // TRAPFRAME (p->trapframe, used by the trampoline) // TRAMPOLINE (the same page as in the kernel) #define TRAPFRAME (TRAMPOLINE - PGSIZE)
QEMU虚拟机中的物理内存布局
QEMU模拟的机器
virt
的内存布局如下:
- 0x00001000 - 启动ROM,由QEMU提供。
- 0x02000000 - CLINT (Core Local Interruptor),负责管理本地中断,包括定时器中断。
- 0x0C000000 - PLIC (Platform-Level Interrupt Controller),负责处理外部中断。
- 0x10000000 - uart0,串口控制器。
- 0x10001000 - virtio磁盘接口。
- 0x80000000 - 启动ROM会在机器模式下跳转到这里,内核也会加载到这里。
- 0x80000000 以后的内存区域为内核和用户空间的使用。
内核物理内存使用情况
- 0x80000000 - 内核的入口点,包含
entry.S
,以及内核的代码和数据。- end - 内核页分配区域的开始。
- PHYSTOP - 内核使用的内存结束位置。
硬件设备的地址定义
以下宏定义了各个硬件设备在物理内存中的地址和中断号:
UART0 和 UART0_IRQ:
#define UART0 0x10000000L #define UART0_IRQ 10
Virtio磁盘接口:
#define VIRTIO0 0x10001000 #define VIRTIO0_IRQ 1
CLINT 和相关寄存器地址:
#define CLINT 0x2000000L #define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid)) #define CLINT_MTIME (CLINT + 0xBFF8) // 启动以来的时钟周期数
PLIC 和相关寄存器地址:
#define PLIC 0x0c000000L #define PLIC_PRIORITY (PLIC + 0x0) #define PLIC_PENDING (PLIC + 0x1000) #define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100) #define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100) #define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000) #define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000) #define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000) #define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)
内核内存布局
KERNBASE 和 PHYSTOP:
#define KERNBASE 0x80000000L #define PHYSTOP (KERNBASE + 128*1024*1024) // 内核使用的内存大小为128MB
TRAMPOLINE:
#define TRAMPOLINE (MAXVA - PGSIZE)
内核栈的地址计算:
#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE)
用户内存布局
用户地址空间从零地址开始,包含以下部分:
- 文本段
- 原始数据段和BSS段
- 固定大小的栈
- 可扩展的堆
- TRAPFRAME:
#define TRAPFRAME (TRAMPOLINE - PGSIZE)
TRAMPOLINE:与内核中的相同页面。
2. kernel/vm.c
这段代码实现了一个基于RISC-V架构的内核页表管理模块,主要用于管理虚拟内存与物理内存之间的映射。下面是对这段代码中各个函数和宏定义的详细解释:
#include "param.h" #include "types.h" #include "memlayout.h" #include "elf.h" #include "riscv.h" #include "defs.h" #include "fs.h"/**该函数创建一个直接映射的内核页表,并将硬件设备、内核代码和数据段、以及跳板页(trampoline)映射 *到内核页表中。*/ pagetable_t kernel_pagetable;extern char etext[]; // kernel.ld sets this to end of kernel code.extern char trampoline[]; // trampoline.S//该函数创建一个直接映射的内核页表,并将硬件设备、 //内核代码和数据段、以及跳板页(trampoline)映射到内核页表中。 void kvminit() {kernel_pagetable = (pagetable_t) kalloc();memset(kernel_pagetable, 0, PGSIZE);// uart registerskvmmap(UART0, UART0, PGSIZE, PTE_R | PTE_W);// virtio mmio disk interfacekvmmap(VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);// CLINTkvmmap(CLINT, CLINT, 0x10000, PTE_R | PTE_W);// PLICkvmmap(PLIC, PLIC, 0x400000, PTE_R | PTE_W);// map kernel text executable and read-only.kvmmap(KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);// map kernel data and the physical RAM we'll make use of.kvmmap((uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);// map the trampoline for trap entry/exit to// the highest virtual address in the kernel.kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X); }//该函数切换硬件页表寄存器到内核页表,并启用分页。 void kvminithart() {w_satp(MAKE_SATP(kernel_pagetable));sfence_vma(); }//该函数在页表中查找虚拟地址va对应的页表项(PTE),如果alloc非零,则在需要时分配页表页 pte_t * walk(pagetable_t pagetable, uint64 va, int alloc) {if(va >= MAXVA)panic("walk");for(int level = 2; level > 0; level--) {pte_t *pte = &pagetable[PX(level, va)];if(*pte & PTE_V) {pagetable = (pagetable_t)PTE2PA(*pte);} else {if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)return 0;memset(pagetable, 0, PGSIZE);*pte = PA2PTE(pagetable) | PTE_V;}}return &pagetable[PX(0, va)]; }//该函数查找虚拟地址va对应的物理地址,如果未映射则返回0。只能用于查找用户页。 uint64 walkaddr(pagetable_t pagetable, uint64 va) {pte_t *pte;uint64 pa;if(va >= MAXVA)return 0;pte = walk(pagetable, va, 0);if(pte == 0)return 0;if((*pte & PTE_V) == 0)return 0;if((*pte & PTE_U) == 0)return 0;pa = PTE2PA(*pte);return pa; }//该函数在内核页表中添加一个映射。在启动时使用,不刷新TLB或启用分页。 void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm) {if(mappages(kernel_pagetable, va, sz, pa, perm) != 0)panic("kvmmap"); }//该函数将内核虚拟地址转换为物理地址。假设va是页对齐的。 uint64 kvmpa(uint64 va) {uint64 off = va % PGSIZE;pte_t *pte;uint64 pa;pte = walk(kernel_pagetable, va, 0);if(pte == 0)panic("kvmpa");if((*pte & PTE_V) == 0)panic("kvmpa");pa = PTE2PA(*pte);return pa+off; }//这段代码实现了mappages函数,用于创建页表条目(PTE),将虚拟地址映射到物理地址。 //函数接受页表指针、虚拟地址、映射大小、物理地址和权限作为参数,并返回成功或失败的状态。 int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) {uint64 a, last;pte_t *pte;a = PGROUNDDOWN(va); // 向下对齐虚拟地址到页边界last = PGROUNDDOWN(va + size - 1); // 向下对齐最后一个虚拟地址到页边界for(;;){if((pte = walk(pagetable, a, 1)) == 0) // 获取或创建对应虚拟地址的PTEreturn -1;if(*pte & PTE_V) // 检查PTE是否有效,防止重复映射panic("remap");*pte = PA2PTE(pa) | perm | PTE_V; // 设置PTE,映射到物理地址并赋予权限if(a == last) // 如果已经处理完最后一个页break;a += PGSIZE; // 前进到下一个页pa += PGSIZE; // 更新物理地址}return 0; }//这段代码实现了uvmunmap函数,用于取消虚拟地址到物理地址的映射。 //函数接受页表指针、虚拟地址、要取消映射的页数和一个标志位作为参数, //标志位决定是否释放物理内存。 void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) {uint64 a;pte_t *pte;if((va % PGSIZE) != 0) // 检查虚拟地址是否对齐到页边界panic("uvmunmap: not aligned");for(a = va; a < va + npages * PGSIZE; a += PGSIZE){ // 遍历每一个页if((pte = walk(pagetable, a, 0)) == 0) // 获取对应虚拟地址的PTEpanic("uvmunmap: walk");if((*pte & PTE_V) == 0) // 检查PTE是否有效panic("uvmunmap: not mapped");if(PTE_FLAGS(*pte) == PTE_V) // 检查PTE是否为叶子节点panic("uvmunmap: not a leaf");if(do_free){ // 如果需要释放物理内存uint64 pa = PTE2PA(*pte);kfree((void*)pa); // 释放物理内存}*pte = 0; // 取消映射} }//用于创建一个空的用户页表。函数通过分配一页物理内存来存储页表, //并初始化该页表。如果内存分配失败,函数返回0 pagetable_t uvmcreate() {pagetable_t pagetable;// 分配一页物理内存用于存储页表pagetable = (pagetable_t) kalloc();if(pagetable == 0)return 0;memset(pagetable, 0, PGSIZE);return pagetable; }//用于将用户初始化代码加载到页表的地址0处。此函数通常在创建第一个 //用户进程时使用。代码执行了内存分配、内存映射和数据拷贝的操作 void uvminit(pagetable_t pagetable, uchar *src, uint sz) {char *mem;// 检查大小是否超过一页if(sz >= PGSIZE)panic("inituvm: more than a page");// 分配一页物理内存并清零mem = kalloc();memset(mem, 0, PGSIZE);// 将分配的物理内存映射到虚拟地址0mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U);// 将初始化代码拷贝到分配的物理内存memmove(mem, src, sz); }//用于为进程分配页表条目和物理内存,以将进程的内存从oldsz //增长到newsz。如果分配成功,函数返回新大小;如果出错,则返回0 uint64 uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) {char *mem;uint64 a;// 如果newsz小于oldsz,不进行任何操作,返回oldszif(newsz < oldsz)return oldsz;// 将oldsz向上取整到页边界oldsz = PGROUNDUP(oldsz);// 从oldsz增长到newsz,按页分配内存for(a = oldsz; a < newsz; a += PGSIZE){// 分配一页物理内存mem = kalloc();if(mem == 0){// 分配失败,释放之前分配的内存uvmdealloc(pagetable, a, oldsz);return 0;}// 清零已分配的内存memset(mem, 0, PGSIZE);// 将物理内存映射到虚拟地址if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){// 映射失败,释放已分配的内存kfree(mem);uvmdealloc(pagetable, a, oldsz);return 0;}}// 分配成功,返回newszreturn newsz; }//用于释放进程的用户页,使其内存大小从oldsz减少到newsz。无论oldsz是否 //大于实际进程大小,或者newsz是否小于oldsz,函数都会按需要进行内存释放, //并返回新的进程大小。 uint64 uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) {// 如果newsz大于等于oldsz,不需要做任何操作,返回oldszif(newsz >= oldsz)return oldsz;// 如果newsz向上取整后的页数小于oldsz向上取整后的页数,说明需要释放一些页if(PGROUNDUP(newsz) < PGROUNDUP(oldsz)){int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);}// 返回新的进程大小newszreturn newsz; }//用于递归地释放页表页。该函数假定所有叶子映射(即实际映射到物理内存的页) //已经被移除,因此它只需要处理非叶子页表条目。 void freewalk(pagetable_t pagetable) {// 页表中有2^9 = 512个页表条目for(int i = 0; i < 512; i++){pte_t pte = pagetable[i];// 如果当前条目有效且不是叶子条目if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){// 该PTE指向一个更低级别的页表uint64 child = PTE2PA(pte);// 递归释放更低级别的页表freewalk((pagetable_t)child);// 将当前条目清零pagetable[i] = 0;} else if(pte & PTE_V){// 如果当前条目是叶子条目,抛出一个错误panic("freewalk: leaf");}}// 释放当前页表kfree((void*)pagetable); }//用于释放用户内存页,然后释放页表页 void uvmfree(pagetable_t pagetable, uint64 sz) {if(sz > 0)uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 1);freewalk(pagetable); }//将父进程的内存复制到子进程的页表中,包括复制页表项和物理内存。 //它在成功时返回0,在失败时返回-1,并在失败时释放已经分配的所有资源以避免内存泄漏 int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) {pte_t *pte;uint64 pa, i;uint flags;char *mem;for(i = 0; i < sz; i += PGSIZE){if((pte = walk(old, i, 0)) == 0)panic("uvmcopy: pte should exist");if((*pte & PTE_V) == 0)panic("uvmcopy: page not present");pa = PTE2PA(*pte);flags = PTE_FLAGS(*pte);if((mem = kalloc()) == 0)goto err;memmove(mem, (char*)pa, PGSIZE);if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){kfree(mem);goto err;}}return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1; }// 将一个页表项标记为用户不可访问。 // 在执行程序加载时用于用户栈的保护页。 void uvmclear(pagetable_t pagetable, uint64 va) {pte_t *pte;// 查找给定页表(pagetable)中虚拟地址 'va' 对应的页表项(PTE)。pte = walk(pagetable, va, 0);// 如果找不到页表项(pte为NULL),则发生panic,表示出现了错误。if(pte == 0)panic("uvmclear");// 清除页表项中的用户访问位(PTE_U)。// 这样标记该页为用户不可访问。*pte &= ~PTE_U; }// 从内核空间复制到用户空间。 // 将长度为len的数据从src复制到给定页表中虚拟地址dstva处。 // 成功时返回0,出错时返回-1。 int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) {uint64 n, va0, pa0;while(len > 0){// 对目标虚拟地址进行页面对齐。va0 = PGROUNDDOWN(dstva);// 获取va0对应的物理地址。pa0 = walkaddr(pagetable, va0);// 如果物理地址为0,则返回-1,表示出错。if(pa0 == 0)return -1;// 计算当前页内剩余空间长度。n = PGSIZE - (dstva - va0);// 如果剩余长度大于要复制的数据长度,取要复制的数据长度。if(n > len)n = len;// 将数据从src复制到物理地址pa0 + (dstva - va0)处,长度为n。memmove((void *)(pa0 + (dstva - va0)), src, n);// 更新剩余数据长度、源地址和目标虚拟地址。len -= n;src += n;dstva = va0 + PGSIZE;}return 0; }// 从用户空间复制到内核空间。 // 将长度为len的数据从给定页表中虚拟地址srcva处复制到目标地址dst。 // 成功时返回0,出错时返回-1。 int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) {uint64 n, va0, pa0;while(len > 0){// 对源虚拟地址进行页面对齐。va0 = PGROUNDDOWN(srcva);// 获取va0对应的物理地址。pa0 = walkaddr(pagetable, va0);// 如果物理地址为0,则返回-1,表示出错。if(pa0 == 0)return -1;// 计算当前页内剩余空间长度。n = PGSIZE - (srcva - va0);// 如果剩余长度大于要复制的数据长度,取要复制的数据长度。if(n > len)n = len;// 将数据从物理地址pa0 + (srcva - va0)处复制到目标地址dst,长度为n。memmove(dst, (void *)(pa0 + (srcva - va0)), n);// 更新剩余数据长度、目标地址和源虚拟地址。len -= n;dst += n;srcva = va0 + PGSIZE;}return 0; }// 从用户空间复制空结尾字符串到内核空间。 // 从给定页表中虚拟地址srcva处复制最多max字节的数据到目标地址dst, // 直到遇到'\0'结束,或者达到max字节。 // 成功时返回0,出错时返回-1。 int copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) {uint64 n, va0, pa0;int got_null = 0; // 标记是否遇到了'\0'while(got_null == 0 && max > 0){// 对源虚拟地址进行页面对齐。va0 = PGROUNDDOWN(srcva);// 获取va0对应的物理地址。pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;// 计算当前页内剩余空间长度。n = PGSIZE - (srcva - va0);if(n > max)n = max;// 将物理地址转换为char指针,从中复制数据直到遇到'\0'或者达到max长度。char *p = (char *) (pa0 + (srcva - va0));while(n > 0){if(*p == '\0'){ // 如果遇到了'\0',复制结束。*dst = '\0';got_null = 1;break;} else { // 否则继续复制字符。*dst = *p;}--n;--max;p++;dst++;}srcva = va0 + PGSIZE; // 更新源虚拟地址为下一页的起始地址。}if(got_null){return 0; // 复制成功,返回0。} else {return -1; // 复制失败(未遇到'\0'但已达到max长度),返回-1。} }
3.kernel/kalloc.c
这段代码实现了一个物理内存分配器,用于用户进程、内核栈、页表页以及管道缓冲区。它主要负责分配和释放4096字节的页面(页)。
// Physical memory allocator, for user processes, // kernel stacks, page-table pages, // and pipe buffers. Allocates whole 4096-byte pages.#include "types.h" #include "param.h" #include "memlayout.h" #include "spinlock.h" #include "riscv.h" #include "defs.h"void freerange(void *pa_start, void *pa_end);extern char end[]; // first address after kernel.// defined by kernel.ld.//run结构体定义了一个单向链表节点,用于维护空闲物理内存页的链表。 struct run {struct run *next; };//kmem结构体包含一个自旋锁和一个空闲内存页链表的头指针, //用于实现线程安全的内存管理。 struct {struct spinlock lock;struct run *freelist; } kmem;//该函数初始化物理内存分配器。它首先初始化自旋锁,然后调用freerange函数, //将从内核结束地址(end)到物理内存顶部(PHYSTOP)之间的内存页加入空闲列表。 void kinit() {initlock(&kmem.lock, "kmem");freerange(end, (void*)PHYSTOP); }//该函数将从pa_start到pa_end范围内的内存页加入空闲列表。 //它首先将pa_start地址向上对齐到页边界,然后逐页调用kfree函数释放这些内存页。 void freerange(void *pa_start, void *pa_end) {char *p;p = (char*)PGROUNDUP((uint64)pa_start);for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)kfree(p); }//该函数释放一个物理内存页,将其加入空闲列表。它首先检查pa是否是页对齐的, //并且在合法范围内。然后用垃圾数据填充该页,防止悬空引用。最后将该页加入空闲列表, //使用自旋锁确保线程安全。 void kfree(void *pa) {struct run *r;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// Fill with junk to catch dangling refs.memset(pa, 1, PGSIZE);r = (struct run*)pa;acquire(&kmem.lock);r->next = kmem.freelist;kmem.freelist = r;release(&kmem.lock); }//该函数分配一个物理内存页。它从空闲列表中取出一个页, //如果成功分配,则用垃圾数据填充该页。返回页的地址,如果分配失败则返回0。 void * kalloc(void) {struct run *r;acquire(&kmem.lock);r = kmem.freelist;if(r)kmem.freelist = r->next;release(&kmem.lock);if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r; }
相关文章:
211.xv6——3(page tables)
在本实验室中,您将探索页表并对其进行修改,以简化将数据从用户空间复制到内核空间的函数。 开始编码之前,请阅读xv6手册的第3章和相关文件: kernel/memlayout.h,它捕获了内存的布局。kernel/vm.c,其中包含…...
yum使用报错:ImportError: /lib64/libxml2.so.2: file too short
系统版本:Rocky 8.10 报错信息: Traceback (most recent call last):File "/usr/lib64/python3.6/site-packages/libdnf/error.py", line 14, in swig_import_helperreturn importlib.import_module(mname)File "/usr/lib64/python3.6/i…...

【Android面试八股文】你是怎么保证Android设备的时间与服务器时间同步的?(使用NTP和TrueTime方案)
文章目录 一、网络时间协议(NTP)二、使用网络时间协议(NTP)2.1 使用系统提供的 NTP 服务器2.2 使用TrueTime2.2.1 引入TrueTime库2.2.2 初始化 TrueTime2.2.3 用法2.2.4 使用 TrueTime 获取时间2.2.4 自动更新时间2.2.5 注意事项二. 使用 HTTP 请求获取服务器时间2.1. 发送…...

解决Python爬虫开发中的数据输出问题:确保正确生成CSV文件
引言 在大数据时代,爬虫技术成为获取和分析网络数据的重要工具。然而,许多开发者在使用Python编写爬虫时,常常遇到数据输出问题,尤其是在生成CSV文件时出错。本文将详细介绍如何解决这些问题,并提供使用代理IP和多线程…...

SCI一区TOP|徒步优化算法(HOA)原理及实现【免费获取Matlab代码】
目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年,SO Oladejo受到徒步旅行启发,提出了徒步优化算法(Hiking Optimization Algorithm, HOA)。 2.算法原理 2.1算法思想 HOA灵感来自于…...
Android的activity广播无法接收,提示process gone or crashing原因有可能是那些?
当Android的Activity无法接收广播,并且收到“process gone or crashing”的提示时,可能的原因有多种。以下是一些常见的原因和排查步骤: Activity生命周期问题: 如果Activity在广播发送之前就已经被销毁(例如…...
如何将等保2.0的要求融入日常安全运维实践中?
等保2.0的基本要求 等保2.0是中国网络安全领域的基本国策和基本制度,它要求网络运营商按照网络安全等级保护制度的要求,履行相关的安全保护义务。等保2.0的实施得到了《中华人民共和国网络安全法》等法律法规的支持,要求相关行业和单位必须按…...

51单片机嵌入式开发:STC89C52环境配置到点亮LED
STC89C52环境配置到点亮LED 1 环境配置1.1 硬件环境1.2 编译环境1.3 烧录环境 2 工程配置2.1 工程框架2.2 工程创建2.3 参数配置 3 点亮一个LED3.1 原理图解读3.2 代码配置3.3 演示 4 总结 1 环境配置 1.1 硬件环境 硬件环境采用“华晴电子”的MINIEL-89C开发板,这…...
源代码加密:保护你的数字宝藏
在当今日益复杂的网络安全环境中,源代码作为企业的核心知识产权,其安全保护显得尤为重要。传统的源代码加密方法虽能提供一定的保护,但在应对新型威胁和复杂场景时,往往显得力不从心。而SDC沙盒技术的出现,为源代码加密…...
Jackson库使用教程
1. Jackson概述 定义: Jackson是一个基于Java的开源JSON解析工具,用于Java对象与JSON数据的互相转换。示例JSON:{"author": "一路向北_Coding","age": 20,"hobbies": ["coding", "leetcode", "r…...
汉王、绘王签字版调用封装
说明 需要配合汉王或绘王签字版驱动以及对应的sdk服务使用 constants.js //汉王、绘王sdk websocket连接地址 export const WS_URLS {1:ws://127.0.0.1:29999, //汉王2:ws://127.0.0.1:7181, }export const COMMAND1 {1: {HWPenSign: "HWStartSign",nLogo: "…...

如何在TikTok上获得更多观看量:12个流量秘诀
TikTok作为热门海外社媒,在跨境出海行业中成为新兴的推广渠道,但你知道如何让你的TikTok赢得更多关注次数吗?如果您正在寻找增加 TikTok 观看次数的方法,接下来这12种策略,你需要一一做好! 1. 在内容中添加…...

vue模板语法v-html
模板语法v-html vue使用一种基于HTML的模板语法,使我们能够声明式的将其组件实例的数据绑定到呈现的DOM上,所有的vue模板都是语法层面的HTML,可以被符合规范的浏览器和HTML解释器解析。 一.文本插值 最基本的数据绑定形式是文本插值&#…...

13 Redis-- 数据一致性模型、MySQL 和 Redis 的数据一致性
数据一致性模型 根据一致性的强弱分类,可以将一致性模型按以下顺序排列: 强一致性 > 最终一致性 > 弱一致性 数据一致性模型一般用于分布式系统中,目的是定义多个节点间的同步规范。 在这里,我们将其引入数据库和缓存组…...

启动Nuxt-hub-starter: Failed to initialize wrangler bindings proxy write EOF
重新安装 node.js 这样做可以确保下载到了适合的 Windows 框架、Chocolatey(一款Windows包管理工具)、Python 等资源。 这个错误与Node版本、pnpm/yarn 的版本无关! Node.js — Download Node.js (nodejs.org)...

技术驱动旅游创新!深度解析景区导览小程序的地图渲染与AR导航技术
随着现代生活节奏的加快,人们在外出旅游时更倾向于轻便出行,携带导览地图已成为过去。然而,面对景区广阔的面积和众多景点,游客常常感到迷茫,难以快速定位到自己所需的地点。景区导览小程序让游客只需搜索景区名称&…...

二叉树之遍历
二叉树之遍历 二叉树遍历遍历分类前序遍历流程描述代码实现 中序遍历流程描述代码实现 后序遍历流程描述代码实现 层次遍历流程描述代码实现 总结 二叉树遍历 遍历分类 遍历二叉树的思路有 4 种,分别是: 前序遍历二叉树,有递归和非递归两种…...
【经验贴】如何做好自己的职业规划(技术转项目经理)
我有几个问题想问大家 第一,你了解自己吗?你知道自己想要是什么吗?你了解自己的优势劣势吗? 第二,你了解这个行业吗?你知道这个行业是如何发展起来的吗?你了解这个行业的背景吗?你…...
【笔记】字符串相似度代码分享
目录 一、算法介绍1、算法1)基于编辑距离2)基于标记3)基于序列4)基于压缩5)基于发音6)简单算法 2、安装 二、代码demo1、Hamming 距离2、Levenshtein 距离3、Damerau-Levenshtein距离4、Jaro 相似度5、Jaro…...

AI墓地:738个倒闭AI项目的启示
近年来,人工智能技术迅猛发展,然而,不少AI项目却在市场上悄然消失。根据AI工具聚合网站“DANG”的统计,截至2024年6月,共有738个AI项目停运或停止维护。本文将探讨这些AI项目失败的原因,并分析当前AI初创企…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...

归并排序:分治思想的高效排序
目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法,由约翰冯诺伊曼在1945年提出。其核心思想包括: 分割(Divide):将待排序数组递归地分成两个子…...

DeepSeek越强,Kimi越慌?
被DeepSeek吊打的Kimi,还有多少人在用? 去年,月之暗面创始人杨植麟别提有多风光了。90后清华学霸,国产大模型六小虎之一,手握十几亿美金的融资。旗下的AI助手Kimi烧钱如流水,单月光是投流就花费2个亿。 疯…...