1. 程式人生 > 其它 >Linux 系統程式設計 學習:5-程序間通訊2:System V IPC

Linux 系統程式設計 學習:5-程序間通訊2:System V IPC

todo: shm 有關例程

IPC的方式通常有:

  • Unix IPC包括:管道(pipe)、命名管道(FIFO)與訊號(Signal)
  • System V IPC:訊息佇列、訊號量、共享記憶體
  • BSD套接字:Socket(支援不同主機上的兩個程序IPC)

我們在這一講介紹System V IPC的訊號量

導言

生產者-消費者模式是一個十分經典的多執行緒併發協作的模式,弄懂生產者-消費者問題能夠讓我們對併發程式設計的理解加深。
所謂生產者-消費者問題,實際上主要是包含了兩類執行緒,一種是生產者執行緒用於生產資料,另一種是消費者執行緒用於消費資料;為了解耦生產者和消費者的關係,通常會採用共享的資料區域,就像是一個倉庫,生產者生產資料之後直接放置在共享資料區中,並不需要關心消費者的行為;而消費者只需要從共享資料區中去獲取資料,就不再需要關心生產者的行為。但是,這個共享資料區域中應該具備這樣的執行緒間併發協作的功能:

  • 如果共享資料區已滿的話,阻塞生產者繼續生產資料放置入內;
  • 如果共享資料區為空的話,阻塞消費者繼續消費資料;
  • 其實,就相當於一個FLAG,用於通訊操作前的檢測,以防止資料時序不當。

訊號量 (semaphore)

訊號量(也叫訊號燈)是為了解決同步、互斥問題的較通用的方法。

訊號量是一個計數器,常用於處理程序或執行緒的同步問題,特別是對臨界資源的同步訪問。

臨界資源可以簡單的理解為在某一時刻只能由一個程序或執行緒進行操作的資源,這裡的資源

可以是一段程式碼、一個變數或某種硬體資源。訊號量的值大於或等於0時表示可供併發程序使用的

資源實體數;小於0時代表正在等待使用臨界資源的程序數。

訊號量 的型別:

  • Posix 無名訊號量、Posix 有名訊號量、System V 訊號量。

訊號量實際是一個整數,它的值由多個程序進行測試(test)和設定(set)。就每個程序所關心的測試和設定操作而言,這兩個操作是不可中斷的,或稱“原子”操作,即一旦開始直到兩個操作全部完成。

  • 測試和設定操作的結果是:訊號量的當前值和設定值相加,其和或者是正或者為負。
  • 根據測試和設定操作的結果,一個程序可能必須睡眠,直到有另一個程序改變訊號量的值。

一個定義在核心系統中的特殊的變數:
1)該變數最小為0,如果等於0的情況下還去進行P操作,預設情況下就會阻塞
2)P操作就是減操作 proberen ("to test" or "to try")
3)V操作就是加操作 verhogen ("increase")

訊號量的有關函式:semgetsemctlsemop


System V 訊號燈使用步驟: 1)開啟/建立訊號燈:申請一個訊號量semget得到一個訊號量集合,集合中有n個元素 2)訊號燈初始化:用 semctl指定集合中第x個元素的初值 3)使用semop進行PV操作(2個程序中,一個P,一個V) 4)刪除訊號燈:semctl銷燬訊號量

建立訊號量 semget

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

intsemget(key_t key, int nsems, int semflg);

描述:建立一個訊號量

引數解析:
key:由ftok函式得到的返回值,如果指定為 IPC_PRIVATE ,則會自動產生新的鍵值(親緣程序通訊可不使用ftok)
nsems :同時建立的訊號量個數(一般只申請3個以內,幾乎總是1),成功建立後的訊號量都會加入到訊號量集中,擁有自己的編號,編號從0開始
semflg : (以下引數可以相 或)

  • IPC_CREAT 如果key對應的共享記憶體不存在,則建立
  • IPC_EXIT 如果key對應的共享記憶體存在,則報錯
  • mode 共享記憶體的訪問許可權( 在有些人的函式中,P(減)操作使用0400,V操作使用0200)

(只要key相同,那麼就對應著一個semid,一個semid中可包含nsems個訊號,可以理解成陣列)

注意,訊號量的建立與下面3個巨集有關:(這3個巨集在/proc/sys/kernel/sem中可以查到)

  • SEMMNS:系統中訊號量的最大數目,等於SEMMNI*SEMMSL
  • SEMOPM:一次semopt()操作的最大訊號量數目
  • SEMMNI:系統核心中訊號量的最大數目

