IPC-信號量

- 信號量

信號量與之前的PIPE、FIFO以及消息隊列不同。它是一個計數器,用於爲多個進程提供對共享數據對象的訪問。信號量在負責數據操作的互斥、同步等功能。

學習信號量之前首先先來學習幾個基本概念:

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源,如“第一類讀寫者模型”。

臨界資源:多道程序系統中存在許多進程,它們共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱爲臨界資源。

臨界區:指的是一個訪問共用資源的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。

- 爲什麼要使用信號量

有時我們會出現多個程序同時訪問一個共享資源,而同時多道程序都要對共享資源進行操作,那麼這樣就會出問題。
例如:這裏的共享資源據說顯示器,父子進程向顯示器發送字符A、B

#include <stdio.h>
#include <unistd.h> 
#include <stdlib.h>
int main()
{
    pid_t id=fork();
    if(id==0)
//child
    {

        while(1)
        {
            usleep(10000);
            printf("A");
            fflush(stdout);
        }
    }
    else if(id>0)//father
    {
        while(1)
        {
            usleep(10000);
            printf("B");
            fflush(stdout);
        }
        wait(NULL);
    }
    else
    {
        printf("fork is faild\n");
    }

    return 0;
}

這裏寫圖片描述
可以看到打印出來的序列有AB也有AABB,信號量大致就是用來解決這種問題的。

對於這一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有一個執行線程訪問代碼的臨界區域。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。共享內存的使用就要用到信號量。

- 信號量的工作原理:

爲了獲得共享資源,進程需要執行下列操作:
1)測試控制該資源的信號量。
2)若此信號量爲正,則進程可以使用該資源。這種情況下,進程會將信號量值減1,表示使用了一個資源單位。
3)否則,若信號量爲0,則進程進入休眠狀態,直至信號量值大於0.進程唄喚醒後,返回步驟1。

爲了正確實現信號量,信號量值的測試及減1應當是原子操作。爲此信號量通常是在內核實現的。常用的信號量形式被稱爲二元信號量。它控制單個資源,其初始值爲1。

- 信號量的特點

  • 和消息隊列一樣。信號量是在內核中實現的,所以信號量聲明週期是隨內核的,當該進程結束信號量是沒有被摧毀的。
  • 信號量不是單個的非負值,而必須定義爲含有一個或多個信號量值的集合,需要給定信號量集合中信號量值的個數。
  • 信號量的創建是獨立於它的初始化的,這是一個致命的缺點,因爲不能原子的創建一個信號量集合,並對該集合中的各個信號量值就行賦值。

信號量的相關函數:

  1. ftok
 #include <sys/types.h>
 #include <sys/ipc.h>
 key_t ftok(const char *pathname, int proj_id);

用來產生提供產生一個鍵,每一個IPC對象都與一個鍵相關聯,這個鍵作爲該對象的外部名。
2. semget

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    int semget(key_t key, int nsems, int semflg);
    返回值:若成功,返回信號量ID;若出錯,返回-1

將key變爲標識符的規則,討論了是創建一個新集合,還是引用一個現有集合。創建一個新集合時,要對semid_ds結構的成員賦初值。
nsems是該信號量集合中的信號量數。如果是創建新集合,則必須制定nsems。如果引用現有集合,則將nsems設置爲0。
3. semctl

   #include <sys/types.h>
   #include <sys/ipc.h>
   #include <sys/sem.h>
   int semctl(int semid, int semnum, int cmd, ...);
   返回值:對於GETALL以外的所以GET命令,semctl函數都返回相應值。對於其他命令,若成功則返回0,出錯,則設置errno並返回-1

參數:
第四個參數是可選的,是否使用取決於所請求的命令,如果使用該參數,則類型是semun,它是多個命令特定參數的聯合(union):

union semun{
    int             val;   /*for SETVAL*/
    struct semid_ds *buf;  /for IPC_STAT and IPC_SET*/
    unsigned short  *array /*for GETALL and SETALL*/
}

