最全面的 linux 信号量解析

2023-05-16

一.什么是信号量

信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。

信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为 0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

二.信号量的分类

在学习信号量之前,我们必须先知道 -- Linux 提供两种信号量:

  1. 内核信号量,由内核控制路径使用
  2. 用户态进程使用的信号量,这种信号量又分为 POSIX 信号量和 SYSTEM V 信号量。

POSIX 信号量又分为有名信号量和无名信号量。

  • 有名信号量,其值保存在文件中,所以它可以用于线程也可以用于进程间的同步。
  • 无名信号量,其值保存在内存中。

倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷失了方向。

三.内核信号量

1.内核信号量的构成

内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。

只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。

内核信号量是 struct semaphore 类型的对象,它在 <asm/semaphore.h> 中定义:

struct semaphore {
   atomic_t count;
   int sleepers;
   wait_queue_head_t wait;
}

count:相当于信号量的值,大于 0,资源空闲;等于 0,资源忙,但没有进程等待这个保护的资源;小于 0,资源不可用,并至少有一个进程等待资源。

wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。

sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。

2.内核信号量中的等待队列(删除,没有联系)

上面已经提到了内核信号量使用了等待队列 wait_queue 来实现阻塞操作。

当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工作队列中等待 CPU 资源,在适当的时机被调度。

内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用户干涉。由用户真正使用的等待队列我们将在另外的篇章进行详解。

3.内核信号量的相关函数

(1)初始化:

void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //将 sem 的值置为 1,表示资源空闲
void init_MUTEX_LOCKED (struct semaphore *sem); //将 sem 的值置为 0,表示资源忙

(2)申请内核信号量所保护的资源:

void down(struct semaphore * sem); // 可引起睡眠
int down_interruptible(struct semaphore * sem); // down_interruptible能被信号打断
int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠。无法锁定资源则
马上返回

(3)释放内核信号量所保护的资源:

void up(struct semaphore * sem);

4.内核信号量的使用例程

在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux 内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

size_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
    //获得信号量
    if (down_interruptible(&sem)) {
        return -ERESTARTSYS;
    }
    //将用户空间的数据复制到内核空间的global_var
    if (copy_from_user(&global_var, buf, sizeof(int))) {
        up(&sem);
        return -EFAULT;
    }
    //释放信号量
    up(&sem);
    return sizeof(int);
}

四.POSIX 信号量与 SYSTEM V 信号量的比较

1、对 POSIX 来说,信号量是个非负整数。常用于线程间同步。而 SYSTEM V 信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为 SYSTEM V IPC 服务的,信号量只不过是它的一部分。常用于进程间同步。

2.POSIX 信号量的引用头文件是“<semaphore.h>”,而 SYSTEM V 信号量的引用头文件是“<sys/sem.h>”。

3.从使用的角度,System V 信号量是复杂的,而 Posix 信号量是简单。比如,POSIX 信
号量的创建和初始化或 PV 操作就很非常方便。

五.POSIX信号量详解

1.无名信号量

无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。

无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

常见的无名信号量相关函数:

int sem_destroy(sem_t * sem);
int sem_init(sem_t *sem, int pshared, unsigned int value);

1)pshared == 0 用于同一多线程的同步;
2)若 pshared > 0 用于多个相关进程间的同步(即由 fork 产生的)

int sem_getvalue(sem_t *sem, int *sval);

取回信号量 sem 的当前值,把该值保存到 sval 中。

若有 1 个或更多的线程或进程调用 sem_wait 阻塞在该信号量上,该函数返回两种值:

1)返回 0
2)返回阻塞在该信号量上的进程或线程数目

linux 采用返回的第一种策略。

sem_wait(或sem_trywait)相当于 P 操作,即申请资源。

int sem_wait(sem_t *sem); // 这是一个阻塞的函数

测试所指定信号量的值,它的操作是原子的。
若 sem > 0,那么它减 1 并立即返回。

若 sem== 0,则睡眠直到 sem > 0,此时立即减 1,然后返回。

int sem_trywait(sem_t *sem); // 非阻塞的函数