返回值:
成功返回該訊號量的ID;失敗:返回 -1 ,同時設定以下errno:

  • EACCES :沒有訪問許可權
  • EEXIST :在semflg中指定了EEXIST IPC_CREAT和IPC_EXCL,但金鑰的訊號量集已存在。
  • EINVAL nsems小於0或大於每個訊號量集(SEMMSL)的訊號量限制。
  • EINVAL:與金鑰對應的訊號量集已存在,但NSEM大於該集中的訊號量數。
  • ENONET:key不存在訊號量集,並且semflg未指定IPC_CREAT。
  • ENOMEM:系統沒有足夠的記憶體申請訊號量。
  • ENOSPC:超過系統SEMMNI(最大訊號量集)、或SEMMNS(系統範圍最大訊號量)限制

控制訊號量 semctl

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


intsemctl(int semid, int semnum, int cmd, ... /*union semun arg*/);

/*使用時,應該先宣告以下共用體(每種成員對應一個 #cmd)*/
union semun {
	int              val;    /* SETVAL 用的值 */
	structsemid_ds *buf;    /* IPC_STAT、IPC_SET用的semid_ds結構 */
	unsigned short  *array;  /* SETALL、GETALL用的陣列值 */
	structseminfo  *__buf;  /* 為控制IPC_INFO提供的快取 (Linux-specific) */
};

描述:控制訊號量中的一個成員(常用於初始化訊號量的值)

引數解析
semid : 訊號量的ID
semnum: 訊號量中訊號的編號值(從0開始)
cmd :

  • IPC_STAT 獲取屬性資訊 // 不想講太詳細,這部分用得上的地方少之又少
  • IPC_SET 設定屬性資訊 // 同上
  • IPC_RMID 刪除訊號量,忽略第二個引數
  • GEDVAL 返回該訊號量元素的值
  • SETALL 設定所有訊號量元素的值(忽略引數semnum)
  • SETVAL 設定該訊號量元素的值

arg:(第四個函式應該是union semun arg)

返回值:
失敗: -1,設定errno:

  • EACCES:引數cmd有GETALL、GETPID、GETVAL、GETNCNT、GETZCNT、IPC_STAT、SEM_STAT、SETALL或SETVAL其中一個值,呼叫程序對訊號量集沒有所需的許可權,並且沒有CAP_IPC_OWNER功能。
  • EFAULT:arg.buf或arg.array指向的地址不可訪問。
  • EIDRM:訊號量集已被刪除。
  • EINVAL:cmd或semid的值無效。或者:對於SEM_STAT操作,在semid中指定的索引值引用了當前未使用的陣列槽。
  • EPERM:引數cmd設了IPC_SET或IPC_RMID,但呼叫程序的有效使用者ID不是訊號量集的建立者(如sem_perm.cuid中所示)或所有者(如sem_perm.uid中所示),並且程序不具有CAP_SYS_ADMIN功能。
  • ERANGE:引數cmd的值為SETALL或SETVAL,要設定semval的值(對於集合的某些訊號量)小於0或大於實現限制SEMVMX。

成功:

  • GETNCNT:semncnt的值。
  • GETPID:sempid的值。
  • GETVAL:semval的值。
  • GETZCNT:semzcnt的值。
  • IPC_INFO:核心內部陣列中記錄所有訊號量集資訊的最高使用項的索引。(此資訊可與重複的SEM-STAT操作一起使用,以獲取有關係統上所有訊號量集的資訊。)
  • SEM_INFO:參考 IPC_INFO。
  • SEM_STAT:訊號量集的識別符號,其索引是在semid中給定的。
  • 其他命令成功時返回 0

操作訊號量 semop

在linux下,pv操作通過呼叫函式semop實現。

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

intsemop(int semid, struct sembuf *sops, size_t nsops);

intsemtimedop(int semid, struct sembuf *sops, size_t nsops,
              const struct timespec *timeout);

/* 用到了以下的結構體 */
structsembuf{
	unsigned short sem_num;  /* semaphore number */
	short          sem_op;   /* semaphore operation */
	short          sem_flg;  /* operation flags */
};

描述: 對semidsemid的訊號量進行批量操作(操作一個或一組訊號,P減V加)。

