IPC (interProcess Communication) 进程间通信,通过内核提供的缓冲区,进行数据的交换机制。
IPC通信的几种方式:
- pipe 管道 最简单
- fifo 有名管道
- mmap 文件映射共享IO, 速度最快
- 本地socket最稳定
- 信号 携带的信号量最小
- 共享内存
- 消息队列
1. 管道pipe
-
管道通信优劣:
优点:简单,相比信号和套接字实现进程通信简单很多
缺点:1. 只能单向通信(半双工),双向通信需要建立两个管道?2. 只能用于父子、兄弟(有共同祖先)进程之间的通信。该问题可以有FIFO有名管道解决。 -
man pipe 查看函数
#include <unistd.h> int pipe(int pipefd[2]); #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <fcntl.h> /* Obtain O_* constant definitions */ #include <unistd.h> int pipe2(int pipefd[2], int flags);
-
进程通信demo
#include <stdio.h> #include <unistd.h> int main() { printf("Begin ..\n"); int fd[2]; pipe(fd); // 先创建管道,再创建子进程 pid_t pid = fork(); if (pid == 0) { // 子进程 write(fd[1], "hello", 5); } else if(pid > 0) { // 父进程 char buff[12] = {0}; int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待 if(ret > 0) { write(STDOUT_FILENO, buff, ret); } } printf("End ...\n"); return 0; }
-
读管道
写端全部关闭 --read读到0,相当于读到文件末尾
写端没有完全关闭,有数据 read 读到数据,没有数据 read 阻塞 fcntl函数可以更改非阻塞
写端全部关闭demo#include <stdio.h> #include <unistd.h> int main() { printf("Begin ..\n"); int fd[2]; pipe(fd); // 先创建管道,再创建子进程 pid_t pid = fork(); if (pid == 0) { // 子进程 sleep(3); close(fd[0]); // 关闭读端 write(fd[1], "hello", 5); close(fd[1]); // 关闭写端 sleep(10); } else if(pid > 0) { // 父进程 char buff[12] = {0}; close(fd[1]); // 关闭写端 while (1) { int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待 if (ret == 0) { // 如果写端全部关闭(所有写端),read会读到0 printf("\n read over! \n"); break; } if(ret > 0) { write(STDOUT_FILENO, buff, ret); } } } printf("End ...\n"); return 0; }
输出:
Begin .. hello read over! End ...
-
写管道
读端全部关闭 产生一个信号SIGPIPE,程序异常终止
读端未全部关闭,管道已满 write 阻塞,管道未满 write 正常写入读端全部关闭程序
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { printf("Begin ..\n"); int fd[2]; pipe(fd); // 先创建管道,再创建子进程 pid_t pid = fork(); if (pid == 0) { // 子进程 sleep(3); close(fd[0]); // 关闭读端 write(fd[1], "hello", 5); close(fd[1]); // 关闭写端 sleep(10); } else if(pid > 0) { // 父进程 char buff[12] = {0}; close(fd[1]); // 关闭写端 close(fd[0]); // 关闭读端 int status; wait(&status); if (WIFSIGNALED(status)) { printf("killed by %d\n", WTERMSIG(status)); } while (1) { int ret = read(fd[0], buff, sizeof(buff)); // 阻塞等待 if (ret == 0) { printf("\n read over! \n"); break; } if(ret > 0) { write(STDOUT_FILENO, buff, ret); } } } printf("End ...\n"); return 0; }
输出结果
Begin .. killed by 13
补充: ulimit -a 查看所有系统资源的上限
2. FIFO有名管道
区分与pipe,pipe只能再有血缘关系的进程之间通信,而fifo可以实现非血缘关系的进程之间的通信。
FIFO是linux系统基础文件的一种,但是,FIFO文件在磁盘上没有数据块,仅仅用来表示内核中一条通道。各个进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。
-
创建一个管道伪文件
mkfifo myfifo 创建命令
也可以使用函数int mkfifo(cosnt char* pathname, mode_t mode); -
内核会针对fifo文件开辟一个缓冲区,操作fifo文件,实现进程间的通信。(实际上就是文件读写)
-
FIFO写数据demo
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> int main(int argc, char *argv[]) { if (argc != 2) { printf("./a.out fifoname\n"); } // 当前目录有一个myfifo文件 // 打开fifo文件 int fd = open(argv[1], O_WRONLY); // 写数据 char buf[256]; int num = 1; while (num < 100) { memset(buf, 0x00, size(buf)); sprintf(buf, "xiaoming%02d", num++); write(fd, buf, strlen(buf)); sleep(1); } close(fd); return 0; }
FIFO读数据demo
int main(int argc, char* argv[]) { if (argc != 2) { printf("./a.out fifoname\n"); } // 当前目录有一个myfifo文件 // 打开fifo文件 int fd = open(argv[1], O_WRONLY); char buf[256]; int ret; while (1){ // 循环读 memset(buf, 0x00, sizeof(buf)); ret = read(fd, buf, sizeof(buf)); if (ret < 0) { printf("read:%s", buf); } } close(fd); return 0; }
注:打开fifo文件的时候,read会阻塞等待write端open,write端也会阻塞等待另一端打开。
3. mmap
- 函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
创建映射区 mmap 函数参数:
- addr: 传 NULL
- length: 映射区长度
- prot:
PROT_READ 可读
PROT_WRITE 可写 - flags:
MAP_SHARED 共享的(进程间通信必须是共享的)
MAP_PRIVATE 私有的 - fd 文件描述符,open打开一个文件
- offset 偏移量
- 返回值
成功, 返回可用的内存首地址
失败, 返回 MAP_FAILED
释放映射区 munmap函数:
- addr 传 mmap返回值
- length mmap创建的长度
- 返回值
成功返回 0
失败返回 -1
创建映射区程序demo
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int fd = open("mem.txt", O_RDWR);
// 创建映射区
char *mem = (char *)mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, 0);
if (mem == MAP_FAILED) {
perror("mmap err");
return -1;
}
strcpy(mem, "hello");
//释放mmap
munmap(mem, 8);
close(fd);
return 0;
}
-
mmap 9问
-
使用mmap实现父子进程之间通信
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/mman.h> int main() { int fd = open("mem.txt", O_RDWR); // 创建映射区 char *mem = (char *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) { perror("mmap err"); return -1; } pid_t pid = fork(); if (pid == 0) { // 子进程 *mem = 'a'; printf("child, *mem = %s\n", mem); sleep(3); // 等待父进程修改 printf("child, *mem = %s\n", mem); } else if (pid > 0) { // 父进程 sleep(1); // 等待子进程修改 printf("parent, *mem = %s\n", mem); *mem = 'b'; printf("parent, *mem = %s\n", mem); wait(NULL); } //释放mmap munmap(mem, 8); close(fd); return 0; }
- 输出结果
child, *mem = a parent, *mem = a parent, *mem = b child, *mem = b
结论:父子进程共享,1. 打开文件; 2. mmap建立映射区(但是必须使用)
-
匿名映射
通过使用mmap发现,使用映射区完成文件的读写非常方便,父子进程间通信也比较容易。缺陷是,每次创建一个映射区必须依赖一个文件才能实现。通过创建一个映射区要open一个temp文件,最后必须close,比较麻烦。使用匿名映射无需依赖一个文件即可创建一个映射区,需要借助标志位参数flags来指定。
-
匿名映射程序demo
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/mman.h> int main() { // 创建映射区 char *mem = (char *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); if (mem == MAP_FAILED) { perror("mmap err"); return -1; } pid_t pid = fork(); if (pid == 0) { // 子进程 *mem = 'a'; printf("child, mem = %s\n", mem); sleep(3); // 等待父进程修改 printf("child, mem = %s\n", mem); } else if (pid > 0) { // 父进程 sleep(1); // 等待子进程修改 printf("parent, mem = %s\n", mem); *mem = 'b'; printf("parent, mem = %s\n", mem); wait(NULL); } //释放mmap munmap(mem, 8); return 0; }
-
使用mmap实现无血缘关系的进程通信
写端demo#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <fcntl.h> struct Student{ int id; char name[20]; }; int main(int argc, char *argv[]) { if (argc != 2) { printf( "./main filename\n"); return -1; } // 1. 打开文件 int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666); int len = sizeof(Student); // ftruncate 修改文件大小为 len ftruncate(fd, len); // 2. mmap Student *stu = (Student *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (stu == MAP_FAILED) { perror("mmap error"); } // 3. 修改内存数据 unsigned int num = 1; while (1) { stu->id = num; sprintf(stu->name, "xiaoming-%03d", num++); sleep(1); } // 4. 释放映射区和关闭文件描述符 munmap(stu, len); close(fd); return 0; }
读端demo
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <fcntl.h> struct Student{ int id; char name[20]; }; int main(int args, char* argv[]) { int fd = open(argv[1], O_RDWR); int len = sizeof(Student); Student *stu = (Student *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (stu == MAP_FAILED) { perror("mmap err"); } while(1) { printf("id = %d, name = %s\n", stu->id, stu->name); sleep(1); } munmap(stu, len); close(fd); return 0; }