Linux系统编程——进程同步与互斥:System V 信号量

时间:2022-10-23 14:48:11

信号量概述

信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。


编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。


在实际应用中两个进程间通信可能会使用多个信号量,因此 System V 的信号量以集合的概念来管理,具体操作和 Posix 信号量大同小异,详情请点此链接:http://blog.csdn.net/tennysonsky/article/details/46496201


信号量集合数据结构:struct semid_ds,此数据结构中定义了整个信号量集的基本属性。

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct semid_ds
{
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};

信号量数据结构:struct sem,此数据结构中定义了信号量的基本属性。

/* One semaphore structure for each semaphore in the system. */
struct sem
{
int semval; /* current value *信号量的值*/
int sempid; /* pid of last operation *最后一个操作信号量的进程号*/
struct list_head sem_pending; /* pending single-sop operations */
};


System V 信号量基本操作

使用 shell 命令操作信号量:

查看信号量:ipcs -s

删除信号量:ipcrm -s semid

Linux系统编程——进程同步与互斥:System V 信号量


以下函数所需头文件如下:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>


1)创建信号量集合

int semget(key_t key, int nsems, int semflg);

功能:

创建或打开一个信号量集合,该集合中可以包含多个信号量。

参数:

key:进程间通信键值,通过调用 ftok() 函数得到的键值,详情请点此链接:http://blog.csdn.net/tennysonsky/article/details/46331643

nsems:创建的信号量的个数。如果只是访问而不创建则可以指定该参数为 0,一旦创建了该信号量,就不能更改其信号量个数,只要不删除该信号量,重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,不会重新创建。

semflg:标识函数的行为及信号量的权限,其取值如下:

IPC_CREAT:创建信号量。
IPC_EXCL:检测信号量是否存在。
位或权限位:信号量位或权限位后可以设置信号量的访问权限,格式和 open 函数的 mode_ t 一样open() 的使用请点此链接,但可执行权限未使用。

返回值:

成功:信号量集标识符

失败:返回 -1


2)控制信号量集合、信号量

int semctl(int semid, int semnum, int cmd, ...);

功能:

对信号量集合以及集合中的信号量进行操作。

参数:

semid:信号量集标识符。

semnum:集合中信号量的序号,指定对哪个信号量操作, 只对几个特殊的 cmd 操作有意义。

cmd:信号量控制类型。semctl() 函数可能有3个参数,也可能有4个参数,参数的个数由 cmd 决定。当有4个参数时,第4个参数为联合体:

union semun{
int val; /*信号量的值*/
struct semid_ds *buf; /*信号量集合信息*/
unsigned short *array;/*信号量值的数组*/
struct seminfo *__buf;/*信号量限制信息*/
};
cmd 的取值如下:

GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值。

SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。 

IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf

IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf

IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用。

GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。

SETALL:设置所有信号量的值 。参数说明同上。

IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的__buf。

GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。semctl() 函数的返回值即为信号的进程号。

GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。

返回值:

成功:0

失败:-1


3)操作信号量

int semop(int semid, struct sembuf *sops, unsigned nsops);

功能:

操作信号量,主要进行信号量加减操作。

参数:

semid:信号量集标识符。
sops:操作信号量的结构体(struct sembuf)数组的首地址( 结构体定义在 sys/sem.h ),此结构体中的数据表明了对信号量进行的操作。

struct sembuf{
unsigned short sem_num; /*信号量的序号*/
short sem_op; /*信号量的操作值*/
short sem_flg; /*信号量的操作标识*/
};

结构体成员使用说明如下:

sem_num:信号量集中信号量的序号

sem_op 取值如下:

sem_op > 0:信号量的值在原来的基础上加上此值。

sem_op < 0:如果信号量的值小于 semop 的绝对值,则挂起操作进程。如果信号量的值大于等于 semop 的绝对值,则信号量的值在原来的基础上减去 semop 的绝对值。

sem_op = 0:对信号量的值进行是否为 0 测试。若为 0 则函数立即返回,若不为 0 则阻塞调用进程。

sem_flag 取值如下: 

