1. 程式人生 > >【Linux】程序間通訊(IPC)之訊號量詳解與測試用例

【Linux】程序間通訊(IPC)之訊號量詳解與測試用例

學習環境centos6.5 Linux核心2.6

程序間通訊概述

1. 程序通訊機制

一般情況下,系統中執行著大量的程序,而每個程序之間並不是相互獨立的,有些程序之間經常需要互相傳遞訊息。但是每個程序在系統中都有自己的地址空間,作業系統通過頁表和實際實體記憶體所關聯,不允許其他程序隨意進入。因此,就必須有一種機制既能保證程序之間的通訊,又能保證系統的安全,即程序間通訊機制——I P C (Inter_Process Communication)

Linux中的記憶體空間分為系統空間使用者空間。在系統空間中,由於各個執行緒的地址空間都是共享的,即一個執行緒能夠隨意訪問kernel中的任意地址,所以無需程序通訊機制的保護。而在使用者空間

中,每個程序都有自己的地址空間,一個程序為了與其他程序通訊,必須陷入到有足夠許可權訪問其他程序空間的kernel中,從而與其他程序進行通訊。在Linux中支援System V 程序通訊的手段有三種:訊息佇列(Message queue)、訊號量(Semaphore)、共享記憶體(Shared memory)。

2. 程序通訊物件標示符和鍵

在kernel中,對每一類I P C 物件,都由一個非負整數來索引。為了識別並唯一標識各個程序通訊的物件,需要一個識別符號(即IPC標示符)來標識各個通訊物件。而為了獲取一個獨一無二的通訊物件,必須使用(可使用ftok( )函式生成,返回值key)。這裡的鍵是用來定位I P C 物件的識別符號的。

背景知識

1. 原子操作(atomic operation)

原子操作意為不可被中斷的一個或一系列操作,也可以理解為就是一件事情要麼做了,要麼沒做。而原子操作的實現,一般是依靠硬體來實現的。

2. 同步與互斥

同步:在訪問資源的時候,以某種特定順序的方式去訪問資源
互斥:一個資源每次只能被一個程序所訪問。

同步與互斥是保證在高效率執行的同時,可以正確執行。大部分情況下同步是在互斥的基礎上進行的。

3. 臨界資源

不同程序能夠看到的一份公共的資源(如:印表機,磁帶機等),且一次僅允許一個程序使用的資源稱為臨界資源

4. 臨界區

臨界區是一段程式碼

,在這段程式碼中程序將訪問臨界資源(例如:公用的裝置或是儲存器),當有程序進入臨界區時,其他程序必須等待,有一些同步的機制必須在臨界區段的進入點和離開點實現,確保這些共用資源被互斥所獲得。

5. 相關命令

  • ipcs -s 顯示已存在的訊號量
  • ipcrm -s 刪除指定訊號量

注意:有時候因為許可權問題需要在root下檢視與刪除。

什麼是訊號量(Semaphore)

訊號量(Semaphore)可以被看做是一種具有原子操作的計數器,它控制多個程序對共享資源的訪問,通常描述臨界資源當中,臨界資源的數目,常常被當做(lock)來使用,防止一個程序訪問另外一個程序正在使用的資源。

訊號量本身不具有資料交換的功能,而是控制其他資源來實現程序間通訊,在此過程中負責資料操作操作的互斥同步等功能。

簡言之:訊號量的主要目的是為了保護臨界資源。

1. 為什麼要使用訊號量

為了防止出現因多個程序同時訪問一個共享資源而引發的問題,我們需要一種方法,可以通過生成並使用令牌來授權,在任一時刻只能有一個執行流訪問程式碼的臨界區域。而訊號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時刻只有一個執行流在訪問它。

2. 訊號量的工作原理

  1. 測試控制該資源的訊號量。

  2. 若此訊號量的值為正,則程序可以使用該資源。程序將訊號量值減1,,表示一個資源被使用。

  3. 若此訊號量的值為0,則程序進入休眠狀態,直至訊號量值大於0,程序被喚醒,從新進入第1步。

  4. 當程序不再使用由一個訊號控制的共享資源時,該訊號量值增1,如果有程序正在休眠等待該訊號量,則會被喚醒。

為了正確地實現訊號量,訊號量的操作應是原子操作,所以訊號量通常是在核心中實現的。

