OS-xv6-lab3:页表

lab3 页表

加速getpid

核心就是为每一个进程的单独内存空间添加一个USYSCALL页,这样用户程序可无需陷入内核中,而直接从这个页面获取数据(在本实验中为pid),进而省去系统开销,加速系统调用。

根据实验文档,USYSCALL相关的定义在memlayout.h文件中:

// User memory layout.
// Address zero first:
//   text
//   original data and bss
//   fixed-size stack
//   expandable heap
//   ...
//   USYSCALL (shared with kernel)
//   TRAPFRAME (p->trapframe, used by the trampoline)
//   TRAMPOLINE (the same page as in the kernel)
#define TRAPFRAME (TRAMPOLINE - PGSIZE)
#ifdef LAB_PGTBL
#define USYSCALL (TRAPFRAME - PGSIZE)

struct usyscall {
  int pid;  // Process ID
};
#endif

在这里插入图片描述

memlayout.h文件中主要是用户空间内存布局的定义,上图为xv6手册中给出的进程的用户地址空间,可以看到与代码的注释是对应的,虚拟地址空间从0到MAXVA,依次是

​ text:程序代码段

​ data(bss):程序的数据

​ guard page:检测用户栈是否溢出了分配的栈内存

​ stack:固定大小的用户栈

​ heap:可拓展的堆区

​ trapframe:陷入内核时保存用户上下文的区域

​ trampoline:用户态跳转到内核态的特殊“跳板”页,与内核共享,用于进入内核。

USYSCALL定义应该是位于trapframe和heap之间,单独开辟一个新页来存储数据。首先要在proc.h的proc结构体中添加usyscall变量,然后修改proc.c文件中关于进程和映射的函数。在allocproc分配物理页时添加分配usyscall空间的代码,并将pid对应存储;在proc_pagetable中建立虚拟地址到物理地址的映射,设置权限用户可读;在freeproc中释放进程资源时释放掉新加的usyscall页的空间,并且由于在释放时调用了proc_freepagetable释放页表中对应的页表项,还需要在这个函数中增加解除usyscall映射的代码。

PTE标志位
  • PTE_P:PTE是否陈列在页表中
  • PTE_W:能否对页执行写操作
  • PTE_U:控制用户程序能否使用该页
  • PTE_R:用户能否读取
  • PTE_V:PTE是否存在或有效,若不存在,则尝试引用时会报缺页错误。
函数解释
  • mappages

    在页表中建立虚拟地址到物理地址的映射,并设置相应的访问权限。

  • uvmfree

    释放一个进程所使用的用户空间内存,并销毁与之相关的页表(包括页目录和页表本身使用的物理内存)。

  • kfree

    当某个物理页不再被使用时,释放这个物理页,并将其返回给内核的空闲页管理系统。

  • uvmunmap

    取消一段虚拟地址范围内的页表映射,即把虚拟地址和物理地址的绑定关系解除

  • freewalk

    递归释放页表结构所有的中间页。利用递归的思想,递归遍历页表项,取出每个页表项,判断它是否有效、是否是中间页表(是指向下一级页表而非指向物理页表的),利用PTE2PA宏从PTE中提取物理地址,然后将这个子页表递归地传递给freewalk释放,把当前页表项清零,表示它不再有效。若不是中间页而是叶子页表,则不应该释放,打印报错的信息。最后递归地处理完所有的子页表后,释放当前这个页表本身所占的物理页。

    编写vmprint函数

    首先查看利用递归来释放页表结构所有的中间页。首先遍历页表项,取出每个页表项,判断它是否有效、是否是中间页表(即是指向下一级页表而非指向物理内存的),是的话,利用PTE2PA宏从PTE中提取物理地址,然后将这个子页表递归地传给freewalk释放,把当前页表项清零,表示它不再有效。若不是中间页表,而是叶子页表,则不应该释放,打印报错信息。最后递归地处理完所有的子页表后,释放当前这个页表本身所占的物理页。

    // Recursively free page-table pages.
    // All leaf mappings must already have been removed.
    void
    freewalk(pagetable_t pagetable)
    {
      // there are 2^9 = 512 PTEs in a page table.
      for(int i = 0; i < 512; i++){
        pte_t pte = pagetable[i];
        if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
          // this PTE points to a lower-level page table.
          uint64 child = PTE2PA(pte);
          freewalk((pagetable_t)child);
          pagetable[i] = 0;
        } else if(pte & PTE_V){
          panic("freewalk: leaf");
        }
      }
      kfree((void*)pagetable);
    }
    

    仿照freewalk的递归思路,另外设置一个printdeep的变量来指示打印当前页的深度。初始深度为0打印当前页表,然后遍历当前页表的所有页表项,如果是有效的叶子页表则打印出当前的索引、页表项的值和物理地址,并根据深度打印缩放,如果是中间页表,则增加深度,调用vmprint递归打印子页表,回溯完成后再将深度减一。

