目录
进程间通信IPC(Inter Process Communicating)主要包括:
无名管道pipe、有名管道fifo、内存映射mmap、共享内存shm,消息队列msg、信号signal、套接字socket(多机之间,多用于网络编程)
一、无名管道PIPE
管道,单工通信,通常指无名管道(无名在于它是伪文件),存在(运行)内存中,只能用于具有亲缘关系的父子进程或兄弟进程(管道可共享于2个进程以上),因为管道数据是通过队列来维护的,一端固定只能读或写,管道建立时会创建两个文件描述符分别用于读写管道,所以只能用文件IO来操作,管道中的数据只能读一次,再读也没内容

int pipe(int fd[2]);
@param: 大小为2的int型数组,用来保存创建的两个文件描述符,fd[0]用来读、fd[1]用来写
@return:成功返回0,失败返回-1
读写特性:
1.读管道:
(1)管道中有数据,read从头读数据,返回实际读到的字节数
(2)管道中无数据且写端没有关闭时,read阻塞等待数据写入;写端关闭时,read返回0
2.写管道:
(1)读端全部关闭,“管道会发生爆裂”,进程异常终止,可捕捉SIGPIPE来让进程不终止
(2)读端没有全部关且管道未满,正常追加写入,write返回实际写入字节数;管道满了(大小64K),write阻塞
示例
父进程将键盘输入的信息写进管道,子进程读管道并打印
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd[2];
pid_t pid;
char writebuf[128] = {'\0'};
char readbuf[128];
int readnum;
//creat pipe
if((pipe(fd)) == -1)
{
perror("pipe");
return 0;
}
//fork
if((pid = fork()) == -1)
{
perror("fork");
return 0;
}
if(pid == 0) //child process
{
close(fd[1]);//ready to read pipe
while(1)
{
bzero(readbuf,128);
do
{
readnum = read(fd[0],readbuf,128);//阻塞读管道
} while (readnum < 0 && readnum == EINTR);
if(readnum < 0)
{
perror("read");
}
if(readnum == 0)
{
printf("child quit!\n");
break;
}
printf("child receive:%s\n",readbuf);
}
close(fd[0]);
}else //father process
{
close(fd[0]);//ready to write pipe
while(1)
{
bzero(writebuf,128);
if(fgets(writebuf,127,stdin) == NULL)//键盘获取输入信息
{
continue;
}
writebuf[strlen(writebuf)-1] = '\0';//注意fgets自动添加换行符 所以手动去除
write(fd[1],writebuf,128);//写进管道
if(strncasecmp(writebuf,"quit",strlen("quit")) == 0)
{
printf("father quit!\n");
break;
}
}
close(fd[1]);
}
return 0;
}
二、有名管道FIFO
一种文件类型,特殊文件,有路径名与之关联,但数据存在运行内存中(ls -la命令查看文件大小始终为0),可在任意无关进程之间交换数据,一端只读或只写,半双工通信,不支持lseek操作,遵循先进先出,同样,FIFO中的数据读一次就清空了,读FIFO从头读,写FIFO追加写
功能:创建或打开fifo
int mkfifo(char *pathname, mode_t mode)
@param: pathname 创建时给FIFO取个名
打开时指定FIFO
mode 创建时,mode不用加O_CREAT,直接赋值权限即可
打开时,mode设置O_WRONLY/O_RDONLY/O_RDWR 三选一
@return:成功返回0,失败返回-1(若存在同名fifo,创建失败,errno置EEXIST)
注意事项:
open fifo时,某进程只读或只写打开(mode设置O_WRONLY/O_RDONLY),会默认阻塞。若mode中或上O_NONBLOCK,则为非阻塞状态。
1)读写进程都默认阻塞时:先运行写或者读进程(被阻塞),再运行读或者写进程,不再阻塞,可以正常读写。
2)读进程非阻塞时:
先运行写进程(被阻塞),再运行读进程,可以正常读写;
先运行读进程,直接崩溃,因为没数据的情况下还不阻塞读!!!
3)写进程非阻塞时:只能先运行读进程,再运行写进程
关于数据完整性,多个进程写同一个管道时,如果写入数据长度<=4K,要么全部写入,要么一个字节都不写入(遵循先进先出);如果写入数据长度过长会导致数据交错
示例
实现不同进程间通过fifo通信
fifowr.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
char wrbuf[128];
if(mkfifo("fifo1",0600) == -1 && errno != EEXIST) //如果创建失败并且是因为该fifo已存在
{
perror("mkfifo");
return 0;
}
fd = open("fifo1",O_WRONLY);
if(fd < 0)
{
perror("open fifo");
return 0;
}
while(1)
{
fgets(wrbuf,128,stdin); //从键盘获取信息到wrbuf
int nwrite = write(fd,wrbuf,strlen(wrbuf));//写进fifo
printf("write %d bytes:%s\n",nwrite,wrbuf);
}
return 0;
}
fiford.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
char rdbuf[128];
if(mkfifo("fifo1",0600) == -1 && errno != EEXIST) //如果创建失败并且是因为该fifo已存在
{
perror("mkfifo");
return 0;
}
fd = open("fifo1",O_RDONLY);
if(fd < 0)
{
perror("open fifo");
return 0;
}
while(1)
{
memset(rdbuf,'\0',128);
int nread = read(fd,rdbuf,128); //读出fifo
rdbuf[strlen(rdbuf)-1] = '\0';
if(nread > 0)
{
printf("read %d bytes:%s\n",nread,rdbuf);
}else{break;};
}
return 0;
}
三、内存映射mmap
内存映射可以通过mmap()映射普通文件,将一个磁盘文件映射到进程自身的地址空间中,进程访问它通过返回的指针即可,不必调用read/write,全双工通信,因为访问内存的时间是纳秒级的,而访问磁盘是毫秒级的,大大提升了通信效率,适合于需要频繁读写大文件的场景
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
功能:申请创建 文件的内存映射区
@param:
addr 映射区目的地址,一般设置为NULL,操作系统自动分配,可通过返回值获取
length 必须>0 映射区的字节数,从文件开头offset个字节开始算起
prot 指定共享内存的访问权限 PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE
flags 常用的有:MAP_SHARED共享的、MAP_PRIVATE私有的,频繁访问磁盘操作时、MAP_ANOYMOUS匿名映射(用于无fd文件,比如父子进程间通信)
fd 文件描述符,匿名映射时为-1
offset 偏移量,指从文件头偏移offset个字节开始映射,一般都给0表示完整映射到映射区
@return:
成功返回映射区的地址,后续的读写操作通过这个地址
失败返回MAP_FAILED
int munmap(void *addr, size_t length)
功能:释放内存映射,即断开连接
@param: addr 映射区首地址 length 映射区大小
注意事项:
创建映射区出错概率很高,应养成返回值查错习惯!
flags为MAP_SHARED时,要求指定的共享内存访问权限<=open的文件权限;为MAP_PRIVATE时,只需要文件有可读权限即可,操作仅对内存有效,不会写入到磁盘,且不能在进程间共享
要映射的文件大小必须>0,否则会报错“总线错误”,系统所分配的映射区大小是以4K(一页内存大小)为单位的,文件大小<4K,系统仍分配4K,超过文件大小的范围可以访问,但仅仅是访问,无法真正写入文件
共享内存映射成功后,可以立即关闭文件,对后续共享内存的操作无影响
offset的偏移量必须为0/4K的整数倍
示例
mmapwr.c
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
void *addr;
int fd;
char wrbuf[128];
fd = open("test_mmap",O_RDWR);
if(fd < 0)
{
perror("open");
return 0;
}
addr = mmap(NULL,128,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(addr == MAP_FAILED)
{
perror("mmap");
return 0;
}
close(fd);
while(1) //mmap opened up at least 4K space
{
bzero(wrbuf,128);
fgets(wrbuf,127,stdin);
wrbuf[strlen(wrbuf)-1] = '\0';//手动去除换行符
memset(addr,0,128);
memcpy(addr,wrbuf,strlen(wrbuf));
if( strncasecmp(wrbuf,"quit",strlen("quit")) == 0) //输入了quit
{
break;
}
}
munmap(addr,128);
close(fd);
return 0;
}
mmaprd.c
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
void *addr;
int fd;
fd = open("test_mmap",O_RDWR);
if(fd < 0)
{
perror("open");
return 0;
}
addr = mmap(NULL,128,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(addr == MAP_FAILED)
{
perror("mmap");
return 0;
}
close(fd);
while(1)
{
printf("read contents:%s\n",(char *)addr);
if( strncasecmp(addr,"quit",strlen("quit")) == 0) //输入了quit
{
break;
}
sleep(1);
}
munmap(addr,128);
close(fd);
return 0;
}
目前这个实验还存在一个bug,具体如下:写进程停止写后,读进程读着读着少了一个字符
后续试试信号量的pv操作来解决~


被折叠的 条评论
为什么被折叠?



