信号量是一个计数器,用于为多个进程提供对共享数据对象的访问。
在信号量上只有三种操作可以进行,初始化、递增和增加,这三种操作都是原子操作。递减操作可以用于阻塞一个进程,增加操作用于解除阻塞一个进程。
为了获得共享资源,需要测试信号量,若信号量为正,则进程可以使用该资源,这时信号量值减一。否则信号量值为0,进程进入休眠状态。当进程不再使用由一个信号量控制的共享资源时,信号量值加一。如果有正在休眠的进程,则唤醒它们。常用的信号量的形式为二元信号量。即是原子性的。
信号量的功能:负责数据操作的互斥、同步等功能。本质上是一种数据操作锁。
我们为什么要使用信号量呢?为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使⽤用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
信号量只能进行两种操作等待和发送信号,PV操作,即P(sv)和V(sv),P申请资源,则将可用资源数-1,V释放资源,则将可用资源数+1。
内核为每个信号量集合维护着一个semid_ds结构:
struct semid_ds
{
    struct ipc_perm sem_perm;
    unsigned short sem_nsems; //该集合的信号量数目
    time_t sem_otime;
    time_t sem_ctime;
};
每个信号量都有一个无名的结构:
unsigned short  semval;   /* semaphore value */
  unsigned short  semzcnt;  /* # waiting for zero */
  unsigned short  semncnt;  /* # waiting for increase */
  pid_t           sempid;   /* process that did last op */
当我们想使用信号量时,首先要通过调用函数semget来获得一个信号量ID:
#include<sys/sem.h>
int semget(key_t key,int nsems,int flag);
//成功,返回信号量ID,出错返回-1
semctl函数包含了多种信号量操作
int semctl(int semid,int semnum,int cmd,.../* union semun arg */);
//第四个参数是可选的,取决于请求的命令,如果使用该参数,则其类型是semun,它是多个命令特定的联合。
union semun
{
    int val;       //for SETVAL;
    struct semid_ds *buf;     //for IPC_STAT and IPC_SET;
    unsigned short *array;    //for GETALL and SETALL
};
我们通常使用的:IPC_RMID:从系统中删除该信号量集合
SETVAL:设置成员semnum的semval值,该值由arg.val指定
函数semop自动执行信号量集合上的操作数组。
int semop(int semid,struct sembuf semoparray[],size_t nops);
//成功,返回0,失败,返回-1;semoparray是一个指针,指向由sembuf结构表示的信号量操作数组,nops规定该数组中操作的数量
struct sembuf
{
    unsigned short sem_num;   //0,1,2,3,....
    short sem_op;  //(负值,0,正值)-1,0,1
    short sem_flg;  //IPC_NOWAIT,SEM_UNDO
};
对集合中每个成员的操作由相应的sem_op值规定。此值可以为负值,0,正值。最易于处理的是为正值,说明需要释放资源,则sem_op的值会加到信号量值上,如果指定了undo标志,则从此信号量调整之后的值上减去sem_op;如果sem_op是负值,说明需要申请资源,则信号量会减去sem_op的绝对值,如果指定undo标志,则sem_op的绝对值也加到信号量的调整值上。sem_op为0,表示调用进程希望等待到该信号量值为0。
对于信号量调整,如果在进程终止时,它占用了经由信号量分配的资源,那么就会成为一个问题。无论何时只要为信号量操作指定了SEM_UNDO标志,然后分配资源(sem_op< 0),那么内核就会记住该特定信号量,分配给调用进程多少资源。对每个操作都指定SEM_UNDO,以处理在未释放资源条件下进程终止的情况。
信号量主要解决互斥与同步问题,下面举个栗子:(实现父子进程输出成对AA或BB)
//comm.h
#ifndef _COMM_H_
#define _COMM_H_


#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>

#define PATHNAME "."
#define PROJ_ID 0X6666
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;  
        };



int CreateSem(int nums);
int  GetSem(int nums);
int DestroySem(int semid);
int initSem(int semid,int nums,int initval);
int SemP(int semid,int who);
int SemV(int semid,int who);


#endif  //_COMM_H_
#include"comm.h"

static int CommSemSet(int nsems,int flags)
{
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key < 0)
    {
        //printf("%d\n", key);
        perror("ftok");
        return -1;
    }
    int semid = semget(key,nsems,flags);
    if(semid < 0)
    {
        perror("semget");
        return -2;
    }
    return semid;
}

int  CreateSemSet(int nums)
{
    return CommSemSet(nums,IPC_CREAT|IPC_EXCL|0666);
}

int GetSem(int nums)
{
    return CommSemSet(nums,IPC_CREAT);
}
int DestroySem(int semid)
{
    if(semctl(semid,0,IPC_RMID) < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}

int initSem(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 _sem;
    _sem.sem_num = who;
    _sem.sem_op = op;
    _sem.sem_flg = 0;

    if(semop(semid,&_sem,1) < 0)
    {
        perror("semop");
        return -1;
    }   
    return 0;
}
int SemP(int semid,int who)
{
    return CommPV(semid,who,-1);
}
int SemV(int semid,int who)
{
    return CommPV(semid,who,1);
}
//sem.c
#include"comm.h"

int main()
{
    int semid = CreateSemSet(1);
    initSem(semid,0,1); 
    pid_t id = fork();
    if(id == 0)//child
    {
        int _semid = GetSem(0);
        while(1){
        SemP(_semid,0);
        printf("A");
        fflush(stdout);
        usleep(123456);
        printf("A");
        fflush(stdout);
        usleep(345678); 
        SemV(_semid,0);
        }
    }
    else
    {
        while(1){
        SemP(semid,0);
        printf("B");
        fflush(stdout);
        usleep(234567);
        printf("B");
        fflush(stdout);
        usleep(456789);
    //  usleep(121212);
        SemV(semid,0);
        }
        wait(NULL);
    }
        DestroySem(semid);
    printf("sem quit!\n");
    return 0;
}
//Makefile
sem:sem.c comm.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f sem
运行结果:

shell sem信号量 linux中的信号量_shell sem信号量