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