IPC_NOWAIT:在对信号量的操作不能执行的情况下使函数立即返回。

SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。

nsops:操作信号量的结构体数组中元素的个数。

返回值:

成功:0

失败:-1


使用示例

示例一:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
key_t key;

//创建key值
key = ftok(".", 'a');
if(key == -1)
{
perror("ftok");
}

//查看信号量
system("ipcs -s");

int semid;

//1: 创建的信号量的个数
semid = semget(key, 1, IPC_CREAT|0666); //创建信号量
if(semid == -1)
{
perror("semget");
}

system("ipcs -s"); //查看信号量

//删去信号量
// 0: 代表对第0个信号量进行操作
// IPC_RMID:删除信号量集
semctl(semid, 0, IPC_RMID);

system("ipcs -s"); //查看信号量

return 0;
}


运行结果如下:

Linux系统编程——进程同步与互斥:System V 信号量

示例二:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>

/*解决编译出错的问题*/
#define IPC_INFO 3

int main(int argc, char *argv[])
{
key_t key;

//创建key值
key = ftok(".", 'a');
if(key == -1)
{
perror("ftok");
}

system("ipcs -s"); //查看信号量

int semid;
//1: 创建的信号量的个数
semid = semget(key, 1, IPC_CREAT|0666);//创建信号量
if(semid == -1)
{
perror("semget");
}

system("ipcs -s"); //查看信号量

struct seminfo buf;
/*
//struct seminfo相关成员
struct seminfo {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};

*/

//IPC_INFO:获取信号量集合的限制信息。
//此时函数有4个参数,第2个参数semnum不起作用。
semctl(semid, 0, IPC_INFO, &buf);

printf("buf.semmni = %d\n", buf.semmni);
printf("buf.semmns = %d\n", buf.semmns);
printf("buf.semmnu = %d\n", buf.semmnu);
printf("buf.semmsl = %d\n", buf.semmsl);
printf("buf.semopm = %d\n", buf.semopm);
printf("buf.semume = %d\n", buf.semume);
printf("buf.semusz = %d\n", buf.semusz);
printf("buf.semvmx = %d\n", buf.semvmx);
printf("buf.semaem = %d\n", buf.semaem);

//删去信号量
// 0: 代表对第0个信号量进行操作
// IPC_RMID:删除信号量集
semctl(semid, 0, IPC_RMID);

system("ipcs -s"); //查看信号量

return 0;
}

运行结果如下:

Linux系统编程——进程同步与互斥:System V 信号量


示例三:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
key_t key;

//创建key值
key = ftok(".", 'a');
if(key == -1)
{
perror("ftok");
}

//查看信号量
system("ipcs -s");

int semid;

//1: 创建的信号量的个数
semid = semget(key, 1, IPC_CREAT|0666); //创建信号量
if(semid == -1)
{
perror("semget");
}

system("ipcs -s"); //查看信号量

int ret;

/*
//SETVAL: 设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。
union semun{
int val; //信号量的值
struct semid_ds *buf; //信号量集合信息
unsigned short *array; //信号量值的数组
struct seminfo *__buf; //信号量限制信息
};
*/
ret = semctl(semid, 0, SETVAL, 20);
if(ret == -1)
{
perror("semctl");
}

//GETVAL:获取信号量的值。函数返回值即为信号量的值。
ret = semctl(semid, 0, GETVAL);
if(ret == -1)
{
perror("semctl");
}
printf("ret = %d\n", ret);

// 0: 代表对第0个信号量进行操作
// IPC_RMID:删除信号量集
semctl(semid, 0, IPC_RMID);

system("ipcs -s");

return 0;
}

运行结果如下:

Linux系统编程——进程同步与互斥:System V 信号量


示例四:

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
key_t key;

//创建key值
key = ftok(".", 'a');
if(key == -1)
{
perror("ftok");
}

//查看信号量
system("ipcs -s");

int semid;

//2: 创建的信号量的个数
semid = semget(key, 2, IPC_CREAT|0666); //创建信号量
if(semid == -1)
{
perror("semget");
}

