有名管道FIFO
FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于"有血缘关系"的进程间。但通过FIFO不相关的进程也能交换数据。
FIFO是Linux基础文件类型中的一种。但,FIFO文件磁盘上没有数据块,仅仅用来标识内核中的一条通道。实际上是在读写内存通道,这样就是实现了进程间通信。
有名管道使用注意事项
一个为只读而打开一个管道的进程会阻塞,直到另一个进程为只写打开管道
一个为只写而打开一个管道的进程会阻塞,直到另一个进程为只读打开管道
创建方式
命令:mkfifo 管道名
库函数:int mkfifo(const char* pathname,mode_t mode);
使用:
像普通文件一样使用就可以
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> using namespace std; int main(void) { //以只读的方式打开fifo int fd = open("myfifo",O_RDONLY); if(fd == -1) { perror("open"); exit(-1); } //不断的读数据 char buf[256]; while(1) { if(read(fd,buf,sizeof(buf)) == 0) { break; } cout<<buf<<endl; } return 0; } //===================================================== #include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> using namespace std; int main(void) { //先创建一个fifo int ret = mkfifo("myfifo",0664); //以只写的方式打开 int fd = open("myfifo",O_WRONLY); if(fd == -1) { perror("open"); exit(-1); } //向管道中写入数据 char buf[256]; strcpy(buf,"我是你爹..."); while(1) { sleep(5); write(fd,buf,strlen(buf)); } return 0; }
文件实现IPC
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> using namespace std; int main(void) { pid_t pid = fork(); if(pid == -1) { perror("fork"); exit(-1); } if(pid > 0) { int fd = open("a.txt",O_WRONLY|O_CREAT,0777); char buf[256]; strcpy(buf,"草泥马...故事开始在哪个梦中..."); write(fd,buf,strlen(buf)); } else if(pid == 0) { int fd = open("a.txt",O_RDONLY); sleep(2); char buf[256]; while(1) { int ret = read(fd,buf,sizeof(buf)); if(ret == 0) { break; } cout<<buf<<endl; } } return 0; }
内存映射(存储映射IO)
内存映射是将磁盘文件的数据映射到内存,用户通过修改内存就能够修改磁盘文件。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区中。这个映射工作可以通过mmap函数实现。
mmap函数
void *mmap(void *adrr,size_t length,int prot,int flags,int fd,off_t offset);
成功返回创建的映射区首地址,失败返回 MAP_FAILED宏
adrr:建立映射区的首地址,传NULL
length:欲创建映射区的大小
prot:映射区权限(PROT_READ PROT_WRITE PORT_READ|PORT_WRITE)
flags:标志位参数
MAP_SHARED:会将映射区所做的操作反映到物理设备上,映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项
MAP_PRTVATE:不同步
fd:用来建立映射区的文件描述符
offset:映射文件的偏移量(4K的整数倍)
munmap函数
释放建立的映射区
int munmap(void **addr,size_t length);
成功0 失败-1
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <unistd.h> using namespace std; int main(void) { int fd = open("a.txt",O_RDWR|O_CREAT,0777); if(fd == -1) { perror("open"); exit(-1); } ftruncate(fd, 100); char *p = (char *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(p == MAP_FAILED) { perror("mmap"); exit(-2); } strcpy(p,"aaa"); munmap(p,4); return 0; }
使用mmap注意事项
1.创建映射区的过程中,隐含着一次对映射文件的读操作
2.当MAP_SHARED时,要求文件具有打开去权限
3.映射区的释放与文件关闭无关。只要映射区建立成功,文件可以随时关闭
因为我们是对映射区操作就可以了
4.当文件大小为0时,不能创建映射区。否则容易出现总线错误
5.munmap传入的地址一定是首地址
6.文件偏移量必须为4K的整数倍
7.mmap创建映射区的出错概率很高,一定要检查返回值
mmap父子进程通信
父子等有血缘关系的进程之间可以通过mmap建立映射区来完成数据通信。fork之后父子进程间的mmap映射区是共享的。
1.打开文件
2.mmap建立的映射区(但必须要使用NAP_SHARED)
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> using namespace std; int main(void) { //打开一个文件 int fd = open("a.txt",O_RDWR); //父进程创建一个内存映射 char *p = (char *)mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(p == MAP_FAILED) { perror("mmap"); exit(-1); } //可以及时关闭这个文件了 close(fd); pid_t pid = fork(); if(pid == -1) { perror("fork"); exit(-1); } if(pid > 0) { sleep(2); //父读子写(这个无所谓哈) cout<<p<<endl; wait(NULL); munmap(p,100); } else if(pid == 0) { strcpy(p,"aaaa"); } if(pid > 0) munmap(p,4); return 0; }
不知道你们能不能体会到这种感觉,其实内存映射很简单。及时通过文件进程通信而已,只不过这样操作会更快更方便而言,本质上与文件实现IPC一样。
仔细一想,这样带来的函数是什么呢?每一个对比就没用伤害,频繁的在用户态和内核态切换还是蛮浪费时间的。通过文件来通信,减少了切换的频率,而且我们可以看到我们是在用户空间中建立的内存空闲区的。
通过映射,底层应该是做了值的拷贝。我们只需要操作内存区看可以实现父子之间进行通信。就像操作数组一样方便。其根基是因为fork之后父子进程的内存映射区是一样的。
匿名映射
我们看内存映射,还得先打开一个文件,以文件描述符为桥梁来操作,是不是感觉太麻烦了。有没有一种方式不用打开文件就可以操作呢?通过匿名映射使用MAP_ANONYMOUS(或MAP_ANON)
mmap(NULL,4,PROT_READ|PORT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
#include <iostream> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <string.h> using namespace std; int main(void) { //创建匿名映射区 char *p = (char *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0); if(p == MAP_FAILED) { perror("mmap"); exit(-1); } //创建子进程 pid_t pid = fork(); if(pid == -1) { perror("fork"); exit(-2); } if(pid > 0) { sleep(2); cout<<p<<endl; wait(NULL); munmap(p,4); } else if(pid == 0) { strcpy(p,"aaa"); } return 0; }
这里做一个小小的总结:
1.有关系的进程(父子进程)
-还没有子进程的时候
父进程先创建内存映射区
-创建子进程
-父子进程共享创建的内存映射区
2.没有关系的进程件通信(通过映射同一块磁盘文件)
-准备一个IO文件(大小不能为0)
-进程1:通过磁盘文件创建内存映射区
得到内存映射区首地址
-进程2:通过磁盘文件创建内存映射区
得到内存映射区首地址
-使用内存映射区通信
读端:
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <string> #include <unistd.h> using namespace std; #define PATHNAME "./a.txt" int main(void) { int fd = open(PATHNAME,O_RDONLY); if(fd == -1) { perror("open err"); exit(-1); } char *addr = (char *)mmap(NULL,100,PROT_READ,MAP_SHARED,fd,0); if(addr == MAP_FAILED) { perror("mmap err"); exit(-1); } while(1) { sleep(3); cout<<addr<<endl; } munmap(addr,100); return 0; }
写端:
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <string> #include <sys/types.h> #include <unistd.h> using namespace std; #define PATHNAME "./a.txt" int main(void) { int fd = open(PATHNAME,O_RDWR|O_CREAT,0777); if(fd == -1) { perror("open err"); exit(-1); } truncate(PATHNAME,100); char *addr = (char *)mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(addr == MAP_FAILED) { perror("mmap err"); exit(-1); } for(int i=0;i<100;i++) { strcpy(addr,"aaa"); sleep(1); } munmap(addr,100); return 0; }
共享内存
基本概念
1.共享内存允许两个或多个进程共享物理内存同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间一部分,因此这种PIC机制无需内核介入。所需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他进程共享同一个段的进程可用。
2.与管道等要求发送进程将数据从用户空间的缓冲区复制进内核和接受进程数据从内核内存复制进用户空间的缓冲区相比,这种IPC技术速度更快。
害,我都懒得说了。这个不就跟前面一样嘛,我们之前说过所以进程的内核区共享一块物理内存,管道是不是就是在内核去建立一个缓冲区,那不是跟这个原理一样嘛。所以我常说一定要把原理搞清楚。
数据实际上是写在物理内存中的,之前说虚拟地址空间,虚拟地址空间只需要实现数据结构上的页目和页表就可以了。它是方便我们操作的,更方便多进程的实现。
使用步骤(5步,分别调用5个函数)
1.调用shmget()创建一个新共享内存段或者取得一个已经存在的共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中用到的共享内存标识符。
2.使用shmat()来附上共享内存段,即使用该段成为调用进程的虚拟地址内存的一部分
3.为了引用这块共享内存,程序需要使用由shmat()调用返回addr值,它指向进程的虚拟地址空间中该共享内存段的起点。
4.调用shmdt()来分离共享内存
5.调用shmctl()来删除共享内存段。只有当当前所有附加内存段的进程的与值分离之后内存段才会销毁。只有一个进程需要执行这一步
int shmget(key_t key,size_t size,int shmflg);
void * shmat(int shmid,const void* shmaddr,int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
key_t fork(const char*pathname,int proj_id);
-------------------------------------------------------------------------------------------------
int shmget(key_t key,size_t size,int shmflg);
==功能:创建一个新的共享内存段或者获取一个既有的共享内存段的标识,新创建的内存段中的数据都会被初始化为0
==参数:
key: key_t类型是一个整型,通过这个找到或者创建一个共享内存。
一般以16进制表示,非0值
size: 共享内存的大小(页的整数倍)
shmflg: 属性
访问权限
附加属性:创建/判断共享内存是不是存在(IPC_EXCL)
创建:IPC_CREAT
判断共享内存是否存在:IPC_EXCL|IPC_CREAT|0664
==返回值
失败 -1 并设置 error
成功 >0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值
--------------------------------------------------------------------------------------------------------
int *shmat(int shmid,const void *shmaddr,int shmflg);
功能:和当前的进程进行关联
参数:
shmid:共享内存的标识符(ID),由shmget返回值获取
shmaddr:申请的共享内存的起始地址,指定NULL,内存指定
shmflg:对共享内存的操作
读:SHM_RDONLY
读写:0
返回值:
成功:返回共享内存的首地址,失败(void *)-1
--------------------------------------------------------------------------------------------------------------
int shmdt(const void* shmaddr);
功能:解除当前进程和共享内存的关联
参数:
shmaddr 共享内存的首地址
返回值:
成功 0,失败 -1
-----------------------------------------------------------------------------------------------------------------
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
功能:删除共享内存,共享内存要删除才会消失
创建共享内存的进程被销毁了对共享内存没用影响
参数:
shmid:共享内存的ID
cmd: 要做的操作
IPC_STAT 获取共享内存当前的状态
IPC_SET 设置共享内存的状态
IPC_RMAD 标记共享内存需要被销毁
buf: 需要设置或者获取的共享内存的属性信息
IPC_STAT buf存储数据
IPC_SET buf中需要初始化数据
IPC_RMID 没有用,NULL
-----------------------------------------------------------------------------------------------------------------
key_t fork(const char* pathname,int proj_id);
功能:根据指定路径名和int,生成一个共享内存的key
参数:
-pathname:指定一个存在的路径
-proj_id:int类型的值,但是系统调用只会使用其中一个1字节 (范围0~255)
-----------------------------------------------------------------------------------------------------------------
案例:
server端读数据
#include <iostream> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> using namespace std; #define PATHNAME "./" #define PROJ_ID 0x666 #define SIZE 4096 int main(void) { //生成一个共享内存 key key_t key = ftok(PATHNAME,PROJ_ID); if(key == -1) { perror("fork err"); exit(-1); } //创建一个共享内存,如果存在则获取。拿到id int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0664); if(shmid == -1) { perror("shmget err:"); exit(-1); } cout<<"shmid:"<<shmid<<endl; //和当前进程进行关联,读写操作 char *addr = (char *)shmat(shmid,NULL,0); //进行数据交换 while(1) { sleep(1); cout<<addr<<endl; } //分离当前进程 shmdt(addr); //删除共享内存段 int ret = shmctl(shmid,IPC_RMID,NULL); if(ret == -1) { perror("shmctl err"); exit(-1); } return 0; }
如果出现如下错误: 这个id已经被占用,移除就可
终端输入:
ipcs -m
ipcrm -m 对应的shmid
然后再./server
client端写数据
#include <iostream> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <string.h> using namespace std; #define PATHNAME "./" #define PROJ_ID 0x666 #define SIZE 4096 int main(void) { //先获取key key_t key = ftok(PATHNAME,PROJ_ID); if(key == -1) { perror("ftok err"); exit(-1); } //获取共享内存块的id int shmid = shmget(key,SIZE,0); if(shmid == -1) { perror("shmget err"); exit(-1); } //和的当前进程进行关联 char *addr = (char *)shmat(shmid,NULL,0); //进行数据交换 for(int i=0;i<10;++i) { strcpy(addr,"aaaaa"); } //分离 shmdt(addr); return 0; }
本来想把信号也放到这里,信号要是进程间通信的一种方式嘛...
感觉篇幅有点长了,我另写一篇文章