linux高级编程笔记(四)——进程间通信(IPC)

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(InterProcess Communication,IPC)。

在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:

(1)管道(使用最简单)

(2)信号(开销最小,后续再进行详细介绍)

(3)共享映射区(无血缘关系)

(4)本地套接字(最稳定,后续再进行详细介绍)


一、管道

1 pipe匿名管道

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

(1)其本质是一个伪文件(实为内核缓冲区)

(2)由两个文件描述符引用,一个表示读端,一个表示写端。

(3)规定数据从管道的写端流入管道,从读端流出。

管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道的局限性:

(1)数据自己读不能自己写。

(2)数据一旦被读走,便不在管道中存在,不可反复读取。

(3)由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

(4)只能在有公共祖先的进程间使用管道。

管道的通信流程如下所示:

1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2.父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3.父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

#include <unistd.h>
int pipe(int filedes[2]);

描述:

管道作用于有血缘关系的进程之间,通过fork来传递调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。

代码示例:

#include <stdlib.h>//perror
#include <unistd.h>//pipe
#include <stdio.h>
#include <sys/wait.h>//wait

#define MAXLINE 80

int main(void)
{
	int n;
	int fd[2];
	pid_t pid;
	char line[MAXLINE];
	if (pipe(fd) < 0) {
		perror("pipe");
		exit(1);
	}
	if ((pid = fork()) < 0) {
		perror("fork");
		exit(1);
	}
	if (pid > 0) { /* parent */
		close(fd[0]);
		write(fd[1], "hello world\n", 12);
		wait(NULL);
	} else { /* child */
		close(fd[1]);
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}
	return 0;
}

2 fifo有名管道

fifo常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过fifo,不相关的进程也能交换数据。

创建方式:

(1)命令方式:mkfifo xwp

(2)函数方式:mkfifo函数

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

mode与open函数的mode功能一样,创建成功返回0,失败返回-1。

代码示例:

#include <stdlib.h>//perror
#include <stdio.h>
#include <sys/stat.h>//mkfifo

#define MAXLINE 80

int main(void)
{
	int ret = mkfifo("myfifo",0664);
	if(ret == -1)
	{
		perror("mkfifo");
	}	

	return 0;
}

执行后可以看到多了一个管道文件:

创建完管道文件后,与读写文件类似的,在一个进程中写管道,另一个进程中读管道,则可以实现进程间的通信,这里不再赘述。

需要注意的几个点如下:

  • 当只写打开FIFO管道时,如果没有FIFO没有读端打开,则open写打开会阻塞。
  • FIFO内核实现时可以支持双向通信。(pipe单向通信,因为父子进程共享同一个file结构体)
  • FIFO可以一个读端,多个写端;也可以一个写端,多个读端。(请测试)

二、内存共享映射:

1 mmap/munmap

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //创建贡献内存映射
int munmap(void *addr, size_t length);

参数:

addr :指定映射区的首地址。通常传NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。

length:需要映射的那一部分文件的长度。(不大于文件的实际大小)

prot:有四种取值,表示读写权限

  •  PROT_EXEC表示映射的这一段可执行,例如映射共享库
  •  PROT_READ表示映射的这一段可读
  • PROT_WRITE表示映射的这一段可写
  • PROT_NONE表示映射的这一段不可访问

flags:参数有很多种取值,这里只讲两种

  • MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
  • MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

fd:用于创建共享内存映射区的那个文件的文件描述符。

offset:偏移位置,默认为0表示映射文件的所有。必须是4k的整数倍。

返回值:

如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。

#include <stdlib.h>//perror
#include <sys/mman.h>//mmap
#include <string.h>//strcpy
#include <stdio.h>
#include <unistd.h>//lseek/ftruncate

int main(void)
{
	char *p;
	int fd = open("hello", O_RDWR|O_CREAT|O_TRUNC,0664);
	if (fd < 0) {
		perror("open hello");
		exit(1);
	}
	
	ftruncate(fd,20);//创建大小为20字节的文件,需要写权限
	int len = lseek(fd,0,SEEK_END);//获取文件长度
	p = mmap(NULL, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}

    close(fd);
	strcpy(p,"hello mmap");//写操作,将同步修改磁盘文件内容

	return 0;
}

2 mmap注意事项

  • 用于创建映射区的文件大小为0,实际制定非0大小创建映射区,出总线错误。
  • 用于创建映射区的文件大小为0,实际制定0大小创建映射区,出无效参数错误。
  • 用于创建映射区的文件读写属性为只读,映射区属性为读写,出权限错误。
  • 文件描述符fd在mmap创建映射区后即可关闭。后续访问文件用地址访问。
  • 创建映射区,需要读权限。“共享”MAP_PRIVATE时,mmap的读写权限(第三个参数prot指定)应该<=文件open时指定的权限。只写不行。
  • offset必须为4k的整数倍,否则报无效参数错误。
  • 对申请的内存,不能越界访问。否则报段错误。
  • mmap用于释放的地址,必须是mmap返回的地址。若需要通过修改指针(p++)访问内存,则可以用临时变量存储初始位置再执行修改指针操作(tmp=p;p++),方便munmap释放映射mumap(tmp,len)。
  • 映射区访问权限为“私有”MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。
  • 映射区访问权限为“私有”MAP_PRIVATE,open文件时,只需要有读权限用于穿件映射区即可。

