UNIX环境高级编程 - 信号量

本文详细介绍了信号量的概念及其在多进程同步中的作用。通过P操作和V操作,信号量用于控制对共享资源的访问。文章提供了一系列C语言API,如semget、semctl和semop,用于创建、初始化、操作和销毁信号量,并展示了如何用信号量解决经典的生产者-消费者问题。此外,还给出了封装这些API的辅助函数示例。
摘要由CSDN通过智能技术生成

1. 概述

信号量一个计数器,用于多进程共享数据对象的存取。

P操作:

为了获得共享资源,进程需要执行下列操作:

  1. 测试控制该资源的信号量。

  2. 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。

  3. 若此信号量的值为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或者是正值。

  1. 若sem_op为正值,则对应于进程释放占用的资源数。
  2. 若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. 例子:用信号量解生产者-消费者模型

生产者-消费者问题可以用信号量如下解决:

image-20220418160026605

例子使用上述的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值