3. Linux的訊號量機制

  1. 在System V中訊號量並非是單個非負值,而必須將訊號量定義為含有一個或多個訊號量值的集合。當建立一個訊號量時,要指定該集合中訊號量值的數量。

  2. 建立訊號量(semget)和對訊號量賦初值(semctl)分開進行,這是一個弱點,因為不能原子地建立一個訊號量集合,並且對該集合中各個訊號量賦初值。

  3. 即使沒有程序在使用I P C資源,它們仍然是存在的,要時刻防止資源被鎖定,避免程式在異常情況下結束時沒有解鎖資源,可以使用關鍵字(SEM_UNDO )在退出時恢復訊號量值為初始值。

相關函式

1、ftok函式

#include <sys/ipc.h>
#include <sys/types.h>
key_t ftok(const char* path, int id);
  • ftok 函式把一個已存在的路徑名和一個整數標識轉換成一個key_t值,即IPC關鍵字

  • path 引數就是你指定的檔名(已經存在的檔名),一般使用當前目錄。當產生鍵時,只使用id引數的低8位。

  • id 是子序號, 只使用8bit (1-255)

  • 返回值:若成功返回鍵值,若出錯返回(key_t)-1

在一般的UNIX實現中,是將檔案的索引節點號取出(inode),前面加上子序號的到key_t的返回值

2、semget函式

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget( key_t key, int nsems, int semflg);
  • 用來建立一個訊號集,或者獲取已存在的訊號集。

  • key: 所建立或開啟訊號量集的鍵值(ftok成果執行的返回值)。

  • nsems:建立的訊號量集中的訊號量個數,該引數只在建立訊號量時有效。

  • semflg :呼叫函式的操作型別,也可用於設定訊號量集的訪問許可權,通過or運算使用。

    • IPC_CREAT | IPC _EXCL | 0666 :一般用於建立,可保證返回一個新的ID,同時制定許可權為666
    • IPC_CREAT : 用於獲取一個已經存在的ID
  • 返回值:成功返回訊號量集的識別符號,失敗返回-1,errno被設定成以下的某個值:

    • EACESS : 沒有訪問該訊號量集的許可權。
    • EEXIST:訊號量集已經存在,無法建立。
    • EINVAL:引數nsems的值小於0,或者大於該訊號量集的限制,或者是該key關聯的訊號量以存在,並且nsems的值大於該訊號量集的訊號量數。
    • ENOENT:訊號量集不存在,同時沒有使用,IPC_CREAT。
    • ENOMEM:沒有足夠的記憶體建立新的訊號量集。

3、semctl函式

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd, ...);
  • 用來初始化訊號集,或者刪除訊號集。

  • semid:訊號量集I P C 識別符號。

  • semun:操作訊號在訊號集中的編號,第一個訊號的號是0.

  • cmd:在semid指定的訊號量集合上執行此命令。

  • 第四個引數是可選的,如果使用該引數,則其型別是semun,它是多個特定命令引數的聯合(union):

union semun
{
    int val;    
    struct semid_ds * buf;
    unsigned short * array;
    struct seminfo * __buf;
};
  • 第三個引數cmd常用命令:

    • IPC_SEAT:對此集合取semid_ds 結構,並存放在由arg.buf指向的結構中。
    • IPC_RMID:從系統中刪除該訊號量集合。
    • SETVAL:設定訊號量集中的一個單獨的訊號量的值,此時需要傳入第四個引數。
  • 返回值:成功返回一個正數,失敗返回-1。

4、 semop函式

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf * sops, unsigned nsops);
  • 功能:操作一個或一組訊號。也可以叫PV操作

  • semid:訊號集的識別碼,可以通過semget獲取。

  • sops:是一個指標,指向一個訊號量運算元組。訊號量操作由結構體sembuf 結構表示如下:

struct sembuf
{
    unsigned short sem_num;  // 在訊號集中的編碼 01... nsems-1
    short  sem_op;     //操作  負值或正值
    short  sem_flg;    // IPC_NOWAIT, SEM_UNDO
};
  • sembuf結構體引數說明:

    1. sem_num:操作訊號在訊號集中的編號,第一個訊號的編號是0,最後一個訊號的編號是nsems-1。

    2. sem_op:操作訊號量

      • 若sem_op 為負(P操作), 其絕對值又大於訊號的現有值,操作將會阻塞,直到訊號值大於或等於sem_op的絕對值。通常用於獲取資源的使用權。

      • 若sem_op 為正(V操作), 該值會加到現有的訊號內值上。通常用於釋放所控制資源的使用權。

      • sem_op的值為0:如果沒有設定IPC_NOWAIT,則呼叫該操作的程序或執行緒將暫時睡眠,直到訊號量的值為0;否則程序或執行緒會返回錯誤EAGAIN。
    3. sem_flg: 訊號操作標識,有如下兩種選擇:

      • IPC_NOWAIT:對訊號的操作不能滿足時,semop()不會阻塞,並立即返回,同時設定錯誤資訊。

      • SEM_UNDO:程式結束時(正常退出或異常終止),保證訊號值會被重設為semop()呼叫前的值。避免程式在異常情況下結束時未解鎖鎖定的資源,早成資源被永遠鎖定。造成死鎖。

  • nsops:訊號操作結構的數量,恆大於或等於1.

  • 返回值:成功執行時,都會回0,失敗返回-1,並設定errno錯誤資訊。

