IPC之信号量

1,多个进程或线程有可能同时访问的资源(变量、链表、文件等等)称为共享资源,
也叫临界资源(critical resources)。
2,访问这些资源的代码称为临界代码,这些代码区域称为临界区(critical zone)。
3,程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,P 操作就是申请资源,如果申请成功,资源数将会减少。
4,程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,V 操作就是释放资源,释放资源就是让资源数增加。
注意:有资源你就可以访问信号量元素,没有资源则访问失败

基本API函数
1.获取信号量函数
IPC之信号量

创建信号量时,还受到以下系统信息的影响:
1,SEMMNI:系统中信号量的总数最大值。
2,SEMMSL:每个信号量中信号量元素的个数最大值。
3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。
Linux 中,以上信息在/proc/sys/kernel/sem 中可查看

2.申请和释放资源(P/V)操作
IPC之信号量
IPC之信号量
注意:
2.1信号量操作结构体的定义如下:
struct sembuf
{
unsigned short sem_num; /* 信号量元素序号(数组下标) /
short sem_op; /
操作参数 /
short sem_flg; /
操作选项 */
};
请注意:信号量元素的序号从 0 开始,实际上就是数组下标。

2.2根据 sem_op 的数值,信号量操作分成 3 种情况:
2.2.1:当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值(semval)将会被加上
sem_op 的值。如果 SEM_UNDO 被设置了,那么该 V 操作将会被系统记录。V 操作永
远不会导致进程阻塞。

2.2.2当 sem_op 等于 0 时:进行等零操作,如果此时 semval 恰好为 0,则 semop( )
立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为
EAGAIN,否则将使得进程进入睡眠,直到以下情况发生:
semval 变为 0。
信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM)
收到信号。(将导致 semop( )出错退出,错误码为 EINTR)

2.2.3当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值(semval)将会被减去
sem_op 的绝对值。如果 semval 大于或等于 sem_op 的绝对值,则 semop( )立即成功
返回,semval 的值将减去 sem_op 的绝对值,并且如果 SEM_UNDO 被设置了,那么该
P 操作将会被系统记录。如果 semval 小于 sem_op 的绝对值并且设置了 IPC_NOWAIT,
那么 semop( )将会出错返回且将错误码置为 EAGAIN,否则将使得进程进入睡眠,直到
以下情况发生:
semval 的值变得大于或者等于 sem_op 的绝对值。
信号量被删除。(将导致 semop( )出错退出,错误码为 EIDRM)
收到信号。(将导致 semop( )出错退出,错误码为 EINTR)

3.获取或者设置信号量的相关属性
IPC之信号量
注意以下几点:
3.1这是一个变参函数,根据 cmd 的不同,可能需要第四个参数,第四个参数是一个如
下所示的联合体,用户必须自己定义:

union semun
{
	int val; /* 当 cmd 为 SETVAL 时使用 */
	struct semid_ds *buf; /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
	unsigned short *array; /* 当 cmd 为 GETALL 或 SETALL 时使用 */
	struct seminfo *__buf; /* 当 cmd 为 IPC_INFO 时使用 */
};

3.2,使用 IPC_STAT 和 IPC_SET 需要用到以下属性信息结构体:

struct semid_ds
{
struct ipc_perm sem_perm; /* 权限相关信息 */
time_t sem_otime; /* 最后一次 semop( )的时间 */
time_t sem_ctime; /* 最后一次状态改变时间 */
unsigned short sem_nsems; /* 信号量元素个数 */
};
权限结构体如下:
struct ipc_perm
{
key_t __key; /* 该信号量的键值 key */
uid_t uid; /* 所有者有效 UID */
gid_t gid; /* 所有者有效 GID */
uid_t cuid; /* 创建者有效 UID */
gid_t cgid; /* 创建者有效 GID */
unsigned short mode; /* 读写权限 */
unsigned short __seq; /* *** */
};

3.3,使用 IPC_INFO 时,需要提供以下结构体:

struct seminfo
{
	int semmap; /* 当前系统信号量总数 */
	int semmni; /* 系统信号量个数最大值 */
	int semmns; /* 系统所有信号量元素总数最大值 */
	int semmnu; /* 信号量操作撤销结构体个数最大值 */
	int semmsl; /* 单个信号量中的信号量元素个数最大值 */
	int semopm; /* 调用 semop( )时操作的信号量元素个数最大值 */
	int semume; /* 单个进程对信号量执行连续撤销操作次数的最大值 */
	int semusz; /* 撤销操作的结构体的尺寸 */
	int semvmx; /* 信号量元素的值的最大值 */
	int semaem; /* 撤销操作记录个数最大值 */
};

