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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...