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的内存布局如下:

  1. 0x00001000 - 启动ROM,由QEMU提供。
  2. 0x02000000 - CLINT (Core Local Interruptor),负责管理本地中断,包括定时器中断。
  3. 0x0C000000 - PLIC (Platform-Level Interrupt Controller),负责处理外部中断。
  4. 0x10000000 - uart0,串口控制器。
  5. 0x10001000 - virtio磁盘接口。
  6. 0x80000000 - 启动ROM会在机器模式下跳转到这里,内核也会加载到这里。
  7. 0x80000000 以后的内存区域为内核和用户空间的使用。

内核物理内存使用情况

  • 0x80000000 - 内核的入口点,包含entry.S,以及内核的代码和数据。
  • end - 内核页分配区域的开始。
  • PHYSTOP - 内核使用的内存结束位置。

硬件设备的地址定义

以下宏定义了各个硬件设备在物理内存中的地址和中断号:

UART0UART0_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)

内核内存布局

  • KERNBASEPHYSTOP

    #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;
}

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

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

相关文章

代谢组数据分析(十二):岭回归、Lasso回归、弹性网络回归构建预测模型

欢迎大家关注全网生信学习者系列: WX公zhong号:生信学习者Xiao hong书:生信学习者知hu:生信学习者CDSN:生信学习者2介绍 在代谢物预测模型的构建中,我们采用了三种主流的回归分析方法:岭回归、Lasso回归以及弹性网络回归。这三种方法各有其独特的原理和适用场景,因此在…

WPS操作技巧:制作可以打对勾的方框,只需简单几步!沈阳wps办公软件培训

日常工作中&#xff0c;我们经常需要在表格中添加复选框&#xff0c;比如【性别选择】、【任务完成状态】等等&#xff0c;通过打对勾来确定状态。今天就分别从WPS的Excel表格和Word文档2种场景&#xff0c;介绍制作可以打对勾的复选框的方法技巧&#xff0c;掌握技巧&#xff…

25、PHP 实现两个链表的第一个公共结点(含源码)

题目&#xff1a; PHP 实现两个链表的第一个公共结点 描述&#xff1a; 输入两个链表&#xff0c;找出它们的第一个公共结点。 <?php /*class ListNode{var $val;var $next NULL;function __construct($x){$this->val $x;} }*/ function FindFirstCommonNode($pHead…

构建zdppy docker镜像

拉取镜像 docker pull python:3.8-alpine3.19创建容器 docker run -itd --name zdppy python:3.8-alpine3.19 sh进入容器 docker exec -it zdppy sh配置pip国内源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple提交容器为镜像 docker commit…

游戏AI的创造思路-技术基础-计算机视觉

让游戏的AI具备“眼睛”和“视觉”&#xff0c;就是通过计算机视觉的方法进行的。现在&#xff0c;越来越多的游戏&#xff0c;特别是动捕类游戏都在使用这个方法。当然&#xff0c;计算机视觉不仅仅用于游戏&#xff0c;越来越多的应用使用到这个技术 目录 1. 定义 2. 发展历…

spring 枚举、策略模式、InitializingBean初使化组合使用示例

实现一个简单的文本处理系统。 在这个系统中&#xff0c;我们将定义不同类型的文本处理策略&#xff0c;比如大小写转换、添加前缀后缀等&#xff0c;并使用工厂模式来管理这些策略。 1 定义一个枚举来标识不同的文本处理类型 public enum TextProcessTypeEnum {UPPER_CASE,LO…

腾讯混元文生图开源模型推出小显存版本,6G显存即可运行,并开源caption模型

7月4日&#xff0c;腾讯混元文生图大模型&#xff08;混元DiT&#xff09;宣布开源小显存版本&#xff0c;仅需6G显存即可运行&#xff0c;对使用个人电脑本地部署的开发者十分友好&#xff0c;该版本与LoRA、ControlNet等插件&#xff0c;都已适配至Diffusers库&#xff1b;并…

探索 Apache Paimon 在阿里智能引擎的应用场景

摘要&#xff1a;本文整理自Apache Yarn && Flink Contributor&#xff0c;阿里巴巴智能引擎事业部技术专家王伟骏&#xff08;鸿历&#xff09;老师在 5月16日 Streaming Lakehouse Meetup Online 上的分享。内容主要分为以下三个部分&#xff1a; 一、 阿里智能引擎…

