Linux内核如何检测内存地址是否被修改以实现COW?

时间:2016-06-01 10:13:23

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

源代码在这里:

 #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反汇编, 我在这里设置了一个断点。并启动此计划。然后使用崩溃查看 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)

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

如果内核负责此操作,请提供内核的源代码。

1 个答案:

答案 0 :(得分:5)

  

我的问题是当cpu执行时发生的事情

     

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

     

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

这是通过处理器和OS的协同努力实现的。

处理方:

当CPU执行这样的指令时:

  

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

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

在提交内存访问权限后,处理器将遍历硬件页面表(在大多数情况下,通过访问TLB来缩短它,但我只是在这里谈基本原理)。当然,页面表应该由OS预先设置。

假设处理器进入最终级别的页面表,并且只是找到相应的页面表条目(对于该答案的其余部分,将简称为 pte ) 建议包含该地址内容的页面不在内存中!(它只是查询该pte的特定页面标志),然后,根据处理器体系结构,引发硬件异常!根据英特尔术语,它将此类异常分类为错误,您必须经常听到&#39;页面错误这一术语(一种例外情况)可以修复,执行可以恢复,好像根本没有发生过这样的例外!)

操作系统方面:

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

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

可以将处理程序划分为特定于arch的部分(OS将进一步执行一些与硬件相关的作业,例如保存更多寄存器,调用特定于arch的挂钩,确定是否允许页面错误等)和arch -independent part(页面错误逻辑),因此处理程序入口点与arch相关并不奇怪。

对于x86上的Linux,特定于arch的部分位于 arch / x86 / entry / entry_64.S (对于64位)和 arch /中的do_page_fault()C函数86 /毫米/ 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()来分配新页面。