【问题标题】:How does the Linux kernel detect if a memory address was modified to implement COW?Linux内核如何检测内存地址是否被修改以实现COW?
【发布时间】:2016-09-30 15:35:33
【问题描述】:

源码在这里:

 #include <stdio.h>
 #include <stdlib.h>

 void main() {
     int *a = malloc(sizeof(int));
     *a = 11;
     int b = 22;//on the stack

     int pid = fork();

     if (pid == 0) {
         printf("pid=%d, a = %d, &a=%p\n", getpid(), *a, a);
         printf("pid=%d, b = %d, &b=%p\n", getpid(), b, &b);
         getchar();
         *a = 33;// ===========cow=========happend here=====
         b = 44;
         printf("pid=%d, a = %d, &a=%p\n", getpid(), *a, a);
         printf("pid=%d, b = %d, &b=%p\n", getpid(), b, &b);
     } else {
         printf("pid=%d, a = %d, &a=%p\n", getpid(), *a, a);
         printf("pid=%d, b = %d, &b=%p\n", getpid(), b, &b);
     }
     pause();
 }

这是将 33 写入 a 的那一行的 gdb 反汇编, 我在这里设置了一个断点。并启动这个程序。然后使用crash查看a

的物理地址
  >│0x40073a <main+154>     movl   $0x2c,-0x20(%rbp)  //copy on write happend here                                                                                                                              │
   │0x400741 <main+161>     mov    -0x18(%rbp),%rax                                                                                                                                 │
   │0x400745 <main+165>     mov    (%rax),%ebx

a的线性地址是0x602010,所以使用vtop,我得到了:

我们可以看到它们都指向2a683010

的同一个物理地址
    PID: 6468
COMMAND: "a.out"
   TASK: ffff88007c317300  [THREAD_INFO: ffff880016728000]
    CPU: 0
  STATE: TASK_TRACED|TASK_WAKEKILL
crash> vtop 0x602010
VIRTUAL     PHYSICAL
602010      2a683010

   PML: 24e6f000 => 2409c067
   PUD: 2409c000 => 7144067
   PMD: 7144018 => 19847067
   PTE: 19847010 => 800000002a683065
  PAGE: 2a683000



    PID: 6464
COMMAND: "a.out"
   TASK: ffff880036992280  [THREAD_INFO: ffff880014e38000]
    CPU: 0
  STATE: TASK_TRACED|TASK_WAKEKILL
crash> vtop 0x602010
VIRTUAL     PHYSICAL
602010      2a683010

   PML: 36df9000 => 3a654067
   PUD: 3a654000 => 1a71a067
   PMD: 1a71a018 => 18f2a067
   PTE: 18f2a010 => 800000002a683065
  PAGE: 2a683000

在 gdb 中键入 ni(将 a 的值更改为 33)后,再次使用 vtop。我可以看到其中一个进程的物理地址发生了变化。

crash> vtop 0x602010
VIRTUAL     PHYSICAL
602010      5d755010

   PML: 24e6f000 => 2409c067
   PUD: 2409c000 => 7144067
   PMD: 7144018 => 19847067
   PTE: 19847010 => 800000005d755067
  PAGE: 5d755000



crash> vtop 0x602010
VIRTUAL     PHYSICAL
602010      2a683010

   PML: 36df9000 => 3a654067
   PUD: 3a654000 => 1a71a067
   PMD: 1a71a018 => 18f2a067
   PTE: 18f2a010 => 800000002a683065
  PAGE: 2a683000

我的问题是cpu执行时发生了什么

movl   $0x2c,-0x20(%rbp)

内核如何知道它正在更改共享内存,因此在写入之前需要执行应对?我猜它正在使用诸如页面错误中断之类的东西。但我没有发现任何与此相关的中断。

如果内核负责,请提供内核源代码。

【问题讨论】:

标签: c linux linux-kernel copy-on-write


【解决方案1】:

我的问题是cpu执行时发生了什么

movl $0x2c,-0x20(%rbp)

内核如何知道它正在更改共享内存,因此在写入之前需要执行应对?我猜它正在使用诸如页面错误中断之类的东西。但我没有发现任何与此相关的中断。

这是通过处理器和操作系统的合作来实现的。

处理器方面:

当CPU执行这样一条指令时:

movl $0x2c,-0x20(%rbp)

即获取存储在 %rbp 寄存器中的地址并向其添加偏移量 -x20,然后对其进行内存访问(movl)。

在提交内存访问时,处理器将遍历硬件页表(嗯,在大多数情况下,它是访问 TLB 的捷径,但我这里只讲基本原理)。当然,页表应该由操作系统预先设置。

假设处理器进入最后一级页表,并找到该地址对应的页表条目(将在此答案的其余部分简称为 pte) 表明包含该地址内容的页面不在内存中!(它只是咨询该 pte 的特定页面标志),然后,根据处理器架构,引发硬件异常!根据 Intel 的术语,它将此类异常归类为 fault,您必须经常听到术语 'page fault'(一种可以修复的异常可以继续执行,就好像根本没有发生这种异常一样!)

操作系统方面:

然后我们将堆栈向上移动到 OS 域。在启动过程中,操作系统会建立一个异常和中断处理表(在x86术语中我们称之为IDT),并将其注册到处理器。

然后在发生此页面错误时,处理器会执行预设置处理程序(从技术上讲,处理器应首先保存 CPU 上下文,如推送 cs 和 rip 寄存器、rflags 寄存器等)。

handler可以分为arch-specific部分(操作系统会进一步做一些硬件相关的工作,比如保存更多的寄存器,调用arch-specific hook,判断是否允许page fault?等等)和arch - 独立部分(页面错误逻辑),因此处理程序入口点依赖于拱门也就不足为奇了。

对于 x86 上的 Linux,arch 特定部分位于 arch/x86/entry/entry_64.S(对于 64 位)和 arch/ 中的 do_page_fault() C 函数x86/mm/fault.c。然后在do_page_fault()中,调用独立于arch的C函数handle_mm_fault(),该函数位于mm/memory.c的核心MM代码中。

对于这个问题,在handle_mm_fault() 中,do_wp_page() 处理COW 逻辑。基本上,handle_mm_fault() 只是遍历故障地址的页表,发现它是一个写保护页(存在,但没有设置写标志),所以它调用 do_wp_page() 来分配一个新页。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 1970-01-01
    • 2017-04-26
    • 2016-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多