进程间通信的概念:
在Linux环境下,进程地址空间是相互独立的,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所有不同进程间不能互相访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据拷到内核缓冲区中,进程2再从缓冲据中把数据读走,内核提供的这种机制称为进程间通信。
进程间通信的方式
常用的进程间通信方式有:
管道(使用最简单)
信号(开销最小)
共享映射区(无血缘关系)
本地套接字(最稳定)
管道-pipe
管道的概念:管道是一种最基本的IPC机制,也称匿名管道,应用于有血缘关系的进程之间,完成数据传递。调用pipe函数即可创建一个管道。
管道的本质是一块内核缓冲区,内部用环形队列实现
由两个文件描述符引起,一个表示读端,一个表示写端
规定数据从写端流入管道,从读端流出
当两个进程都终结的时候,管道也自动消失
管道的读端和写端默认都是阻塞的
管道的局限性:
管道中的数据一旦被读走就不在管道中了,不能反复读取。
数据只能在一个方向上流动,若要实现双向流动,必须使用两个管道
只能在有血缘关系的进程间使用管道
pipe函数
int pipe(int fd[2]);//若函数调用成功,fd[0]存放管道的读端,fd[1]存放管道的写端
父子进程使用管道通信
第一步:父进程创建管道
第二步:父进程fork出子进程
第三步:父进程关闭读端,子进程关闭写端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
//创建管道
//int pipe(int pipefd[2]);
int fd[2];
int ret = pipe(fd);
if(ret<0)
{
perror("pipe error");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)
{
//关闭读端
close(fd[0]);
sleep(5);
write(fd[1], "hello world", strlen("hello world"));
wait(NULL);
}
else
{
//关闭写端
close(fd[1]);
char buf[64];
memset(buf, 0x00, sizeof(buf));
int n = read(fd[0], buf, sizeof(buf));
printf("read over, n==[%d], buf==[%s]\n", n, buf);
}
return 0;
}
父子进程通信实现ps aux | grep bash
//使用pipe完成ps aux | grep bash操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
//创建管道
//int pipe(int pipefd[2]);
int fd[2];
int ret = pipe(fd);
if(ret<0)
{
perror("pipe error");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid<0)
{
perror("fork error");
return -1;
}
else if(pid>0)
{
//关闭读端
close(fd[0]);
//将标准输出重定向到管道的写端
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
perror("execlp error");
}
else
{
//关闭写端
close(fd[1]);
//将标准输入重定向到管道的读端
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "--color=auto", "bash", NULL);
perror("execlp error");
}
return 0;
}
FIFO
FIFO被称为命名管道,可以用于不相关的进程间通信
创建管道
方式1:使用命名 mkfifo
命令格式: mkfifo 管道名
方式2:使用函数
int mkfifo(const char *pathname, mode_t mode)
使用FIFO完成两个进程间通信
进程A:
创建一个fifo文件:myfifo
调用open函数打开myfifo文件
调用write函数写入一个字符串如:“hello world”(其实是将数据写入到了内核缓冲区)
调用close函数关闭myfifo文件
进程B:
调用open函数打开myfifo文件
调用read函数读取文件内容(其实就是从内核中读取数据)
打印显示读取的内容
调用close函数关闭myfifo文件
fifo_write
//fifo完成两个进程间通信的测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//创建fifo文件
//int mkfifo(const char *pathname, mode_t mode);
int ret = access("./myfifo", F_OK);
if(ret!=0)
{
ret = mkfifo("./myfifo", 0777);
if(ret<0)
{
perror("mkfifo error");
return -1;
}
}
//打开文件
int fd = open("./myfifo", O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//写fifo文件
int i = 0;
char buf[64];
while(1)
{
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "%d:%s", i, "hello world");
write(fd, buf, strlen(buf));
sleep(1);
i++;
}
//关闭文件
close(fd);
//getchar();
return 0;
}
fifo_read
//fifo完成两个进程间通信的测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//创建fifo文件
//int mkfifo(const char *pathname, mode_t mode);
//判断myfofo文件是否存在,若不存在则创建
int ret = access("./myfifo", F_OK);
if(ret!=0)
{
ret = mkfifo("./myfifo", 0777);
if(ret<0)
{
perror("mkfifo error");
return -1;
}
}
//打开文件
int fd = open("./myfifo", O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//读fifo文件
int n;
char buf[64];
while(1)
{
memset(buf, 0x00, sizeof(buf));
n = read(fd, buf, sizeof(buf));
printf("n==[%d], buf==[%s]\n", n, buf);
}
//关闭文件
close(fd);
//getchar();
return 0;
}
内存映射区
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区像映射。
映射工作可以通过mmap函数实现
mmap函数的作用是建立存储缓冲区
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags, intfd, off_t offset)
函数返回的是创建的映射区的首地址
addr: 指定映射的起始地址, 通常设为NULL, 由系统指定
length:映射到内存的文件长度
prot: 映射区的保护方式, 最常用的:
读:PROT_READ
写:PROT_WRITE
读写:PROT_READ | PROT_WRITE
flags: 映射区的特性, 可以是
MAP_SHARED: 写入映射区的数据会写回文件, 且允许其他映射该文件的进程共享。
MAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符, 代表要映射的文件。
offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射。
munmap函数
函数的作用:释放由mmap函数建立的存储映射区
函数原型:
int munmap(void *addr,size_t length)//addr表示mmap函数返回的映射区首地址,length是映射区的大小
mmap_write
//使用mmap函数完成两个不相干进程间通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
//使用mmap函数建立共享映射区
//void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
int fd = open("./test.log", O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
int len = lseek(fd, 0, SEEK_END);
//建立共享映射区
void * addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr==MAP_FAILED)
{
perror("mmap error");
return -1;
}
memcpy(addr, "0123456789", 10);
return 0;
}
mmap_read
//使用mmap函数完成两个不相干进程间通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
//使用mmap函数建立共享映射区
//void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);
int fd = open("./test.log", O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
int len = lseek(fd, 0, SEEK_END);
//建立共享映射区
void * addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr==MAP_FAILED)
{
perror("mmap error");
return -1;
}
char buf[64];
memset(buf, 0x00, sizeof(buf));
memcpy(buf, addr, 10);
printf("buf=[%s]\n", buf);
return 0;
}
mmap注意事项
创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
当MAP_SHARED时,映射区的权限必须<=文件打开的权限。
映射区的释放与文件的关闭无关,只要映射创建成功,文件可以立即关闭。
当映射文件大小为0时,不能创建映射区。
munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
文件偏移量必须为0或者4k的整数倍。
mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。