其他的行为和 sem_wait 一样,除了:
若 sem== 0,不是睡眠,而是返回一个错误 EAGAIN。
sem_post 相当于 V 操作,释放资源。

int sem_post(sem_t *sem);

把指定的信号量 sem 的值加 1;
呼醒正在等待该信号量的任意线程。
注意:在这些函数中,只有 sem_post 是信号安全的函数,它是可重入函数

(a)无名信号量在多线程间的同步

无名信号量的常见用法是将要保护的变量放在 sem_wait 和 sem_post 中间所形成的临界区内,这样该变量就会被保护起来,例如:

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id;
void *thread_one_fun(void *arg)
{
    sem_wait(&sem_id);
    printf("thread_one have the semaphore\n");
    number++;
    printf("number = %d\n", number);
    sem_post(&sem_id);
}
void *thread_two_fun(void *arg)
{
    sem_wait(&sem_id);
    printf("thread_two have the semaphore \n");
    number--;
    printf("number = %d\n", number);
    sem_post(&sem_id);
}
int main(int argc, char *argv[])
{
    number = 1;
    pthread_t id1, id2;
    sem_init(&sem_id, 0, 1);
    pthread_create(&id1, NULL, thread_one_fun, NULL);
    pthread_create(&id2, NULL, thread_two_fun, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    printf("main,,,\n");
    return 0;
}

上面的例程,到底哪个线程先申请到信号量资源,这是随机的。如果想要某个特定的顺序的话,可以用 2 个信号量来实现。例如下面的例程是线程 1 先执行完,然后线程 2 才继续执行,直至结束。

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id1, sem_id2;
void *thread_one_fun(void *arg)
{
    sem_wait(&sem_id1);
    printf("thread_one have the semaphore\n");
    number++;
    printf("number = %d\n", number);
    sem_post(&sem_id2);
}
void *thread_two_fun(void *arg)
{
    sem_wait(&sem_id2);
    printf("thread_two have the semaphore \n");
    number--;
    printf("number = %d\n", number);
    sem_post(&sem_id1);
}
int main(int argc, char *argv[])
{
    number = 1;
    pthread_t id1, id2;
    sem_init(&sem_id1, 0, 1); // 空闲的
    sem_init(&sem_id2, 0, 0); // 忙的
    pthread_create(&id1, NULL, thread_one_fun, NULL);
    pthread_create(&id2, NULL, thread_two_fun, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    printf("main,,,\n");
    return 0;
}

(b)无名信号量在相关进程间的同步

说是相关进程,是因为本程序中共有 2 个进程,其中一个是另外一个的子进程(由 fork 产生)的。本来对于 fork 来说,子进程只继承了父进程的代码副本,mutex 理应在父子进程中是相互独立的两个变量,但由于在初始化 mutex 的时候,由 pshared = 1 指定了 mutex 处于共享内存区域,所以此时 mutex 变成了父子进程共享的一个变量。此时,mutex 就可以用来同步相关进程了。

#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
    int fd, i, count = 0, nloop = 10, zero = 0, *ptr;
    sem_t mutex;
    //open a file and map it into memory
    fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU);
    write(fd, &zero, sizeof(int));
    ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);
    /* create, initialize semaphore */
    if (sem_init(&mutex, 1, 1) < 0) //
    {
        perror("semaphore initilization");
        exit(0);
    }
    if (fork() == 0) { /* child process*/
        for (i = 0; i < nloop; i++) {
            sem_wait(&mutex);
            printf("child: %d\n", (*ptr)++);
            sem_post(&mutex);
        }
        exit(0);
    }
    /* back to parent process */
    for (i = 0; i < nloop; i++) {
        sem_wait(&mutex);
        printf("parent: %d\n", (*ptr)++);
        sem_post(&mutex);
    }
    exit(0);
}

2、有名信号量

有名信号量的特点是把信号量的值保存在文件中。

这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。

(a)有名信号量能在进程间共享的原因

由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当然文件里面保存的有名信号量值就共享了。

(b)有名信号量相关函数说明

有名信号量在使用的时候,和无名信号量共享 sem_wait 和 sem_post 函数。

