深度剖析管道与共享内存

管道

  • 管道是进程间通信的一个重要方式,事实上进程间通信的本质就是让两个或多个进程看到同一份资源。一个进程写,另一个进程读取,这样就实现了进程间通信。
  • 管道本质上就是一个文件,一个进行在磁盘中创建了一个文件,另一个进程同步打开该文件,一个进程写,一个进程读,也就实现了进程间通信。也就是说管道就是通过文件系统来实现通信的。
  • 由于文件的特性,不可以两个进程同时写入,也就注定了管道只能实现单向通信,写段只负责写入,读断只负责读取,要实现两个进程同时具有读写功能就只有创建两个管道。

匿名管道

在这里插入图片描述
使用pipe函数来创建一个匿名管道,因为管道实际上就是一个文件,匿名指的就是该文件没有名字。
参数:
该函数只有一个参数,是一个整型数组,数组有两个元素,创建成功后两个元素为两个文件描述符,均指向刚创建好的管道。pipefd[0]为以读方式打开,pipefd[1]为以写方式打开 。
在这里插入图片描述
创建成功返回0,失败返回-1。

#include <stdio.h>    
#include <unistd.h>    
#include <string.h>    

int main() {
	int fd[2] = { 0 };
	if (pipe(fd) == -1) {
		//创建管道失败                                                                                                                          
		perror("Creat pipe faild!\n");
	}

	pid_t pid = fork();

	if (pid < 0) {
		//创建子进程失败    
		perror("fork faild!\n");
	}
	if (pid == 0) {
		//sleep(5);    
		//创建子进程成功,子进程同步打开管道,拥有fd数组    
		close(fd[0]);//关闭读端,子进程负责写入    
		char str[] = "hello world!";
		while (1) {
			write(fd[1], str, strlen(str));
			printf("i am write\n");
			sleep(1);
		}
	}
	else {
		close(fd[1]);//关闭写端,父进程负责读取
		char buf[128];
		ssize_t num = read(fd[0], buf, 127);
		buf[num] = '\0';//字符串最后一位放'\0'
		int count = 0;
		while (count < 5) {
			if (num > 0) {
				printf("haha,i got you:%s\n", buf);
			}
			count++;
			sleep(1);
		}
		close(fd[0]);
	}
	return 0;
}  

运行结果:
在这里插入图片描述

父进程中创建管道,然后创建子进程,子进程同步父进程的代码与资源,同时也打开了管道,子进程写入字符串,父进程读取字符串,实现进程间通信。
注意:两个进程使用管道通信时,如果写端不关闭fd,也不进行写入,那读端会进入阻塞状态,等待另一端写入数据。
如果管道已满,而程序未做任何处理时,写端在进行写入时会进入阻塞状态。
如果写端关闭fd,读端会读取到文件结尾"0"。
如果读端关闭fd,写端在下一次写入时会直接关掉进程。

命名管道

  • 匿名管道可以实现进程间通信,不过缺点也显而易见,匿名管道创建的文件没有名字,所以只能在具有亲缘关系的进程中通信,其他进程不可以。
  • 为了实现两个独立进程间进行通信,所以产生了命名管道,命名管道就是在创建管道时戴上名字,使其他进程也可以通过名字来打开管道,进而实现独立进程间的通信。
    在这里插入图片描述
    创建命名管道使用mkfifo函数。
    第一个参数:要创建管道的名字
    第二个参数:创建管道(文件)的权限。
    在这里插入图片描述

创建成功返回0,创建失败返回-1。
这里展示一个例子,client进程创建命名管道并作为写端往管道内写数据,server进程打开管道并从管道内读取数据。
client代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main(){

  if(mkfifo("mypipe",0664) == -1){
    //创建管道失败
    perror("mkfifo faild!\n");
  }
  int fd = open("mypipe",O_WRONLY);//打开管道
  if(fd < 0){
    perror("open faild!\n");
  }
  while(1){
    char buf[128];
    printf("[put in]# ");
    fflush(stdout);
    int num = read(0, buf, sizeof(buf) - 1);//往buf内写数据
    if(num > 0){
      buf[num] = 0;
      write(fd, buf, num);//往管道内写数据                                                                                                                     
    }                                                            
  }                                                              
  return 0;                                                      
}   

server代码:

#include <stdio.h>                                         
#include <sys/types.h>                                     
#include <sys/stat.h>                                      
#include <unistd.h>                                        
#include <fcntl.h>                                         
#include <stdlib.h>                                        
                                                           
                                                           
int main(){                                                
                                                           
  int fd = open("mypipe",O_RDONLY);                        
  if(fd < 0){                                              
    perror("open faild!\n");                               
  }                                                        
  while(1){                                                
    char buf[128];                                         
    ssize_t num = read(fd, buf, 127);//从文件中读数据到buf中
    if(num > 0){//从文件中读到了数据     
      buf[num] = 0;                      
      printf("[get message]# %s",buf);   
    }                                    
    else if(num == 0){//写端已经关闭,读端不必再等待,退出          
      printf("i got close!\n");                           
      exit(0);                                            
    }                                                     
  }                                                       
  return 0;                                               
}       

在这里插入图片描述

先打开client进程,发现当前目录多了mypipe文件,文件类型为管道类型。
在这里插入图片描述

client进程写入字符串,server端可以读取字符串。当client端退出时,server端可以检测到,然后同步退出。

