*inux進程間通信總結(全)

IPC進程間通信(Inter-Process Communication)就是指多個進程之間相互通信,交換信息的方法。Linux IPC基本上都是從Unix平臺上繼承而來的。主要包括最初的Unix IPC,System V IPC以及基於Socket的IPC。另外,Linux也支持POSIX IPC。

System V,BSD,POSIX

    System V是Unix操作系統最早的商業發行版之一。它最初由AT&T(American Telephone & Telegraph)開發,最早在1983年發佈。System V主要發行了4個版本,其中SVR4(System V Release 4)是最成功的版本。BSD(Berkeley Software Distribution,有時也被稱爲Berkeley Unix)是加州大學於1977至1995年間開發的。在19世紀八十年代至九十年代之間,System V和BSD代表了Unix的兩種主要的操作風格。它們的主要區別如下:

    系統                      System V           BSD
    root腳本位置            /etc/init.d/       /etc/rc.d/
    默認shell                 Bshell             Cshell
    文件系統數據            /etc/mnttab     /etc/mtab
    內核位置                  /UNIX             /vmUnix
    打印機設備                lp                  rlp
    字符串函數                memcopy       bcopy
    終端初始化設置文件    /etc/initab       /etc/ttys
    終端控制                  termio            termios

    Linux系統的操作風格往往介於這兩種風格之間。

    POSIX(Portable Operating System Interface [for Unix])是由IEEE(Institute of Electrical and Electronics Engineers,電子電氣工程協會)開發的。現有的大部分Unix都遵循POSIX標準,而Linux從一開始就遵循POSIX標準。

最初的Unix IPC

1、信號

    信號是Unix/Linux系統在一定條件下生成的事件。信號是一種異步通信機制,進程不需要執行任何操作來等待信號的到達。信號異步通知接收信號的進程發生了某個事件,然後操作系統將會中斷接收到信號的進程的執行,轉而去執行相應的信號處理程序。

    (1)註冊信號處理函數
        #include <signal.h>
        /*typedef void (*sighandler_t)(int);  sighandler_t signal(int signum,sighandler_t handler);*/
        * void (*signal(int signum, void (*handler)(int)))(int);  //SIG_IGN && SIG_DFL
        * int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

    (2)發送信號
        #include <signal.h>
        * int kill(pid_t pid,int sig); //#include <sys/types.h> 
        * int raise(int sig);            //kill(getpid(),sig);
        * unsigned int alarm(unsigned int seconds); //(#include <unistd.h>) seconds秒後,向進程本身發送SIGALRM信號。

    (3)信號集
        信號集被定義爲:typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
        * int sigaddset(sigset_t *set,int sig);
        * int sigemptyset(sigset_t *set);

2、管道(Pipe)

    管道用來連接不同進程之間的數據流。

    (1)在兩個程序之間傳遞數據的最簡單的方法是使用popen()和pclose()函數:
        #include <stdio.h>
        FILE *popen(const char *command, const char *open_mode);
        int pclose(FILE *stream);
    popen()函數首先調用一個shell,然後把command作爲參數傳遞給shell。這樣每次調用popen()函數都需要啓動兩個進程;但是由於在Linux中,所有的參數擴展(parameter expansion)都是由shell執行的,這樣command中包含的所有參數擴展都可以在command程序啓動之前完成。

    (2)pipe()函數:
        #include <unistd.h>
        int pipe(int pipefd[2]);
    popen()函數只能返回一個管道描述符,並且返回的是文件流(file stream),可以使用函數fread()和fwrite()來訪問。pipe()函數可以返回兩個管道描述符:pipefd[0]pipefd[1],任何寫入pipefd[1]的數據都可以從pipefd[0]讀回;pipe()函數返回的是文件描述符(file descriptor),因此只能使用底層的read()和write()系統調用來訪問。pipe()函數通常用來實現父子進程之間的通信。

    (3)命名管道:FIFO
        #include <sys/types.h>
        #include <sys/stat.h>
        int mkfifo(const char *fifo_name, mode_t mode);
    前面兩種管道只能用在相關的程序之間,使用命名管道可以解決這個問題。在使用open()打開FIFO時,mode中不能包含O_RDWR。mode最常用的是O_RDONLY,O_WRONLY與O_NONBLOCK的組合。O_NONBLOCK影響了read()和write()在FIFO上的執行方式。

    PS:要想查看庫函數用法,最可靠的資料來自Linux manual page:

    $sudo apt-get install manpages-dev

    $man 3 function_name

 

