IPC--进程间通讯教程
常用的进程间通讯方式:
a.管道(使用最简单)匿名管道
b.信号(开销最小)
c.共享映射区(无血缘关系)
d.本地套接字(最稳定)
e.FIFO(命名管道)
1.管道:
pipe:管道一般读写行为
2.fifo:(有名管道)
用于非血缘关系进程间通讯
命令:mkfifo
函数:mkfifo
3.共享映射区:
mmap
函数的参数使用注意事项
用于非血缘关系进程间通讯
管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1.其本质是一个伪文件(实为内核缓冲区)
七种文件类型:
1). - 文件 2). d 目录 3). l 字符链接 此三种为真实文件,占据磁盘存储空间
4). s 套接字 5). b 块设备 6). c 字符设备 7). p 管道 此四种为伪文件
2.有两个文件描述符引用,一个表示读端,一个表示写端。
3.规定数据从管道的写端流入管道,从读端流出。
管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。【使用ulimit -a查看,函数fpathconf 参2:\_PC\_PIPE\_BUF】
管道的局限性:
1.数据自己读不能自己写
2.数据一旦被读走,便不在管道中存在,不可反复读取。
3.由于管道采用半双工通讯方式。因此数据只能在一个方向上流动
4.只能在有公共祖先的进程间使用管道
常见的通信方式有:单工通信、半双工通信、全双工通信。
模拟一个管道间pipe的通信:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
int fd[2];
pid_t pid;
int ret = pipe(fd);
if(ret == -1){
perror("pipe error.");
exit(1);
}
pid = fork();
if(pid == -1){
perror("fork error.");
exit(1);
} else if(pid == 0) {
//规定子进程负责读端,父进程负责写端
//默认fd[0]--读端,fd[1]--写端
//关闭子进程的写端
close(fd[1]);
char buf[1024];
ret = read(fd[0], buf, sizeof(buf));
if(ret == 0){
printf("---finished read---");
}
write(STDOUT_FILENO, buf, ret);
} else {
//关闭父进程的读端
close(fd[0]);
write(fd[1], "hello pipe\n", strlen("hello pipe\n"));
}
}
共享内存:
1.mmap函数:参数、返回值
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
返回:
成功:返回创建的映射区首地址
失败:MAP_FAILED 宏
参数:
addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length: 预创建映射区的大小
prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备
fd: 用来建立映射区的文件描述符
offset: 映射文件的偏移(4k的整数倍)
mmap示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(void)
{
int len, ret;
char *p = NULL;
int fd = open("mytest.txt", O_CREAT|O_RDWR, 0644);
if(fd < 0) {
perror("open error:");
exit(1);
}
len = ftruncate(fd, 5);
if(len == -1) {
perror("ftruncate error:");
exit(1);
}
p = mmap(NULL, 5, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED) {
perror("mmap error:");
exit(1);
}
strcpy(p, "abc\n");
ret = munmap(p, 5);
if(ret == -1) {
perror("munmap error:");
exit(1);
}
close(fd);
return 0;
}
munmap函数
同malloc函数申请内存空间类似的,mmap建立的映射区在使用之后也应调用类似free的函数来释放。
使用mmap时的注意事项:
1).创建映射区的过程中,隐含着一次对映射文件的读操作
2).当MAP\_SHARED时,要求:映射区的权限应<=文件打开的权限(出于对映射区的保护)。而MAP\_PRIVATE则无所谓,
因为mmap中的权限是对内存的限制。
3).映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
4).特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!
mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
5).munmap传入的地址一定是mmap的返回地址,坚决杜绝指针++操作。
6).如果有文件偏移量必须为4k的整数倍(4*1024*n)
7).mmap创建映射区的出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
2.借助共享内存放磁盘文件,借助指针访问磁盘文件
3.父子进程、血缘关系进程通信
4.匿名映射区
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也比较容易。但缺陷是,每次创建映射区
一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。可以直接
使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助
标志位参数flags来指定。
使用MAP_ANONYMOUS (或 MAP_ANON),如:
int *p=mmap(NULL,4,PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
"4"可以为任意值,可以根据实际需要填写。
需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中无该宏定义,可使用下面两步来创建匿名映射区:
1). fd=open("/dev/zero", O_RDWR);
2). P=mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
5.mmap无血缘关系进程间通信
mmap\_r.c和mmap\_w.c共用一个文件file\_shared模拟无血缘关系进程间通信
mmap\_r.c表示读进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int fd;
struct STU student;
struct STU *mm;
if(argc < 2) {
printf("./a.out file_shared\n");
exit(-1);
}
fd = open(argv[1], O_RDONLY);
if(fd == -1){
sys_err("open error:");
}
mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED){
sys_err("mmap error:");
}
close(fd);
while(1) {
printf("id=%d\tname=%s\t%c\n", mm->id, mm->name, mm->sex);
sleep(2);
}
munmap(mm, sizeof(student));
return 0;
}
mmap\_w.c表示写进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
struct STU {
int id;
char name[20];
char sex;
};
void sys_err(char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int fd;
struct STU student = {10, "xiaoming", 'm'};
char *mm;
if(argc < 2) {
printf("./a.out file_shared\n");
exit(-1);
}
fd = open(argv[1], O_RDWR | O_CREAT, 0664);
ftruncate(fd, sizeof(student));
mm = mmap(NULL, sizeof(student), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mm == MAP_FAILED){
sys_err("mmap error:");
}
close(fd);
while(1) {
memcpy(mm, &student, sizeof(student));
student.id++;
sleep(1);
}
munmap(mm, sizeof(student));
return 0;
}
先执行mmap\_w.c后执行mmap\_r.c
可以使用 strace mmap\_w.out 来追踪mmap\_w.out执行过程中调用了哪些系统函数。
yum install strace -y 来安装strace
模拟进程组:cat | cat | cat | cat
使用 ps ajx来查看
杀掉进程组 kill -9 -gpid