1 进程通信相关概念
1 什么是IPC
进程间通信, InterProcess Communication
2 进程间通信常用4种方式
- 管道 (使用最简单)
- 信号 (开销最小)
- 共享内存/映射区 (无血缘关系)
- 本地套接字 (最稳定)
2 管道(匿名)
1 管道的概念
本质:
内核缓冲区
伪文件 - 不占用磁盘空间
特点:
两部分:
读端,写端,对应两个文件描述符
数据写端流入, 读端流出
操作管道的进程被销毁之后,管道自动被释放了
管道默认是阻塞的。
读写
#### 2 管道的原理
内部实现方式:队列
环形队列
特点:先进先出
缓冲区大小:
默认4k, 大小会根据实际情况做适当调整
3 管道的局限性
队列:
数据只能读取一次,不能重复读取
全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。
半双工(Half Duplex)所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台.
单工通信是指通信线路上的数据按单一方向传送.
匿名管道:适用于有血缘关系的进程
4 创建匿名管道
int pipe(int fd[2]);
数组有两个元素, 一个读端一个写端, 各自对应一个文件描述符
- fd - 传出参数,
- fd[0] - 读端
- fd[1] - 写端
例:
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h> #include <unistd.h>
int main()
{
int fd[2];
// 创建管道
int ret = pipe(fd);
// 如果返回值为-1, 则创建失败
if(ret == -1)
{
perror("pipe error");
exit(1);
}
// 读端
printf("pipe[0] = %d\n", fd[0]);
// 写端
printf("pipe[1] = %d\n", fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
5 父子进程使用管道通信
思考:
单个进程能否使用管道完成读写操作?
可以
父子进程间通信是否需要sleep函数?
父写 - 写的慢
子读 - 读的快
不用, 因为管道是阻塞的
注意事项
先创建管道, 再创建子进程
父子进程实现
ps aux | grep "bash"
数据重定向: dup2
execlp
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h>
int main(int argc, const char* argv[])
{
// 创建管道
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror(“pipe error”);
exit(1);
}// 创建子进程 pid_t pid = fork(); if(pid == -1) { perror("fork error"); exit(1); } // ps aux | grep bash // 父进程执行 ps aux , 写管道, 关闭读端 if(pid > 0) { //关闭读端 close(fd[0]); // 数据写到管道,STDOUT_FILENO 指向fd[1]的指向,也就是管道的写端 dup2(fd[1], STDOUT_FILENO); // 执行命令ps -aux execlp("ps", "ps", "aux", NULL); // 如果上面失败则: perror("execlp ps"); exit(1); } // 子进程 grep bash 从管道中搜索, 读管道, 关闭写端 else if(pid == 0) { // 关掉写端 close(fd[1]); dup2(fd[0], STDIN_FILENO); execlp("grep", "grep", "bash","--color=auto", NULL); perror("execlp grep"); exit(1); } close(fd[0]); close(fd[1]); return 0;
“`
兄弟进程实现
ps aux | grep "bash"
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <sys/wait.h> int main(int argc, const char* argv[]) { int fd[2]; int ret = pipe(fd); if(ret == -1) { perror("pipe error"); exit(1); } printf("fd[0] = %d\n", fd[0]); printf("fd[1] = %d\n", fd[1]); //创建两个子进程 int i = 0; int num = 2; for(; i<num; ++i) { pid_t pid = fork(); //如果是子进程则跳出 if(pid == 0) { break; } } //父进程回收紫禁城的pcb if(i == num) { close(fd[0]); close(fd[1]); //循环回收子进程 //WNOHANG 非阻塞回收 pid_t wpid; while( (wpid = waitpid(-1, NULL, WNOHANG)) != -1 ) { if(wpid == 0) { continue; } printf("died child pid = %d\n", wpid); } } else if(i == 0) { // ps aux close(fd[0]); dup2(fd[1], STDOUT_FILENO); execlp("ps", "ps", "aux", NULL); } else if(i == 1) { // grep bash close(fd[1]); dup2(fd[0], STDIN_FILENO); execlp("grep", "grep", "bash", NULL); } return 0;
6 管道读写行为
读操作
有数据
read(fd)
- 正常读, 返回读出的字节无数据
可能写端全部关闭
read解除阻塞, 返回0
相当于读文件读到了尾部
没有全部关闭
read阻塞
写操作
读端全部被关闭
管道破裂, 进程被中止
内核给当前进程法信号SIGPIPE, 中止进程
读端没有全部关闭
缓冲区写满
write函数阻塞
缓冲区没满
write继续写, 知道满
如何设置非阻塞?
默认读写两端都阻塞
设置读端为非阻塞pipe(fd)
fcntl - 变参函数
- 复制文件描述符
- 修改文件的属性 - open的时候对应的flag属性
设置方法:
获取原来的flags:
int flags = fcntl(fd[0], F_GETFL);
设置新的flags: 非阻塞
flag |= O_NONBLOCK
fcntl(fd[0], F_SETFL, flags);
7 查看缓冲区大小
命令
ulimit -a
函数
fpathconf
例:
long num = fpathconf(fd[0], _PC_PIPE_BUF);
3 fifo
1 特点
- 有名管道
- 在磁盘上为类型为p(管道)的文件
- 伪文件, 在磁盘大小永远为0
- 数据在内核中对应的缓冲区
- 半双工通信
2 使用场景
无血缘关系进程间通信
3 创建方式
- 命令
mkfifo 管道名
- 函数
mkfifo
4 fifo文件使用IO函数进行操作
- open/close
- read/write
- 不能lseek
5 进程间通信
fifo文件 — myfifo
- 两个不相干的进程A(a.c) B(b.c)
a.c –> read
- int fd = open(“myfifo”, O_RDONLY);
- read(fd, buf, sizeof(buf));
close(fd);
b.c — write
int fd1 = open(“myfifo”, O_WRONLY);
write(fd1, “hello”, 5);
close(fd1);
4 内存映射区
1 mmap – 创建内存映射区
作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件
函数原型
void *mmap { void *adrr, //映射区首地址, 传NULL size_t length, //映射区的大小, 最小4k, 不能为0, 一般和文件同大小 int prot, //映射区权限 PROT_READ--映射区必须要有读权限 int flags, //标志位参数 //MAP_SHARED共享的,数据会同步到磁盘. MAP_PRIVATE私有的,不会同步 int fd, //文件描述符, 需要映射的源文件, 需要先open一个文件 off_t offset //映射文件的指针偏移量, 为4k的整数倍 }
返回值:
成功: 返回映射区的首地址
失败: 返回MAP_FAILED, 就是(void *)-1
2 munmap – 释放内存映射区
函数原型
int munmap(void *addr, size_t length);
addr–映射区的首地址
length–映射区的长度
3 注意事项
如修改内存映射区是地址的指针, 则释放失败, 可以复制之后再操作
char *pt = ptr
open的文件的权限应 >= 映射区的权限
4 有血缘关系的进程间通信
父子进程间永远共享:
文件描述符
内存映射区
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <sys/mman.h> #include <fcntl.h> #include <sys/wait.h> int main(int argc, const char* argv[]) { //获取文件描述符 int fd = open("english.txt", O_RDWR); if(fd == -1) { perror("open error"); exit(1); } // get file length // len > 0 int len = lseek(fd, 0, SEEK_END); void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(ptr == MAP_FAILED) { perror("mmap error"); exit(1); } close(fd); char buf[4096]; // 从内存中读数据 printf("buf = %s\n", (char*)ptr); strcpy(ptr, "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyaaaaa"); // 进程间通信 pid_t pid = fork(); if(pid == -1) { perror("fork error"); exit(1); } //父进程 if(pid >0) { //写数据 strcpy((char*)ptr, "come on!"); //回收 wait(NULL); } else if(pid == 0) { //读数据 printf("%s\n", (char*)ptr); } // ptr++; int ret = munmap(ptr, len); if(ret == -1) { perror("munmap error"); exit(1); } return 0;
匿名映射区:
int main(int argc, const char* argv[])
{
//创建匿名映射区
int len = 4096;
void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if(ptr == MAP_FAILED)
{
perror("mmap error");
exit(1);
}
// 进程间通信
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
//父进程
if(pid >0)
{
//写数据
strcpy((char*)ptr, "come on!");
//回收
wait(NULL);
}
else if(pid == 0)
{
//读数据
printf("%s\n", (char*)ptr);
}
//释放内存映射区
int ret = munmap(ptr, len);
if(ret == -1)
{
perror("munmap error");
exit(1);
}
return 0;
5 无血缘关系的进程间通信
a.c和b.c, 只能借助磁盘文件创建映射区, 不阻塞
a.c
int fd = open(“hello”);
void* ptr = mmap(, , , ,fd, 0);
写
int main(int argc, char *argv[]) { int fd = open("temp", O_RDWR | O_CREAT, 0664); void* ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(1); } while(1) { char*p = (char*)ptr; p += 1024; strcpy(p, "hello parent, i am your 朋友!!!\n"); sleep(2); } // 释放 int ret = munmap(ptr, 4096); if(ret == -1) { perror("munmap"); exit(1); } return 0; }
b.c
int fd1 = open(“hello”);
void* ptr1 = mmap( , , , , fd1, 0);
读
int main(int argc, char *argv[]) { int fd = open("temp", O_RDWR | O_CREAT, 0664); ftruncate(fd, 4096); int len = lseek(fd, 0, SEEK_END); void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(1); } while(1) { sleep(1); printf("%s\n", (char*)ptr+1024); } // 释放 int ret = munmap(ptr, len); if(ret == -1) { perror("munmap"); exit(1); } return 0; }