因为每一个进程都拥有自己的独立的进程虚拟地址空间,导致了进程的独立性。进程间的通信技术是为了让各个进程之间可以很好的交换数据或者进程控制而产生的。
常用的进程间通信的方式有管道、共享内存、消息队列和信号量。
管道有分为匿名管道和命名管道两种,接下来主要介绍管道通信中的匿名管道。匿名管道就是指在内核中创建出来的一块没有标识符的一段缓冲区。
在Linux系统中,可以用pipe函数来创建匿名管道
函数原型:
#include
int pipe(int fd[2]);
参数解释:
fd[2]:
(1)本质上是一个出参,fd[0],fd[1]当中的值是pipe函数赋予的
(2)fd[2]数组中有两个元素,保存的都是文件描述符,fd[0]表示写端,fd[1]表示读端
返回值:
成功返回0,失败返回-1
例子:
#include
#include
#include
#include
int main()
{
int fd[2];
char buf[100];
int len;
int pipeid = pipe(fd);
if(pipeid < 0)
{
perror("pipe");
return -1;
}
while(fgets(buf,100,stdin))
{
len = strlen(buf);
if(write(fd[1],buf,len) != len)
{
perror("write to pipe!\n");
break;
}
memset(buf,0x00,sizeof(buf));
if((len = read(fd[0],buf,100)) == -1 )
{
perror("read from pipe!\n");
break;
}
if(write(1,buf,len) != len)
{
perror("writer to stfout!\n");
break;
}
}
}
管道是半双工通信,这就意味着数据流向只能是从写端流向读端
查看进程ID
用ll /proc/进程号/fd命令查看
关闭进程,再次查看,发现找不到该目录
说明管道的生命周期是跟随进程的。
匿名管道是没有标识符的,就像一个人没有名字一样,我们也不认识他,所以在茫茫人海中,我们就无法通过名字找到这个人,这也就意味着进程无法通过标识符来找到这个管道,但是进程之间又要进行通信,这时候我们就联想到子进程的创建了,子进程创建的原理是复制父进程的PCB,代码共享数据独享,因此我们可以通过创建子进程的方法来找到这个管道实现进程间的通信。(注意:一定要先创建管道在创建子进程)
例子:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
char buf[1024] = {0};
if(ret < 0)
{
perror("pipe");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(0 == pid)
{
ssize_t ret = read(fd[0],buf,sizeof(buf) - 1);
if(ret < 0)
{
perror("read");
}
printf("i am child ,buf is:%s\n",buf);
sleep(1);
}
else
{
printf("i am fsther,I will write:");
fgets(buf,100,stdin);
int len = strlen(buf);
if(write(fd[1],buf,len) != len)
{
perror("write");
sleep(1);
}
return 0;
}
1.进程操作写端往匿名管道当中写的时候,如果写入的字节数量小于4k,则保证当前写入操作的原子性
首先我们先用代码验证一下管道能存储多大的数据:
代码:
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return -1;
}
int count = 0;
while(1)
{
write(fd[1], "h", 1);
count++;
printf("count:%d\n",count);
}
return 0;
}
运行结果:
可以看到管道能存储的最大数据为65536字节,也就是6K,其实中间还有一个关键值于PIPE_BUF(4K),当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性,就有可能会出现如下情况
数据中途配打断,失去了原有的含义,被破坏。
2.将写端文件描述符设置成非阻塞属性,读端文件描述符不管
(1)读端文件描述符关闭,调用writer的进程会收到管道破裂的信号SIGPIPE,导致当前调用writer的进程会退出掉(如果是子进程调用writer,可能会产生僵尸进程,如果是父进程会产生孤儿进程)
代码:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return -1;
}
int flag = fcntl(fd[1], F_GETFL);
fcntl(fd[1], F_SETFL, flag | O_NONBLOCK);
int pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
close(fd[0]);
int count = 0;
while(1)
{
ssize_t ret = write(fd[1], "l", 1);
printf("ret : %d\n", ret);
if(w_size <= 0)
{
perror("write");
break;
}
count++;
printf("write : %d\n", count);
}
}
else
{
//father
close(fd[0]);
while(1)
{
printf("i am father!\n");
sleep(1);
}
}
return 0;
}
(2)读端文件描述符不关闭,但是也不读,写端一直写,直到将管道写满,则再次调用writer函数,会返回-1,报错:资源不可用
代码:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return -1;
}
int flag = fcntl(fd[1], F_GETFL);
fcntl(fd[1], F_SETFL, flag | O_NONBLOCK);
int pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
while(1)
{
int count = 0;
while(1)
{
ssize_t ret = write(fd[1], "l", 1);
printf("ret : %d\n", ret);
if(write <= 0)
{
perror("write");
break;
}
count++;
printf("write : %d\n", count);
}
}
else
{
while(1)
{
printf("i am father!\n");
sleep(1);
}
return 0;
}
运行结果:
3.将读端文件描述符设置成非阻塞属性,写端文件描述符不管
(1)写端文件描述符关闭,读端一直读,直到将管道的内容读完,再次调用read函数,返回0,表示没有读到任何内容
代码:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return -1;
}
int flag = fcntl(fd[0], F_GETFL);
fcntl(fd[0], F_SETFL, flag | O_NONBLOCK);
int pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
//child
close(fd[1]);
while(1)
{
char buf[1024] = {0};
ssize_t ret = read(fd[0], buf, sizeof(buf) - 1);
printf("ret = %d\n", ret);
if(ret < 0)
{
perror("read");
break;
}
sleep(1);
}
}
else
{
close(fd[1]);
while(1)
{
printf("i am father\n");
sleep(1);
}
}
return 0;
}
(2)写端文件描述符不关闭,但是也不写,读端调用read函数,则read函会返回-1,报错:资源不可用
代码:
#include
#include
#include
#include
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret < 0)
{
perror("pipe");
return -1;
}
int flag = fcntl(fd[0], F_GETFL);
fcntl(fd[0], F_SETFL, flag | O_NONBLOCK);
int pid = fork();
if(pid < 0)
{
perror("fork");
return -1;
}
else if(pid == 0)
{
//child
//close(fd[1]);
while(1)
{
char buf[1024] = {0};
ssize_t ret = read(fd[0], buf, sizeof(buf) - 1);
printf("ret = %d\n", ret);
if(ret < 0)
{
perror("read");
break;
}
sleep(1);
}
}
else
{
//close(fd[1]);
while(1)
{
printf("i am father\n");
sleep(1);
}
}
return 0;
}