linux內核剖析(十)進程間通訊之-信號量semaphore

信號量


什麼是信號量


信號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個進程(線程)所擁有。css

信號量的值爲正的時候,說明它空閒。所測試的線程能夠鎖定而使用它。若爲0,說明它被佔用,測試的線程要進入睡眠隊列中,等待被喚醒。html

爲了防止出現因多個程序同時訪問一個共享資源而引起的一系列問題,咱們須要一種方法,它能夠經過生成並使用令牌來受權,在任一時刻只能有一個執行線程訪問代碼的臨界區域linux

臨界區域是指執行數據更新的代碼須要獨佔式地執行。而信號量就能夠提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。編程

信號量是一個特殊的變量,程序對其訪問都是原子操做,且只容許對它進行等待(即P(信號變量))和發送(即V(信號變量))信息操做。數組

最簡單的信號量是隻能取0和1的變量,這也是信號量最多見的一種形式,叫作二進制信號量。而能夠取多個正整數的信號量被稱爲通用信號量。這裏主要討論二進制信號量。安全

信號量的工做原理


因爲信號量只能進行兩種操做等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的:服務器

  • P(sv):若是sv的值大於零,就給它減1;若是它的值爲零,就掛起該進程的執行數據結構

  • V(sv):若是有其餘進程因等待sv而被掛起,就讓它恢復運行,若是沒有進程因等待sv而掛起,就給它加1.多線程

舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操做,它將獲得信號量,並能夠進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,由於當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就能夠恢復執行。架構

信號量的分類


在學習信號量以前,咱們必須先知道——Linux提供兩種信號量:

  • 內核信號量,由內核控制路徑使用

  • 用戶態進程使用的信號量,這種信號量又分爲POSIX信號量和SYSTEM V信號量。

POSIX信號量又分爲有名信號量無名信號量
有名信號量,其值保存在文件中, 因此它能夠用於線程也能夠用於進程間的同步。無名信號量,其值保存在內存中。

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操做就很很是方便。

內核信號量


Linux內核的信號量在概念和原理上與用戶態的System V的IPC機制信號量是同樣的,可是它毫不可能在內核以外使用,它是一種睡眠鎖。

若是有一個任務想要得到已經被佔用的信號量時,信號量會將其放入一個等待隊列(它不是站在外面癡癡地等待而是將本身的名字寫在任務隊列中)而後讓其睡眠。

當持有信號量的進程將信號釋放後,處於等待隊列中的一個任務將被喚醒(由於隊列中可能不止一個任務),並讓其得到信號量。

這一點與自旋鎖不一樣,處理器能夠去執行其它代碼。

關於 他們的不一樣之處,請參見

自旋鎖,Mutex和信號量的使用

linux 自旋鎖和信號量

它與自旋鎖的差別:因爲爭用信號量的進程在等待鎖從新變爲可用時會睡眠,因此信號量適用於鎖會被長時間持有的狀況

相反,鎖被短期持有時,使用信號量就不太適宜了,由於睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖佔用的所有時間表還要長;

因爲執行線程在鎖被爭用時會睡眠,因此只能在進程上下文中才能得到信號量鎖,由於在中斷上下文中是不能進行調試的;持有信號量的進行也能夠去睡眠,固然也能夠不睡眠,由於當其餘進程爭用信號量時不會所以而死鎖;不能同時佔用信號量和自旋鎖,由於自旋鎖不能夠睡眠而信號量鎖能夠睡眠。相對而來講信號量比較簡單,它不會禁止內核搶佔,持有信號量的代碼能夠被搶佔。

信號量還有一個特徵,就是它容許多個持有者,而自旋鎖在任什麼時候候只能容許一個持有者。

固然咱們常常遇到也是隻有一個持有者,這種信號量叫二值信號量或者叫互斥信號量。容許有多個持有者的信號量叫計數信號量,在初始化時要說明最多容許有多少個持有者(Count值)
信號量在建立時須要設置一個初始值,表示同時能夠有幾個任務能夠訪問該信號量保護的共享資源,初始值爲1就變成互斥鎖(Mutex),即同時只能有一個任務能夠訪問信號量保護的共享資源。
當任務訪問完被信號量保護的共享資源後,必須釋放信號量,釋放信號量經過把信號量的值加1實現,若是信號量的值爲非正數,代表有任務等待當前信號量,所以它也喚醒全部等待該信號量的任務。

關於內核信號量的其餘信息

請參見

大話Linux內核中鎖機制之信號量、讀寫信號量

內核信號量的構成


