我已經撰寫了一些代碼來測驗mmap系統呼叫。
在這里,我要映射的虛擬記憶體地址空間的STDOUT,并通過指標列印一個字串ptr,通過回傳的mmap。
int main()
{
void *ptr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_PRIVATE, STDOUT_FILENO, 0);
memcpy(ptr, "hello", 6);
}
但是這段代碼失敗了:
$ gcc mmap.c
$ ./a.out
Segmentation fault (core dumped)
而且我已經測驗過mmap了STDIN,沒問題。
int main()
{
// executed by `./a.out < text.txt`
void *ptr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_PRIVATE, STDIN_FILENO, 0);
write(STDOUT_FILENO, ptr, 1024);
}
為什么mmap在STDOUT這里失敗?mmaponSTDIN和之間有什么區別STDOUT?
uj5u.com熱心網友回復:
作為一般規則,當您使用服務時,請檢查錯誤代碼以縮小問題范圍。重寫你的程式如下:
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
void *ptr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_PRIVATE, STDOUT_FILENO, 0);
if (MAP_FAILED == ptr) {
fprintf(stderr, "mmap(): error '%m' (%d)\n", errno);
return 1;
} else {
memcpy(ptr, "hello", 6);
}
return 0;
}
程式顯示如下:
$ ./a.out
mmap(): error 'No such device' (19)
也就是說你得到了ENODEV錯誤代碼。查看手冊,您會得到以下解釋:
ENODEV 指定檔案的底層檔案系統不支持記憶體映射。
實際上,檔案描述符指向的是終端設備驅動程式,而后者是不允許 mmap()操作的。
關于映射STDIN_FILENO的第二個程式,當您像這樣運行它時:
$ ./a.out < text.txt
前面使STDIN_FILENO “指向” text.txt檔案,而不是輸入終端。因此,mmap()在這里作業......
因此,出于同樣的想法,我們希望第一個程式可以與:
$ ./a.out > foo.txt
由于輸出不再是終端而是檔案。但是您會收到EACCES錯誤:
$ ./a.out > foo.txt
mmap(): error 'Permission denied' (13)
答案在這篇文章中有解釋。檔案必須以讀取訪問權限打開才能被映射,但由 shell 啟動的程式的標準輸出打開O_WRONLY。因此,mmap()失敗了。在 Linux 下,可以使用一個技巧。檔案描述符是/proc/pid/fd目錄中的符號鏈接。因此,可以使用O_RDWR標志重新打開符號鏈接/proc/pid/fd/1指向的檔案,并使用著名的技巧使檔案描述符編號 1 ( STDOUT_FILENO ) 指向這個新打開的檔案。需要呼叫 來保留檔案中的空間和MAP_SHAREDclose()/dup()ftruncate() 需要標志才能使對其他行程的修改可見(例如,程式終止時的外殼程式):
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
char symlink[256];
int fd;
snprintf(symlink, sizeof(symlink), "/proc/%d/fd/1", getpid());
fd = open(symlink, O_RDWR);
if (fd < 0) {
fprintf(stderr, "fopen(): error '%m' (%d)\n", errno);
return 1;
}
close(STDOUT_FILENO);
dup(fd);
close(fd);
ftruncate(STDOUT_FILENO, 1024);
void *ptr = mmap(NULL, 1024, PROT_WRITE | PROT_READ, MAP_SHARED, STDOUT_FILENO, 0);
if (MAP_FAILED == ptr) {
fprintf(stderr, "mmap(%d): error '%m' (%d)\n", fd, errno);
return 1;
} else {
memcpy(ptr, "hello", 6);
}
return 0;
}
因此,您會得到您所期望的:
$ ./a.out
mmap(3`): error 'No such device' (19) # The output is the terminal (mmap() forbidden)
$ ./a.out > foo.txt # The output is a file
$ cat foo.txt
hello$
uj5u.com熱心網友回復:
區別不是標準輸入與標準輸出
它介于常規磁盤檔案描述符和一些偽 tty 檔案描述符之間。請參閱stat(2)(更準確地說fstat)和inode(7)。
由于常規檔案(型別S_IFREG)可以是lseek(2) -ed 并且 Linux 內核在使用mmap.
套接字或偽 tty 不能被lseek-ed。
你可以嘗試echo foo | a.out ,它也會失敗,因為管道(7)不能被lseek-ed。
當然,您應該閱讀mmap(2)的檔案。它可能會失敗,您應該在失敗時使用errno(3)或perror(3)。
所以代碼改為:
void *ptr =
mmap(NULL, 1024, PROT_WRITE | PROT_READ,
MAP_PRIVATE, STDIN_FILENO, 0);
if (ptr == MMAP_FAILED)
{ perror("mmap"); exit(EXIT_FAILURE); }
還可以在現有命令列程式上嘗試strace(1)以了解有關syscalls(2) 的更多資訊
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/363033.html