区别是有名信号量使用 sem_open 代替 sem_init,另外在结束的时候要像关闭文件一样去关闭这个有名信号量。

(1)打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完
成了信号量的创建、初始化和权限的设置。

sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

name 是文件的路径名;

Oflag 有 O_CREAT 或 O_CREAT | EXCL 两个取值;

mode_t 控制新的信号量的访问权限;

Value 指定信号量的初始化值。

注意:

这里的 name 不能写成 /tmp/aaa.sem 这样的格式,因为在 linux 下,sem 都是创建在 /dev/shm 目录下。你可以将 name 写成“/mysem”或“mysem”,创建出来的文件都是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。

当 oflag = O_CREAT 时,若 name 指定的信号量不存在时,则会创建一个,而且后面的 mode 和 value 参数必须有效。若 name 指定的信号量已存在,则直接打开该信号量,同时忽略 mode 和 value 参数。

当 oflag = O_CREAT | O_EXCL 时,若 name 指定的信号量已存在,该函数会直接返回 error。

(2)一旦你使用了一信号量,销毁它们就变得很重要。

在做这个之前,要确定所有对这个有名信号量的引用都已经通过 sem_close() 函数关闭了,然后只需在退出或是退出处理函数中调用 sem_unlink() 去删除系统中的信号量,注意如果有任何的处理器或是线程引用这个信号量,sem_unlink() 函数不会起到任何的作用。

也就是说,必须是最后一个使用该信号量的进程来执行 sem_unlink 才有效。因为每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink 必须等待这个数为 0 时才能把 name 所指的信号灯从文件系统中删除。也就是要等待最后一个 sem_close 发生。

(c)有名信号量在无相关进程间的同步

前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于共享内存区,只有这样才能被无相关的进程所共享。

在下面这个例子中,服务进程和客户进程都使用 shmget 和 shmat 来获取得一块共享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。

File1: server.c 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main()
{
    char ch;
    int shmid;
    key_t key;
    char *shm, *s;
    sem_t *mutex;
    //name the shared memory segment
    key = 1000;
    //create & initialize semaphore
    mutex = sem_open(SEM_NAME, O_CREAT, 0644, 1);
    if (mutex == SEM_FAILED) {
        perror("unable to create semaphore");
        sem_unlink(SEM_NAME);
        exit(-1);
    }
    //create the shared memory segment with this key
    shmid = shmget(key, SHMSZ, IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("failure in shmget");
        exit(-1);
    }
    //attach this segment to virtual memory
    shm = shmat(shmid, NULL, 0);
    //start writing into memory
    s = shm;
    for (ch = 'A'; ch <= 'Z'; ch++) {
        sem_wait(mutex);
        *s++ = ch;
        sem_post(mutex);
    }
    //the below loop could be replaced by binary semaphore
    while (*shm != '*') {
        sleep(1);
    }
    sem_close(mutex);
    sem_unlink(SEM_NAME);
    shmctl(shmid, IPC_RMID, 0);
    exit(0);
}

client.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[] = "vik";
int main()
{
    char ch;
    int shmid;
    key_t key;
    char *shm, *s;
    sem_t *mutex;
    //name the shared memory segment
    key = 1000;
    //create & initialize existing semaphore
    mutex = sem_open(SEM_NAME, 0, 0644, 0);
    if (mutex == SEM_FAILED) {
        perror("reader:unable to execute semaphore");
        sem_close(mutex);
        exit(-1);
    }
    //create the shared memory segment with this key
    shmid = shmget(key, SHMSZ, 0666);
    if (shmid < 0) {
        perror("reader:failure in shmget");
        exit(-1);
    }
    //attach this segment to virtual memory
    shm = shmat(shmid, NULL, 0);
    //start reading
    s = shm;
    for (s = shm; *s != NULL; s++) {
        sem_wait(mutex);
        putchar(*s);
        sem_post(mutex);
    }
    //once done signal exiting of reader:This can be replaced by
    another semaphore
        *shm
        = '*';
    sem_close(mutex);
    shmctl(shmid, IPC_RMID, 0);
    exit(0);
}

六.SYSTEM V 信号量

这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由 <sys/ipc.h> 引用。

1、信号量结构体