System V IPC

    System V IPC指的是AT&T在System V.2發行版中引入的三種進程間通信工具:(1)信號量,用來管理對共享資源的訪問 (2)共享內存,用來高效地實現進程間的數據共享 (3)消息隊列,用來實現進程間數據的傳遞。我們把這三種工具統稱爲System V IPC的對象,每個對象都具有一個唯一的IPC標識符(identifier)。要保證不同的進程能夠獲取同一個IPC對象,必須提供一個IPC關鍵字(IPC key),內核負責把IPC關鍵字轉換成IPC標識符。   

    System V IPC具有相似的語法,一般操作如下:

    (1)選擇IPC關鍵字,可以使用如下三種方式:

       a)IPC_PRIVATE。由內核負責選擇一個關鍵字然後生成一個IPC對象並把IPC標識符直接傳遞給另一個進程。
       b)直接選擇一個關鍵字。
       c)使用ftok()函數生成一個關鍵字。

    (2)使用semget()/shmget()/msgget()函數根據IPC關鍵字key和一個標誌flag創建或訪問IPC對象。如果key是IPC_PRIVATE;或者key尚未與已經存在的IPC對象相關聯且flag中包含IPC_CREAT標誌,那麼就會創建一個全新的IPC對象。

    (3)使用semctl()/shmctl()/msgctl()函數修改IPC對象的屬性。

    (4)使用semctl()/shmctl()/msgctl()函數和IPC_RMID標誌銷燬IPC實例。

    System V IPC爲每個IPC對象設置了一個ipc_perm結構體並在創建IPC對象的時候進行初始化。這個結構體中定義了IPC對象的訪問權限和所有者:

    struct ipc_perm{
       uid_t uid;   //所有者的用戶id
       gid_t gid;   //所有者的組id
       uid_t cuid;  //創建者的用戶id
       gid_t cgid;  //創建者的組id
       mode_t mode; //訪問模式
       …
    };

    shell中管理IPC對象的命令是ipcs、ipcmk和ipcrm。

1、信號量(Semaphores)

    System V的信號量集表示的是一個或多個信號量的集合。內核爲每個信號量集維護一個semid_ds數據結構,而信號量集中的每個信號量使用一個無名結構體表示,這個結構體至少包含以下成員:
    struct{
        unsigned short semval;//信號量值,總是>=0
        pid_t sempid;  //上一次操作的pid
       …
    };

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    (1)創建或訪問信號量
        * int semget(key_t key,int nsems,int flag); 
    nsems指定信號量集中信號量的個數,如果只是獲取信號量集的標識符(而非新建),那麼nsems可以爲0。flag的低9位作爲信號量的訪問權限位,類似於文件的訪問權限;如果flag中同時指定了IPC_CREAT和IPC_EXCL,那麼如果key已與現存IPC對象想關聯的話,函數將會返回EEXIST錯誤。例如,flag可以爲IPC_CREAT|0666。

    (2)控制信號量集
        * int semctl(int semid,int semnum,int cmd,union semun arg);
    對semid信號量集合執行cmd操作;cmd常用的兩個值是:SETVAL初始化第semnum個信號量的值爲arg.val;IPC_RMID刪除信號量。

    (3)對一個或多個信號量進行操作
        * int semop(int semid,struct sembuf *sops,unsigned nsops);
        * struct sembuf{
              unsigned short sem_num;  //信號量索引
              short   sem_op;     //對信號量進行的操作,常用的兩個值爲-1和+1,分別代表P、V操作
              short   sem_flag;   //比較重要的值是SEM_UNDO:當進程結束時,相應的操作將被取消;同時,如果進程結束時沒有釋放資源的話,系統會自動釋放
           };

2、共享內存

    共享內存允許兩個或多個進程共享一定的存儲區,因爲不需要拷貝數據,所以這是最快的一種IPC。

    #include <sys/ipc.h>
    #include <sys/shm.h>
    (1)創建或訪問共享內存
        * int shmget(key_t key,size_t size,int shmflg);

    (2)附加共享內存到進程的地址空間
        * void *shmat(int shmid,const void *shmaddr,int shmflg);//shmaddr通常爲NULL,由系統選擇共享內存附加的地址;shmflg可以爲SHM_RDONLY

    (3)從進程的地址空間分離共享內存
        * int shmdt(const void *shmaddr); //shmaddr是shmat()函數的返回值

    (4)控制共享內存
        * int shmctl(int shmid,int cmd,struct shmid_ds *buf);
        * struct shmid_ds{
              struct ipc_perm shm_perm;
              …
          }; 
    cmd的常用取值有:(a)IPC_STAT獲取當前共享內存的shmid_ds結構並保存在buf中(2)IPC_SET使用buf中的值設置當前共享內存的shmid_ds結構(3)IPC_RMID刪除當前共享內存

