进程间通讯有四种方式:信号量、管道、消息队列、共享内存

我们之前已着重介绍过信号量、管道。

现着重介绍一下共享内存。

共享内存

       共享内存是最高效的IPC机制,因为它不涉及进程之间任何的数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则就会产生竞态条件。因此,共享内存通过和其他进程间通信方式一起使用。

       Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用函数:shmget、shmat、shmdt和shmctl

shmget

       shmget系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。请定义如下:

       #include<sys/shm.h>

       int   shmget(key_t   key,   size_t   size,   int   shmflg);

       和semget系统调用一样,key参数是一个键值,用来表示一段全局唯一的共享内存。size参数指定共享内存的大小,单位是字节。如果是创建新的共享内存,则size值必须被指定。如果是获取已经存在的共享内存,则可以把size设置为0。

       shmflg参数的使用和含义与semget系统调用的sem_flags参数相同。不过shmget支持两个额外的标志——SHM_HUGETLB和SHM_NORESERVE。它们的含义如下:

       SHM_HUGETLB,类似于mmap的MAP_NORESERVE标志,系统将使用“大页面”来共享内存分配空间。

       SHM_NORESERVE,类似于mmap的MAP_NORESERVE标志,不为共享内存保留交换分区(swap空间)。这样,当物理内存不足的时候,对该共享内存执行写操作将触发SIGSEGV信号。

       这里sem_flags参数:它低端的9个比特是该信号量的权限,其格式和含义都与系统调用open的mode参数相同。此外,它还可以和IPC_CREAT标志做按位“或”运算以创建新的信号量集。此时即使信号量已经存在,semget也不会产生错误。我们还可以联合使用IPC_CREAT和IPC_EXCL标志来确保创建一组新的、唯一的信号量集。在这种情况下,如果信号量集已经存在,则semget返回错误并设置errno为EEXIST。这种创建信号量的行为与用O_CREAT和O_EXCL标志调用open来排他式地打开一个文件相似。

        shmget成功时返回一个正整数值,它是共享内存的标识符。shmget失败时返回-1,并设置errno。

shmat  &  shmdt   

        共享内存被创建、共享之后,我们不能立即访问它,而是需要先将它关联到进程的地址空间中。使用完共享内存后,我们也需要先将它从进程的地址空间中分离。这两项任务分别由如下的两个系统调用实现:

         #include<sys/shm.h> 

         void  *shmat(int  shm_id,   const   void *shm_addr,   int   shmflg);

         int   shmdt(const   void *  shm_addr);

         其中,shm_id参数是由shmget调用返回的共享内存标识符。shm_addr参数指定将共享内存关联到进程的哪块地址空间,最终的效果还受到shmflg参数的可选标志SHM_RND的影响:

          

             除了SHM_RND标志以外,shmflg参数还支持如下标志:

             

               

            shmat成功时返回共享内存被关联到的地址,失败则返回(void  *)-1  并设值为errno。shmat成功时,将修改内核数据结构shmid_ds的部分字段,如下:

           1.将shm_nattach加1

           2.将shm_Ipid设置为调用进程的PID

           3.将shm_atime设置为当前的时间

shmdt函数将关联到shm_addr处的共享内存从进程空间中分离。它成功时返回0,失败时则返回-1并设置errno。shmdt在成功调用时将修改内核数据结构shmid_ds的部分字段,如下:

           1.将shm_nattach减1

           2.将shm_Ipid设置为调用进程的PID

           3.将shm_dtime设置为当前的时间

shmctl

           #include<sys/shm.h>

           int  shmctl(int  shm_id,  int  command,  struct  shmid_ds*  buf);

 其中,shmid_id参数是由shmget调用返回的共享内存标识符。command参数指定要执行的命令,shmctl支持的所有命令如下表所示:

          

 shmctl成功时的返回值取决于command参数,shmctl失败时返回-1,并设置errno。

这里,将共享内存和信号量结合起来进行进程间通讯,这里实现的是在进程中传递数据,并可以从大写字母转换成小写字母:

代码实现

sem.h

#ifndef __SEM_H
#define __SEM_H

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>

typedef union semun
{
	int val;
}semun;

int sem_init(int key);

void sem_p(int id,int sem);

void sem_v(int id,int sem);

void sem_del(int id);

#endif

sem.c

#include"sem.h"

int sem_init(int key)
{
	int semid;
	semid=semget((key_t)key,0,0666);
	if(semid==-1)
	{
		semid=semget((key_t)key,1,0666 | IPC_CREAT);

		semun mu;
		mu.val=1;

		semctl(semid,0,SETVAL,mu);
	}
	return semid;
}

void sem_p(int id,int sem)
{
	struct sembuf buff;
	buff.sem_num=sem;
	buff.sem_op=-1;
	buff.sem_flg=SEM_UNDO;
	semop(id,&buff,1);
}

void sem_v(int id,int sem)
{
	struct sembuf buff;
	buff.sem_num=sem;
	buff.sem_op=1;
	buff.sem_flg=SEM_UNDO;
	semop(id,&buff,1);
}

void sem_del(int id)
{
	semctl(id,0,IPC_RMID);
}

shma.c 

#include"sem.h"
#include<sys/shm.h>
//因为共享内存中可能存在的竟态条件,因此共享内存需要和其他进程间通讯方式一起使用

int main()
{
	int shmid=shmget((key_t)1234,512,0666 | IPC_CREAT);
	assert(shmid!=-1);

	char *ptra=(char *)shmat(shmid,NULL,0);    //共享内存被创建后,我们不能立即访问它,而是需要现将它从关联到进程的地址空间中分离
	assert(ptra!=(char *)-1);

	int semid=sem_init(123);
    
	while(1)
	{
		sem_p(semid,0);
		printf("Put sentence:");
		fflush(stdout);

		memset(ptra,0,512);
		fgets(ptra,128,stdin);

		ptra[strlen(ptra)-1]=0;

		if((strcmp(ptra,"end")==0) || (strcmp(ptra,"END")==0))
		{
			break;
		}
		sem_v(semid,0);
	}
	shmdt(ptra);
	return 0;
}

 shmb.c

#include"sem.h"
#include<sys/shm.h>

int main()
{
	int shmid=shmget((key_t)1234,512,0666 | IPC_CREAT);
	assert(shmid!=-1);

	char *ptrb=(char *)shmat(shmid,NULL,0);
	assert(ptrb!=(char *)-1);

	int semid=sem_init(123);
    
	while(1)
	{
		sem_p(semid,0);
		if((strcmp(ptrb,"end")==0) || (strcmp(ptrb,"END")==0))
		{
			break;
		}
		int i=0;
		for(;i<strlen(ptrb);i++)
		{
			if(ptrb[i]>='A' &&  ptrb[i]<='Z')  //将大写字符转换成小写字符
			{
				ptrb[i]=ptrb[i]+32;
			}
			printf("%c\n",ptrb[i]);     //观看效果
		    sleep(1);
		}
		printf("%s\n",ptrb);

		sem_v(semid,0);
	}
	shmdt(ptrb);
	return 0;
}

执行结果


版权声明:本文为weixin_42736024原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_42736024/article/details/84791738