天天看點

Linux 程序間通信(四)信号量

1 信号量概述

信号量和其他IPC不同,并沒有在程序之間傳送資料,信号量用于多程序在存取共享資源時的同步控制就像交通路口的紅路燈一樣,當信号量大于0,表示綠燈允許通過,當信号量等于0,表示紅燈,必須停下來等待綠燈才能通過。

程序間的互斥關系與同步關系存在的根源在于臨界資源。

臨界資源是在同一個時刻隻允許有限個(通常隻有一個) 程序可以通路(讀) 或修改(寫)的資源, 通常包括硬體資源(處理器、 記憶體、 存儲器及其他外圍裝置等) 和軟體資源(共享代碼段、 共享結構和變量等)。通路臨界資源的代碼叫做臨界區, 臨界區本身也會成為臨界資源。

信号量是用來解決程序間的同步與互斥問題的一種程序間通信機制,包括一個稱為信号量的變量和在該信号量下等待資源的程序等待列, 以及對信号量進行的兩個原子操作(PV操作)。 其中信号量對應于某一種資源, 取一個非負的整型值。 信号量值指的是目前可用的該資源的數量, 若等于 0 則意味着目前沒有可用的資源。PV 原子操作的具體定義如下。

  • P 操作: 如果有可用的資源(信号量值>0), 則占用一個資源(給信号量值減 1,進入臨界區代碼); 如果沒有可用的資源(信号量值=0), 則被阻塞直到系統将資源配置設定給該程序(進入等待隊列, 一直等到資源輪到該程序)。
  • V 操作: 如果在該信号量的等待隊列中有程序在等待資源, 則喚醒一個阻塞程序;如果沒有程序等待它, 則釋放一個資源(給信号量值加 1)。

常見的使用信号量通路臨界區的僞代碼如下:

{
/* 設 R 為某種資源, S 為資源 R 的信号量 */
INIT_VAL(S); /* 對信号量 S 進行初始化 */
非臨界區;
P(S); /* 進行 P 操作 */
臨界區(使用資源 R) ; /* 隻有有限個(通常隻有一個) 程序被允許進入該區 */
V(S); /* 進行 V 操作 */
非臨界區;
}      

最簡單的信号量隻能取 0 和 1 兩種值, 這種信号量叫做二維信号量。 這裡主要讨論二維信号量。 二維信号量的應用比較容易擴充到使用多元信号量的情況。

2 信号量程式設計

2.1  函數說明

在 Linux 系統中, 使用信号量通常分為以下幾個步驟:

(1) 建立信号量或獲得在系統中已存在的信号量, 此時需要調用 semget()函數。 不同程序通過使用同一個信号量鍵值來獲得同一個信号量。

(2) 初始化信号量, 此時使用 semctl()函數的 SETVAL 操作。 當使用二維信号量時, 通常将信号量初始化為 1。

(3) 進行信号量的 PV 操作, 此時調用 semop()函數。 這一步是實作程序間的同步和互斥的核心工作部分。

(4)如果不需要信号量, 則從系統中删除它, 此時使用 semctl ()函數的 IPC_RMID 操作。需要注意的是, 在程式中不應該出現對已經被删除的信号量的操作。

注意:建立了一個信号就會在系統中一直存在,直到我們去删除

1.建立信号量的函數

這裡列舉了 semget()函數的文法要點。

Linux 程式間通信(四)信号量

key:如果為IPC_PRIVATE,則表示建立信号量,如果key不為IPC_PRIVATE且key所對應的信号量已經存在,則傳回信号量

nsems: 需要建立的信号量數目, 通常取值為 1

semflg:

如果semget用于建立新的信号量,則的值為IPC_CREAT  |  perm,perm為新建立信号量的存取權限

如果semget用于獲得已經存在的信号量則semflg的值為0

執行個體代碼:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
  int semid = -1;
  semid = semget(222, 1, 0666 | IPC_CREAT);
  if(semid >= 0)
  {
    printf("建立信号的辨別符,值為 [%d]\n", semid);    
  }else
  {
    perror("semget");
  }

  return 0;
}      

結果:

# ./a.out

建立信号的辨別符,值為 [65538]

注意:

建立的辨別符的值,不等于在semget函數中給定的key值

2.獲得和釋放信号量的函數:

int semop(int semid, struct sembuf *sops, size_t nsops)

semid: semget()函數傳回的信号量辨別符

sops: 指向信号量操作數組, 一個數組包括以下成員。

nsops: 操作數組 sops 中的操作個數(元素數目) , 通常取值為 1(一個操作)

