linux進程間通信之信號量

1 信號量

信號量與管道和信號不同,它是一個計數器,用於多進程對共享數據對象的訪問,通常與共享內存相結合使用。一般在多任務環境系統下,多個進程會同時運行,特別是在多核cpu的情況下,並且有些進程相互之間可能存在一定關聯。多個進程可能爲了完成同一個任務相互協作,這樣形成進程之間的同步關係。而且在不同進程之間,爲了爭奪有限的系統資源(硬件或軟件資源)會進入競爭狀態,這就是進程之間的互斥關係。
進程間的互斥和同步關係存在根源在與臨界資源。臨界資源是在同一個時刻只允許有限個(通常只有一個)進程可以訪問或修改資源。通常包括硬件資源(處理器,內存,存儲器以及其他處於外圍設備)和軟件資源(共享代碼,共享結構和變量等)。訪問臨界資源的代碼叫做臨界區,臨界區本身也會成爲臨界資源
信號量可以用來解決進程之間的同步與互斥問題。通常爲了獲得共享資源,進行需要執行下列操作:
1:建立初始化控制該資源的信號量(semget(), semctl()函數)
2:若信號量的值爲正,則進程可以使用該資源,進程將信號量值減爲1,通常稱爲P操作
3:若信號量的值爲0,則進程進入休眠狀態,直到值大於零
4:若使用完該資源後,則釋放該資源,將信號量值加1,通常稱爲V操作。

使用信號量臨界區的僞代碼可以表示爲:

INIT_VAL(s) /*初始化信號量*、
非臨界區
P(S) /*P操作 -1*/
臨界區
V(S) /*V操作 +1*/
非臨界區

通常使用信號量只取0和1, 被稱爲二維信號量。

2: 信號量主要函數

新建或獲取信號量函數:
int semget(key_t key, int nsems, int flag)
參數:
Key:信號量的鍵值,多個進程可以通過它訪問同一個信號量,其中有個特殊值IPC_PRIVATE。用於創建當前進程的 私有信號量
nsems: 創建信號量數目,通常爲1.
semflag:同open()函數的權限位,也可以用八進制表示法,其中使用IPC_CREATE標誌創建新的信號量,即使該信號量已經存在,也不會出錯。如果同時使用IPC_EXECL標誌可以創建唯一一個新 的信號量,此時如果該信號量已經存在,該函數會返回出錯。

信號量操作函數:
int semctl(int semid, int senum, int cmd, union semun arg)
參數:
semid: semget()函數返回的信號量標識符
semnum, 信號量編號,當使用信號量集時纔會被用到。通常取值爲0,就是使用單個信號量
cmd: 對信號量的各種操作,通常使用一下幾種:
IPC_STAT:獲得該信號量的semid_ds結構,並存放在由第4個參數arg的buf指向的semid_ds結構中。semid_ds是在系統中描述信號量的數據結構
IPC_SETVL:將信號量值設置爲arg的val值
IPC_GETVAL:返回信號量的當前值
IPC_RMID:從系統中,刪除信號量(或者信號量集)
arg:是unio semnn結構
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}

實現PV操作重要函數semop:
int semop(int semid, struct sembuf *sops, size_t nsops)
參數:
semid: semget()函數返回的信號量標識符。
sops:指向信號量操作數組,
struct sembuf
{
short sem_num; /* 信號量編號,使用單個信號量時,通常取值爲 0 */
short sem_op;
/* 信號量操作:取值爲-1 則表示 P 操作,取值爲+1 則表示 V 操作*/
short sem_flg;
/* 通常設置爲 SEM_UNDO。這樣在進程沒釋放信號量而退出時,系統自動
釋放該進程中未釋放的信號量 */
}

3:實驗用例

下面用一個例子來現實信號量對進程間同步的操作,參考《linux應用程序開發標準教程》中:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};


int init_sem(int sem_id, int init_value)
{
    union semun sem_union;

    sem_union.val = init_value;
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
    {
        perror("Initialize semaphore");
        return -1;
    }

    return 0;
}

int del_sem(int sem_id)
{
    union semun sem_union;

    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    {
        perror("Delete semaphore");
        return -1;
    }

}


int sem_p(int sem_id)
{
    struct sembuf sem_b;

    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sem_b, 1) == -1)
    {
        perror("P operation fail");
        return -1;
    }

    return 0;
    }

int sem_v(int sem_id)
{
    struct sembuf sem_b;

    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;

    if (semop(sem_id, &sem_b, 1) == -1)
    {
        perror("V operation");
        return -1;
    }

    return 0;
}

int main(void)
{
    pid_t result;
    int sem_id;
   /* create sem*/
    sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT);

    /* init sem */
    init_sem(sem_id, 0);

    result = fork();
    if (result == -1)
    {
        perror("Fork\n");
    }
    else if(result == 0)
    {
        printf("Child process will wait for some seconds... \n");
        sleep(3);
        printf("The returned value is %d in the child process(PID = %d)\n", result, getpid());
        sem_v(sem_id);
    } else
    {
        sem_p(sem_id);
        printf("The return value is %d in the father process(PID = %d)\n", result, getpid());
        sem_v(sem_id);
    }
    exit(0);
}

運行結果:
root@baohua-VirtualBox:/repo/training/fork# ./a.out
Child process will wait for some seconds…
The returned value is 0 in the child process(PID = 4203)
The return value is 4203 in the father process(PID = 4202)

以上實驗代碼github地址:https://github.com/zhikunhuo/training.git

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