Linux下的共享内存的使用

前言:

工作中有一块涉及到Linux下两个进程以上的进程间数据交互,后来因为种种原因不使用了。当时参考网上的接收端与发送端的共享内存程序,重新编写为适合加进工程内使用的形式。
程序中进程间通信使用了共享内存(Share Memory)与信号(Signal)这两种方式,程序亲测可用。
笔者对共享内存(Share Memory)的理解:Linux系统上开辟一块内存空间,而这块内存空间是所有进程都能访问的空间,不是进程的独有资源。
笔者对信号(Signal)的理解:类似MCU、SoC等嵌入式芯片的中断机制,可理解为操作系统的软中断。

暂时只接触到共享内存(Share Memory)与信号(Signal)这两种Linux下的进程间通信,其他方式等接触后再写,这里挖坑,后续待更新……

1. Linux进程间通信的初略介绍:

Inter Process Communication,简称IPC,即进程间通信。

# 在终端命令行下查看IPC情况
$ ipcs
------ Shared Memory Segments 共享内存段 --------
key        shmid      owner      perms      bytes      nattch     status
键         shmid      拥有者      权限        字节       连接数      状态
------ Semaphore Arrays 信号量数组 --------
key        semid      owner      perms      nsems
键         semid      拥有者      权限        nsems
------ Message Queues 消息队列 --------
key        msqid      owner      perms      used-bytes   messages
键         msqid      拥有者      权限        已用字节数     消息

ipcs [-m|-q|-s]
-m 输出有关共享内存(shared memory)的信息
-q 输出有关信息队列(message queue)的信息
-s 输出有关“遮断器”(semaphore)的信息

输出格式选择:
 -t, --time     show attach, detach and change times,查看最新调用IPC资源的详细时间
 -p, --pid      show PIDs of creator and last operator,查看IPC资源的创建者和使用的进程ID
 -c, --creator  show creator and owner,查看IPC的创建者和所有者
 -l, --limits   show resource limits,查看IPC资源的限制信息
 -u, --summary  show status summary,查看IPC资源状态汇总信息
     --human    show sizes in human-readable format,大小以容易理解的格式显示
 -b, --bytes    show sizes in bytes,大小转换为Byte显示

Linux目前使用较多的进程间通信有6种:

1.1 管道(Pipe)及有名管道(Named Pipe):

管道,一般指无名管道,只能用于具有亲缘关系进程间的通信。在文件系统没有节点(没有文件名),创建的描述符fds[0]固定用于读管道、描述符fds[1]固定用于写管道。对于指定进程而言,0In,1Out。
有名管道在管道所具有的功能基础上,还具备允许无亲缘关系进程间的通信。在文件系统有节点(有文件名)。

1.2 信号(Signal):

软件层次上对中断机制的一种模拟,比较复杂的通信方式。用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。信号可分为:不可靠信号(也称为非实时信号)、可靠信号(也称为实时信号)。

******************** 笔者在线请教路过的同学问题 ********************
笔者在嵌入式Linux开发板上使用信号遇到一个很奇怪的问题,希望后面的同学看到能解答下:
使用C语言编写的程序中,定义了信号编码为41的信号,并使用信号接收器接收这个信号。运行该C语言编写的程序在后台,然后使用脚本发送信号到该进程。奇怪的问题在于脚本的第一行如果是“#!/bin/sh”,那么该脚本需要发送信号编码为43的信号,该进程才能接收到;脚本的第一行如果是“#!/bin/bash”,那么该脚本发送信号编码为41的信号,该进程就能接收到。
******************** 笔者在线请教路过的同学问题 ********************

$ kill -l
 1) HUP		 2) INT		 3) QUIT	 4) ILL			 5) TRAP
 6) ABRT	 7) BUS		 8) FPE		 9) KILL		10) USR1
11) SEGV	12) USR2	13) PIPE	14) ALRM		15) TERM
16) STKFLT	17) CHLD	18) CONT	19) STOP		20) TSTP
21) TTIN	22) TTOU	23) URG		24) XCPU		25) XFSZ
26) VTALRM	27) PROF	28) WINCH	29) POLL		30) PWR
31) SYS		32) RTMIN	64) RTMAX

# Linux系统已定义的信号在HUP-SYS之间,即1-31,这部分信号称为非实时信号[网上资料看来的]
# 可自定义的信号在RTMin-RTMax之间,即32-64,这部分信号称为实时信号[网上资料看来的]

# 一般情况下,用户使用的信号自定为:
#define SIG_XXXX	(SIGRTMIN + xxx)		// C语言下
kill -s SIGRTMIN+xxx $ProgressID			// Bash下
kill -s SIGRTMIN+xxx+2 $ProgressID			// sh下

