Jas*_*ung 2 c linux copy-on-write linux-kernel
源代码在这里:
#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();
}
Run Code Online (Sandbox Code Playgroud)
这里是一个线,写的GDB拆机33至一个,我在这里设置一个断点。并启动该程序。然后,使用崩溃看到的物理地址一
>?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
Run Code Online (Sandbox Code Playgroud)
的线性地址一个是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
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
我的问题是cpu执行时会发生什么
movl $0x2c,-0x20(%rbp)
Run Code Online (Sandbox Code Playgroud)
内核如何知道它正在更改共享内存,因此需要在写入之前执行应对措施?我猜它正在使用类似页面错误中断的东西。但是我没有发现与此有关的任何中断。
如果由内核负责,请提供内核的源代码。
我的问题是cpu执行时会发生什么
movl $ 0x2c,-0x20(%rbp)
内核如何知道它正在更改共享内存,因此需要在写入之前执行应对措施?我猜它正在使用类似页面错误中断的东西。但是我没有发现与此有关的任何中断。
这是通过处理器和OS的共同努力来实现的。
处理器端:
当CPU执行这样的指令时:
movl $ 0x2c,-0x20(%rbp)
例如,获取存储在%rbp寄存器中的地址,并向其添加偏移量-x20,然后发出对其的内存访问(移动)。
提交内存访问后,处理器将遍历硬件页表(嗯,在大多数情况下,通过访问TLB是捷径,但是我在这里只讲基本原理)。当然,页表应该由操作系统预先设置。
假设处理器进入最终级别的页面表,只是发现该地址的相应页面表条目(此答案的其余部分简称为pte)表明包含该地址内容的页面不在内存!(它只查询该pte的特定页面标志),然后根据处理器体系结构引发硬件异常!根据Intel的术语,它将此类异常归类为Fault,并且您必须经常听到“ page fault”一词(一种可以修复的异常,可以恢复执行,就好像没有发生此类异常一样)。根本!)
操作系统方面:
然后,我们将堆栈向上移至OS域。在引导过程中,操作系统将设置一个异常和中断处理程序表(在x86术语中,我们称为IDT),并将其注册到处理器。
然后在发生此页面错误时,处理器将执行预设置处理程序(从技术上讲,处理器应首先保存CPU上下文,例如推送cs和rip寄存器,rflags寄存器等)。
处理程序可以分为特定于架构的部分(操作系统将进一步执行一些与硬件相关的工作,例如保存更多的寄存器,调用特定于架构的钩子,确定是否允许页面错误等)和独立于架构的部分(页面错误逻辑),因此处理程序入口点取决于体系结构也就不足为奇了。
对于x86上的Linux,特定于arch的部分位于 arch / x86 / entry / entry_64.S(用于64位)中,而do_page_fault()C函数位于arch / x86 / mm / fault.c中。然后在do_page_fault()中,它将调用与拱无关的C函数handle_mm_fault(),该函数位于核心MM代码中的mm / memory.c处。
对于这个问题,在handle_mm_fault()中,do_wp_page()处理COW逻辑。基本上,handle_mm_fault()只是遍历故障地址的页表,并发现它是一个写保护页(存在,但未设置写标志),因此它调用do_wp_page()来分配新页。