linux 處理序間通訊二 訊號量以及執行個體

來源:互聯網
上載者:User

訊號量

代碼來自:嵌入式linux應用開發標準教程

訊號量

http://www.cnblogs.com/hjslovewcl/archive/2011/03/03/2314341.html

當我們在多使用者系統,多進程系統,或是兩者混合的系統中使用線程操作編寫程式時,我們經常會發現我們有段臨界代碼,在此處我們需要保證一個進程(或是一個線程的執行)需要排他的訪問一個資源。
訊號量有一個複雜的編程介面。幸運的是,我們可以很容易的為自己提供一個對於大多數的訊號量編程問題足夠高效的簡化介面。
為了阻止多個程式同時訪問一個共用資源所引起的問題,我們需要一種方法產生並且使用一個標記從而保證在臨界區部分一次只有一個線程執行。線程相關的方法,我們可以使用互斥或訊號量來控制一個多線程程式對於臨界區的訪問。


編寫通用目的的代碼保證一個程式排他的訪問一個特定的資源是十分困難的,儘管有一個名為Dekker的演算法解決方案。不幸的是,這個演算法依賴於"忙等待" 或是"自旋鎖",即一個進程的連續運行需要等待一個記憶體位址發生改變。在一個多任務環境中,例如Linux,這是對CPU資源的無謂浪費。如果硬體支援, 這樣的情況就要容易得多,通常以特定CPU指令的形式來支援排他訪問。硬體支援的例子可以是訪問指令與原子方式增加寄存器值,從而在讀取/增加/寫入的操 作之間就不會有其他的指令運行。

我們已經瞭解到的一個要行的解決方案就是使用O_EXCL標記調用open函數來建立檔案,這提供了原子方式的檔案建立。這會使得一個進程成功的獲得一個標記:新建立的檔案。這個方法可以用於簡單的問題,但是對於複雜的情況就要顯得煩瑣與低效了。

當Dijkstr引入訊號量的概念以後,並行編程領域前進了一大步。正如我們在第12章所討論的,訊號量是一個特殊的變數,他是一個整數,並且只有兩個操 作可以使得其值增加:等待(wait)與訊號(signal)。因為在Linux與UNIX編程中,"wait"與"signal"已經具有特殊的意義 了,我們將使用原始概念:
用於等待(wait)的P(訊號量變數)
用於訊號(signal)的V(訊號量變數)

這兩字母來自等待(passeren:通過,如同臨界區前的檢測點)與訊號(vrjgeven:指定或釋放,如同釋放臨界區的控制權)的荷蘭語。有時我們也會遇到與訊號量相關的術語"up"與"down",來自於訊號標記的使用。

訊號量定義
 

最簡單的訊號量是一個只有0與1兩個值的變數,二值訊號量。這是最為通常的形式。具有多個正數值的訊號量被稱之為通用訊號量。在本章的其餘部分,我們將會討論二值訊號量。

P與V的定義出奇的簡單。假定我們有一個訊號量變數sv,兩個操作定義如下:

P(sv)    如果sv大於0,減小sv。如果sv為0,掛起這個進程的執行。                       P就是排隊去等待用這個資源
V(sv)    如果有進程被掛起等待sv,使其恢複執行。如果沒有進行被掛起等待sv,增加sv。V就是企圖釋放這個共用資源

訊號量的另一個理解方式就是當臨界區可用時訊號量變數sv為true,當臨界區忙時訊號量變數被P(sv)減小,從而變為false,當臨界區再次可用時 被V(sv)增加。注意,簡單的具有一個我們可以減小或是增加的通常變數並不足夠,因為我們不能用C,C++或是其他的程式設計語言來表述產生訊號,進行原子 測試來確定變數是否為true,如果是則將其變為false。這就是使得訊號量操作特殊的地方。

一個理論例子 

我們可以使用一個簡單的理論例子來瞭解一下訊號量是如何工作的。假設我們有兩個進程proc1與proc2,這兩個進程會在他們執行的某一時刻排他的訪問 一個資料庫。我們定義一個單一的二值訊號量,sv,其初始值為1並且可以為兩個進程所訪問。兩個進程然後需要執行同樣的處理來訪問臨界區代碼;實際上,這 兩個進程可以是同一個程式的不同調用。

