进程间通信, InterProcess Communication
本质:
内核缓冲区
伪文件 - 不占用磁盘空间
特点:
两部分:
读端,写端,对应两个文件描述符
数据写端流入, 读端流出
操作管道的进程被销毁之后,管道自动被释放了
管道默认是阻塞的。
读写
#### 2 管道的原理
内部实现方式:队列
环形队列
特点:先进先出
缓冲区大小:
默认4k, 大小会根据实际情况做适当调整
队列:
数据只能读取一次,不能重复读取
全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。
半双工(Half Duplex)所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台.
单工通信是指通信线路上的数据按单一方向传送.
匿名管道:适用于有血缘关系的进程
int pipe(int fd[2]);
数组有两个元素, 一个读端一个写端, 各自对应一个文件描述符
例:
#include
#include
#include
#include
#include #include
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;
}
思考:
单个进程能否使用管道完成读写操作?
可以
父子进程间通信是否需要sleep函数?
父写 - 写的慢
子读 - 读的快
不用, 因为管道是阻塞的
注意事项
先创建管道, 再创建子进程
父子进程实现 ps aux | grep "bash"
数据重定向: dup2
execlp
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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//如果是子进程则跳出
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;
有数据 read(fd)
- 正常读, 返回读出的字节
无数据
可能写端全部关闭
read解除阻塞, 返回0
相当于读文件读到了尾部
没有全部关闭
read阻塞
读端全部被关闭
管道破裂, 进程被中止
内核给当前进程法信号SIGPIPE, 中止进程
读端没有全部关闭
缓冲区写满
write函数阻塞
缓冲区没满
write继续写, 知道满
默认读写两端都阻塞
设置读端为非阻塞pipe(fd)
fcntl - 变参函数
设置方法:
获取原来的flags: int flags = fcntl(fd[0], F_GETFL);
设置新的flags: 非阻塞
flag |= O_NONBLOCK
fcntl(fd[0], F_SETFL, flags);
命令ulimit -a
函数fpathconf
例: long num = fpathconf(fd[0], _PC_PIPE_BUF);
无血缘关系进程间通信
mkfifo 管道名
mkfifo
fifo文件 — myfifo
a.c –> read
close(fd);
b.c — write
int fd1 = open(“myfifo”, O_WRONLY);
write(fd1, “hello”, 5);
close(fd1);
作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件
函数原型
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
函数原型 int munmap(void *addr, size_t length);
addr–映射区的首地址
length–映射区的长度
如修改内存映射区是地址的指针, 则释放失败, 可以复制之后再操作
char *pt = ptr
open的文件的权限应 >= 映射区的权限
父子进程间永远共享:
文件描述符
内存映射区
#include
#include
#include
#include
#include
#include
#include
#include
#include
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;
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;
}