linux 核心訊號量 使用者態訊號量 詳解
Linux 核心中的訊號量使用和使用者態的訊號量使用有所不同,
1、核心訊號量,由核心控制路徑使用。
2、使用者態訊號量分為兩種,一種為POSIX,另一種為 SYSTEM V
核心中訊號量的構成以及使用:
核心訊號量的構成
核心訊號量類似於自旋鎖,因為當鎖關閉著時,它不允許核心控制路徑繼續進行。然而,當核心控制路徑試圖獲取核心訊號量鎖保護的忙資源時,相應的程序就被掛起。只有在資源被釋放時,程序才再次變為可執行。
只有可以睡眠的函式才能獲取核心訊號量;中斷處理程式和可延遲函式都不能使用核心訊號量。
核心訊號量是struct semaphore型別的物件,它在<asm/semaphore.h>中定義
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
}
count:相當於訊號量的值,大於0,資源空閒;等於0,資源忙,但沒有程序等待這個保護的資源;小於0,資源不可用,並至少有一個程序等待資源。
wait:存放等待佇列連結串列的地址,當前等待資源的所有睡眠程序都會放在這個連結串列中。
sleepers:存放一個標誌,表示是否有一些程序在訊號量上睡眠
核心訊號量中的等待佇列(刪除,沒有聯絡)
上面已經提到了核心訊號量使用了等待佇列wait_queue來實現阻塞操作。當某任務由於沒有某種條件沒有得到滿足時,它就被掛到等待佇列中睡眠。當條件得到滿足時,該任務就被移出等待佇列,此時並不意味著該任務就被馬上執行,因為它又被移進工作佇列中等待CPU資源,在適當的時機被排程。 核心訊號量是在內部使用等待佇列的,也就是說該等待佇列對使用者是隱藏的,無須使用者干涉
核心訊號量的相關函式
初始化:
void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //將sem的值置為1,表示資源空閒
void init_MUTEX_LOCKED (struct semaphore *sem); //將sem的值置為0,表示資源忙
申請核心訊號量所保護的資源:
void down(struct semaphore * sem); // 可引起睡眠
int down_interruptible(struct semaphore * sem); // down_interruptible能被訊號打斷
int down_trylock(struct semaphore * sem); // 非阻塞函式,不會睡眠。無法鎖定資源則馬上返回
釋放核心訊號量所保護的資源:
void up(struct semaphore * sem);
核心訊號量的使用例程
在驅動程式中,當多個執行緒同時訪問相同的資源時(驅動中的全域性變數時一種典型的共享資源),可能會引發“競態“,因此我們必須對共享資源進行併發控制。Linux核心中解決併發控制的最常用方法是自旋鎖與訊號量(絕大多數時候作為互斥鎖使用)。
static ssize_t globarl_var(struct file *file, const char __user *ubuf,
size_t count,loff_t *offp)
{
//試圖獲得訊號量,用可被訊號打斷方式
if(down_interruptible(&sema) < 0){
return -ERESTARTSYS;
}
//對共享資源(global_var)進行操作
if(copy_from_user(&global_var, buf, sizeof(int))){
//失敗也要進行釋放訊號量,要不死鎖了
up(&sema);
return -EFAULT;
}
//成功釋放訊號量
up(&sema);
return sizeof(int);
}
使用者態訊號量使用:POSIX 訊號量與SYSTEM V訊號量的比
1.對POSIX來說,訊號量是個非負整數。常用於執行緒間同步。而SYSTEM V訊號量則是一個或多個訊號量的集合,它對應的是一個訊號量結構體,這個結構體是為SYSTEM V IPC服務的,訊號量只不過是它的一部分。常用於程序間同步。
2.POSIX訊號量的引用標頭檔案是“<semaphore.h>”,而SYSTEM V訊號量的引用標頭檔案是“<sys/sem.h>”。
3.從使用的角度,System V訊號量是複雜的,而Posix訊號量是簡單。比如,POSIX訊號量的建立和初始化或PV操作就很非常方便。
POSIX訊號量詳解
1.無名訊號量
無名訊號量的建立就像宣告一般的變數一樣簡單,例如:sem_t sem_id。然後再初始化該無名訊號量,之後就可以放心使用了。
無名訊號量常用於多執行緒間的同步,同時也用於相關程序間的同步。也就是說,無名訊號量必須是多個程序(執行緒)的共享變數,無名訊號量要保護的變數也必須是多個程序(執行緒)的共享變數,這兩個條件是缺一不可的。
常見的無名訊號量相關函式:sem_destroy
int sem_init(sem_t *sem, int pshared, unsigned int value);1) pshared ==0 用於同一多執行緒的同步;
2)若pshared>0 用於多個相關程序間的同步(即由fork產生的)
int sem_getvalue(sem_t *sem, int *sval);
取回訊號量sem的當前值,把該值儲存到sval中。
若有1個或更多的執行緒或程序呼叫sem_wait阻塞在該訊號量上,該函式返回兩種值:
1) 返回0
2) 返回阻塞在該訊號量上的程序或執行緒數目
linux採用返回的第一種策略。
sem_wait(或sem_trywait)相當於P操作,即申請資源。
int sem_wait(sem_t *sem); // 這是一個阻塞的函式
測試所指定訊號量的值,它的操作是原子的,
若sem>0,那麼它減1並立即返回。
若sem==0,則睡眠直到sem>0,此時立即減1,然後返回。
int sem_trywait(sem_t *sem); // 非阻塞的函式
其他的行為和sem_wait一樣,除了:
若sem==0,不是睡眠,而是返回一個錯誤EAGAIN。
sem_post相當於V操作,釋放資源。
int sem_post(sem_t *sem);
把指定的訊號量sem的值加1;
呼醒正在等待該訊號量的任意執行緒。
注意:在這些函式中,只有sem_post是訊號安全的函式,它是可重入函式
(a)無名訊號量在多執行緒間的同步
無名訊號量的常見用法是將要保護的變數放在sem_wait和sem_post中間所形成的臨界區內,這樣該變數就會被保護起來,例如:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int global_var;
sem_t sem_id;
void* thread_fun2(void *arg)
{
//償試訊號量是否大於0,如果是減1,如果不是等待
sem_wait(&sem_id);
printf("fun2\n");
global_var--;
printf("global_var = %d\n",global_var);
//訊號量加1
sem_post(&sem_id);
}
void* thread_fun1(void *arg)
{
//償試訊號量是否大於0,如果是減1,如果不是等待
sem_wait(&sem_id);
printf("fun1\n");
global_var--;
printf("global_var = %d\n",global_var);
//訊號量加1
sem_post(&sem_id);
}
int main(void)
{
global_var = 1;
pthread_t id1, id2;
//初始化訊號量
sem_init(&sem_id,0,1);
pthread_create(&id1, NULL, thread_fun1,NULL);
pthread_create(&id2, NULL, thread_fun2,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main...\n");
return 0;
}
上面的例程,到底哪個執行緒先申請到訊號量資源,這是隨機的。如果想要某個特定的順序的話,可以用2個訊號量來實現。例如下面的例程是執行緒1先執行完,然後執行緒2才繼續執行,直至結束。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_id1,sem_id2;
int global_var;
void * pt1_func(void *argc)
{
while(1){
sem_wait(&sem_id1);
global_var =0;
printf("func 1 global_var = %d\n",global_var);
sleep(2);
sem_post(&sem_id2);
}
}
void *pt2_func(void *argc)
{
while(1){
sem_wait(&sem_id2);
global_var = 1;
printf("func 2 global_var = %d\n",global_var);
sleep(2);
sem_post(&sem_id1);
}
}
int main(void)
{
pthread_t pt1;
pthread_t pt2;
sem_init(&sem_id1,0,1);
sem_init(&sem_id2,0,0);
pthread_create(&pt1,NULL,pt1_func,NULL);
pthread_create(&pt2,NULL,pt2_func,NULL);
pthread_join(pt1,NULL);
pthread_join(pt2,NULL);
printf("main..\n");
return 0;
}
(b)無名訊號量在相關程序間的同步說是相關程序,是因為本程式中共有2個程序,其中一個是另外一個的子程序(由fork產生)的。本來對於fork來說,子程序只繼承了父程序的程式碼副本,mutex理應在父子程序中是相互獨立的兩個變數,但由於在初始化mutex的時候,由pshared = 1指定了mutex處於共享記憶體區域,所以此時mutex變成了父子程序共享的一個變數。此時,mutex就可以用來同步相關程序了。#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <semaphore.h>
int main(void)
{
sem_t sem_id;
//第二個引數為1
sem_init(&sem_id,1,1);
if(0 == fork()){
while(1){
sem_wait(&sem_id);
printf("This child \n");
sleep(2);
sem_post(&sem_id);
}
}
while(1){
sem_wait(&sem_id);
printf("This father\n");
sleep(2);
sem_post(&sem_id);
}
return 0;
}
2.有名訊號量有名訊號量的特點是把訊號量的值儲存在檔案中。
這決定了它的用途非常廣:既可以用於執行緒,也可以用於相關程序間,甚至是不相關程序。
(a)有名訊號量能在程序間共享的原因由於有名訊號量的值是儲存在檔案中的,所以對於相關程序來說,子程序是繼承了父程序的檔案描述符,那麼子程序所繼承的檔案描述符所指向的檔案是和父程序一樣的,當然檔案裡面儲存的有名訊號量值就共享了。
(b)有名訊號量相關函式說明有名訊號量在使用的時候,和無名訊號量共享sem_wait和sem_post函式。
區別是有名訊號量使用sem_open代替sem_init,另外在結束的時候要像關閉檔案一樣去關閉這個有名訊號量。
(1)開啟一個已存在的有名訊號量,或建立並初始化一個有名訊號量。一個單一的呼叫就完成了訊號量的建立、初始化和許可權的設定。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name是檔案的路徑名;
Oflag 有O_CREAT或O_CREAT|EXCL兩個取值;
mode_t控制新的訊號量的訪問許可權;
Value指定訊號量的初始化值。
注意:這裡的name不能寫成/tmp/aaa.sem這樣的格式,因為在linux下,sem都是建立在/dev/shm目錄下。你可以將name寫成“/mysem”或“mysem”,創建出來的檔案都是“/dev/shm/sem.mysem”,千萬不要寫路徑。也千萬不要寫“/tmp/mysem”之類的。
當oflag = O_CREAT時,若name指定的訊號量不存在時,則會建立一個,而且後面的mode和value引數必須有效。若name指定的訊號量已存在,則直接開啟該訊號量,同時忽略mode和value引數。
當oflag = O_CREAT|O_EXCL時,若name指定的訊號量已存在,該函式會直接返回error。
(2) 一旦你使用了一訊號量,銷燬它們就變得很重要。
在做這個之前,要確定所有對這個有名訊號量的引用都已經通過sem_close()函式關閉了,然後只需在退出或是退出處理函式中呼叫sem_unlink()去刪除系統中的訊號量,注意如果有任何的處理器或是執行緒引用這個訊號量,sem_unlink()函式不會起到任何的作用。
也就是說,必須是最後一個使用該訊號量的程序來執行sem_unlick才有效。因為每個訊號燈有一個引用計數器記錄當前的開啟次數,sem_unlink必須等待這個數為0時才能把name所指的訊號燈從檔案系統中刪除。也就是要等待最後一個sem_close發生。
(c)有名訊號量在無相關程序間的同步
前面已經說過,有名訊號量是位於共享記憶體區的,那麼它要保護的資源也必須是位於共享記憶體區,只有這樣才能被無相關的程序所共享。
在下面這個例子中,服務程序和客戶程序都使用shmget和shmat來獲取得一塊共享記憶體資源。然後利用有名訊號量來對這塊共享記憶體資源進行互斥保護。
File1: server.c
#include <stdio.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ipc.h>
#define SHMSZ 27
#define SEM_NAME "vik3"
int main(void)
{
char ch;
int shmid;
key_t key;
char *shm, *s;
sem_t *mutex;
key = 1000;
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED){
printf("unable to create semaphore");
sem_unlink(SEM_NAME);
return -1;
}
shmid = shmget(key, SHMSZ, IPC_CREAT|0666);
if(shmid < 0){
printf("failure in shmget");
return -1;
}
shm = shmat(shmid,NULL,0);
s = shm;
for(ch ='A'; ch <= 'Z'; ch++){
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
while(*shm != '*'){
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
return -1;
}
File 2: client.c#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#define SHMSZ 27
#define SEM_NAME "vik3"
int main(void)
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
key = 1000;
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED){
printf("unable to execute semaphore\n");
sem_close(mutex);
return -1;
}
shmid = shmget(key,SHMSZ,0666);
if(shmid < 0){
printf("failure in shmget");
return -1;
}
shm = shmat(shmid,NULL,0);
//s = shm;
for(s = shm; *s != NULL; s++){
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
putchar('\n');
*shm = '*';
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
return -1;
}
SYSTEM V訊號量這是訊號量值的集合,而不是單個訊號量。相關的訊號量操作函式由<sys/ipc.h>引用。
1.訊號量結構體
核心為每個訊號量集維護一個訊號量結構體,可在<sys/sem.h>找到該定義:
struct semid_ds {
struct ipc_perm sem_perm; /* 訊號量集的操作許可許可權*/
struct sem *sem_base; /* 某個訊號量sem結構陣列的指標,當前訊號量集
中的每個訊號量對應其中一個數組元素*/
ushort sem_nsems; /* sem_base 陣列的個數*/
time_t sem_otime; /* 最後一次成功修改訊號量陣列的時間*/
time_t sem_ctime; /* 成功建立時間*/
};
struct sem {
ushort semval; /* 訊號量的當前值 */
short sempid; /* 最後一次返回該訊號量的程序ID號 */
ushort semncnt; /* 等待semval大於當前值的程序個數 */
ushort semzcnt; /* 等待semval變成0的程序個數 */
};
2.常見的SYSTEM V訊號量函式
(a)關鍵字和描述符SYSTEM V訊號量是SYSTEM V IPC(即SYSTEM V程序間通訊)的組成部分,其他的有SYSTEM V訊息佇列,SYSTEM V共享記憶體。而關鍵字和IPC描述符無疑是它們的共同點,也使用它們,就不得不先對它們進行熟悉。這裡只對SYSTEM V訊號量進行討論。IPC描述符相當於引用ID號,要想使用SYSTEM V訊號量(或MSG、SHM),就必須用IPC描述符來呼叫訊號量。而IPC描述符是核心動態提供的(通過semget來獲取),使用者無法讓伺服器和客戶事先認可共同使用哪個描述符,所以有時候就需要到關鍵字KEY來定位描述符。
某個KEY只會固定對應一個描述符(這項轉換工作由核心完成),這樣假如伺服器和客戶事先認可共同使用某個KEY,那麼大家就都能定位到同一個描述符,也就能定位到同一個訊號量,這樣就達到了SYSTEM V訊號量在程序間共享的目的。
(b)建立和開啟訊號量
int semget(key_t key, int nsems, int oflag)
(1) nsems>0 : 建立一個信的訊號量集,指定集合中訊號量的數量,一旦建立就不能更改。
(2) nsems==0 : 訪問一個已存在的集合
(3) 返回的是一個稱為訊號量識別符號的整數,semop和semctl函式將使用它。
(4) 建立成功後訊號量結構被設定:
.sem_perm 的uid和gid成員被設定成的呼叫程序的有效使用者ID和有效組ID
.oflag 引數中的讀寫許可權位存入sem_perm.mode
.sem_otime 被置為0,sem_ctime被設定為當前時間
.sem_nsems 被置為nsems引數的值
該集合中的每個訊號量不初始化,這些結構是在semctl,用引數SET_VAL,SETALL初始化的。
semget函式執行成功後,就產生了一個由核心維持的型別為semid_ds結構體的訊號量集,返回semid就是指向該訊號量集的引索。
(c)關鍵字的獲取
有多種方法使客戶機和伺服器在同一IPC結構上會合:
(1) 伺服器可以指定關鍵字IPC_PRIVATE建立一個新IPC結構,將返回的識別符號存放在某處(例如一個檔案)以便客戶機取用。關鍵字IPC_PRIVATE保證伺服器建立一個新IPC結構。這種技術的缺點是:伺服器要將整型識別符號寫到檔案中,然後客戶機在此後又要讀檔案取得此識別符號。
IPC_PRIVATE關鍵字也可用於父、子關係程序。父程序指定IPC_PRIVATE建立一個新IPC結構,所返回的識別符號在fork後可由子程序使用。子程序可將此識別符號作為exec函式的一個引數傳給一個新程式。
(2) 在一個公用標頭檔案中定義一個客戶機和伺服器都認可的關鍵字。然後伺服器指定此關鍵字建立一個新的IPC結構。這種方法的問題是該關鍵字可能已與一個IPC結構相結合,在此情況下,get函式(msgget、semget或shmget)出錯返回。伺服器必須處理這一錯誤,刪除已存在的IPC結構,然後試著再建立它。當然,這個關鍵字不能被別的程式所佔用。
(3) 客戶機和伺服器認同一個路徑名和課題I D(課題I D是0 ~ 2 5 5之間的字元值) ,然後呼叫函式ftok將這兩個值變換為一個關鍵字。這樣就避免了使用一個已被佔用的關鍵字的問題。
使用ftok並非高枕無憂。有這樣一種例外:伺服器使用ftok獲取得一個關鍵字後,該檔案就被刪除了,然後重建。此時客戶端以此重建後的檔案來ftok所獲取的關鍵字就和伺服器的關鍵字不一樣了。所以一般商用的軟體都不怎麼用ftok。
一般來說,客戶機和伺服器至少共享一個頭檔案,所以一個比較簡單的方法是避免使用ftok,而只是在該標頭檔案中存放一個大家都知道的關鍵字。
(d)設定訊號量的值(PV操作)
某個KEY只會固定對應一個描述符(這項轉換工作由核心完成),這樣假如伺服器和客戶事先認可共同使用某個KEY,那麼大家就都能定位到同一個描述符,也就能定位到同一個訊號量,這樣就達到了SYSTEM V訊號量在程序間共享的目的。
(b)建立和開啟訊號量
int semget(key_t key, int nsems, int oflag)
(1) nsems>0 : 建立一個信的訊號量集,指定集合中訊號量的數量,一旦建立就不能更改。
(2) nsems==0 : 訪問一個已存在的集合
(3) 返回的是一個稱為訊號量識別符號的整數,semop和semctl函式將使用它。
(4) 建立成功後訊號量結構被設定:
.sem_perm 的uid和gid成員被設定成的呼叫程序的有效使用者ID和有效組ID
.oflag 引數中的讀寫許可權位存入sem_perm.mode
.sem_otime 被置為0,sem_ctime被設定為當前時間
.sem_nsems 被置為nsems引數的值
該集合中的每個訊號量不初始化,這些結構是在semctl,用引數SET_VAL,SETALL初始化的。
semget函式執行成功後,就產生了一個由核心維持的型別為semid_ds結構體的訊號量集,返回semid就是指向該訊號量集的引索。
(c)關鍵字的獲取
有多種方法使客戶機和伺服器在同一IPC結構上會合:
(1) 伺服器可以指定關鍵字IPC_PRIVATE建立一個新IPC結構,將返回的識別符號存放在某處(例如一個檔案)以便客戶機取用。關鍵字IPC_PRIVATE保證伺服器建立一個新IPC結構。這種技術的缺點是:伺服器要將整型識別符號寫到檔案中,然後客戶機在此後又要讀檔案取得此識別符號。
IPC_PRIVATE關鍵字也可用於父、子關係程序。父程序指定IPC_PRIVATE建立一個新IPC結構,所返回的識別符號在fork後可由子程序使用。子程序可將此識別符號作為exec函式的一個引數傳給一個新程式。
(2) 在一個公用標頭檔案中定義一個客戶機和伺服器都認可的關鍵字。然後伺服器指定此關鍵字建立一個新的IPC結構。這種方法的問題是該關鍵字可能已與一個IPC結構相結合,在此情況下,get函式(msgget、semget或shmget)出錯返回。伺服器必須處理這一錯誤,刪除已存在的IPC結構,然後試著再建立它。當然,這個關鍵字不能被別的程式所佔用。
(3) 客戶機和伺服器認同一個路徑名和課題I D(課題I D是0 ~ 2 5 5之間的字元值) ,然後呼叫函式ftok將這兩個值變換為一個關鍵字。這樣就避免了使用一個已被佔用的關鍵字的問題。
使用ftok並非高枕無憂。有這樣一種例外:伺服器使用ftok獲取得一個關鍵字後,該檔案就被刪除了,然後重建。此時客戶端以此重建後的檔案來ftok所獲取的關鍵字就和伺服器的關鍵字不一樣了。所以一般商用的軟體都不怎麼用ftok。
一般來說,客戶機和伺服器至少共享一個頭檔案,所以一個比較簡單的方法是避免使用ftok,而只是在該標頭檔案中存放一個大家都知道的關鍵字。
(d)設定訊號量的值(PV操作)
int semop(int semid, struct sembuf *opsptr, size_t nops);
(1) semid: 是semget返回的semid
(2)opsptr: 指向訊號量操作結構陣列
(3) nops : opsptr所指向的陣列中的sembuf結構體的個數
struct sembuf {
short sem_num; // 要操作的訊號量在訊號量集裡的編號,
short sem_op; // 訊號量操作
short sem_flg; // 操作表示符
};
(4) 若sem_op 是正數,其值就加到semval上,即釋放訊號量控制的資源
若sem_op 是0,那麼呼叫者希望等到semval變為0,如果semval是0就返回;
若sem_op 是負數,那麼呼叫者希望等待semval變為大於或等於sem_op的絕對值
例如,當前semval為2,而sem_op = -3,那麼怎麼辦?
注意:semval是指semid_ds中的訊號量集中的某個訊號量的值
(5) sem_flg
SEM_UNDO 由程序自動釋放訊號量
IPC_NOWAIT 不阻塞
到這裡,讀者肯定有個疑惑:semop希望改變的semval到底在哪裡?我們怎麼沒看到有它的痕跡?其實,前面已經說明了,當使用semget時,就產生了一個由核心維護的訊號量集(當然每個訊號量值即semval也是隻由核心才能看得到了),使用者能看到的就是返回的semid。核心通過semop函式的引數,知道應該去改變semid所指向的訊號量的哪個semval。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <fcntl.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
int main(void)
{
char* buf_child[]={"this", "is", "the", "child", "process"};
char* buf_father[]={"father", "say", "hello", "to", "child"};
int i = 0, semid, fd;
pid_t pid;
struct sembuf sb; //訊號量操作
union semun sem;
semid = semget(1000, 2, 0666 | IPC_CREAT); //申請訊號量組,包含2個訊號量
sem.val = 0;
semctl(semid, 0, SETVAL, sem); //初始化0號訊號量為0
sem.val = 1;
semctl(semid, 1, SETVAL, sem); //初始化1號訊號量為1
fd=open("tmp",O_CREAT|O_TRUNC|O_WRONLY,0666);
pid = fork();
switch (pid) {
case -1:
perror("fork fail");
break;
case 0: /* child consume */
srand((unsigned int)getpid());
while (i < 5) {
sb.sem_num = 1; //將1號訊號量
sb.sem_op = -1; //減1
sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;
semop(semid, &sb, 1);
write(fd,buf_child[i], strlen(buf_child[i]));
sleep(2);
write(fd,&" ", 1);
i++;
sb.sem_num = 0; //將0號訊號量
sb.sem_op = 1; //加1
sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;
semop(semid, &sb, 1); //操作訊號量
}
break;
default:/* parent production */
srand((unsigned int)getpid());
while (i < 5) {
sb.sem_num = 0; //將0號訊號量
sb.sem_op = -1; //減1
sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;
semop(semid, &sb, 1); //操作訊號量
write(fd,buf_father[i], strlen(buf_father[i]));
sleep(2);
write(fd,&" ", 1);
i++;
sb.sem_num = 1;
sb.sem_op = 1;
sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;
semop(semid, &sb, 1);
}
break;
}
return 0;
}
(e)對訊號集實行控制操作(semval的賦值等)int semctl(int semid, int semum, int cmd, ../* union semun arg */);
semid是訊號量集合;
semnum是訊號在集合中的序號;
semum是一個必須由使用者自定義的結構體,在這裡我們務必弄清楚該結構體的組成:
union semun
{
int val; // cmd == SETVAL
struct semid_ds *buf // cmd == IPC_SET或者cmd == IPC_STAT
ushort *array; // cmd == SETALL,或cmd = GETALL
};
val只有cmd ==SETVAL時才有用,此時指定的semval = arg.val。
注意:當cmd == GETVAL時,semctl函式返回的值就是我們想要的semval。千萬不要以為指定的semval被返回到arg.val中。
array指向一個數組,當cmd==SETALL時,就根據arg.array來將訊號量集的所有值都賦值;當cmd ==GETALL時,就將訊號量集的所有值返回到arg.array指定的陣列中。
buf指標只在cmd==IPC_STAT或IPC_SET時有用,作用是semid所指向的訊號量集(semid_ds機構體)。一般情況下不常用,這裡不做談論。
另外,cmd == IPC_RMID還是比較有用的。
例
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
static int nsems;
static int semflg;
static int semid;
int errno=0;
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
int main()
{
struct sembuf sops[2]; //要用到兩個訊號量,所以要定義兩個運算元組
int rslt;
unsigned short argarray[80];
arg.array = argarray;
semid = semget(IPC_PRIVATE, 2, 0666);
if(semid < 0 )
{
printf("semget failed. errno: %d\n", errno);
exit(0);
}
//獲取0th訊號量的原始值
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
//初始化0th訊號量,然後再讀取,檢查初始化有沒有成功
arg.val = 1; // 同一時間只允許一個佔有者
semctl(semid, 0, SETVAL, arg);
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[1].sem_flg = 0;
rslt=semop(semid, sops, 1); //申請0th訊號量,嘗試鎖定
if (rslt < 0 )
{
printf("semop failed. errno: %d\n", errno);
exit(0);
}
//可以在這裡對資源進行鎖定
sops[0].sem_op = 1;
semop(semid, sops, 1); //釋放0th訊號量
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
rslt=semctl(semid, 0, GETALL, arg);
if (rslt < 0)
{
printf("semctl failed. errno: %d\n", errno);
exit(0);
}
printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]);
if(semctl(semid, 1, IPC_RMID) == -1)
{
Perror(“semctl failure while clearing reason”);
}
return(0);
}
生產者與消費者問題
1.問題描述:
有一個長度為N的緩衝池為生產者和消費者所共有,只要緩衝池未滿,生產者便可將訊息送入緩衝池;只要緩衝池未空,消費者便可從緩衝池中取走一個訊息。生產者往緩衝池放資訊的時候,消費者不可操作緩衝池,反之亦然。
2.使用多執行緒和訊號量解決該經典問題的互斥
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #define BUFF_SIZE 10 char buffer[BUFF_SIZE]; char count; sem_t sem_mutex; sem_t p_sem_mutex; sem_t c_sem_mutex; void *p_funp(void *argc) { sem_wait(&p_sem_mutex); sem_wait(&sem_mutex); count ++; if(count < BUFF_SIZE) sem_post(&p_sem_mutex); if(count > 0) sem_post(&c_sem_mutex); sem_post(&sem_mutex); } void *c_func(void *arg) { sem_wait(&c_sem_mutex); sem_wait(&sem_mutex); count--; if(count > 0) sem_post(&c_sem_mutex); if(count < BUFF_SIZE) sem_post(&p_sem_mutex); sem_post(&sem_mutex); } int main(void) { pthread_t pid1,pid2; sem_init(&sem_mutex,0,1); sem_init(&p_sem_mutex,0,1); sem_init(&c_sem_mutex,0,0); pthread_create(&pid1,NULL,p_func,NULL); pthread_create(&pid2,NULL,c_func,NULL); pthread_join(pid1,NULL); pthread_join(pid2,NULL); sem_destory(&sem_mutex); sem_destory(&c_sem_mutex); sem_destory(&p_sem_mutex); return 0; }