注意,這個選項參數是一個聯合,而不能是指向聯合的指針。
cmd參數是選擇命令,根據操作目的選擇命令。

cmd命令 作用
SETVAL 設置semnum的semval值
IPC_RMID 從系統中刪除該信號量集合

4. semop

 #include <sys/sem.h>
 int semop(int semid, struct sembuf *sops, unsigned nsops);

semid是信號量ID。
sops是一個指針它指向一個有sembuf結構表示的信號量操作數組。如下:

struct sembuf{
    unsigned short sem_num;  /*表示進行操作的信號集合中的信號量下標*/
    short sem_op;            /*表示操作方式*/   
    short sem_flg;           /*IPC_NOWAIT,SEM_UNDO*/
}

npos表示信號量集合當中信號量的個數。
sem_flg:信號操作標誌,可能的選擇有兩種
IPC_NOWAIT //對信號的操作不能滿足時,semop()不會阻塞,並立即返回,同時設定錯誤信息。
SEM_UNDO //程序結束時(不論正常或不正常),保證信號值會被重設爲semop()調用前的值。這樣做的目的在於避免程序在異常情況下結束時未將鎖定的資源解鎖,造成該資源永遠鎖定。

信號量實例:

/**********************************************************************
    > File Name: sem.c
    > Author: 
    > Mail: 
    > Created Time: Mon 12 Jun 2017 01:00:25 AM PDT ************************************************************************/
#include <stdio.h>
#include <unistd.h> 
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <string.h>
#include <errno.h>
#define KEY_PATH "."
#define PROJECT_ID 88
/*struct sembuf{
    unsigned short sem_num;
    short sem_op;
    short sem_flg;
};*/
static int comm_sem(int flag,int nsem)
{
    key_t semkey=ftok(KEY_PATH,PROJECT_ID);
    if(semkey<0)
    {
        printf("ftok is faild\n");
    }
    return semget(semkey,nsem,flag);
}
int create_sem(int _semset_num)
{
    return comm_sem(IPC_CREAT|0666,_semset_num);
}
int init_sem(int _sem_id,int _which)
{
    union semun{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
    };
    union semun sem_un;
    sem_un.val=1;
    return semctl(_sem_id,_which,SETVAL,sem_un);
}
int get_sem()
{
    return comm_sem(IPC_CREAT,0);
}
static int op_sem(int sem_id,int which,int op)
{
    struct sembuf sem_buf;
    memset(&sem_buf,0,sizeof(sem_buf));
    sem_buf.sem_num=which;
    sem_buf.sem_op=op;
    sem_buf.sem_flg=0;
    return semop(sem_id,&sem_buf,1);
}
int sem_p(int sem_id,int which)
{
   return  op_sem(sem_id,which,-1);   
}
int sem_v(int sem_id,int which)
{
    return op_sem(sem_id,which,1);
}
int destory_sem(int _sem_id)
{
   return semctl(_sem_id,0,IPC_RMID);
}
int main()
{
    int sem_id=create_sem(1); 
    printf("semid:%d",sem_id);
    init_sem(sem_id,0);
    pid_t id=fork();
    if(id==0)
    {
        //child
        while(1)
        {
            sem_p(sem_id,0);
            usleep(10000)
            printf("A");
            fflush(stdout);
            usleep(2000);
            printf("A");
            fflush(stdout);
            sem_v(sem_id,0);
        }
    }
    else if(id>0)//father
    {
        while(1)
        {
            sem_p(sem_id,0);
            usleep(10000);
            printf("B");
            fflush(stdout);
            usleep(2000);
            printf("B");
            fflush(stdout);
            sem_v(sem_id,0);
        }
        wait(NULL);
    }
    else
    {
        printf("fork is faild\n");
    } 
    destory_sem(sem_id);
    return 0;

}

這裏寫圖片描述
加入信號量之後發現,AABB成對出現,所以達到了互斥的目的。

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