【Linux】进程间通信——信号量

1. 信号量的基本概念

信号量主要用于同步与互斥的,先简单的说几个概念吧


  • 原子性:表示一个事件的两种状态,要么做了,要么没有做,没有第三种状态;
  • 同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同相互制约的关系;
  • 互斥:在一段时间内,资源只允许被一个进程访问;
  • 临界资源:像打印机这类一次只允许一个进程使用的资源;
  • 临界区:多个进程访问临界资源的那一段代码。

  那么什么是信号量呢,我们可以简单的理解成计数器(当然实际上并没有那么简单)描述资源的多少,它本身不具备数据交换的功能,而是通过控制临界资源来实现进程间通信。
  简单说下信号量的工作机制,我么说信号量可以理解为一个计数器,它有一个初值(>0),每当有进程申请使用信号量时,通过P操作来对信号量进行-1;当计数器减到0的时候,其他进程想要访问资源,就需要挂起等待,直到该进程执行完操作,通过V操作对信号量+1释放资源。所以,我们说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是二元信号量,一个进程在使用互斥锁时,通知其他进程,阻止他们的访问挂起等。举一个例子:假如我们一趟火车只有一张火车票了,但是有两个人都要买,其中一个人先申请到了资源,买到了票,那么另一人只能等,等到如果有人退票或者改签,他就可以买到票了。
  

2.信号量的操作函数

信号量的操作函数与消息队列也是十分类似的:

semget
  • 功能:用来创建和访问一个信号
  • 原型:
int semget(key_t key, int nsems, int semflg);
  • 参数:
    • key:信号集的名字,与消息队列类似,不在详细的说了
    • nsems:信号集中信号量的个数,我们使用semget这个函数创建的是一个信号集,包括了好多信号量的, 当然可以选择只创建一个
    • semflg: 两个参数IPC_CREAT和IPC_EXCL,与之前的消息队列也类似-
  • 返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1
semctl
  • 功能:⽤用于控制信号量集
  • 原型:
int semctl(int semid, int semnum, int cmd, ...);
  • 参数:
    • semid:由semget返回的信号集标识码
    • semnum:信号集中信号量的序号,序号从0开始
    • cmd:将要采取的动作(有三个可取值)
    • 最后⼀一个参数根据命令不同⽽而不同
  • 返回值:成功返回0;失败返回-1

      这个函数,我们初始化和删除都会用到它,当第三个参数为IPC_RMID时,用于删除信号集;当第三个参数设置为SETVAL时,可以用于信号量的初始化,但此时就需要第四个参数了;第四个参数是这样的,它需要加入一个联合体:

union semun
{
    int val; /* Value for SETVAL */
    struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
    unsigned short *array; /* Array for GETALL, SETALL */
    struct seminfo *__buf; /* Buffer for IPC_INFO */
}
semop
  • 功能:⽤来实现PV操作的,这个函数还用得到了一个结构体
  • 原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 参数:
    • semid:是该信号量的标识码,semget函数的返回值
    • sops:指向一个结构体的指针
    • nsops:信号量的个数
  • 返回值:成功返回0;失败返回-1

这个结构体是这样的:

struct sembuf 
{
    short sem_num;//sem_num是信号量的编号
    short sem_op;//sem_op是信号量⼀次PV操作时加减的数值,一般只会⽤用到两个值:一个是“-1”,也就是P操作,等待信号量变得可⽤;另⼀个是“+1”,也就是我们的V操作,发出信号量已经变得可⽤
    short sem_flg;//sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO
};

同消息队列类似,这里也可以使用ipcs -s查看IPC资源,使用ipcrm -s删除IPC资源。
这里写图片描述

  这里需要注意的是:信号量与信号并不一样,有的同学可能会把这两个概念搞混了,信号量是用来操作系统进程间同步访问共享资源。信号是用来通知进程发生了异步事件,虽然二者名字上很相似,但本质上相差很大。
  

3.实例

下面我们就用一段很经典的代码使用这些函数:
先写一个Makefile

sem_test:sem_test.c comm.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f sem_test

comm.h

#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
    int   val;    /* value for setval */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};
int createSemSet(int nums);
int initSem(int semid, int nums, int initval);
int getSemSet(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destorySemSet(int semid);
#endif

comm.c

#include "comm.h"
static int commSemSet(int nums, int flags)
{
    key_t key = ftok("/tmp", 0x6666);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int semid = semget(key, nums, flags);
    if(semid < 0)
    {
        perror("semget");
        return -2;
    }
    return semid;
}
int createSemSet(int nums)
{
    return commSemSet(nums, IPC_CREAT|IPC_EXCL|0666);
}
int getSemSet(int nums)
{
    return commSemSet(nums, IPC_CREAT);
}
int initSemSet(int semid, int nums, int initval)
{
    union semun _un;
    _un.val = initval;
    if(semctl(semid, nums, SETVAL, _un) < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}
static int commPV(int semid, int who, int op)
{
    struct sembuf _sf;
    _sf.sem_num = who;
    _sf.sem_op = op;
    _sf.sem_flg = 0;
    if(semop(semid, &_sf, 1) < 0)
    {
        perror("semop");
        return -1;
    }
    return 0;
}
int P(int semid, int who)
{
    return commPV(semid, who, -1);
}
int V(int semid, int who)
{
    return commPV(semid, who, 1);
}
int destorySemSet(int semid)
{
    if(semctl(semid, 0, IPC_RMID) < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}

test.c

#include "comm.h"
int main()
{
    int semid = createSemSet(1);
    initSemSet(semid, 0, 1);
    pid_t pid = fork();
    if(pid == 0)
    {
        //child
        int _semid = getSemSet(0);
        while(1)
        {
            P(_semid, 0);
            printf("A");
            fflush(stdout);
            usleep(123456);
            printf("A ");
            fflush(stdout);
            usleep(123456);
            V(_semid, 0);
        }
    }
    else
    {
        //parent
        while(1)
        {
            P(semid, 0);
            printf("B");
            fflush(stdout);
            usleep(123456);
            printf("B ");
            fflush(stdout);
            usleep(123456);
            V(semid, 0);
        }
        wait(NULL);
    }
    destorySemSet(semid);
    return 0;
}

看上面的测试程序,使用fork()创建了两个进程,但是两个进程交替执行,所以如果没有使用PV操作的话,打印出来的是酱紫的:
这里写图片描述
但是如果我们加了PV操作,这样就可以保证A和B是成都存在的了
这里写图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值