IPC之信号量
1,多个进程或线程有可能同时访问的资源(变量、链表、文件等等)称为共享资源,
也叫临界资源(critical resources)。
2,访问这些资源的代码称为临界代码,这些代码区域称为临界区(critical zone)。
3,程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,P 操作就是申请资源,如果申请成功,资源数将会减少。
4,程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,V 操作就是释放资源,释放资源就是让资源数增加。
注意:有资源你就可以访问信号量元素,没有资源则访问失败
基本API函数
1.获取信号量函数
创建信号量时,还受到以下系统信息的影响:
1,SEMMNI:系统中信号量的总数最大值。
2,SEMMSL:每个信号量中信号量元素的个数最大值。
3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。
Linux 中,以上信息在/proc/sys/kernel/sem 中可查看
2.申请和释放资源(P/V)操作
注意:
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.获取或者设置信号量的相关属性
注意以下几点:
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
}
}