什么是进程间通信?
linux环境下,进程地址空间相互独立,每个进程都有各自的用户地址空间。进程之间不能相互访问,要交换数据必须通过内核,在内核开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
进程间通信的方式有:
文件、管道、命名管道、共享内存、信号、消息队列、套接字
用于有血缘关系的进程之间,调用管道pipe函数即可创建一个管道
特性:
ulimit -a
来查看;管道局限性
创建管道
int pipe(int fd[2]);
param:
若函数调用成功,fd[0]存放读端,fd[1]存放写端
return:
成功:0
失败:-1,并设置errno
1、父进程创建管道
2、父进程fork出子进程
3、父进程关闭fd[0],子进程关闭fd[1]
#include
#include
#include
#include
#include
#include
int main()
{
int fd[2];
pipe(fd);
pid_t pid=fork();
if(pid>0)
{
close(fd[0]);//父进程关闭读端
write(fd[1], "hello world", strlen("hello world"));
wait(NULL);//回收子进程,保证一定是父进程后退出(原因请看"linux——进程"那一节)
}
else if(pid==0)
{
close(fd[1]);//关闭写端
char buf[128];
memset(buf, 0, sizeof(buf));
int n = read(fd[0], buf, sizeof(buf));
printf("n=[%d], buf=[%s]\n", n, buf);
}
return 0;
}
n=[11], buf=[hello world]
进阶练习
使用execlp函数和dup2函数,实现 ps aux | grep bash
的父子进程间通信
1、创建管道
2、创建子进程
3、父进程关闭读端fd[0]
4、子进程关闭写端fd[1]
5、父进程将标准输出重定向到写端
6、子进程将标准输入重定向到读端
7、父进程调用execl函数执行ps aux命令
8、子进程调用execl函数执行grep bash命令
9、父进程回收子进程
#include
#include
#include
#include
#include
#include
int main()
{
int fd[2];
pipe(fd);
pid_t pid=fork();
if(pid>0)
{
close(fd[0]);//父进程关闭读端
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
wait(NULL);//回收子进程,保证一定是父进程后退出
}
else if(pid==0)
{
close(fd[1]);//关闭写端
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "--color=auto", "bash", NULL);
}
return 0;
}
duan 3902 0.0 0.2 11600 5444 pts/0 Ss 04:50 0:00 bash
duan 4330 0.0 0.2 11492 5284 pts/1 Ss+ 05:58 0:00 bash
duan 4486 0.0 0.2 11492 5356 pts/2 Ss+ 06:02 0:00 bash
duan 8217 0.0 0.0 9400 720 pts/0 S+ 08:37 0:00 grep --color=auto bash
管道的读写行为
设置管道为非阻塞
1、
int flag = fcntl(fd[0], F_SETFL, 0);
2、flag |= O_NONBLOCK;
3、fcntl(fd[0], F_SETFL, flag);
FIFO是linux文件类型的一种(P),但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来表示内核中的一条通道,进程可以打开这个文件进行读写,实际上是在读写内核缓冲区,这样就实现了进程间通信。FIFO可用于不相关的进程。
mkfifo 管道名
int mkfifo(const char* pathname, mode_t mode);
FIFO严格遵循先进先出(first in first out),由于FIFO是大小为0的文件,所以不能使用lseek等文件定位操作。
进程间通信
两个进程通信:进程1要先启动,进程2后启动(可以用access函数来解决这个缺点)
进程1
1 创建FIFO文件
2 open fifo文件,获得一个文件描述符
3 写FIFO文件
4 关闭FIFO文件
进程2
1 打开FIFO文件,获得文件描述符
2 读FIFO文件
3 关闭文件
int main()
{
int ret = access("./myfifo", F_OK);//access判断文件是否存在,这样两个进程无所谓谁先运行
if(ret!=0)
mkfifo("./myfifo", 0777);
int fd = open("myfifo", O_RDWR);
char buf[64];
int n;
while(1)
{
write(fd, "hello world", strlen("hello world"));
sleep(1);
memset(buf, 0x0, sizeof(buf));
n = read(fd, buf, sizeof(buf));
printf("n=[%d], buf=[%s]\n", n, buf);
sleep(1);
}
close(fd);
return 0;
}
int main()
{
int ret = access("./myfifo", F_OK);//access判断文件是否存在,这样两个进程无所谓谁先运行
if(ret!=0)
mkfifo("./myfifo", 0777);
int fd = open("./myfifo", O_RDWR);
char buf[64];
int n;
while(1)
{
memset(buf, 0x0, sizeof(buf));
n = read(fd, buf, sizeof(buf));
printf("n=[%d], buf=[%s]\n", n, buf);
sleep(1);
write(fd, "ni hao", strlen("ni hao"));
sleep(1);
}
close(fd);
return 0;
}
内存映射就是将一个磁盘文件映射到内存中,操作内存数据就相当于操作文件了,这样可以不使用read/write,而是用指针来完成I/O操作。
内存映射,首先应通知内核,将一个指定的文件映射到内存中,这个过程可以用mmap实现。
void *mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
param:
addr: 指定内存的起始地址,通常为NULL,由系统指定
length: 映射到内存的文件长度(用lseek或stat函数获取)
prot: 映射区的保护方式
PROT_READ——读
PROT_WRITE——写
PROT_READ | PROT_WRITE——读写
flags: 映射区的特性
MAP_SHARED: 写入映射区的数据会写回文件,且允许其它映射该文件的进程共享
MAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制,对此区域所做的修改不会写回到源文件
fd: 由open返回的描述符,代表要映射的文件
offset: 以文件开始处的偏移量,必须是4K的整数倍,通常为0,表示从文件头开始映射
return:
成功:返回映射区的首地址
失败:MAP_FAILED宏
mmap实现进程间通信
/*write*/
int main()
{
int fd = open("./test.log", O_RDWR);
int len = lseek(fd, 0, SEEK_END);
void* addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED)
return -1;
memcpy(addr, "0123456789", sizeof("0123456789"));
return 0;
}
/*read*/
int main()
{
int fd = open("./test.log", O_RDWR);
int len = lseek(fd, 0, SEEK_END);
void* addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED)
return -1;
char buf[64];
memset(buf, 0x0, sizeof(buf));
memcpy(buf, addr, 10);
printf("buf=[%s]\n", buf);
return 0;
}
//释放由mmap函数建立的内存映射
int munmap(void* addr, size_t length);
param:
addr: 调用mmap后返回的内存区首地址
length: 映射区大小
return:
成功:返回0
失败:返回-1,并设置errno
相关资源:mmap详解
强调:
1、创建映射区的过程,隐含着一次对映射文件的读操作;
2、映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭;
3、当映射文件大小为0时,不创建映射区,所以文件必须有实际大小;
4、坚决杜绝对mmap返回的指针进行++/–操作;
5、文件偏移量必须为或者4K的整数倍;
6、mmap出错概率非常,一定要检查返回值;
匿名映射
//只能用于有血缘关系的进程
mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);