当前位置: 首页 > wzjs >正文

要想浏览国外网站 应该怎么做电商网络推广

要想浏览国外网站 应该怎么做,电商网络推广,汽车网站哪个好,2014做社交网站首先了解操作系统中内存管理相关内容:XV6:页表 - 知乎 (zhihu.com)这篇博客讲解得比较通俗易懂。 虚拟地址和物理地址 物理内存相关 代码:物理内存分配器 分配器在 kalloc.c(kernel/kalloc.c:1)中。物理内存分配器负…

首先了解操作系统中内存管理相关内容:XV6:页表 - 知乎 (zhihu.com)这篇博客讲解得比较通俗易懂。

虚拟地址和物理地址

物理内存相关

代码:物理内存分配器

分配器在 kalloc.ckernel/kalloc.c:1)中。物理内存分配器负责为用户进程、内核栈、页表页和管道缓冲区等分配和释放 4096 字节的整页内存。这个内存分配器的设计非常简单,它使用一个单链表 struct run *freelist 来管理空闲的页面,分配物理内存时调用 kalloc 时从链表头部取出一个页面,释放时调用 kfree 时将页面加入链表的头部。以下是物理内存分配的相关函数:

  • kinit 函数用于初始化内核内存分配器。它首先通过调用 initlock 函数初始化一个自旋锁 kmem.lock,然后调用 freerange 函数,将从 endPHYSTOP(物理内存的结束地址)的内存范围标记为可用,并加入到空闲列表中。

  • freerange 函数用于将一个连续的内存区域加入到空闲列表中。它接受两个参数 pa_startpa_end,分别表示内存区域的起始和结束地址。函数首先将起始地址向上舍入到页面的整数倍,然后遍历这个范围内的每个页面,调用 kfree 函数将其加入到空闲列表中。

  • kfree 函数用于释放一个页面的物理内存。在释放之前,它会先检查传入的地址 pa 是否有效,即是否在允许的范围内,并且是否是4096字节的整数倍。如果地址无效,将调用 panic 函数导致系统崩溃。如果地址有效,kfree 会将该页面的内存用垃圾值填充,然后将该页面加入到 kmem 结构的 freelist 中,以便以后再次使用。

  • kalloc 函数用于分配一个 4096 字节的页面。它首先通过自旋锁保护的 kmem 结构获取一个空闲页面,然后将该页面的内存用垃圾值填充,最后返回该页面的地址。如果在尝试分配内存时没有可用的空闲页面,kalloc 将返回0,表示内存分配失败。

// 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.struct run {struct run *next;
};struct {struct spinlock lock;struct run *freelist;
} kmem;// kinit 函数用于初始化内核内存分配器
void
kinit()
{// 初始化一个自旋锁 kmem.lockinitlock(&kmem.lock, "kmem");// 将从 end 到 PHYSTOP(物理内存的结束地址)的内存范围标记为可用,并加入到空闲列表中。freerange(end, (void*)PHYSTOP);
}// freerange 函数用于将一个连续的内存区域加入到空闲列表中。
void
freerange(void *pa_start, void *pa_end)
{char *p;// 将起始地址向上对齐到页面边界(避免未对齐的内存)p = (char*)PGROUNDUP((uint64)pa_start);for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)    // PGSIZE(4096 字节)kfree(p);
}// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
// kfree 函数用于释放一个页面的物理内存。
void
kfree(void *pa)
{struct run *r;// 它会先检查传入的地址 pa 是否有效,即是否在允许的范围内,并且是否是4096字节的整数倍。if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// Fill with junk to catch dangling refs.// 如果地址有效,kfree 会将该页面的内存用垃圾值填充memset(pa, 1, PGSIZE);// 将该页面加入到 kmem 结构的 freelist 中,以便以后再次使用r = (struct run*)pa;acquire(&kmem.lock);r->next = kmem.freelist;kmem.freelist = r;release(&kmem.lock);
}// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
// 用于分配一个4096字节的页面
void *
kalloc(void)
{struct run *r;// 通过自旋锁保护的 kmem 结构获取一个空闲页面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;
}

地址转换

地址转换函数是 xv6 操作系统中处理虚拟内存和物理内存映射的核心部分。xv6 采用 RISV-Sv39 分页方案,该方案使用三级页表来将虚拟地址映射到物理地址。在 xv6 中地址转换函数主要有两个:

  • walk 函数:walk 函数遍历页表,返回与给定虚拟地址va对应的页表项(PTE)。
  • mappagesmappages 函数将一系列虚拟地址映射到对应的物理地址,并设置相应的页表项(PTE)。
代码:walk 和 mappages
// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va.  If alloc!=0,
// create any required page-table pages.
// 遍历页表并返回与给定虚拟地址va对应的页表项(PTE)
// pagetable 是当前进程的页表,va 是需要映射或访问的虚拟地址,alloc 是一个标志,指示是否在必要时创建新的页表项或页表
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
//   39..63 -- must be zero.
//   30..38 -- 9 bits of level-2 index.
//   21..29 -- 9 bits of level-1 index.
//   12..20 -- 9 bits of level-0 index.
//    0..11 -- 12 bits of byte offset within the page.
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{if(va >= MAXVA)panic("walk");for(int level = 2; level > 0; level--) {// 对于每个级别的页表,函数首先计算虚拟地址va在当前级别的索引pte_t *pte = &pagetable[PX(level, va)];// 检查 pte 中的 PTE_V 位,这个位表示页表项是否有效。// 如果该位被设置,说明找到了有效的页表项,函数继续向下一级页表遍历。if(*pte & PTE_V) {pagetable = (pagetable_t)PTE2PA(*pte);} else {  // 如果PTE_V位未被设置,说明当前页表项未被分配或不存在,这时需要创建一个新的页表。if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)return 0;memset(pagetable, 0, PGSIZE);*pte = PA2PTE(pagetable) | PTE_V;}}return &pagetable[PX(0, va)];
}// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned. Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
// 将一系列虚拟地址映射到对应的物理地址上,并设置相应的页表项(PTE)
// pagetable:当前进程的页表。
// va:映射开始的虚拟地址。
// size:需要映射的内存大小。
// pa:映射开始的物理地址。
// perm:页表项的权限位,例如读、写和执行权限。
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{uint64 a, last;pte_t *pte;if(size == 0)panic("mappages: size");// 使用 PGROUNDDOWN 宏将 va 和 va + size - 1 对齐到最近的页边界。// 这样做是为了确保映射的虚拟地址和物理地址都是页对齐的,因为大多数硬件和操作系统要求内存操作必须在页边界上进行。a = PGROUNDDOWN(va);last = PGROUNDDOWN(va + size - 1);for(;;){if((pte = walk(pagetable, a, 1)) == 0)return -1;if(*pte & PTE_V)panic("mappages: remap");*pte = PA2PTE(pa) | perm | PTE_V;if(a == last)break;a += PGSIZE;pa += PGSIZE;}return 0;
}
代码:walkadd - 虚拟地址转物理地址

这个函数只返回页面基地址(物理地址的高 44 位),也就是不包含 offsetoffset 在后续函数(copyin 等函数)中自己计算。

// Look up a virtual address, return the physical address,
// or 0 if not mapped.
// Can only be used to look up user pages.
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;
}

虚拟内存相关

代码:kvmmapmappages - 添加映射
// add a mapping to the kernel page table.
// only used when booting.
// does not flush TLB or enable paging.
void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{if(mappages(kpgtbl, va, sz, pa, perm) != 0)panic("kvmmap");
}// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned. Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{uint64 a, last;pte_t *pte;if(size == 0)panic("mappages: size");a = PGROUNDDOWN(va);last = PGROUNDDOWN(va + size - 1);for(;;){if((pte = walk(pagetable, a, 1)) == 0)return -1;if(*pte & PTE_V)panic("mappages: remap");*pte = PA2PTE(pa) | perm | PTE_V;if(a == last)break;a += PGSIZE;pa += PGSIZE;}return 0;
}
  • kvmmap():封装 mappages(),用于内核映射。

  • mappages()

    • VAVA+size 的范围映射到 PA,设置权限。
    • 对齐到页面边界,逐页创建 PTE
    • 如果 PTE 已存在,报错(防止重映射)。
  • 用途:构建内核和用户页表。

内核地址空间

xv6 为每一个进程维护了一个页表,用于描述每个进程的用户地址空间,以及一个单独的描述内核地址空间的页表。上图左边是xv6中内核地址空间(内核页表)的布局。

QEMU 模拟的计算机包括 RAM(物理内存),从物理地址 0x80000000(KERNBASE) 开始,至少持续到 0x86400000(PHYSTOP)。QEMU 模拟还包括 I/O 设备,如磁盘接口,这些设备接口作为内存映射控制寄存器提供给软件,这些寄存器位于物理地址空间 0x80000000 以下。内核可通过读/写这些特殊的物理地址与设备交互;此类读写与设备硬件而非 RAM 通信。