# 笔者记得曾看过有网友写过,信号尽可能的用(SIGRTMIN + 2)后的信号,SIGRTMIN +0、
# SIGRTMIN +1的信号不要用,这两个信号可能操作系统有用到,为了不冲突就尽量不用。
# 可以通过更改内核源码的方式,将信号的上限设为更高,大于64。
1.3 消息队列(Messge Queue):

消息队列是信息的链接表,包括Posix消息队列SystemV消息队列。

1.4 共享内存(Shared Memory):

可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。

1.5 信号量(Semaphore):

主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。

1.6 套接字(Socket):

这是一种更为一般的进程间通信机制,它可用于网络中不用机器之间的进程间通信,应用非常广泛。

2. 接收端的共享内存程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
//#include <sys/types.h>
//#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHSIGRT				"/tmp/PathSigRT.txt"
#define PROJ_ID_STORAGESIZE		(6)
#define SIGSTORAGESIZE			(SIGRTMIN + 6)

#define N 26
typedef struct
{
	pid_t MainPID;			// 主进程 RS_UV_APP 的PID
	pid_t AuxilliaryPID;	// 辅进程的PID
	int ShmID;				// 这段共享内存的ID
	char buf[N];
}SHM;
SHM* pSHM = NULL;

void handler(int signo)
{
	int i = 0;
	SHM *p = pSHM;
	
	printf("== Rx Data == \n");
	for(i = 0; i < N; i++)
	{
		printf("Rx %c, ", p->buf[i]);
	}
}

int ReceiverProgressInit(SHM** p, char fname[], int id, int* shmid)
{
	key_t key;

	// 系统IPC键值的格式转换函数,建立IPC通讯,获取key值。
	// 函数原型:key_t ftok( const char * fname, int id )
	// fname:指定的文件名(已经存在的文件名),一般使用当前目录,fname为“.”。
	// id:是子序号。虽然是int类型,但是只使用8bits(1-255)。在一般的UNIX实现中,
	// 是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
	if((key = ftok(fname, id)) < 0)
	{
		perror("== Rx == fail to ftok ");
		return -1;
	}
	// 打印共享内存定位的路径
	printf("== Rx == PathSigRT = %s == \n", fname);
	
	// 安装信号接收器
	signal(SIGSTORAGESIZE, handler);
	
	// 根据Key值获取共享内存,若不存在则创建与获取,若存在则获取。返回值为共享内存段标识符
	if((*shmid = shmget(key, sizeof(SHM), 0666 | IPC_CREAT | IPC_EXCL)) < 0)
	{
		// 判断文件是否存在
		if(EEXIST == errno)
		{
			// 根据Key值获取共享内存段标识符,若不存在则创建与连接,若存在则连接。返回值为共享内存段标识符
			*shmid = shmget(key, sizeof(SHM), 0666);
			// 根据共享内存段标识符,把共享内存映射到进程空间,返回被映射的段地址
			*p = (SHM *)shmat(*shmid, NULL, 0);
		}
		else
		{
			perror("== Rx == Fail to get ShareMemory ");
			return -1;
		}
	}
	else
	{
		// 根据共享内存段标识符,把共享内存映射到进程空间,返回被映射的段地址
		*p = (SHM*)shmat(*shmid, NULL, 0);
	}
	
	// 打印共享内存段的key值、段标识符、地址
	printf("== Rx == key = 0x%X, shmid = %d, addr = 0x%X \n", key, *shmid, *(unsigned int *)p);
	
	return 1 ;
}

void ShareMemRemove(SHM** p , int shmid)
{
	// 解除映射共享内存
	shmdt(*p);
	// 销毁共享内存
	shmctl(shmid, IPC_RMID, NULL);
}

void ReceiverProgress(void)
{
	SHM *p = NULL;
	int ret = 0;
	int shmid = 0;
	
	// 初始化接收进程
	ret = ReceiverProgressInit(&p, PATHSIGRT, PROJ_ID_STORAGESIZE, &shmid);
	if(ret > 0)
	{
		// 将共享内存的指针赋值到全局变量
		pSHM = p;
		printf("== Rx == SHM = pSHM = 0x%X == \n", (unsigned int)pSHM);
		
		// 清空共享内存的空间
		memset(p, 0, sizeof(SHM));
		
		// 将主函数的PID写入共享内存
		p->MainPID = getpid();
		p->ShmID = shmid;
		
		printf("== Rx == p->MainPID = %d \n", p->MainPID);
	}
	
}