内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:

struct semid_ds {
    struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
    struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集
中的每个信号量对应其中一个数组元素 */
    ushort sem_nsems; /* sem_base 数组的个数 */
    time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */
    time_t sem_ctime; /* 成功创建时间 */
};
struct sem {
    ushort semval; /* 信号量的当前值 */
    short sempid; /* 最后一次返回该信号量的进程ID 号 */
    ushort semncnt; /* 等待semval大于当前值的进程个数 */
    ushort semzcnt; /* 等待semval变成0的进程个数 */
};

2、常见的 SYSTEM V 信号量函数

(a)关键字和描述符

SYSTEM V 信号量是 SYSTEM V IPC(即 SYSTEM V 进程间通信)的组成部分,其他的有SYSTEM V 消息队列,SYSTEM V 共享内存。而关键字和 IPC 描述符无疑是它们的共同点,也使用它们,就不得不先对它们进行熟悉。这里只对 SYSTEM V 信号量进行讨论。

IPC描述符相当于引用 ID 号,要想使用 SYSTEM V 信号量(或MSG、SHM),就必须用 IPC 描述符来调用信号量。而 IPC 描述符是内核动态提供的(通过 semget 来获取),用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字 KEY 来定位描述符。

某个 KEY 只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和客户端事先认可共同使用某个 KEY,那么大家就都能定位到同一个描述符,也就能定位到同一个信号量,这样就达到了 SYSTEM V 信号量在进程间共享的目的。

(b)创建和打开信号量

int semget(key_t key, int nsems, int oflag)

(1)nsems > 0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。

(2)nsems == 0 : 访问一个已存在的集合。

(3)oflag == IPC_CREAT,semget()为一个新创建的集合返回标识号,或者返回具有相同键值的已存在集合的标识号。

oflag == IPC_CREAT | IPC_EXCL,要么创建一个新的集合,要么对已存在的集合返回 -1。

oflag == IPC_EXCL,IPC_EXCL 单独是没有用的。

(4)返回的是一个称为信号量标识符的整数,semop 和 semctl 函数将使用它。

(5)创建成功后信号量结构被设置:

.sem_perm 的 uid 和 gid 成员被设置成的调用进程的有效用户 ID 和有效组 ID。

.oflag 参数中的读写权限位存入 sem_perm.mode

.sem_otime 被置为 0,sem_ctime 被设置为当前时间

.sem_nsems 被置为 nsems 参数的值

该集合中的每个信号量不初始化,这些结构是在 semctl,用参数 SET_VAL,SETALL 初始化的。

semget 函数执行成功后,就产生了一个由内核维持的类型为 semid_ds 结构体的信号量集,返回 semid 就是指向该信号量集的索引。

(c)关键字的获取

有多种方法使客户机和服务器在同一 IPC 结构上会合:

(1)服务器可以指定关键字 IPC_PRIVATE 创建一个新 IPC 结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字 IPC_PRIVATE 保证服务器创建一个新 IPC 结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。

IPC_PRIVATE 关键字也可用于父、子关系进程。父进程指定 IPC_PRIVATE 创建一个新 IPC 结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为 exec 函数的一个参数传给一个新程序。

(2)在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的 IPC 结构。这种方法的问题是该关键字可能已与一个 IPC 结构相结合,在此情况下,get 函数(msgget、semget 或 shmget)出错返回。服务器必须处理这一错误,删除已存在的 IPC 结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。

(3)客户机和服务器认同一个路径名和 project ID(project ID 是 0 ~ 255 之间的字符值) ,然后调用函数 ftok 将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的问题。

extern key_t ftok (const char *__pathname, int __proj_id) __THROW;

使用 ftok 并非高枕无忧。有这样一种例外:服务器使用 ftok 获取得一个关键字后,该文件就被删除了,然后重建。此时客户端以此重建后的文件来 ftok 所获取的关键字就和服务器的关键字不一样了。所以一般商用的软件都不怎么用 ftok。

一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用 ftok,而只是在该头文件中存放一个大家都知道的关键字。

(d)设置信号量的值(PV操作)

int semop(int semid, struct sembuf *opsptr, size_t nops);

