linux(2)- 共享内存的实现

问题

(1)X、Y两个进程相互配合实现对输入文件中数据的处理,并将处理结果写入输出文件。
(2)X进程负责分块读取输入文件,并将输入数据利用共享内存传输给Y进程。
(3)Y进程负责将读入的数据(假定皆为文本数据)全部处理成大写,然后写入输出文件。
(4)为提高并行效率,X、Y两个进程之间创建2个共享内存区A、B。X读入数据到A区,然后用LInux信号或信号量机制通知Y进程进行处理;在Y进程处理A区数据时,X继续读入数据到B区;B区数据被填满之后,X进程通知Y进程处理,自己再继续向A区读入数据。如此循环直至全部数据处理完毕。

环境

      Ubuntu20.04 64位虚拟机

问题分析及思路

      上述问题中的核心在于以下几点:1,c语言读写文件;2,构建两个进程之间的共享内存;3,linux信号机制;4,实现两个进程互斥
      接下来一步步实现即可。


      按照刚才的思路,我们可以从简单的开始着手。
      首先,比如c语言读写文件,这个比较简单,无非就是打开文件,对文件操作,关闭文件这一套。然后,关于建立两个进程,可以采用 fork()来实现或者编写两个独立的程序文件,在这里我们采用 fork()来实现。
      这样,一个大体的框架就有了,我们采用Y进程作为父进程,用X进程作为子进程(X进程作为读进程,它的工作一定比写进程结束的早,所以将X进程作为子进程,一旦其工作结束,父进程采用执行完毕之后采用wait()将其回收,在逻辑也符合结束的先后顺序)。
      然后我们休息一下,洗把脸再继续。


      那如何在两个进程之间创建共享内存呢?相关的函数为 shmget、shmat、shmdt和shmctl(shm 的意思是 shared-memory )。包含的头文件为 <sys/ipc.h> 和<sys/shm.h>。
      shmget函数的原型为 int shmget(key_t key, size_t size, int shmflg) ,它用来创建一个共享内存对象并返回一个共享内存标识符。下面仅当创建一个新的共享内存时进行相关参数的说明:参数 key 为0(IPC_PRIVATE),表示创建一个新的共享内存对象; size 表示新建共享内存的大小,单位为字节;shmflg 是标识符,简单的可以认为对共享内存的读写权限,新建对象时设置其为 IPC_CREAT | 读写权限。创建成功时返回标识符ID,失败返回-1。

(补充:linux中的权限说明。Linux 系统中常采用三位十进制数表示权限,并且会在前面加上0表示采用十进制形式。
如0755, 0644,对应的形式为ABCD,其中A- 0, 表示十进制,B-用户,C-组用户,D-其他用户。
不同的权限分别用数字0-7来表示:
0 (no excute, no write, no read)
1 excute (no write, no read)
2 write
3 write, excute
4 read
5 read, excute
6 read, write
7 read, write, excute
0755->即用户具有读/写/执行权限,组用户和其它用户具有读执行权限;
0644->即用户具有读写权限,组用户和其它用户具有只读权限;
0600->仅拥有者具有文件的读取和写入权限

      shmat函数的原型为 void *shmat(int shmid, const void *shmaddr, int shmflg),功能为:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。参数shmid为shmget函数的返回值,参数shmaddr指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置,参数shmflg表示进程对连接的共享内存的读写权限,可以采用三位十进制数来表示,函数执行成功:返回附加好的共享内存地址,出错返回-1。

      shmdt函数的原型为 int shmdt(const void *shmaddr),功能为:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存。参数shmaddr:连接的共享内存的起始地址,即shmat函数的返回值。函数执行成功:返回0,出错返回-1。注意:这个函数仅仅是将本进程与共享内存的连接断开,并非删除共享内存。

      shmctl函数的原型为 int shmctl(int shmid, int cmd, struct shmid_ds *buf),功能为:进行对共享内存的控制。参数shmid是共享内存标识符(shmget函数的返回值),参数cmd为IPC_RMID时表示删除这片共享内存,参数buf是共享内存管理结构体,函数执行成功:返回0,出错返回-1。

      共享内存的各个函数示例如下:它创建一个大小为64B的新的共享内存对象并返回该共享内存的标识符,保存在变量 memoryID 中。

	
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define SIZE 64

memoryID=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT);
//创建一个大小为64B的新的共享内存对象并返回该共享内存的标识符,保存在变量 memoryID 中。仅对当前用户具有读写权限。