內核信號量相似於自旋鎖,由於當鎖關閉着時,它不容許內核控制路徑繼續進行。然而,當內核控制路徑試圖獲取內核信號量鎖保護的忙資源時,相應的進程就被掛起。只有在資源被釋放時,進程纔再次變爲可運行。
只有能夠睡眠的函數才能獲取內核信號量;中斷處理程序和可延遲函數都不能使用內核信號量。
內核信號量是struct semaphore類型的對象,在內核源碼中位於include\linux\semaphore.h文件

struct semaphore {    atomic_t count;    int sleepers;    wait_queue_head_t wait; }

 

成員 描述
count 至關於信號量的值,大於0,資源空閒;等於0,資源忙,但沒有進程等待這個保護的資源;小於0,資源不可用,並至少有一個進程等待資源
wait 存放等待隊列鏈表的地址,當前等待資源的全部睡眠進程都會放在這個鏈表中
sleepers 存放一個標誌,表示是否有一些進程在信號量上睡眠

內核信號量中的等待隊列


上面已經提到了內核信號量使用了等待隊列wait_queue來實現阻塞操做。

當某任務因爲沒有某種條件沒有獲得知足時,它就被掛到等待隊列中睡眠。當條件獲得知足時,該任務就被移出等待隊列,此時並不意味着該任務就被立刻執行,由於它又被移進工做隊列中等待CPU資源,在適當的時機被調度。

內核信號量是在內部使用等待隊列的,也就是說該等待隊列對用戶是隱藏的,無須用戶干涉。由用戶真正使用的等待隊列咱們將在另外的篇章進行詳解。

內核信號量的相關函數


這裏寫圖片描述

初始化


#define __SEMAPHORE_INITIALIZER(name, n) \ { \ .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ .count = n, \ .wait_list = LIST_HEAD_INIT((name).wait_list), \ } 

 

該宏聲明一個信號量name是直接將結構體中count值設置成n,此時信號量可用於實現進程間的互斥量。

#define DECLARE_MUTEX(name) \ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

 

該宏聲明一個互斥鎖name,但把它的初始值設置爲1

void sema_init (struct semaphore *sem, int val);

 

該函用於數初始化設置信號量的初值,它設置信號量sem的值爲val。

#define init_MUTEX(sem) sema_init(sem, 1)

 

該函數用於初始化一個互斥鎖,即它把信號量sem的值設置爲1。

#define init_MUTEX_LOCKED(sem) sema_init(sem, 0) 

 

該函數也用於初始化一個互斥鎖,但它把信號量sem的值設置爲0,即一開始就處在已鎖狀態。

注意:對於信號量的初始化函數Linux最新版本存在變化,如init_MUTEX和init_MUTEX_LOCKED等初始化函數目前新的內核中已經沒有或者更換了了名字等

所以建議之後在編程中遇到須要使用信號量的時候儘可能採用sema_init(struct semaphore *sem, int val)函數,由於這個函數就目前爲止從未發生變化。

獲取信號量–申請內核信號量所保護的資源


void down(struct semaphore * sem);

 

該函數用於得到信號量sem,它會致使睡眠,所以不能在中斷上下文(包括IRQ上下文和softirq上下文)使用該函數。該函數將把sem的值減1,若是信號量sem的值非負,就直接返回,不然調用者將被掛起,直到別的任務釋放該信號量才能繼續運行。

int down_interruptible(struct semaphore * sem);

 

該函數功能與down相似,不一樣之處爲,down不會被信號(signal)打斷,但down_interruptible能被信號(好比Ctrl+C)打斷,所以該函數有返回值來區分是正常返回仍是被信號中斷,若是返回0,表示得到信號量正常返回,若是被信號打斷,返回-EINTR

int down_trylock(struct semaphore * sem);

 

該函數試着得到信號量sem,若是可以馬上得到,它就得到該信號量並返回0,不然,表示不能得到信號量sem,返回值爲非0值。所以,它不會致使調用者睡眠,能夠在中斷上下文使用

釋放內核信號量所保護的資源


void up(struct semaphore * sem);

 

該函數釋放信號量sem,即把sem的值加1,若是sem的值爲非正數,代表有任務等待該信號量,所以喚醒這些等待者。

內核信號量的使用例程


