为什么不解决分叉过程中的变化?
Why doesn't address change in forked process?
我正在尝试理解 fork()
和进程地址 space。我编写了一个基本的概念验证程序,该程序分叉一个新进程并更改新进程中的一个变量。我的期望是,当我更改子变量中的变量时,这应该会导致该变量获得新地址。如果我理解正确,Linux 使用 fork 进行写时复制。因此,我希望父项和子项中的变量地址匹配,直到我在其中一个中更改它。然后我希望他们有所不同。然而,这不是我所看到的。
这是因为写时复制从物理内存分配了一个新页面,但进程地址 space 没有改变 - 只是被 TLB 重新映射到新页面?还是我不理解这一点或在我的程序中犯了转储错误?
概念验证代码:
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void describe(const std::string &descr, const int &data) {
pid_t ppid = getppid();
pid_t pid = getpid();
std::cout << "In " << descr << ":\n"
<< "Parent Process ID: " << ppid
<< "\nMy Process ID: " << pid
<< "\nValue of data: " << data
<< "\nAddress of data: " << &data << "\n\n";
}
void change(int &data) {
// Should cause data to get new page frame:
data *= 2;
}
int main () {
int data = 42;
int status;
pid_t pid = fork();
switch(pid) {
case -1:
std::cerr << "Error: Failed to successfully fork a process.\n";
exit(1);
break;
case 0:
// In forked child
describe("Child", data);
// Lazy way to wait for parent to run describe:
usleep(1'000);
break;
default:
// In calling parent
describe("Parent", data);
// Lazy way to wait for child to run describe:
usleep(1'000);
}
if (pid == 0) {
std::cout << "Only change data in child...\n";
change(data);
describe("Child", data);
} else {
// Lazy way to wait for child to change data:
usleep(1'000);
describe("Parent", data);
}
// Wait for child:
if (pid != 0) {
wait(&status);
}
return 0;
}
示例运行:
ubuntuvm:~$ ./example
In Parent:
Parent Process ID: 265569
My Process ID: 316986
Value of data: 42
Address of data: 0x7fffb63878d4
In Child:
Parent Process ID: 316986
My Process ID: 316987
Value of data: 42
Address of data: 0x7fffb63878d4
Only change data in child...
In Child:
Parent Process ID: 316986
My Process ID: 316987
Value of data: 84
Address of data: 0x7fffb63878d4
In Parent:
Parent Process ID: 265569
My Process ID: 316986
Value of data: 42
Address of data: 0x7fffb63878d4
My expectation was that when I change a variable in the child, this should cause that variable to get a new address.
不是,因为它们是虚拟地址。
If I understand correctly, Linux does copy-on-write with fork. So I would expect the variable address in the parent and child to match until I change it in one of them.
某处将使用新的物理页面,但虚拟地址可以(并且将)保持不变。
Is this because with copy-on-write a new page is allocated from physical memory, but the process address space is unchanged - just remapped to the new page by the TLB?
当然可以。否则它的用处就大打折扣了。如果它像你说的那样工作,那么考虑一下你在分叉之前的任何指针都会突然失效。把代码想象成这样简单:
int * p = new int;
if (!fork()) {
// the child
*p = 42;
// now `p` is invalid since we wrote to it?!
// another read or write would segfault!
*p = 43;
}
在某种程度上,这就像在其中一个平台(对我们来说是页面)踩一次就会掉下来的游戏中进行现场直播。很好玩! :)
我们可以检查通过让操作系统或 CPU 用新地址重写(以某种方式)您的指针来解决问题,以保持一切正常。
然而,即使那是可能的,我们还有更多问题。例如,您需要处理覆盖多个页面的分配。想象一下堆栈(假设 Linux 在 fork()
上也为堆栈执行 CoW)。一旦向堆栈写入任何内容,您就必须更新堆栈指针并复制所有页面,而不仅仅是修改后的页面。
然后我们必须解决间接指针和不指向分配的数据结构中的指针等。如果不跟踪哪些寄存器和指针需要为每个可能的未来写入更新(或有一些正如@R 提到的那样,C 指针的整体实现不同——寄存器等也相同)。
我正在尝试理解 fork()
和进程地址 space。我编写了一个基本的概念验证程序,该程序分叉一个新进程并更改新进程中的一个变量。我的期望是,当我更改子变量中的变量时,这应该会导致该变量获得新地址。如果我理解正确,Linux 使用 fork 进行写时复制。因此,我希望父项和子项中的变量地址匹配,直到我在其中一个中更改它。然后我希望他们有所不同。然而,这不是我所看到的。
这是因为写时复制从物理内存分配了一个新页面,但进程地址 space 没有改变 - 只是被 TLB 重新映射到新页面?还是我不理解这一点或在我的程序中犯了转储错误?
概念验证代码:
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void describe(const std::string &descr, const int &data) {
pid_t ppid = getppid();
pid_t pid = getpid();
std::cout << "In " << descr << ":\n"
<< "Parent Process ID: " << ppid
<< "\nMy Process ID: " << pid
<< "\nValue of data: " << data
<< "\nAddress of data: " << &data << "\n\n";
}
void change(int &data) {
// Should cause data to get new page frame:
data *= 2;
}
int main () {
int data = 42;
int status;
pid_t pid = fork();
switch(pid) {
case -1:
std::cerr << "Error: Failed to successfully fork a process.\n";
exit(1);
break;
case 0:
// In forked child
describe("Child", data);
// Lazy way to wait for parent to run describe:
usleep(1'000);
break;
default:
// In calling parent
describe("Parent", data);
// Lazy way to wait for child to run describe:
usleep(1'000);
}
if (pid == 0) {
std::cout << "Only change data in child...\n";
change(data);
describe("Child", data);
} else {
// Lazy way to wait for child to change data:
usleep(1'000);
describe("Parent", data);
}
// Wait for child:
if (pid != 0) {
wait(&status);
}
return 0;
}
示例运行:
ubuntuvm:~$ ./example
In Parent:
Parent Process ID: 265569
My Process ID: 316986
Value of data: 42
Address of data: 0x7fffb63878d4
In Child:
Parent Process ID: 316986
My Process ID: 316987
Value of data: 42
Address of data: 0x7fffb63878d4
Only change data in child...
In Child:
Parent Process ID: 316986
My Process ID: 316987
Value of data: 84
Address of data: 0x7fffb63878d4
In Parent:
Parent Process ID: 265569
My Process ID: 316986
Value of data: 42
Address of data: 0x7fffb63878d4
My expectation was that when I change a variable in the child, this should cause that variable to get a new address.
不是,因为它们是虚拟地址。
If I understand correctly, Linux does copy-on-write with fork. So I would expect the variable address in the parent and child to match until I change it in one of them.
某处将使用新的物理页面,但虚拟地址可以(并且将)保持不变。
Is this because with copy-on-write a new page is allocated from physical memory, but the process address space is unchanged - just remapped to the new page by the TLB?
当然可以。否则它的用处就大打折扣了。如果它像你说的那样工作,那么考虑一下你在分叉之前的任何指针都会突然失效。把代码想象成这样简单:
int * p = new int;
if (!fork()) {
// the child
*p = 42;
// now `p` is invalid since we wrote to it?!
// another read or write would segfault!
*p = 43;
}
在某种程度上,这就像在其中一个平台(对我们来说是页面)踩一次就会掉下来的游戏中进行现场直播。很好玩! :)
我们可以检查通过让操作系统或 CPU 用新地址重写(以某种方式)您的指针来解决问题,以保持一切正常。
然而,即使那是可能的,我们还有更多问题。例如,您需要处理覆盖多个页面的分配。想象一下堆栈(假设 Linux 在 fork()
上也为堆栈执行 CoW)。一旦向堆栈写入任何内容,您就必须更新堆栈指针并复制所有页面,而不仅仅是修改后的页面。
然后我们必须解决间接指针和不指向分配的数据结构中的指针等。如果不跟踪哪些寄存器和指针需要为每个可能的未来写入更新(或有一些正如@R 提到的那样,C 指针的整体实现不同——寄存器等也相同)。