3、消息隊列

    消息隊列保存在內核中,是一個由消息組成的鏈表。

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    (1)創建或訪問消息隊列
    * int msgget(key_t key,int msgflg);

    (2)操作消息隊列
        * int msgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);
    msg指向的結構體必須以一個long int成員開頭,作爲msgrcv()的消息類型,必須大於0。nbytes指的是msg指向結構體的大小,但不包括long int部分的大小
        * ssize_t msgrcv(int msqid,void *msg,size_t nbytes,long msgtype,int msgflg);
    如果msgtype是0,就返回消息隊列中的第一個消息;如果是正整數,就返回隊列中的第一個該類型的消息;如果是負數,就返回隊列中具有最小值的第一個消息,並且該最小值要小於等於msgtype的絕對值。

    (3)控制消息隊列
        * int msgctl(int msqid,int cmd,struct msqid_ds *buf);
        * struct msqid_ds{
              struct ipc_perm msg_perm;
              …
           };

Socket
  套接字(Socket)是由Berkeley在BSD系統中引入的一種基於連接的IPC,是對網絡接口(硬件)和網絡協議(軟件)的抽象。它既解決了無名管道只能在相關進程間單向通信的問題,又解決了網絡上不同主機之間無法通信的問題。

  套接字有三個屬性:域(domain)、類型(type)和協議(protocol),對應於不同的域,套接字還有一個地址(address)來作爲它的名字。

  域(domain)指定了套接字通信所用到的協議族,最常用的域是AF_INET,代表網絡套接字,底層協議是IP協議。對於網絡套接字,由於服務器端有可能會提供多種服務,客戶端需要使用IP端口號來指定特定的服務。AF_UNIX代表本地套接字,使用Unix/Linux文件系統實現。

  IP協議提供了兩種通信手段:流(streams)和數據報(datagrams),對應的套接字類型(type)分別爲流式套接字和數據報套接字。流式套接字(SOCK_STREAM)用於提供面向連接、可靠的數據傳輸服務。該服務保證數據能夠實現無差錯、無重複發送,並按順序接收。流式套接字使用TCP協議。數據報套接字(SOCK_DGRAM)提供了一種無連接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到數據。數據報套接字使用UDP協議。

  一種類型的套接字可能可以使用多於一種的協議來實現,套接字的協議(protocol)屬性用於指定一種特定的協議。

總結:

 

 

 

System V IPC API

 

1,消息隊列

int ftok(const char *pathname, int prj_id);

int msgget(key_t key,int msgflag);

int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);

int msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);

 

2,信號量

int semget(key_t key,int nsems,int semflag);

int semctl(int semid,int semnum,int cmd,…);

int semop(int semid,struct sembuf *sops,unsigned nsops,struct timespec *timeout);

 

3,共享內存

int shmget(key_t key,size_t size,int shmflag);

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

  

POSIX IPC API


Unix/Linux下的IPC---信號量集  

最初的Unix IPC包括:管道、FIFO、信號;
System V IPC包括:System V消息隊列、System V信號燈、System V共享內存區;
POSIX IPC包括:POSIX消息隊列、POSIX信號燈、POSIX共享內存區;
由於Unix系統版本的多樣性,電子電氣工程協會(IEEE)開發了一套獨立的Unix標準,這套新的ANSI Unix標準被稱爲計算機環境的可移植操作系統接口(POSIX:Portable Operation System Interface).現有大部分Unix和流行的版本都遵循POSIX標準;而Linux從一開始就遵循了POSIX標準;

System V IPC對象屬於系統內核對象,執行同步操作的時候,系統內核參與完成了大部分操作,所以它是一套重量級同步對象;
POSIX IPC對象屬於用戶進程對象,執行同步操作的時候,不需要系統內核參與,所以它是一套輕量級同步對象;
一般來說,使用POSIX IPC來控制對共享資源的訪問,就足夠了;

典型的IPC對象:管道、命名管道、信號、信號燈、信號量集、消息隊列、共享內存;下面僅介紹信號量集、共享內存;

