System V信號量

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

[TOC] 1. System V IPC 概述 以下三種類型的IPC合稱為System V IPC: System V信號量 System V消息隊列 System V共用記憶體 System V IPC在訪問它們的函數和內核為它們維護的信息上有一些類似點,主要包括: IPC鍵和ftok函數 ipc ...


目錄

1. System V IPC

概述

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

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

System V IPC在訪問它們的函數和內核為它們維護的信息上有一些類似點,主要包括:

  • IPC鍵和ftok函數
  • ipc_perm結構
  • 創建或打開時指定的用戶訪問許可權
  • ipcs和ipcrm命令

下表彙總了所有System V IPC函數。

  信號量 消息隊列 共用記憶體
頭文件 sys/sem.h sys/msg.h sys/shm.h
創建或打開IPC的函數 semget msgget shmget
控制IPC操作的函數 semctl msgctl shmctl
IPC操作函數 semop msgsnd
msgrcv
shmat
shmdt

IPC鍵和ftok函數

三種類型的System V IPC都使用IPC鍵作為它們的標識,IPC鍵是一個key_t類型的整數,該類型在sys/types.h中定義。
IPC鍵通常是由ftok函數賦予的,該函數把一個已存在的路徑名pathname和一個非0整數id組合轉換成一個key_t值,即IPC鍵。

#include <sys/ipc.h>

//成功返回IPC鍵,失敗返回-1
key_t ftok(const char *pathname, int id);

參數說明:

  • pathname在是程式運行期間必須穩定存在,不能反覆創建與刪除
  • id不能為0,可以是正數或者負數

ipc_perm結構

內核給每個IPC對象維護一個信息結構,即struct ipc_perm結構,該結構及System V IPC函數經常使用的常值定義在sys/ipc.h頭文件中。

struct ipc_perm
{
    uid_t   uid;   //owner's user id
    gid_t   gid;   //owner's group id
    uid_t   cuid;  //creator's group id
    gid_t   cgid;  //creator's group id
    mode_t  mode;  //read-write permissions
    ulong_t seq;   //slot usage sequence number
    key_t   key;   //IPC key
};

創建與打開IPC對象

創建或打開一個IPC對象使用相應的xxxget函數,它們都有兩個共同的參數:

  • 參數key,key_t類型的IPC鍵
  • 參數oflag,用於指定IPC對象的讀寫許可權(ipc_perm.mode),並選擇是創建一個新的IPC對象還是打開一個已存在的IPC對象

對於參數key,應用程式有兩種選擇:

  • 調用ftok,給它傳pathname和id
  • 指定key為IPC_PRIVATE,這將保證會創建一個新的、唯一的IPC對象,但該標誌不能用於打開已存在的IPC對象,只能是新建

對於參數oflag,如上所述,它包含讀寫許可權、創建或打開這兩方面信息:

  • 可以指定IPC_CREAT標誌,其含義和Posix IPC的O_CREAT一樣
  • 還可以設置為下表所示的常值來指定讀寫許可權

ipcs和ipcrm命令

  • 由於System V IPC的三種類型不是以文件系統路徑名標識的,因此無法使用ls和rm命令查看與刪除它們
  • ipcs和ipcrm分別用於查看與刪除系統中的System V IPC
usage : ipcs -asmq -tclup 
    ipcs [-s -m -q] -i id
    ipcs -h for help.
usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
          [-Q msgkey] [-M shmkey] [-S semkey] ... ]

2. System V信號量

計數信號量集

我們已經知道了Posix信號量採用計數信號量,System V信號量在此基礎上增加了一級複雜度,它採用計數信號量集,計數信號量集是由一個或多個計數信號量構成的集合。
對於系統中的每個信號量集,內核都維護一個struct semid_ds信息結構,它定義在sys/sem.h頭文件中。

struct semid_ds
{
    struct ipc_perm  sem_perm;
    struct sem       *sem_base;  //指向信號量集的指針
    ushort           sem_nsems;  //信號量集中的信號量個數
    time_t           sem_otime;  //上一次調用semop的時間
    time_t           sem_ctime;  //創建時間或上一次以IPC_SET調用semctl的時間
};

其中,sem_base是指向信號量集的指針,信號量集中的每個成員都對應一個struct sem結構:

struct sem
{
    ushort_t  semval;  //信號量的值
    short     sempid;  //上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
    ushort_t  semncnt; //等待semval變為大於當前值的線程數
    ushort_t  semzcnt; //等待semval變為0的線程數
};

semget

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

  • nsems參數指定集合中的信號量個數,如果是打開一個已存在的信號量集,就把該參數設為0
  • oflag參數可設置為IPC_CREAT,以及SEM_R和SEM_A指定的訪問許可權,如果是打開一個已存在的信號量集,就把該參數設為0
  • 成功時返回一個稱為信號量標識符的非負整數,semop和semctrl函數將使用它
//成功返回信號量標識符,失敗返回-1
int semget(key_t key, int nsems, int oflag);