在驅動程序中,當多個線程同時訪問相同的資源時(驅動中的全局變量時一種典型的
共享資源),可能會引起「競態「,所以咱們必須對共享資源進行併發控制。Linux內核中
解決併發控制的最經常使用方法是自旋鎖與信號量(絕大多數時候做爲互斥鎖使用)。

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off) {  //得到信號量  if (down_interruptible(&sem))  {   return - ERESTARTSYS;  }  //將用戶空間的數據複製到內核空間的global_var  if (copy_from_user(&global_var, buf, sizeof(int)))  {   up(&sem);   return - EFAULT;  }  //釋放信號量  up(&sem);  return sizeof(int); }

 

讀-寫信號量


跟自旋鎖同樣,信號量也有區分讀-寫信號量之分

若是一個讀寫信號量當前沒有被寫者擁有而且也沒有寫者等待讀者釋放信號量,那麼任何讀者均可以成功得到該讀寫信號量;

不然,讀者必須被掛起直到寫者釋放該信號量。若是一個讀寫信號量當前沒有被讀者或寫者擁有而且也沒有寫者等待該信號量,那麼一個寫者能夠成功得到該讀寫信號量,不然寫者將被掛起,直到沒有任何訪問者。所以,寫者是排他性的,獨佔性的。

讀寫信號量有兩種實現,一種是通用的,不依賴於硬件架構,所以,增長新的架構不須要從新實現它,但缺點是性能低,得到和釋放讀寫信號量的開銷大;另外一種是架構相關的,所以性能高,獲取和釋放讀寫信號量的開銷小,但增長新的架構須要從新實現。在內核配置時,能夠經過選項去控制使用哪種實現。
讀寫信號量的相關API有:

DECLARE_RWSEM(name)

 

該宏聲明一個讀寫信號量name並對其進行初始化。

void init_rwsem(struct rw_semaphore *sem);

 

該函數對讀寫信號量sem進行初始化。

void down_read(struct rw_semaphore *sem);

 

讀者調用該函數來獲得讀寫信號量sem。該函數會致使調用者睡眠,所以只能在進程上下文使用。

int down_read_trylock(struct rw_semaphore *sem);

 

該函數相似於down_read,只是它不會致使調用者睡眠。它盡力獲得讀寫信號量sem,若是可以當即獲得,它就獲得該讀寫信號量,而且返回1,不然表示不能馬上獲得該信號量,返回0。所以,它也能夠在中斷上下文使用。

void down_write(struct rw_semaphore *sem);

 

寫者使用該函數來獲得讀寫信號量sem,它也會致使調用者睡眠,所以只能在進程上下文使用。

int down_write_trylock(struct rw_semaphore *sem);

 

該函數相似於down_write,只是它不會致使調用者睡眠。該函數盡力獲得讀寫信號量,若是可以馬上得到,就得到該讀寫信號量而且返回1,不然表示沒法馬上得到,返回0。它能夠在中斷上下文使用。

void up_read(struct rw_semaphore *sem);

 

讀者使用該函數釋放讀寫信號量sem。它與down_read或down_read_trylock配對使用。若是down_read_trylock返回0,不須要調用up_read來釋放讀寫信號量,由於根本就沒有得到信號量。

void up_write(struct rw_semaphore *sem);

 

寫者調用該函數釋放信號量sem。它與down_write或down_write_trylock配對使用。若是down_write_trylock返回0,不須要調用up_write,由於返回0表示沒有得到該讀寫信號量。

void downgrade_write(struct rw_semaphore *sem);

 

該函數用於把寫者降級爲讀者,這有時是必要的。由於寫者是排他性的,所以在寫者保持讀寫信號量期間,任何讀者或寫者都將沒法訪問該讀寫信號量保護的共享資源,對於那些當前條件下不須要寫訪問的寫者,降級爲讀者將,使得等待訪問的讀者可以馬上訪問,從而增長了併發性,提升了效率。
讀寫信號量適於在讀多寫少的狀況下使用,在linux內核中對進程的內存映像描述結構的訪問就使用了讀寫信號量進行保護。
究竟何時使用自旋鎖何時使用信號量,下面給出建議的方案
當對低開銷、短時間、中斷上下文加鎖,優先考慮自旋鎖;當對長期、持有鎖須要休眠的任務,優先考慮信號量。

POSIX信號量詳解


無名信號量


無名信號量的建立就像聲明通常的變量同樣簡單,例如:sem_t sem_id。而後再初始化該無名信號量,以後就能夠放心使用了。

無名信號量經常使用於多線程間的同步,同時也用於相關進程間的同步。也就是說,無名信號量必須是多個進程(線程)的共享變量,無名信號量要保護的變量也必須是多個進程(線程)的共享變量,這兩個條件是缺一不可的。

常見的無名信號量相關函數


int sem_init(sem_t *sem, int pshared, unsigned int value);

 

  • pshared==0 用於同一多線程的同步;

  • 若pshared>0 用於多個相關進程間的同步(即由fork產生的)

int sem_getvalue(sem_t *sem, int *sval);

 

取回信號量sem的當前值,把該值保存到sval中。
如有1個或更多的線程或進程調用sem_wait阻塞在該信號量上,該函數返回兩種值:

  • 返回0

  • 返回阻塞在該信號量上的進程或線程數目

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是信號安全的函數,它是可重入函數

無名信號量在多線程間的同步


無名信號量的常見用法是將要保護的變量放在sem_wait和sem_post中間所造成的
臨界區內,這樣該變量就會被保護起來,例如:

#include <pthread.h> #include <semaphore.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> int number; // 被保護的全局變量 sem_t sem_id; void* thread_one_fun(void *arg) { sem_wait(&sem_id); printf("thread_one have the semaphore\n"); number++; printf("thread_one : number = %d\n", number); sem_post(&sem_id); return NULL; } void* thread_two_fun(void *arg) { sem_wait(&sem_id); printf("thread_two have the semaphore \n"); number--; printf("thread_two : number = %d\n", number); sem_post(&sem_id); return NULL; } int main(int argc, char *argv[]) { number = 1; pthread_t id1, id2; sem_init(&sem_id, 0, 1); pthread_create(&id1, NULL, thread_one_fun, NULL); pthread_create(&id2, NULL, thread_two_fun, NULL); pthread_join(id1, NULL); pthread_join(id2, NULL); printf("main...\n"); return 0; }

 

上面的例程,到底哪一個線程先申請到信號量資源,這是隨機的。

進程1先執行,進城2後執行
這裏寫圖片描述

進程2先執行,進城1後執行
這裏寫圖片描述

若是想要某個特定的順序的話,能夠用2個信號量來實現。例以下面的例程是線程1先執行完,而後線程2才繼續執行,直至結束。

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> int number; // 被保護的全局變量 sem_t sem_id1, sem_id2; /* * 線程1, * 對sem_id1加鎖(P操做)之後 * 將number增長1 * 同時對sem_id2進行釋放,V操做 * * */ void* thread_one_fun(void *arg) { sem_wait(&sem_id1); printf("thread_one have the semaphore\n"); number++; printf("number = %d\n",number); sem_post(&sem_id2); return NULL; } /* * 線程2, * 對sem_id2加鎖(P操做)之後 * 將number減小1 * 同時對sem_id1進行釋放,V操做 * * */ void* thread_two_fun(void *arg) { sem_wait(&sem_id2); printf("thread_two have the semaphore \n"); number--; printf("number = %d\n",number); sem_post(&sem_id1); return NULL; } int main(int argc,char *argv[]) { number = 1; pthread_t id1, id2; /* * 因爲程序初始時, sem_id1可進入, sem_id2不可進入 * 兩個線程的動做以下: * thread one P(id1) number++ V(id2) * thread two P(id2) number-- V(id1) * 而id1可進入, id2不可進入 * 所以thread one先執行 * 若是將id1與id2的順序交換, 則執行順序相反 * */ sem_init(&sem_id1, 0, 1); // 空閒的 sem_init(&sem_id2, 0, 0); // 忙的 pthread_create(&id1, NULL, thread_one_fun, NULL); pthread_create(&id2, NULL, thread_two_fun, NULL); pthread_join(id1, NULL); pthread_join(id2, NULL); printf("main...\n"); return EXIT_SUCCESS; } 

 

無名信號量在相關進程間的同步


說是相關進程,是由於本程序中共有2個進程,其中一個是另一個的子進程(由fork產生)的。

原本對於fork來講,子進程只繼承了父進程的代碼副本,mutex理應在父子進程中是相互獨立的兩個變量,但因爲在初始化mutex的時候,由pshared = 1指定了mutex處於共享內存區域,因此此時mutex變成了父子進程共享的一個變量。此時,mutex就能夠用來同步相關進程了。

#include <stdio.h> #include <stdlib.h> #include <semaphore.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> int main(int argc, char **argv) { int fd, i; int nloop = 10, zero = 0; int *ptr; sem_t mutex; // open a file and map it into memory fd = open("log.txt", O_RDWR | O_CREAT, S_IRWXU); write(fd,&zero,sizeof(int)); ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); /* create, initialize semaphore */ if(sem_init(&mutex, 1, 1) < 0) // { perror("semaphore initilization"); exit(0); } if (fork() == 0) { /* child process*/ for (i = 0; i < nloop; i++) { sem_wait(&mutex); printf("child: %d\n", (*ptr)++); //sleep(1); sem_post(&mutex); } exit(0); } /* back to parent process */ for (i = 0; i < nloop; i++) { sem_wait(&mutex); printf("parent: %d\n", (*ptr)++); //sleep(1); sem_post(&mutex); } exit(0); } 

 

這裏寫圖片描述

有名信號量


有名信號量的特色是把信號量的值保存在文件中。

這決定了它的用途很是廣:既能夠用於線程,也能夠用於相關進程間,甚至是不相關進程。

有名信號量能在進程間共享的緣由


因爲有名信號量的值是保存在文件中的,因此對於相關進程來講,子進程是繼承了父進程的文件描述符,那麼子進程所繼承的文件描述符所指向的文件是和父進程同樣的,固然文件裏面保存的有名信號量值就共享了。

有名信號量相關函數說明


有名信號量在使用的時候,和無名信號量共享sem_wait和sem_post函數。
區別是有名信號量使用sem_open代替sem_init,另外在結束的時候要像關閉文件同樣去關閉這個有名信號量。

  • 打開一個已存在的有名信號量,或建立並初始化一個有名信號量。一個單一的調用就完
    成了信號量的建立、初始化和權限的設置。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

 

參數 描述
name 文件的路徑名;
Oflag 有O_CREAT或O_CREAT
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。

  • 一旦你使用了一信號量,銷燬它們就變得很重要。
    在作這個以前,要肯定全部對這個有名信號量的引用都已經經過sem_close()函數關閉了,而後只需在退出或是退出處理函數中調用sem_unlink()去刪除系統中的信號量,
    注意若是有任何的處理器或是線程引用這個信號量,sem_unlink()函數不會起到任何的做用。

也就是說,必須是最後一個使用該信號量的進程來執行sem_unlick纔有效。由於每一個信號燈有一個引用計數器記錄當前的打開次數,sem_unlink必須等待這個數爲0時才能把name所指的信號燈從文件系統中刪除。也就是要等待最後一個sem_close發生。

有名信號量在無相關進程間的同步


前面已經說過,有名信號量是位於共享內存區的,那麼它要保護的資源也必須是位於共享內存區,只有這樣才能被無相關的進程所共享。
在下面這個例子中,服務進程和客戶進程都使用shmgetshmat來獲取得一塊共享內存資源。而後利用有名信號量來對這塊共享內存資源進行互斥保護。

服務器程序

//server.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <semaphore.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define SHMSZ 27 char SEM_NAME[]= "vik"; int main() { char ch; int shmid; key_t key; char *shm,*s; sem_t *mutex; //name the shared memory segment key = 1000; //create & initialize semaphore mutex = sem_open(SEM_NAME, O_CREAT, 0644, 1); if(mutex == SEM_FAILED) { perror("unable to create semaphore"); sem_unlink(SEM_NAME); exit(-1); } //create the shared memory segment with this key shmid = shmget(key, SHMSZ, IPC_CREAT | 0666); if(shmid < 0) { perror("failure in shmget"); exit(-1); } //attach this segment to virtual memory shm = shmat(shmid, NULL, 0); //start writing into memory s = shm; for(ch = 'A'; ch <= 'Z'; ch++) { sem_wait(mutex); *s++ = ch; sem_post(mutex); } //the below loop could be replaced by binary semaphore while(*shm != '*') { sleep(1); } sem_close(mutex); sem_unlink(SEM_NAME); shmctl(shmid, IPC_RMID, 0); return EXIT_SUCCESS; } 

 

客戶端程序

// client.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <semaphore.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define SHMSZ 27 char SEM_NAME[]= "vik"; int main() { int shmid; key_t key; char *shm, *s; sem_t *mutex; // name the shared memory segment key = 1000; // create & initialize existing semaphore mutex = sem_open(SEM_NAME, 0, 0644, 0); if(mutex == SEM_FAILED) { perror("reader:unable to execute semaphore"); sem_close(mutex); exit(-1); } // create the shared memory segment with this key shmid = shmget(key, SHMSZ, 0666); if(shmid < 0) { perror("reader:failure in shmget"); exit(-1); } // attach this segment to virtual memory shm = shmat(shmid, NULL, 0); // start reading s = shm; for(s = shm; *s != '\0'; s++) { sem_wait(mutex); putchar(*s); sem_post(mutex); } // once done signal exiting of reader:This can be replaced by another semaphore *shm = '*'; sem_close(mutex); shmctl(shmid, IPC_RMID, 0); return EXIT_SUCCESS; } 

 

SYSTEM V信號量


這是信號量值的集合,而不是單個信號量。相關的信號量操做函數由<sys/ipc.h>引用。

ystem V 信號量在內核中維護,其中包括二值信號量 、計數信號量、計數信號量集。

  • 二值信號量 : 其值只有0、1 兩種選擇,0表示資源被鎖,1表示資源可用;

  • 計數信號量:其值在0 和某個限定值之間,不限定資源數只在0 1 之間;

  • 計數信號量集 :多個信號量的集合組成信號量集

信號量結構體


內核爲每一個信號量集維護一個信號量結構體,可在

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; /* 成功建立時間 */ };

 