這兩個進程共用sv訊號量變數。一旦一個進程已經執行P(sv)操作,這個進程就可以獲得訊號量並且進入臨界區。第二個進程就會被阻止進行臨界區,因為當他嘗試執行P(sv)時,他就會等待,直到第一個進程離開臨界區並且執行V(sv)操作來釋放訊號量。

所需要的過程如下:

semaphore sv = 1;
loop forever {
    P(sv);
    critical code section;
    V(sv);
    noncritical code section;
}

這段代碼出奇的簡單,因為P操作與V操作是十分強大的。圖14-1顯示了P操作與V操作如何成為進行臨界區代碼的門檻。

Linux訊號量工具 

現在我們已經瞭解了什麼是訊號量以及他們在理論上是如何工作的,現在我們可以來瞭解一下這些特性在Linux中是如何?的。訊號量函數介面設計十分精 細,並且提供了比通常所需要的更多的實用效能。所有的Linux訊號量函數在通用的訊號量數組上進行操作,而不是在一個單一的二值訊號量上進行操作。乍看 起來,這似乎使得事情變得更為複雜,但是在一個進程需要鎖住多個資源的複雜情況下,在訊號量數組上進行操作將是一個極大的優點。在這一章,我們將會關注於 使用單一訊號量,因為在大多數情況下,這正是我們需要使用的。

訊號量函數定義如下:

#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

事實上,為了獲得我們特定操作所需要的#define定義,我們需要在包含sys/sem.h檔案之前通常需要包含sys/types.h與sys/ipc.h檔案。而在某些情況下,這並不是必須的。

因為我們會依次瞭解每一個函數,記住,這些函數的設計是用於操作訊號量值數組的,從而會使用其操作向比單個訊號量所需要的操作更為複雜。

注意,key的作用類似於一個檔案名稱,因為他表示程式也許會使用或是合作所用的資源。相類似的,由semget所返回的並且為其他的共用記憶體函數所用的標 識符與由fopen函數所返回 的FILE *十分相似,因為他被進程用來訪問共用檔案。而且與檔案類似,不同的進程會有不同的訊號量標識符,儘管他們指向相同的訊號量。key與標識符的用法對於在 這裡所討論的所有IPC程式都是通用的,儘管每一個程式會使用獨立的key與標識符。

semget 

semget函數建立一個新的訊號量或是獲得一個已存在的訊號量索引值。

int semget(key_t key, int num_sems, int sem_flags);

第一個參數key是一個用來允許不相關的進程訪問相同訊號量的整數值。所有的訊號量是為不同的程式通過提供一個key來間接訪問的,對於每一個訊號量系統 產生一個訊號量標識符。訊號量索引值只可以由semget獲得,所有其他的訊號量函數所用的訊號量標識符都是由semget所返回的。

還有一個特殊的訊號量key值,IPC_PRIVATE(通常為0),其作用是建立一個只有建立進程可以訪問的訊號量。這通常並沒有有用的目的,而幸運的是,因為在某些Linux系統上,手冊頁將IPC_PRIVATE並沒有阻止其他的進程訪問訊號量作為一個bug列出。

num_sems參數是所需要的訊號量數目。這個值通常總是1。

sem_flags參數是一個標記集合,與open函數的標記十分類似。低九位是訊號的許可權,其作用與檔案權限類別似。另外,這些標記可以與 IPC_CREAT進行或操作來建立新的訊號量。設定IPC_CREAT標記並且指定一個已經存在的訊號量索引值並不是一個錯誤。如果不需 要,IPC_CREAT標記只是被簡單的忽略。我們可以使用IPC_CREAT與IPC_EXCL的組合來保證我們可以獲得一個新的,唯一的訊號量。如果 這個訊號量已經存在,則會返回一個錯誤。

如果成功,semget函數會返回一個正數;這是用於其他訊號量函數的標識符。如果失敗,則會返回-1。

semop 

函數semop用來改變訊號量的值:

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

第一個參數,sem_id,是由semget函數所返回的訊號量標識符。第二個參數,sem_ops,是一個指向結構數組的指標,其中的每一個結構至少包含下列成員:

struct sembuf {
    short sem_num;
    short sem_op;
    short sem_flg;
}