(1)semid: 是 semget 返回的 semid。

(2)opsptr: 指向信号量操作结构数组。

(3)nops : opsptr 所指向的数组中的 sembuf 结构体的个数。

struct sembuf {
    short sem_num; // 要操作的信号量在信号量集里的编号,
    short sem_op; // 信号量操作
    short sem_flg; // 操作表示符
};

(4) 若 sem_op 是正数,其值就加到 semval 上,即释放信号量控制的资源

若 sem_op 是 0,那么调用者希望等到 semval 变为 0,如果 semval 是 0 就返回;

若 sem_op 是负数,那么调用者希望等待 semval 变为大于或等于 sem_op 的绝对值

例如,当前 semval 为 2,而 sem_op = -3,那么怎么办?

注意:semval 是指 semid_ds 中的信号量集中的某个信号量的值

(5)sem_flg

SEM_UNDO 由进程自动释放信号量

IPC_NOWAIT 不阻塞

到这里,读者肯定有个疑惑:semop 希望改变的 semval 到底在哪里?我们怎么没看到有它的痕迹?其实,前面已经说明了,当使用 semget 时,就产生了一个由内核维护的信号量集(当然每个信号量值即 semval 也是只由内核才能看得到了),用户能看到的就是返回的 semid。内核通过 semop 函数的参数,知道应该去改变 semid 所指向的信号量的哪个semval。

(e)对信号集实行控制操作(semval的赋值等)

int semctl(int semid, int semum, int cmd, ../* union semun arg */);

semid 是信号量集合;

semnum 是信号在集合中的序号;

semum 是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成:

union semun {
    int val; // cmd == SETVAL
    struct semid_ds *buf; // cmd == IPC_SET或者 cmd == IPC_STAT
    ushort *array; // cmd == SETALL,或 cmd = GETALL
};

val 只有 cmd ==SETVAL 时才有用,此时指定的 semval = arg.val。

注意:当 cmd == GETVAL 时,semctl 函数返回的值就是我们想要的 semval。千万不要以为指定的 semval 被返回到 arg.val 中。

array 指向一个数组,当 cmd == SETALL 时,就根据 arg.array 来将信号量集的所有值都赋值;

当 cmd == GETALL 时,就将信号量集的所有值返回到 arg.array 指定的数组中。

buf 指针只在 cmd == IPC_STAT 或 IPC_SET 时有用,作用是 semid 所指向的信号量集 semid_ds机构体)。一般情况下不常用,这里不做谈论。另外,cmd == IPC_RMID 还是比较有用的。

(f)例码

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
static int nsems;
static int semflg;
static int semid;
int errno = 0;
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
} arg;
int main()
{
    struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组
    int rslt;
    unsigned short argarray[80];
    arg.array = argarray;
    semid = semget(IPC_PRIVATE, 2, 0666);
    if (semid < 0) {
        printf("semget failed. errno: %d\n", errno);
        exit(0);
    }
    //获取0th信号量的原始值
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n", rslt);
    //初始化0th信号量,然后再读取,检查初始化有没有成功
    arg.val = 1; // 同一时间只允许一个占有者
    semctl(semid, 0, SETVAL, arg);
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n", rslt);
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;
    sops[0].sem_flg = 0;
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;
    sops[1].sem_flg = 0;
    rslt = semop(semid, sops, 1); //申请0th信号量,尝试锁定
    if (rslt < 0) {
        printf("semop failed. errno: %d\n", errno);
        exit(0);
    }
    //可以在这里对资源进行锁定
    sops[0].sem_op = 1;
    semop(semid, sops, 1); //释放0th信号量
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n", rslt);
    rslt = semctl(semid, 0, GETALL, arg);
    if (rslt < 0) {
        printf("semctl failed. errno: %d\n", errno);
        exit(0);
    }
    printf("val1:%d val2: %d\n", (unsigned int)argarray[0], (unsigned int)argarray[1]);
    if (semctl(semid, 1, IPC_RMID) == -1) {
        Perror(“semctl failure while clearing reason”);
    }
    return (0);
}

七、信号量的牛刀小试 -- 生产者与消费者问题