【LeetCode】全排列

目录 一、题目二、解法完整代码 一、题目 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] …

LVS+Nginx高可用集群--基础篇

1.集群概述 单体部署&#xff1a; 可以将上面内容分别部署在不同的服务器上。 单体架构的优点&#xff1a; 小团队成型就可完成开发&#xff0c;测试&#xff0c;上线 迭代周期短&#xff0c;速度快 打包方便&#xff0c;运维简单 单体架构的挑战&#xff1a;单节点宕机造成…

DVWA sql手注学习(巨详细不含sqlmap)

这篇文章主要记录学习sql注入的过程中遇到的问题已经一点学习感悟&#xff0c;过程图片会比较多&#xff0c;比较基础和详细&#xff0c;不存在看不懂哪一步的过程 文章目录 靶场介绍SQL注入 lowSQL注入 MediumSQL注入 HighSQL注入 Impossible 靶场介绍 DVWA&#xff08;Damn…

必备的 Adobe XD 辅助工具

想要高效便捷的使用 Adobe XD&#xff0c; Adobe XD 插件是必不可少的&#xff0c; Adobe XD 的插件非常多&#xff0c;但 90%都是英文&#xff0c;并且良莠不齐。在这儿挑选 9 个好用的 Adobe XD 插件给大家&#xff0c;这里是我整理的一些实用 Adobe XD 插件&#xff0c;让你…

大屏开发系列——Echarts的基础使用

本文为个人近期学习总结&#xff0c;若有错误之处&#xff0c;欢迎指出&#xff01; Echarts在vue2中的基础使用 一、简单介绍二、基本使用&#xff08;vue2中&#xff09;1.npm安装2.main.js引入3.使用步骤(1)准备带有宽高的DOM容器&#xff1b;(2)初始化echarts实例&#xff…

gcc: warning: -Wunused-function;加了选项,为什么就不报警告呢?

文章目录 问题clang的编译而使用gcc是就不报问题分析原因如果是非static的函数问题 下面这个代码段,其中这个函数hton_ext_2byte,在整个程序里就没有使用。 static inline uint16_t hton_ext_2byte(uint8_t **p) {uint16_t v;******return v;

PHP宜邦家政服务管理系统-计算机毕业设计源码04426

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2开发现状 1.3论文结构与章节安排 2 宜邦家政服务管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用…

国标GB28181视频汇聚平台LntonCVS视频监控安防平台与国标协议对接解决方案

应急管理部门以“以信息化推动应急管理能力现代化”为总体目标&#xff0c;加快现代信息技术与应急管理业务深度融合&#xff0c;全面支持现代应急管理体系建设&#xff0c;这不仅是国家加强和改进应急管理工作的关键举措&#xff0c;也是应对日益严峻的应急管理形势和满足公众…

微信小程序的运行机制与更新机制

1. 小程序运行机制 1.1. 冷启动与热启动 冷启动为用户第一次打开小程序时&#xff0c;因为之前没有打开过&#xff0c;这是第一种冷启动的情兑。第二种情况为虽然之前用户打开过&#xff0c;但是小程序被用户主动的销毁过&#xff0c;这种情况下我们再次打开小程序&#xff0…

【PALM、WRF-LES】微尺度气象数值模拟—大涡模拟技术

针对微尺度气象的复杂性&#xff0c;大涡模拟&#xff08;LES&#xff09;提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟&#xff0c;这些过程往往与天气模式、地形影响和人为因素如城市布局紧密相关。在这种规模上&#xff0c;传统的气象模…

doc文档下载

目录 下载 安装谷歌浏览器(chrome)Microsoft Edge浏览器 常见问题 下载 见邮件附件 安装 谷歌浏览器(chrome) 打开浏览器&#xff0c;地址栏输入&#xff1a;chrome://extensions/ 右上角打开开发者模式 点击如上图左上角的加载已解压的拓展程序&#xff0c;并选择刚刚解压…

安卓应用开发学习:通过腾讯地图SDK实现定位功能

一、引言 这几天有些忙&#xff0c;耽误了写日志&#xff0c;但我的学习始终没有落下&#xff0c;有空我就会研究《 Android App 开发进阶与项目实战》一书中定位导航方面的内容。在我的手机上先后实现了“获取经纬度及地理位置描述信息”和“获取导航卫星信息”功能后&#x…