Linux(高級編程)9————進程間通信5(信號量)

信號量是是什麼?
信號量是進程間通信方式之一,用來實現進程間的同步與互斥。信號量的原理是一種數據操作鎖的概念,它本身不具備數據交換的功能,而是通過控制其他通信資源(如文本、外部設備等)來實現進程間通信。信號量本身不具備數據傳輸的功能,他只是一種外部資源的標識。
信號量的本質是:具有等待隊列的計數器。
相關概念介紹:
臨界資源:多個進程或線程可以共享的資源。
臨界區:對臨界資源進行操作的代碼。
同步:訪問臨界資源的時序可控性。
互斥:同一時間訪問臨界資源的唯一性。
爲了獲得共享資源,進程需要執行以下步驟:
1.測試控制該資源的信號量
2.若此信號爲正,當進程對信號量做減1操作時,表明它使用了一個單位的資源。
3.如此信號量的值爲0,則進程進入休眠狀態,直至信號量值大於0時。進程被喚醒後再執行第1步。
4.當進程不再使用該信號量控制的共享資源時,該信號值增加1.如果此時有等待此信號量的進程,則喚醒它。
注意:常用的信號量形式被稱爲二元信號量或雙態信號量。它控制單個資源,初值爲1。其實信號量初值可以爲任一正值,該值說明有多少個共享資源單位可供使用。信號量是對臨界資源的一種保護機制,所以對它操作是不能被打斷的即原子性,而這些都是內核幫我們做好的。
信號量的操作步驟:

  • 信號量創建:
int semget(key_t key, int nsems, int semflg);

參數:
key:操作系統對該信號量的標識。
nsems:創建該信號量集合中的信號量數。
semflg:IPC_CREAT、IPC_EXCL等(創建標記)。
返回值:成功返回信號量操作句柄semid;失敗返回-1,並設置errno。

  • 信號量的控制:
 int semctl(int semid, int semnum, int cmd, ...);

參數:
semid:信號量ID
semnum:只要操作的第幾個信號量。
cmd:爲具體所要進行的操作,常用的操作:SETVAL設置單個信號量的初值。
SETALL:設置所有信號量的初值,此時的semnum會被忽略。
IPC_RMID:刪除信號量。
…:不定參數,可有可無。如:我們需要獲取信號量信息時就可通過這個參數獲取。

          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(Linux-specific) */
          };

當我們要控制信號量的時候,必須手動定義這個聯合體。
val:是對信號量所要賦的初值。
buf:關於信號量的一些詳細信息存放在buf中
array:信號量集,用來設置信號量集中的信號量或獲取。

  • 操作信號量:
int semop(int semid, struct sembuf *sops, unsigned nsops);

參數:
semid:所要操作信號量集的ID。
sops:對該信號量集的操作(下面詳細說明sembuf)。
nsops:要操作的信號數量。
返回值:成功返回0;失敗返回-1,並設置errno。
struct sembuf:這個結構體在我們PV操作時會用到。

struct sembuf
{
  unsigned short int sem_num;   /* semaphore number */
  short int sem_op;             /* semaphore operation */
  short int sem_flg;            /* operation flag */
};

數據成員:
sem_num:所要操作第幾個信號量
sem_op:所要做的操作,賦值爲-1表示P操作(資源申請),賦值爲1表示V操作(資源釋放)。
short_flag:通常設置爲SEM_UNDO,使得操作系統能夠跟蹤信號量,並在沒有釋放信號量時自動釋放。
下面我們可以通過一個小案例來實現一個同步互斥的例子:
案例1:加入現在我們有一個人是專門生產糖果的,還有一個人是專門吃糖果的,那麼這兩個人就必須遵守一個時序(吃糖果的人必須要在生糖果人生產出糖果後才能吃到糖果),這用專業術語就叫對同一資源訪問的時序性控制。程序說明:父進程(生產糖果的人)、子進程(吃糖果的人)
sync.c(同步):

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#define KEY 0x01234567