1.问题描述

有一个长度为 N 的缓冲池为生产者和消费者所共有,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。生产者往缓冲池放信息的时候,消费者不可操作缓冲池,反之亦然。

2.使用多线程和信号量解决该经典问题的互斥

#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#define BUFF_SIZE 10
char buffer[BUFF_SIZE];
char count; // 缓冲池里的信息数目
sem_t sem_mutex; // 生产者和消费者的互斥锁
sem_t p_sem_mutex; // 空的时候,对消费者不可进
sem_t c_sem_mutex; // 满的时候,对生产者不可进
void *Producer()
{
    while (1) {
        sem_wait(&p_sem_mutex); //当缓冲池未满时
        sem_wait(&sem_mutex); //等待缓冲池空闲
        count++;
        sem_post(&sem_mutex);
        if (count < BUFF_SIZE) //缓冲池未满
            sem_post(&p_sem_mutex);
        if (count > 0) //缓冲池不为空
            sem_post(&c_sem_mutex);
    }
}
void *Consumer()
{
    while (1) {
        sem_wait(&c_sem_mutex); //缓冲池未空时
        sem_wait(&sem_mutex); //等待缓冲池空闲
        count--;
        sem_post(&sem_mutex);
        if (count > 0)
            sem_post(c_sem_nutex);
    }
}
int main()
{
    pthread_t ptid, ctid;
    //initialize the semaphores
    sem_init(&empty_sem_mutex, 0, 1);
    sem_init(&full_sem_mutex, 0, 0);
    //creating producer and consumer threads
    if (pthread_create(&ptid, NULL, Producer, NULL)) {
        printf("\n ERROR creating thread 1");
        exit(1);
    }
    if (pthread_create(&ctid, NULL, Consumer, NULL)) {
        printf("\n ERROR creating thread 2");
        exit(1);
    }
    if (pthread_join(ptid, NULL)) /* wait for the producer to finish */
    {
        printf("\n ERROR joining thread");
        exit(1);
    }
    if (pthread_join(ctid, NULL)) /* wait for consumer to finish */
    {
        printf("\n ERROR joining thread");
        exit(1);
    }
    sem_destroy(&empty_sem_mutex);
    sem_destroy(&full_sem_mutex);
    //exit the main thread
    pthread_exit(NULL);
    return 1;
}

转载:最全面的linux信号量解析_qinxiongxu的博客-CSDN博客_linux 信号量

(SAW:Game Over!)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

