上一章,講述了 V信號量,主要運行于進程之間,本章主要介紹POSIX信號量:有名信號量、無名信號量。
POSIX信號量
POSIX信號量進程是3種 IPC(Inter- ) 機制之一,3種 IPC 機制源于 POSIX.1 的實時擴展。 UNIX 將3種機制(消息隊列,信號量和共享存儲)置于可選部分中。在 SUSv4 之前,POSIX 信號量接口已經被包含在信號量選項中。在 SUSv4 中linux 信號量使用,這些接口被移至了基本規范,而消息隊列和共享存儲接口依然是可選的。
POSIX 信號量接口意在解決 XSI 信號量接口的幾個缺陷。
相比于 XSI 接口,POSIX 信號量接口考慮了更高性能的實現。
POSIX 信號量使用更簡單:沒有信號量集,在熟悉的文件系統操作后一些接口被模式化了。盡管沒有要求一定要在文件系統中實現,但是一些系統的確是這么實現的。
POSIX 信號量在刪除時表現更完美。回憶一下,當一個 XSI 信號量被刪除時,使用這個信號量標識符的操作會失敗,并將 errno 設置成 EIDRM。使用 POSIX 信號量時,操作能繼續正常工作直到該信號量的最后一次引用被釋放。
分類
POSIX信號量是一個sem_t類型的變量,但POSIX有兩種信號量的實現機制:無名信號量和命名信號量。無名信號量只可以在共享內存的情況下,比如實現進程中各個線程之間的互斥和同步,因此無名信號量也被稱作基于內存的信號量;命名信號量通常用于不共享內存的情況下,比如進程間通信。
同時,在創建信號量時,根據信號量取值的不同,POSIX信號量還可以分為:
區別
有名信號量和無名信號量的差異在于創建和銷毀的形式上,但是其他工作一樣。
無名信號量只能存在于內存中,要求使用信號量的進程必須能訪問信號量所在的這一塊內存,所以無名信號量只能應用在同一進程內的線程之間(共享進程的內存),或者不同進程中已經映射相同內存內容到它們的地址空間中的線程(即信號量所在內存被通信的進程共享)。意思是說無名信號量只能通過共享內存訪問。
相反,有名信號量可以通過名字訪問,因此可以被任何知道它們名字的進程中的線程使用。
單個進程中使用 POSIX 信號量時,無名信號量更簡單。多個進程間使用 POSIX 信號量時,有名信號量更簡單。
聯系
無論是有名信號量還是無名信號量,都可以通過以下函數進行信號量值操作。
wait(P)
wait 為信號量值減一操作,總共有三個函數,函數原型如下:
#include?
int?sem_wait(sem_t?*sem);
int?sem_trywait(sem_t?*sem);
int?sem_timedwait(sem_t?*sem,?const?struct?timespec?*abs_timeout);
Link?with?-pthread.這一句表示?gcc?編譯時,要加?-pthread.
返回值:
????若成功,返回?0?;若出錯,返回-1
結構體定義如下:
struct?timespec?{
????time_t?tv_sec;??????/*?Seconds?*/
????long???tv_nsec;?????/*?Nanoseconds?[0?..?999999999]?*/
};?
如果指定的阻塞時間到了,但是 sem 仍然小于 0 ,則會返回一個錯誤 (錯誤設置為 )。
post(V)
post 為信號量值加一操作,函數原型如下:
#include?
int?sem_post(sem_t?*sem);
Link?with?-pthread.
返回值:
???若成功,返回?0?;若出錯,返回-1
無名信號量接口函數
信號量的函數都以sem_開頭,線程中使用的基本信號函數有4個,他們都聲明在頭文件.h中,該頭文件定義了用于信號量操作的sem_t類型:
該函數用于創建信號量,原型如下:
int?sem_init(sem_t?*sem,?int?pshared,?unsigned?int?value);
功能:該函數初始化由sem指向的信號對象,設置它的共享選項,并給它一個初始的整數值。控制信號量的類型,如果其值為0,就表示信號量是當前進程的局部信號量,否則信號量就可以在多個進程間共享,value為sem的初始值。返回值:該函數調用成功返回0,失敗返回-1。
該函數用于對用完的信號量進行清理,其原型如下:
int?sem_destroy(sem_t?*sem);
返回值:
成功返回0,失敗返回-1。
函數
該函數返回當前信號量的值,通過輸出參數返回。如果當前信號量已經上鎖(即同步對象不可用),那么返回值為0,或為負數,其絕對值就是等待該信號量解鎖的線程數。
int?sem_getvalue(sem_t?*restrict,?int?*restrict);
使用實例
【實例1】:
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
sem_t?sem;
#define?handle_error(msg)???do?{?\
????????????????????????????????perror(msg);?\
????????????????????????????????exit(EXIT_FAILURE);?\
????????????????????????????}while?(0)
static?void?handler(int?sig){
write(STDOUT_FILENO,?"sem_post()?from?handler\n",?24);
if(sem_post(&sem)?==?-1)
{
????write(STDERR_FILENO,?"sem_post()?failed\n",?18);
????_exit(EXIT_FAILURE);
}}
int?main(int?argc,?char?*argv[]){
????int?s;
????struct?timespec?ts;
????struct?sigaction?sa;
????if?(argc?!=?3)
????{
?????fprintf(stderr,?"Usage:?%s??\n" ,?argv[0]);
????exit(EXIT_FAILURE);
???}
????
????if?(sem_init(&sem,?0,?0)?==?-1)
????????handle_error("sem_init");
????
????/*?Establish?SIGALRM?handler;?set?alarm?timer?using?argv[1]?*/
????sa.sa_handler?=?handler;
????sigemptyset(&sa.sa_mask);
????sa.sa_flags?=?0;
????if?(sigaction(SIGALRM,?&sa,?NULL)?==?-1)
????????handle_error("sigaction");
????
????alarm(atoi(argv[1]));
????
????/*?Calculate?relative?interval?as?current?time?plus
??????number?of?seconds?given?argv[2]?*/
????
????if?(clock_gettime(CLOCK_REALTIME,?&ts)?==?-1)
????????handle_error("clock_gettime");
????
????ts.tv_sec?+=?atoi(argv[2]);
????
????printf("main()?about?to?call?sem_timedwait()\n");
????while?((s?=?sem_timedwait(&sem,?&ts))?==?-1?&&?errno?==?EINTR)
????????continue;???????/*?Restart?if?interrupted?by?handler?*/
????
????/*?Check?what?happened?*/
????if?(s?==?-1)
????{
????????if?(errno?==?ETIMEDOUT)
????????????printf("sem_timedwait()?timed?out\n");
????????else
????????????perror("sem_timedwait");
????}
????else
????{
????????printf("sem_timedwait()?succeeded\n");
????}
????
????exit((s?==?0)???EXIT_SUCCESS?:?EXIT_FAILURE);
}
【實例2】:
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
sem_t?sem;
void?*func1(void?*arg){
????sem_wait(&sem);
????int?*running?=?(int?*)arg;
????printf("thread?func1?running?:?%d\n",?*running);
????
????pthread_exit(NULL);
}
void?*func2(void?*arg)
{
????printf("thread?func2?running.\n");
????sem_post(&sem);
????
????pthread_exit(NULL);
}
int?main(void)
{
????int?a?=?3;
????sem_init(&sem,?0,?0);
????pthread_t?thread_id[2];
????
????pthread_create(&thread_id[0],?NULL,?func1,?(void?*)&a);
????printf("main?thread?running.\n");
????sleep(10);
????pthread_create(&thread_id[1],?NULL,?func2,?(void?*)&a);
????printf("main?thread?still?running.\n");
????pthread_join(thread_id[0],?NULL);
????pthread_join(thread_id[1],?NULL);
????sem_destroy(&sem);
????
????return?0;
}
有名信號量
有時候也叫命名信號量,之所以稱為命名信號量,是因為它有一個名字、一個用戶ID、一個組ID和權限。這些是提供給不共享內存的那些進程使用命名信號量的接口。命名信號量的名字是一個遵守路徑名構造規則的字符串。
接口函數
函數
該函數用于創建或打開一個命名信號量,其原型如下:
sem_t?*sem_open(const?char?*name,?int?oflag);
sem_t?*sem_open(const?char?*name,?int?oflag,mode_t?mode,?unsigned?int?value);
參數
函數
該函數用于關閉命名信號量:
int?sem_close(sem_t?*);
功能:單個程序可以用函數關閉命名信號量,但是這樣做并不能將信號量從系統中刪除,因為命名信號量在單個程序執行之外是具有持久性的。當進程調用_exit、exit、exec或從main返回時,進程打開的命名信號量同樣會被關閉。
函數功能:函數用于在所有進程關閉了命名信號量之后,將信號量從系統中刪除:
int?sem_unlink(const?char?*name);
信號量操作函數與無名信號量一樣。
使用實例
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define?SEM_NAME?"?/sem_name"
sem_t?*p_sem;
void?*testThread(void?*ptr){
????sem_wait(p_sem);
????sleep(2);
????pthread_exit(NULL);}
????int?main(void){
????int?i?=?0;
????pthread_t?pid;
????int?sem_val?=?0;
????p_sem?=?sem_open(SEM_NAME,?O_CREAT,?0555,?5);
????
????if(p_sem?==?NULL)
????{
????????printf("sem_open?%s?failed!\n",?SEM_NAME);
????????sem_unlink(SEM_NAME);
????????return?-1;
????}
????
????for(i?=?0;?i?7;?i++)
????{
????????pthread_create(&pid,?NULL,?testThread,?NULL);
????????sleep(1);
????????//?pthread_join(pid,?NULL);??//?not?needed,?or?loop
????????sem_getvalue(p_sem,?&sem_val);
????????printf("semaphore?value?:?%d\n",?sem_val);
????}
????
????sem_close(p_sem);
????sem_unlink(SEM_NAME);
????
????return?0;
}
命名和無名信號量的持續性
命名信號量是隨內核持續的。當命名信號量創建后linux 信號量使用,即使當前沒有進程打開某個信號量,它的值依然保持,直到內核重新自舉或調用()刪除該信號量。
無名信號量的持續性要根據信號量在內存中的位置確定:
如果無名信號量是在單個進程內部的數據空間中,即信號量只能在進程內部的各個線程間共享,那么信號量是隨進程的持續性,當進程終止時他也就消失了;
如果無名信號量位于不同進程的共享內存區,因此只要該共享內存區仍然存在,該信號量就會一直存在;所以此時無名信號量是隨內核的持續性。
信號量-互斥量-條件變量
很多時候信號量、互斥量和條件變量都可以在某種應用中使用,那這三者的差異有哪些呢?下面列出了這三者之間的差異: