一、什么是信号量
多个线程同一时刻对共享资源的读写会引起一些问题,需要通过一种机制,使得在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。信号量就是这种机制中的一种,其可用来调协进程对共享资源的访问的。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。一种常见的信号量是二进制信号量,其值只能为0和1。而取多个正整数值的信号量,称为通用信号量。
二、信号量的工作原理
信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),其行为分别如下:
P(sv): 如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行,进入等待信号量状态;
V(sv):
如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给信号量加1。
例如:两个进程共享一个二进制信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待{BANNED}中国第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
三、Linux的信号量机制
Linux提供了一套信号量操作接口,其支持通用信号量,二进制信号量是其特殊情况。下面分别认识下这些接口。
1、semget()函数
该函数作用为根据key值创建一个信号量或获得一个已有的信号量,其原型如下:
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/sem.h>
-
-
int semget(key_t key, int nsems, int semflg);
{BANNED}中国第一个参数key:唯一非0,通过该key值创建一个信号量标识符。不同进程使用相同的key值,通过semget()得到信号量标识符,其他操作函数均通过该标识符操作该信号量。
第二个参数num_sems:指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags:是一组标志,其低9bit指定了键值对象操作的用户权限,其指定方式和含义与open()函数类似。此外,还可指定IPC_CREAT和IPC_EXCL,来控制信号量对象已存在时,调用该接口的行为方式。
返回值:成功返回正整数,失败返回-1.
2、semop()函数
该函数的作用是改变信号量的值,其定义如下:
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/sem.h>
-
-
int semop(int semid, struct sembuf *sops, size_t nsops);
semid: 为通过semget()创建的信号量标识符。
sembuf结构定义如下:
-
struct sembuf {
-
unsigned short sem_num; /* semaphore number */
-
short sem_op; /* semaphore operation */
-
short sem_flg; /* operation flags */
-
}
sem_num:若为二进制信号量,它为0。
sem_op: 信号量在依次操作需要改变的数据,通常为两个数。-1:即P等待操作+1,即发送信号操作。
sem_flg可为IPC_NOWAIT和SEM_UNDO。设置操作为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放信号量而终止时,操作系统释放信号量。,
3、semctl()函数
函数原型如下:
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/sem.h>
-
-
int semctl(int semid, int semnum, int cmd, ...);
该函数用来直接控制信号量信息。
semid: 为通过semget()创建的信号量标识符。
semnum:指定集合中的第semnum个信号量。
cmd: 通过cmd指定控制操作。函数有三个还是四个变量,取决于cmd。
当有四个变量时,第四个入参为union semun类型。定义如下:
-
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) */
-
}
struct semid_ds的定义如下:
-
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 */
-
};
cmd通常为以下两个值:
SETVAL: 用来把信号量初始化一个信号量的值,即对信号的量的初始化。
IPC_RMID: 用于删除一个信号量标识符。
四、进程信号量通信测试
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/sem.h>
-
-
#include <unistd.h>
-
#include <string.h>
-
#include <fcntl.h>
-
#include <stdlib.h>
-
#include <stdio.h>
-
-
union semun
-
{
-
int val;
-
struct semid_ds *buf;
-
unsigned short *arry;
-
};
-
-
-
int set_semval(int sem_id)
-
{
-
union semun sem_union;
-
int ret;
-
-
sem_union.val = 1; // 二进制信号量
-
return semctl(sem_id, 0, SETVAL, sem_union);
-
}
-
-
static void del_sem(int sem_id)
-
{
-
union semun sem_union;
-
-
semctl(sem_id, 0, IPC_RMID, sem_union);
-
}
-
-
int sem_p(int sem_id)
-
{
-
// 等待获取信号量,值-1
-
struct sembuf sem_b;
-
-
sem_b.sem_num = 0;
-
sem_b.sem_op = -1;
-
sem_b.sem_flg = SEM_UNDO;
-
-
return semop(sem_id, &sem_b, 1);
-
}
-
-
int sem_v(int sem_id)
-
{
-
// 释放信号量,发送信号
-
struct sembuf sem_b;
-
-
sem_b.sem_num = 0;
-
sem_b.sem_op = 1;
-
sem_b.sem_flg = SEM_UNDO;
-
-
return semop(sem_id, &sem_b, 1);
-
}
-
-
int main(int argc, char *argv[])
-
{
-
char process_log = 'S';
-
int sem_id;
-
key_t key;
-
-
key = ftok(argv[0], 0x18);
-
if (key == -1) {
-
fprintf(stderr, "Failed to get key.\n");
-
goto out;
-
}
-
-
sem_id = semget(key, 1, IPC_CREAT | 0666);
-
if (sem_id == -1) {
-
fprintf(stderr, "Failed to get sem object.\n");
-
goto out;
-
-
}
-
-
// 带参数的为主进程,负责初始化信号量
-
if (argc > 1) {
-
if (set_semval(sem_id) == -1)
-
goto out;
-
-
process_log = argv[1][0];
-
sleep(2);
-
}
-
-
for (int i = 0; i < 10; i++) {
-
if (sem_p(sem_id) == -1)
-
goto out;
-
printf("%c", process_log);
-
-
fflush(stdout);
-
sleep(1);
-
fflush(stdout);
-
-
if (sem_v(sem_id) == -1)
-
goto out;
-
sleep(1);
-
}
-
-
printf("\n%d - finished\n", getpid());
-
-
if (argc > 1) {
-
sleep(3);
-
del_sem(sem_id);
-
}
-
-
return 0;
-
-
out:
-
return -1;
-
}
带有参数的进程为{BANNED}中国第一个进程,负责初始化信号量为二进制信号量。不带参数的进程打印S,直接使用信号量。结果运行如下:
六、信号量的总结
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。
阅读(2606) | 评论(0) | 转发(0) |