xv6 中内核进程的 VA 与 PA 是直接映射的(大部分是相等的关系),也就是 VA = PA。但是有两个内核虚拟地址不是直接映射的:

  1. trampoline:trampoline直译过来是蹦床的意思,可以理解为它是一个 U 态到 S 态的蹦床。trampoline 位于虚拟地址空间的顶部,内核地址空间与用户地址空间有相同的映射。trampoline是一个神奇的东西,由于我们需要从用户态转换到内核态,执行内核代码,所以我们肯定要切换用户页表到内核页表,而无论用户页表还是内核页表中都有 trampoline 这个东西,并映射到了相同的位置,所以,在 trampoline 代码中切换页表是安全的,切换页表后程序不会崩溃,对于 trampoline 中的下一条指令,在切换后的页表中仍然存在相同的虚拟地址。
  2. kernel stack:每一个进程都有自己的内核栈,映射在虚拟地址的高地址中,其中有未映射的保护页(guard page),这些保护页是无效页(PTE中User位清空),如果内核栈页溢出,就会触发缺页异常(page fault)并且内核也会崩溃。如果没有保护页发生内核栈溢出将会重写内核内存,会导致错误的操作。

代码:创建内核地址空间

大部分用于操作地址空间和页表的 xv6 代码都在 vm.ckernel/vm.c:1)中。

  • 核心数据结构是 pagetable_t,它实际上是一个指向 RISC-V 根页表页的指针;pagetable_t 可以是内核页表,也可以是进程用户页表。
  • 核心函数是 walk 和 mappages,前者通过虚拟地址得到 PTE,后者将虚拟地址映射到物理地址。和地址空间相关操作都离不开这两个函数,因此要先从这两个函数开始。
  • 以 kvm 开头的函数操作内核页表;以 uvm 开头的函数操作用户页表;其他函数同时用于这两种页表。
  • copyoutcopyin将数据复制到或复制出到用户虚拟地址;它们在 vm.c 中,因为它们需要显式转换用户空间的地址,以便找到相应的物理内存。
kvminit — 初始化全局 kernel_pagetable

kvminit 函数是内核页表初始化的入口点。它调用 kvmmake 函数来创建一个新的内核页表,并将其赋值给 kernel_pagetable 全局变量。这个全局变量在内核中被广泛使用,作为当前运行的内核页表的引用。kvmmake 函数负责创建一个新的内核页表,并通过 kvmmap 完成必要的映射。kvmmap 函数用于将一个物理内存区域映射到内核页表中的指定虚拟地址,该函数调用 mappages 来实际进行映射操作。

// Initialize the one kernel_pagetable
void
kvminit(void)
{kernel_pagetable = kvmmake();
}// Make a direct-map page table for the kernel.
pagetable_t
kvmmake(void)
{pagetable_t kpgtbl;kpgtbl = (pagetable_t) kalloc();memset(kpgtbl, 0, PGSIZE);// uart registerskvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);// virtio mmio disk interfacekvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);// PLICkvmmap(kpgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W);// map kernel text executable and read-only.kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);// map kernel data and the physical RAM we'll make use of.kvmmap(kpgtbl, (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(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);// map kernel stacksproc_mapstacks(kpgtbl);return kpgtbl;
}// add a mapping to the kernel page table.
// only used when booting.
// does not flush TLB or enable paging.
void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{if(mappages(kpgtbl, va, sz, pa, perm) != 0)panic("kvmmap");
}
kvminithart
  • satp 寄存器设置为内核页表地址,启用分页。
  • sfence_vma():刷新 TLB,确保硬件使用新页表。
