天天看點

【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資源。

【Linux】程式間通信——信号量

  這裡需要注意的是:信号量與信号并不一樣,有的同學可能會把這兩個概念搞混了,信号量是用來作業系統程序間同步通路共享資源。信号是用來通知程序發生了異步事件,雖然二者名字上很相似,但本質上相差很大。

  

3.執行個體

下面我們就用一段很經典的代碼使用這些函數:

先寫一個Makefile

sem_test:sem_test.c comm.c
    gcc -o [email protected] $^
.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", x6666);
    if(key < )
    {
        perror("ftok");
        return -;
    }
    int semid = semget(key, nums, flags);
    if(semid < )
    {
        perror("semget");
        return -;
    }
    return semid;
}
int createSemSet(int nums)
{
    return commSemSet(nums, IPC_CREAT|IPC_EXCL|666);
}
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) < )
    {
        perror("semctl");
        return -;
    }
    return ;
}
static int commPV(int semid, int who, int op)
{
    struct sembuf _sf;
    _sf.sem_num = who;
    _sf.sem_op = op;
    _sf.sem_flg = ;
    if(semop(semid, &_sf, ) < )
    {
        perror("semop");
        return -;
    }
    return ;
}
int P(int semid, int who)
{
    return commPV(semid, who, -);
}
int V(int semid, int who)
{
    return commPV(semid, who, );
}
int destorySemSet(int semid)
{
    if(semctl(semid, , IPC_RMID) < )
    {
        perror("semctl");
        return -;
    }
    return ;
}
           

test.c

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

看上面的測試程式,使用fork()建立了兩個程序,但是兩個程序交替執行,是以如果沒有使用PV操作的話,列印出來的是醬紫的:

【Linux】程式間通信——信号量

但是如果我們加了PV操作,這樣就可以保證A和B是成都存在的了

【Linux】程式間通信——信号量

繼續閱讀