P操作先對訊號量元素進行檢查,看看其是否大於0,如果不,在設定SEM_UNDO時被系統記錄,且不會導致程序阻塞,(未設定時導致程序阻塞,直到訊號量元素值大於0解除阻塞)

V操作使得訊號量元素指加一,該操作永遠不會導致阻塞。

引數解析
semid : 要進行操作的訊號量(由semget函式得到)
sops : 訊號量操作結構體

  • sem_num:訊號量集合中的訊號量編號,0代表第1個訊號量
  • sem_op 操作型別(sem_op的值分為3類):

sem_op > 0:將值新增到semval上,對應與釋放某個資源。 (V操作)
sem_op = 0:希望等待到semval值變為0,如果已經是0,則立即返回,否則semzcnt+1,併線程阻塞。 (等0操作)
sem_op < 0:希望等待到semval值變為大於或等於|sem_op|。這對應分配資源。(P操作)如果已經滿足條件,則semval減去sem_op的絕對值,否則 semncnt+1並且執行緒投入睡眠。

  • sem_flg 設定訊號量的預設操作

IPC_NOWAIT : 設定訊號量操作不等待
SEM_UNDO :選項會讓核心記錄一個與呼叫程序相關的UNDO記錄,如果該程序崩潰,則根據這個程序的UNDO記錄自動恢復相應訊號量的計數值

nsops :進行操作訊號量的個數,即sops結構變數的個數,需大於或等於1。(最常見設定此值等於1,只完成對一個訊號量的操作)

返回值:如果所有的操作都執行,則成功返回0。失敗返回-1,設定errno:

  • E2BIG:引數nsops大於SEMOPM,即每個系統呼叫允許的最大運算元。

  • EACCES:沒有操作的許可權,並且沒有CAP_IPC_OWNER功能。

  • EAGAIN:操作無法立即繼續,並且在sem flg中指定了IPC NOWAIT,或者在timeout中指定的時間限制已過期。

  • EFAULT :在sops或timeout引數中指定的地址不可訪問。

  • EFBIG:對於某些操作,sem_num的值小於0或大於或等於集合中的訊號量數。

  • EIDRM :訊號量集已被刪除。

  • EINTR:在這個系統呼叫中阻塞時,執行緒捕獲到一個訊號;參考 signal。

  • EINVAL:訊號量集不存在,或者semid小於零,或者nsops是非正值。

  • ENOMEM:某些操作的sem flg指定了SEM_UNDO,系統沒有足夠的記憶體來分配UNDO結構。

  • ERANGE :對於某些操作,sem_op+semval大於SEMVMX,semval的與實現相關的最大值。

PV 操作的範例

c