第一個成員,sem_num,是訊號量數目,通常為0,除非我們正在使用一個訊號量數組。sem_op成員是訊號量的變化量值。(我們可以以任何量改變信 號量值,而不只是1)通常情況下中使用兩個值,-1是我們的P操作,用來等待一個訊號量變得可用,而+1是我們的V操作,用來通知一個訊號量可用。

最後一個成員,sem_flg,通常設定為SEM_UNDO。這會使得作業系統跟蹤當前進程對訊號量所做的改變,而且如果進程終止而沒有釋放這個訊號量, 如果訊號量為這個進程所佔有,這個標記可以使得作業系統自動釋放這個訊號量。將sem_flg設定為SEM_UNDO是一個好習慣,除非我們需要不同的行 為。如果我們確實變我們需要一個不同的值而不是SEM_UNDO,一致性是十分重要的,否則我們就會變得十分迷惑,當我們的進程退出時,核心是否會嘗試清 理我們的訊號量。

semop的所用動作會同時作用,從而避免多個訊號量的使用所引起的競爭條件。我們可以在手冊頁中瞭解關於semop處理更為詳細的資訊。

semctl 

semctl函數允許訊號量資訊的直接控制:

int semctl(int sem_id, int sem_num, int command, ...);

第一個參數,sem_id,是由semget所獲得的訊號量標識符。sem_num參數是訊號量數目。當我們使用訊號量數組時會用到這個參數。通常,如果 這是第一個且是唯一的一個訊號量,這個值為0。command參數是要執行的動作,而如果提供了額外的參數,則是union semun,根據X/OPEN規範,這個參數至少包括下列參數:

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
}

許多版本的Linux在標頭檔(通常為sem.h)中定義了semun聯合,儘管X/Open確認說我們必須定義我們自己的聯合。如果我們發現我們確實需 要定義我們自己的聯合,我們可以查看semctl手冊頁瞭解定義。如果有這樣的情況,建議使用手冊頁中提供的定義,儘管這個定義與上面的有區別。

有多個不同的command值可以用於semctl。在這裡我們描述兩個會經常用到的值。要瞭解semctl功能的詳細資料,我們應該查看手冊頁。

這兩個通常的command值為:

SETVAL:用於初始化訊號量為一個已知的值。所需要的值作為聯合semun的val成員來傳遞。在訊號量第一次使用之前需要設定訊號量。
IPC_RMID:當訊號量不再需要時用於刪除一個訊號量標識。

semctl函數依據command參數會返回不同的值。對於SETVAL與IPC_RMID,如果成功則會返回0,否則會返回-1。

使用訊號量 

正如我們在前面部分的描述中所看到的,訊號量操作是相當複雜的。這是最不幸的,因為使用臨界區進行多進程或是多線程編程是一個十分困難的問題,而其擁有其自己複雜的編程介面也增加了編程負擔。

幸運的是,我們可以使用最簡單的二值訊號量來解決大多數需要訊號量的問題。在我們的例子中,我們會使用所有的編程介面來建立一個非常簡單的用於二值訊號量的P
與V類型介面。然後,我們會使用這個簡單的介面來示範訊號量如何工作。

要實驗訊號量,我們將會使用一個簡單的程式,sem1.c,這個程式我們可以多次調用。我們將會使用一個可選的參數來標識這個程式是負責建立訊號量還是銷毀訊號量。

我們使用兩個不同字元的輸出來標識進入與離開臨界區。使用參數調用的程式會在進入與離開其臨界區時輸出一個X,而另一個程式調用會在進入與離開其臨界區時輸出一個O。因為在任何指定的時間內只有一個進程能夠進入其臨界區,所以所有X與O字元都是成對出現的




執行個體:父進程建立一個子進程,資源預設是被佔用的只要子進程不釋放資源,父進程就一直等待使用資源

/* sem_com.h */#ifndefSEM_COM_H#defineSEM_COM_H#include <sys/ipc.h>#include <sys/sem.h>union semun{int val;struct semid_ds *buf;unsigned short *array;};int init_sem(int, int);int del_sem(int);int sem_p(int);int sem_v(int); #endif /* SEM_COM_H */

