加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • POSIX 信號量
    • 分類
    • 區(qū)別
    • 聯(lián)系
    • 無名信號量
    • 有名信號量
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

Linux信號量(2)-POSIX 信號量

2020/10/16
290
閱讀需 25 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

上一章,講述了 SYSTEM V 信號量,主要運行于進程之間,本章主要介紹 POSIX 信號量:有名信號量、無名信號量。

POSIX 信號量

POSIX 信號量進程是 3 種 IPC(Inter-Process Communication) 機制之一,3 種 IPC 機制源于 POSIX.1 的實時擴展。Single UNIX Specification 將 3 種機制(消息隊列,信號量和共享存儲)置于可選部分中。在 SUSv4 之前,POSIX 信號量接口已經(jīng)被包含在信號量選項中。在 SUSv4 中,這些接口被移至了基本規(guī)范,而消息隊列和共享存儲接口依然是可選的。

POSIX 信號量接口意在解決 XSI 信號量接口的幾個缺陷。

  1. 相比于 XSI 接口,POSIX 信號量接口考慮了更高性能的實現(xiàn)。

  2. POSIX 信號量使用更簡單:沒有信號量集,在熟悉的文件系統(tǒng)操作后一些接口被模式化了。盡管沒有要求一定要在文件系統(tǒng)中實現(xiàn),但是一些系統(tǒng)的確是這么實現(xiàn)的。

  3. POSIX 信號量在刪除時表現(xiàn)更完美?;貞浺幌拢?dāng)一個 XSI 信號量被刪除時,使用這個信號量標(biāo)識符的操作會失敗,并將 errno 設(shè)置成 EIDRM。使用 POSIX 信號量時,操作能繼續(xù)正常工作直到該信號量的最后一次引用被釋放。

分類

POSIX 信號量是一個 sem_t 類型的變量,但 POSIX 有兩種信號量的實現(xiàn)機制:無名信號量和命名信號量。無名信號量只可以在共享內(nèi)存的情況下,比如實現(xiàn)進程中各個線程之間的互斥和同步,因此無名信號量也被稱作基于內(nèi)存的信號量;命名信號量通常用于不共享內(nèi)存的情況下,比如進程間通信。

同時,在創(chuàng)建信號量時,根據(jù)信號量取值的不同,POSIX 信號量還可以分為:

  • 二值信號量:信號量的值只有 0 和 1,這和互斥量很類似,若資源被鎖住,信號量的值為 0,若資源可用,則信號量的值為 1;

  • 計數(shù)信號量:信號量的值在 0 到一個大于 1 的限制值之間,該計數(shù)表示可用的資源的個數(shù)。

區(qū)別

有名信號量和無名信號量的差異在于創(chuàng)建和銷毀的形式上,但是其他工作一樣。

無名信號量只能存在于內(nèi)存中,要求使用信號量的進程必須能訪問信號量所在的這一塊內(nèi)存,所以無名信號量只能應(yīng)用在同一進程內(nèi)的線程之間(共享進程的內(nèi)存),或者不同進程中已經(jīng)映射相同內(nèi)存內(nèi)容到它們的地址空間中的線程(即信號量所在內(nèi)存被通信的進程共享)。意思是說無名信號量只能通過共享內(nèi)存訪問。

相反,有名信號量可以通過名字訪問,因此可以被任何知道它們名字的進程中的線程使用。

單個進程中使用 POSIX 信號量時,無名信號量更簡單。多個進程間使用 POSIX 信號量時,有名信號量更簡單。

聯(lián)系

無論是有名信號量還是無名信號量,都可以通過以下函數(shù)進行信號量值操作。

wait(P)

wait 為信號量值減一操作,總共有三個函數(shù),函數(shù)原型如下:

