1. 概述
信号量一个计数器,用于多进程对共享数据对象的存取。
P操作:
为了获得共享资源,进程需要执行下列操作:
-
测试控制该资源的信号量。
-
若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
-
若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至第1步。
V操作:
当进程不再使用由一个信息量控制的共享资源时,该信号量值增1。如果有进程正在睡眠等待此信号量,则唤醒它们。
为了正确地实现信息量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。
2. 获取信号量
当使用XSI信号量时,首先需要通过调用函数 semget
来获得一个信号量ID。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag);
返回值:若成功,返回信号量ID;若出错,返回-1
nsems
是该集合中的信号量数。如果是创建新集合(一般在服务器中),则必须指定nsems
。如果引用一个现存的集合(一个客户机),则将nsems指定为0。
3. 信号量基本操作
semctl
函数包含了多种信号量操作。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semum, int cmd, . . . /* union semun arg */);
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
u_short *array; /* array for GETALL & SETALL */
};
对于除GETALL以外的所有GET命令,semctl函数都返回相应值。其他命令的返回值为0。
宏定义 | 作用 |
---|---|
IPC_STAT | 对此集合取semid_ds结构,并存储在由arg.buf指向的结构中。 |
IPC_SET | 按arg.buf指向的结构中的值设置集合相关结构体中的相关成员。 |
IPC_RMID | 从系统中删除该信号量集合。 |
GETVAL | 返回成员semnum的semval值。 |
SETVAL | 设置成员semnum的semval值。该值由arg.val指定。 |
GETPID | 返回成员semnum的sempid的值。 |
GETNCNT | 返回成员semnum的semncnt值 |
GETZCNT | 返回成员semnum的semzcnt值。 |
GETALL | 返回该集合中的所有的信号量值。 |
SETALL | 将该集合中的所有的信号量设置成arg.array指向的数组中的值。 |
4. 信号量PV操作
函数semop
自动执行信号量集合上的操作数组。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[ ], size_t nops);
返回值:若成功,返回信号量ID;若出错,返回-1
struct sembuf {
unsigned short sem_num; /* member # in set (0, 1, …, nsems-1) */
short sem_op; /* operation (negative, 0, or positive) */
short sem_flg; /* IPC_NOWAIT, SEM_UNDO */
};
参数nops规定该数组中操作的数量(元素数)。
对集合中的每个成员的操作由相应的sem_op值规定。此值可以是负值、0或者是正值。
- 若sem_op为正值,则对应于进程释放占用的资源数。
- 若sem_op为负值,则表示要获得由该信号量控制的资源。
5. 包装(warp function)
对相关api包装一下可以更加通用:
bool sem_get(const key_t key, int *sem_id) {
*sem_id = semget((key_t)key, 1, 0666 | IPC_CREAT);
if (*sem_id == -1) {
printf("sem_get fail!\n");
return false;
}
return true;
}
bool sem_init(const int sem_id, const int val) {
if (semctl(sem_id, 0, SETVAL, val) == -1) {
printf("sem_init fail!\n");
return false;
}
return true;
}
bool sem_unlink(const int sem_id) {
if (semctl(sem_id, 0, IPC_RMID, sem_id) == -1) {
printf("sem_unlink fail!\n");
return false;
}
return true;
}
bool sem_wait(int sem_id) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = 0;
if(semop(sem_id, &buf, 1) == -1) {
printf("sem_wait fail! %d\n", errno);
return false;
}
return true;
}
bool sem_post(int sem_id) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = +1;
buf.sem_flg = 0;
if(semop(sem_id, &buf, 1) == -1) {
printf("sem_post fail! %d\n", errno);
return false;
}
return true;
}
6. 例子:用信号量解生产者-消费者模型
生产者-消费者问题可以用信号量如下解决:
例子使用上述的warp函数,代码如下:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define NUMBER 50000
#define CHILD 5
#define BUFSIZE 10
bool sem_get(const key_t key, int* sem_id);
bool sem_init(const int sem_id, const int val);
bool sem_unlink(const int sem_id);
bool sem_wait(const int semid); // P
bool sem_post(const int semid); // V
#define IS_ERROR(ret) \
do { \
if (!ret) { \
exit(0); \
} \
} while(0); \
// critical resource
int fd;
// semaphore
int empty = -1;
int full = -1;
int mutex = -1;
void comsumer() {
int buf_out = 0;
int data = 0;
int cnt = 0;
for (int k = 0; k < NUMBER / CHILD; k++) {
sem_wait(full);
sem_wait(mutex);
// fetch buf_out
lseek(fd, BUFSIZE * sizeof(int), SEEK_SET);
read(fd, (char*)&buf_out, sizeof(int));
cnt++;
lseek(fd, sizeof(int) * buf_out, SEEK_SET);
read(fd, (char*)&data, sizeof(int));
printf("%d comsume %d %d\n", getpid(), data, cnt);
fflush(stdout);
// write back
buf_out = (buf_out + 1) % BUFSIZE;
lseek(fd, BUFSIZE * sizeof(int), SEEK_SET);
write(fd, (char *)&buf_out, sizeof(int));
sem_post(mutex);
sem_post(empty);
}
printf("%d total consume %d\n", getpid(), cnt);
}
void producer() {
int buf_in = 0;
for (int i = 0 ; i < NUMBER; i++) {
sem_wait(empty);
sem_wait(mutex);
lseek(fd, buf_in * sizeof(int), SEEK_SET);
write(fd, (char*)&i, sizeof(int));
buf_in = (buf_in + 1) % BUFSIZE;
printf("produce %d\n", i);
fflush(stdout);
sem_post(mutex);
sem_post(full);
}
}
int main() {
int ret = sem_get(123, &empty);
IS_ERROR(ret);
ret = sem_get(234, &full);
IS_ERROR(ret);
ret = sem_get(345, &mutex);
IS_ERROR(ret);
ret = sem_init(empty, BUFSIZE);
IS_ERROR(ret);
ret = sem_init(full, 0);
IS_ERROR(ret);
ret = sem_init(mutex, 1);
IS_ERROR(ret);
int out_index = 0;
fd = open("buffer.dat", O_CREAT | O_RDWR | O_TRUNC, 0666);
lseek(fd, BUFSIZE * sizeof(int), SEEK_SET);
write(fd, (char *)&(out_index), sizeof(int));
pid_t p;
// create producer
if((p = fork()) == 0) {
producer();
return 0;
} else if(p < 0){
printf("Fail to fork!\n");
return -1;
}
// create comsumer
for(int j = 0; j < CHILD ; j++)
{
if((p = fork()) == 0) {
comsumer();
return 0;
} else if(p < 0) {
printf("Fail to fork!\n");
return -1;
}
}
int cnt = 0;
printf("wait children!\n");
pid_t pid;
while (pid = waitpid(-1, NULL, 0)) {
if (errno == ECHILD) {
break;
}
cnt ++;
printf("pid: %d end | sum: %d\n", pid, cnt);
}
ret = sem_unlink(empty);
IS_ERROR(ret);
ret = sem_unlink(full);
IS_ERROR(ret);
ret = sem_unlink(mutex);
IS_ERROR(ret);
return 0;
}
bool sem_get(const key_t key, int *sem_id) {
*sem_id = semget((key_t)key, 1, 0666 | IPC_CREAT);
if (*sem_id == -1) {
printf("sem_get fail!\n");
return false;
}
return true;
}
bool sem_init(const int sem_id, const int val) {
if (semctl(sem_id, 0, SETVAL, val) == -1) {
printf("sem_init fail!\n");
return false;
}
return true;
}
bool sem_unlink(const int sem_id) {
if (semctl(sem_id, 0, IPC_RMID, sem_id) == -1) {
printf("sem_unlink fail!\n");
return false;
}
return true;
}
bool sem_wait(int sem_id) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = 0;
if(semop(sem_id, &buf, 1) == -1) {
printf("sem_wait fail! %d\n", errno);
return false;
}
return true;
}
bool sem_post(int sem_id) {
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = +1;
buf.sem_flg = 0;
if(semop(sem_id, &buf, 1) == -1) {
printf("sem_post fail! %d\n", errno);
return false;
}
return true;
}