/* sem_com.c */#include "sem_com.h"int init_sem(int sem_id, int init_value){union semun sem_union;sem_union.val = 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; }}int sem_p(int sem_id){struct sembuf sem_b;sem_b.sem_num = 0; /*id*/sem_b.sem_op = -1; /* P operation*/sem_b.sem_flg = SEM_UNDO;if (semop(sem_id, &sem_b, 1) == -1) {perror("P operation");return -1;}return 0;}int sem_v(int sem_id){struct sembuf sem_b;sem_b.sem_num = 0; /* id */sem_b.sem_op = 1; /* V operation */sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1){perror("V operation");return -1;}return 0;}

應用

/* fork.c */#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define DELAY_TIME3int main(void){pid_t result;int sem_id;sem_id = semget(ftok(".", 'a'),  1, 0666|IPC_CREAT); /* 建立一個訊號量*/init_sem(sem_id, 0);  //初始值設為0資源被佔用/*調用fork函數,其傳回值為result*/result = fork();/*通過result的值來判斷fork函數的返回情況,首先進行出錯處理*/if(result ==  -1){perror("Fork\n");}else if (result == 0) /*傳回值為0代表子進程*/{printf("Child process will wait for some seconds...\n");sleep(DELAY_TIME);printf("The returned value is %d in the child process(PID = %d)\n", result, getpid());sem_v(sem_id);   //釋放資源}else /*傳回值大於0代表父進程*/{sem_p(sem_id);     //等待資源,如果子進程不釋放 就一直等printf("The returned value is %d in the father process(PID = %d)\n", result, getpid());sem_v(sem_id);     //釋放資源del_sem(sem_id);  //刪除訊號量}exit(0);}

執行個體2

伺服器#include #include #define SEGSIZE 1024#define READTIME 1union semun {int val;struct semid_ds *buf;unsigned short *array;} arg;//產生訊號量int sem_creat(key_t key){union semun sem;int semid;sem.val = 0;semid = semget(key,1,IPC_CREAT|0666);if (-1 == semid){printf("create semaphore error\n");exit(-1);}semctl(semid,0,SETVAL,sem);return semid;}//刪除訊號量void del_sem(int semid){union semun sem;sem.val = 0;semctl(semid,0,IPC_RMID,sem);}//pint p(int semid){struct sembuf sops={0,+1,IPC_NOWAIT};return (semop(semid,&sops,1));}//vint v(int semid){struct sembuf sops={0,-1,IPC_NOWAIT};return (semop(semid,&sops,1));}int main(){key_t key;int shmid,semid;char *shm;char msg[7] = "-data-";char i;struct semid_ds buf;key = ftok("/",0);shmid = shmget(key,SEGSIZE,IPC_CREAT|0604);if (-1 == shmid){printf(" create shared memory error\n");return -1;}shm = (char *)shmat(shmid,0,0);if (-1 == (int)shm){printf(" attach shared memory error\n");return -1;}semid = sem_creat(key);for (i = 0;i <= 3;i++){sleep(1);p(semid);sleep(READTIME);msg[5] = '0' + i;memcpy(shm,msg,sizeof(msg));sleep(58);v(semid);}shmdt(shm);shmctl(shmid,IPC_RMID,&buf);del_sem(semid);return 0;//gcc -o shm shm.c -g}2.2 用戶端#include #include #include #define SEGSIZE 1024#define READTIME 1union semun {int val;struct semid_ds *buf;unsigned short *array;} arg;// 列印程式執行時間void out_time(void){static long start = 0;time_t tm;if (0 == start){tm = time(NULL);start = (long)tm;printf(" now start ...\n");}printf(" second: %ld \n",(long)(time(NULL)) - start);}//建立訊號量int new_sem(key_t key){union semun sem;int semid;sem.val = 0;semid = semget(key,0,0);if (-1 == semid){printf("create semaphore error\n");exit(-1);}return semid;}//等待訊號量變成0void wait_v(int semid){struct sembuf sops={0,0,0};semop(semid,&sops,1);}int main(void){key_t key;int shmid,semid;char *shm;char msg[100];char i;key = ftok("/",0);shmid = shmget(key,SEGSIZE,0);if(-1 == shmid){printf(" create shared memory error\n");return -1;}shm = (char *)shmat(shmid,0,0);if (-1 == (int)shm){printf(" attach shared memory error\n");return -1;}semid = new_sem(key);for (i = 0;i < 3;i ++){sleep(2);wait_v(semid);printf("Message geted is: %s \n",shm + 1);out_time();}shmdt(shm);return 0;// gcc -o shmc shmC.c -g}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.