使用互斥量构建信号量

Building semaphores using Mutexes

我正在阅读现代操作系统,其中有一个练习要求您使用二进制信号量构建计数信号量。

我想知道我是否可以使用信号量和共享内存(C 和 POSIX IPC)来做到这一点。

我将在此处简化我的程序。

typedef struct sem {
    int semid; /* contains two semaphores with value set to 1, one for the critical region and the other for locking */
    int shmid; /* shared counter */
}Sem;

/* the parent process spawns a few child processes and then waits for them */
void child() {
    Sem *s;
    
    s = get_sem();
    down_sem(s);
    printf("working...");
    sleep(5);
    printf("finished!");
    up_sem(s);
}

void down_sem(Sem *s) {
    int n;

    enter_critical(s); // down to critical region sem
    n = get_counter(s);
    
    if (n == 0) {
        exit_critical(s); // up to critical region sem
        acquire_lock(s); // down to lock sem
    } else {
        n--;
        exit_critical(s); // up to critical region sem
    }
}

void up_sem(Sem *s) {
    int n;

    enter_critical(s);
    n = get_counter(s);
    n++;
    
    if (n == 1) {
        exit_critical(s);
        release_lock(s); // up to lock sem
    } else {
        exit_critical(s);
    }
}

但这不起作用,因为某些进程永远阻塞在锁信号量中。我刚刚开始学习这些概念,所以我的设计可能完全不对。

如果你愿意,我可以分享完整的代码。

提前致谢!

编辑:请求 mre

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/shm.h>

#define NPROC 5
#define SLOTS 2

#define PATH "."
#define SID 'S'
#define SEMN 2

#define SHMID 'M'
#define SHM_SIZE 32

#define CRIT 0
#define LOCK 1

#define UP 1
#define DOWN -1

typedef struct sem {
    int shmid;
    int semid;
}Sem;

typedef union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}Args;

void err (char *msg) {
    char error[50];
    int n;

    n = snprintf(error, 50, "[%d] %s", getpid(), msg);
    error[n] = 0;
    perror(error);
    exit(1);
}

int* get_n(Sem *s) {
    int *data = NULL;

    data = shmat(s->shmid, (void *)0, 0);
    if (data == (int *)(-1))
        err("get_n shmat");

    return data;
}

void dettach_n(int *data) {
    if (shmdt(data) == -1)  
        err("dettach_n shmdt");
}

void enter_crit(Sem *s) {       
    struct sembuf sb;

    sb.sem_num = CRIT;
    sb.sem_op = DOWN;

    printf("[%d] enter crit\n", getpid());
    if (semop(s->semid, &sb, 1) == -1)
        err("enter crit");
}

void exit_crit(Sem *s) {
    struct sembuf sb;

    sb.sem_num = CRIT;
    sb.sem_op = UP;

    printf("[%d] exit crit\n", getpid());
    if (semop(s->semid, &sb, 1) == -1)
        err("exit crit");
}

void acquire_lock(Sem *s) {
    struct sembuf sb;

    sb.sem_num = LOCK;
    sb.sem_op = DOWN;

    printf("[%d] acquire lock\n", getpid());
    if (semop(s->semid, &sb, 1) == -1)
        err("acquire lock");
}

void release_lock(Sem *s) {
    struct sembuf sb;

    sb.sem_num = LOCK;
    sb.sem_op = UP;

    printf("[%d] release lock\n", getpid());
    if (semop(s->semid, &sb, 1) == -1)
        err("release lock");
}

int sem_init(Sem *s, int n) {
    key_t key;
    Args arg;
    int *data;

    /* sem */
    if ((key = ftok(PATH, SID)) == -1) {
        return -1;
    }
    if ((s->semid = semget(key, SEMN, 0600 | IPC_CREAT)) == -1) {
        return -1;
    }

    arg.val = 1;
    if (semctl(s->semid, 0, SETVAL, arg) == -1) {
        return -1;
    }
    if (semctl(s->semid, 1, SETVAL, arg) == -1) {
        return -1;
    }

    /* mem */
    if ((key = ftok(PATH, SHMID)) == -1) {
        return -1;
    }
    if ((s->shmid = shmget(key, SHM_SIZE, 0664 | IPC_CREAT)) == -1) {
        return -1;
    }

    data = shmat(s->shmid, (void *)0, 0);
    if (data == (int *)(-1)) {
        return -1;
    }
    *data = n;
    if (shmdt(data) == -1) {
        return -1;
    }

    return 0;
}

int sem_get(Sem *s) {
    key_t key;
    
    if ((key = ftok(PATH, SID)) == -1) {
        return -1;
    }
    if ((s->semid = semget(key, SEMN, 0)) == -1) {
        return -1;
    }

    if ((key = ftok(PATH, SHMID)) == -1) {
        return -1;
    }
    if ((s->shmid = shmget(key, SHM_SIZE, 0)) == -1) {
        return -1;
    }

    return 0;
}

int sem_up(Sem *s) {
    int *data;

    enter_crit(s);
    data = get_n(s);
    printf("[%d] read %d\n", getpid(), *data);
    (*data)++;

    if (*data == 1) {
        printf("[%d] now is %d\n", getpid(), *data);
        dettach_n(data);
        exit_crit(s);
        release_lock(s);
    } else {
        exit_crit(s);
    }

    return 0;
}

int sem_down(Sem *s) {
    int *data;

    enter_crit(s);
    data = get_n(s);

    printf("[%d] checked %d\n", getpid(), *data);
    if (*data == 0) {
        dettach_n(data);
        exit_crit(s);
        acquire_lock(s);
    } else {
        (*data)--;
        dettach_n(data);
        exit_crit(s);
    }

    return 0;
}

int sem_rm(Sem *s) {
    if (semctl(s->semid, 0, IPC_RMID) == -1)
        return -1;

    if (shmctl(s->shmid, 0, IPC_RMID) == -1)
        return -1;

    return 0;
}

void child() {
    pid_t pid;
    Sem s;

    pid = getpid();
    printf("\x1b[31m[%d] hello!3[0m\n", pid);

    if (sem_get(&s) == -1) {
        perror("sem_get");
        exit(1);
    }   
    sem_down(&s);
    printf("\x1b[32m[%d] working...3[0m\n", pid);
    sleep(5);
    printf("\x1b[32m[%d] finishing...3[0m\n", pid);
    sem_up(&s);
    printf("\x1b[34m[%d] **child leaving**3[0m\n", pid);
    exit(0);
}

int main() {
    Sem s;
    int i;

    if (sem_init(&s, SLOTS) == -1) {
        perror("sem_init");
        exit(1);
    }

    printf("[%d] parent\n", getpid());

    for (i = 0; i < NPROC; i++) {
        switch(fork()) {
        case 0:
            child();
        case -1:
            perror("fork");
            exit(1);
        }
    }   

    printf("waiting for children...\n");
    for (i = 0; i < NPROC; i++)
        wait(NULL);

    if (sem_rm(&s) == -1) {
        perror("sem_rm");
        exit(1);
    }

    printf("good bye!\n");

    return 0;
}

问题出在 up_sem 函数中。如果 n 之前为 0,它只会执行锁定 sem(release_lock func)。这是不正确的,因为它可能有多个进程在等待。

我的解决方案是添加一个额外的共享内存来计算等待进程,并在该数字大于 0 时执行锁定 sem。