From 9041b0b8d14025da626cc7e56f88b57fafcbea1e Mon Sep 17 00:00:00 2001 From: zhaoyanbai Date: Sat, 3 Oct 2026 12:34:55 +0800 Subject: [PATCH] =?utf8?q?=E5=9C=A8=E6=B3=A8=E9=87=8A=E4=B8=AD=E5=8E=98?= =?utf8?q?=E6=B8=85=E7=BC=BA=E9=A1=B5=E5=BC=82=E5=B8=B8#PF=E7=9A=84?= =?utf8?q?=E5=90=84=E7=A7=8D=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- include/page.h | 18 +++--- include/task.h | 2 +- include/types.h | 6 +- kernel/fork.c | 47 ++++++++------- kernel/innerint.c | 37 +----------- kernel/page_fault.c | 135 ++++++++++++++++++++++++++++++++++++++++++++ kernel/sched.c | 4 +- 7 files changed, 180 insertions(+), 69 deletions(-) create mode 100644 kernel/page_fault.c diff --git a/include/page.h b/include/page.h index c2eb14e..ef8bf87 100644 --- a/include/page.h +++ b/include/page.h @@ -36,7 +36,7 @@ #define PAGE_P 0x001 // 在内存中 #define PAGE_WR 0x002 // 表示可读写 #define PAGE_US 0x004 // 用户级 -#define PAGE_G 0x100 // 全局页表项 +#define PAGE_G 0x100 // 全局页表项 #define PAGE_SHIFT (12) #define PAGE_SIZE (1UL << PAGE_SHIFT) @@ -47,10 +47,12 @@ #define PAGE_PTE_CNT 1024 // P: 有效位 -// 0 表示当前表项无效,访问该虚拟地址直接#PF缺页异常,CPU不翻译,也不看其它位,此时余下31位CPU完全不管,内核可以随便用。 +// 0 +// 表示当前表项无效,访问该虚拟地址直接#PF缺页异常,CPU不翻译,也不看其它位,此时余下31位CPU完全不管,内核可以随便用。 // 修改必须刷对应TLB // R/W: 0 表示只读写就#PF; 1表示可读写。PDE和PTE要同时看,逻辑与。比如PDE.RW=0 && PTE.RW=1 最终还是只读。 -// 另外还需要注意CR0.WP,如果CR0.WP=0(默认),那么只限制用户态,内核写只读页也不缺页; CR0.WP=1,那么内核写只读页也#PF。 +// 另外还需要注意CR0.WP,如果CR0.WP=0(默认),那么只限制用户态,内核写只读页也不缺页; +// CR0.WP=1,那么内核写只读页也#PF。 // U/S: 0 表示只能0、1、2特权级可访问; 3 表示只有特权级程序可访问。 // 同样是PDE与PTE按逻辑与。 // PWT: 0 表示写进缓存,迟早刷回内存; 1 表示写缓存同时写内存,慢但内存总是新的。 @@ -64,9 +66,9 @@ // D: 脏位。0表示该页未写过; 1表示该页被写过。只有PTE用这位,PDE这个位忽略或保留。 // 用于换出页面时判断要不要写回磁盘。D=0的页换出可直接丢弃,磁盘上已经有相同内容; D=1才需要写回。 // 清D位也要刷新TLB。 -// PS: PAGE SIZE. 只存在于页目录表项。在CR4中的PSE打开的情况下,0表示这是4KB页,指向一个页表。1表示这是4MB大页,直接指向物理页。 -// PAT: PAGE ATTRIBUTE TABLE. 只存在于页表项。 Pentium III后引入的机制。 -// G: GLOBAL 全局位 +// PS: PAGE SIZE. +// 只存在于页目录表项。在CR4中的PSE打开的情况下,0表示这是4KB页,指向一个页表。1表示这是4MB大页,直接指向物理页。 PAT: +// PAGE ATTRIBUTE TABLE. 只存在于页表项。 Pentium III后引入的机制。 G: GLOBAL 全局位 // 4K分页下PDE.G被忽略,如果用4MB大页映射内核空间,PDE.G有效 // 内核空间映射在所有进程页表里都一样,设为全局,切换进程不刷新这些TLB项,减少内核映射的TLB miss。 // 用户页不能随便设G,会跨进程泄露TLB翻译。改G相关属性也需要注意TLB一致性。 @@ -77,7 +79,6 @@ // 3. 写时复制里记录"这是共享只读页" // 4. 页面锁标记,禁止换出 - // x // |<------ 31~12------>|<------ 11~0 --------->| 比特 @@ -152,7 +153,6 @@ typedef unsigned long pte_t; #define MAX_ORDER (11) - static inline pde_t* get_pgd() { return (pde_t*)pa2va((read_cr3() & PAGE_MASK)); } @@ -232,7 +232,7 @@ typedef enum { GFP_NONE = 0, GFP_ATOMIC = 1, GFP_KERNEL = 2, - GFP_REWRITE = 3, // 后续需要重新处理的接口,暂时写个值在这里提醒 + GFP_REWRITE = 3, // 后续需要重新处理的接口,暂时写个值在这里提醒 } gfp_t; page_t* alloc_pages(unsigned int order, gfp_t gfpflags); diff --git a/include/task.h b/include/task.h index 731239d..574a686 100644 --- a/include/task.h +++ b/include/task.h @@ -100,7 +100,7 @@ typedef union task_union { unsigned char stack[TASK_SIZE]; } task_t; -task_t* alloc_task_t(); +task_t* alloc_task(); static inline task_t* get_current() { task_t* tsk; diff --git a/include/types.h b/include/types.h index 3a5a879..7c02bf7 100644 --- a/include/types.h +++ b/include/types.h @@ -49,9 +49,9 @@ typedef long int32_t; typedef unsigned long long uint64_t; typedef long long int64_t; -typedef uint32_t uintptr_t; -typedef uint32_t paddr_t; -typedef uint32_t vaddr_t; +typedef unsigned long uintptr_t; +typedef unsigned long paddr_t; +typedef unsigned long vaddr_t; typedef int64_t loff_t; typedef int32_t off_t; diff --git a/kernel/fork.c b/kernel/fork.c index b51ab47..43160db 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -16,9 +16,11 @@ extern pid_t get_next_pid(); extern list_head_t all_tasks; +void copy_page_tables(pde_t* pds_vaddr, pde_t* pdd_vaddr); + int do_fork(pt_regs_t* regs, unsigned long flags) { task_t* tsk; - tsk = alloc_task_t(); + tsk = alloc_task(); printd("fork task %08x flags %08x\n", tsk, flags); if (tsk == NULL) { @@ -34,10 +36,10 @@ int do_fork(pt_regs_t* regs, unsigned long flags) { task_init_lists(tsk); task_init_stats(tsk); - unsigned long iflags; - irq_save(iflags); + unsigned long eflags; + irq_save(eflags); list_add(&tsk->list, &all_tasks); - irq_restore(iflags); + irq_restore(eflags); tsk->cr3 = (uint32_t)page2pa(alloc_one_page(0)); assert(tsk->cr3 != 0); @@ -47,21 +49,9 @@ int do_fork(pt_regs_t* regs, unsigned long flags) { memcpy((void*)pa2va(tsk->cr3), (void*)pa2va(current->cr3), PAGE_SIZE); - for (int i = 0; i < PAGE_PDE_CNT; ++i) { - // unsigned long spde = (unsigned long)pde_src[i]; - // unsigned long dpde = 0; - - if (i >= get_npde(PAGE_OFFSET)) { - pde_dst[i] = pde_src[i]; - continue; - } - - if (pde_src[i] == 0) { - continue; - } - - pde_dst[i] = pde_src[i] & (~PDE_RW); - } + irq_save(eflags); + copy_page_tables(pde_src, pde_dst); + irq_restore(eflags); pt_regs_t* child_regs = ((pt_regs_t*)(TASK_SIZE + (unsigned long)tsk)) - 1; @@ -105,6 +95,25 @@ fork_child: return 0; } +void copy_page_tables(pde_t* pds_vaddr, pde_t* pdd_vaddr) { + assert(pds_vaddr != NULL); + assert(pdd_vaddr != NULL); + assert(PAGE_ALIGN(pds_vaddr) == (vaddr_t)pds_vaddr); + assert(PAGE_ALIGN(pdd_vaddr) == (vaddr_t)pdd_vaddr); + assert((vaddr_t)pds_vaddr >= KERNEL_VADDR_BASE); + assert((vaddr_t)pdd_vaddr >= KERNEL_VADDR_BASE); + + const int knpde = get_npde(KERNEL_VADDR_BASE); + for (int i = 0; i < PAGE_PDE_CNT; i++) { + if (i < knpde) { + // 把所有可写页设置为只读 + pds_vaddr[i] = pds_vaddr[i] & (~PDE_RW); + } + + pdd_vaddr[i] = pds_vaddr[i]; + } +} + int sysc_fork(pt_regs_t regs) { return do_fork(®s, 0); } diff --git a/kernel/innerint.c b/kernel/innerint.c index dd49d21..e3acd07 100644 --- a/kernel/innerint.c +++ b/kernel/innerint.c @@ -16,7 +16,6 @@ *-------------------------------------------------------------------------- */ -#include #include #define DIE_MSG() \ @@ -69,41 +68,9 @@ void doGeneralProtection(pt_regs_t regs) { DIE_MSG(); } -void do_no_page(void*); -void do_wp_page(void*); void do_page_fault(pt_regs_t regs) { -#if 0 -US RW P - Description -0 0 0 - Supervisory process tried to read a non-present page entry -0 0 1 - Supervisory process tried to read a page and caused a protection fault -0 1 0 - Supervisory process tried to write to a non-present page entry -0 1 1 - Supervisory process tried to write a page and caused a protection fault -1 0 0 - User process tried to read a non-present page entry -1 0 1 - User process tried to read a page and caused a protection fault -1 1 0 - User process tried to write to a non-present page entry -1 1 1 - User process tried to write a page and caused a protection fault -#endif -#if 0 - bit 0: 0 non-present page entry; 1 protection fault - bit 1: 0 read; 1 write - bit 2: 0 supervisor mode; 1 user mode -#endif - // DIE_MSG(); - void* addr; - u32 errcode = regs.errcode; - - asm("movl %%cr2,%%eax" : "=a"(addr)); - - printk("do page fault errcode %x addr %08x [%08x] %s\n", errcode, addr, current, current->name); - - // assert(errcode != 2 && errcode != 6); - - printk("errcode %x addr %x\n", errcode, addr); - if ((errcode & PAGE_P) == 0) { - do_no_page(addr); - } else { - do_wp_page(addr); - } + void do_page_fault_handler(pt_regs_t); + do_page_fault_handler(regs); } void doCoprocError(pt_regs_t regs) { diff --git a/kernel/page_fault.c b/kernel/page_fault.c new file mode 100644 index 0000000..6b1c5fc --- /dev/null +++ b/kernel/page_fault.c @@ -0,0 +1,135 @@ +/* + * ------------------------------------------------------------------------ + * File Name: page_fault.c + * Author: Zhao Yanbai + * 2026-10-02 09:16:33 Friday CST + * Description: none + * ------------------------------------------------------------------------ + */ + +#include +#include + +#if 0 +// #PF error code + + 31 16 15 7 6 5 4 3 2 1 0 ++----+-----------------------+----+--//--+----+----+----+----+----+----+----+----+ +| RMP| reserved | SGX| |HLAT| SS | PK | I/D|RSVD|U/S |W/R | P | ++----+-----------------------+----+--//--+----+----+----+----+----+----+----+----+ + +bit 0: P 0 - 页不存在; 1 - 页存在(权限/其它违例) +bit 1: W/R 0 - 读/取指; 1 - 写 +bit 2: U/S 0 - 内核态(CPL<3); 1 - 用户态(CPL=3) +bit 3: RSVD 保留位冲突(写了必需为0的页表位) +bit 4: I/D 指令fetch导致的缺页异常。取指令(需PAE+NXE/SMEP支持)(开了NX想区分取指才用) +bit 5: PK 保护密钥违例(需要CR4.PKE=1) +bit 6: SS 影子栈访问(需要CET支持) +bit 7: HLAT HLAT分页找不到翻译(仅VMX+HLAT开启) +bit15: SGX SGX访问违例(需要CR4.SGXE=1) +bit31: RMP SEV-SNP的RMP访问违例(仅SNP开启) +32位非PAE只需关心低4位(P、W/R、U/S、RSVD) + + +1. U/S位永远反映的是触发时的CPL,不反映页表项的属性。用户态触发任何缺页,U/S一定为1 +2. W/R们永远反映的是本次访问的动作,不反映页表项的R/W属性。做写操作就置1,做读/取指就是0 +3. P=1时硬件就已经完成了页表遍历,所有级别的PTE都找到了且P=1。拒绝发生在最后一步权限检查时 -- MMU发现CPL和页表项的U/S不匹配,或发现写操作但页表项的R/W=0 +4. 内核仅靠错误码无法区分"COW写保护"还是"真违规" 必须检查软件元数据 +#endif + +#if 0 +US RW P - Description +0 0 0 - Supervisory process tried to read a non-present page entry +0 0 1 - Supervisory process tried to read a page and caused a protection fault +0 1 0 - Supervisory process tried to write to a non-present page entry +0 1 1 - Supervisory process tried to write a page and caused a protection fault +1 0 0 - User process tried to read a non-present page entry +1 0 1 - User process tried to read a page and caused a protection fault +1 1 0 - User process tried to write to a non-present page entry +1 1 1 - User process tried to write a page and caused a protection fault +#endif + +#if 0 + bit 0: 0 non-present page entry; 1 protection fault + bit 1: 0 read; 1 write + bit 2: 0 supervisor mode; 1 user mode +#endif + +#if 0 +核心判断逻辑: + +STEP 1: P位判定 - 是缺页,还是权限违例 + if (errorcode & PF_P) == 0 : + -> 页面不存在(P == 0),是正常缺页 + -> 需要分配/映射物理页 + else : + -> 页面存在(P == 1),是权限违例 + -> 需要检查PDE/PTE的权限位,是否是写保护导致的 + -> 如果是写保护导致的,则需要进行写时复制(COW)处理 + -> 如果不是写保护导致的,则是非法访问,直接杀死进程 + +STEP 2: 对于缺页的情况 (P == 0),看是谁、怎么触发的 + A: P=0, W/R=0, U/S=0: 内核读不存在的页 + 内核BUG或内核动态映射未建立 + B: P=0, W/R=0, U/S=1: 用户读不存在的页 + 正常缺页,可能是用户访问了未映射的页,需要分配物理页,建立映射 + C: P=0, W/R=1, U/S=0: 内核写不存在的页 + 同上,内核缺页处理 + D: P=0, W/R=1, U/S=1: 用户写不存在的页 + 正常缺页(写时分配),建立映射 + + +STEP 3: 对于权限违例的情况 (P == 1), 细分原因 + A: P=1, W/R=0, U/S=0: 内核读了被保护的页,如不可执行或特殊保护 + 内核BUG, panic + B: P=1, W/R=0, U/S=1: 用户读内核页或NX页 + 段错误 + C: P=1, W/R=1, U/S=0: 内核写只读页 + 内核BUG,或COW场景,内核代表进程写 + D: P=1, W/R=1, U/S=1: 用户写只读页 + COW缺页 或 段错误 (mprotect只读区) + + +STEP 4: 特殊位 + RSVD=1: 页表项保留位被写了1 + 说明页表项被破坏了,内核BUG,直接panic + I/D=1, P=1: 尝试执行不可执行页 + NX违规(PAE下),段错误 + I/D=1, P=0: 取指令时页面不存在 + 代码段缺页,按需加载 +#endif + +#define PF_P (1U << 0) +#define PF_WR (1U << 1) +#define PF_US (1U << 2) +#define PF_RSVD (1U << 3) +#define PF_ID (1U << 4) +#define PF_PK (1U << 5) +#define PF_SS (1U << 6) +#define PF_HLAT (1U << 7) +#define PF_SGX (1U << 15) +#define PF_RMP (1U << 31) + +void do_no_page(vaddr_t vaddr); +void do_wp_page(void*); +void do_page_fault_handler(pt_regs_t regs) { + vaddr_t pf_vaddr; + u32 errcode = regs.errcode; + + asm("movl %%cr2,%%eax" : "=a"(pf_vaddr)); + + printk("#PF: errcode 0x%08x addr 0x%08x [0x%08x] %s\n", errcode, pf_vaddr, current, current->name); + + if (errcode & PF_RSVD) { + panic("#PF: reserved bit set, addr 0x%08x errcode 0xmake%08x\n", pf_vaddr, errcode); + } + + // assert(errcode != 2 && errcode != 6); + if ((errcode & PF_P) == 0) { + printk("#PF: page not present\n"); + do_no_page(pf_vaddr); + } else { + printk("#PF: page protection violation\n"); + do_wp_page((void*)pf_vaddr); + } +} diff --git a/kernel/sched.c b/kernel/sched.c index 2ef444a..f055aee 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -213,7 +213,7 @@ void setup_tasks() { } } -task_t* alloc_task_t() { +task_t* alloc_task() { task_t* task; task = (task_t*)kmem_cache_alloc(task_t_cache, 0); return task; @@ -404,4 +404,4 @@ void clear_need_schedule() { bool need_schedule() { return _need_schedule; -} \ No newline at end of file +} -- 2.55.0