一、信號量集:
POSIX IPC標準對信號量的的要求並不高:信號量(sem_init)、命名信號量(sem_open);
System V IPC要求信號量必須是一個集合,即:信號量集;
信號量集和信號量一樣,都是爲了控制多個進程對共享資源的同步訪問而引入的同步對象;System V IPC中規定:不能只單獨定義一個信號量,而是隻能定義一個信號量的集合,即:信號量集,其中包含一組信號量,同一信號量集中的多個信號量使用同一個唯一的ID來引用,這樣做的目的是爲了對多個共享資源進行同步控制的需要;

1、信號量集的創建與打開:
   系統調用semget()用於創建一個新的信號量集,或者是存取一個已經存在的信號量集;
   函數原型: int semget(key_t key, int nsems, int semflag);
   參數說明: key     --> 需要創建或打開的信號量集的鍵,用於唯一地標記一個信號量集;這個參數是用戶程序可以直接訪問的用戶態參數;
             nsems   --> 表示待創建的信號量集key中的信號量的個數,這個參數只在創建信號量集的時候有效;
             semflag --> 表示調用函數的操作類型,也可以用於設置信號量集的訪問權限,兩者通過邏輯或(or)表示;
   返回值:成功:返回一個正數,這個正數也用於唯一地標記已經創建或打開的信號量集,這個唯一標示由系統內核使用;這個正數被稱爲是IPC標識符;
          失敗:返回-1;並設置錯誤碼errno來標記錯誤原因:
               EEXIST(信號量集已經存在,無法創建)
               EIDRM(信號量集已經刪除)
               ENOENT(信號量集不存在,同時沒有使用IPC_CREAT)
               ENOMEM(沒有足夠的內存創建新的信號量集)
               ENOSPC(超出限制)
   當調用semget()創建一個信號量集的時候,信號量集的semid_ds結構會被初始化;ipc_perm中的各個量被設置爲相應的值;sem_nsems被設置爲參數nsems的值;sem_otime被設置爲0,sem_ctime被設置爲當前時間;
   系統調用semget()的第一個參數是關鍵字值(一般由系統調用ftok()返回的).系統內核將此值和系統中存在的其它的信號量集的關鍵字值進行比較,不存在的話,則直接創建這個新的信號量集,如果已經存在同關鍵字值的信號量集,則直接打開這個信號量集,不必再新建信號量集;打開和存取操作與參數semflag有關.IPC_CREAT:如果待創建的信號量集在系統內核中不存在,則創建信號量集;IPC_EXCL與IPC_CREAT同時使用時,如果信號量集在系統內核中已經存在,則調用失敗;如果單獨使用IPC_CREAT,則系統調用semget()要麼返回新創建的信號量集的標識符,要麼返回系統內核中已經存在的具有相同關鍵字值的信號量集的標識符;如果IPC_EXCL與IPC_CREAT一起使用,則要麼返回新創建的信號量集的標識符,要麼返回-1;IPC_EXCEL單獨使用時沒有意義;
   參數nsems指出了一個新創建的信號量集中應該創建的信號量的個數;

2、信號量相關的內核結構:
   struct semid_ds
   {
     struct ipc_perm   sem_perm;       /* operation permission struct */
     struct sem*       sem_base;       /* ptr to first semaphore in set:指向數組中第一個信號量的指針*/
     ushort_t          sem_nsems;      /* number of semaphores in set:信號量集(數組)中的信號量的個數 */
     #if defined(_LP64)
       time_t          sem_otime;      /* last semop time:最後一次semop()操作的時間 */
       time_t          sem_ctime;      /* last change time:最後一次改動此數據結構的時間 */
     #else   /* _LP64 */
       time_t          sem_otime;      /* last semop time */
       int32_t         sem_pad1;       /* reserved for time_t expansion */
       time_t          sem_ctime;      /* last change time */
       int32_t         sem_pad2;       /* time_t expansion */
     #endif  /* _LP64 */
     int               sem_binary;     /* flag indicating semaphore type */
     long              sem_pad3[3];    /* reserve area */
    };
    
    /*Common IPC access structure*/
    struct ipc_perm
    {
      uid_t           uid;    /* owner's user id */
      gid_t           gid;    /* owner's group id */
      uid_t           cuid;   /* creator's user id */
      gid_t           cgid;   /* creator's group id */
      mode_t          mode;   /* access modes */
      uint_t          seq;    /* slot usage sequence number */
      key_t           key;    /* key */
      #if !defined(_LP64)
        int           pad[4]; /* reserve area */
      #endif
    };
    mode的取值:
    權限     位
    用戶讀 : 0400
    用戶寫 : 0200
    組讀   : 0040
    組寫   : 0020
    其他讀 : 0004
    其他寫 : 0002
    內核中的sem結構:
    在數據結構semid_ds中包含一個指向信號量數組的指針;此數組中的每一個元素都是一個數據結構sem.它定義在linux/sem.h文件中:
    /*One semaphore structure for each semaphore in the system.*/
    struct sem
    {
      short   sempid;   /*pid of last operation:最後一個操作的PID(進程ID)*/
      ushort  semval;   /*current value:信號量的當前值*/
      ushort  semncnt;  /*num procs awaiting increase in semval:等待資源的進程數量*/
      ushort  semzcnt;  /*num procs awaiting semval=0:等待資源完全空閒的進程數量*/
    };

