一,管道PIPE
二,FIFO通信
三,mmap通信
创建内存映射区。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int mmap(void *addr, size_t length);
函数mmap:打开一个文件,指定一个文件的区域,作为一个区域,映射到内存中,以后就直接操作那个内存,就能够实现进程间的通信。因为是内存操作,所以速度最快。
- addr:固定NULL
- length: 拿出文件中的多长的一段,映射到内存。
- offset: 从文件内容中的哪个位置开始拿。
- prot
PROT_EXEC Pages may be executed
PROT_READ Pages may be read.
PROT_WRITE Pages may be written
PROT_NONE Pages may not be accessed
- flags
MAP_SHARED: 对内存里的值进行修改,会反映到文件,也就是文件也被修改。
MAP_PRIVATE:对内存里的值进行修改,不会反映到文件,文件不会被修改。
- offset: 起始位置
- 返回值:
成功:可用的内存的首地址
失败:MAP_FAILED(that is, (void *) -1)
释放内存映射区.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
- addr: mmap的返回值;
- length: mmap创建的长度;
- 返回值: 成功0, 失败-1.
例子:
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
int fd = open("mem", O_RDWR);
// char *buf = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
char *buf = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
printf("%s\n", buf);
strcpy(buf, "FFFFF");
//释放映射区
munmap(buf, 8);
close(fd);
}
mmap的七个问题:
- 如果更改上面例子里变量buf的地址,释放的时候munmap,还能成功吗?
不能成功,错误信息: [Invalid argument]
- 对映射区的操作,越界了怎么样?
open文件size > 要写入的size > mmap参数的length: 能够全部写入文件。
open文件size < 要写入的size > mmap参数的length: 不能全部写入文件,能够给写入的size是open文件的size
- 偏移量随便填个数字会怎么样?
mmap函数出错,错误信息: [Invalid argument]
offset必须是4K的整数倍,[0, 4*1024......]
用[stat]命令查看文件,发现文件的size实际小于4096,但是[IO Block: 4096]
File: pi2.c
Size: 442 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 424247 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ ys) Gid: ( 1000/ ys)
Access: 2019-05-02 12:54:13.812282158 +0800
Modify: 2019-04-29 13:49:42.489004001 +0800
Change: 2019-04-29 13:49:42.489004001 +0800
- 如果文件描述符先关闭,对mmap映射有没有影响。
没有影响。
- open的时候,可以用新创建一个文件的方式,来创建映射区吗?
错误:Bus error(core dumped).
错误理由是:创建的文件的size为0,所以出上面的错误。新创建一个文件后,马上把文件大小扩展一下就不会发生错误了。
int fd = open("mem", O_RDWR|O_CREAT, 0666);
ftruncate(fd, 8); //把新创建的文件mem的大小扩展为8.
- open文件时,选择O_WRONLY,可以吗
mmap函数出错,错误: [Permission denied]。
因为要把文件的内容读到内存,所以隐含一次读取操作,所有没有读的权限的话,就出这个错误。
- 当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择[PROT_READ|PROT_WRITE]吗
mmap函数出错,错误: 【Permission denied】.
MAP_SHARED的时候会去写文件,但是open的时候只有读权限,所以权限不够。
用mmap实现父子进程间通信的例子:
注意:参数flags必须是MAP_SHARED,因为2个进程间通信,需要互相读写,所以必须是MAP_SHARED
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
int main() {
int fd = open("mem", O_RDWR);
int* mem = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED) {
perror("mmap");
return -1;
}
pid_t pid = fork();
if (pid == 0) {
*mem = 100;
printf("child:mem=%d\n", *mem);
sleep(3);
printf("child:mem=%d\n", *mem);
}
else if (pid > 0) {
sleep(1);
printf("parent:mem=%d\n", *mem);
*mem = 200;
printf("parent:mem=%d\n", *mem);
wait(NULL);
}
munmap(mem, 4);
close(fd);
}
执行结果:
child:mem=100
parent:mem=100
parent:mem=200
child:mem=200
不知道读者同学们发现了没有,用mmap有个非常鸡肋的地方,就是必须要使用一个文件,其实这个文件对程序没有什么作用。所以linux给我们提供了一个方法,叫做[匿名映射]。
匿名映射:在调用mmap函数时候,在flags参数那里,设置[MAP_ANON],并在fd参数那里设置[-1]。
int* mem = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
有个问题,在有些unix系统里是没有[MAP_ANON][MAP_ANONYMOUS]这2个宏的,这2个宏的作用是一样的,其中一个是简写。那怎么办呢?
使用下面2个文件去映射,因为要用文件,所以必须还得有open的调用,但好处是不用事先做出一个大小合适的文件了。
- /dev/zero: 可以随意映射,size无限大,诨名为[聚宝盆]
- /dev/null:可以随意映射,size无限大,但映射完后,文件里不会存有任何内容,所以也叫[无底洞],一般错误日志太多,而且不想保留的时候,会重定向到这个文件。
匿名映射的弱点:不能实现无血缘关系进程间的通信。
用mmap实现无血缘关系的进程间通信的例子:
写入端:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
typedef struct _student {
int id;
char name[20];
} Student;
int main(int argc, char *argv[]) {
int fd = open("aaa", O_RDWR|O_TRUNC|O_CREAT, 0666);
int length = sizeof(Student);
ftruncate(fd, length);
Student *std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (std == MAP_FAILED) {
perror("mmap");
return -1;
}
int num = 0;
while (1) {
std->id = num;
sprintf(std->name, "xiaoming-%03d", num++);
sleep(1);
}
munmap(std, length);
close(fd);
}
读入端:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
typedef struct _student{
int fd;
char name[20];
} Student;
int main(int argc, char *argv[]) {
int fd = open("aaa", O_RDWR);
int length = sizeof(Student);
Student *std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (std == MAP_FAILED) {
perror("mmap");
return -1;
}
while (1) {
printf("id:%03d, name:%s\n", std->id, std->name);
sleep(1);
}
munmap(std, length);
close(fd);
}
利用mmap实现用多个进程拷贝一个文件的例子
核心思想:把要拷贝的文件和目标文件都映射成内存映射区,然后在各自的子进程里做拷贝,拷贝时,注意起点和大小。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
//argv[1]: 进程的数量
//argv[2]: 要拷贝的文件
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("bad argv, need 3 arg\n");
return -1;
}
//用stat函数取得文件的大小
struct stat sbuf;
int ret = stat(argv[2], &sbuf);
if (ret < 0) {
perror("stat");
return -1;
}
//文件的大小
off_t sz = sbuf.st_size;
//余数
off_t yu = sz % atoi(argv[1]);
//每个进程拷贝的大小
off_t proSz = (sz - yu) / atoi(argv[1]);
//open src file
int srcfd = open(argv[2], O_RDONLY);
//create target file
char wk[20] = {0};
sprintf(wk, "%s.copy", argv[2]);
int decfd = open(wk, O_RDWR|O_CREAT|O_TRUNC, 0766);
//创建和src文件同等大小的目标文件
ret = ftruncate(decfd, sz);
if (ret < 0) {
perror("ftruncate");
return -1;
}
//打开要被拷贝文件的内存映射区
void *dst = mmap(NULL, sz, PROT_READ, MAP_SHARED, srcfd, 0);
if (src == MAP_FAILED) {
perror("mmap src:");
return -1;
}
//打开要目标文件的内存映射区
void *dst = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, decfd, 0);
if (dst == MAP_FAILED) {
perror("mmap dst:");
return -1;
}
int i;
int pCnt = atoi(argv[1]);
//创建子进程,都是这些子进程都是本程序的子进程
for (i=0; i<pCnt; ++i) {
//in child process
if (fork() == 0) {
break;
}
}
//子进程的处理
if (i < pCnt) {
//last time
if (i == pCnt - 1) {
memcpy(dst+i*proSz, src+i*proSz, proSz+yu);
}
else {
memcpy(dst+i*proSz, src+i*proSz, proSz);
}
}
//父进程的处理
else {
for (int i=0; i<pCnt; ++i) {
wait(NULL);
}
//printf("parent pid=%d\n", getpid());
if (munmap(src, sz) == -1) {
perror(munmap src");
}
if (munmap(dst, sz) == -1) {
perror("munmap dsc");
}
close(srcfd);
close(decfd);
}
}