【Linux】進程間通信 -- 信號量

信號量的相關概念:

信號量

    信號量本質上是一個計數器(不設置全局變量是因爲進程間是相互獨立的,而這不一定能看到,看到也不能保證++引用計數爲原子操作),用於多進程對共享數據對象的讀取,它和管道有所不同,它不以傳送數據爲主要目的,它主要是用來保護共享資源(信號量也屬於臨界資源),使得資源在一個時刻只有一個進程獨享。

在瞭解信號量之前,我們先來看幾個概念:

臨界資源:兩個進程看到的同一個公共的資源,但是同時只能被一個進程所使用的的資源叫做臨界資源(互斥資源)

臨界區:在晉城中涉及到互斥資源的程序段叫臨界區

信號量主要用於同步和互斥,下面我們來看看什麼是同步和互斥。

互斥:各個進程都要訪問共享資源,但共享資源是互斥的,同時只能有一個進程使用。因此,各個進程之間競爭使用這些資源,將這種關係稱爲互斥。

同步:多個進程需要相互配合共同完成一項任務。

信號量的工作機制

    簡單說一下信號量的工作機制,可以直接理解成計數器,信號量會有初值(>0),每當有進程申請使用信號量,通過一個P操作來對信號量進行-1操作,當計數器減到0的時候就說明沒有資源了,其他進程要想訪問就必須等待,當該進程執行完這段工作(我們稱之爲臨界區)之後,就會執行V操作來對信號量進行+1操作。

由於信號量只能進行兩種操作等待和發送信號,即 P 和 V ,他們的行爲是這樣的:(進程共享信號量sv) 
P:如果sv的值大於零,就給它減1;如果它的值爲零,就掛起該進程的執行 

V:如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就+1。

在信號量進行PV操作時都爲原子操作(因爲它需要保護臨界資源)

信號量的相關函數:

信號量結構體

struct semaphore  
{  
    int value;  
    pointer_PCB queue;  
}  

P原語

P(s)  
{  
    s.value = s.value--;  
    if (s.value < 0)  
    {  
        該進程狀態置爲等待狀態  
        將該進程的PCB插入相應的等待隊列s.queue末尾  
    }  
}  
V原語
V(s)  
{  
    s.value = s.value++;  
    if (s.value < =0)  
    {  
        喚醒相應等待隊列s.queue中等待的一個進程  
        改變其狀態爲就緒態  
        並將其插入就緒隊列  
    }  
}  

信號量集結構

struct semid_ds {  
    struct ipc_perm sem_perm; /* Ownership and permissions */  
    time_t sem_otime; /* Last semop time */  
    time_t sem_ctime; /* Last change time */  
    unsigned short sem_nsems; /* No. of semaphores in set */  
};  

信號集函數:

semget函數:

功能:⽤用來創建和訪問⼀一個信號量集
原型
int semget(key_t key, int nsems, int semflg);
參數
key: 信號集的名字
nsems:信號集中信號量的個數
semflg: 由九個權限標誌構成,它們的⽤用法和創建⽂文件時使⽤用的mode模式標誌是⼀一樣的
返回值:成功返回⼀一個⾮非負整數,即該信號集的標識碼;失敗返回-1

shmctl函數:

功能:⽤用於控制信號量集
原型
int semctl(int semid, int semnum, int cmd, ...);
參數
semid:由semget返回的信號集標識碼
semnum:信號集中信號量的序號
cmd:將要採取的動作(有三個可取值)
最後⼀一個參數根據命令不同⽽而不同
返回值:成功返回0;失敗返回-1

semop函數:

功能:⽤用來創建和訪問⼀一個信號量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
參數
semid:是該信號量的標識碼,也就是semget函數的返回值
sops:是個指向⼀一個結構數值的指針
nsops:信號量的個數
返回值:成功返回0;失敗返回-1

說明:

sembuf結構體:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信號量的編號。
sem_op是信號量⼀一次PV操作時加減的數值,⼀一般只會⽤用到兩個值:
⼀一個是“-1”,也就是P操作,等待信號量變得可⽤用;
另⼀一個是“+1”,也就是我們的V操作,發出信號量已經變得可⽤用
sem_flag的兩個取值是IPC_NOWAIT或SEM_UNDO

同消息隊列類似,這裏也可以使用ipcs -s查看IPC資源,使用ipcrm -s刪除IPC資源:


信號量實例:

應用以上函數,編寫一段代碼:

創建Makefile:

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

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_sem.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;
}

未使用PV操作時結果:


加入PV操作時的結果:


       我們可以看到所有的AB都是成對出現的,不會出現交叉的情況。這是因爲P、V操作實現過程中具有原子性,能夠實現對臨界區的管理,它的執行是不會受其他進程的影響。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章