作为一般规则,当您使用服务时,请检查错误代码以缩小问题范围。
重写你的程序如下:
#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错误码。查看manual,得到如下解释:
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)
post 中解释了答案。必须使用 read 访问权限打开文件才能进行映射,但打开由 shell 启动的程序的标准输出 O_WRONLY。因此,mmap() 失败。在 Linux 下,可以使用一个技巧。文件描述符是 /proc/pid/fd 目录中的符号链接。因此,可以使用 O_RDWR 标志重新打开符号链接 /proc/pid/fd/1 指向的文件,并使用著名的 close()/dup() 技巧来制作文件描述符编号 1 (STDOUT_FILENO) 指向这个新打开的文件。需要调用 ftruncate() 来保留文件中的空间,并且需要 MAP_SHARED 标志才能使对其他进程的修改可见(例如,程序终止时的 shell):
#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$