
操作系统教程【免费下载链接】How-to-Make-a-Computer-Operating-SystemHow to Make a Computer Operating System in C项目地址https://gitcode.com/gh_mirrors/ho/How-to-Make-a-Computer-Operating-System点击查看免费下载本章chapter9/README.md是该项目操作系统内核内存管理子系统的核心说明内核如何通过 GRUB 获知物理内存总量、如何将前 8MB 物理内存保留给内核自身使用以及如何在虚拟地址空间中划分 1GB 内核空间与用户空间并通过“任务页目录前 256 项共享内核页目录”这一机制让所有任务共享内核映射。读完本文你将掌握该项目中物理页帧位图分配器、页目录/页表初始化、4MB 大页与 4KB 页混合映射、页表自镜像recursive mapping以及缺页异常处理等核心实现并能对照 vmm.cc、x86.h 等源码进行深入研读。物理内存布局内核保留区与自由内存本内核的物理内存总量信息来源于引导加载器 GRUBGRUB 遵循 Multiboot 规范加载内核并通过 multiboot 信息结构体其中包含low_mem、high_mem、mmap_addr/mmap_length等字段向内核报告可用内存具体引导流程参见 Chapter-3/README.md。在该实现中物理内存的前 8MB地址范围0x00000000~0x00800000被保留给内核专用主要存放以下内容内核本体KernelGDT、IDT 以及 TSS 等系统描述符表内核栈Kernel Stack为硬件预留的空间如显存 Video Memory 等内核的页目录Page Directory与页表Page Table这 8MB 以内的区域不允许被一般分配器挪用其余物理内存则由内核和应用程序自由使用这一保留策略在源码中有直接印证。vmm.cc 的Memory_init()中内核初始化页帧位图bitmap时将PAGE(0x0)到PAGE(pg1_end)即0x800000之间的所有页全部标记为“已使用”/* Pages reservees pour le noyau */ for (pg PAGE(0x0); pg (int)(PAGE((u32) pg1_end)); pg) { set_page_frame_used(pg); }其中pg1_end对应宏KERN_PG_1_LIM 0x800000见 x86.h正好是 8MB 边界与文档描述完全一致。虚拟内存映射1GB 内核空间与用户空间的分界在虚拟地址空间中内核以地址0x40000000即宏USER_OFFSET定义于 x86.h为分界线划分两类区域内核空间从内存起始地址到0x40000000即低端 1GB 虚拟地址空间用户空间从0x40000000到内存末尾宏RAM_MAXSIZE 0x100000000即 4GB 边界见 x86.h。这里有一个值得注意的细节虽然物理内存总量由 GRUB 上报通常小于 4GB但虚拟地址空间的上限仍按 x86 分页机制支持的完整 4GB 来规划。RAM_MAXPAGE 0x1000001,048,576 页 × 4096 字节 4GB也印证了这一点见 x86.h。内核空间对全部任务可见内核空间在虚拟内存中占用了整整 1GB并且对内核任务和用户任务都是公共可见的——也就是说无论当前运行的是哪个进程其地址空间低端 1GB 的映射内容完全一致、永远指向内核的页表。这种“内核空间共享”的实现方式非常直接创建任务页目录时把任务页目录的前 256 项直接复制内核页目录pd0[]的前 256 项。由于页目录每项覆盖 4MB 虚拟地址空间1024 项 × 4MB 4GB256 项恰好覆盖 1GB。这是文档中给出的核心代码位于 vmm.cc 的pd_create()/* * Kernel Space. v_addr USER_OFFSET are addressed by the kernel pages table */ pdir (u32 *) pd-base-v_addr; for (i 0; i 256; i) pdir[i] pd0[i];配合 vmm.cc 中对用户空间的清零初始化/* Espace utilisateur */ for (i 256; i 1023; i) pdir[i] 0; /* Page table mirroring magic trick !... */ pdir[1023] ((u32) pd-base-p_addr | (PG_PRESENT | PG_WRITE));这段代码同时揭示了三个关键设计前 256 项1GB共享内核页目录保证所有任务的低端 1GB 映射一致第 2561022 项用户空间区域初始为空0即不映射任何页面后续通过pd_add_page()按需填充第 1023 项指向本任务页目录自身所在的物理页实现“页目录自镜像”这是后面虚拟地址直接操作页表的基础。源码级纵深页目录与页表的初始化过程关键宏定义速查理解后续源码前先列出 x86.h 中与本章直接相关的核心宏宏值含义KERN_PDIR0x00001000内核页目录所在物理地址KERN_PG_10x400000内核 4MB 页page 1起始物理地址KERN_PG_1_LIM0x800000内核保留区上限8MBKERN_PG_HEAP/KERN_PG_HEAP_LIM0x00800000/0x10000000内核虚拟页堆的起止地址KERN_HEAP/KERN_HEAP_LIM0x10000000/0x40000000内核 kmalloc 堆的起止地址USER_OFFSET0x40000000用户空间起始虚拟地址1GB 分界USER_STACK0xE0000000用户栈顶地址PAGESIZE4096标准页大小4KBRAM_MAXSIZE0x100000000可寻址物理内存上限4GBRAM_MAXPAGE0x100000最大页帧数量1,048,576PG_PRESENT/PG_WRITE/PG_USER/PG_4MB0x1/0x2/0x4/0x80页表项标志位PAGING_FLAG/PSE_FLAGCR0bit31 /CR4bit4分页使能 / 4MB 大页使能Memory_init()位图初始化与分页开启Memory_init(u32 high_mem)vmm.cc是物理内存管理的总入口high_mem即来自 GRUB 的上报值。其执行步骤如下计算物理页总数pg_limit (high_mem * 1024) / PAGESIZE将 GRUB 报告的 KB 数换算成页帧数量初始化页帧位图mem_bitmap[]是全局位图数组u8 mem_bitmap[RAM_MAXPAGE / 8]见 vmm.cc大小为 131,072 字节恰好覆盖 4GB / 4KB 的全部页帧。pg_limit以内的位清 0空闲超出实际内存的部分全部置0xFF不可用for (pg 0; pg pg_limit / 8; pg) mem_bitmap[pg] 0; for (pg pg_limit / 8; pg RAM_MAXPAGE / 8; pg) mem_bitmap[pg] 0xFF;标记内核保留页将前 8MBPAGE(0x0)~PAGE(pg1_end)置为已用初始化内核页目录pd0[]vmm.ccpd0[0] ((u32) pg0 | (PG_PRESENT | PG_WRITE | PG_4MB)); pd0[1] ((u32) pg1 | (PG_PRESENT | PG_WRITE | PG_4MB)); for (i 2; i 1023; i) pd0[i] ((u32) pg1 PAGESIZE * i) | (PG_PRESENT | PG_WRITE); // Page table mirroring magic trick ! pd0[1023] ((u32) pd0 | (PG_PRESENT | PG_WRITE));这里体现了大页 普通页混合映射的策略前两项覆盖 0~8MB 物理内存即文档所说“前 8MB 内核保留区”使用PG_4MB大页直接映射借助 CR4 的 PSE 位开启 4MB 页支持后 1020 项则用 4KB 页表按pg1 PAGESIZE * i逐页建立第 1023 项让页目录指向自身实现“页目录自镜像”开启分页vmm.cc通过内联汇编依次设置cr3 pd0、cr4 | PSE_FLAG、cr0 | PAGING_FLAG完成从实模式线性地址到分页模式的切换初始化内核堆kern_heap KERN_HEAP并调用ksbrk(1)预分配第一页初始化内核虚拟页空闲列表在kern_free_vm链表中登记KERN_PG_HEAP0x800000到KERN_PG_HEAP_LIM0x10000000之间的空闲虚拟地址区间。页帧分配器get_page_frame() 与位图操作物理页帧的分配使用最经典的位图算法。get_page_frame()vmm.cc从mem_bitmap[]的起始位置开始线性扫描寻找第一个值为 0 的位随即通过set_page_frame_used(page)将其标记为已用并返回对应物理地址for (byte 0; byte RAM_MAXPAGE / 8; byte) if (mem_bitmap[byte] ! 0xFF) for (bit 0; bit 8; bit) if (!(mem_bitmap[byte] (1 bit))) { page 8 * byte bit; set_page_frame_used(page); return (char *) (page * PAGESIZE); } return (char *) -1;对应的置位/清位宏定义在 vmm.h#define set_page_frame_used(page) mem_bitmap[((u32) page)/8] | (1 (((u32) page)%8)) #define release_page_frame(p_addr) mem_bitmap[((u32) p_addr/PAGESIZE)/8] ~(1 (((u32) p_addr/PAGESIZE)%8))当位图全满所有字节为0xFF时返回-1调用方如get_page_from_heap()会打印 PANIC 信息并停机。虚拟页与物理页的配对get_page_from_heap()get_page_from_heap()vmm.cc是内核分配“一页虚拟地址 一页物理地址”的核心函数流程为调用get_page_frame()取得一个空闲物理页帧从内核空闲虚拟地址链表kern_free_vm头部取出一段vm_area取其vm_start作为虚拟地址并将vm_start PAGESIZE若该区间耗尽则从链表中删除该vm_area调用pd0_add_page(v_addr, p_addr, 0)把映射写入内核页目录构造并返回一个struct page包含v_addr、p_addr定义见 vmm.h。反向操作release_page_from_heap()vmm.cc则负责释放先经get_p_addr()找到虚拟地址对应的物理页并release_page_frame()清位再pd_remove_page()清除页表项最后将这段虚拟地址重新并入空闲vm_area链表包括与前/后相邻区间合并甚至重建新区间的逻辑。页目录自镜像为何能用虚拟地址直接操作页表pd_add_page()vmm.cc展示了“页目录自镜像”技巧的妙用。由于每个页目录的第 1023 项都指向自身因此所有0xFFC00000以上开头的虚拟地址都会经过该“镜像项”解析。由此可以推导出两条访问规则源码注释中亦有明确说明见 vmm.cc地址0xFFFFF000等价于页目录自身的基址可通过它读写 PDE地址0xFFC00000 | (PT 索引 12)通过镜像项进入第 PT 个页表可通过它读写 PTE。基于此pd_add_page()先通过pde (u32 *) (0xFFFFF000 | ((v_addr 0xFFC00000) 20))检查对应页表是否已存在*pde PG_PRESENT若不存在则用get_page_from_heap()分配一页作为新页表并初始化清零、写入 PDE然后把该页登记进进程的pd-pt链表以便回收随后通过pte (u32 *) (0xFFC00000 | ((v_addr 0xFFFFF000) 10))写入 PTE*pde (u32) pg-p_addr | (PG_PRESENT | PG_WRITE | flags); ... *pte ((u32) p_addr) | (PG_PRESENT | PG_WRITE | flags);pd_remove_page()vmm.cc则清除 PTE 的PG_PRESENT位并执行invlpg指令使 TLB 中对应条目失效get_p_addr()vmm.cc通过同样的镜像寻址读取 PDE、PTE返回虚拟地址对应的物理地址。这些辅助宏VADDR_PD_OFFSET、VADDR_PT_OFFSET、VADDR_PG_OFFSET、PAGE(addr)均定义于 x86.h。进程页目录的创建与切换pd_create()vmm.cc为每个新进程建立独立的页目录kmalloc一个struct page_directory并用get_page_from_heap()为其分配存放页目录的物理页如前所述复制pd0[]前 256 项作为共享内核空间256~1022 项清零作为用户空间第 1023 项指向本目录自身初始化pd-pt链表用于跟踪该目录下已分配的页表页。切换进程时将当前进程页目录的物理地址写入cr3即可完成地址空间切换例如 architecture.cc 中的asm(mov %0, %%eax; mov %%eax, %%cr3::m((info-pd)-base-p_addr))同时用户栈被放置在USER_STACK - 16architecture.cc用户程序装载器也会校验 ELF 装载地址不低于USER_OFFSET见 elf_loader.cc从加载与运行两侧共同保证“用户程序不得侵入内核空间”。缺页异常Page Fault中的内核/用户空间判定x86 分页机制下访问未映射或未授权地址会触发#PF异常。本内核的缺页处理程序x86.cc会读取faulting_addr并利用USER_OFFSET与USER_STACK判定缺页地址的归属if (faulting_addr USER_OFFSET faulting_addr USER_STACK) { /* 用户地址空间缺页分配物理页并映射到当前进程页目录 */ pg-v_addr (char *) (faulting_addr 0xFFFFF000); list_add(pg-list, current-pglist); pd_add_page(pg-v_addr, pg-p_addr, PG_USER, current-pd); } else { io.print(No autorized memory acces on : %p (eip:%p,code:%p)\n, faulting_addr, eip, code); }这正体现了本章 1GB 分界线的运行时价值0x40000000不仅是一个布局约定还是缺页处理逻辑中区分“合法用户缺页按需分配”与“非法访问报错”的分水岭。内核堆分配ksbrk / kmalloc / kfree内核空间内的动态内存由 alloc.cc 中的三个函数提供它们与页管理紧密配合ksbrk(n)alloc.cc以页为单位扩展内核堆。每扩展一页就调用get_page_frame()取物理页并pd0_add_page()建立映射同时校验kern_heap n*PAGESIZE不超过KERN_HEAP_LIM0x40000000即 1GB 内核空间上限超出则 PANICkmalloc(size)alloc.cc在KERN_HEAP起的堆区间内按“首次适配”策略寻找空闲块kmalloc_header记录size与used位最小块大小KMALLOC_MINSIZE 16见 vmm.h需要时自动调用ksbrk扩展kfree(v_addr)alloc.cc将块标记为空闲并与紧随其后的空闲块合并实现简单的碎片整理。可以看到虚拟内存映射为内核堆提供了统一的线性地址空间物理页帧则由位图分配器按需供给——这正是本章“内核空间共享 自由物理内存”设计在实际分配路径上的落地。小结与延伸阅读本章完成的是操作系统内存管理的“地基”物理层以 8MB 为界划分内核保留区与自由区用位图管理全部页帧虚拟层以0x40000000为界划分 1GB 内核空间与用户空间通过“任务页目录前 256 项 内核页目录前 256 项”实现内核映射对所有任务可见机制层4MB 大页PSE 4KB 页表混合映射、页目录自镜像、缺页按需分配共同构成完整的地址转换与保护体系。对照仓库继续深入可依次阅读Chapter-3/README.mdGRUB 引导、Multiboot 信息结构体与物理内存大小上报的完整流程src/kernel/arch/x86/vmm.cc本章核心位图、页目录、进程地址空间的全部实现src/kernel/arch/x86/vmm.hpage、page_directory、vm_area等核心数据结构定义src/kernel/arch/x86/x86.h分页相关宏与标志位定义src/kernel/arch/x86/alloc.cc内核堆分配器实现src/kernel/arch/x86/x86.cc缺页异常处理中对内核/用户空间的运行时判定。赞分享操作系统教程【免费下载链接】How-to-Make-a-Computer-Operating-SystemHow to Make a Computer Operating System in C项目地址https://gitcode.com/gh_mirrors/ho/How-to-Make-a-Computer-Operating-System点击查看免费下载相关推荐如何在Android项目中集成Android-ActionItemBadge3分钟快速上手教程如何在Android项目中集成Android ActionItemBadge3分钟快速上手教程 Android ActionItemBadge是一个轻量级库移动开发UI库/组件How-to-Make-a-Computer-Operating-System实战块缓存实现How to Make a Computer Operating System实战块缓存实现 你还在为操作系统开发中的磁盘I/O性能问题烦恼吗块缓存Blo操作系统教程Memcached Session Manager集群部署大规模Web应用架构设计指南Memcached Session Manager集群部署大规模Web应用架构设计指南 Memcached Session Manager是一个Tomcat会上一篇猫抓浏览器扩展深度解析网页视频资源嗅探完整方案下一篇3步快速掌握ZenTimingsAMD内存超频调试的专业监控工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考