其中ipc_perm 結構是內核給每一個進程間通訊對象維護的一個信息結構,其成員包含全部者用戶id,全部者組id、建立者及其組id,以及訪問模式等;semid_ds結構體中的sem結構是內核用於維護某個給定信號量的一組值的內部結構,其結構定義:

struct sem { ushort semval; /* 信號量的當前值 */ short sempid; /* 最後一次返回該信號量的進程ID 號 */ ushort semncnt; /* 等待semval大於當前值的進程個數 */ ushort semzcnt; /* 等待semval變成0的進程個數 */ };

 

常見的SYSTEM V信號量函數


關鍵字和描述符


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信號量在進程間共享的目的。

建立和打開信號量


建立一個信號量或訪問一個已經存在的信號量集。

int semget(key_t key, int nsems, int oflag)

 

該函數執行成功返回信號量標示符,失敗返回-1

參數 描述
key 經過調用ftok函數獲得的鍵值
nsems 表明建立信號量的個數,若是隻是訪問而不建立則能夠指定該參數爲0,咱們一旦建立了該信號量,就不能更改其信號量個數,只要你不刪除該信號量,你就是從新調用該函數建立該鍵值的信號量,該函數只是返回之前建立的值,不會從新建立;
semflg 指定該信號量的讀寫權限,當建立信號量時不準加IPC_CREAT ,若指定IPC_CREAT