3、PV操作:
   P操作(代表荷蘭語Proberen:嘗試):
      等待一個信號燈,等待申請使用一個單位的共享資源 ,該操作測試這個信號燈的值,如果小於或等於0,則阻塞調用者,一旦值變大,就將它減1;
      
   V操作(代表荷蘭語Verhogen:增加):
      掛出(post)一個信號燈,該操作將信號燈的值加1;表示釋放一個單位的共享資源;
      
4、信號量集的操作:
   系統調用semop()對信號量集進行原子操作;
   函數原型:int semop(int semid, struct sembuf* semoparray, size_t nsops);
   參數說明:semid      --> 信號量集的IPC標識符,用於引用對應的信號量集;
            semoparray --> sembuf結構的數組,用於指定調用semop()函數所做的操作(PV操作);
            nsops      --> 指出數組semoparray中操作結構元素的個數:有nsops個sembuf元素參與了PV操作;nsops>=1;
   返回值:==0:成功; -1:失敗;
   
   函數說明:semoparray是一個sembuf結構的數組,其中每個元素都代表一個操作,由於此函數執行的是原子操作,所以,一旦執行,就會執行數組中所有sembuf元素所定義的操作;
   當函數返回失敗時,errno:
   E2BIG(nsops大於最大的ops數目),即:一次對信號的操作數超出系統的限制;
   EACCESS(權限不夠),即:調用進程沒有權能執行請求的操作,並且不具有CAP_IPC_OWNER權能;
   EAGAIN(使用了IPC_NOWAIT,但操作不能繼續進行),即:信號操作暫時不能滿足,需要重試;
   EFAULT(sops指向的地址無效),即:sops或timeout指針指向的空間不可訪問;
   EFBIG:sem_num指定的值無效;
   EIDRM(信號量集已經刪除)
   EINTR(當睡眠時接收到其他信號),即:系統調用阻塞時,被信號中斷;
   EINVAL(信號量集不存在,或者semid無效)
   ENOMEM(使用了SEM_UNDO,但無足夠的內存創建所需的數據結構)
   ERANGE(信號量值超出範圍),即:信號所允許的值越界;
   struct sembuf結構:
   struct sembuf
   {
     ushort_t        sem_num;        /* semaphore index in array:待操作的信號量集中的某一個信號量的索引 */
     short           sem_op;         /* semaphore operatio:對該信號量所執行的操作 */
     short           sem_flg;        /* operation flags:操作標誌 */
   };
   成員sem_num:待操作的信號量集中的某一個信號量的索引,所以,其取值範圍是[0,信號量集中信號量的個數);
               即:操作信號量在信號集中的編號,第一個信號的編號是0;
   成員sem_op:定義了semop()函數對信號量所作的操作;如果其值爲正數,該值會加到現有的信號內含值中.通常用於釋放所控資源的使用權;如果sem_op的值爲負數,而其絕對值又大於信號的現值,操作將會阻塞,直到信號值大於或等於sem_op的絕對值.通常用於獲取資源的使用權;如果sem_op的值爲0,則操作將暫時阻塞,直到信號的值變爲0.
   成員sem_flg:定義了semop()的操作標誌;可能的選擇有兩種:
               IPC_NOWAIT --> 對信號的操作不能滿足時,semop()不會阻塞,並立即返回,同時設定錯誤信息.
               IPC_UNDO   --> 程序結束時(不論正常或不正常),保證信號值會被重設爲semop()調用前的值.這樣做的目的在於避免程序在異常情況下結束時未將鎖定的資源
                              解鎖,造成該資源永遠鎖定.
   sem_op成員的取值詳解:
   >0:釋放相應數量的資源,將sem_op的值累加到信號量的值上;
   =0:進程阻塞,直到相應信號量的值爲0,當信號量的值已經爲0,函數立即返回;如果信號量的值不爲0,則依據sem_flag的IPC_NOWAIT位決定函數操作;如果sem_flag指定了IPC_NOWAIT位,則semop函數出錯返回EAGAIN(異步返回).如果sem_flag沒有指定IPC_NOWAIT,則將該信號量的semncnt值加1(等待空閒資源的進程數多一個),然後掛起進程,直到下述情況發生:信號量的值爲0,將信號量的semzcnt的值減1(等待資源完全空閒的進程數少一個),函數semop成功返回;此信號量被刪除(只有超級用戶或創建用戶進程擁有此權限),函數semop出錯返回EIDRM;進程捕捉到系統信號,並從信號處理函數返回,在此情況下,將此信號量的semncnt值減1(等待資源完全空閒的進程數少一個),函數semop出錯返回EINTR;
   <0:請求sem_op的絕對值所表示的數目的資源;如果相應的資源數可以滿足請求,則信號量的值減去sem_op的絕對值,函數成功返回;當相應的資源數目不能滿足請求時,這個操作與sem_flag有關;如果sem_flag指定了IPC_NOWAIT,則semop函數出錯返回EAGAIN(異步返回);如果sem_flag沒有指定IPC_NOWAIT,則將該信號量的semncnt值加1(表示等待空閒資源的進程多了一個),然後掛起進程,直到下述情況發生:當相應的資源數目可以滿足請求時,該信號量的值減去sem_op的絕對值(表示|sem_op|個數目的共享資源被申請走了),然後成功返回;當此信號量已被刪除(只有超級用戶或創建用戶進程擁有此權限)時,函數出錯返回EIDRM;當進程捕捉到系統信號並從信號處理函數返回時,將此信號量的semzcnt值減1,函數semop出錯返回EINTR;
   簡單地說:如果sem_op爲負數,則把信號量的值減去sem_op的絕對值,此時,sem_op的絕對值表示本次調用semop()操作需要申請的共享資源的數量;這與信號量集控制的資源有關;如果沒有設置IPC_NOWAIT,那麼,調用進程將進入休眠狀態,直到信號量控制的資源具有可用的數量爲止;如果sem_op爲正數,則信號量的值加上sem_op的絕對值,表示調用進程需要歸還|sem_op|個單位數目的共享資源給系統,即:調用進程釋放信號量集所控制的共享資源;如果sem_op爲0,那麼,調用進程將調用sleep()休眠,直到信號量的值爲0;這在一個調用進程等待完全空閒的共享資源時使用;
   
