LV030-SystemV信号量
一、System V 信号量
1. 创建或打开信号量
1.1 semget()
1.1.1 函数说明
在linux下可以使用man 2 semget命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 函数声明 */
int semget(key_t key, int nsems, int semflg);【函数说明】该函数用于创建或者打开一个System V信号量集,并获取信号量集的ID。
【函数参数】
key:key_t类型,ftok产生的key值(和信号灯关联)或者IPC_PRIVATE(这样只能用于具有血缘关系的进程通信)。nsems:int类型,信号量集中信号量的个数,当没有创建信号量集时,参数nems可以为0(表示不关心)。否则,nems必须大于0且小于等于每个信号量集(SEMMSL)的最大信号量数。semflg:int类型,信号灯集的访问权限,我们一般设置为为IPC_CREAT |0666。semflg 取值说明如下:
(1)如果key值为IPC_PRIVATE,或者没有现有的信号量集与key相关联,并且在semflg中指定了IPC_CREAT,则会创建一组新的nems信号量。
(2)如果semflg同时指定了IPC_CREAT和IPC_EXCL,并且key的信号量集已经存在,那么semget()将失败,errno被设置为EEXIST。
【返回值】int类型,成功返回信号量集标识符(ID),否则返回-1,并设置errno。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 至少应该有的语句 */
int semid;
semid = semget(key, 2, IPC_CREAT|0666);【注意事项】none
1.1.2 使用实例
暂无。
2. 信号量P、V操作实现
在System V信号量的相关函数中,没有直接实现P、V操作的函数,只有一个semop函数,通过这个函数,。我们可以自己实现对信号量加1和减1操作。
2.1 semop()
2.1.1 函数说明
在linux下可以使用man 2 semop命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 函数声明 */
int semop(int semid, struct sembuf *sops, size_t nsops);【函数说明】该函数用于实现信号量的P、V操作。
【函数参数】
semid:int类型,已创建的信号量集的ID。sops:struct sembuf类型的结构体指针变量,sops所指向的信号集中的每一个nops元素都是一个结构体,用于指定在单个信号量上执行的操作。struct sembuf 成员如下:
struct sembuf
{
short sem_num; /* 要操作的信号灯的编号 */
short sem_op; /* 1,释放资源,V操作; -1,分配资源,P操作 */
short sem_flg; /* 0(阻塞), IPC_NOWAIT, SEM_UNDO */
};定义一个struct sembuf类型的结构体变量,只能对某一个信号灯的操作,如果需要同时对多个信号量操作,则需要定义struct sembuf结构体数组或者多个struct sembuf结构体变量。
nsops:size_t类型,表示要进行操作的信号量的个数。
【返回值】int类型,成功返回0,失败返回-1,并设置errno。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 至少应该有的语句 */
struct sembuf sbuf;
sbuf.sem_num = 0;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid, &sbuf, 1);【注意事项】none
2.1.2 使用实例
暂无。
2.2 Poperation()
该函数为自定义的信号量的P操作,通过semop实现。
/**
* @Function: Poperation
* @Description: System V 信号量P操作,也就是获取资源
* @param semid : 已经创建的信号量集ID
* @param semindex : 信号量在信号量集中的编号(索引)
* @return : none
*/
void Poperation(int semid, int semindex)
{
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;
semop(semid, &sbuf, 1);
}2.3 Voperation()
该函数为自定义的信号量的V操作,通过semop实现。
/**
* @Function: Voperation
* @Description: System V 信号量V操作,也就是释放资源
* @param semid : 已经创建的信号量集ID
* @param semindex : 信号量在信号量集中的编号(索引)
* @return : none
*/
void Voperation(int semid,int semindex)
{
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid, &sbuf, 1);
}3. 信号量集控制
3.1 semctl()
3.1.1 函数说明
在linux下可以使用man 2 semctl命令查看该函数的帮助手册。
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 函数声明 */
int semctl(int semid, int semnum, int cmd, ...);/* man手册函数声明 */
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */ );/* 我自己加的 */【函数说明】该函数对semid标识的System V信号量集或该信号量集的semnum信号量执行cmd指定的控制操作。(集合中的信号量从0开始编号)。
【函数参数】
semid:int类型,已创建的信号量集的ID。semnum:int类型,要操作的信号量集中的信号量编号。cmd:int类型,表示要执行的操作。 cmd 常见取值及含义如下:
| GETVAL | 获取信号量的值,返回值是获得值。 |
| SETVAL | 设置信号量的值,需要用到第四个参数,这个参数是一个共用体,后边会有介绍。 |
| IPC_RMID | 从系统中删除信号量集合,唤醒所有因调用semop()阻塞在该信号量集合里的所有进程(相应调用会返回错误且errno被设置为EIDRM)。 |
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};【返回值】int类型,成功时返回值受到cmd的影响,一般会返回0,失败都是返回-1,并设置errno,详情可查看man手册。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* 至少应该有的语句 */
union semun mysem;/* 信号量参数结构体 */
mysem.val = 0; /* 设置信号量的值 */
semctl(semid, SEM_READ, SETVAL, mysem); /* 初始化信号量 */【注意事项】none
3.1.2 使用实例
暂无。
二、使用实例
需要提前说明的是,下边的例子只是为了测试System V信号量,最后没有释放共享内存区域。
/* 头文件 */
#include <stdio.h> /* perror fgets */
#include <sys/shm.h> /* shmget shmat */
#include <sys/ipc.h> /* semop semget ftok shmget shmat semctl */
#include <sys/sem.h> /* semop semget semctl */
#include <stdlib.h> /* system */
#include <signal.h> /* sigaction sigemptyset */
#include <sys/types.h>/* fork */
#include <unistd.h> /* fork */
#define SEM_READ 0 /* 读信号量编号 */
#define SEM_WRITE 1 /* 写信号量编号 */
union semun
{
int val;
};
void Poperation(int semid,int semindex);/* 获取资源 */
void Voperation(int semid,int semindex);/* 释放资源 */
/* 主函数 */
int main(int argc, char *argv[])
{
key_t key;
int shmid, semid;
char *shmAddr;
union semun mysem;/* 信号灯参数结构体 */
pid_t pid;
/* 1. 生成key */
key = ftok("./key.txt", 100);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key=%x\n", key);
/* 2. 创建或者打开共享内存 */
shmid = shmget(key, 512, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
return -1;
}
printf("shmid=%d\n", shmid);
/* 3. 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问*/
shmAddr = shmat(shmid, NULL, 0);
if(shmAddr < 0)
{
perror("shmat");
return -1;
}
printf("shmAddr=%p\n", shmAddr);
/* 4. 创建或者打开信号量集 */
semid = semget(key, 2, IPC_CREAT|0666);/* 信号量集包含两个信号量 */
if(semid < 0)
{
perror("semget");
return -1;
}
/* 5. 初始化读写信号量 */
mysem.val = 0;
semctl(semid, SEM_READ, SETVAL, mysem);
mysem.val = 1;
semctl(semid, SEM_WRITE, SETVAL, mysem);
/* 6. 创建父子进程 */
pid = fork();
if(pid < 0) /* 创建出错 */
{
perror("fork");
shmctl(shmid, IPC_RMID, NULL);/* 释放共享内存 */
semctl(semid, 0, IPC_RMID); /* 删除信号量 */
exit(-1);
}
else if(pid == 0) /* 子进程 读信号量 */
{
while(1)
{
Poperation(semid, SEM_READ);
printf("%s\n", shmAddr);
Voperation(semid, SEM_WRITE);
}
}
else /* 父进程 写信号量*/
{
while(1)
{
Poperation(semid, SEM_WRITE);
printf(">");
fgets(shmAddr, 32, stdin);
Voperation(semid, SEM_READ);
}
}
return 0;
}
/**
* @Function: Poperation
* @Description: System V 信号量P操作,也就是获取资源
* @param semid : 已经创建的信号量集ID
* @param semindex : 信号量在信号量集中的编号(索引)
* @return : none
*/
void Poperation(int semid,int semindex)
{
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;
semop(semid, &sbuf, 1);
}
/**
* @Function: Poperation
* @Description: System V 信号量V操作,也就是释放资源
* @param semid : 已经创建的信号量集ID
* @param semindex : 信号量在信号量集中的编号(索引)
* @return : none
*/
void Voperation(int semid,int semindex)
{
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid, &sbuf, 1);
}在终端执行以下命令编译程序:
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
key=643d0b38
shmid=18
shmAddr=0x7f92372a9000
>qidaink
qidaink
>当我们使用Ctrl+c结束进程后,使用ipcs -m显示所有的共享内存IPC对象信息,则有:
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x643d0b38 18 hk 666 512 0可以发现确实没有释放,但是关注点是在信号量的操作,所以这里我们其实可以手动删除共享内存,就是使用ipcrm -m shmid命令