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 registers
  kvmmap(UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  kvmmap(VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // CLINT
  kvmmap(CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  kvmmap(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)  // 获取或创建对应虚拟地址的PTE
      return -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)  // 获取对应虚拟地址的PTE
      panic("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);

  // 将分配的物理内存映射到虚拟地址0
  mappages(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,不进行任何操作,返回oldsz
  if(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;
    }
  }
  // 分配成功,返回newsz
  return newsz;
}


//用于释放进程的用户页,使其内存大小从oldsz减少到newsz。无论oldsz是否
//大于实际进程大小,或者newsz是否小于oldsz,函数都会按需要进行内存释放,
//并返回新的进程大小。
uint64
uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)
{
  // 如果newsz大于等于oldsz,不需要做任何操作,返回oldsz
  if(newsz >= oldsz)
    return oldsz;

  // 如果newsz向上取整后的页数小于oldsz向上取整后的页数,说明需要释放一些页
  if(PGROUNDUP(newsz) < PGROUNDUP(oldsz)){
    int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
    uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);
  }

  // 返回新的进程大小newsz
  return 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 junk
  return (void*)r;
}

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

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

相关文章

Redis 典型应用——分布式锁

一、什么是分布式锁 在一个分布式的系统中&#xff0c;也会涉及到多个节点访问同一个公共资源的情况&#xff0c;此时就需要通过锁来做互斥控制&#xff0c;避免出现类似于 "线程安全" 的问题&#xff1b; 而 Java 中的 synchronized&#xff0c;只能在当前进程中生…

线上问题定位分析宝典——Linux中定位JVM问题常用命令

查询Java进程ID #ps axu | grep java #ps elf | grep java查看机器负载及CPU信息 #top -p 1(进程ID) #top (查看所有进程)获取CPU飙升线程堆栈 1. top -c 找到CPU飙升进程ID&#xff1b; 2. top -Hbp 9702(替换成进程ID) 找到CPU飙升线程ID&#xff1b; 3. $ printf &quo…

ubuntu20.04配置调试工具

1.准备工作&#xff1a;安装g或者gdb sudo apt updatesudo apt install gg --versionsudo apt install gdbgdb --version 2.配置环境 2.1在本地新建一个main.cpp #include <iostream> #include <vector> #include <string>using namespace std;int main(…

【SpringBoot3学习 | 第2篇】SpringBoot3整合+SpringBoot3项目打包运行

文章目录 一. SpringBoot3 整合 SpringMVC1.1 配置静态资源位置1.2 自定义拦截器&#xff08;SpringMVC配置&#xff09; 二. SpringBoot3 整合 Druid 数据源三. SpringBoot3 整合 Mybatis3.1 Mybatis整合3.2 声明式事务整合配置3.3 AOP整合配置 四. SpringBoot3 项目打包和运行…

界面材料知识

界面材料是用于填充芯片和散热器之间的空隙&#xff0c;将低导热系数的空气挤出&#xff0c;换成较高导热系数的材料&#xff0c;以提高芯片散热能力。参考下图 图片来源网上 热阻是衡量界面材料性能最终的参数&#xff0c;其中与热阻有关的有&#xff1a; 1、导热系数&#x…

(三十一)Flask之wtforms库【剖析源码下篇】

每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 &#x1f525;&#x1f525;本文已收录于Flask框架从入门到实战专栏&#xff1a;《Flask框架从入…

使用java stream对集合中的对象按指定字段进行分组并统计

一、概述 有这样一个需求&#xff0c;在一个list集合中的对象有相同的name&#xff0c;我需要把相同name的对象进行汇总计算。使用java stream来实现这个需求&#xff0c;这里做一个记录&#xff0c;希望对有需求的同学提供帮助 一、根据指定字段进行分组 一、先准备好给前端要…

菱形继承和菱形虚拟继承

c具有多继承的特性&#xff0c;那么菱形继承就是多继承的一种特殊情况&#xff0c;但是菱形继承会出现一些问题&#xff0c;比如数据冗余和二义性&#xff1b; 那么怎么解决这个问题呢&#xff1f; 菱形虚拟继承 菱形虚拟继承的原理 class A { public:int _a; };class B: v…

Stable Diffusion【基础篇】:降噪强度(denoising strength)

提到降噪强度&#xff08;denoising strength&#xff09;&#xff0c;大家一定不会陌生&#xff0c;这个参数是图生图中最关键的参数之一。今天在Stable Diffusion Art网站看到一篇介绍降噪强度&#xff08;denoising strength&#xff09;的文章&#xff08;地址&#xff1a;…

【postgresql】版本学习