char* memoryAddr=(char*)shmat(memoryID,NULL,0200);
//将标识符为memoryID的共享内存映射到本进程的地址空间中,本进程对其仅具有写权限。返回共享内存的起始地址memoryAddr。

shmdt((void*)memoryAddr);
//断开本进程与起始地址为memoryAddr的共享内存的连接。

shmctl(memoryID,IPC_RMID,NULL);
//删除标识符为memoryID的共享内存。


      linux的信号机制 (更多的可以参考博客https://blog.csdn.net/qq_37653144/article/details/81942026)

      在Linux中,可以通过signal和sigaction函数注册信号并指定接收到该信号时需要完成的动作,对于已经有自己的功能动作的信号而言其注册就是用一个用户自己定义的动作去替换Linux内核预定义的动作。

      signal函数可以为一个特定的信号(除了无法捕获的SIGKILL和SIGSTOP信号)注册相应的处理函数。其包含在头文件#include <signal.h>中,原型为 void (*signal(int signum, void (*handler)(int)))(int);参数signum表示所注册函数针对的信号名称,参数handler通常是指向调用函数的函数指针,即所谓的信号处理函数。通俗的来讲:进程A采用signal函数为信号a(参数signum)注册了一个处理函数haha(参数handler),那么在进程A收到信号a的时候就会执行haha处理函数。

      一般我们常采用sigaction函数,它在完成信号注册工作的同时提供了更多的功能选择。函数原型为 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)。参数signum指定要处理的信号,参数act和oldact都是指向信号动作结构的指针(即信号处理函数)。函数执行成功:返回0,出错返回-1。
      结构体struct sigaction {
      void (*sa_handler)(int);
      void (*sa_sigaction)(int, siginfo_t *, void *);
      sigset_t sa_mask;
      int sa_flags;
      void (*sa_restorer)(void);
      };
      信号处理函数可以采用void (*sa_handler)(int)或void (*sa_sigaction)(int, siginfo_t *, void *)。到底采用哪个要看sa_flags中是否设置了SA_SIGINFO位,如果设置了就采用void (*sa_sigaction)(int, siginfo_t *, void *),此时可以向处理函数发送附加信息;默认情况下采用void (*sa_handler)(int),此时只能向处理函数发送信号的数值。当设置sa_flags为0时,sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数,其他参数暂时可忽略。

      kill函数,它给指定进程发送指定信号,原型为 int kill(pid_t pid, int sig);参数pid为接受参数sig信号的进程。函数执行成功:返回0,出错返回-1。

      关于信号机制的示例如下:

#include<signal.h>
#include<sys/types.h>

void sig_usr(int signum);//信号处理函数 handler

struct sigaction sa_usr;
sa_usr.sa_flags=0;
sa_usr.sa_handler=sig_usr;//信号处理函数sig_usr与sigaction结构体关联起来
if(sigaction(SIGUSR1,&sa_usr,NULL))//为信号SIGUSR1注册信号处理函数sig_usr。
	printf("Error in sigaction().SIGUSR1\n");
//至此,一旦本进程收到信号SIGUSR1,就会执行处理函数sig_usr