當實際操作為創建一個新的信號量集時,相應semid_ds結構中與集合中每個信號量關聯的struct sem結構並不初始化,而是在以SETVAL或SETALL命令調用semctrl時初始化的。
也就是說,創建一個新的System V信號量集(semget)並將它初始化(semctl)需要兩次函數調用,
這是System V信號量的一個致命缺陷。

semop

使用semget打開一個信號量集後,對其中一個或多個信號量值的操作就使用semop函數。

//成功返回0,失敗返回-1
int semop(int semid, struct sembuf *ops, size_t nops);
  • semid指定待操作的信號量集
  • nops為集合中的信號量個數
  • ops指向一個struct sembuf類型的結構數組,該數組中的每個元素給目標信號量集中某個特定的信號量指定sem_op操作,這個特定的信號量由sem_num指定

我們只保證sembuf含有以下三個成員,不保證這三個成員的順序,也不保證還有其他成員,因此sembuf數組元素必須採用如ops[0].sem_num = 0所示的方法進行初始化。

struct sembuf
{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
};

sem_op指定的操作有三類:

  • sem_op > 0:操作內容為semval += sem_op,這對應於釋放某個信號量控制的資源
  • sem_op = 0:調用者阻塞等待直到semval變為0
  • sem_op < 0:調用者阻塞等待直到semval >= abs(sem_op),調用者阻塞等待直到semval>這對應於分配資源

sem_flg可設置的值有:0、IPC_NOWAIT、SEM_UNDO,一般使用0,對於其餘兩個值:

  • IPC_NOWAIT用於給信號量集中某個特定信號量設置非阻塞標誌
  • SEM_UNDO為System V信號量特有的複舊機制

semctl

semctl函數對一個信號量集執行各種控制操作。

//成功根據cmd返回相應的非負值,失敗返回-1
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
  • semid指定待控制的信號量集
  • semnum指定信號量集內的某個成員,僅在cmd為GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID時使用
  • cmd指定控制命令
  • arg是可選的,取決於cmd的具體值

第四個參數arg是可選的,取決於cmd的值,當需要用到該參數時,應用程式必須按如下結構定義union semun:

union semun
{
   int              val;    /* Value for SETVAL */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

定義如下術語用於後續cmd說明:

  • semval:信號量的當前值
  • sempid:上一次成功調用semop,或以SETVAL、SETALL調用semctl的進程ID
  • semncnt:等待semval變為大於當前值的線程數
  • semzcnt:等待semval變為0的線程數

System V支持下列cmd值,除非特殊說明,否則成功時返回值均為0。

cmd 說 明
GETVAL 把semval的當前值作為函數返回值返回,它是一個unsigned short類型的整數
SETVAL 把semval的值設為arg.val
GETPID 把sempid的當前值作為函數返回值返回
GETNCNT 把semncnt的當前值作為函數返回值返回
GETZCNT 把semzcnt的當前值作為函數返回值返回
GETALL 返回信號量集內每個成員的semval值,這些值通過arg.array返回,arg.array需由調用者分配記憶體
SETALL 設置信號量集內每個成員的semval值,這些值通過arg.array指定,此時第二個參數semnum設為0即可
IPC_STAT 返回信號量集當前的semid_ds結構,該結構通過arg.buf返回,arg.buf需由調用者分配記憶體,可以用該命令獲得信號量集中的信號量個數
IPC_SET 設置信號量集對應semid_ds結構中的sem_perm.uid、sem_perm.gid和sem_perm.mode,設置的值來自arg.buf
IPC_RMID 刪除由semid指定的信號量集,此時第二個參數semnum設為0即可

其中,前五個命令針對的都是信號量集semid中由semnum指定的信號量。

3. 測試程式

代碼實現

semcreate.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1
#define SEM_MODE        0666

/*
#define SEM_MODE_OWNER  SEM_R | SEM_A
#define SEM_MODE_GROUP  (SEM_R >> 3) | (SEM_A >> 3)
#define SEM_MODE_OTHER  (SEM_R >> 6) | (SEM_A >> 6)
#define SEM_MODE        (SEM_MODE_OWNER | SEM_MODE_GROUP | SEM_MODE_OTHER)
*/

int main()
{
    int nsems = 3;
    int oflag = IPC_CREAT | SEM_MODE;
    key_t key = ftok(FTOK_FILE, FTOK_ID);
    int semid = semget(key, nsems, oflag);

    if (semid >= 0)
    {
        printf("semcreate success, semid = %d\n", semid);
    }

    return 0;
}

這裡遇到個問題,SEM_MODE一開始是按註釋部分定義的,但man semget給出的三個頭文件都已經包含了,編譯時卻報錯,提示SEM_R和SEM_A未定義,不知道為什麼,只能用0666定義了。

semrmid.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

int main()
{
    key_t key = ftok(FTOK_FILE, FTOK_ID);
    int semid = semget(key, 0, 0);
    semctl(semid, 0, IPC_RMID);

    return 0;
}

semsetvalues.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    unsigned short *semvals;
    union semun arg;
    struct semid_ds seminfo;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 設置信號量集中每個信號量的值 */
    semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));

    for (i = 0; i < nsems; i++)
    {
        semvals[i] = atoi(argv[i + 1]); //通過命令行參數分別指定集合中每個信號量的值
    }

    arg.array = semvals;
    semctl(semid, 0, SETALL, arg);

    return 0;
}