void
kvminithart()
{w_satp(MAKE_SATP(kernel_pagetable));sfence_vma();
}
copyoutcopyin
// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
// copyin 函数用于将从用户空间的地址srcva开始的数据复制到内核空间的地址dst开始。它接受以下参数:
//    pagetable: 用户进程的页表,用于映射虚拟地址到物理地址。
//    dst: 内核空间的目的地物理地址。
//    srcva: 用户空间的源虚拟地址。
//    len: 要复制的字节长度。
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{uint64 n, va0, pa0;while(len > 0){// PGROUNDDOWN宏的作用是取掉一个地址的低12位(因为4096是2的12次方,page的大小是4096),确保结果地址是页面大小的整数倍。// 使用PGROUNDDOWN宏将虚拟地址srcva对齐到页面的起始地址,得到va0,va0相当于当前遍历到的页面的起始虚拟地址va0 = PGROUNDDOWN(srcva);pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;n = PGSIZE - (srcva - va0);if(n > len)n = len;memmove(dst, (void *)(pa0 + (srcva - va0)), n);len -= n;dst += n;srcva = va0 + PGSIZE;}return 0;
}// Copy from kernel to user.
// Copy len bytes from src to virtual address dstva in a given page table.
// Return 0 on success, -1 on error.
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{uint64 n, va0, pa0;while(len > 0){va0 = PGROUNDDOWN(dstva);pa0 = walkaddr(pagetable, va0);if(pa0 == 0)return -1;n = PGSIZE - (dstva - va0);if(n > len)n = len;memmove((void *)(pa0 + (dstva - va0)), src, n);len -= n;src += n;dstva = va0 + PGSIZE;}return 0;
}

进程地址空间

每个进程都有一个单独的页表,当 xv6 在进程间切换时,它也会改变页表。如图 2.3 所示,进程的用户内存从虚拟地址 0 开始,最大可扩展至 MAXVA(#define MAXVA (1L << (9 + 9 + 9 + 12 - 1))),原则上允许一个进程寻址 256 千兆字节的内存。

系统调用exec用于创建进程地址空间。函数首先使用namei获取可执行文件,读取 ELF 头,检查 ELF 中的 magic。之后使用proc_pagetable创建进程页表。

// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{pagetable_t pagetable;// An empty page table.pagetable = uvmcreate();if(pagetable == 0)return 0;// map the trampoline code (for system call return)// at the highest user virtual address.// only the supervisor uses it, on the way// to/from user space, so not PTE_U.if(mappages(pagetable, TRAMPOLINE, PGSIZE,(uint64)trampoline, PTE_R | PTE_X) < 0){uvmfree(pagetable, 0);return 0;}// map the trapframe just below TRAMPOLINE, for trampoline.S.if(mappages(pagetable, TRAPFRAME, PGSIZE,(uint64)(p->trapframe), PTE_R | PTE_W) < 0){uvmunmap(pagetable, TRAMPOLINE, 1, 0);uvmfree(pagetable, 0);return 0;}return pagetable;
}

proc_pagetable函数中,先使用uvmcreate函数申请一个页面,之后将 trampoline 和 trapframe 映射到高位地址空间中。

exec之后使用uvmalloc申请内存空间,再使用loadseg函数将程序加载到对应页面中。在 Program Header 中描述了各段的 filesz,memsz等信息,当 filesz 小于 memsz 时,中间的空隙用 0 填充(如 C 语言中的全局变量)。

程序加载完成后,再申请两块页面,第一块为 guard page ,使用uvmclear函数将该页面PTE_U设置为0,即不允许 user mode 访问。第二页设置为进程的栈。然后将argcargv和返回地址压栈,完成栈的准备工作。

最后,exec函数更新进程结构体,将旧页表释放。

在 Program Header 的vaddr中,程序可以指定被加载到的虚拟地址,而这可能是危险的,因此在exec中会检查if(ph.vaddr + ph.memsz < ph.vaddr),避免发生加法溢出。

参考

  • XV6:页表 - 知乎 (zhihu.com)
  • xv6-riscv-book-Chinese/Chapter-3
  • XV6学习(3)Page tables - 星見遥 - 博客园 (cnblogs.com)
http://www.dtcms.com/wzjs/296821.html

相关文章:

  • 煤炭建设行业协会网站贵州seo培训
  • 正规的邯郸网站建设搜索引擎平台有哪些软件
  • 浙江省网站备案时间网络舆情监测平台
  • 思茅区建设局网站武汉seo优化公司
  • 做飞机票的图片的网站深圳新闻最新事件
  • 网站建设哪家做的好网络营销组织的概念
  • 网站 语言切换怎么做影响关键词优化的因素
  • wordpress显示10篇文章seo查询工具
  • 成品网站价格表网络推广外包哪家好
  • 做网站要通过网信办备案吗学生制作个人网站
  • 导航网站html模板外包公司排名
  • 做一小说网站要花多钱高端网站建设深圳
  • drupal网站开发上海排名优化推广工具
  • 郑州管家网站托管搜索引擎名词解释
  • 方案设计基本步骤北仑seo排名优化技术
  • wordpress多域一网seo长尾关键词优化
  • 网页升级紧急通知怎么取消seo资料网
  • 成都百度seo搜索引擎优化培训宁波seo外包公司
  • 什么网站可以做pie chart网站平台有哪些
  • 做网页和做网站百度搜索关键词规则
  • 舟山公司网站建设网络营销企业有哪些公司
  • 怎么做淘宝客网站和APP品牌推广方式都有哪些
  • wordpress创建三级分类抚顺优化seo
  • sharepoint网站制作下载一个百度时事新闻
  • 阜阳网站建设阜阳广告公司取名字参考大全
  • 建手机网站要多少钱广东省疫情最新
  • 包装设计网站官网搜索关键词网站
  • 网站建设功免费b站推广网站短视频
  • 社区网站的建设镇江关键字优化公司
  • szfob外贸论坛首页搜索引擎优化管理实验报告