3.4,使用 SEM_INFO 时,跟 IPC_INFO 一样都是得到一个 seminfo 结构体,但其中几个
成员的含义发生了变化:
semusz 此时代表系统当前存在的信号量的个数
semaem 此时代表系统当前存在的信号量中信号量元素的总数

4.例子
使用信号来实现两个进程间同步互斥访问共享内存
进程1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <signal.h>

#include <wiringPi.h> 

#define SHMSIZE 128		//共享内存的大小(byte)
#define PASHKEY "." 	// 用以产生键值 key
#define SHM_PROJKEY 1	// 用以产生键值 key
#define SEM_PROJKEY 2	// 用以产生键值 key

#define DATA	0		//定义DATA为信号量第0个元素
#define SPACE 	1		//定义SPACE为信号量第1个元素

int semid;
int shmid;
char *shmaddr = NULL;

union semun // 自定义的信号量操作联合体
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};

static void seminit(int semid, int semnum, int value)	//初始化信号量的第几个元素的初始值
{
	union semun a;
	a.val = value;
	semctl(semid, semnum, SETVAL, a);
}

static void sem_p(int semid, int semnum)	//申请信号量第几个元素资源
{
	struct sembuf op[1];	//该结构体告诉内核,对信号量元素是申请还是释放
	op[0].sem_num = semnum;	//信号量的第几个元素
	op[0].sem_op = -1;		//对信号量元素进行资源加减操作
	op[0].sem_flg = 0;

	//信号量元素进行申请(p)操作
	semop(semid, op, 1);	//op:信号量要操作的结构体数组,1:信号量结构体数组元素有几个
}

static void sem_v(int semid, int semnum)	//释放信号量第几个元素资源
{
	struct sembuf op[1];
	op[0].sem_num = semnum;
	op[0].sem_op = 1;
	op[0].sem_flg = 0;

	//信号量元素进行释放(v)操作
	semop(semid, op, 1);//op:信号量要操作的结构体数组,1:信号量结构体数组元素有几个
}

char *shm_Creat128Byte(void)
{
	key_t key1 = ftok(PASHKEY, SHM_PROJKEY); // 获取 SHM 对应的键	
	int shmid = shmget(key1, SHMSIZE, IPC_CREAT|0644);// 获取 SHM 的 ID,并将之映射到本进程虚拟内存空间中
	return ((char *)shmat(shmid, NULL, 0));		//NULL让系统自动分配映射地址,0:默认映射内存可读可写	
}

void routine1(int signum) 
{
	printf("SEM2 exit handler.\n");
	semctl(semid, DATA, IPC_RMID);	//删除信号量元素DATA
	semctl(semid, SPACE, IPC_RMID);	//删除信号量元素SPACE
	shmdt(shmaddr);					//解除共享内存映射
	shmctl(shmid, IPC_RMID, NULL);	//删除共享内存
	exit(0);
}

int main(int argc, void *srgv[])
{
	int	i = 0;
	int semval = 0; //记录信号量元素值
	
	signal(SIGINT, routine1);	//捕获信号SIGINT
	
	shmaddr = shm_Creat128Byte();	//共享内存的获取和映射

	key_t shmkey = ftok(PASHKEY, SEM_PROJKEY); // 获取 SEM 对应的键
	semid = semget(shmkey, 2, IPC_CREAT|IPC_EXCL|0644);// 获取 SEM 的 ID,并申请含2个元素的信号量
	if(semid == -1 && errno == EEXIST)	//如果信号量已经存在,不需要初始化信号量元素的初始值
	{
		semid = semget(shmkey, 2, 0644); // 直接获取 SEM 的 ID
	}
	else
	{
		seminit(semid, DATA, 0); 	// 将信号量第 0 个元素初始值为 0,代表数据
		seminit(semid, SPACE, 1); 	// 将信号量第 1 个元素初始值为 1,代表空间
	}

	while(1)
	{
		semval = semctl(semid, SPACE, GETVAL);//获取信号量元素值
		if(semval > 0)//有资源就申请资源
		{
			sem_p(semid, SPACE); // 向第 1 个信号量元素申请内存空间资源
			sprintf(shmaddr, "sem1 to sem2 %d\n", i++);
			sem_v(semid, DATA); // 增加代表数据资源的第 0 个信号量元素的值		
		}
		printf("sem1 running\n");
		delay(1000); //ms
	}	
}

