[英]How to handle pointers in shared memory?
我正在了解進程之間的 memory 共享。 我知道shared memory中的指針有問題,但是我還是想看看shared memory中出現指針時會發生什么。所以我寫了下面的代碼進行測試。
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <semaphore.h>
#include <unistd.h>
#define INIT( i, u, v ) sem_init(&i, u, v)
#define P( i ) sem_wait(&i)
#define V( i ) sem_post(&i)
struct share_mem {
char buf[10];
sem_t s;
char* ptr;
};
int main() {
int shm_id = shmget(getpid(), sizeof(share_mem), IPC_CREAT|0600);
void* ptr = shmat(shm_id, 0, 0);
share_mem *shared = (share_mem *)ptr;
shared->ptr = NULL;
INIT(shared->s, 1, 1);
pid_t pid = fork();
if (pid > 0) {
wait(0);
P(shared->s);
if (shared->ptr == NULL) {
shared->ptr = new char[10];
}
for (int i = 0; i < 5; i++) {
(shared->buf)[i] = i + '0';
(shared->ptr)[i] = i + '0';
}
printf("parent: ");
for (int i = 0; i < 10; i++) {
printf("%c ", (shared->buf)[i]);
}
printf("\nparent_ptr: ");
for (int i = 0; i < 10; i++) {
printf("%c ", (shared->ptr)[i]);
}
printf("\n");
V(shared->s);
} else if (pid == 0) {
shared = (share_mem *)shmat(shm_id, 0, 0);
P(shared->s);
if (shared->ptr == NULL) {
shared->ptr = new char[10];
}
for (int i = 5; i < 10; i++) {
(shared->buf)[i] = (i - 5) + '0';
(shared->ptr)[i] = (i - 5) + '0';
}
printf("child: ");
for (int i = 0; i < 10; i++) {
printf("%c ", (shared->buf)[i]);
}
printf("\nchild_ptr: ");
for (int i = 0; i < 10; i++) {
printf("%c ", (shared->ptr)[i]);
}
printf("\n");
V(shared->s);
}
int stat = shmdt((void*)shared);
if (pid > 0) {
stat = shmctl(shm_id, IPC_RMID, 0);
}
return 0;
}
我以為這段代碼會出現段錯誤,因為父進程會使用一段不存在的memory。 但是,用G++編譯執行后,output如下。
child: 0 1 2 3 4
child_ptr: 0 1 2 3 4
parent: 0 1 2 3 4 0 1 2 3 4
parent_ptr: p a r e n t _ p t r
child
和parent
的output是應該的,但是parent_ptr
的output有點奇怪。 無論我重新編譯和運行多少次,output 總是一樣的。
我想知道為什么?
共享 memory 中的指針與其他任何地方的指針一樣; 它們包含一個指向內存位置的內存地址。 對於用戶空間程序,該內存位置表示為虛擬內存地址,當它被取消引用時,計算機的 MMU 查找相應的物理內存地址以找到正確的頁面 memory 以訪問,根據調用進程的虛擬->物理頁面映射表。
在共享 memory 中存儲指針通常會遇到麻煩,如果您從不同的進程訪問共享的 memory - 如果取消引用指針的進程與寫入指針的進程不同,那么幾乎可以肯定虛擬內存 -地址對於該進程將無效/不可翻譯。 例如,進程 A 可能調用shared->ptr = new char[10];
從而將指針值0x12345678
(或其他)放入共享內存區域,進程 A 稍后可以取消引用該指針值以毫無問題地讀取或寫入堆 memory 的那 10 個字節....但是如果進程B 嘗試取消引用該指針,進程 B 將調用未定義的行為(可能導致分段錯誤,或者可能只是破壞其自身的 memory 空間等),因為進程 B 從未在其自身的指針值0x12345678
處分配 10 個字節的堆 memory虛擬地址空間。
child和parent的output是應該的,但是parent_ptr的output有點奇怪。 無論我重新編譯和運行多少次,output 總是一樣的。
當我運行發布的代碼時(在 MacOS/X 10.15.6,FWIW 上),我得到這個 output:
$ ./a.out
child: 0 1 2 3 4
child_ptr: 0 1 2 3 4
Segmentation fault: 11
因此,我相信您所看到的是調用未定義行為的結果,如上所述。 一旦未定義的行為被調用,所有的賭注都將取消,任何事情都可能發生,這取決於程序運行的系統的實現細節。
我的猜測是,在您的 Linux 框中,無效指針恰好指向您之前傳遞給printf()
的字符串常量“\nparent_ptr:”存儲位置開始后的一個字節。
您可以在共享 memory 中使用指針。大體上,您需要做的是:
shmat
返回的指針地址存儲在共享 memory 段中。shmat
調用返回的地址與存儲在該段中的地址進行比較。shmdt
從該段分離,然后重新附加到該段,將第一個進程存儲的地址作為第二個參數提供給shmat
,而不是使用0
。這將強制所有進程將共享的 memory 段附加到同一虛擬地址,這意味着指針將跨進程邊界排列。
這有點黑魔法,乍一看似乎很危險,但我已經看到在一個非常重要的代碼庫中使用該技術效果很好並且沒有問題。
有關示例,請參見參考標准 M中的UTIL_Share
function。
正如我們所知,共享 memory 是 memory 中聲明為將由多個進程同時使用的部分的區域。 這意味着這個 memory 段將被多個進程看到,對嗎? 因此,多個進程可能會嘗試同時更改此 memory 區域,因此使用信號量來同步它們的訪問,使其互斥。
因此,您使用 shmget() 系統調用分配一個共享的 memory 段,並且 shmget() 返回一個標識符,用於將其添加到進程的 memory 頁表中。 此標識符用於 shmat() 系統調用,該調用返回一個指針,您現在可以使用該指針將數據放入此共享 memory 中。
但是,由於指針包含虛擬地址,因此在使用指針時需要小心。 因為同一個段可能附加在其他進程中的不同虛擬地址中,所以一個指向一個 memory 區域的指針在一個進程中可能指向另一個進程中的不同 memory 區域。 這就是這種未定義行為背后的原因,如果一個進程想要訪問被另一個進程損壞的位置,則可能會導致分段錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.