semget函數執行成功後,就產生了一個由內核維持的類型爲semid_ds結構體的信號量集,返回semid就是指向該信號量集的引索。

  • nsems>0 : 建立一個信的信號量集,指定集合中信號量的數量,一旦建立就不能更改。

  • nsems==0 : 訪問一個已存在的集合

  • 返回的是一個稱爲信號量標識符的整數,semop和semctl函數將使用它。

  • 建立成功後信號量結構被設置:

.sem_perm 的uid和gid成員被設置成的調用進程的有效用戶ID和有效組ID .oflag 參數中的讀寫權限位存入sem_perm.mode .sem_otime 被置爲0,sem_ctime被設置爲當前時間 .sem_nsems 被置爲nsems參數的值

 

該集合中的每一個信號量不初始化,這些結構是在semctl,用參數SET_VAL,SETALL初始化的。

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #include <sys/ipc.h> #define SEM_R 0400 //用戶(屬主)讀 #define SEM_A 0200 //用戶(屬主)寫 #define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6) int main(int argc,char *argv[]) { int c, oflag, semid, nsems; oflag = SVSEM_MODE | IPC_CREAT; //設置建立模式 //根據命令行參數e判斷是否制定了IPC_EXCL模式 while((c = getopt(argc,argv,"e")) != -1) { switch(c) { case 'e': oflag |= IPC_EXCL; break; } } //判斷命令行參數是否合法 if (optind != argc -2) { printf("usage: semcreate [-e] <pathname> <nsems>"); exit(-1); } //獲取信號量集合中的信號量個數 nsems = atoi(argv[optind+1]); //建立信號量,經過ftok函數建立一個key,返回信號量 標識符 semid = semget(ftok(argv[optind],0),nsems,oflag); return EXIT_SUCCESS; } 

 