#include?<semaphore.h>

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
  • sem_wait 的作用是,若 sem 小于 0 ,則線程阻塞于信號量 sem ,直到 sem 大于 0 ;否則信號量值減 1。

  • sem_trywait 作用與 sem_wait 相同,只是此函數(shù)不阻塞線程,如果 sem 小于 0,直接返回一個錯誤(錯誤設(shè)置為 EAGAIN )。

  • sem_timedwait 作用也與 sem_wait 相同,第二個參數(shù)表示阻塞時間,如果 sem 小于 0 ,則會阻塞,參數(shù)指定阻塞時間長度。abs_timeout 指向一個結(jié)構(gòu)體,這個結(jié)構(gòu)體由從 1970-01-01 00:00:00 +0000 (UTC) 開始的秒數(shù)和納秒數(shù)構(gòu)成。

結(jié)構(gòu)體定義如下:

struct?timespec?{
????time_t?tv_sec;??????/*?Seconds?*/
????long???tv_nsec;?????/*?Nanoseconds?[0?..?999999999]?*/
};?

如果指定的阻塞時間到了,但是 sem 仍然小于 0 ,則會返回一個錯誤 (錯誤設(shè)置為 ETIMEDOUT )。

post(V)

post 為信號量值加一操作,函數(shù)原型如下:

#include?<semaphore.h>
int?sem_post(sem_t?*sem);
Link?with?-pthread.
返回值:
???若成功,返回?0?;若出錯,返回 -1

無名信號量

接口函數(shù)

信號量的函數(shù)都以 sem_ 開頭,線程中使用的基本信號函數(shù)有 4 個,他們都聲明在頭文件 semaphore.h 中,該頭文件定義了用于信號量操作的 sem_t 類型:

sem_init

該函數(shù)用于創(chuàng)建信號量,原型如下:

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

功能:該函數(shù)初始化由 sem 指向的信號對象,設(shè)置它的共享選項,并給它一個初始的整數(shù)值。pshared 控制信號量的類型,如果其值為 0,就表示信號量是當(dāng)前進程的局部信號量,否則信號量就可以在多個進程間共享,value 為 sem 的初始值。返回值:該函數(shù)調(diào)用成功返回 0,失敗返回 -1。

sem_destroy

該函數(shù)用于對用完的信號量進行清理,其原型如下:

int?sem_destroy(sem_t?*sem);

返回值:

成功返回 0,失敗返回 -1。

sem_getvalue 函數(shù)

該函數(shù)返回當(dāng)前信號量的值,通過 restrict 輸出參數(shù)返回。如果當(dāng)前信號量已經(jīng)上鎖(即同步對象不可用),那么返回值為 0,或為負數(shù),其絕對值就是等待該信號量解鎖的線程數(shù)。

int?sem_getvalue(sem_t?*restrict,?int?*restrict);

使用實例

【實例 1】:

#include?<time.h>

#include?<stdio.h>

#include?<errno.h>

#include?<unistd.h>

#include?<stdlib.h>

#include?<assert.h>

#include?<signal.h>

#include?<semaphore.h>

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?handlern",?24);
if(sem_post(&sem)?==?-1)
{
????write(STDERR_FILENO,?"sem_post()?failedn",?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?<alarm-secs>?<wait-secs>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?outn");
????????else
????????????perror("sem_timedwait");
????}
????else
????{
????????printf("sem_timedwait()?succeededn");
????}
????
????exit((s?==?0)???EXIT_SUCCESS?:?EXIT_FAILURE);

}

【實例 2】:

#include?<time.h>

#include?<stdio.h>

#include?<errno.h>

#include?<unistd.h>

#include?<stdlib.h>

#include?<assert.h>

#include?<signal.h>

#include?<semaphore.h>