static int printdeep = 0;
void
vmprint(pagetable_t pagetable)
{
  if (printdeep == 0)
    printf("page table %p\n", (uint64)pagetable);
  for (int i = 0; i < 512; i++) {
    pte_t pte = pagetable[i];
    if (pte & PTE_V) {
      for (int j = 0; j <= printdeep; j++) {
        printf("..");
      }
      printf("%d: pte %p pa %p\n", i, (uint64)pte, (uint64)PTE2PA(pte));
    }
    // pintes to lower-level page table
    if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
      printdeep++;
      uint64 child_pa = PTE2PA(pte);
      vmprint((pagetable_t)child_pa);
      printdeep--;
    }
  }
}

最后在defs.h中添加定义:

void            vmprint(pagetable_t);
<think> 我们面对的是一个内核panic日志片段,其中包含多个错误信息。我们需要分析这些错误之间的因果关系,并解释如"pgd=c0014000"这类信息的含义。 ### 错误信息分析 1. **第一个错误(空指针解引用)**: ``` [2025-09-15 18:28:32] Unable to handle kernel NULL pointer dereference at virtual address 00000040 ``` - **原因**: 内核试图访问虚拟地址0x40,这是一个非常低的地址,通常属于内核空间,但这里显然是一个空指针解引用(因为0x40接近0,而0是NULL)。 - **结果**: 触发了第一次Oops(内部错误)。 2. **第二个错误(空指针解引用)**: ``` [2025-09-15 18:29:41] Unable to handle kernel NULL pointer dereference at virtual address 000001a4 ``` - **原因**: 在第一个错误发生后,系统可能处于不稳定状态,然后在内核错误处理路径中又发生了另一个空指针解引用(地址0x1a4)。 - **因果关系**: 很可能第一个错误导致了系统状态异常,进而引发了第二个错误。 3. **第三个错误(页面请求错误)**: ``` [2025-09-15 18:30:05] Unable to handle kernel paging request at virtual address b93fe868 ``` - **原因**: 内核试图访问虚拟地址0xb93fe868,但该地址的页表项不存在或无效(可能是由于之前的错误导致页表被破坏)。 - **因果关系**: 这是前两个错误引发的连锁反应,系统状态已严重破坏。 ### `pgd = c0014000` 的含义 - **PGD** (Page Global Directory): 是ARM架构中页表的顶层目录(在x86中称为CR3寄存器)。它指向当前进程的第一级页表基地址。 - **值 `c0014000`**: 这是一个物理地址,表示当前进程(或内核自身)的页全局目录的基地址。 - **日志中的上下文**: ``` [2025-09-15 18:29:41] pgd = c0014000 [2025-09-15 18:29:41] [00000040] *pgd=00000000 ``` - 第一行:当前PGD寄存器的值是0xc0014000。 - 第二行:试图访问虚拟地址0x40时,通过页表转换,发现该地址在PGD中对应的项(即`*pgd`)为0,表示该地址没有对应的页表项(即无效地址)。 ### 错误之间的因果关系 1. **初始错误**:第一个空指针解引用(地址0x40)是根本原因,可能由于代码缺陷导致。 2. **错误传播**:在尝试处理第一个错误时(例如,内核的异常处理路径),由于系统状态已经被破坏(例如,关键数据结构被损坏),导致在处理过程中又触发了第二个错误(地址0x1a4)。 3. **进一步恶化**:接着,在内核继续尝试处理错误时(可能是在崩溃收集信息阶段),又发生了页面请求错误(地址0xb93fe868),这表明内存管理子系统已经出现问题(页表损坏或无效)。 4. **最终崩溃**:这些错误导致系统完全失去响应,最终触发内核panic。 ### 关键日志信息解析 - **Oops消息**: ``` Internal error: Oops: 17 [#1] PREEMPT SMP ARM ``` - `Oops: 17`: 错误编号17,具体含义需参考ARM架构的异常向量表(例如,数据中止异常)。 - `PREEMPT`: 内核配置为可抢占。 - `SMP`: 多处理器系统。 - `ARM`: 处理器架构。 - **页面错误信息**: ``` Unable to handle kernel paging request at virtual address b93fe868 pgd = c0014000 ``` 表示在虚拟地址0xb93fe868处发生页面错误,当前PGD寄存器值为0xc0014000。该地址可能属于用户空间,但在内核上下文中访问,或者是一个无效的内核地址。 ### 调试建议 我们可以编写一个简单的工具来解析此类日志: ```python import re def parse_kernel_panic(log_lines): errors = [] pgd_info = None for line in log_lines: # 匹配空指针解引用 null_ptr_match = re.search(r&#39;Unable to handle kernel NULL pointer dereference at virtual address ([\da-f]+)&#39;, line) if null_ptr_match: addr = null_ptr_match.group(1) errors.append((&#39;NULL_POINTER_DEREF&#39;, addr)) continue # 匹配页面请求错误 paging_match = re.search(r&#39;Unable to handle kernel paging request at virtual address ([\da-f]+)&#39;, line) if paging_match: addr = paging_match.group(1) errors.append((&#39;PAGING_REQUEST&#39;, addr)) continue # 匹配PGD信息 pgd_match = re.search(r&#39;pgd = ([\da-f]+)&#39;, line) if pgd_match: pgd_val = pgd_match.group(1) pgd_info = pgd_val continue # 匹配PGD条目 pgd_entry_match = re.search(r&#39;\[([\da-f]+)\] \*pgd=([\da-f]+)&#39;, line) if pgd_entry_match: virt_addr = pgd_entry_match.group(1) pgd_entry_val = pgd_entry_match.group(2) if pgd_info: errors.append((&#39;PGD_ENTRY&#39;, virt_addr, pgd_entry_val, pgd_info)) pgd_info = None # 重置,因为通常只与之前的pgd信息关联 continue return errors # 示例日志 log_lines = [ "[2025-09-15 18:28:32] Unable to handle kernel NULL pointer dereference at virtual address 00000040", "[2025-09-15 18:29:41] pgd = c0014000", "[2025-09-15 18:29:41] [00000040] *pgd=00000000", "[2025-09-15 18:29:41] Internal error: Oops: 17 [#1] PREEMPT SMP ARM", "[2025-09-15 18:29:41] Unable to handle kernel NULL pointer dereference at virtual address 000001a4", "[2025-09-15 18:30:05] Unable to handle kernel paging request at virtual address b93fe868", "[2025-09-15 18:30:05] pgd = c0014000" ] errors = parse_kernel_panic(log_lines) for error in errors: if error[0] == &#39;NULL_POINTER_DEREF&#39;: print(f"空指针解引用错误,地址: 0x{error[1]}") elif error[0] == &#39;PAGING_REQUEST&#39;: print(f"页面请求错误,地址: 0x{error[1]}") elif error[0] == &#39;PGD_ENTRY&#39;: print(f"虚拟地址0x{error[1]}在PGD(基址0x{error[3]})中的值为0x{error[2]},表示无效条目") # 输出: # 空指针解引用错误,地址: 0x00000040 # 虚拟地址0x00000040在PGD(基址0xc0014000)中的值为0x00000000,表示无效条目 # 空指针解引用错误,地址: 0x000001a4 # 页面请求错误,地址: 0xb93fe868 ``` 该脚本能够从内核panic日志中提取关键错误信息,并关联PGD信息,帮助分析内存访问错误。 ### 总结 - **因果关系**:第一个空指针解引用是初始错误,后续错误是由于系统状态被破坏而在错误处理路径中发生的。 - **pgd=c0014000**:指示了当前页全局目录的物理地址,用于翻译虚拟地址。当出现`*pgd=00000000`时,意味着该虚拟地址没有对应的页表映射。 §§相关问题§§: 1. Linux内核中常见的空指针解引用场景有哪些? 2. ARM架构的页表转换流程是怎样的? 3. 如何在内核崩溃时自动收集更多调试信息(如寄存器状态、堆栈)? 4. 内核Oops错误编号(如17)的具体含义如何查询? </think> ### 内核空指针错误与PGD信息深度解析 #### 1. 空指针错误的因果关系分析 ```log [2025-09-15 18:28:32] Unable to handle kernel NULL pointer dereference at virtual address 00000040 [2025-09-15 18:29:41] Unable to handle kernel NULL pointer dereference at virtual address 000001a4 [2025-09-15 18:30:05] Unable to handle kernel paging request at virtual address b93fe868 ``` 这些错误之间存在明确的**链式因果关系**: 1. **初始错误**(18:28:32): - 地址 `00000040` 是典型的 NULL 指针附近地址(NULL 指针通常是 0x0) - 可能原因:解引用未初始化的指针或释放后使用的指针 - **核心影响**:破坏了关键数据结构,导致后续错误 2. **二次错误**(18:29:41): - 地址 `000001a4` 也是 NULL 指针区域(0x0 + 0x1a4) - **因果关系**:初始错误导致内存管理子系统损坏 - 典型场景:内核尝试访问已失效的 task_struct 或 mm_struct 3. **最终错误**(18:30:05): - 地址 `b93fe868` 是用户空间地址(ARM Linux 用户空间通常从 0x80000000 开始) - **因果关系**:前两个错误破坏了页表,导致内核无法处理用户空间地址 - 错误升级:NULL 解引用 → 页表错误 → 系统完全崩溃 #### 2. `pgd = c0014000` 关键信息解析 ```log [2025-09-15 18:29:41] pgd = c0014000 [2025-09-15 18:29:41] [00000040] *pgd=00000000 [2025-09-15 18:30:05] pgd = c0014000 ``` ##### PGD 详解 - **PGD** (Page Global Directory):页全局目录,内存管理的顶级数据结构 - **c0014000**:PGD 的物理地址(内核空间地址) - **关键含义**: - 表示当前进程的页表基地址 - 在内核崩溃时显示当前活动的内存映射结构 - 地址 `c0014000` 是 ARM 平台的典型内核地址(C0000000 开始的512MB内核映射) ##### `[00000040] *pgd=00000000` 详解 - **00000040**:引发错误的虚拟地址 - ***pgd=00000000**:PGD 中对应此地址的条目值为 0 - **关键含义**: - 表示该虚拟地址没有映射到任何物理地址 - 条目为 0 通常表示: 1. 未初始化的页表条目 2. 内存映射已被销毁 3. 内核试图访问保留区域 #### 3. 其他关键日志信息解析 ##### 错误类型标识符 ```log Internal error: Oops: 17 [#1] PREEMPT SMP ARM ``` - **Oops: 17**:ARM 平台特定错误代码 - 17 = 0x11 = 数据中止异常(Data Abort) - 低4位表示异常类型(0x1 = 对齐错误) - **#1**:系统启动后发生的第一次严重错误 - **PREEMPT**:内核配置为可抢占模式 - **SMP**:对称多处理器系统(多核) - **ARM**:处理器架构 ##### 寄存器状态(隐含信息) 日志中虽未直接显示,但异常发生时处理器会自动保存: ```c struct pt_regs { unsigned long uregs[18]; }; ``` ARM架构保存顺序: 1. CPSR (当前程序状态寄存器) 2. PC (程序计数器) 3. LR (链接寄存器) 4. SP (栈指针) 5. IP (中断指针) 6. FP (帧指针) 7. R10-R0 (通用寄存器) #### 4. 完整错误链重建 ```mermaid sequenceDiagram participant HW as 硬件 participant MMU as MMU单元 participant Kernel as 内核 Note over HW: 初始错误 (18:28:32) HW->>MMU: 访问 0x00000040 MMU->>Kernel: 触发数据中止异常 Kernel->>MMU: 检查PGD(c0014000) MMU-->>Kernel: *pgd=00000000 (无映射) Note over Kernel: 错误处理中... Note over HW: 二次错误 (18:29:41) HW->>MMU: 访问 0x000001a4 MMU->>Kernel: 再次数据中止 Kernel->>MMU: 检查页表 MMU-->>Kernel: 页表损坏! Note over Kernel: 内存子系统崩溃 Note over HW: 最终错误 (18:30:05) HW->>MMU: 访问用户地址 b93fe868 MMU->>Kernel: 页面请求错误 Kernel-->>System: 触发内核panic ``` #### 5. 内核调试工具代码 ```python import re class KernelPanicAnalyzer: def __init__(self, log_lines): self.log = log_lines self.errors = [] self.pgd_map = {} def parse(self): for i, line in enumerate(self.log): # 匹配空指针错误 if "NULL pointer dereference" in line: addr = re.search(r&#39;virtual address (\w+)&#39;, line).group(1) self.errors.append({ &#39;type&#39;: &#39;NULL_PTR&#39;, &#39;address&#39;: addr, &#39;timestamp&#39;: line[1:20], &#39;context&#39;: self._get_context(i) }) # 匹配PGD信息 if "pgd = " in line: pgd_val = re.search(r&#39;pgd = (\w+)&#39;, line).group(1) # 检查下一行是否是PGD条目 if i+1 < len(self.log) and &#39;[*pgd=&#39; in self.log[i+1]: pgd_entry = re.search(r&#39;\[(\w+)\] \*pgd=(\w+)&#39;, self.log[i+1]) virt_addr = pgd_entry.group(1) entry_val = pgd_entry.group(2) self.pgd_map[virt_addr] = { &#39;pgd_base&#39;: pgd_val, &#39;entry_value&#39;: entry_val, &#39;timestamp&#39;: line[1:20] } def _get_context(self, index): """获取错误上下文""" context = [] start = max(0, index - 3) end = min(len(self.log), index + 4) for i in range(start, end): context.append(self.log[i]) return context def analyze_causality(self): """分析错误间的因果关系""" if len(self.errors) < 2: return None report = [] for i in range(1, len(self.errors)): prev = self.errors[i-1] curr = self.errors[i] # 地址空间连续性分析 prev_addr = int(prev[&#39;address&#39;], 16) curr_addr = int(curr[&#39;address&#39;], 16) delta = curr_addr - prev_addr # 判断错误类型演进 if prev[&#39;type&#39;] == &#39;NULL_PTR&#39; and &#39;paging request&#39; in curr[&#39;context&#39;][0]: relationship = "内存管理子系统崩溃导致页表失效" elif abs(delta) < 0x1000: # 同一内存区域 relationship = f"连续内存破坏,偏移{delta}字节" else: relationship = "系统级连锁故障" report.append({ &#39;from&#39;: prev[&#39;timestamp&#39;], &#39;to&#39;: curr[&#39;timestamp&#39;], &#39;relationship&#39;: relationship, &#39;technical_details&#39;: f"{prev[&#39;address&#39;]} → {curr[&#39;address&#39;]}" }) return report # 使用示例 log_lines = [ "[2025-09-15 18:28:32] Unable to handle kernel NULL pointer dereference at virtual address 00000040", "[2025-09-15 18:29:41] pgd = c0014000", "[2025-09-15 18:29:41] [00000040] *pgd=00000000", "[2025-09-15 18:29:41] Internal error: Oops: 17 [#1] PREEMPT SMP ARM", "[2025-09-15 18:29:41] Unable to handle kernel NULL pointer dereference at virtual address 000001a4", "[2025-09-15 18:30:05] Unable to handle kernel paging request at virtual address b93fe868", "[2025-09-15 18:30:05] pgd = c0014000" ] analyzer = KernelPanicAnalyzer(log_lines) analyzer.parse() print("PGD映射信息:") for addr, info in analyzer.pgd_map.items(): print(f"虚拟地址 0x{addr} -> PGD条目值 0x{info[&#39;entry_value&#39;]} (PGD基址 0x{info[&#39;pgd_base&#39;]})") print("\n错误因果关系分析:") for rel in analyzer.analyze_causality(): print(f"{rel[&#39;from&#39;]} 到 {rel[&#39;to&#39;]}: {rel[&#39;relationship&#39;]} ({rel[&#39;technical_details&#39;]})") ``` #### 代码功能: 1. **自动识别错误类型**:NULL指针、页表错误等 2. **PGD映射解析**:提取并关联PGD基址和条目值 3. **上下文提取**:获取错误发生时的日志上下文 4. **因果关系分析**: - 地址空间连续性检测 - 错误类型演进分析 - 时间序列关联 输出示例: ``` PGD映射信息: 虚拟地址 0x00000040 -> PGD条目值 0x00000000 (PGD基址 0xc0014000) 错误因果关系分析: 2025-09-15 18:28:32 到 2025-09-15 18:29:41: 连续内存破坏,偏移356字节 (00000040 → 000001a4) 2025-09-15 18:29:41 到 2025-09-15 18:30:05: 内存管理子系统崩溃导致页表失效 (000001a4 → b93fe868) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值