笔记三:进程间的通信(管道)
1)内核空间的对象是不存在的;
1)内核空间的对象是不存在的;
2)进程间的通信每一种通信方式都是基于文件IO思想;open函数,read函数,write函数,close函数等等;
3)通过open函数打开或者创建一个文件(只能创建普通文件),当我们打开一个文件,就会在内核空间开辟一个缓存,通过write往内核写,以及用户空间从内核read;
对象不一样意味着通信方式不一样
2.有哪几种通信方式?
管道通信:
无名管道、有名管道(文件系统中有名)
信号通信:
信号(通知)通信包括:信号的发送、信号的接收和信号的处理。
IPC(Inter-Process Communication)通信:
共享内存、消息队列和信号灯。
以上是单机模式下的进程通信(只有一个Linux内核)
Socket通信:
存在于一个网络中两个进程之间的通信(两个Linux内核)。
管道:
无名管道和有名管道(管道其实就是一个队列)只能在两端就行插入和删除 出队:读 入队:写
无名管道:
在文件系统中无文件节点或者说无文件名(特殊的文件);
有名管道:
在文件系统中有文件节点或者说有文件名(特殊的文件);
二:无名管道
通信原理:
管道文件是一个特殊的文件,是由队列来实现的。
无名管道通过pipe函数创建特殊文件(管道)返回一个文件描叙符
无名管道通过pipe函数创建特殊文件(管道)返回一个文件描叙符
在文件IO中创建一个文件或打开一个文件是由open函数来实现的,它不能创建管道文件。只能用pipe函数来创建管道。
函数形式:int pipe(int fd[2])
功能:创建管道,为系统调用:unistd.h
参数:就是得到的文件描述符。可见有两个文件描述符:fd[0]和fd[1],管道有一个读端fd[0]用来读和一个写端fd[1]用来写,这个规定不能变。
返回值:成功是0,出错是-1;
例1:pipe函数使用
/*无名管道*/
#include "unistd.h"
#include "string.h"
#include "stdio.h"
int main()
{
int fd[2];
int ret;
char buf[]="hello linux\n";
char readbuf[128]={0};
ret=pipe(fd);//创建管道
if(ret < 0)
{
printf("创建管道文件失败");
return -1;
}
printf("%d,%d\n",fd[0],fd[1]);//fd[0] read fd[1] write
write(fd[1],buf,sizeof(buf));//往管道中写
read(fd[0],readbuf,128);
printf("readbuf=%s",readbuf);
memset(readbuf,0,128);//清空数组
read(fd[0],readbuf,128);//从管道中读取
printf("second read :readbuf=%s",readbuf);
close(fd[0]);
close(fd[1]);
return 0;
}
注意:
管道中的东西,读完了就删除了;队列
如果管道中没有东西可读,则会读阻塞。
例2:验证读阻塞。
读取完以后如果在读取进程将处于阻塞状态
例3:验证写阻塞:可以计算出内核开辟的管道有多大。5456 处于不阻塞 5457处于阻塞状态
/*无名管道*/
#include "unistd.h"
#include "string.h"
#include "stdio.h"
int main()
{
int fd[2];
int ret;
int i = 0;
char buf[]="hello linux\n";
char readbuf[128]={0};
ret=pipe(fd);//创建管道
if(ret < 0)
{
printf("创建管道文件失败");
return -1;
}
printf("%d,%d\n",fd[0],fd[1]);//fd[0]:read fd[1]:write
while(i < 5500)
{
write(fd[1],buf,sizeof(buf));//往管道中写
i++;
}
/*总共写5500次(hello linux),如果阻塞已经超过范围,那么下面一句话将不会打印出来*/
printf("write pipe end\n");
close(fd[0]);
close(fd[1]);
return 0;
}
例4:实现进程通信
通过管道实现用户间的进程通信(实现A与B通信,要对同一个管道进行通信)通过fork函数把父进程都进行了拷贝,除了pid号
/*通过pipe管道实现两个进程间的通信,如果没有管道进程是无法在用户空间进行通信的*/
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
int pid;
int i;
char flag=0;
int fd[2];
pipe(fd);//必须在创建父子进程之前创建管道,保证对同一个管道进行通信(操作);
pid=fork();
if(pid < 0)
return -1;
/*达到父进程先运行,子进程才运行目的*/
if(pid == 0)
{
read(fd[0],&flag,1);//pipd empty --- > sleep
// while(flag ==0 );
if(flag == 1)
{
for(i=0;i<10;i++)
{
usleep(100);
printf("this is child process i=%d\n",i);
}
}
while(1);
}
if(pid > 0)
{
for(i=0;i<10;i++)
{
usleep(100);
printf("this is parent process i=%d\n",i);
}
flag =1 ;
write(fd[1],&flag,1);
while(1);
}
return 0;
}
无名管道的缺点:只能实现父子进程(有亲缘关系进程)之间的通信,正由于这无名管道的缺点,对无名管道进行改进:有名管道。
所谓的有名,即文件系统中存在这个一样文件节点,每一个文件节点都有一个inode号
而且这是一个特殊的文件类型:p管道类型。
1.
创建这个文件节点,不可以通过open 函数,open 函数只能创建普通文件,不能创建特殊文件(管道-mkfifo,套接字-socket,字符设备文件-mknod,块设备文件-mknod,符号链接文件-ln –s,目录文件mkdir)
2.
管道文件只有inode号,不占磁盘块空间,和套接字、字符设备文件、块设备文件一样。普通文件和符号链接文件及目录文件,不仅有inode号,还占磁盘块空间。
3. mkfifo 用来创建管道文件的节点,没有在内核中创建管道。
只有通过open 函数打开这个文件时才会在内核空间创建管道。
有名管道(由于无名管道只能实现父子进程或有情缘关系进程的通信,但是往往我们的程序(实际应用)都是无亲缘关系,所以就衍生了有名管道)
普通文件 ------------> ‘_’ 通过open函数创建
目录文件 ------------> 'd' 通过mkdir函数创建
链接文件(主要是指软链接)-----------> ‘l’ ln -s 选项创建
管道文件 ---------------> 通过mkfifo或者pipe系统调用函数创建
三.有名管道
mkfifo(只是生成了一个文件,并没有在内核生成一个管道,当通过open函数打开这个节点时,会在内核空间生成一个管道(缓存))
mkfifo(只是生成了一个文件,并没有在内核生成一个管道,当通过open函数打开这个节点时,会在内核空间生成一个管道(缓存))
函数形式:int mkfifo(const char *filename,mode_t mode);
功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
返回值:创建成功返回0,创建失败返回-1。
例1:mkfifo的用法。
#include "unistd.h"
#include "stdio.h"
int main()
{
int fd;
fd = mkfifo("./a.c",0777);//在内核创建一个文件,此时并没有在内核创建管道,只有通过open函数打开时才会在内核创建一个管道
if(fd < 0)
{
printf("creat mkfifo failure\n");
return -1;
}
return 0;
}
例子:通过管道实现无情缘进程间的通信
/*mkfifo创建管道(mkfifo.c)*/
#include "stdio.h"
#include "unistd.h"
int main()
{
int fd,fd1;
fd = mkfifo("./a.c",0777);
if(fd < 0)
{
printf("creat myfifo a.c failure\n");
return -1;
}
fd1 = mkfifo("./b.c",0777);
if(fd1 < 0)
{
printf("creat myfifo b.c failure\n");
return -2;
}
return 0;
}
/*read端(read.c)*/
#include "unistd.h"
#include "fcntl.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
int fd;
int ret;
int pid;
char writebuf[128] = {0},readbuf[128] = {0};
pid = fork();
if(pid < 0)
{
printf("creat process failure\n");
return -1;
}
if(pid > 0)
{
fd = open("./b.c",O_WRONLY);//当open函数打开这个管道文件时,会在内核生成一个管道(管道);
while(1)
{
fgets(writebuf,128,stdin);
if(strncmp(writebuf,"quit",4) == 0)
break;
write(fd,writebuf,strlen(writebuf));
memset(writebuf,0,128);
}
}
if(pid == 0)
{
fd =open("./a.c",O_RDONLY);
while(1)
{
read(fd,readbuf,128);
if(strncmp(readbuf,"quit",4) == 0)
break;
printf("from a.c read=%s\n",readbuf);
memset(readbuf,0,128);
}
}
return 0;
}
/*write端(write.c)*/
#include "unistd.h"
#include "fcntl.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
int fd;
int ret;
int pid;
char writebuf[128] = {0},readbuf[128] = {0};
pid = fork();
if(pid < 0)
{
printf("creat process failure\n");
return -1;
}
if(pid > 0)
{
fd = open("./a.c",O_WRONLY);
while(1)
{
fgets(writebuf,128,stdin);
if(strncmp(writebuf,"quit",4) == 0)
break;
write(fd,writebuf,strlen(writebuf));
memset(writebuf,0,128);
}
}
if(pid == 0)
{
fd =open("./b.c",O_RDONLY);
while(1)
{
read(fd,readbuf,128);
if(strncmp(readbuf,"quit",4) == 0)
break;
printf("from b.c read=%s\n",readbuf);
memset(readbuf,0,128);
}
}
return 0;
}