mmap函数的保险调用方式:

  1. open(O_RDWR)
  2. mmap(NULL,实际文件大小,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

3 使用mmap进程间通信

3.1 父子间通信

(1)有名映射

#include <stdlib.h>//perror
#include <sys/mman.h>//mmap
#include <sys/wait.h>//wait
#include <string.h>//strcpy
#include <stdio.h>
#include <unistd.h>//lseek/ftruncate
#include <fcntl.h>//flags

int main(void)
{

	int var = 100;
	int *p;
	int fd = open("hello", O_RDWR|O_CREAT|O_TRUNC,0664);
	if (fd < 0) {
		perror("open hello");
		exit(1);
	}
	
	ftruncate(fd,20);//创建大小为20字节的文件,需要写权限
	int len = lseek(fd,0,SEEK_END);//获取文件长度
	p = (int *)mmap(NULL, len, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	
 	close(fd);

	pid_t pid = fork();

	if(pid == 0)//子进程
	{
		*p = 2000; //向共享映射区写入2000
		var = 1000;
		printf("child, *p=%d, var=%d\n",*p,var);
	}
	else
	{
		sleep(1);
		printf("parent,*p=%d,var=%d\n",*p,var);//读共享映射区

		int ret = munmap(p,20);//释放映射区

		if(ret == -1)	
		{
			perror("munmap error");
			exit(1);
		}
	}


	return 0;
}

(2)匿名映射

#include <stdlib.h>//perror
#include <sys/mman.h>//mmap
#include <sys/wait.h>//wait
#include <string.h>//strcpy
#include <stdio.h>
#include <unistd.h>//lseek/ftruncate
#include <fcntl.h>//flags

int main(void)
{

	int var = 100;
	int *p;
	

	p = (int *)mmap(NULL, 40, PROT_WRITE|PROT_READ, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
	if (p == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	

	pid_t pid = fork();

	if(pid == 0)//子进程
	{
		*p = 2000; //向共享映射区写入2000
		var = 1000;
		printf("child, *p=%d, var=%d\n",*p,var);
	}
	else
	{
		sleep(1);
		printf("parent,*p=%d,var=%d\n",*p,var);//读共享映射区

		int ret = munmap(p,20);//释放映射区

		if(ret == -1)	
		{
			perror("munmap error");
			exit(1);
		}
	}


	return 0;
}

3.2 无血缘关系进程通信

写端


#include <stdio.h>                                                                      
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <string.h>                                                                     
#include <sys/mman.h>                                                                   
                                                                                        
                                                          
struct student {                                                                            
    int id;                                                                             
    char name[20];                                                                      
    int age;                                                                           
};                                                                                      
                                                                                        
void sys_err(char* s) {                                                                 
    perror(s);                                                                          
    exit(1);                                                                            
}                                                                                       
                                                                                        
int main() {                                                                            
    int fd;                                                                             
    struct student stu = {10, "xiaoming", 18};                                             
    struct student* mm;                                                                     
                                                                                        
    if ((fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644)) < 0)                   
        sys_err("open tmpfile");                                                        
   
    ftruncate(fd, sizeof(stu));                                                                      
                                                                                     
    mm = mmap(NULL, sizeof(stu), PROT_WRITE, MAP_SHARED, fd, 0);                        
    if (mm == MAP_FAILED)                                                               
        sys_err("mmap failed");                                                         
    close(fd);                                                                          
                                                                                        
    while (stu.id++ < 100) {
        printf("id = %d, name = %s, age= %d \n", mm->id, mm->name, mm->sex);                                                            
        memcpy(mm, &stu, sizeof(stu));                                                  
        sleep(1);                                                                       
    }                                                                                   
                                                                                        
    unlink("tmpfile");                                                                                                                                                                                                                               
    munmap(mm, sizeof(stu));                                                            
                                                                                        
    return 0;                                                                        
}           

读端

#include <stdio.h>                                                                      
#include <stdlib.h>                                                                     
#include <unistd.h>                                                                     
#include <fcntl.h>                                                                      
#include <sys/mman.h>                                                                   
                                                                                                                                                
struct student {                                                                            
    int id;                                                                             
    char name[20];                                                                      
    int sex;                                                                           
};                                                                                      
                                                                                        
void sys_err(char* s) {                                                                 
    perror(s);                                                                          
    exit(1);                                                                            
}                                                                                       
                                                                                        
int main() {                                                                            
    int fd;                                                                                                                                               
    struct student* mm;                                                                     
                                                                                        
    if ((fd = open("tmpfile", O_RDONLY)) < 0)                                           
        sys_err("open tmpfile");                                                        
                         
    ftruncate(fd, sizeof(struct student));                                                         
                                                                                        
    mm = mmap(NULL, sizeof(struct student), PROT_READ, MAP_SHARED, fd, 0);                                                                                                                                                                                      
    if (mm == MAP_FAILED)                                                               
        sys_err("mmap failed");                                                         
    close(fd);                                                                          
                                                                                        
    while (1) {                                                              
        printf("id = %d, name = %s, age= %d \n", mm->id, mm->name, mm->sex);           
        sleep(1);                                                                       
    }                                                                                   
                                                                                        
    munmap(mm, sizeof(struct student));                                                            
                                                                                        
    return 0;                                                                           
}           

连个进程打开同一个文件,创建映射区。

是定flags为MAP_SHARED.

一个进程写入,另一个读出。

【注意】

mmap:数据可以重复读取。

fifo:数据只能一次读取

另外,可以使用/dev/zero文件进行通信,而不需要创建。任何程序可以从其中取出无线大小的空洞内容。而/dev/null相反,可以向其中写入无限大小的内容。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值