目录
一、信号量集
信号量集,其实就是无名信号量的集合,主要用于完整多个进程间的同步问题.。
使用 semget 函数申请信号量集时,需指定申请的信号量的数量,函数会对信号量从零开始编号;对信号量进行初始化则、删除等操作需要使用 semctl 函数;申请和释放信号量时需指定信号量编号使用的时 semop 函数。
由于直接使用函数库提供的函数操作繁琐,使用不方便,所以对信号量集相关函数进行再次封装,以便使用。
1、信号量集的API函数接口
1、创建一个信号量集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
功能:创建信号灯集
参数1:用于创建信号量集的key值,可以是IPC_PRIVATE,也可以由ftok创建出来
参数2:创建的信号灯集中的信号量的个数
参数3:创建的标识
IPC_CREAT:表示本次操作要创建一个信号量集,如果该key值对应的信号量集已经存在,则直接打开该信号量集对象
IPC_EXCL:表示本次确保要创建一个新的信号量集,如果该信号量集已经存在,则该函数报错,错误码为EEXIST
创建文件的权限,也在该参数中,使用位或连接
返回值:成功返回创建出来的共享内存的id,失败返回-1并置位错误码2、信号量集的控制函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
功能:信号量集的控制函数
参数1:信号量集的id
参数2:要控制的信号灯集中的信号灯的编号,编号从0开始的
参数3:控制指令
IPC_RMID:删除信号灯集,此时参数4可以省略不写,参数2被忽略
IPC_STAT\IPC_SET:获取或设置信号灯集的属性
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):
struct ipc_perm {
key_t __key; /* Key supplied to semget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
GETVAL\SETVAL:设置参数2这一个信号灯中的值,放到参数4提供的整数中
GETALL\SETALL:设置或获取所有信号灯的值,放入到参数4提供的数组中
参数4:可变参数,会根据参数3的不同,使用的类型也不同,所以是一个共用体变量
union semun {
int val; /* 参数3为 SETVAL使用该成员 */
struct semid_ds *buf; /* 参数3位 IPC_STAT, IPC_SET使用该成员 */
unsigned short *array; /* 参数3位 GETALL, SETALL 使用该成员*/
struct seminfo *__buf; /* 参数3为 IPC_INFO
使用该成员*/
};
返回值:对于GETVAL成功返回获取的信号号
对于GETVAL:返回信号量集ID
失败全部返回-1并置位错误码3、对信号灯进行PV操作(申请资源和释放资源)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, size_t nsops);
功能:完成对信号灯集的相关操作
参数1:信号灯集的id
参数2:执行的操作,是一个结构体,成员如下
unsigned short sem_num; /* 要操作的信号灯的编号 */
short sem_op; 执行的操作:正数表示释放资源,负数表示申请资源
short sem_flg; 是否阻塞:0表示阻塞,IPC_NOWAIT表示非阻塞
参数3:参数2的个数
返回值: 成功返回0,失败返回-1并置位错误码
二、 将信号量集函数再次封装
1、sem.h
#ifndef SEM_H
#define SEM_H
#include <myhead.h>
union semun
{
int val; /* 参数3为 SETVAL使用该成员 */
struct semid_ds *buf; /* 参数3位 IPC_STAT, IPC_SET使用该成员 */
unsigned short *array; /* 参数3位 GETALL, SETALL 使用该成员*/
struct seminfo *__buf; /* 参数3为 IPC_INFO
使用该成员*/
};
// 创建信号灯集并初始化
// 返回值:信号灯集id
// 参数:信号灯集中灯的个数
int sem_create(int semcont);
// 执行申请某个信号灯的资源操作(P操作)
// 返回值:成功返回0,失败返回-1
// 参数1:信号灯集id
// 参数2:要操作的信号灯编号
int P(int semid, int semnum);
// 执行释放某个信号灯的资源操作(V操作)
// 返回值:成功返回0,失败返回-1
// 参数1:信号灯集id
// 参数2:要操作的信号灯编号
int V(int semid, int semnum);
// 删除信号灯集
// 参数:信号灯id号
int sem_del(int semid);
#endif
2、sem.c
#include"sem.h"
//定义设置某个信号灯的值的函数
int init_semnum(int semid, int semnum)
{
int val = 0;
printf("请输入第%d号灯的初始值:", semnum+1);
scanf("%d", &val);
getchar();
//准备共用体变量
union semun buf;
buf.val = val; //要传递的数据
//调用semctl函数完成对信号灯的值的设置
if(semctl(semid, semnum, SETVAL, buf)==-1)
{
perror("semctl error");
return -1;
}
return 0;
}
//创建信号灯集并初始化
int sem_create(int semcont)
{
//1、创建key值
key_t key = ftok("/", 't');
if(key == -1)
{
perror("ftok error");
return -1;
}
//2、创建信号量集
int semid = semget(key, semcont, IPC_CREAT|IPC_EXCL|0664);
if(semid == -1)
{
//对错误码进行判断
if(errno == EEXIST)
{
//说明消息队列已经存在,直接打开即可
semid = semget(key, semcont, IPC_CREAT|0664);
return semid;
}
perror("semget error");
return -1;
}
//3、对信号灯进行初始化
for(int i=0; i<semcont; i++)
{
init_semnum(semid, i);
}
//4、返回创建的信号灯集id
return semid;
}
//P操作:申请资源
int P(int semid, int semnum)
{
//定义操作结构体变量
struct sembuf buf;
buf.sem_num = semnum; //要操作的信号灯
buf.sem_op = -1; //表示申请资源,如果semnum灯的资源为0,则阻塞
buf.sem_flg = 0; //表示如果没有资源,则阻塞
//调用函数进行申请资源
if(semop(semid, &buf, 1) ==-1)
{
perror("P error");
return -1;
}
//成功返回0
return 0;
}
//V操作:释放资源
int V(int semid, int semnum)
{
//定义操作结构体变量
struct sembuf buf;
buf.sem_num = semnum; //要操作的信号灯
buf.sem_op = 1; //表示申请资源,如果semnum灯的资源为0,则阻塞
buf.sem_flg = 0; //表示如果没有资源,则阻塞
//调用函数进行申请资源
if(semop(semid, &buf, 1) ==-1)
{
perror("V error");
return -1;
}
//成功返回0
return 0;
}
//删除信号灯集
int sem_del(int semid)
{
//调用semctl删除信号灯集
if(semctl(semid, 1, IPC_RMID) ==-1)
{
perror("delete error");
return -1;
}
printf("信号灯集删除成功\n");
return 0;
}
三、使用信号量集完成共享内存的进程同步
1、发送端
#include <myhead.h>
#include <sys/user.h>
#include "sem.h"
int main(int argc, char const *argv[])
{
//1、创建/打开信号量集
int semid = sem_create(2);
//2、创建key值用于创建共享内存段
key_t key = ftok("/",'t');
if (key == -1)
{
perror("ftok error");
return -1;
}
printf("key = %d\n",key);
//3、创建一个共享内存的对象
int shmid = shmget(key,PAGE_SIZE,IPC_CREAT|0664);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
printf("shmid = %d\n",shmid);
//4、将共享内存段映射到程序中
char *addr = (char *)shmat(shmid,NULL,0);
printf("addr = %p\n",addr);
//5、向共享内存中写入数据
while(1)
{
P(semid,0);
printf("输入信息:");
fgets(addr,PAGE_SIZE,stdin);
addr[strlen(addr)-1] = 0;
V(semid,1);
if (strcmp(addr,"quit") == 0)
{
printf("退出\n");
break;
}
}
//6、取消映射关系
if (shmdt(addr) == -1)
{
perror("shmdt error");
return -1;
}
return 0;
}
2、接收端
#include <myhead.h>
#include <sys/user.h>
int main(int argc, char const *argv[])
{
// 1、创建key值用于创建共享内存段
key_t key = ftok("/", 't');
if (key == -1)
{
perror("ftok error");
return -1;
}
printf("key = %d\n", key);
// 2、创建一个共享内存的对象
int shmid = shmget(key, PAGE_SIZE, IPC_CREAT | 0664);
if (shmid == -1)
{
perror("shmget error");
return -1;
}
printf("shmid = %d\n", shmid);
// 3、将共享内存段映射到程序中
char *addr = (char *)shmat(shmid, NULL, 0);
printf("addr = %p\n", addr);
//4、创建/打开信号量集
int semid = sem_create(2);
// 5、读出共享内存的数据
while(1)
{
P(semid,1);
printf("接收到:%s\n",addr);
if (strcmp(addr,"quit") == 0)
{
break;
}
V(semid,0);
}
// 6、取消映射关系
if (shmdt(addr) == -1)
{
perror("shmdt error");
return - 1;
}
// 7、删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("shmctl error");
return -1;
}
return 0;
}