semgetvalues.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    unsigned short *semvals;
    union semun arg;
    struct semid_ds seminfo;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 獲得信號量集中每個信號量的值 */
    semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
    arg.array = semvals;
    semctl(semid, 0, GETALL, arg);

    for (i = 0; i < nsems; i++)
    {
        printf("semvals[%d] = %d\n", i, semvals[i]);
    }

    return 0;
}

semops.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define FTOK_FILE       "/home/delphi/ftok.file"
#define FTOK_ID         1

union semun
{
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
};

int main(int argc, char **argv)
{
    key_t key;
    int semid;
    int nsems;
    union semun arg;
    struct semid_ds seminfo;
    struct sembuf *semops;
    int i;

    /* 打開semcreate創建的信號量集 */
    key = ftok(FTOK_FILE, FTOK_ID);
    semid = semget(key, 0, 0);

    /* 獲得信號量集中的信號量個數 */
    arg.buf = &seminfo;
    semctl(semid, 0, IPC_STAT, arg);
    nsems = arg.buf->sem_nsems;

    /* 對信號量集中的所有信號量進行相同的semop操作 */
    semops = (struct sembuf *)calloc(nsems, sizeof(struct sembuf));

    for (i = 0; i < nsems; i++)
    {
        semops[i].sem_num = i;
        semops[i].sem_op = atoi(argv[1]); //操作類型由命令行參數指定,>0, 0, <0
        semops[i].sem_flg = 0;
    }

    semop(semid, semops, nsems);

    return 0;
}

運行測試

  • 運行semcreate,通過運行前後的ipcs -s,可以確認信號量集創建成功

  • 運行semsetvalues,將三個信號量的值分別設為1、2、3
  • 然後運行semgetvalues,列印信息和我們設置的值一致,符合預期

  • 運行semops,並指定sem_op > 0
  • 然後運行semgetvalues,列印信息顯示每個信號量的值都增加了1,符合預期

  • 運行semrmid,通過運行前後的ipcs -s,可以確認信號量集已經從系統刪除


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

-Advertisement-
Play Games
更多相關文章
  • 之前的ResourceDictionary主題資源替換(一)通過載入順序來覆蓋之前的主題資源,介紹了WPF框架對ResourceDictionary資源的合併規則。 此篇介紹一種在編譯期間,實現資源替換的方案 前言 如下圖,項目中存在倆個主題資源字典,我們想通過配置一鍵修改項目的主題。 首先,我們默 ...
  • 前言:第一次寫文章,有問題請輕噴 當前使用 Net Core 版本 2.1.3 我們經常在開發中需要把實體的主鍵 Id 傳輸到前端,但是在Get的時候又不想讓前端能看到明文,我們通常會加密這些數據,所以有了這篇文章來寫一些心得。(主要是我在網上找的代碼寫得太簡單了,不符合我的需求) 這裡我用的是 N ...
  • 場景 有時需要使用配置文件保存一些配置的屬性,使其在下次打開時設置仍然生效。 這裡以對xml配置文件的讀寫為例。 1.讀取XML配置文。 2.寫入XML配置文件。 3.匹配 XPath 表達式的第一個 XmlNode。 4.獲取節點text。 5.根據xPath獲取節點個數。 6.根據xPath獲取 ...
  • 場景 在使用ZedGraph時,經常有圖形選項功能,設置曲線圖相關屬性後, 點擊保存會設置另一個窗體的屬性並刷新圖。 效果 實現 在設置圖形的選項的類中,聲明委托和事件 在當前窗體的確定按鈕的點擊事件中 將相關曲線圖的Y軸以及X軸的相關屬性存到xml配置文件中,然後調用方法 去刷新曲線圖,使其重新加 ...
  • 題目:將一個正整數分解質因數。例如:輸入90,列印出90=2*3*3*5。程式分析:對n進行分解質因數,應先找到一個最小的質數k,然後按下述步驟完成:(1)如果這個質數恰等於n,則說明分解質因數的過程已經結束,列印出即可。(2)如果n<>k,但n能被k整除,則應列印出k的值,並用n除以k的商,作為新 ...
  • 題目:列印出100-999之間所有的”水仙花數”,所謂”水仙花數”是指一個三位數,其各位數字立方和等於該數本身。例如:153是一個”水仙花數”,因為153=1的三次方+5的三次方+3的三次方。1.程式分析:利用for迴圈控制100-999個數,每個數分解出個位,十位,百位。 ...
  • 題目:判斷1至輸入數值之間有多少個素數,並輸出所有素數。1.程式分析:判斷素數的方法:用一個數分別去除2到當前數-1,如果能被整除,則表明此數不是素數,反之是素數。 ...
  • 中斷處理程式的作⽤:I/O中斷處理程式的作⽤是將發出I/O請求⽽被阻塞的進程喚醒 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...