#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 表示写缓存同时写内存,慢但内存总是新的。
// 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一致性。
// 3. 写时复制里记录"这是共享只读页"
// 4. 页面锁标记,禁止换出
-
// x
// |<------ 31~12------>|<------ 11~0 --------->| 比特
#define MAX_ORDER (11)
-
static inline pde_t* get_pgd() {
return (pde_t*)pa2va((read_cr3() & PAGE_MASK));
}
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);
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;
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;
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) {
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);
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;
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);
}
*--------------------------------------------------------------------------
*/
-#include <sched.h>
#include <system.h>
#define DIE_MSG() \
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) {
--- /dev/null
+/*
+ * ------------------------------------------------------------------------
+ * 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);
+ }
+}
}
}
-task_t* alloc_task_t() {
+task_t* alloc_task() {
task_t* task;
task = (task_t*)kmem_cache_alloc(task_t_cache, 0);
return task;
bool need_schedule() {
return _need_schedule;
-}
\ No newline at end of file
+}