1. 程式人生 > >關於Linux訊號量的理解和探討(別說看不懂,耐心看完,你會恍然大悟~)

關於Linux訊號量的理解和探討(別說看不懂,耐心看完,你會恍然大悟~)

工作環境(藍色粗體字為特別注意內容)

1,實驗環境:Linux2.6

2,參考文獻:https://www.cnblogs.com/LZYY/p/3453582.html

最近在操作裝置檔案的時候,要求使用獨佔模式使用串列埠裝置,即一個程序用完之後釋放該串列埠,供其他程序使用。該如何實現該需求呢?自然想到了用訊號量來實現。訊號量是什麼呢?

首先了解一下,訊號量機概念是由荷蘭科學家Dijkstr引入,值得一提的是,它提出的Dijksrtr演算法解決了最短路徑問題。
      訊號量又稱為訊號燈,它是用來協調不同程序間的資料物件的,而最主要的應用是共享記憶體方式的程序間通訊。本質上,訊號量是一個計數器,它用來記錄對某個資源(如共享記憶體)的存取狀況,訊號量是一個特殊的變數,並且只有兩個操作可以改變其值:等待(wait)與訊號(signal)。
因為在Linux與UNIX程式設計中,"wait"與"signal"已經具有特殊的意義了(暫不知這特殊意義是啥),所以原始概念: 
     用於等待(wait)的P(訊號量變數) ; 
     用於訊號(signal)的V(訊號量變數) ; 
這兩字母來自等待(passeren:通過,如同臨界區前的檢測點)與訊號(vrjgeven:指定或釋放,如同釋放臨界區的控制權)的荷蘭語。
P操作

負責把當前程序由執行狀態轉換為阻塞狀態,直到另外一個程序喚醒它。
操作為:申請一個空閒資源(把訊號量減1),若成功,則退出;若失敗,則該程序被阻塞;
V操作 負責把一個被阻塞的程序喚醒,它有一個引數表,存放著等待被喚醒的程序資訊。
操作為:釋放一個被佔用的資源(把訊號量加1),如果發現有被阻塞的程序,則選擇一個喚醒之。 

補充:檢視共享資訊的記憶體的命令是ipcs [-m|-s|-q] (全部的話是ipcs -a) ;檢視共享資訊的記憶體的命令是ipcs [-m|-s|-q]。

標頭檔案pv.h

//pv.h標頭檔案
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>

#define SEMPERM 0600
typedef union _semun {
  int val;
  struct semid_ds *buf;
  ushort *array;
} semun;
int init_sem(key_t semkey);
int p(int semid);
int v(int semid);
int destroysem();

//實現pv.c

//pv.c  對訊號量賦初值,初值固定為1
//檢視訊號量
//ipcs -s
/*
建立訊號量
int semget(key_t key, int nsems, int semflg);
key:自定義一個整數。這個引數類似open函式的第一個引數,由呼叫者指定一個“檔名”。
nsems:要初始化多少個訊號量,通常設為1。
semflg:這個引數也和open函式類似。支援許可權和IPC_CREAT以及IPC_EXCL
     IPC_CREAT表示要建立一個訊號量,但是如果訊號量已經存在了也不會報錯。
     IPC_EXCL和IPC_CREAT一起使用時,如果訊號量已經存在就會報EEXIST。
     通常可以將semflg設為IPC_CREAT|IPC_EXCL|0666等
*/

#include "pv.h"

int init_sem(key_t semkey)
{
   int status=0,semid;                    //訊號量識別符號semid
  if ((semid=semget(semkey,1,SEMPERM|IPC_CREAT|IPC_EXCL))==-1)
  {
    if (errno==EEXIST)               //EEXIST:訊號量集已經存在,無法建立
      semid=semget(semkey,1,0);      //建立一個訊號量
  }
  else
  {
    semun arg;
    arg.val=1;                                        //訊號量的初值
    status=semctl(semid,0,SETVAL,arg);      //設定訊號量集中的一個單獨的訊號量的值。
  }
  if (semid==-1||status==-1)
  {
    perror("initsem failed");
    return(-1);
  }
  /*all ok*/
  return(semid);
}

int p(int semid)
{
  struct sembuf p_buf;

  p_buf.sem_num=0;
  p_buf.sem_op=-1;        //訊號量減1,注意這一行的1前面有個負號
  p_buf.sem_flg=SEM_UNDO;
  
  //p_buf = {0,-1,SEM_UNDO};
  if (semop(semid, &p_buf, 1)==-1)   
  {
    perror("p(semid)failed");
    exit(1);
  }
  return(0);
}

int v(int semid)
{
  struct sembuf v_buf;

  v_buf.sem_num=0;
  v_buf.sem_op=1;    //訊號量加1
  v_buf.sem_flg=SEM_UNDO;
  
  if (semop(semid, &v_buf, 1)==-1)
  {
    perror("v(semid)failed");
    exit(1);
  }
  return(0);
}

int destroy_sem(int semid){
 // fprintf(stderr, "Failed to delete semaphore\n");
 return semctl(semid,0,IPC_RMID);  //刪除程序訊號量值,IPC_RMID是刪除命令
}

測試程式如下:

//testsem.c  主程式,使用PV操作實現三個程序的互斥
#include "pv.h"
void handlesem(key_t skey);
int semid;
main()
{
  key_t semkey=0x200;
  int i;
  for (i=0;i<3;i++)
  {
    if (fork()==0)           //父程序負責產生3個子程序
      handlesem(semkey);  //子程序中才執行handlesem,做完後就exit。
  }

 if (destroy_sem(semid)<0)
  {
    perror("semctl error");
   exit(1);
  }
}

void handlesem(key_t skey)
{
  int sleep_s=5;
  pid_t pid=getpid();
  
  if ((semid=init_sem(skey))<0)
    exit(1);
  printf("程序 %d 在臨界資源區之前 \n",pid);
  p(semid);                                      //程序進入臨界資源區,訊號量減少1
  printf("程序 %d 在使用臨界資源時,停止%ds \n",pid,sleep_s);

  /*in real life do something interesting */
  sleep(sleep_s);
  printf("程序 %d 退出臨界區後 \n",pid);
  v(semid);                                //程序退出臨界資源區,訊號量加1
  printf("程序 %d 完全退出\n",pid);
  exit(0);
}

編譯命令gcc pv.c testsem.c -o testsem

執行之後即可看到效果,有個地方不太明白,就是訊號量應該在什麼時候釋放掉?請各位網友多指教,感激不盡。