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
#include <fcntl.h>
#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) {
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");
}
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");
}
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");
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);
}
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);
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);
}
munmap(mem, 8);
return 0;
}
|