intsem_p(int sem_id, int semnum){
        // P操作,操作時會檢查訊號量編號為#semnum的訊號量-1,若值在操作前已經為0,阻塞,等到非0為止
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intsem_v(int sem_id, int semnum){
        // V操作,操作時讓訊號量編號為#semnum的訊號量值+1
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

訊號量的應用例項:

父子程序中使用 訊號量。

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  sem.c
#    Created  :  Thu 19 Mar 2020 04:10:48 PM CST
*/

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

intsem_p(int sem_id, int semnum){
    // P操作,操作時會檢查訊號量編號為#semnum的訊號量-1,若值在操作前已經為0,阻塞,等到非0為止
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intsem_v(int sem_id, int semnum){
    // V操作,操作時讓訊號量編號為#semnum的訊號量值+1
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intmain(int argc, char *argv[]){
    pid_t pid;
    int semid, ret;
    structsembufsops;

    // ftok("/tmp/", 0x123);
    semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    printf("Sem get id is %d.\n", semid);

    ret = semctl(semid, 0, SETVAL, 0);
    printf("Init sem : count is 1 with value 0\n");

    if(( pid = fork() ) == -1 ) { perror("fork"); }
    if(pid == 0) // son
    {
        printf("Creat son progress for sem_v(+1)\n");
        sem_v(semid,0);

        printf("sem_v(+1) passed.\n");
    }else{ // father
        printf("Father progress ofr sem_p(-1)\n");
        sleep(5);
        sem_p(semid,0);
        printf("sem_p(-1) passed.\n");
        wait(NULL);
    }

    semctl(semid, 0, IPC_RMID);
    return 0;
}

todo: shm 有關例程

IPC的方式通常有:

  • Unix IPC包括:管道(pipe)、命名管道(FIFO)與訊號(Signal)
  • System V IPC:訊息佇列、訊號量、共享記憶體
  • BSD套接字:Socket(支援不同主機上的兩個程序IPC)

我們在這一講介紹System V IPC的訊號量

導言

生產者-消費者模式是一個十分經典的多執行緒併發協作的模式,弄懂生產者-消費者問題能夠讓我們對併發程式設計的理解加深。
所謂生產者-消費者問題,實際上主要是包含了兩類執行緒,一種是生產者執行緒用於生產資料,另一種是消費者執行緒用於消費資料;為了解耦生產者和消費者的關係,通常會採用共享的資料區域,就像是一個倉庫,生產者生產資料之後直接放置在共享資料區中,並不需要關心消費者的行為;而消費者只需要從共享資料區中去獲取資料,就不再需要關心生產者的行為。但是,這個共享資料區域中應該具備這樣的執行緒間併發協作的功能:

  • 如果共享資料區已滿的話,阻塞生產者繼續生產資料放置入內;
  • 如果共享資料區為空的話,阻塞消費者繼續消費資料;
  • 其實,就相當於一個FLAG,用於通訊操作前的檢測,以防止資料時序不當。

訊號量 (semaphore)

訊號量(也叫訊號燈)是為了解決同步、互斥問題的較通用的方法。

訊號量是一個計數器,常用於處理程序或執行緒的同步問題,特別是對臨界資源的同步訪問。

臨界資源可以簡單的理解為在某一時刻只能由一個程序或執行緒進行操作的資源,這裡的資源

可以是一段程式碼、一個變數或某種硬體資源。訊號量的值大於或等於0時表示可供併發程序使用的

資源實體數;小於0時代表正在等待使用臨界資源的程序數。

訊號量 的型別:

  • Posix 無名訊號量、Posix 有名訊號量、System V 訊號量。

訊號量實際是一個整數,它的值由多個程序進行測試(test)和設定(set)。就每個程序所關心的測試和設定操作而言,這兩個操作是不可中斷的,或稱“原子”操作,即一旦開始直到兩個操作全部完成。

  • 測試和設定操作的結果是:訊號量的當前值和設定值相加,其和或者是正或者為負。
  • 根據測試和設定操作的結果,一個程序可能必須睡眠,直到有另一個程序改變訊號量的值。

一個定義在核心系統中的特殊的變數:
1)該變數最小為0,如果等於0的情況下還去進行P操作,預設情況下就會阻塞
2)P操作就是減操作 proberen ("to test" or "to try")
3)V操作就是加操作 verhogen ("increase")

訊號量的有關函式:semgetsemctlsemop


System V 訊號燈使用步驟: 1)開啟/建立訊號燈:申請一個訊號量semget得到一個訊號量集合,集合中有n個元素 2)訊號燈初始化:用 semctl指定集合中第x個元素的初值 3)使用semop進行PV操作(2個程序中,一個P,一個V) 4)刪除訊號燈:semctl銷燬訊號量

建立訊號量 semget

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

intsemget(key_t key, int nsems, int semflg);

描述:建立一個訊號量

引數解析:
key:由ftok函式得到的返回值,如果指定為 IPC_PRIVATE ,則會自動產生新的鍵值(親緣程序通訊可不使用ftok)
nsems :同時建立的訊號量個數(一般只申請3個以內,幾乎總是1),成功建立後的訊號量都會加入到訊號量集中,擁有自己的編號,編號從0開始
semflg : (以下引數可以相 或)

  • IPC_CREAT 如果key對應的共享記憶體不存在,則建立
  • IPC_EXIT 如果key對應的共享記憶體存在,則報錯
  • mode 共享記憶體的訪問許可權( 在有些人的函式中,P(減)操作使用0400,V操作使用0200)

(只要key相同,那麼就對應著一個semid,一個semid中可包含nsems個訊號,可以理解成陣列)

注意,訊號量的建立與下面3個巨集有關:(這3個巨集在/proc/sys/kernel/sem中可以查到)

  • SEMMNS:系統中訊號量的最大數目,等於SEMMNI*SEMMSL
  • SEMOPM:一次semopt()操作的最大訊號量數目
  • SEMMNI:系統核心中訊號量的最大數目