最全面的 linux 信号量解析 的相关文章

  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 加载数据infile,Windows和Linux的区别

    我有一个需要导入到 MySQL 表的文件 这是我的命令 LOAD DATA LOCAL INFILE C test csv INTO TABLE logs fields terminated by LINES terminated BY n
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 如何在 Linux shell 中将十六进制转换为 ASCII 字符?

    假设我有一个字符串5a 这是 ASCII 字母的十六进制表示Z 我需要找到一个 Linux shell 命令 它将接受一个十六进制字符串并输出该十六进制字符串代表的 ASCII 字符 所以如果我这样做 echo 5a command im
  • PHP 从命令行启动 gui 程序,但 apache 不启动

    首先 我阅读了有类似问题的人的一些帖子 但所有答案都没有超出导出 DISPLAY 0 0 和 xauth cookies 这是我的问题 提前感谢您的宝贵时间 我开发了一个小库 它使用 OpenGL 和 GLSL 渲染货架 过去几天我将它包装
  • CentOS:无法安装 Chromium 浏览器

    我正在尝试在 centOS 6 i 中安装 chromium 以 root 用户身份运行以下命令 cd etc yum repos d wget http repos fedorapeople org repos spot chromium
  • 如何在shell中输出返回码?

    我正在尝试通过调用自定义 shell 脚本sh bin sh c myscript sh gt log txt 2 gt 1 echo 该命令的输出是创建的后台进程的 PID 我想指导 bin sh保存返回码myscript sh到某个文件
  • 使用 sh 运行 bash 脚本

    我有 bash 脚本 它需要 bash 另一个人尝试运行它 sh script name sh 它失败了 因为 sh 是他的发行版中 dash 的符号链接 ls la bin sh lrwxrwxrwx 1 root root 4 Aug
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • PHP 无法打开流:是一个目录

    非常简单的 PHP 脚本 我在我亲自设置的 Ubuntu Web 服务器上的 EE 模板中运行 我知道这与权限有关 并且我已经将我尝试写入的目录的所有者更改为 Apache 用户 我得到的错误是 遇到 PHP 错误 严重性 警告 消息 fi
  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • 如何确保应用程序在 Linux 上持续运行

    我试图确保脚本在开发服务器上保持运行 它会整理统计数据并提供网络服务 因此它应该会持续存在 但一天中有几次 它会因未知原因而消失 当我们注意到时 我们只需再次启动它 但这很麻烦 并且某些用户没有权限 或专有技术 来启动它 作为一名程序员 我
  • CoAP数据包的大小是多少?

    我是这项技术的新手 有人可以帮助我了解一些疑问吗 Q 1 CoAP数据包的大小是多少 我知道有 4 字节固定标头 但是包括标头 选项和负载在内的最大大小限制是多少 Q 2 有像MQTT那样的Keep Alive的概念吗 它在UDP上工作 它
  • “make install”将库安装在 /usr/lib 而不是 /usr/lib64

    我正在尝试在 64 位 CentOS 7 2 上构建并安装一个库 为了这个目的我正在跑步 cmake DCMAKE BUILD TYPE Release DCMAKE INSTALL PREFIX usr DCMAKE C COMPILER
  • 按进程名称过滤并记录 CPU 使用情况

    Linux 下有选项吗顶部命令 https www man7 org linux man pages man1 top 1 html我可以在哪里按名称过滤进程并将每秒该进程的 CPU 使用情况写入日志文件 top pgrep 过滤输出top
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • 监视目录的更改

    很像一个类似的问题 https stackoverflow com questions 112276 directory modification monitoring 我正在尝试监视 Linux 机器上的目录以添加新文件 并希望在这些新文
  • 子目录中的头文件(例如 gtk/gtk.h 与 gtk-2.0/gtk/gtk.h)

    我正在尝试使用 GTK 构建一个 hello world 其中包括以下行 include

