Linux System V IPC

轉載地址:http://blog.csdn.net/colzer/article/details/8146138
Linux下的進程通信基本上是從Unix平臺上的進程通信手段繼承而來的。而對Unix發展做出重大貢獻的兩大主力AT&T的貝爾實驗室及BSD(加州大學伯克利分校的伯克利軟件發佈中心)在進程間通信方面的側重點有所不同。前者對Unix早期的進程間通信手段進行了系統的改進和擴 充,形成了“system V IPC”,通信進程侷限在單個計算機內;後者則跳過了該限制,形成了基於套接口(socket)的進程間通信機制。Linux則把兩者繼承了下來

其中,最初Unix IPC包括:管道、FIFO、信號;System V IPC包括:System V消息隊列、System V信號燈、System V共享內存區;Posix IPC包括: Posix消息隊列、Posix信號燈、Posix共享內存區。有兩點需要簡單說明一下:1)由於Unix版本的多樣性,電子電氣工程協會(IEEE)開 發了一個獨立的Unix標準,這個新的ANSI Unix標準被稱爲計算機環境的可移植性操作系統界面(PSOIX)。現有大部分Unix和流行版本都是遵循POSIX標準的,而Linux從一開始就遵 循POSIX標準;2)BSD並不是沒有涉足單機內的進程間通信(socket本身就可以用於單機內的進程間通信)。事實上,很多Unix版本的單機 IPC留有BSD的痕跡,如4.4BSD支持的匿名內存映射、4.3+BSD對可靠信號語義的實現等等。

我們知道管道和信號都是隨着進程持續而存在(IPC一直存在到打開IPC對象的最後一個進程關閉該對象爲止),如果進程結束了,管道和信號都會關閉或者丟失。下面將會分別介紹基於System V IPC的通信機制:消息隊列,信號燈,共享內存區。基於System V IPC的通信機制的特點是:它是隨着內核的持續而存在(IPC一直持續到內核重新啓動或者顯示刪除該對象爲止)。本文將介紹System V IPC 在內核中實現的原理和以及相應的API,應用。

System V IPC原理

首先基於System V IPC的通信是基於內核來實現。首先我們來分析整個System V IPC的結構。在linux 3.6.5內核源碼中我們可以在/include/linux/ipc_namespace.h文件中找到struct ipc_namespace這個結構體,該結構體是基於System V IPC 三種通信機制的命名空間或者說全局入口,在該結構體中定義了一個struct ipc_ids ids[3]結構體數組,關鍵的結構體代碼如下:

struct ipc_namespace {
atomic_t count;
struct ipc_ids ids[3];
...
};
struct ipc_ids {
int in_use;
unsigned short seq;
unsigned short seq_max;
struct rw_semaphore rw_mutex;
struct idr ipcs_idr;
};

每一個struct ipc_ids結構體對應System V IPC 每一種通信機制,struct ipc_ids ids[3]就對應了三種IPC(msg_ids消息隊列,sem_ids信號量,shm_ids共享內存區)。通過下面宏定義可以分別得到三種IPC結構體:

#define IPC_SEM_IDS     0
#define IPC_MSG_IDS     1
#define IPC_SHM_IDS     2
#define msg_ids(namespace)     ((namespace)->ids[IPC_MSG_IDS])
#define sem_ids(namespace)     ((namespace)->ids[IPC_SEM_IDS])
#define shm_ids(namespace)     ((namespace)->ids[IPC_SHM_IDS])

每一個struct ipc_ids結構體對應System V IPC 每一種通信機制,struct ipc_ids結構體中struct idr結構體記錄了該IPC所有條目(比如:如果是消息隊列,此時idr中記錄了系統中當前所有消息隊列的信息)。在文件/include/linux/idr.h中定義struct idr結構體,它是一種類似數組的內存區域。在IPC通信中,我們把該數組的每一項條目存儲內容爲struct kern_ipc_perm的結構體的指針。通過/ipc/util.c文件中的int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)函數,可以把struct kern_ipc_perm結構體指針添加到相對應的struct ipc_ids的struct idr中,此時struct kern_ipc_perm*就指向相應的IPC的一個條目,其結構體定義如下:

struct kern_ipc_perm
{
spinlock_t lock;
int deleted;
int id;
key_t key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
umode_t mode;
unsigned long seq;
void *security;
};