返回值:
成功返回該訊號量的ID;失敗:返回 -1 ,同時設定以下errno:

  • EACCES :沒有訪問許可權
  • EEXIST :在semflg中指定了EEXIST IPC_CREAT和IPC_EXCL,但金鑰的訊號量集已存在。
  • EINVAL nsems小於0或大於每個訊號量集(SEMMSL)的訊號量限制。
  • EINVAL:與金鑰對應的訊號量集已存在,但NSEM大於該集中的訊號量數。
  • ENONET:key不存在訊號量集,並且semflg未指定IPC_CREAT。
  • ENOMEM:系統沒有足夠的記憶體申請訊號量。
  • ENOSPC:超過系統SEMMNI(最大訊號量集)、或SEMMNS(系統範圍最大訊號量)限制

控制訊號量 semctl

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


intsemctl(int semid, int semnum, int cmd, ... /*union semun arg*/);

/*使用時,應該先宣告以下共用體(每種成員對應一個 #cmd)*/
union semun {
	int              val;    /* SETVAL 用的值 */
	structsemid_ds *buf;    /* IPC_STAT、IPC_SET用的semid_ds結構 */
	unsigned short  *array;  /* SETALL、GETALL用的陣列值 */
	structseminfo  *__buf;  /* 為控制IPC_INFO提供的快取 (Linux-specific) */
};

描述:控制訊號量中的一個成員(常用於初始化訊號量的值)

引數解析
semid : 訊號量的ID
semnum: 訊號量中訊號的編號值(從0開始)
cmd :

  • IPC_STAT 獲取屬性資訊 // 不想講太詳細,這部分用得上的地方少之又少
  • IPC_SET 設定屬性資訊 // 同上
  • IPC_RMID 刪除訊號量,忽略第二個引數
  • GEDVAL 返回該訊號量元素的值
  • SETALL 設定所有訊號量元素的值(忽略引數semnum)
  • SETVAL 設定該訊號量元素的值

arg:(第四個函式應該是union semun arg)

返回值:
失敗: -1,設定errno:

  • EACCES:引數cmd有GETALL、GETPID、GETVAL、GETNCNT、GETZCNT、IPC_STAT、SEM_STAT、SETALL或SETVAL其中一個值,呼叫程序對訊號量集沒有所需的許可權,並且沒有CAP_IPC_OWNER功能。
  • EFAULT:arg.buf或arg.array指向的地址不可訪問。
  • EIDRM:訊號量集已被刪除。
  • EINVAL:cmd或semid的值無效。或者:對於SEM_STAT操作,在semid中指定的索引值引用了當前未使用的陣列槽。
  • EPERM:引數cmd設了IPC_SET或IPC_RMID,但呼叫程序的有效使用者ID不是訊號量集的建立者(如sem_perm.cuid中所示)或所有者(如sem_perm.uid中所示),並且程序不具有CAP_SYS_ADMIN功能。
  • ERANGE:引數cmd的值為SETALL或SETVAL,要設定semval的值(對於集合的某些訊號量)小於0或大於實現限制SEMVMX。

成功:

  • GETNCNT:semncnt的值。
  • GETPID:sempid的值。
  • GETVAL:semval的值。
  • GETZCNT:semzcnt的值。
  • IPC_INFO:核心內部陣列中記錄所有訊號量集資訊的最高使用項的索引。(此資訊可與重複的SEM-STAT操作一起使用,以獲取有關係統上所有訊號量集的資訊。)
  • SEM_INFO:參考 IPC_INFO。
  • SEM_STAT:訊號量集的識別符號,其索引是在semid中給定的。
  • 其他命令成功時返回 0

操作訊號量 semop

在linux下,pv操作通過呼叫函式semop實現。

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

intsemop(int semid, struct sembuf *sops, size_t nsops);

intsemtimedop(int semid, struct sembuf *sops, size_t nsops,
              const struct timespec *timeout);

/* 用到了以下的結構體 */
structsembuf{
	unsigned short sem_num;  /* semaphore number */
	short          sem_op;   /* semaphore operation */
	short          sem_flg;  /* operation flags */
};

描述: 對semidsemid的訊號量進行批量操作(操作一個或一組訊號,P減V加)。

P操作先對訊號量元素進行檢查,看看其是否大於0,如果不,在設定SEM_UNDO時被系統記錄,且不會導致程序阻塞,(未設定時導致程序阻塞,直到訊號量元素值大於0解除阻塞)