5、信號量集的控制:
   系統調用semctl()可以實現對信號量集的控制;
   函數原型:int semctl(int semid, int semnum, int cmd, /*union semun arg*/...);
   參數說明:semid  --> 信號量集的IPC標識符;
            semnum --> 信號量集semid中的某一個信號量的索引;
            cmd    --> 對信號量集semid中的特定信號量semnum執行的操作命令;
            變參   --> 這是一個聯合體類型union semun的副本,而不是一個指向聯合類型的指針;聯合體中各個量的使用情況與參數cmd的設置有關;
   返回值:-1:失敗;
          >=0:成功(依賴cmd參數的設置):
              GETVAL          the value of semval
              GETPID          the value of (int) sempid
              GETNCNT         the value of semncnt
              GETZCNT         the value of semzcnt
              cmd取其餘值時,返回0表示成功;
   union semun
   {
     int              val;    /* value for SETVAL */
     struct semid_ds* buf;    /* buffer for IPC_STAT&IPC_SET */
     unsigned short*  array;  /* array for GETALL&SETALL */
     structseminfo*   __buf;  /* buffer for IPC_INFO */
     void*__pad;
   } arg;
   cmd參數的取值詳解:
   CMD的取值    操作描述
   GETVAL       返回成員semnum的semval值,信號量集中的一個單個的信號量的值;
   SETVAL       使用arg.val對該信號量的semnum.sempid賦值(需要參數arg),設置信號量集中的一個單獨的信號量的值;
   GETPID       返回成員semnum的sempid值,最後一個執行semop操作的進程的PID;
   GETNCNT      返回成員semnum的semncnt值,正在等待資源的進程數目;
   GETZCNT      返回成員semnum的semzcnt值,正在等待完全空閒的資源的進程數目;
   GETALL       將該信號量集中所有信號量的值賦值到arg.array(需要參數arg)所指向的數組中,用於讀取信號量集中的所有信號量的值;
   SETALL       使用arg.array所指向的數組中的值對信號量集賦值(需要參數arg),設置信號量集中的所有的信號量的值;
   IPC_RMID     刪除信號量集.此操作只能由具有超級用戶的進程或信號量集擁有者的進程執行,
                這個操作會影響到正在使用該信號量集的進程;
   IPC_SET      設置此信號量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值.值來自semun.buf結構中
                此操作只能由具有超級用戶的進程或信號量集擁有者的進程執行;
                設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數;
   IPC_STAT     讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中;


