Skip to content

LV030-SystemV信号量

一、System V 信号量

1. 创建或打开信号量

1.1 semget()

1.1.1 函数说明

linux下可以使用man 2 semget命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

/* 函数声明 */
int semget(key_t key, int nsems, int semflg);

函数说明】该函数用于创建或者打开一个System V信号量集,并获取信号量集的ID

函数参数

  • keykey_t类型,ftok产生的key值(和信号灯关联)或者IPC_PRIVATE(这样只能用于具有血缘关系的进程通信)。
  • nsemsint类型,信号量集中信号量的个数,当没有创建信号量集时,参数nems可以为0(表示不关心)。否则,nems必须大于0且小于等于每个信号量集(SEMMSL)的最大信号量数。
  • semflgint类型,信号灯集的访问权限,我们一般设置为为IPC_CREAT |0666。semflg 取值说明如下:

(1)如果key值为IPC_PRIVATE,或者没有现有的信号量集与key相关联,并且在semflg中指定了IPC_CREAT,则会创建一组新的nems信号量。

(2)如果semflg同时指定了IPC_CREATIPC_EXCL,并且key的信号量集已经存在,那么semget()将失败,errno被设置为EEXIST

返回值int类型,成功返回信号量集标识符(ID),否则返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#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. 信号量PV操作实现

System V信号量的相关函数中,没有直接实现PV操作的函数,只有一个semop函数,通过这个函数,。我们可以自己实现对信号量加1和减1操作。

2.1 semop()

2.1.1 函数说明

linux下可以使用man 2 semop命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

/* 函数声明 */
int semop(int semid, struct sembuf *sops, size_t nsops);

函数说明】该函数用于实现信号量的PV操作。

函数参数

  • semidint类型,已创建的信号量集的ID
  • sopsstruct sembuf类型的结构体指针变量,sops所指向的信号集中的每一个nops元素都是一个结构体,用于指定在单个信号量上执行的操作。struct sembuf 成员如下:
c
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结构体变量。

  • nsopssize_t类型,表示要进行操作的信号量的个数。

返回值int类型,成功返回0,失败返回-1,并设置errno

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#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实现。

c
/**
 * @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实现。

c
/**
 * @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命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#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开始编号)。

函数参数

  • semidint类型,已创建的信号量集的ID
  • semnumint类型,要操作的信号量集中的信号量编号。
  • cmdint类型,表示要执行的操作。 cmd 常见取值及含义如下:
GETVAL获取信号量的值,返回值是获得值。
SETVAL设置信号量的值,需要用到第四个参数,这个参数是一个共用体,后边会有介绍。
IPC_RMID从系统中删除信号量集合,唤醒所有因调用semop()阻塞在该信号量集合里的所有进程(相应调用会返回错误且errno被设置为EIDRM)。
- `arg`:`union semun`类型,当`cmd`为`SETVAL`时才会有该参数,该参数用于设置信号集中信号量的参数,一般使用`val`成员较多,该成员表示信号量的值。union semun 定义如下:
c
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手册。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#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信号量,最后没有释放共享内存区域。

c
/* 头文件 */
#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);
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
key=643d0b38
shmid=18
shmAddr=0x7f92372a9000
>qidaink
qidaink

>

当我们使用Ctrl+c结束进程后,使用ipcs -m显示所有的共享内存IPC对象信息,则有:

shell
------------ 共享内存段 --------------
        shmid      拥有者  权限     字节     连接数  状态      
0x643d0b38 18         hk         666        512        0

可以发现确实没有释放,但是关注点是在信号量的操作,所以这里我们其实可以手动删除共享内存,就是使用ipcrm -m shmid命令