關鍵字的獲取


有多種方法使客戶機和服務器在同一IPC結構上會合:
* 服務器能夠指定關鍵字IPC_PRIVATE建立一個新IPC結構,將返回的標識符存放在某處(例如一個文件)以便客戶機取用。關鍵字 IPC_PRIVATE保證服務器建立一個新IPC結構。這種技術的缺點是:服務器要將整型標識符寫到文件中,而後客戶機在此後又要讀文件取得此標識符。

IPC_PRIVATE關鍵字也可用於父、子關係進程。父進程指定 IPC_PRIVATE建立一個新IPC結構,所返回的標識符在fork後可由子進程使用。子進程可將此標識符做爲exec函數的一個參數傳給一個新程序。

  • 在一個公用頭文件中定義一個客戶機和服務器都承認的關鍵字。而後服務器指定此關鍵字建立一個新的IPC結構。這種方法的問題是該關鍵字可能已與一個 IPC結構相結合,在此狀況下,get函數(msgget、semget或shmget)出錯返回。服務器必須處理這一錯誤,刪除已存在的IPC結構,而後試着再建立它。固然,這個關鍵字不能被別的程序所佔用。

  • 客戶機和服務器認同一個路徑名和課題I D(課題I D是0 ~ 2 5 5之間的字符值) ,而後調用函數ftok將這兩個值變換爲一個關鍵字。這樣就避免了使用一個已被佔用的關鍵字的問題。
    使用ftok並不是高枕無憂。有這樣一種例外:服務器使用ftok獲取得一個關鍵字後,該文件就被刪除了,而後重建。此時客戶端以此重建後的文件來ftok所獲取的關鍵字就和服務器的關鍵字不同了。因此通常商用的軟件都不怎麼用ftok。
    通常來講,客戶機和服務器至少共享一個頭文件,因此一個比較簡單的方法是避免使用ftok,而只是在該頭文件中存放一個你們都知道的關鍵字。