PostgreSQL 17 Beta 2 发布于2024-06-27。 PostgreSQL 17 Beta 2功能和变更功能的完整列表&#xff1a;PostgreSQL: Documentation: 17: E.1. Release 17 ​ 支持的版本&#xff1a; 16 ( 当前版本) / 15 / 14 / 13 / 12 ​ 不支持的版本&#xff1a; 11 / 10 / 9.6 / 9.5 /…

「前端」快速排序算法演示

快速排序算法演示。 布局描述 一个简单的HTML页面,用户可以在其中输入一系列用逗号分隔的数字。 一个CSS样式表,提供了一个美观大方的布局和样式。 一个JavaScript脚本,实现了快速排序算法,并在用户点击按钮时对输入的数字进行排序,并显示结果。 效果演示 核心代码 <…

Django创建项目

虚拟环境创建成功 使用命令行创建项目 创建一个文件夹&#xff0c;用pycharm打开&#xff0c;将之前创建好的虚拟环境选中&#xff08;这一步不在仔细赘述了&#xff0c;比较简单&#xff09; cd进入虚拟环境所在文件目录&#xff0c;打开虚拟环境pipenv shell 创建django项…

身边有填报志愿需求别错过!张雪峰透露今年志愿填报技巧:报专业,别报行业!(文末附稳定高薪专业推荐)

高考填报志愿是每个考生和家长都要面对的重大抉择。在当前就业形势日趋严峻、部分行业发展前景不明朗的大背景下,考生在填报志愿时更需要全面了解各个专业的就业前景,理性权衡自身兴趣特长与社会需求&#xff0c;而不是盲目跟风报考所谓的"热门专业"。 今天跟大家分…

对谈 MoonBit:AI 时代的编程语言应该是什么样子的?丨编码人声

「编码人声」是由「RTE开发者社区」策划的一档播客节目&#xff0c;关注行业发展变革、开发者职涯发展、技术突破以及创业创新&#xff0c;由开发者来分享开发者眼中的工作与生活。 本期节目&#xff0c;我们请到了 MoonBit 的创始人宏波和资深的开发者狼叔作为我们的嘉宾&…

人工智能在日常生活中的十大应用:从医疗到智能家居

人工智能已成为当今人类日常生活的重要组成部分&#xff0c;无论您是否意识到&#xff0c;它几乎在所有场景中都能提供帮助。每次您进行网络搜索、在线预订旅行、接收来自京东等购物平台的产品推荐又或是打开您的新浪、抖音时&#xff0c;都能看到影子&#xff0c;这些只是一些…

Python番外篇之代码编译与字节码

引言 关于字节码&#xff0c;不太想讲&#xff0c;不影响实际使用&#xff0c;对新手不友好…… 但是&#xff0c;涉及到新手经常碰到的问题的解惑&#xff0c;似乎又不得不讲。 最终&#xff0c;还是打算以番外篇的形式&#xff0c;稍微提一下。 不过&#xff0c;关于字节码的…

RabbitMQ入门教程(精细版二带图)

目录 六 RabbitMQ工作模式 6.1Hello World简单模式 6.1.1 什么是简单模式 6.1.2 RabbitMQ管理界面操作 6.1.3 生产者代码 6.1.4 消费者代码 6.2 Work queues工作队列模式 6.2.1 什么是工作队列模式 6.2.2 RabbitMQ管理界面操作 6.2.3 生产者代码 6.2.4 消费者代码 …

RAID详解

一、RAID存储是什么&#xff1f; RAID 存储&#xff08;Redundant Arrays of Independent Disks&#xff0c;独立磁盘冗余阵列&#xff09;是一种通过将多个独立的物理磁盘组合在一起&#xff0c;以实现更高的存储性能、数据可靠性和容错能力的技术。其主要目的是解决单个磁盘…

tapd 与国内外主流的8大项目管理软件大对比

对比Tapd与8大项目管理工具&#xff1a;PingCode、Worktile、Redmine、Teambition、广联达、Jira、禅道、飞书。 Tapd 是腾讯推出的一款敏捷开发管理工具&#xff0c;特别适合那些需要高效协作和快速迭代的敏捷开发团队。它支持多种敏捷方法论&#xff0c;包括Scrum和Kanban&am…

liunx文件系统,日志分析

文章目录 1.inode与block1.1 inode与block概述1.2 inode的内容1.3 文件存储1.4 inode的大小1.5 inode的特殊作用 2.硬链接与软链接2.1链接文件分类 3.恢复误删除的文件3.1 案例:恢复EXT类型的文件3.2 案例:恢复XFS类型的文件3.2.1 xfsdump使用限制 4.分析日志文件4.1日志文件4.…