V操作使得訊號量元素指加一,該操作永遠不會導致阻塞。

引數解析
semid : 要進行操作的訊號量(由semget函式得到)
sops : 訊號量操作結構體

  • sem_num:訊號量集合中的訊號量編號,0代表第1個訊號量
  • sem_op 操作型別(sem_op的值分為3類):

sem_op > 0:將值新增到semval上,對應與釋放某個資源。 (V操作)
sem_op = 0:希望等待到semval值變為0,如果已經是0,則立即返回,否則semzcnt+1,併線程阻塞。 (等0操作)
sem_op < 0:希望等待到semval值變為大於或等於|sem_op|。這對應分配資源。(P操作)如果已經滿足條件,則semval減去sem_op的絕對值,否則 semncnt+1並且執行緒投入睡眠。

  • sem_flg 設定訊號量的預設操作

IPC_NOWAIT : 設定訊號量操作不等待
SEM_UNDO :選項會讓核心記錄一個與呼叫程序相關的UNDO記錄,如果該程序崩潰,則根據這個程序的UNDO記錄自動恢復相應訊號量的計數值

nsops :進行操作訊號量的個數,即sops結構變數的個數,需大於或等於1。(最常見設定此值等於1,只完成對一個訊號量的操作)

返回值:如果所有的操作都執行,則成功返回0。失敗返回-1,設定errno:

  • E2BIG:引數nsops大於SEMOPM,即每個系統呼叫允許的最大運算元。

  • EACCES:沒有操作的許可權,並且沒有CAP_IPC_OWNER功能。

  • EAGAIN:操作無法立即繼續,並且在sem flg中指定了IPC NOWAIT,或者在timeout中指定的時間限制已過期。

  • EFAULT :在sops或timeout引數中指定的地址不可訪問。

  • EFBIG:對於某些操作,sem_num的值小於0或大於或等於集合中的訊號量數。

  • EIDRM :訊號量集已被刪除。

  • EINTR:在這個系統呼叫中阻塞時,執行緒捕獲到一個訊號;參考 signal。

  • EINVAL:訊號量集不存在,或者semid小於零,或者nsops是非正值。

  • ENOMEM:某些操作的sem flg指定了SEM_UNDO,系統沒有足夠的記憶體來分配UNDO結構。

  • ERANGE :對於某些操作,sem_op+semval大於SEMVMX,semval的與實現相關的最大值。

PV 操作的範例

c

intsem_p(int sem_id, int semnum){
        // P操作,操作時會檢查訊號量編號為#semnum的訊號量-1,若值在操作前已經為0,阻塞,等到非0為止
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intsem_v(int sem_id, int semnum){
        // V操作,操作時讓訊號量編號為#semnum的訊號量值+1
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

訊號量的應用例項:

父子程序中使用 訊號量。

c
/*
#    Copyright By Schips, All Rights Reserved
#    https://gitee.com/schips/
#
#    File Name:  sem.c
#    Created  :  Thu 19 Mar 2020 04:10:48 PM CST
*/

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

intsem_p(int sem_id, int semnum){
    // P操作,操作時會檢查訊號量編號為#semnum的訊號量-1,若值在操作前已經為0,阻塞,等到非0為止
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = -1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intsem_v(int sem_id, int semnum){
    // V操作,操作時讓訊號量編號為#semnum的訊號量值+1
    static structsembufmysembuf;

    mysembuf.sem_num = semnum;
    mysembuf.sem_op = 1;
    mysembuf.sem_flg = 0;

    return semop( sem_id, &mysembuf, 1);
}

intmain(int argc, char *argv[]){
    pid_t pid;
    int semid, ret;
    structsembufsops;

    // ftok("/tmp/", 0x123);
    semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    printf("Sem get id is %d.\n", semid);

    ret = semctl(semid, 0, SETVAL, 0);
    printf("Init sem : count is 1 with value 0\n");

    if(( pid = fork() ) == -1 ) { perror("fork"); }
    if(pid == 0) // son
    {
        printf("Creat son progress for sem_v(+1)\n");
        sem_v(semid,0);

        printf("sem_v(+1) passed.\n");
    }else{ // father
        printf("Father progress ofr sem_p(-1)\n");
        sleep(5);
        sem_p(semid,0);
        printf("sem_p(-1) passed.\n");
        wait(NULL);
    }

    semctl(semid, 0, IPC_RMID);
    return 0;
}