設置信號量的值(PV操做)


int semop(int semid, struct sembuf *opsptr, size_t nops);

 

參數 描述
semid 是semget返回的semid信號量標示符
opsptr 指向信號量操做結構數組
nops opsptr所指向的數組中的sembuf結構體的個數

該函數執行成功返回0,失敗返回-1;

第二個參數sops爲一個結構體數組指針,結構體定義在sys/sem.h中,結構體以下

struct sembuf { unsigned short sem_num; /* semaphore index in array */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ };

 

sem_num 操做信號的下標,其值能夠爲0 到nops
sem_flg爲該信號操做的標誌:其值能夠爲0、IPC_NOWAIT 、 SEM_UNDO

sem_flg標識 描述
0 在對信號量的操做不能執行的狀況下,該操做阻塞到能夠執行爲止;
IPC_NOWAIT 在對信號量的操做不能執行的狀況下,該操做當即返回;
SEM_UNDO 當操做的進程推出後,該進程對sem進行的操做將被取消;
sem_op取值 描述
>0
  則信號量加上它的值,等價於進程釋放信號量控制的資源
=0
  若沒有設置IPC_NOWAIT, 那麼調用進程將進入睡眠狀態,直到信號量的值爲0,不然進程直接返回
<0
  則信號量加上它的值,等價於進程申請信號量控制的資源,若進程設置IPC_NOWAIT則進程再沒有可用資源狀況下,進程阻塞,不然直接返回。

例如,當前semval爲2,而sem_op = -3,那麼怎麼辦?

注意:semval是指semid_ds中的信號量集中的某個信號量的值

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #include <sys/ipc.h> int main(int argc,char *argv[]) { int c,i,flag,semid,nops; struct sembuf *ptr; flag = 0; //根據命令行參數設置操做模式 while( ( c = getopt(argc,argv,"nu")) != -1) { switch(c) { case 'n': flag |= IPC_NOWAIT; //非阻塞 break; case 'u': flag |= SEM_UNDO; //不可恢復 break; } } if(argc - optind < 2) { printf("usage: semops [-n] [-u] <pathname> operation..."); exit(0); } //打開一個已經存在的信號量集合 if((semid = semget(ftok(argv[optind],0),0,0)) == -1) { perror("semget() error"); exit(-1); } optind++; //指向當前第一個信號量的位置 nops = argc - optind; //信號量個數 ptr = calloc(nops,sizeof(struct sembuf)); for(i=0;i<nops;++i) { ptr[i].sem_num = i; //信號量變換 ptr[i].sem_op = atoi(argv[optind+i]); //設置信號量的值 ptr[i].sem_flg = flag; //設置操做模式 } //對信號量執行操做 if(semop(semid,ptr,nops) == -1) { perror("semop() error"); exit(-1); } return EXIT_SUCCESS; } 

 

對信號集實行控制操做(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 };

 

描述
IPC_STAT 讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。
IPC_SET 設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。
IPC_RMID 將信號量集從系統中刪除
GETALL 用於讀取信號量集中的全部信號量的值,存於semnu的array中
SETALL 設置所指定的信號量集的每一個成員semval的值
GETPID 返回最後一個執行semop操做的進程的PID。
LSETVAL 把的val數據成員設置爲當前資源數
GETVAL 把semval中的當前值做爲函數的返回,即現有的資源數,返回值爲非負數。

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仍是比較有用的。

示例程序


調用semctl函數設置信號量的值程序


