]> Zhao Yanbai Git Server - kernel.git/commitdiff
在注释中厘清缺页异常#PF的各种情况
authorzhaoyanbai <zhaoyanbai@126.com>
Sat, 3 Oct 2026 04:34:55 +0000 (12:34 +0800)
committerzhaoyanbai <zhaoyanbai@126.com>
Sat, 3 Oct 2026 04:34:55 +0000 (12:34 +0800)
include/page.h
include/task.h
include/types.h
kernel/fork.c
kernel/innerint.c
kernel/page_fault.c [new file with mode: 0644]
kernel/sched.c

index c2eb14ecaf8a9a4ada69af148a3bb22c94ffa3b8..ef8bf8725550ffb1efa5c63d826cf9a9e1bf625b 100644 (file)
@@ -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)
 #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);
index 731239dd3f03b866b671ef252a323f04218d8838..574a68650bd5d5e0c8b674d6a8ba4bac391c0f65 100644 (file)
@@ -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;
index 3a5a879a378b90b74bdfe2b0b06f83177e836101..7c02bf70cce6a5e2c1e97568fc09f03b58c6079d 100644 (file)
@@ -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;
index b51ab4751538d95cf96c971f703066cc6b9ecc5d..43160db9a7de738381039ab9c27427ca601df401 100644 (file)
 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(&regs, 0);
 }
index dd49d2118479e18582b1a006cb859505c95a741e..e3acd070adfafeda34aa0f1b515aa87ecb707fda 100644 (file)
@@ -16,7 +16,6 @@
  *--------------------------------------------------------------------------
  */
 
-#include <sched.h>
 #include <system.h>
 
 #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 (file)
index 0000000..6b1c5fc
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * ------------------------------------------------------------------------
+ *   File Name: page_fault.c
+ *      Author: Zhao Yanbai
+ *              2026-10-02 09:16:33 Friday CST
+ * Description: none
+ * ------------------------------------------------------------------------
+ */
+
+#include <sched.h>
+#include <system.h>
+
+#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);
+    }
+}
index 2ef444a35a4782f88dd8b6158c2f18824067a115..f055aee1806438bdcdf19239694a137e6a042966 100644 (file)
@@ -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
+}