Posix信號量

来源:https://www.cnblogs.com/songhe364826110/archive/2019/09/15/11520386.html
-Advertisement-
Play Games

[TOC] 1. Posix IPC 概述 以下三種類型的IPC合稱為Posix IPC: Posix信號量 Posix消息隊列 Posix共用記憶體 Posix IPC在訪問它們的函數和描述它們的信息上有一些類似點,主要包括: IPC名字 創建或打開時指定的讀寫許可權、創建標誌以及用戶訪問許可權 下表匯 ...


目錄

1. Posix IPC

概述

以下三種類型的IPC合稱為Posix IPC:

  • Posix信號量
  • Posix消息隊列
  • Posix共用記憶體

Posix IPC在訪問它們的函數和描述它們的信息上有一些類似點,主要包括:

  • IPC名字
  • 創建或打開時指定的讀寫許可權、創建標誌以及用戶訪問許可權

下表彙總了所有Posix IPC函數。

  信號量 消息隊列 共用記憶體
頭文件 semaphore.h mqueue.h sys/mman.h
創建、打開或刪除IPC的函數
 
 
 
 
 
sem_open
sem_close
sem_unlink
 
sem_init
sem_destroy
mq_open
mq_close
mq_unlink
 
 
 
shm_open
shm_unlink
 
 
 
 
控制IPC操作的函數
 
 
 
mq_getattr
mq_setattr
ftruncate
fstat
IPC操作函數
 
 
 
sem_wait
sem_trywait
sem_post
sem_getvalue
mq_send
mq_receive
mq_notify
 
mmap
munmap
 
 

IPC名字

除了Posix無名信號量,其餘三種類型的Posix IPC都使用"Posix IPC"名字進行標識,它可能是文件系統中真實存在的一個路徑名,也可能不是。Posix.1是這麼描述的:

  • 它必須符合系統規定的路徑名規則
  • 如果它以斜杠符開頭,那麼Posix IPC函數的不同調用將訪問同一個IPC對象;否則,具體效果取決於系統實現
  • 對IPC名字中額外斜杠符的解釋取決於系統實現

因此,為了便於代碼移植,通常在實際項目中遵循下麵兩條規則:

  • Posix IPC名字必須以一個斜杠符開頭,且不能再含有任何其他斜杠符
  • 把所有Posix IPC名字的巨集定義統一放在一個便於修改的頭文件中

創建與打開IPC

以下是三種Posix IPC的創建與打開函數:

  • sem_open用於創建或打開一個Posix有名信號量
  • mq_open用於創建或打開一個Posix消息隊列
  • shm_open用於創建或打開一個Posix共用記憶體

讀寫許可權與創建標誌

這三個函數的第二個參數都是oflag,作用是指定IPC的讀寫許可權與創建標誌,下表給出了可組合構成該參數的所有常值。

說 明 sem_open mq_open shm_open
只讀
只寫
讀寫



O_RDONLY
O_WRONLY
O_RDWR
O_RDONLY

O_RDWR
若不存在則創建
排他性創建
O_CREAT
O_EXCL
O_CREAT
O_EXCL
O_CREAT
O_EXCL
非阻塞模式
若已存在則截短
O_NONBLOCK
O_EXCL



O_TRUNC

前三行指定讀寫許可權:只讀、只寫、讀寫,從表中可以看出:

  • 有名信號量不指定該標誌
  • 消息隊列可指定任意模式
  • 共用記憶體不能以只寫方式打開

後面四行指定創建標誌:

  • O_CREAT若函數第一個參數指定的IPC不存在,則進行創建,此時至少需要第三個參數mode指定用戶訪問許可權(詳見後續)
  • O_EXCL:如果和O_CREAT一起指定,那麼當IPC已存在且指定了O_CREAT | O_EXCL標誌時,會出錯返回EEXIST
  • O_NONBLOCK:僅適用於Posix消息隊列,作用是隊列為空時的讀操作隊列為滿時的寫操作不會阻塞
  • O_TRUNC:僅適用於Posix共用記憶體,作用是當共用記憶體對象已存在時,將其長度截為0

用戶訪問許可權