union semun 
{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
};
//PV操作
void sem_p(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = -1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

void sem_v(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

int main()
{
        //1.創建1個信號量
        int semid = semget(KEY,1,IPC_CREAT|0664);
        if(semid<0)
        {
                perror("semget error!\n");
                return 1;
        }
        //同過父子進程完成同步操作
        if(fork()==0)
        {
                while(1)
                {
                        sem_p(semid);
                        printf("吃了一顆糖!!\n");
                        sem_v(semid);
                }
        }
        else
        {       //2.設置信號量初值,需要定義一個union semun
                union semun sem;
                sem.val = 1;
                if(semctl(semid,0,SETVAL,sem)<0)
                {
                        perror("semctl error!");
                        return 2;
                }

                while(1)
                {
                        sem_p(semid);
                        printf("生產了一顆糖!!\n");
                        sleep(1);
                        sem_v(semid);
                }
        }
         //3.最後要釋放信號量
        semctl(semid,0,IPC_RMID);
       return 0;
}

效果展示:
在這裏插入圖片描述
案例2:父子進程爭搶顯示器資源父進程打印B然後睡上1毫秒再打印個B、子進程打印A然後睡上1毫秒再打印個A。案例分析:如果我們不對顯示器資源進行保護,那麼可能在父子進程休眠的過程中,雙方都對顯示器資源操作,此時打印出來的將是 父子進程數據的交錯信息。
print.c:

#include<stdio.h>
#include<unistd.h>

int main()
{
        pid_t pid = -1;
        if((pid = fork())<0)
        {
                perror("fork!");
                return 1;
        }
        if(pid == 0)
        {
                while(1)
                {
                        printf("A");
                        fflush(stdout);
                        usleep(1000);
                        printf("A ");
                        fflush(stdout);
                }
        }
        else if(pid>0)
        {
                while(1)
                {
                        printf("B");
                        fflush(stdout);
                        usleep(1000);
                        printf("B ");
                        fflush(stdout);
                }
        //3.最後要釋放信號量
        semctl(semid,0,IPC_RMID);
       return 0;
        }
}

不對顯示器資源進行保護打印的結果將會是這樣:
在這裏插入圖片描述

此時我們看到父子進程交叉進行,而沒有做到(同一時刻對共享資源的唯一訪問性即就是互斥)。
**下面通過互斥來保護同一時刻只能有一個進程訪問顯示器。

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/sem.h>
#define KEY 0x01234567

union semun 
{
        int val;
        struct semid_ds *buf;
        unsigned short *array;
};
//PV操作
void sem_p(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = -1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

void sem_v(int semid)
{
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1;
        buf.sem_flg = SEM_UNDO;
        semop(semid,&buf,1);
}

int main()
{
        //1.創建1個信號量
        int semid = semget(KEY,1,IPC_CREAT|0664);
        if(semid<0)
        {
                perror("semget error!\n");
                return 1;
        }
        //2.設置信號量初值,需要定義一個union semun
        union semun sem;
        sem.val = 1;
        if(semctl(semid,0,SETVAL,sem)<0)
        {
                perror("semctl error!");
                return 2;
        }
        //同過父子進程完成同步操作
        if(fork()==0)
        {
        
                while(1)
                {
                        sem_p(semid);
                        printf("A");
                        fflush(stdout);
                        usleep(1000);
                        printf("A ");
                        fflush(stdout);
                        sem_v(semid);
                }
        }
        else
        {
        
                while(1)
                {
                        sem_p(semid);
                        printf("B");
                        fflush(stdout);
                        usleep(1000);
                        printf("B ");
                        fflush(stdout);
                        sem_v(semid);
                }
        //3.最後要釋放信號量
        semctl(semid,0,IPC_RMID);
        return 0;
        }
}

運行效果展示:
在這裏插入圖片描述
此時我們看到A、B是成雙成對出現的說明同一個時刻父子進程只有一個在操作顯示器(輸出設備)。
通過對信號量的學習,我們就可以對上次共享內存的server&client案例進行改進了,這裏就不在演示了,後面有機會在更新,這裏關於信號量重點已經講完。

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