#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #include <sys/ipc.h> //定義信號量操做共用體結構 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main(int argc,char *argv[]) { int semid,nsems,i; struct semid_ds seminfo; unsigned short *ptr; union semun arg; if(argc < 2) { printf("usage: semsetvalues <pathname>[values ...]"); exit(0); } //打開已經存在的信號量集合 semid = semget(ftok(argv[1],0),0,0); arg.buf = &seminfo; //獲取信號量集的相關信息 semctl(semid,0,IPC_STAT,arg); nsems = arg.buf->sem_nsems; //信號量的個數 if(argc != nsems + 2 ) { printf("%s semaphores in set,%d values specified",nsems,argc-2); exit(0); } //分配信號量 ptr = calloc(nsems,sizeof(unsigned short)); arg.array = ptr; //初始化信號量的值 for(i=0;i<nsems;i++) { ptr[i] = atoi(argv[i+2]); } //經過arg設置信號量集合 semctl(semid,0,SETALL,arg); return EXIT_SUCCESS; } 

 

調用semctl獲取信號量的值


#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #include <sys/ipc.h> union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main(int argc,char *argv[]) { int semid,nsems,i; struct semid_ds seminfo; unsigned short *ptr; union semun arg; if(argc != 2) { printf("usage: semgetvalues<pathname>"); exit(0); } //打開已經存在的信號量 semid = semget(ftok(argv[1], 0), 0, 0); arg.buf = &seminfo; //獲取信號量集的屬性,返回semid_ds結構 semctl(semid, 0, IPC_STAT, arg); nsems = arg.buf->sem_nsems; //信號量的數目 ptr = calloc(nsems,sizeof(unsigned short)); arg.array = ptr; //獲取信號量的值 semctl(semid, 0, GETALL, arg); for(i = 0; i < nsems; i++) { printf("semval[%d] = %d\n", i, ptr[i]); } return EXIT_SUCCESS; } 

 

經過semctl實現PV操做的函數庫

#include <semaphore.h> #include <sys/sem.h> #include <stdio.h> #include <stdlib.h> union semun { int val; struct semid_ds *buf; unsigned short *array; }; // 將信號量sem_id設置爲init_value 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("Sem init"); exit(1); } return 0; } // 刪除sem_id信號量 int del_sem(int sem_id) { union semun sem_union; if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) { perror("Sem delete"); exit(1); } return 0; } // 對sem_id執行p操做 int sem_p(int sem_id) { struct sembuf sem_buf; sem_buf.sem_num = 0;//信號量編號 sem_buf.sem_op = -1;//P操做 sem_buf.sem_flg = SEM_UNDO;//系統退出前未釋放信號量,系統自動釋放 if (semop(sem_id, &sem_buf, 1) == -1) { perror("Sem P operation"); exit(1); } return 0; } // 對sem_id執行V操做 int sem_v(int sem_id) { struct sembuf sem_buf; sem_buf.sem_num = 0; sem_buf.sem_op = 1;//V操做 sem_buf.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_buf, 1) == -1) { perror("Sem V operation"); exit(1); } return 0; } 

 

#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 <stdlib.h> #include <pthread.h> #include <stdio.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 * Producer() { while(1) { sem_wait(&p_sem_mutex); // 當緩衝池未滿時 sem_wait(&sem_mutex); // 等待緩衝池空閒 count++; sem_post(&sem_mutex); if(count < BUFF_SIZE) // 緩衝池未滿 { sem_post(&p_sem_mutex); } if(count > 0) // 緩衝池不爲空 { sem_post(&c_sem_mutex); } } } void * Consumer() { while(1) { sem_wait(&c_sem_mutex); // 緩衝池未空時 sem_wait(&sem_mutex); // 等待緩衝池空閒 count--; sem_post(&sem_mutex); if(count > 0) { sem_post(&c_sem_mutex); } } return NULL; } int main() { pthread_t ptid,ctid; // initialize the semaphores //sem_init(&empty_sem_mutex, 0, 1); //sem_init(&full_sem_mutex, 0, `0); //creating producer and consumer threads if(pthread_create(&ptid, NULL,Producer, NULL)) { printf("\n ERROR creating thread 1"); exit(1); } if(pthread_create(&ctid, NULL,Consumer, NULL)) { printf("\n ERROR creating thread 2"); exit(1); } if(pthread_join(ptid, NULL)) /* wait for the producer to finish */ { printf("\n ERROR joining thread"); exit(1); } if(pthread_join(ctid, NULL)) /* wait for consumer to finish */ { printf("\n ERROR joining thread"); exit(1); } //sem_destroy(&empty_sem_mutex); //sem_destroy(&full_sem_mutex); //exit the main thread pthread_exit(NULL); return EXIT_SUCCESS; } 
相關文章
相關標籤/搜索