system("ipcs -s"); //查看信号量

int ret;

unsigned short sem_arry[2] = {30,20};

/*
//SETALL: 设置所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
union semun{
int val; //信号量的值
struct semid_ds *buf; //信号量集合信息
unsigned short *array; //信号量值的数组
struct seminfo *__buf; //信号量限制信息
};
*/
ret = semctl(semid, 0, SETALL, sem_arry);
if(ret == -1)
{
perror("semctl");
}


bzero(sem_arry, sizeof(sem_arry));
//GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
ret = semctl(semid, 0, GETALL, sem_arry);
if(ret == -1)
{
perror("semctl");
}
printf("sem_arry[0] = %d\n", sem_arry[0]);
printf("sem_arry[1] = %d\n", sem_arry[1]);

// IPC_RMID:删除信号量集
semctl(semid, 0, IPC_RMID);
system("ipcs -s");

return 0;
}

运行结果如下:

Linux系统编程——进程同步与互斥:System V 信号量


示例五:

//此范例使用信号量来同步共享内存的操作
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>

#define SHM_KEY 0x33
#define SEM_KEY 0x44

union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};

int P(int semid)
{
struct sembuf sb;
/*
//操作信号量的结构体
struct sembuf{
unsigned short sem_num;//信号量的序号
short sem_op; //信号量的操作值
short sem_flg; //信号量的操作标识
};
*/
sb.sem_num = 0;
sb.sem_op = -1;
//SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。
sb.sem_flg = SEM_UNDO;

//操作1个信号量
if(semop(semid, &sb, 1) == -1){
perror("semop");
return -1;
}

return 0;
}

int V(int semid)
{
struct sembuf sb;
/*
//操作信号量的结构体
struct sembuf{
unsigned short sem_num;//信号量的序号
short sem_op; //信号量的操作值
short sem_flg; //信号量的操作标识
};
*/

sb.sem_num = 0;
sb.sem_op = 1;
//SEM_UNDO:当进程退出后,该进程对信号量进行的操作将被撤销。
sb.sem_flg = SEM_UNDO;

//操作1个信号量
if(semop(semid, &sb, 1) == -1){
perror("semop");
return -1;
}

return 0;
}

int main(int argc, char **argv)
{
pid_t pid;
int i, shmid, semid;
int *ptr = NULL;
union semun semopts;
/*
union semun{
int val; //信号量的值
struct semid_ds *buf; //信号量集合信息
unsigned short *array; //信号量值的数组
struct seminfo *__buf; //信号量限制信息
};
*/

//创建一块共享内存, 存一个int变量
if ((shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0600)) == -1) {
perror("msgget");
return -1;
}

//将共享内存映射到进程, fork后子进程可以继承映射
ptr = (int *)shmat(shmid, NULL, 0);
if (ptr == (int *)-1) {
perror("shmat");
return -1;
}

*ptr = 0; //赋值为0

// 创建一个信号量用来同步共享内存的操作
if ((semid = semget(SEM_KEY, 1, IPC_CREAT | 0600)) == -1) {
perror("semget");
return -1;
}

//初始化信号量
semopts.val = 1;
if (semctl(semid, 0, SETVAL, semopts) < 0) {
perror("semctl");
return -1;
}

if ((pid = fork()) < 0) { //创建进程
perror("fork");
_exit(0);
}else if (pid == 0){ // Child
// 子进程对共享内存加1
for (i = 0; i < 100000; i++) {
P(semid);
(*ptr)++;
V(semid);
printf("child: %d\n", *ptr);
}

} else { //Parent
// 父进程对共享内存减1
for (i = 0; i < 100000; i++) {
P(semid);
(*ptr)--;
V(semid);
printf("parent: %d\n", *ptr);
}

//如果子进程结束,回收其资源
wait(NULL);

//如果同步成功, 共享内存的值为0
printf("finally: %d\n", *ptr);
}

return 0;
}

运行结果如下:

Linux系统编程——进程同步与互斥:System V 信号量


本教程示例代码下载请点此链接:http://download.csdn.net/detail/tennysonsky/9029479