sem_t?sem;
void?*func1(void?*arg){
????sem_wait(&sem);
????int?*running?=?(int?*)arg;
????printf("thread?func1?running?:?%dn",?*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 和權(quán)限。這些是提供給不共享內(nèi)存的那些進程使用命名信號量的接口。命名信號量的名字是一個遵守路徑名構(gòu)造規(guī)則的字符串。

接口函數(shù)

sem_open 函數(shù)

該函數(shù)用于創(chuàng)建或打開一個命名信號量,其原型如下:

sem_t?*sem_open(const?char?*name,?int?oflag);
sem_t?*sem_open(const?char?*name,?int?oflag,mode_t?mode,?unsigned?int?value);

參數(shù)

  • name 是一個標(biāo)識信號量的字符串。

  • oflag 用來確定是創(chuàng)建信號量還是連接已有的信號量。oflag 的參數(shù)可以為 0,O_CREAT 或 O_EXCL:如果為 0,表示打開一個已存在的信號量;如果為 O_CREAT,表示如果信號量不存在就創(chuàng)建一個信號量,如果存在則打開被返回,此時 mode 和 value 都需要指定;如果為 O_CREAT|O_EXCL,表示如果信號量存在則返回錯誤。

  • mode 用于創(chuàng)建信號量時指定信號量的權(quán)限位,和 open 函數(shù)一樣,包括:S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH、S_IWOTH。

  • value 表示創(chuàng)建信號量時,信號量的初始值。

sem_close 函數(shù)

該函數(shù)用于關(guān)閉命名信號量:

int?sem_close(sem_t?*);

功能:單個程序可以用 sem_close 函數(shù)關(guān)閉命名信號量,但是這樣做并不能將信號量從系統(tǒng)中刪除,因為命名信號量在單個程序執(zhí)行之外是具有持久性的。當(dāng)進程調(diào)用 _exit、exit、exec 或從 main 返回時,進程打開的命名信號量同樣會被關(guān)閉。

sem_unlink 函數(shù)功能:sem_unlink 函數(shù)用于在所有進程關(guān)閉了命名信號量之后,將信號量從系統(tǒng)中刪除:

int?sem_unlink(const?char?*name);

信號量操作函數(shù)與無名信號量一樣。

使用實例

#include?<time.h>
#include?<stdio.h>
#include?<errno.h>
#include?<fcntl.h>
#include?<unistd.h>
#include?<stdlib.h>
#include?<assert.h>
#include?<signal.h>
#include?<semaphore.h>
#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?:?%dn",?sem_val);
????}
????
????sem_close(p_sem);
????sem_unlink(SEM_NAME);
????
????return?0;
}

命名和無名信號量的持續(xù)性

命名信號量是隨內(nèi)核持續(xù)的。當(dāng)命名信號量創(chuàng)建后,即使當(dāng)前沒有進程打開某個信號量,它的值依然保持,直到內(nèi)核重新自舉或調(diào)用 sem_unlink()刪除該信號量。

無名信號量的持續(xù)性要根據(jù)信號量在內(nèi)存中的位置確定:

如果無名信號量是在單個進程內(nèi)部的數(shù)據(jù)空間中,即信號量只能在進程內(nèi)部的各個線程間共享,那么信號量是隨進程的持續(xù)性,當(dāng)進程終止時他也就消失了;

如果無名信號量位于不同進程的共享內(nèi)存區(qū),因此只要該共享內(nèi)存區(qū)仍然存在,該信號量就會一直存在;所以此時無名信號量是隨內(nèi)核的持續(xù)性。

信號量 - 互斥量 - 條件變量

很多時候信號量、互斥量和條件變量都可以在某種應(yīng)用中使用,那這三者的差異有哪些呢?下面列出了這三者之間的差異:

  • 互斥量必須由給它上鎖的線程解鎖;而信號量不需要由等待它的線程進行掛出,可以在其他進程進行掛出操作;

  • 互斥量要么被鎖住,要么被解開,只有這兩種狀態(tài);而信號量的值可以支持多個進程 / 線程成功的進行 wait 操作;

  • 信號量的掛出操作總是被記住,因為信號量有一個計數(shù)值,掛出操作總會將該計數(shù)值加 1,然而當(dāng)條件變量發(fā)送一個信號時,如果沒有線程等待在條件變量,那么該信號就會丟失。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

公眾號『一口Linux』號主彭老師,擁有15年嵌入式開發(fā)經(jīng)驗和培訓(xùn)經(jīng)驗。曾任職ZTE,某研究所,華清遠見教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專利和軟件著作。精通計算機網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實際項目出發(fā),保持原理+實踐風(fēng)格,適合Linux驅(qū)動新手入門和技術(shù)進階。