《linux的POSIX IPC用到的工具》

POSIX IPC爲每個對象都使用文件描述符,這與System V的IPC不同,System V的每個對象都使用鍵值(keys).
由於每個IPC對象可以寫到一個純文本文件,因此,在純文本文件上使用的工具對於操作POSIX IPC對象來說通常已經足夠了.


1)POSIX共享內存
在Linux中,POSIX共享內存對象駐留在tmpfs僞文件系統中.系統默認掛載在/dev/shm目錄下.
當調用shm_open函數創建或打開POSIX共享內存對象時,系統會將創建/打開的共享內存文件放到/dev/shm目錄下.

同樣也可以手工在/dev/shm目錄下創建文件,以供POSIX共享內存程序連接使用.

我們用下面的源程序對POSIX共享內存進行測試,如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>
void error_out(const char *msg)
{
        perror(msg);
        exit(EXIT_FAILURE);
}

int main (int argc, char *argv[])
{
        int r;
        const char *memname = "/mymem";
        const size_t region_size = sysconf(_SC_PAGE_SIZE);
        int fd = shm_open(memname, O_CREAT|O_TRUNC|O_RDWR, 0666);
        if (fd == -1)
                error_out("shm_open");
        r = ftruncate(fd, region_size);
        if (r != 0)
                error_out("ftruncate");
        void *ptr = mmap(0, region_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (ptr == MAP_FAILED)
                error_out("MMAP");
        close(fd);
        pid_t pid = fork();
        if (pid == 0){
                u_long *d = (u_long *)ptr;
                *d = 0xdeadbeef;
                exit(0);
        }
        else{
                int status;
                waitpid(pid, &status, 0);
                printf("child wrote %#lx\n", *(u_long *)ptr);
        }
        sleep(50);
        r = munmap(ptr, region_size);
        if (r != 0)
                error_out("munmap");
        r = shm_unlink(memname);
        if (r != 0)
                error_out("shm_unlink");
        return 0;
}

編譯程序pmem.c
gcc pmem.c -o pmem -lrt


執行程序pmem
./pmem
child wrote 0xdeadbeef

注:程序會通過shm_open函數創建一個共享內存對象,並通過mmap函數將文件映射到內存,此時修改內存,即是修改文件.
子進程向共享內存寫數據,父進程將共享內存數據打印輸出,在輸出完成後,會等待50秒鐘.我們可以在另一個終端看到/dev/shm/mymem文件,如下:

ls -l /dev/shm/mymem 
-rw-r--r-- 1 root root 4096 2011-03-16 15:22 /dev/shm/mymem



2)POSIX消息隊列

在Linux中通過mqueue僞文件系統來顯示POSIX消息隊列,與POSIX共享內存不同的是,它沒有一個標準的掛載點來爲這個文件系統服務.
如果需要調試來自shell的POSIX消息隊列,則需要手動掛載文件系統,例如,爲了將其掛載在命名爲/mnt/mqs的目錄下,可以使用以下命令:

mkdir /mnt/mqs
mount -t mqueue none /mnt/mqs


我們用下面的源程序對POSIX消息隊列進行測試,如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <sys/wait.h>

struct message{
 char mtext[128];
};

int send_msg(int qid, int pri, const char text[])
{
 int r = mq_send(qid, text, strlen(text) + 1,pri);
 if (r == -1){
  perror("mq_send");
 }
 return r;
}

void producer(mqd_t qid)
{
 send_msg(qid, 1, "This is my first message.");
 send_msg(qid, 1, "This is my second message.");

 send_msg(qid, 3, "No more messages.");
}

