最好的參考資料:
1.師從互連網。
2.UNP v2 Posix IPC的相關章節2、5、10、13。
3.Linux man 命令。
先緬懷下 Stevens 大師。好那麼開始~~~~ 說點不打緊的,雖說Posix IPC 是標準的IPC,是以後趨勢,但是,現在大多數應用程式仍然在使用 System V IPC機制。這裡從APUE和《深入理解Linux核心架構》一點都沒介紹,ULK也只是介紹了Posix 訊息佇列。
第一條:Posix IPC都使用 “Posix IPC 名字”進行標誌。
Posix.1是這麼描述Posix IPC名字的:它可能是某個檔案系統中的一個真正的路徑名,也可能不是。mq_open、sem_open和shm_open這三個函數的第一個參數就是這樣的一個名字。這是UNPv2 中2.2節的描述。不是很詳細,書中很多已和現在的linux 不符~~
sem_open原型:sem_t *sem_open(const char *name, int oflag,...);
Linux下,sem_open都是建立在/dev/shm目錄下的(Linux的/dev/shm是一個tmpfs類型的特殊fs,就像/proc, /sys一樣,用mount命令就可以看到),而且會加上sem.這樣的prefix,所以,如果name是“hehe”,這樣建立出來的檔案就是/dev/shm/sem.hehe了。
參看glibc中nptl/sem_open.c源碼,name參數若是以‘/’開頭將被忽略掉
- /* Construct the filename. */
- while (name[0] == '/')
- ++name;
結論:
1.sem_open中
name參數可以是以大於等於0個‘/’開頭(都被忽略了)的字串,且在除了開頭不能再含有‘/’字元了。(例如name參數是hehe/hehe,即便/dev/shm/hehe/這個路徑存在,也不對,為什麼不能,看了源碼也沒懂,希望懂的人回一下,:-));
2.shm_open
和sem_open中不同的就是name可以是個路徑即 參數中除了開頭可以含有‘/’,但前提是路徑中的目錄必須存在。
3.mq_open
要求很嚴格,只能以”/+字串“這種作為參數,即必須以‘/’開頭,且字串不能在含有‘/’;
第二條:運行前提
訊號量運行時要加 -lrt。
訊息佇列則要手動掛載 mqueue類型的檔案系統,具體命令如下:(詳細參看 man 7 mq_overview)
mkdir /dev/mqueue mount -t
mqueue none /dev/mqueue
第三條: 運行結果
訊息佇列在/dev/mqueue目錄下產生檔案。
訊號量和共用記憶體產生檔案在/dev/shm 目錄下。
第四條:Posix 訊息佇列機制Posix 訊息佇列的實現
可以參見《ULK》第十九章。基本的原理是:訊息佇列描述符mqd通過VFS的fget函數---->對應的/dev/mqueue/上的訊息佇列檔案----->該檔案的i節點號------>找到包含這個節點的mqueue_inode_info。每個訊息佇列對應一個mqueue_inode_info,他包含inode對象,而inode又與/dev/mqueue/特殊檔案系統的一個檔案相對應。而隊列中的訊息被放在mqueue_inode_info中的單向鏈表中。訊息的為msg_msg,與System VIPC 訊息描述符完全一樣。
#include <mqueue.h>
mqd_t mq_open (const char *name, int oflag, .../*mode_t mode ,struct mq_attr_t *attr */);//在/dev/mqueue 下建立名為name的檔案
int mq_close (mqd_t mqdes);
int mq_unlink (const char *name);//name索引的檔案的目錄項連結stat.nlink減1;
struct mq_attr
{
long int mq_flags;/* Message queue flags. */
long int mq_maxmsg;/* Maximum number of messages. */
long int mq_msgsize;/* Maximum message size of a message. */
long int mq_curmsgs;/* Number of messages currently queued. */
long int __pad[4];
};//做mq_open的第四個參數 自動忽略 flags和curmsgs的值;
int mq_getattr (mqd_t mqdes, struct mq_attr * mqstat);
int mq_setattr (mqd_t __mqdes, const struct mq_attr * restrict mqstat, struct mq_attr * restrict omqstat);
/* Receive the oldest from highest priority messages in message queue MQDES. */
ssize_t mq_receive (mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio) ;
/* Add message pointed by MSG_PTR to message queue MQDES. */
int mq_send (mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio) ;
以上這幾個函數的原理是:進程甲通過mq_open在/dev/mqueue/下建立一個訊息佇列檔案 ,之後進程甲、乙、丙、丁等通過mq_open開啟他,再通過mq_send 和mq_receive以這個檔案為媒介(訊息佇列) 收發資訊。mq_getatt、mq_setatt獲得和設定訊息佇列屬性。
union sigval { /* Data passed with notification */
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value */
};
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal */
union sigval sigev_value; /* Data passed with notification */
void (*sigev_notify_function) (union sigval);/* Function for thread notification */
void *sigev_notify_attributes; /* Thread function attributes */
};
/* Register notification issued upon message arrival to an empty message queue MQDES. */
int mq_notify (mqd_t mqdes, const struct sigevent * notification);//Ubuntu 10.10測試這個函數在原來隊列中存在訊息後註冊,新產生的資料,註冊的進程無反映。只有當訊息佇列為空白時註冊,當有新訊息產生,註冊進程會有反映~~~
mq_notify函數當參數notification->sigev_notify==SIGEV_SIGNAL 時的原理是通過參數mqd,notification->sigev_signo以及本調用進程本身,將訊息佇列通過一個訊號和進程聯絡起來,在隊列為空白時,放置一個訊息向註冊進程發送以訊號,收到訊號的進程遞送這個訊號(即調用訊號處理函數處理之,:-) 。)。
mq_notify函數當參數notification->sigev_notify==SIGEV_THREAD時的原理是通過參數mqd,notification->sigev_notify_function和這個線程函數的參數notification->sigev_value以及建立的這個線程的屬性notification->sigev_notify_attributes,將訊息佇列通過一個訊號和一個線程聯絡起來,在隊列為空白時,放置一個訊息時就調用這個線程處理函數處理訊息。
無論是訊號版還是線程版,一次訊息接受後,都需要重新註冊。
(亂入:int sigwait(const sigset_t *set,int *sig);//同步的等待一個非同步事件:我們是在使用訊號,但是卻沒有涉及非同步訊號處理常式!)
第五條:Posix 共用記憶體機制 Psoix共用記憶體區的實現
是構築在mmap系統調用函數之上的。
#include <sys/mman.h>
int shm_open ( const char * name, int oflag, mode_t mode);//shm_open 的mode參數必須總是指定,可以設為0.
int shm_unlink ( const char * name);
#include <unistd.h>
#include <sys/types.h>
int ftruncate (int fd, off_t length) ;
//If the file previously was larger than this size, the extra data is lost. If the file previously was shorter, it is extended, and the extended part reads as null bytes ('/0'). (man ~~~)
#include <sys/types.h>
#include <sys/stat.h>
int fstat(int fd, struct stat *buf);
Posix 共用記憶體的基本原理是:進程甲通過shm_open在/dev/shm/目錄下建立一個以參數name為名子的檔案,進程甲、乙、丙、丁都可以通過調用shm_open以相同的name參數開啟在/dev/shm/下的這個檔案,而且可選的是可以用ftruncate函數改變這個檔案的大小。並通過fstat獲得stat.st_size獲得這個檔案的整體大小用來做mmap的參數。用mmap函數把這個檔案對應 的調用進程的記憶體空間。獲得一個在調用進程自身內部的一個地址,進行讀寫操作。
第六條:Posix 訊號量Posix訊號量的實現
Posix訊號量常用於線程同步(無名訊號量);system v訊號量常用於進程同步。Posix訊號量有兩類:有名訊號量和無名訊號量(即基於記憶體的訊號量)。一個進程內多個線程之間共用、使用基於記憶體(就是進程的記憶體空間^_^)的訊號量。多個進程之間共用、使用處於共用記憶體區的有名訊號量。UNPv2給出了Posix有名訊號量基於FIFO、記憶體對應 I/O+互斥鎖+條件變數、System V訊號量的三種實現方式。看了下nptl/sem_open.c ,linux上Posix訊號量也是基於mmap實現的。而Posix無名訊號量的實現簡單說來就是在進程空間分配了一個sem_t 類型的記憶體空間調用sem_init初始化後使用。
訊號量還可以分為二值訊號量——把訊號量初始化為1,計數訊號量——通常初始化為某個代表資源數的N值。但實現訊號量的代碼中並無這種區分。
Stevens大師在UNPv2中列出了訊號量、互斥鎖、和條件變數之間的差異:
1.互斥鎖總是由給它上鎖的線程解鎖,訊號量的P操作卻不必由執行過它的V操作的同一線程執行。
2.互斥鎖要麼被鎖住,要麼被解開。(二值狀態,類似二值訊號量)。
3.由於訊號量中有一個狀態值(N),所以訊號量的P操作總是留下了他們的回憶(就是被記住了——N編程N+1了)。然而當向一 個條件變數發送訊號時,如果沒有線程等在這個條件變數上,那麼訊號就丟失了。
#include <semaphore.h>
//基於記憶體的訊號量使用的函數
int sem_init (sem_t * sem, int pshared, unsigned int value);
int sem_destroy (sem_t *__sem);
//有名訊號量使用的函數
sem_t *sem_open (const char *name, int oflag, ...) ;
int sem_close (sem_t *sem);
int sem_unlink (const char *name);
//共用的函數
int sem_wait (sem_t * sem);
int sem_trywait (sem_t * sem);
int sem_post (sem_t *sem) ;//此函數為訊號安全函數(UNPv2)
int sem_getvalue (sem_t *restrict sem, int *restrict sval);
int sem_timedwait (sem_t *restrict sem,const struct timespec *restrict abstime);
Posix 訊號量的基本原理是:P、V操作!
參看:http://wangcong.org/blog/?p=591http://www.cnblogs.com/super119/archive/2010/12/13/1904392.html