程式碼演示

(1)、目的闡述:使用P操作和V操作控制臨界區,實現兩個程序(父程序與子程序)列印不同的文字,在一個程序進入臨界區時,另一程序等待。下面使用的測試是,讓子程序在它的臨界區打印出(你好:),讓父程序打印出(在嗎?)。

(2)、在沒有使用訊號量控制時,打印出來的漢字順序不是通順的話。在加入PV操作後,可以保證一個程序列印完,另外一個程序繼續列印剩下的漢字,而不會互相交叉。

PV操作成功列印截圖示例

PV成功

無PV操作時列印截圖
無PV

注:
1. 對於第一次進入臨界區時父程序先進入還子程序先進入,與作業系統的程序排程演算法有關。
2. 沒有在程式中設定跳出迴圈條件,可以ctrl+c 結束後,用命令去刪除訊號量集。

程式碼:

mysem_h

#ifndef _MYSEM_H_
#define _MYSEM_H_

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

#define PATHNAME "."  // ftok 中生成key值 . 表示當前路徑
#define PROJ_ID  56  // ftok 中配合PATHNAME 生成唯一key值


int create_sems(int nums);  // 建立含有nums個訊號量的集合
int get_sems();     // 獲取訊號量

// 初始化semid對應的訊號量集中編號為which的訊號量值為value
int init_sems(int semid , int which, int value);

int destroy_sems(int semid); // 釋放該訊號量集


int P(int semid, int which);    // 表示分配 訊號量值-1
int V(int semid, int which);    // 表示釋放 訊號量值+1

#endif /* _MYSEM_H_ */

mysem_c

#include "mysem.h"

