Linux信号量 | 进程间通信—信号量——详解

目录

一.信号量的概念

二.信号量的使用

三.信号量的具体应用

四.总结


一.信号量的概念

1.信号量的定义

为了防止出现因多个程序同时访问临界资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对临界资源的访问的。

  • 临界资源:指同一时刻,只允许一个进程(或线程)访问的资源
  • 临界区:指访问临界资源的代码段

例如:通常我们上公共卫生间时都会有一个提示灯,如果提示灯的颜色是绿色的则代表此时卫生间没有人在使用,因此任意一个人都可以来使用卫生间,但如果此时卫生间的提示灯是红色的,则代表此时卫生间正在被其他人使用,如果有其他人想要使用卫生间,则这个人需要等待前一个人使用完卫生间。

2.对信号量的操作

我们对信号量只可以进行两种操作,p操作v操作

  • p操作:当信号量值为0时,进程阻塞;当信号量值大于0时,信号量减1,进程获得资源继续运行。
  • v操作:将信号量值加1,不会发生阻塞,v操作代表释放资源

例子:如果此时有两个进程a和b要访问某一临界资源,用一个信号量来协调这两个进程对这一临界资源的访问。假设信号量的初始值为1,若此时进程a要对临界资源进行访问,则a进程首先会获取此时信号量的值,而此时信号量的值为1,则a进程访问临界资源,且同时对信号量进行p操作,信号量的值变为0。

若此时进程b也要访问临界资源,则它首先会获取信号量的值,而此时信号量的值为0,则进程b无法访问临界资源,接着进程b进入阻塞状态。

最后,进程a访问完临界资源后,对信号量进行v操作,信号量的值变为1,接着进程b退出阻塞状态,开始对临界资源进行访问,且同时对信号量进行p操作,此时信号量的值为0。


问题一

在上述例子中,如果进程a在访问临界资源且对信号量进行p操作时(此时p操作还没有完成,说明此时信号量的值还为1),进程b也要对临界资源进行访问怎么办?

答案是肯定的,就是进程b不会被允许访问临界资源。原因是信号量是一种特殊的变量,访问具有原子性

  • 原子操作:指该操作绝不会在执行完毕前被任何其他任务或事件打断,也就是说,它是最小的执行单位,不能有比它更小的执行单元。

也就是说,在进程a完成对信号量的p操作以前,其他任何进程都无法访问此信号量。


3.信号量的类型

  • 计数信号量:计数信号量的初始值其实就是共享资源的数量。有几个进程访问,信号量就减去几,直到再次没有资源可以使用。及信号量的初始值不为1。
  • 二值信号量:二值信号量可以理解成计数信号量的一种特殊形式,即初始化为仅有一个资源可以使用,当临界资源被获取了,信号量值就是 0,临界资源被释放,信号量值就是 1,把这种只有 0 和 1 两种情况的信号量称之为二值信号量。及信号量的初始值为1。

二.信号量的使用

1.头文件

<sys/sem.h>


2.semget函数

semget函数的作用是创建一个新信号量或获取一个已有信号量的键。

#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);

参数

  • key:信号量键值,可以理解为信号量的唯一性标记。不相关的进程可以通过它访问一个同一个信号量。例如上述例子中,进程a和b怎么知道在那么多的信号量当中,他们二者访问的是同一个信号量呢?就是通过这个key值。
  • num_sems:信号量的数目,一般为 1
  • sem_flags:有两个值,IPC_CREATEIPC_EXCLIPC_CREATE表示若信号量已存在,返回该信号量标识符。IPC_EXCL表示若信号量已存在,返回错误。

返回值

成功则返回相应的信号量标识符,失败返回-1


3.semop函数

semop函数用于改变信号量的值

#include <sys/sem.h>

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

参数

  • sem_id:由semget返回的信号量标识符;
  • sem_opa:表示一个由sembuf结构表示的信号量操作数组;
  • num_sem_ops:规定该数组中操作的数量。

sembuf(信号量操作数组)

struct sembuf{
	// 除非使用一组信号量,否则它为0,一般从0,1,...num_secs-1
	unsigned short	sem_num; 
	// 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,
	// 即P(等待)操作,一个是+1,即V(发送信号)操作。
	short 			sem_op;
	// 通常为SEM_UNDO,使操作系统跟踪信号,
	// 并在进程没有释放该信号量而终止时,操作系统释放信号量。
	short  			sem_flg;
}

 

返回值

成功返回0,失败返回-1


4.semctl函数

semctl用来直接控制信号量信息,常用来进行信号量的初始化和销毁。

#include <sys/sem.h>

int semctl(int sem_id, int sem_num, int command, [union semun sem_union]);

参数:

  • sem_id:信号量标识符;
  • sem_num:信号量的值,一般取值为0;
  • command:通常是下面两个值中的其中一个:
    •     SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置;(初始化信号量
    •     IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。(删除信号量
  • sem_union:可选参数,是一个union semun结构,它至少包含以下几个成员:
union semun{
	int 			val;
	struct semid_ds *buf;
	unsigned short  *array;
}

 一般用到的是val,表示要传给信号量的初始值。注意:semun联合结构必须由程序员自己定义

返回值

成功返回0,失败返回-1。


三.信号量的具体应用

sem.h

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>

void sem_init();

void sem_p();

void sem_v();

void sem_destroy();

sem.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>
#include "sem.h"
union semun // man semctl
{
    int val;
};

static int sem_id;
void sem_init()
{
    sem_id = semget((key_t)1234, 1, IPC_CREAT | IPC_EXCL | 0600); // 尝试创建一个信号量
    if(sem_id == -1) // 信号量已存在或者创建失败
    {
        sem_id = semget((key_t)1234, 1, 0600); // 获取已有信号量的id
        if(sem_id == -1) // 判断是否成功获得信号量id
        {
            perror("semget erorr");
            return;
        }
    }
    else
    {
        union semun a;
        a.val = 1;

        if(semctl(sem_id, 0, SETVAL, a) == -1) // 信号量赋初值
        {
            perror("semctl error");
        }
    }
}

void sem_p()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = -1; // p操作
    buf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &buf, 1) == -1)
    {
        perror("semop error");
    }
}

void sem_v()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = 1; // v操作
    buf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &buf, 1) == -1)
    {
        perror("semop error");
    }
}

void sem_destroy()
{
    if(semctl(sem_id, 0, IPC_RMID) == -1)
    {
        perror("semctl error");
    }
}

a.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "sem.h"

int main(int argc, char *argv[])
{
    sem_init();
    int i = 0;
    for(; i < 5; i++)
    {
        sem_p();
    
        printf("A");
        fflush(stdout);
        
        int n = rand() % 3;
        sleep(n);

        printf("A");
        fflush(stdout);
        
        n = rand() % 3;
        sleep(n);

        sem_v();
    }

    exit(0);
}

b.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "sem.h"

int main(int argc, char *argv[])
{
    sem_init();
    int i = 0;
    for(; i < 5; i++)
    {
        sem_p();
    
        printf("B");
        fflush(stdout);
        
        int n = rand() % 3;
        sleep(n);

        printf("B");
        fflush(stdout);
        
        sem_v();
        
        n = rand() % 3;
        sleep(n);
    }

    sleep(10);
    sem_destroy();
    exit(0);
}

 

接着执行命令:

运行结果:


四.总结

优点:可以同步进程

缺点:信号量的数目有限

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值