對於每一種IPC具體的條目中,struct kern_ipc_perm爲相應條目的第一個元素,得到struct kern_ipc_perm的指針的頭指針,就相當於得到相應條目的頭指針,以消息隊列爲例子代碼如下。struct kern_ipc_perm結構體中的key_t key爲該條目的唯一的key標識符。struct kern_ipc_perm結構體中還定義對應的ipc的特徵信息(uid用戶ID等)。

struct msg_queue {
struct kern_ipc_perm q_perm;
....
};

通過前面描述的內容,我們可以得到到每一個IPC條目的索引,下面我們將介紹具體的IPC條目的存儲內容。

消息隊列
消息隊列就是一個消息的鏈表。可以把消息看作一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向中按照一定的規則添加新消息;對消息隊列有讀權限的進程則可以從消息隊列中讀走消息。消息隊列是隨內核持續的,記錄消息隊列的數據結構位於內核中,只有在內核重起或者顯示刪除一個消息隊列時,該消息隊列纔會真正被刪除。
通過上面的分析我們知道通過struct kern_ipc_perm的指針可以找到相應的條目,在消息隊列中,我們的每一個條目爲一個消息隊列msg_queue,定義在/include/linux/msg.h:

/* one msq_queue structure for each present queue on the system */
struct msg_queue {
    struct kern_ipc_perm q_perm;
    time_t q_stime;         /* last msgsnd time */
    time_t q_rtime;         /* last msgrcv time */
    time_t q_ctime;         /* last change time */
    unsigned long q_cbytes;     /* current number of bytes on queue */
    unsigned long q_qnum;       /* number of messages in queue */
    unsigned long q_qbytes;     /* max number of bytes on queue */
    pid_t q_lspid;          /* pid of last msgsnd */
    pid_t q_lrpid;          /* last receive pid */

    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
};`

每一個消息隊列包括了該隊列的基本信息,struct list_head 類型的消息隊列,以及當前出於阻塞狀態的消息接受者和發送者。對於q_messages隊列來說,每一個元素都爲struct msg_msg類型:

/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
int m_ts; /* message text size */
struct msg_msgseg* next;
void *security;
/* the actual message follows immediately */
};

該處採用了文章 《通過看linux環境相關源碼學習編程》中提到的第四條的方法來存儲具體的消息內容,該結構體用於內核部分存儲消息。在用戶空間的代碼發送和接受到消息爲一個如下的簡單的結構體:

struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};

mtype成員代表消息類別,從消息隊列中讀取消息的一個重要依據就是消息的類型;mtext是消息內容,當然長度不一定爲1。因此,在用戶空間裏,對於發送消息來說,首先預置一個msgbuf緩衝區並寫入消息類型和內容,調用相應的發送函數即可;對讀取消息來說,首先分配這樣一個msgbuf緩衝區,然後把消息讀入該緩衝區即可。
上面基本上介紹了整個消息隊列的內存模型,對消息的操作也比較少,在include/linux/ipc.h文件中定義:

#define MSGSND      11//發送消息到隊列
#define MSGRCV      12//從隊列中接受消息
#define MSGGET      13//打開或創建消息隊列
#define MSGCTL      14//控制消息隊列

第二節將詳細介紹消息隊列的操作。

信號量
信號燈與其他進程間通信方式不大相同,它主要提供對進程間共享資源訪問控制機制。相當於內存中的標誌,進程可以根據它判定是否能夠訪問某些共享資源,同時,進程也可以修改該標誌。除了用於訪問控制外,還可用於進程同步。信號燈有以下兩種類型:

二值信號燈:最簡單的信號燈形式,信號燈的值只能取0或1,類似於互斥鎖。 注:二值信號燈能夠實現互斥鎖的功能,但兩者的關注內容不同。信號燈強調共享資源,只要共享資源可用,其他進程同樣可以修改信號燈的值;互斥鎖更強調進程,佔用資源的進程使用完資源後,必須由進程本身來解鎖。
計算信號燈:信號燈的值可以取任意非負值(當然受內核本身的約束)。

和消息隊列一樣,通過struct kern_ipc_perm的指針可以找到相應的信號量,在System V IPC 信號量中,我們的每一個條目爲一個信號量,定義在/include/linux/sem.h:

struct sem_array {
struct kern_ipc_perm ____cacheline_aligned_in_smp
sem_perm; /* permissions .. see ipc.h */
time_t sem_otime; /* last semop time */
time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct list_head sem_pending; /* pending operations to be processed */
struct list_head list_id; /* undo requests on this array */
int sem_nsems; /* no. of semaphores in array */
int complex_count; /* pending complex operations */
};

其中struct sem *sem_base爲信號量列表的頭指針。struct sem是一個簡單的數據結構:

struct sem {
int semval; /* current value */
int sempid; /* pid of last operation */
struct list_head sem_pending; /* pending single-sop operations */
};

它維持一個當前值,最後操作的進程ID以及一個阻塞隊列。在用戶空間可以通過struct sembuf對sem中的信號量的值進行改變(SETVAL操作)或者通過通過聯合體union semun對整個信號量進行改變(IPC_STAT SETVAL等操作),兩個結構分別如下:

/* semop system calls takes an array of these. */
struct sembuf {
    unsigned short  sem_num;    /* semaphore index in array */
    short       sem_op;     /* semaphore operation */
    short       sem_flg;    /* operation flags */
};

/* arg for semctl system calls. */
union semun {
    int val;            /* value for SETVAL */
    struct semid_ds __user *buf;    /* buffer for IPC_STAT & IPC_SET */
    unsigned short __user *array;   /* array for GETALL & SETALL */
    struct seminfo __user *__buf;   /* buffer for IPC_INFO */
    void __user *__pad;
};

和消息隊列一樣,在在include/linux/ipc.h文件中定義相應的操作:

#define SEMOP        1//改變信號量的值
#define SEMGET       2//打開或者創建一個信號量
#define SEMCTL       3//消息量控制
#define SEMTIMEDOP   4//好像是內部使用吧,沒有仔細去看

具體的操作函數以及參數含義在後面API部分會詳細介紹。

共享內存
共享內存可以說是最有用的進程間通信方式,也是最快的IPC形式。兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由於多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。
採用共享內存通信的一個顯而易見的好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝。對於像管道和消息隊列等通信方式,則需要在內核和用戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據[1]: 一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,並不總是讀寫少量數據後就解除映射,有新的通信時,再重新建 立共享內存區域。而是保持共享區域,直到通信完畢爲止,這樣,數據內容一直保存在共享內存中,並沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回 文件的。因此,採用共享內存的通信方式效率是非常高的。
內核中關於共享內存的實現的數據結構就不去翻閱了,感覺弄懂了前面兩種以後,共享內存的性質基本上差不多。這裏列出一下有關共享內存的操作:

#define SHMAT       21//空間映射:把上面打開的內存區域連接到用戶的進程空間中
#define SHMDT       22//解除映射:將共享內存從當前進程中分離
#define SHMGET      23//創建打開一個內存區域
#define SHMCTL      24//內存區域的控制:包括初始化和刪除內存區域。

一般對內存區域的操作是先打開-》映射-》(操作)-》(控制)-》解除映射。

API以及應用

上面從實現原理上對三種System V IPC進行介紹,我們發現其實三種通信機制和原理差不多,對其進行操作也不多,並且比較相似。下面我將介紹在用戶空間通過相應的API函數來操作相應的IPC。

消息隊列API
在上面原理部分我們介紹了,對消息隊列的操作主要包括:MSGSND:發送消息到隊列;MSGRCV:從隊列中接受消息;MSGGET:打開或創建消息隊列;MSGCTL:控制消息隊列。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

打開或創建消息隊列:System V IPC中通過一個key來唯一標識一個IPC對象,在消息隊列中,一個key唯一標識一個隊列。msgflg低端的九個位爲權限標誌。如果需要創建一個新的消息隊列,需要設置IPC_CREAT標誌,即:msgflg |=IPC_CREAT。如果創建的ID已經存在,此時函數不會出現錯誤,而只是忽略創建標誌。但是如果msgflg 是聯合使用IPC_CREAT和IPC_EXCL,那麼如果創建的ID已經存在,此時將會返回錯誤,可以確保創建的是一個新的IPC對象。如果成功將會返回一個隊列標識符,失敗返回-1.
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

發送消息到消息隊列:msgid爲msgget函數返回隊列標識符,已經不是隊列Key了。msgp爲一個具體的消息內容,它指向一個struct msgbuf類型的結構體:

struct msgbuf {
long mtype; /* type of message */
char *mtext;
};

在具體的應用中,可以自定義該結構體,只要第一個字段爲一個long類型的消息類型,比如,如下的結構體:

struct msgbuf {
long mtype; /* type of message */
int fromPID;
int cmdID
};

msgsz爲發送消息的內容的長度,注意:該長度不包括類型字段的一個long類型的大小,比如上面例子msgsz=sizeof(msgbuf)-sizeof(long)。

msgflg主要是用來控制當前消息隊列無法容納發送過來的數據或者消息的個數達到系統的限制數目時,操作的阻塞或者直接返回。如果被設置了 IPC_NOWAIT,函數將立即返回,不會發送消息,並且返回值爲-1;如果清除了該標誌,函數將會掛起等待隊列騰出可用空間,直到可以容納完整消息或者消息隊列被刪除,或被信號中斷;
msgsnd的發送的數據保存完整性,要麼全部發送成功,要麼失敗,不會發送部分數據。

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

從消息隊列中讀取消息:前三個參數和msgsnd一樣,這裏就不描述了。
msgtyp是一個long類型的整數,用來標識要接受消息類別。如果msgtyp=0,那麼將獲取隊列第一個可用消息。如果msgtyp>0,那麼將接受第一個相同類型的第一個消息。如果msgtyp<0,將獲取類型等於或小於msgtyp絕對值的第一個消息。
msgflg用來控制當前隊列沒有相應類型的消息可以接受時,採取的操作。如果被設置爲IPC_NOWAIT,函數將會立即返回,返回值爲-1。如果該標誌被清除,進程將會掛起等待,直到相應的消息到達,或者消息隊列被刪除,或被信號中斷;

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

消息隊列控制函數:根據cmd的不同,該函數功能不一樣,下面主要討論三種:

IPC_STAT:檢索當期當前消息隊列的屬性,返回的值儲存在一個struct msqid_ds結構體中,該結構見下面。
IPC_SET:如果進程有足夠權限,可以利用buf來設置隊列屬性。
IPC_RMID:用於刪除隊列。

struct msqid_ds是一個定義在/include/linux/msg.h中的結構體,相對來說,還是比較負責,如下:

struct msqid_ds {
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};

由於該結構體比較複雜,在實際開發過程中,特別在進行IPC_SET操作時候,正確的辦法是先調用IPC_STAT得到這樣一個結構體,然後再修改該結構體,最後再調用IPC_SET操作。

信號量API
信號量主要是用來提供對進程間共享資源訪問控制機制。通常我們可能需要僅僅一個進程進入到一個稱爲臨界區域的代碼段,並對相應的資源擁有獨佔式的訪問權。對信號量主要有兩個操作P()和V()操作。假設sv爲一個信號量變量,此時

P(sv):如果sv值是大於0,就給它減去1;如果它的值爲0,就掛起進程的執行。
V(sv):如果有其他進程因爲等待sv而掛起,就讓它恢復運行;如果沒有因sv等待而掛起的進程,就對該信號量進行加1操作。

從信號量的作用來說,我們知道我們對信號量的操作主要有:SEMOP:改變信號量的值;SEMGET:打開或者創建一個信號量;SEMCTL:消息量控制。對信號量的操作流程爲:SEMGET–》SEMOP(-1)—》(操作臨界區)–》SEMOP(+1);(SEMCTL刪除或者修改信號量參數等)。具體的API函數如下

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

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

打開或者創建信號量:參數key是一個鍵值,唯一標識一個信號燈集,用法與msgget()中的key相同;參數nsems指定打開或者新創建的信號燈集中將包含信號燈的數目,一般情況下,都是取值爲1;semflg參數是一些標誌位。參數key和semflg的取值,以及何時打開已有信號燈集或者創建一個新的信號燈集與msgget()中的對應部分相同,不再祥述。該調用返回與健值key相對應的信號燈集描述字。調用返回:成功返回信號燈集描述字,否則返回-1。

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

對信號量進行PV操作:semid爲semget返回的信號量描述符。我們知道在打開或者創建的時候,如果nsems參數不爲1,此時semid指向的是一個信號量集,而不是單獨的一個信號量。因此每次對該信號集進行操作時候必須指定需要操作的信號量數目,即nsops大小。struct sembuf *sops指向的是一個struct sembuf結構體數組,數組大小即爲nsops。如果我們的信號量集只有一個信號量,此時,nsops=1,我們的sops就直接指向一個struct sembuf類型的指針。下面主要介紹一下struct sembuf數據結構,在上面原理部分已經給出該結構體的定義,爲了描述,重複給一次了:

/* semop system calls takes an array of these. */
struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};

該結構比較簡單,semnum是當前需要操作的信號量在信號集中編號,從0開始。
sem_flg可取IPC_NOWAIT以及SEM_UNDO兩個標誌。如果設置了SEM_UNDO標誌,那麼在進程結束時,相應的操作將被取消,這是比較重要的一個標誌位。如果設置了該標誌位,那麼在進程沒有釋放共享資源就退出時,內核將代爲釋放。如果爲一個信號燈設置了該標誌,內核都要分配一個sem_undo結構來記錄它,爲的是確保以後資源能夠安全釋放。事實上,如果進程退出了,那麼它所佔用就釋放了,但信號燈值卻沒有改變,此時,信號燈值反映的已經不是資源佔有的實際情況,在這種情況下,問題的解決就靠內核來完成。這有點像殭屍進程,進程雖然退出了,資源也都釋放了,但內核進程表中仍然有它的記錄,此時就需要父進程調用waitpid來解決問題了。
sem_op爲操作類型.即PV。如果其值爲正數,該值會加到現有的信號內含值中。通常用於釋放所控資源的使用權;如果sem_op的值爲負數,而其絕對值又大於信號的現值,操作將會阻塞,直到信號值大於或等於sem_op的絕對值。通常用於獲取資源的使用權;如果sem_op的值爲0,則操作將暫時阻塞,直到信號的值變爲0。

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

信號量控制函數:semnum爲需要控制的信號量在信號集中的編號,如果信號集只有一個元素,該值爲0。cmd爲控制類型,對於有些操作,需要第四個參數,即爲一個union semun聯合體,根據cmd不同,使用聯合體中不同的字段:

/* arg for semctl system calls. */
union semun {
int val; /* value for SETVAL */
struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short __user *array; /* array for GETALL & SETALL */
struct seminfo __user *__buf; /* buffer for IPC_INFO */
void __user *__pad;
};

cmd=SETVAL:用於把信號量初始化爲一個已知的值,用於第一次使用該信號量時,完成信號量值的初始化。此時使用的是union semun 中val字段。
cmd=IPC_RMID:用於刪除已經不再繼續使用的信號量標識符,該操作會解除所有在該信號量上的掛起進程。
cmd=IPC_STAT/IPC_SET:和消息隊列相似。

共享內存API
共享內存是通過把同一段內存分別映射到自己用戶進程空間中,從而實現兩個進程之間的通信。注意:共享內存通信本身沒有提供同步機制,如果同時被多個進程進行映射和寫操作,會導致破壞該內存空間的內容。因此在實際應用過程中,需要通過其他的機制來同步對共享內存的訪問。共享內存的操作和前面兩個差不多,這裏就放在一起說了,不分開,感覺囉嗦了。

#include <sys/types.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);//創建共享內存
void *shmat(int shmid, const void *shmaddr, int shmflg);//映射到自己的內存空間
int shmdt(const void *shmaddr);//解除映射
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//控制共享內存

sheget爲創建或者打開一個共享內存,成功就返回相應的共享內存標識符,否則就返回-1。shmflg低端9位爲權限標誌,利用共享內存進行通信時候,可以利用該標誌對共享內存進行只讀,只寫等權限控制。

shmat爲空間映射。通過創建的共享內存,在它能被進程訪問之前,需要把該段內存映射到用戶進程空間。shmaddr是用來指定共享內存映射到當前進程中的地址位置,要想該設置有用,shmflg必須設置爲SHM_RND標誌。大部分情況下,應該設置爲爲空指針(void *)0。讓系統自動選擇地址,從而減小程序對硬件的依賴性。shmflg除了上面的設置以外,還可以設置爲SHM_RDONLY,使得映射過來的地址只讀。如果函數調用成功,返回映射的地址的第一個字節,否則返回-1。

shmdt用於解除上面的映射。

shmctl用於控制共享內存,相比上面幾個控制函數,這裏的比較簡單,明確的三個參數。struct shmid_ds定義在include/linux/shm.h,如下。cmd有IPC_STAT,IPC_SET,IPC_RMID含義和消息隊列一樣的。好了。好像很簡單一樣。。。。偷笑

struct shmid_ds {
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

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