創建一個新的Posix IPC時,需要使用第三個參數指定用戶訪問許可權,它是由下表所示常值按位或構成的,常值的格式為S_IRXXX和S_IWXXX,其中XXX代表訪問用戶。

常 值 說 明
S_IRUSR
S_IWUSR
用戶讀
用戶寫
S_IRGRP
S_IWGRP
組成員讀
組成員寫
S_IROTH
S_IWOTH
其他用戶讀
其他用戶寫

IPC對象的持續性

IPC對象的持續性,指的是該類型的一個對象一直存在多長時間,IPC的持續性有三類:

  • 隨進程持續:IPC對象一直存在到打開該對象的最後一個進程關閉該對象
  • 隨內核持續:IPC對象一直存在到內核重新自舉顯式刪除該對象為止
  • 隨文件系統持續:IPC對象一直存在到顯式刪除該對象為止,即使內核重新自舉,該對象依然存在

在預設情況下,除了Posix無名信號量是隨進程持續,其餘所有Posix IPC和System V IPC都是隨內核持續。

2. 信號量概述

信號量定義及分類

信號量是一種用於進程間同步或線程間同步的機制,共有三種類型的信號量IPC:

  • Posix有名信號量
  • Posix無名信號量
  • System V信號量

按信號量值的範圍,可分為:

  • 記錄信號量:信號量的值可以為負數,負數的絕對值代表當前因等待該信號量的值變為正數而阻塞的進程和線程數
  • 計數信號量:信號量的值必須是非負整數,二值信號量(信號量值只能為0或1)是其特殊情況,Linux採用計數信號量

信號量操作

  • 創建(create):創建信號量時需要指定初始值
  • 等待(wait):也叫P操作,若信號量的值大於0就將它減1並結束操作,否則就阻塞等待
  • 掛出(post):也叫V操作,該操作將信號量的值加1

信號量、互斥鎖和條件變數的差異

  • 互斥鎖必須由給他上鎖的線程解鎖,而信號量的等待和掛出沒有這種限制
  • 互斥鎖只有上鎖和解鎖兩種狀態,信號量可以有多個狀態,因為信號量的值可以有多個
  • 信號量掛出後的狀態是持續的,即使掛出時沒有線程阻塞於該信號量,掛出操作也不會丟失
  • 條件變數給線程發信號時,若沒有相應的線程阻塞,那麼給該信號將會丟失

3. Posix有名信號量

Posix有名信號量由IPC路徑名標識,因此它天生既可用於線程同步,又可用於進程同步,相關API在頭文件<semaphore.h>中,編譯時需要指定鏈接-lrt-pthread

創建和打開

sem_open用於創建一個新的信號量或打開一個已存在的信號量。

//成功返回信號量指針,失敗返回SEM_FAILED,鏈接時需指定 -lrt or -pthread
sem_t *sem_open(const char *name, int oflag, ... /*mode_t mode, unsigned int value*/);

函數參數說明在概述中基本都有介紹,這裡不再贅述,只強調兩點:

  • oflag只能指定為0、O_CREAT或O_CREAT | O_EXCL
  • value為信號量的初始值,可設範圍為[0, SEM_VALUE_MAX]

在Linux中,創建的Posix有名信號量存放在/dev/shm/目錄下,可通過ls命令查看:

#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <stdio.h>

#define POSIX_SEM_NAME  "sem_test"

int main()
{
    sem_t *sem = sem_open(POSIX_SEM_NAME, O_CREAT, 0666, 1);
    
    if (sem != SEM_FAILED)
    {
        printf("sem_open() success\n");
    } 
    
    return 0;   
}

關閉和刪除

//兩個函數返回值:成功返回0,失敗返回-1
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
  • sem_close用於關閉已經打開的有名信號量
  • sem_unlink用於從系統中刪除有名信號量

進程終止時,會自動關閉所有已打開的IPC對象(包括有名信號量、消息隊列和共用記憶體),但關閉不等於刪除,因為它們都至少具有隨內核的持續性,這一點從上面示例代碼的執行結果也可以看出來——進程已終止,但/dev/shm/目錄下剛剛創建的信號量依然存在。事實上,所有以路徑名標識的Posix IPC都有一個引用計數:

  • close和unlink會使引用計數減1
  • IPC名字本身也占用一個引用計數
  • 當引用計數大於0時,unlink就能夠從文件系統中刪除IPC對象
  • 如果在引用計數大於1時調用unlink,IPC對象會被刪除,但不會被析構
  • 只有當引用計數變為0,即在引用計數為1時調用unlink,內核才會對IPC對象進行析構