struct sembuf{ 
short sem_num; /* 信号量編号, 使用單個信号量時, 通常取值為 0 */
short sem_op;/* 信号量操作: 取值為-1 則表示 P 操作, 取值為+1 則表示 V 操作 */
short sem_flg;/* 通常設定為 SEM_UNDO。 這樣在程序沒釋放信号量而退出時, 系統自動釋放該程序中未釋放的信号量 */
}      

sem_op  >  0:那麼操作将sem_op加入到信号量的值中,并喚醒等待信号增加的程序

sem_op  ==  0:當信号量的值是0時,函數傳回,否則阻塞直到信号量的值為0

sem_op  <  0:則判斷   目前信号量   +  sem_op   的值

  1. 如果為0,喚醒等待信号量為0的程序,
  2. 如果小于0,調用該函數的程序阻塞,
  3. 如果大于0,那麼信号量減去這個值并傳回

擷取信号量的代碼:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
  int semid = -1;
  struct sembuf sb;
  semid = semget(222, 1, 0);
  if(semid >= 0)
  {
    sb.sem_num = 0;
    sb.sem_op = -1;  //-1表示擷取信号量
    sb.sem_flg = 0;
    printf("值為 [%d]\n", semid);
    if(semop(semid, &sb, 1) == -1)  //如果沒得到信号量會阻塞在這
    {
      perror("semop");
      exit(-1);
    }
    printf("值為 [%d]\n", semid);
    
  }
  return 0;
}      

釋放信号量的代碼:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
  int semid = -1;
  struct sembuf sb;
  semid = semget(222, 1, 0);
  if(semid >= 0)
  {
    sb.sem_num = 0;
    sb.sem_op = 1;  //大于0,表示釋放信号量
    sb.sem_flg = 0;
    printf("值為 [%d]\n", semid);
    if(semop(semid, &sb, 1) == -1)
    {
      perror("semop");
      exit(-1);
    }
    printf("值為 [%d]\n", semid);
    
  }
  return 0;
}      

結果:

終端1運作擷取信号量的代碼,如果它沒有擷取到信号量,就會一直阻塞,直到終端2釋放信号量

3.控制信号量操作的函數:

Linux 程式間通信(四)信号量
Linux 程式間通信(四)信号量

删除信号量執行個體代碼:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main(void)
{
  int semid = -1;
  int ret = -1;
  struct sembuf sb;
  semid = semget(222, 1, 0);
  if(semid >= 0)
  {
    ret = semctl(semid, 0, IPC_RMID);
    if(ret != -1)
    {
      printf("删除值為 [%d] 信号成功\n", semid);
    }else
    {
      perror("semctl");
    }     
  }
  return 0;
}      

完整信号量處理代碼:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.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
                                           (Linux-specific) */
           };

/* 信号量初始化(指派) 函數 */
int init_sem(int sem_id, int init_value)
{
  union semun sem_union;
  sem_union.val = init_value; /* 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;
  }
} 
/* P 操作函數 */
int sem_p(int sem_id)
{
  struct sembuf sem_b;
  sem_b.sem_num = 0; /* 單個信号量的編号應該為 0 */
  sem_b.sem_op = -1; /* 表示 P 操作 */
  sem_b.sem_flg = SEM_UNDO; /* 系統自動釋放将會在系統中殘留的信号量 */
  if (semop(sem_id, &sem_b, 1) == -1)
  {
    perror("P operation");
    return -1;
  } 
  return 0;
  }
/* V 操作函數 */
int sem_v(int sem_id)
{
  struct sembuf sem_b;
  sem_b.sem_num = 0; /* 單個信号量的編号應該為 0 */
  sem_b.sem_op = 1; /* 表示 V 操作 */
  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;
  sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT); /* 建立一個信号量 */
  init_sem(sem_id, 0);
  /* 調用 fork()函數 */
  result = fork();
  if(result == -1)
  {
    perror("Fork\n");
  }else if (result == 0) /* 傳回值為 0 代表子程序 */
  {
    sleep(5);
    printf("子程序一下\n");
    sem_v(sem_id);
    sem_p(sem_id);
    sem_v(sem_id);
    printf("子程序再一下\n");
  }else /* 傳回值大于 0 代表父程序 */
  {
    sem_p(sem_id);
    printf("父程序一下\n");
    sem_v(sem_id);
    sem_p(sem_id);
    sem_v(sem_id);
    printf("父程序再一下\n");
    del_sem(sem_id);
  }
  exit(0);
}      

結果:

# ./a.out 

子程序一下

父程序一下

子程序再一下

父程序再一下

總結:

1.信号量類似于線程中的互斥鎖,(一個線程上鎖後,另一個線程要等待另一個程序解鎖後才能繼續運作)

2.定義了一個信号量後,會一直在系統中存在

3.信号量比互斥鎖複雜太多了

4.semctl函數中的union semun需要自己定義

5.如果多個程序等待一個信号量,就把信号量的值設定為大于1