共享内存

  • 前面提到过,进程间通信就是让两个进程看到同一份资源,进而实现通信,管道通过文件系统来实现这一目的,那可不可以绕过文件系统,让两个进程直接看到内存中的一块空间,然后通信呢?共享内存就是基于这种原理实现的。
  • 共享内存首先通过ftok()函数获取一个key值,key值具有唯一性,用来标识共享内存,就像身份证号用来标识每个人一样。
  • 然后用使用key值,通过shmget()函数在内存中开辟空间,至此,共享内存已经开辟好,接下来的工作就是让两个进程可以同时访问这块内存了。
  • 使用shmat()函数,让进程与这块内存产生关联,即可以访问这块内存。
  • 使用shmdt()函数,解除进程与这块内存的关联。
  • 使用shmctl()函数,释放共享内存
    共享内存必须手动申请与释放,由于该内存是直接通过系统调用在内存中开辟空间,而非通过进程申请,即使进程退出该内存也不会自动释放,所以必须手动释放!
    在这里插入图片描述

ftok()函数,创建key值
第一个参数:代表一个路径,函数会将路径视为一个字符串,与第二个参数一起生成一个唯一的key值,因此路径具体是哪条路径无所谓。但必须为正常路径,非正常路径无法生成key值。
第二个参数:一个int型随机值,自己设置即可
在这里插入图片描述
返回值:成功返回key值,失败返回-1。
在这里插入图片描述

在这里插入图片描述
shmget()函数,开辟共享内存。
第一个参数:ftok()函数生成的key值。
第二个参数:要开辟的共享内存的大小,单位是字节,尽量设置大小为4096的整数倍,因为系统在开辟共享内存时是以4096字节(页)为基本单位的。
第三个参数:
IPC_CREAT:如果内存不存在,创建共享内存
IPC_EXCL:如果该内存已经存在,则出错返回。以此来保证创建的共享内存都是全新的(不会打开之前创建好的共享内存)
在这里插入图片描述
返回值:创建成功返回一个标识符,标识该共享内存,类似于文件描述符,这个区别于key值,key是对于系统而言的一个唯一的标识符,而这个返回的是对于用户而言的。用户通过该标识符对共享内存进行操作。
在这里插入图片描述
0666为创建内存的权限

在这里插入图片描述

shmat()函数,让进程与共享内存产生关联。
第一个参数:由shmget()函数返回的标识符,用于确定关联哪一块内存。
第二个参数:该参数是一个指针,指共享内存具体关联到进程的哪一块虚拟地址,一般置空就可以。
第三个参数:表示关联此内存的方式,如可读或可写,一般置0,可读可写。
在这里插入图片描述
返回值:成功返回共享内存的起始地址,失败返回-1(0xffffffff)。
在这里插入图片描述

在这里插入图片描述

shmdt()函数,取消关联。
第一个参数:要取消关联的共享内存。
在这里插入图片描述

返回值:成功返回0,失败返回-1。
在这里插入图片描述

在这里插入图片描述
shmctl()函数,管理共享内存。
第一个参数:共享内存描述符。
第二个参数:命令,要如何管理,下面列举三个。
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:释放共享内存
一般使用第三个,用来释放共享内存。
第三个参数:一个结构体,os在管理共享内存时有对应的结构体来进行管理,如果我们要修改共享内存的属性,就需要创建一个类似的结构体传给os,os就会根据你传入的命令以及结构体的值对共享内存进行管理。如果第二个参数为IPC_RMID,则第三个参数可置空。
在这里插入图片描述
这就是os管理共享内存的结构体。
在这里插入图片描述
返回值,释放内存的话成功返回0,失败返回-1。
在这里插入图片描述

下面看一个服务端与客户端通信的例子
client:

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/ipc.h>    
#include <sys/shm.h>    
#include <unistd.h>    
    
int main(){    
  int key = ftok("../shm",2021);//通过ftok函数生成key值    
  int id = shmget(key, 4096, IPC_CREAT);//通过key值在内存中开辟空间                                                                         
  if(id == -1){    
    perror("shmget");    
  }    
  char* str = (char*)shmat(id, NULL, 0);    
  int i = 0;    
  for(; i < 10; i++){    
    str[i] = i + 'a';    
    sleep(1);    
  }    
  shmdt(str);    
  return 0;    
}  

server:

#include <stdio.h>                  
#include <sys/types.h>                              
#include <sys/ipc.h>                                
#include <sys/shm.h>                                
#include <unistd.h>                                 
                                                    
int main(){                                         
  int key = ftok("../shm",2021);//通过ftok函数生成key值    
  int id = shmget(key, 4096, IPC_CREAT|IPC_EXCL|0666);//通过key值在内存中开辟空间    
  if(id == -1){                                     
    perror("shmget");                               
    return 1;                                       
  }                                                 
  char* str = (char*)shmat(id, NULL, 0);//关联内存    
                                                    
  int count = 0;                                    
  while(count < 20){                                
    printf("%s\n",str);                             
    sleep(1);                                       
    count++;                                        
  }                                                 
  shmdt(str);//取消关联                             
  shmctl(id, IPC_RMID, NULL);//释放内存                                                                                                     
  return 0;                                                      
}  

在这里插入图片描述

共享内存命令

ipcs -m

查看当前系统的共享内存

ipcrm -m [shmid]

删除对应共享内存
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值