#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <stdio.h>

#define POSIX_SEM_NAME  "sem_test"

int main()
{
    sem_t *sem = sem_open(POSIX_SEM_NAME, O_CREAT, 0666, 1);
    
    if (sem != SEM_FAILED)
    {
        printf("sem_open() success\n");
        
        printf("before sem_unlink()\n");
        system("ls /dev/shm/");
        
        sem_close(sem);
        sem_unlink(POSIX_SEM_NAME);
        
        printf("after sem_unlink()\n");
        system("ls /dev/shm/");
    } 
    
    return 0;   
}

等待和掛出

//兩個函數返回值:成功返回0,失敗返回-1
int sem_wait(sem_t *sem);
int sem_post(sem_t *name);

sem_wait用於等待有名信號量:

  • 若信號量的值等於0,調用線程將阻塞,直到該值變為大於0
  • 若信號量的值大於0,就將它減1並立即返回

sem_post用於掛出有名信號量,該函數把信號量的值加1,然後阻塞於sem_wait等待該信號量的線程就能夠被喚醒。

#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

#define POSIX_SEM_NAME  "sem_test"

pthread_t tid[2];
sem_t *sem;

/*thread0先處理自己的工作,之後調用sem_post將信號量的值加1,通知thread1可以執行了*/
void *thread0(void *arg)
{
    int value;
    
    while (1)
    {
        /* do work thread0 */
        
        sem_post(sem);
        sem_getvalue(sem, &value);
        printf("thread 0: sem value is %d\n", value);
        sleep(2);
    }
}

/*thread1等待時間比thread0少,但也必須等待thread0調用sem_post將信號量的值加1,才能繼續執行*/
void *thread1(void *arg)
{
    int value;
    
    while (1)
    {
        sem_wait(sem);
        sem_getvalue(sem, &value);
        printf("thread 1: sem value is %d\n", value);
        sleep(1);
    } 
}

int main()
{    
    sem = sem_open(POSIX_SEM_NAME, O_CREAT, 0666, 0);
     
    pthread_create(&tid[0], NULL, thread0, NULL);
    pthread_create(&tid[1], NULL, thread1, NULL);  
    sleep(10);

    pthread_cancel(tid[0]);
    pthread_join(tid[0], NULL);
    
    pthread_cancel(tid[1]);
    pthread_join(tid[1], NULL);
    
    sem_close(sem);
    sem_unlink(POSIX_SEM_NAME);
    
    return 0;
}

獲取信號量的值

//成功返回0,失敗返回-1
int sem_getvalue(sem_t *sem, int *sval);

sem_getvalue用於獲取信號量sem的當前值,該值通過參數sval返回。如果有線程或進程正阻塞於sem_wait,POSIX.1-2001允許通過sval返回兩種結果:

  • 返回0,這也是Linux的選擇,因為Linux採用計數信號量
  • 返回一個負值,其絕對值代表當前阻塞於sem_wait調用的進程和線程數,對應記錄信號量

4. Posix無名信號量

Posix無名信號量是基於記憶體的信號量,也就是說它沒有IPC路徑名,而是像普通變數一樣創建在記憶體中。

  • Posix無名信號量由sem_init初始化,由sem_destroy銷毀
  • Posix無名信號量沒有close和unlink之分,銷毀即徹底刪除
  • Posix無名信號量等待、掛出、獲取信號量的值使用和有名信號量相同的API
//兩個函數返回值:成功返回0,失敗返回-1
int sem_init(sem_t *sem, int shared, unsigned int value);
int sem_destroy(sem_t *sem);

sem_init的sem參數指向要初始化的信號量,shared參數用於指定信號量線上程間共用還是在進程間共用:

  • shared = 0:線上程間共用,信號量創建在當前進程地址空間中,可用於線程間同步,隨進程持續
  • shared ≠ 0:在進程間共用,信號量必須創建在共用記憶體中,可用於進程間同步,隨內核持續