// 建立訊號量和獲取訊號量公用函式
static int comm_sem ( int nums , int semflag)
{
    // 獲取key
    key_t key = ftok(PATHNAME, PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int semid = semget(key,nums, semflag );
    if( semid < 0)
    {
        perror("semget");
        return -1;
    }
    return semid;
}

int create_sems(int nums)  // 建立含有nums個訊號量的集合
{
    return comm_sem(nums, IPC_CREAT|IPC_EXCL|0666);
}


int get_sems()     // 獲取訊號量
{
    return comm_sem(0, IPC_CREAT);
}

union semun
{
    int val; // value for SETVAL
    struct semid_ds *buf; // buffer for IPC_STAT & IPC_SET
    unsigned short *array; // buffer for GETALL & SELALL
    struct seminfo * __buf; // buffer for IPC_INFO
};

// 初始化semid對應的訊號量集中編號為which的訊號量值為value
int init_sems(int semid , int which, int value)
{
    union semun _semun;
    _semun.val = value;
    int ret = semctl(semid, which, SETVAL,_semun);
    if(ret < 0)
    {
        perror("inin_sem");
        return -1;
    }
    return 0;
}

int destroy_sems(int semid) // 釋放該訊號量集
{
    int ret = semctl(semid, 0, IPC_RMID, NULL);
    if(ret < 0)
    {
        perror("rm_sem");
        return -1;
    }
    return 0;
}

static int comm_sem_op(int semid, int which, int op)
{
    struct sembuf _sembuf;
    _sembuf.sem_num = which;
    _sembuf.sem_op = op;
    _sembuf.sem_flg = 0; //  IPC_NOWAIT  SEM_UNDO
    return semop(semid, &_sembuf, 1);
}


int P(int semid, int which)    // 表示通過 訊號量值-1
{
    return comm_sem_op(semid, which , -1);
}
int V(int semid, int which)    // 表示釋放 訊號量值+1
{
    return comm_sem_op(semid, which, 1);
}

test_mysem_c

// 加入訊號量操作後的程式
#include "mysem.h"
#include <stdio.h>
#include <unistd.h>

int main()
{
    int semid = create_sems(10); // 建立一個包含10個訊號量的訊號集
    init_sems(semid, 0, 1);  // 初始化編號為 0 的訊號量值為1

    pid_t id = fork(); // 建立子程序
    if( id < 0)
    {
        perror("fork");
        return -1;
    }
    else if (0 == id)
    {// child 
        int sem_id = get_sems();
        while(1)
        {
            P(sem_id, 0); // 對該訊號量集中的0號訊號  做P操作
            printf("你");
            fflush(stdout);
            sleep(1);
            printf("好");
            printf(":");
            fflush(stdout);
            sleep(1);
            V(sem_id, 0);
        }
    }
    else
    {// father
        while(1)
        {
            P(semid,0);
            printf("在");
            sleep(1);
            printf("嗎");
            printf("?");
            fflush(stdout);
            V(semid, 0);
        }
        wait(NULL);
    }

    destroy_sems(semid);
    return 0;
}
// 未加訊號量的測試程式碼
#include "mysem.h"
#include <stdio.h>
#include <unistd.h>

int main()
{

    pid_t id = fork(); // 建立子程序
    if( id < 0)
    {
        perror("fork");
        return -1;
    }
    else if (0 == id)
    {// child 
        int sem_id = get_sems();
        while(1)
        {
            printf("你");
            fflush(stdout);
            sleep(1);
            printf("好");
            printf(":");
            fflush(stdout);
            sleep(1);
        }
    }
    else
    {// father
        while(1)
        {
            printf("在");
            sleep(1);
            printf("嗎");
            printf("?");
            fflush(stdout);
        }
        wait(NULL);
    }

    return 0;
}

相關推薦

Linux程序通訊IPC訊號試用

學習環境centos6.5 Linux核心2.6 程序間通訊概述 1. 程序通訊機制 一般情況下,系統中執行著大量的程序,而每個程序之間並不是相互獨立的,有些程序之間經常需要互相傳遞訊息。但是每個程序在系統中都有自己的地址空間,作業系統通過頁表

Linux程序通訊IPC共享記憶體試用

學習環境centos6.5 Linux核心2.6 什麼是共享記憶體 共享記憶體允許兩個或更多程序訪問同一塊記憶體。當一個程序改變了這塊記憶體中的內容的的時候,其他程序都會察覺到這個更改。 效率: 因為所有程序共享同一塊記憶體,共享記憶體在各種程序

Linux程序通訊IPC訊息佇列試用

學習環境 Centos6.5 Linux 核心 2.6 什麼是訊息佇列? 訊息佇列是SystemV版本中三種程序通訊機制之一,另外兩種是訊號量和共享儲存段。訊息佇列提供了程序間傳送資料塊的方法,而且每個資料塊都有一個型別標識。訊息佇列是基於訊息的,而管

作業系統程序通訊C#

程序間通訊命名管道程序間通訊的一種方式,Pipes:管道,分為無名管道:在父子程序間交換資料;有名管道:可在不同主機間交換資料,分為伺服器方和客戶方,在Win9X下只支援有名管道客戶。命名管道的命名命名管道是一個有名字的,單向或雙向的通訊管道。管道的名稱有兩部分組成:計算機名

QtQt程序通訊IPC

簡述 程序間通訊,就是在不同程序之間傳播或交換資訊。那麼不同程序之間存在著什麼雙方都可以訪問的介質呢?程序的使用者空間是互相獨立的,一般而言是不能互相訪問的,唯一的例外是共享記憶體區。但是,系統空間卻是“公共場所”,所以核心顯然可以提供這樣的條件。除此以外,那就是雙方都可以訪問的外設了。在這個意義上,兩

Linux程序通訊IPC方式總結

程序間通訊概述 程序通訊的目的 資料傳輸  一個程序需要將它的資料傳送給另一個程序,傳送的資料量在一個位元組到幾M位元組之間 共享資料  多個程序想要操作共享資料,一個程序對共享資料 通知事件 一個程序需要向另一個或一組程序傳送訊息,通知它(它們)

linux程序通訊IPC小結

linux IPC型別 1、匿名管道 2、命名管道 3、訊號 4、訊息佇列 5、共享記憶體 6、訊號量 7、Socket 1、匿名管道 過程: 1、管道實質是一個核心緩衝區,先進先出(佇列)讀取緩衝區記憶體資料 2、一個數據只能讀一次,讀完後在緩衝區就不存在

Linux程序通訊

1.程序間通訊的目的 資料傳輸,一個程序需要將它的資料傳送給另一個程序 資源共享:多個程序之間共享同樣的資源 通知事件:一個程序需要向另一個或一組程序傳送資訊,通知發生了某種事件(如程序終止時要通知父程序) 程序控制:有些程序希望完全控制另一個程序的執行(如

深刻理解Linux程序通訊IPC(轉)

序linux下的程序通訊手段基本上是從Unix平臺上的程序通訊手段繼承而來的。而對Unix發展做出重大貢獻的兩大主力 AT&T的貝爾實驗室及BSD(加州大學伯克利分校的伯克利軟體釋出中心)在程序間通訊方面的側重點有所不同。前者對Unix早期的程序間通訊手 段進行了系統的改進和擴充,形成了“system

Linux程序通訊訊息佇列、訊號和共享儲存

訊息佇列、訊號量和共享儲存是IPC(程序間通訊)的三種形式,它們功能不同,但有相似之處,下面先介紹它們的相似點,然後再逐一說明。 1、相似點 每個核心中的IPC結構(訊息佇列、訊號量和共享儲存)都用一個非負整數的識別符號加以引用,與檔案描述符不同,當一個

Linux程序通訊-訊號及程式設計例項

前面一篇文章執行緒同步之訊號量同步 講的是執行緒之間的訊號量,這篇講的更加具有通用性,能夠實現程序之間的同步。 訊號量概述 訊號量定義: 它是一個特殊變數,只允許對它進行等待和傳送訊號這兩種操作。 P(訊號量變數sv):等待。如果sv大於0,減小sv。如果sv為0,掛起這

Linux程序通訊-命名管道FIFO

命名管道概述 如果我們要在不相關的程序間交換資料,那麼使用FIFO檔案將會十分方便。 FIFO檔案通常也稱為命名管道(named pipe)。命名管道是一種特殊型別的檔案,它在檔案系統中以檔名的形式存在。 建立命名管道 建立命名管道一般有兩種方式: 命令列方式 一個比較舊

Linux程序11——程序通訊IPC概述

以下內容源於朱有鵬《物聯網大講堂》的課程學習整理,如有侵權,請告知刪除。 1、為什麼需要程序間通訊? (1)程序間通訊(IPC) 指的是2個任意程序之間的通訊。 (2)同一個程序在一個地址空間中 同一個程序的不同模組(不同函式、不同檔案)之間的通訊很簡單很多時候都是全

深刻理解Linux程序通訊IPC

序 linux下的程序通訊手段基本上是從Unix平臺上的程序通訊手段繼承而來的。而對Unix發展做出重大貢獻的兩大主力AT&T的貝爾實驗室及BSD(加州大學伯克利分校的伯克利軟體釋出中心)在程序間通訊方面的側重點有所不同。前者對Unix早期的程序間通訊手段進行了

Linux程序通訊訊息佇列

在上一篇部落格裡,我們學習了程序間通訊的一種方式,那就是管道,今天我們繼續學習另一種方式訊息佇列。 訊息佇列 一. 什麼是訊息佇列?   訊息佇列是訊息的連結串列,存放在核心中並由訊息佇列識別符號表示。   訊息佇列提供了一個從一個程序向另一個程

Linux 程序通訊IPC總結

概述 一個大型的應用系統,往往需要眾多程序協作,程序(Linux程序概念見附1)間通訊的重要性顯而易見。本系列文章闡述了 Linux 環境下的幾種主要程序間通訊手段。 程序隔離 程序隔離是為保護作業系統中程序互不干擾而設計的一組不同硬體和軟體的技術。這個技術是為了避免程序A寫入程序B的情況發生。

Linux環境程序通訊: 共享記憶體(轉)

轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html, 作者:鄭彥興系統呼叫mmap()通過對映一個普通檔案實現共享記憶體。系統V則是通過對映特殊檔案系統shm中的檔案實現程序間的共享記憶體通訊。也就是說,每個共享記憶體區域對

Linux環境程序通訊: 共享記憶體(轉)

轉自http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html, 作者:鄭彥興採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需要任何資料的拷貝。對於像管道和訊息佇列等通訊方式,則需要在內 核和使用者空間

Linux環境程序通訊: 訊號(轉)

訊號本質訊號是在軟體層次上對中斷機制的一種模擬,在原理上,一個程序收到一個訊號與處理器收到一箇中斷請求可以說是一樣的。訊號是非同步的,一個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。訊號是程序間通訊機制中唯一的非同步通訊機制,可以看作是非同步通知,通知接收訊號的程序有哪些事

Linux 環境程序通訊 套介面(轉)

轉自https://www.ibm.com/developerworks/cn/linux/l-ipc/part6/, 作者:鄭彥興一個套介面可以看作是程序間通訊的端點(endpoint),每個套介面的名字都是唯一的(唯一的含義是不言而喻的),其他程序可以發現、連線並且 與之通訊。通訊域用來說明套介面通訊的協