[英]Why doesn't address change in forked process?
我正在嘗試了解fork()
和進程地址空間。 我編寫了一個基本的概念驗證程序,該程序分叉一個新進程並更改新進程中的變量。 我的期望是,當我更改孩子中的變量時,這應該會導致該變量獲得新地址。 如果我理解正確,Linux 會使用 fork 進行寫時復制。 所以我希望父級和子級中的變量地址匹配,直到我在其中之一中更改它。 那么我希望他們會有所不同。 然而,這不是我所看到的。
這是因為寫時復制從物理內存中分配了一個新頁面,但進程地址空間沒有改變 - 只是由 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
我的期望是,當我更改孩子中的變量時,這應該會導致該變量獲得新地址。
不,因為它們是虛擬地址。
如果我理解正確,Linux 會使用 fork 進行寫時復制。 所以我希望父級和子級中的變量地址匹配,直到我在其中之一中更改它。
將在某處使用新的物理頁面,但虛擬地址可以(並且將)保持不變。
這是因為寫時復制從物理內存中分配了一個新頁面,但進程地址空間沒有改變 - 只是由 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)。 一旦您向堆棧寫入任何內容,您就必須更新堆棧指針並復制所有頁面,而不僅僅是修改后的頁面。
然后我們必須解決數據結構中不指向分配等的間接指針和指針等問題。如果不跟蹤每個可能的未來寫入需要更新哪些寄存器和指針(或對 C 指針有一些不同的實現),似乎不可能解決總體而言,正如@R 提到的一樣——寄存器等也是如此)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.