随机推荐

  • 大学六年我读过的一些书

    明天就是2014年 xff0c 今晚是13年最后一天 xff0c 我想不少人会写一下自己13年的一些心得 xff0c 在上一篇日志中 xff0c 该总结的已经总结 xff0c 想了解更多的已经加我个人微信了 我在想 xff0c 大学其实很多
  • Ubuntu硬盘分区和分区文件类型及Linux分区基础知识

    Ubuntu硬盘分区的常用系统 xff0c 于是我学习研究了Ubuntu硬盘分区 xff0c 在这里对大家详细介绍下Ubuntu硬盘分区应用 xff0c 希望对大家有用Ubuntu硬盘分区包含了非常好的翻译和容易使用的架构 如果你准备在硬盘
  • 欢迎使用CSDN-markdown编辑器

    我和谁都不争 xff0c 和谁争 xff0c 我都不屑 xff1b 我爱大自然 xff0c 其次就是艺术 xff1b 我双手烤着生命之火取暖 xff0c 火萎了 xff0c 我也准备走了 本Markdown编辑器使用StackEdit修改而
  • 从高考到程序员--------你到底在追求着什么?------串烧似的文章

    随便写写 xff0c 万事开头难 xff0c 不知道应该怎么写出来 xff0c 因为只有自己经历的才能是最美好的回忆 xff0c 不知道这个编辑器写的效果如何 xff0c 凑合着看吧 xff0c 从高考到程序员征文看了几篇 xff0c 决定
  • AI以来----已不再是未来

    在过去的2016年 xff0c 可以说AI的核心发展一直围绕着开源和生态 国际巨头纷纷涉足深度学习平台 xff0c 让原本是高高在上的人工智能 xff0c 迅速落地细化到了行业的各个领域 随着AI技术正在为全球开发者所用 xff0c 极大的
  • 即兴嘻哈

    kao ha wowo 深夜不能寐 xff0c 那个翻来覆也不能把觉睡 一首即兴style说说ni为何半夜还不睡 xff0c 一线城市压力大来人总疲惫 xff0c 为了买房买车拼命都在向前奔 年轻人出来打拼为何这么累 xff0c 多少人为了
  • 裸辞3个月扛不住后,随便接了offer更惨!

    最近发现年底找工作的人不少 xff0c 部门里就2个hc xff0c 一周能收2000 43 简历 xff0c 这比例有点 过分 了 虽说大部分是年底先看看机会试试水 xff0c 准备年后冲击的 xff0c 但看简历里也有不少中间裸辞的 x
  • Docker常用命令-自用

    Docker常用命令 自用 1 镜像仓库 1 1 登陆 登出 登陆 默认Docker Hub docker login u span class token punctuation span username span class toke
  • 程序猿的浪漫之二进制表白篇

    那天情人节 xff0c 我给她发了一串数字 xff08 01001001 00100000 01101100 01101111 01110110 01100101 00100000 01111001 01101111 01110101 xf
  • Dockerfile使用教程

    第一 创建一个spring boot项目 第二 创建 xff1a Dockerfile文件 xff08 在项目根目录下 xff09 第三 复制粘贴 FROM java 8 设置基础镜像 FROM openjdk 8 jdk alpine 指
  • C++ 用socket封装成http

    HttpSocket h interface for the CHttpSocket class if defined AFX HTTPSOCKET H F49A8F82 A933 41A8 AF47 68FBCAC4ADA6 INCLUD
  • echarts更换主题

    前言 本篇文章基于上一篇文章 xff1a vue工程整合echarts xff0c 因此这里主要讲的是主题是如何配置的 xff0c 其他代码在这里不再赘述 官网中已经说明了echarts除了有默认的主题外 xff0c 并且内置了dark主题
  • Java final修饰符详解

    final 在 Java 中的意思是最终 xff0c 也可以称为完结器 xff0c 表示对象是最终形态的 xff0c 不可改变的意思 final 应用于类 方法和变量时意义是不同的 xff0c 但本质是一样的 xff0c 都表示不可改变 x
  • Python Requests库安装和使用

    Python 提供了多个用来编写爬虫程序的库 xff0c 除了前面已经介绍的 urllib 库之外 xff0c 还有一个很重的 Requests 库 xff0c 这个库的宗旨是 让 HTTP 服务于人类 Requests 是 Python
  • C语言 十进制转十六进制

    问题描述 十六进制数是在程序设计时经常要使用到的一种整数的表示方式 它有0 1 2 3 4 5 6 7 8 9 A B C D E F共16个符号 xff0c 分别表示十进制数的0至15 十六进制的计数方法是满16进1 xff0c 所以十进
  • Pandas知识点超全总结

    Pandas知识点超全总结 一 数据结构1 Series1 创建2 切片 修改3 其他属性 2 DataFrame1 创建2 切片3 增加 修改4 删除5 查看 二 读写数据1 读数据1 excel文件2 csv文件3 sql文件 2 写数
  • socket通信小结

    1 网络中的进程之间如何进行通信 区别于本地的进程间通信 xff0c 我们首要解决的问题是如何唯一标识一个进程 xff0c 否则通信无从谈起 xff01 在本地可以通过进程PID来唯一标识一个进程 xff0c 但是在网络中这是行不通的 其实
  • eclipse alt+/代码智能提示总是报错:problems during content assist

    解决办法如下图 xff1a 依次点击红框标记的地方即可解决问题
  • linux C 遍历目录及其子目录 opendir -> readdir -> closedir

    在 linux 下遍历某一目录下内容 LINUX 下历遍目录的方法一般是这样的 xff1a 打开目录 gt 读取 gt 关闭目录 相关函数是 opendir gt readdir gt closedir xff0c 其原型如下 xff1a
  • 最全面的 linux 信号量解析

    一 xff0e 什么是信号量 信号量的使用主要是用来保护共享资源 xff0c 使得资源在一个时刻只有一个进程 xff08 线程 xff09 所拥有 信号量的值为正的时候 xff0c 说明它空闲 所测试的线程可以锁定而使用它 若为 0 xff