int main(int argc, char *argv[])
{
	int i = 0;
	
	system("touch /tmp/PathSigRT.txt");
	printf("==== ReceiverProgress ==== \n");
	
	ReceiverProgress();
	
	i = 0;
	while(1)
	{
		if(i < 5)	i++;
		else		break;
//		printf("== Rx = %d === \n", i);
		sleep(10);
	}
	
	// 删除该共享内存段
	ShareMemRemove(&pSHM , pSHM->ShmID);
	
	i = 0;
	while(1)
	{
		if(i < 100)	i ++;
		else		i = 0;
//		printf("==== i = %03d \n", i);
		sleep(10);
	}
	return 0;
}

3. 发送端的共享内存程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHSIGRT				"/tmp/PathSigRT.txt"
#define PROJ_ID_STORAGESIZE		(6)
#define SIGSTORAGESIZE			(SIGRTMIN + 6)

#define N 26
typedef struct
{
	pid_t MainPID;			// 主进程的PID,即接收进程
	pid_t AuxilliaryPID;	// 辅进程的PID,即发送进程
	int ShmID;				// 这段共享内存的ID
	char buf[N];
}SHM;
SHM* pSHM = NULL;

int TransmitterProgressInit(SHM** p, char fname[], int id, int* shmid)
{
	key_t key;

	// 系统IPC键值的格式转换函数,建立IPC通讯,获取key值。
	// 函数原型:key_t ftok( const char * fname, int id )
	// fname:指定的文件名(已经存在的文件名),一般使用当前目录,fname为“.”。
	// id:是子序号。虽然是int类型,但是只使用8bits(1-255)。在一般的UNIX实现中,
	// 是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
	if((key = ftok(fname, id)) < 0)
	{
		perror("== Tx == fail to ftok ");
		return -1 ;
	}
	// 打印共享内存定位的路径
	printf("== Tx == PathSigRT = %s == \n", fname);
	
	// 安装信号接收器,可实现互发与互收,这里不实现
	//signal(SIGSTORAGESIZE, handler);
	
	// 根据Key值获取共享内存,若不存在则创建与获取,若存在则获取。返回值为共享内存段标识符
	if((*shmid = shmget(key, sizeof(SHM), 0666 | IPC_CREAT | IPC_EXCL)) < 0)
	{
		// 判断文件是否存在
		if(EEXIST == errno)
		{
			// 根据Key值获取共享内存段标识符,若不存在则创建与连接,若存在则连接。返回值为共享内存段标识符
			*shmid = shmget(key, sizeof(SHM), 0666);
			// 根据共享内存段标识符,把共享内存映射到进程空间,返回被映射的段地址
			*p = (SHM *)shmat(*shmid, NULL, 0);
		}
		else
		{
			perror("== Tx == Fail to get ShareMemory ");
			return -1;
		}
	}
	else
	{
		// 根据共享内存段标识符,把共享内存映射到进程空间,返回被映射的段地址
		*p = (SHM*)shmat(*shmid, NULL, 0);
	}
	
	// 打印共享内存段的key值、段标识符、地址
	printf("== Tx == key = 0x%X, shmid = %d, addr = 0x%X \n", key, *shmid, *(unsigned int *)p);
	
	return 1;
}

void TransmitterProgress(void)
{
	SHM *p = NULL;
	int ret = 0;
	int shmid = 0;
	
	printf("== Tx == SHM = p = 0x%X == \n", (unsigned int)p);
	
	// 初始化发送进程
	ret = TransmitterProgressInit(&p, PATHSIGRT, PROJ_ID_STORAGESIZE, &shmid);
	if(ret != 1)
	{
		perror("Fail to get ShareMemory");
		return;
	}
	
	// 将共享内存的指针赋值到全局变量
	pSHM = p;
	printf("== Tx == SHM = pSHM = 0x%X == \n", (unsigned int)pSHM);
	
	// 将主函数的PID写入共享内存
	p->AuxilliaryPID = getpid();
	
	printf("== Tx == p->MainPID = %d \n", p->MainPID);
	printf("== Tx == p->AuxilliaryPID = %d \n", p->AuxilliaryPID);
	
	unsigned char i = 0;
	
	// 往共享内存中写入数据
	for(i = 0; i < N; i++)
	{
		p->buf[i] = 'a' + i;
	}
	
	// 3.发送信号给接收进程
	ret = kill(p->MainPID, SIGSTORAGESIZE);
	if(ret < 0)
	{
		printf("== Tx == Transmit Signal Error \n");
	}
	
}

int main(int argc, char *argv[])
{
	printf("== Tx == TransmitterProgress ==== \n");
	
	TransmitterProgress();
	
#if 0
	int i = 0 ;
	printf("==== argc = %d ==== \n", argc);
	for(i = 1; argv[i] != NULL; i ++)
	{
		printf("==== argv[%d] = %s ==== \n" , i , *(argv + i));
	}
#endif
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值