linux——进程间通信

17 篇文章 0 订阅

进程间通信

基本概念

什么是进程间通信?
linux环境下,进程地址空间相互独立,每个进程都有各自的用户地址空间。进程之间不能相互访问,要交换数据必须通过内核,在内核开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
进程间通信IPC
进程间通信的方式有:
文件、管道命名管道共享内存信号、消息队列、套接字


管道——pipe、匿名管道

用于有血缘关系的进程之间,调用管道pipe函数即可创建一个管道

特性:

  1. 管道的本质是一块内核缓冲区,内部使用环形队列实现的;
  2. 缓冲区默认大小是4K(512X8),可以使用 ulimit -a 来查看;
  3. 由两个文件描述符引用,一个表示写端,一个表示读端;
  4. 规定数据只能从管道的写端流入,从读端流出
  5. 当两个进程都终结的时候,管道也自动消失;
  6. 管道的读端和写端默认是阻塞的

pipe管道

管道局限性

  1. 数据一旦被读走,便不在管道中存在,不可反复读取
  2. 数据只能在一个方向上流动,若要实现双向流动,必须使用两个管道;
  3. 只能在有血缘关系的进程中使用(因为是通过文件描述符来使用的,父子进程可以共享文件描述符)

创建管道

int pipe(int fd[2]);
param:
	若函数调用成功,fd[0]存放读端,fd[1]存放写端
return:
	成功:0
	失败:-1,并设置errno

1、父进程创建管道
2、父进程fork出子进程
3、父进程关闭fd[0],子进程关闭fd[1]

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
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<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
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

管道的读写行为

  • 读操作
    • 有数据
      read正常读,返回读出的字节数
    • 无数据
      • 写端全部关闭
        read解除阻塞,返回0,相当于读到文件的结尾
      • 写端没有全部关闭
        read阻塞
  • 写操作
    • 读端全部关闭
      管道破裂,进程终止,内核给当前进程发SIGPIPE信号
    • 读端没有全部关闭
      • 缓冲区写满了
        write阻塞
      • 缓冲区没有满
        继续write

设置管道为非阻塞

1、int flag = fcntl(fd[0], F_SETFL, 0);
2、flag |= O_NONBLOCK;
3、fcntl(fd[0], F_SETFL, flag);


FIFO——命名管道

FIFO是linux文件类型的一种(P),但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来表示内核中的一条通道,进程可以打开这个文件进行读写,实际上是在读写内核缓冲区,这样就实现了进程间通信。FIFO可用于不相关的进程

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;
}

内存映射mmap

内存映射就是将一个磁盘文件映射到内存中,操作内存数据就相当于操作文件了,这样可以不使用read/write,而是用指针来完成I/O操作。

内存映射,首先应通知内核,将一个指定的文件映射到内存中,这个过程可以用mmap实现。

  • 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实现进程间通信

  1. 和进程间变量不同的是,进程间的全局变量不能被共享,而mmap返回的指针可以被两个进程共享;
  2. 另外,如果文件大小为10个字符,但使用共享内存通信的时候,即使你通信了20个字符但最后文件大小仍为你通信的20个字符的前10个字符;
  3. 使用mmap方式获取磁盘上的文件信息,只需要将磁盘上的数据拷贝至那块共享内存中去,用户进程可以直接获取到信息,而相对于传统的write/read IO系统调用,必须先把数据从磁盘拷贝至到内核缓冲区中,然后再把数据从内核缓冲区拷贝至用户缓冲区中。两者相比,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);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值