kill(pid,SIGUSR1);//对进程id为pid的进程发送信号SIGUSR1


      进程互斥机制的实现

      进程互斥可以采用信号量来实现,这是一种优秀的方式,但是实现起来稍有些复杂,所以我们采用简单的循环判定的方法来实现互斥。为什么要实现互斥?原因在于对同一块内存任意一个时刻只能有一个进程去访问它,即写满了才能读,读完了才能写。
      在此采用几个标记来表示每个进程对每块内存的可用状态,分别为变量X1,X2,Y1 和 Y2。其中X1和X2表示X进程对两块内存的可用状态(是否可读),Y1和Y2表示Y进程对两块内存的可用状态(是否可写)。初始时将X1和X2均置为1,表示X进程可读,一旦内存1被X进程读入数据,设置X1为0,当读入完毕时,发送信号给Y进程,相应的处理函数将Y1置为1,从而Y进程从该内存中转换数据并写入到输出文件。重复上述流程,直至所有数据转换传输结束。

程序文件说明和执行

      两个c文件,分别为init.c 文件和sharedm-v2.c,将二者放在同一目录下面,先编译并执行 init.c 文件,会得到一个 helloIn.txt 输入文件。后编译并执行 sharedm-v2.c 文件,生成 helloOut.txt 目标文件。
      输入文件大小通过init.c文件可以修改,当前大小为26*100字节,两块共享内存均为64字节。

init.c

#include<stdio.h>

int main()
{
    
    
	FILE* fp=fopen("helloIn.txt","w");
	char temp;
	for(int i=0;i<100;i++)
	{
    
    
		temp='a';
		for(int j=0;j<26;j++)
		{
    
    
			fputc(temp,fp);
			temp++;
		}
	}
	fclose(fp);
	return 0;
}

sharedm-v2.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
#include<signal.h>
#include<wait.h>
#define SIZE 64
#define C 10000 //延时功能

volatile int X1=1;
volatile int X2=1;
//初始时两块内存1和2对X内存均可用,均设为1。
volatile int Y1=0;
volatile int Y2=0;
//初始时两块内存1和2对X内存均不可用,均设为0。

void sig_usrX(int signum);//信号处理函数
void sig_usrY(int signum);//信号处理函数

void X_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* X,int pid,int sig);//X进程写内存操作
void Y_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* Y,int ppid,int sig);//Y进程读内存操作

int main()
{
    
    
	pid_t pid;
	int memoryID1,memoryID2;//共享内存标识符
	char* memoryAddr=NULL;
	
	memoryID1=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT);//读写权限
	memoryID2=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT);//读写权限
	if(memoryID1<0||memoryID2<0)
			printf("Get memory with error.");
			
	if((pid=fork())<0)//出错返回
		printf("Fork error!\n");
	else if(pid>0)//父进程作为Y进程
	{
    
    
		printf("parentID:%d\n",getpid());
		
		FILE* fp=fopen("helloOut.txt","w");
		if(!fp)
		{
    
    
			printf("Fail at open the file\n");
			return 0;
		}
		
 		struct sigaction sa_usr;
		sa_usr.sa_flags=0;
		sa_usr.sa_handler=sig_usrY;//信号处理函数
		if(sigaction(SIGUSR1,&sa_usr,NULL))
			printf("Error in sigaction().SIGUSR1\n");
		if(sigaction(SIGUSR2,&sa_usr,NULL))
			printf("Error in sigaction().SIGUSR2\n");

		volatile int flag=1;//退出时处理标志
		while(flag)
		{
    
    
			if(Y1==1)
				Y_Process(fp,&flag,memoryID1,&Y1,pid,SIGUSR1);
			if(Y2==1)
				Y_Process(fp,&flag,memoryID2,&Y2,pid,SIGUSR2);
		}

	
		fclose(fp);	
		
			
		if(shmctl(memoryID1,IPC_RMID,NULL)==0)
			printf("Release the sharememory1.\n");//由Y进程来释放掉共享内存,因为它结束的比较晚
		if(shmctl(memoryID2,IPC_RMID,NULL)==0)
			printf("Release the sharememory2.\n");
			
		printf("Y process will quit.\n");
		
	}
	else//子进程作为X进程
	{
    
    
		int ppid=getppid();
		printf("childID:%d\n",getpid());
		
		FILE* fp=fopen("helloIn.txt","r");
		if(!fp)
		{
    
    
			printf("Fail ot open the file\n");
			return 0;
		}
		
		
		struct sigaction sa_usr;
		sa_usr.sa_flags=0;
		sa_usr.sa_handler=sig_usrX;//信号处理函数
		if(sigaction(SIGUSR1,&sa_usr,NULL))
			printf("Error in sigaction().SIGUSR1\n");
		if(sigaction(SIGUSR2,&sa_usr,NULL))
			printf("Error in sigaction().SIGUSR2\n");
			
		volatile int flag=1;//退出时处理标志
		while(flag)
		{
    
    
			if(X1==1)
				X_Process(fp,&flag,memoryID1,&X1,ppid,SIGUSR1);
			else if(X2==1)
				X_Process(fp,&flag,memoryID2,&X2,ppid,SIGUSR2);
		}

		fclose(fp); 
		
		
		
		printf("X process will quit.\n");
		
	}
	return 0;
}