例子2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <signal.h>

#include <wiringPi.h> 

#define SHMSIZE 128		//共享内存的大小(byte)
#define PASHKEY "." 	// 用以产生键值 key
#define SHM_PROJKEY 1	// 用以产生键值 key
#define SEM_PROJKEY 2	// 用以产生键值 key

#define DATA	0		//定义DATA为信号量第0个元素
#define SPACE 	1		//定义SPACE为信号量第1个元素

int semid;
int shmid;
char *shmaddr = NULL;
//semun:当semctl函数的第四个参数需要填写时,就要自己定义
union semun // 自定义的信号量操作联合体
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};

static void seminit(int semid, int semnum, int value)	//初始化信号量的第几个元素的初始值
{
	union semun a;
	a.val = value;
	semctl(semid, semnum, SETVAL, a);
}

static void sem_p(int semid, int semnum)	//申请信号量第几个元素资源
{
	struct sembuf op[1];	//该结构体告诉内核,对信号量元素是申请还是释放
	op[0].sem_num = semnum;	//信号量的第几个元素
	op[0].sem_op = -1;		//对信号量元素进行资源加减操作
	op[0].sem_flg = 0;

	//信号量元素进行申请(p)操作
	semop(semid, op, 1);	//op:信号量要操作的结构体数组,1:信号量结构体数组元素有几个
}

static void sem_v(int semid, int semnum)	//释放信号量第几个元素资源
{
	struct sembuf op[1];
	op[0].sem_num = semnum;
	op[0].sem_op = 1;
	op[0].sem_flg = 0;

	//信号量元素进行释放(v)操作
	semop(semid, op, 1);//op:信号量要操作的结构体数组,1:信号量结构体数组元素有几个
}

char *shm_Creat128Byte(void)
{
	key_t key1 = ftok(PASHKEY, SHM_PROJKEY); // 获取 SHM 对应的键	
	shmid = shmget(key1, SHMSIZE, IPC_CREAT|0644);// 获取 SHM 的 ID,并将之映射到本进程虚拟内存空间中
	return ((char *)shmat(shmid, NULL, 0));		//NULL让系统自动分配映射地址,0:默认映射内存可读可写	
}

void routine1(int signum) 
{
	printf("SEM2 exit handler.\n");
	semctl(semid, DATA, IPC_RMID);	//删除信号量元素DATA
	semctl(semid, SPACE, IPC_RMID);	//删除信号量元素SPACE
	shmdt(shmaddr);					//解除共享内存映射
	shmctl(shmid, IPC_RMID, NULL);	//删除共享内存
	exit(0);
}

int main(int argc, void *srgv[])
{
	int semval = 0; //记录信号量元素值
	
	signal(SIGINT, routine1);	//捕获信号SIGINT
	
	shmaddr = shm_Creat128Byte();	//共享内存的获取和映射

	key_t shmkey = ftok(PASHKEY, SEM_PROJKEY); // 获取 SEM 对应的键
	semid = semget(shmkey, 2, IPC_CREAT|IPC_EXCL|0644);// 获取 SEM 的 ID,并申请含2个元素的信号量
	if(semid == -1 && errno == EEXIST)	//如果信号量已经存在,不需要初始化信号量元素的初始值
	{
		semid = semget(shmkey, 2, 0644); // 直接获取 SEM 的 ID
	}
	else
	{
		seminit(semid, DATA, 0); // 将信号量第 0 个元素初始值为 0,代表数据标志
		seminit(semid, SPACE, 1);// 将信号量第 1 个元素初始值为 1,代表空间标志
	}

	while(1)
	{
		semval = semctl(semid, DATA, GETVAL);//获取信号量元素值
		if(semval > 0)//有资源就申请资源
		{
			sem_p(semid, DATA); // 向第 0 个信号量元素申请数据资源
			printf("from sem: %s", shmaddr);
			sem_v(semid, SPACE); // 增加代表空间资源的第 1 个信号量元素的值
		}
		printf("sem2 running\n");
		delay(500); //ms	
	}
}