一般來說,線程間同步使用有名信號量和無名信號量都可以,而進程間同步直接使用有名信號量就可以了,除非對通訊速度有特殊需求,才考慮shared ≠ 0的無名信號量。

把第3章的示例代碼改為使用shared = 0的無名信號量,只有main函數發生了變動,如下所示:

int main()
{    
    sem = (sem_t *)malloc(sizeof(sem_t)); //這裡使用動態分配,也可以使用靜態分配sem,然後給sem_init傳&sem
    sem_init(sem, 0, 0);
     
    pthread_create(&tid[0], NULL, thread0, NULL);
    pthread_create(&tid[1], NULL, thread1, NULL);  
    sleep(10);

    pthread_cancel(tid[0]);
    pthread_join(tid[0], NULL);
    
    pthread_cancel(tid[1]);
    pthread_join(tid[1], NULL);
    
    free(sem);
    sem_destroy(sem);
    
    return 0;
}

5. Posix信號量限制

Posix定義了兩個信號量限制:

  • SEM_NSEMS_MAX:一個進程可同時打開的最大信號量個數,該值至少為256
  • SEM_VALUE_MAX:信號量的最大值,該值至少為32767

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • // 當時我裝這個也是折騰了一下午 , 所以寫一個筆記記錄一下; //如果哪裡有問題的話我們可以一起討論( qq: 2970911340,郵箱[email protected]),這也是我第一次寫博客 練練手 1. 安cmake工具 # yum install -y cmake 2. 創建mysql用戶 #usera ...
  • 一、Linux系統簡介 通過實驗一瞭解了Linux 的歷史,Linux與windows之間的區別以及學習Linux的方法。因為一直用的都是windows系統,習慣了圖形界面,而Linux是通過輸入命令執行操作,所以初學還很不適應。正如那句話說的windows能做的Linux都能做,windows不能 ...
  • 舊的小米6在抽屜吃灰半年,一直沒想好要怎麼處理,於是就想著安裝Linux。 完整教程來自https://blog.csdn.net/Greepex/article/details/85333027 原文里把每一個步驟都描述得很清楚(所以本文就不貼詳細步驟圖了,豎版截圖太影響觀感),但難免會踩一些坑。 ...
  • 常用的linux命令 ls 查看當前(或者指定)目錄下的文件列表 ls -l 查看詳細信息列表 ls -a 或ls -al 查看目錄下所有文件(包含隱藏文件)的詳細信息 cd ./ 切換到當前目錄 cd ../ 切換到上一級目錄 clear 清屏 (或者ctrl+l) / 根目錄 ~ 家目錄 cd ...
  • 打開dos命令視窗1、win+r-->運行-->cmd 2、摁住shift+滑鼠右擊 選擇 在此處打開命令視窗3、在磁碟某文件夾下,選擇標題欄中輸入框,輸入cmd 回車 windows下常用的命令 系統管理和文件管理systeminfo 獲取系統信息 系統 補丁 網卡path 查看環境變數set 查 ...
  • centOS 7安裝步驟: 1.選擇新建虛擬機,稍後安裝,linux選centos7 64位 2.位置改到存放虛擬機的文件夾 3.把硬碟空間改到40g,記憶體分到4g,1處理器2個核心 4 更改cd/dvd到鏡像位置。 5 選擇中文安裝 6選擇需要的安裝軟體,gui和gnome桌面 7 設置root密 ...
  • 背景 By 魯迅 By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex A53,雙核 3. 使用工具:Source Insight 3.5, Visio 1. 介紹 順著之前的分析,我們來到了 函數了,本以為一篇文章能搞定,大概掃了一遍代碼之後,我默默的把它拆 ...
  • [TOC] 1. 概述 定義 生產者消費者問題是線程同步的經典問題,也稱為有界緩衝區問題,問題描述大致如下: 生產者和消費者之間共用一個有界數據緩衝區 一個或多個生產者(線程或進程)向緩衝區放置數據 一個或多個消費者(線程或進程)從緩衝區取出數據 緩衝區 生產者消費者問題中的緩衝區,包括隊列緩衝區和 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...