什么是信号量?
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量主要用于同步和互斥。
信号量的操作
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用:大于0,资源可以请求,将信号量的值-1(P操作);
等于0,无资源可用,进程会进入睡眠状态直至资源可用;
当进程不再使用一个信号量控制的共享资源时,信号量的值+1(V操作)。
PV操作均为原子操作。这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性(SystemV版本信号量的缺陷),因为这个版本的信号量的创建与初始化是分开的。
使用信号量的优势
信号量可以用来控制多个进程对共享资源的访问,它作为一种锁机制,可以防止某个进程正在访问共享资源时,其他进程也访问该资源。它主要作为进程间以及同一进程内不同线程之间的同步手段。
函数原型
semget:用来创建和访问一个信号量集
原型:
int semget(key_t key,int nsems ,int semflg);
参数:
key:信号集的名字
nsems:信号集中信号的个数
semfig:和消息队列的flg参数一模一样:IPC_CREAT:存在则打开,否则创建;IPC_CREAT | IPC_EXCL存在则出错返回,否则创建,这样保证了打开的是一个全新的信号量集。还是要注意,这个IPC_EXCL单独使用没有任何意义。
返回值:
成功返回一个非负整数,即该信号集的表示码,失败返回-1
shmctl:用于控制信号量集
原型:
int semctl(int semid, int semnum,int cmd, ...);
参数:
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(三个可取值)
最后一个参数根据命令不同而不同
返回值:
成功返回0,失败返回-1
semop:创建和访问一个信号量集
原型:
int semop(int semid, struct sembuf * sops, unsigned nsops);
参数:
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是一个指向一个结构数值的指针
nsops:信号量的个数
返回值:成功返回0,失败返回-1
sembuf结构体:
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信号量的编号。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值:
一个是"-1",也就是P操作,等待信号量变的可用;
实例
comm.h
#pragma once
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#define _PATH_NAME_ "/tmp"
#define _PROJ_ID_ 0x666
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int create_sem_set(int nums);
int get_sem_set(int nums);
int init_sem_set(int sem_id, int which, int val);
int P(int sem_id);
int V(int sem_id);
int destroy(int sem_id);
comm.c
#include "comm.h"
static int comm_sem_set(int nums, int flag)
{
key_t key = ftok(_PATH_NAME_, _PROJ_ID_);
if (key < 0)
{
perror("ftok");
return -2;
}
return semget(key, nums, flag);
}
int create_sem_set(int nums)
{
int flag = IPC_CREAT | IPC_EXCL | 0644;
return comm_sem_set(nums, flag);
}
int get_sem_set(int nums)
{
int flag = IPC_CREAT;
return comm_sem_set(nums, flag);
}
int init_sem_set(int sem_id, int which, int val)
{
union semun un;
un.val = val;
return semctl(sem_id, which, SETVAL, un);
}
static int pv(int sem_id, int op)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = op;
buf.sem_flg = 0;
return semop(sem_id, &buf, 1);
}
int P(int sem_id)
{
return pv(sem_id, -1);
}
int V(int sem_id)
{
return pv(sem_id, 1);
}
int destroy(int sem_id)
{
return semctl(sem_id, 0, IPC_RMID);
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "comm.h"
int main()
{
int sem_id = create_sem_set(1);
init_sem_set(sem_id, 0, 1);
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 1;
}
else if (id == 0) // child
{
while (1)
{
P(sem_id);
printf("A");
fflush(stdout);
usleep(rand()%12345);
usleep(200000);
printf("A");
fflush(stdout);
usleep(rand()%12345);
V(sem_id);
}
}
else // father
{
while (1)
{
P(sem_id);
printf("B");
fflush(stdout);
usleep(rand()%12345);
usleep(200000);
printf("B");
fflush(stdout);
usleep(rand()%12345);
V(sem_id);
}
}
destroy(sem_id);
return 0;
}
两个进程同时打印,此时显示器为临界资源通过使用二元信号量后,父子进程对显示器这个临界资源实行互斥访问,保证了父子进程打印的消息成对出现: