信号量

当我们在多用户系统,多进程系统,或是两者混合的系统中使用线程操作编写程序时,我们经常会发现我们有段临界代码,在此处我们需要保证一个进程(或是一个线程的执行)需要排他的访问一个资源。

为了解决这个问题,引用了信号量机制,我们可以使用互斥或信号量来控制一个多线程程序对于临界区的访问。

信号量是一个特殊的变量,他是一个整数,并且只有两个操 作可以使得其值增加:等待(wait)与信号(signal)。

用于等待(wait)的P(信号量变量)
用于信号(signal)的V(信号量变量)

二值信号量

二值信号量使是只有0和1两个值的变量

假如有一个信号量为mutex,有如下两个操作

  • P(mutex)/wait(mutex) 若mutex大于0,mutex减为0;若mutex等于0,则挂起该进程
  • V(mutex)/signal(mutex) 若有进程被挂起等待mutex则释放mutex使被挂起的进程执行,若没有,则mutex加到1
**mutex取值只能为1/0**

也叫互斥信号量,可以用其管理临界区资源的控制权

Linux中的信号量工具

信号量函数

信号量函数定义:

1
2
3
4
#include <sys/sem.h>/*有可能还需要包含sys/types.h与sys/ipc.h文件*/
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

这些函数用于操作信号量值数组

semget函数

semget的作用: 创建一个新信号量 或者 取得一个已有的信号量

1
int semget(key_t key, int num_sems, int sem_flags);
  • key是整数值(唯一非零),可以任意指定一个正整数,semget可以根据key,新创建一个信号量,返回改信号量的标识,不相关的进程可以通过它访问这个创建的信号量,代表程序可能会使用某个资源.如果在两个进程中使用相同的key则key将负责两个进程的协调工作.

    PS:

    ​ 同一个key值返回的信号量标识相同

    ​ 不同的key值会创建不同的信号量,且信号量之间没有任何关系

  • num_sems表示需要的信号量数目,一般为1

  • sem_flags是一组标志,与fopen函数的 “w” “b” “r”类似,最低的9位二进制数字代表了这个信号量的权限信息.如IPC_CREATE | 0666,这些标记可以与 IPC_CREAT进行或操作来创建新的信号量,同时又可以用于取一个已有的信号量,使用IPC_CREAT | IPC_EXCL 来确保新建信号量,如果信号量已经有了会返回错误。

    PS:

    ​ IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作。

    ​ IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。

semget函数成功返回一个相应信号量标识符(非0),失败返回-1.

semop函数

semop函数用于对信号量进行操作。

1
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
  • sem_id,表示对哪一个信号量进行操作,由semget函数返回
  • sembuf *sem_ops
1
2
3
4
5
6
7
8
9
struct sembuf {

short sem_num; //要处理的信号量的下标,即指定对哪个信号灯进行操作(0,1,2...)若为二值信号量则为0

short sem_op; //要执行的操作,1表示加一,-1表示减一

short sem_flg; //操作标志,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所 //做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进 //程所占有,这个标记可以使得操作系统自动释放这个信号量。

}
  • size_t num_sem_ops表示操作次数一般为1

semctl函数

semctl用于直接控制信号量的信息,例如初始化一个值或删除信号量。

1
int semctl(int sem_id, int sem_num, int command, ...);
  • sem_id,表示对哪一个信号量进行操作,由semget函数返回

  • sem_num,要处理的信号量的下标,即指定对哪个信号灯进行操作(0,1,2…)若为二值信号量则为0

  • command,表示要执行的动作

    • SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun.val成员来传递。在信号量第一次使用之前需要设置信号量。
    • IPC_RMID:当信号量不再需要时用于删除一个信号量标识。
  • union semun

    1
    2
    3
    4
    5
    6
    7
    8
    9
    union semun{

    int val;

    struct semid_ds *buf;

    unsigned short *array;

    };

    这个声明一般包含在sem.h里面,也有可能没有,没有的话需要自己声明。

根据command的不同,返回值也不同。对于 SETVAL和IPC_RMID 成功返回0 失败返回-1

信号量的使用

创建信号量

1
2
3
4
int sem_id;
sem_id = semget((key_t)1234, 2, 0666 | IPC_CREAT);
if (sem_id == -1)
fprintf(stderr, "Failed to create semapore\n");

设置信号量初值

1
2
3
4
5
6
7
8
9
10
int set_semvalue(int sem_id, int index, int value)//index为信号量中的第几个信号灯
{
union semun sem_union;
sem_union.val = value;

if (semctl(sem_id, index, SETVAL, sem_union) == -1)
return 0;
else
return 1;
}

删除信号量

1
2
3
4
5
void del_semvalue(int sem_id)
{
if (semctl(sem_id, 0, IPC_RMID) == -1)
fprintf(stderr, "Failed to delete semapore\n");
}

P操作

P操作是通过调用semop函数实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int P(int sem_id, int index)
{
struct sembuf sem_b;

sem_b.sem_num = index;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;

if (semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semapore_p failed\n");
return 0;
}
return 1;
}

V操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int V(int sem_id, int index)
{
struct sembuf sem_b;

sem_b.sem_num = index;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;

if (semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semapore_v failed\n");
return 0;
}
return 1;
}