void consumer(mqd_t qid)
{
 struct mq_attr mattr;
 do{
  u_int pri;
  struct message msg;
  ssize_t len;

  len = mq_receive(qid, (char *)&msg, sizeof(msg), &pri);
  if (len == -1){
   perror("mq_receive");
   break;
  }
  printf("got pri %d '%s' len=%d\n", pri, msg.mtext, len);

  int r = mq_getattr(qid, &mattr);
  if (r == -1){
   perror("mq_getattr");
   break;
  }
 }while(mattr.mq_curmsgs);
}

int
main (int argc, char *argv[])
{
 struct mq_attr mattr = {
  .mq_maxmsg = 10,
  .mq_msgsize = sizeof(struct message)
 };

 mqd_t mqid = mq_open("/myq",
    O_CREAT|O_RDWR,
    S_IREAD|S_IWRITE,
    &mattr);
 if (mqid == (mqd_t) -1){
  perror("mq_open");
  exit (1);
 }

 pid_t pid = fork();
 if (pid == 0){
  producer(mqid);
  mq_close(mqid);
  exit(0);
 }
 else
 {
  int status;
  wait(&status);
  sleep(50);
  consumer(mqid);
  mq_close(mqid);
 }
 mq_unlink("/myq");
 return 0;
}
編譯:
gcc posix_mqs.c -o posix_mqs -lrt

運行程序:
./posix_mqs 
got pri 3 'No more messages.' len=18
got pri 1 'This is my first message.' len=26
got pri 1 'This is my second message.' len=27


注:程序通過fork使子進程發送三條消息到消息隊列,父進程從消息隊列中接收這三條消息.
/mnt/mqs/myq文件是程序創建的消息隊列文件,程序會自動在僞文件系統mqueue中創建消息隊列文件.

如果我們掛載僞文件系統到多個目錄,而在每個掛載目錄下都會生成消息隊列文件.
例如:
mkdir /dev/mqs/
mount -t mqueue none /dev/mqs

df -aTh
Filesystem    Type    Size  Used Avail Use% Mounted on
/dev/sda1     ext3     19G  3.3G   15G  19% /
proc          proc       0     0     0   -  /proc
sysfs        sysfs       0     0     0   -  /sys
devpts      devpts       0     0     0   -  /dev/pts
tmpfs        tmpfs    512M     0  512M   0% /dev/shm
none   binfmt_misc       0     0     0   -  /proc/sys/fs/binfmt_misc
sunrpc  rpc_pipefs       0     0     0   -  /var/lib/nfs/rpc_pipefs
none        mqueue       0     0     0   -  /mnt/mqs
none        mqueue       0     0     0   -  /dev/mqs

再次運行程序
./posix_mqs

我們看到在兩個mqueue僞文件系統掛載的目錄下都生成了myq文件
cat /mnt/mqs/myq /dev/mqs/myq 
QSIZE:71         NOTIFY:0     SIGNO:0     NOTIFY_PID:0     
QSIZE:71         NOTIFY:0     SIGNO:0     NOTIFY_PID:0  

QSIZE:71表示/mnt/mqs/myq消息隊列中一共有71個字節.
NOTIFY,SIGNO,NOTIFY_PID都和mq_notify函數有關.


3)POSIX信號量

在Linux中,POSIX信號量對象駐留在tmpfs僞文件系統中,同POSIX共享內存一樣,有名字的信號量作爲文件被創建在/dev/shm裏.

我們用下面的源程序來測試POSIX信號量,如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <assert.h>
void busywait(void)
{
 clock_t t1 = times(NULL);
 while(times(NULL) - t1 < 2);
}
int main (int argc, char *argv[])
{
 const char *message = "Hello World\n";
 int n = strlen(message)/2;
 sem_t *sem = sem_open("/thesem", O_CREAT, S_IRUSR|S_IWUSR);
 sleep(30);
 assert(sem != NULL);
 int r = sem_init(sem, 1, 0);
 assert(r == 0);
 pid_t pid = fork();
 int i0 = (pid == 0) ? 0 :n ;
 int i;

 if (pid)
  sem_wait(sem);
 for (i = 0;i < n;i++){
  write (1,message+i0+i,1);
  busywait();
 }
 if (pid == 0)
  sem_post(sem);
}

編譯:
gcc posix-sem.c -o posix-sem -lrt

執行posix-sem程序:
./posix-sem 

在另一個終端查看信號量文件,如下:
ls -l /dev/shm/
total 4
-rw------- 1 root root 16 Mar 18 10:32 sem.thesem

一個被命名爲thesem的信號量出現在/dev/shm/sem.thesem

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章