void sig_usrX(int signum)
{
    
    
	if(signum==SIGUSR1)
		X1=1;
	else if(signum==SIGUSR2)
		X2=1;
	return;
}
void sig_usrY(int signum)
{
    
    
	if(signum==SIGUSR1)
		Y1=1;
	else if(signum==SIGUSR2)
		Y2=1;
	return;
}
void X_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* X,int pid,int sig)
{
    
    
	
	*X=0;
	char* memoryAddr=(char*)shmat(memoryID,NULL,0200);//连接共享内存(写)
	char* memoryAddrX=memoryAddr;
	int count=0;
	char temp;
	while(1)
	{
    
    
		if(count<SIZE)
		{
    
    
			temp=fgetc(fp);
			if(temp==EOF)
				break;
			int index=0;
			while(index<C)
				index++;
			*(memoryAddrX++)=temp;
			count++;
		}
		else
		{
    
    
			if(shmdt((void*)memoryAddr))//断开与共享内存的连接
				printf("Error in shmdt().");
			
			kill(pid,sig);
			break;
		}
		
	}
	if(temp==EOF)
	{
    
    
		*flag=0;
		*memoryAddrX='\0';//'\0'作为文件尾在共享内存中的结束标志
		if(count!=0)
			kill(pid,sig);//最后一块未满的共享内存让Y进程来处理。

		if(shmdt((void*)memoryAddr))//断开与共享内存的连接
			printf("Error in shmdt().");
		printf("Last time for shmdt.X\n");
	}
	

	return;
}
void Y_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* Y,int ppid,int sig)
{
    
    
	
	*Y=0;
	char* memoryAddr=(char*)shmat(memoryID,NULL,0400);//连接共享内存(读) 0400
	//if(memoryAddr==-1)
		//printf("Error in shmat(),Y\n");
	char* memoryAddrY=memoryAddr;
	int count=0;
	char temp;
	
	while(1)
	{
    
    	
		if(count<SIZE)
		{
    
    
			temp=*memoryAddrY;
			if(temp=='\0')
				break;	//X进程会设置内存结尾为'\0'
			int index=0;
			while(index<C)
				index++;
				
			temp-=32;//转换小写字母为大写字母
			fputc(temp,fp);
			memoryAddrY++;
			count++;
		}
		else
		{
    
    
			if(shmdt((void*)memoryAddr))//断开与共享内存的连接
				printf("Error in shmdt().");
			
			kill(ppid,sig);
			break;
		}
	}
	if(temp == '\0')
	{
    
    
		*flag=0;
		if(shmdt((void*)memoryAddr))//断开与共享内存的连接
			printf("Error in shmdt().");
		printf("Last time for shmdt.Y\n");
	}
	
	return;
}

相关截图

      程序执行截图:
在这里插入图片描述
      查看输入输出文件内容:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Little_ant_/article/details/113076673