linux基礎——linux進程間通信(IPC)機制總結 linux基礎——linux進程間通信(IPC)機制總結

linux基礎——linux進程間通信(IPC)機制總結

    在linux下的多個進程間的通信機制叫做IPC(Inter-Process Communication),它是多個進程之間相互溝通的一種方法。在linux下有多種進程間通信的方法:半雙工管道、命名管道、消息隊列、信號、信號量、共享內存、內存映射文件,套接字等等。使用這些機制可以爲linux下的網絡服務器開發提供靈活而又堅固的框架。

1. 管道 (PIPE)
    管道實際是用於進程間通信的一段共享內存,創建管道的進程稱爲管道服務器,連接到一個管道的進程爲管道客戶機。一個進程在向管道寫入數據後,另一進程就可以從管道的另一端將其讀取出來。
管道的特點:
1、管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
2、只能用於父子進程或者兄弟進程之間(具有親緣關係的進程)。比如fork或exec創建的新進程,在使用exec創建新進程時,需要將管道的文件描述符作爲參數傳遞給exec創建的新進程。當父進程與使用fork創建的子進程直接通信時,發送數據的進程關閉讀端,接受數據的進程關閉寫端。
3、單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。
4、數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。

管道的實現機制:

    管道是由內核管理的一個緩衝區,相當於我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩衝區不需要很大,它被設計成爲環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。

管道只能在本地計算機中使用,而不可用於網絡間的通信。 
pipe函數原型:
  1. #include <unistd.h>
  2. int pipe(int file_descriptor[2]);//建立管道,該函數在數組上填上兩個新的文件描述符後返回0,失敗返回-1。
  3. eg.int fd[2]
  4. int result = pipe(fd);
通過使用底層的read和write調用來訪問數據。 向file_descriptor[1]寫數據,從file_descriptor[0]讀數據。寫入與讀取的順序原則是先進先出。 
管道讀寫規則
當沒有數據可讀時
    O_NONBLOCK disable:read調用阻塞,即進程暫停執行,一直等到有數據來到爲止。
    O_NONBLOCK enable:read調用返回-1,errno值爲EAGAIN。
當管道滿的時候
    O_NONBLOCK disable: write調用阻塞,直到有進程讀走數據
    O_NONBLOCK enable:調用返回-1,errno值爲EAGAIN
如果所有管道寫端對應的文件描述符被關閉,則read返回0
如果所有管道讀端對應的文件描述符被關閉,則write操作會產生信號SIGPIPE
當要寫入的數據量不大於PIPE_BUF(Posix.1要求PIPE_BUF至少512字節)時,linux將保證寫入的原子性。
當要寫入的數據量大於PIPE_BUF時,linux將不再保證寫入的原子性。

2. 命名管道(FIFO)
命名管道是一種特殊類型的文件,它在系統中以文件形式存在。這樣克服了管道的弊端,他可以允許沒有親緣關係的進程間通信。 
創建管道的兩個系統調用原型:
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. int mkfifo(const char *filename,mode_t mode); //建立一個名字爲filename的命名管道,參數mode爲該文件的權限(mode%~umask),若成功則返回0,否則返回-1,錯誤原因存於errno中。
  4. eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );

具體操作方法只要創建了一個命名管道然後就可以使用open、read、write等系統調用來操作。創建可以手工創建或者程序中創建。 
  1. int mknod(const char *path, mode_t mode, dev_t dev); //第一個參數表示你要創建的文件的名稱,第二個參數表示文件類型,第三個參數表示該文件對應的設備文件的設備號。只有當文件類型爲 S_IFCHR 或 S_IFBLK 的時候該文件纔有設備號,創建普通文件時傳入0即可。
  2. eg.mknod(FIFO_FILE,S_IFIFO|0666,0);

管道和命名管道的區別:
對於命名管道FIFO來說,IO操作和普通管道IO操作基本一樣,但是兩者有一個主要的區別,在命名管道中,管道可以是事先已經創建好的,比如我們在命令行下執行
mkfifo myfifo
就是創建一個命名通道,我們必須用open函數來顯示地建立連接到管道的通道,而在管道中,管道已經在主進程裏創建好了,然後在fork時直接複製相關數據或者是用exec創建的新進程時把管道的文件描述符當參數傳遞進去。
一般來說FIFO和PIPE一樣總是處於阻塞狀態。也就是說如果命名管道FIFO打開時設置了讀權限,則讀進程將一直阻塞,一直到其他進程打開該FIFO並向管道寫入數據。這個阻塞動作反過來也是成立的。如果不希望命名管道操作的時候發生阻塞,可以在open的時候使用O_NONBLOCK標誌,以關閉默認的阻塞操作。

3. 信號 (signal)
信號機制是unix系統中最爲古老的進程之間的通信機制,用於一個或幾個進程之間傳遞異步信號。信號可以有各種異步事件產生,比如鍵盤中斷等。shell也可以使用信號將作業控制命令傳遞給它的子進程。
在此列出幾個簡單使用方法定義: 
  1. #include <sys/types.h>
  2. #include <signal.h>
  3. void (*signal(int sig,void (*func)(int)))(int); //用於截取系統信號,第一個參數爲信號,第二個參數爲對此信號掛接用戶自己的處理函數指針。返回值爲以前信號處理程序的指針。
  4. eg.int ret = signal(SIGSTOP, sig_handle);

由於signal不夠健壯,推薦使用sigaction函數。
  1. int kill(pid_t pid,int sig); //kill函數向進程號爲pid的進程發送信號,信號值爲sig。當pid爲0時,向當前系統的所有進程發送信號sig。
  2. int raise(int sig);//向當前進程中自舉一個信號sig, 即向當前進程發送信號。
  3. #include <unistd.h>
  4. unsigned int alarm(unsigned int seconds); //alarm()用來設置信號SIGALRM在經過參數seconds指定的秒數後傳送給目前的進程。如果參數seconds爲0,則之前設置的鬧鐘會被取消,並將剩下的時間返回。使用alarm函數的時候要注意alarm函數的覆蓋性,即在一個進程中採用一次alarm函數則該進程之前的alarm函數將失效。
  5. int pause(void); //使調用進程(或線程)睡眠狀態,直到接收到信號,要麼終止,或導致它調用一個信號捕獲函數。


4. 消息隊列(Message queues)
消息隊列是內核地址空間中的內部鏈表,通過linux內核在各個進程直接傳遞內容,消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲得,每個消息隊列可以用IPC標識符唯一地進行識別。內核中的消息隊列是通過IPC的標識符來區別,不同的消息隊列直接是相互獨立的。每個消息隊列中的消息,又構成一個獨立的鏈表
消息隊列克服了信號承載信息量少,管道只能承載無格式字符流。 
消息隊列頭文件: 
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <sys/msg.h>

1、消息緩衝區結構:
  1. struct msgbuf{
  2. long mtype;
  3. char mtext[1];//柔性數組
  4. }

在結構中有兩個成員,mtype爲消息類型,用戶可以給某個消息設定一個類型,可以在消息隊列中正確地發送和接受自己的消息。mtext爲消息數據,採用柔性數組,用戶可以重新定義msgbuf結構。例如:
  1. struct msgbuf{
  2. long mtype;
  3. char mtext[1];//柔性數組
  4. }

當然用戶不可隨意定義msgbuf結構,因爲在linux中消息的大小是有限制的,在linux/msg.h中定義如下:
#define MSGMAX 8192
消息總的大小不能超過8192個字節,包括mtype成員(4個字節)。
2、msqid_ds內核數據結構。
<div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">struct msgid_ds{</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   struct ipc_perm msg_perm{</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   time_t msg_stime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   time_t msg_rtime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   time_t msg_ctime;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   unsigned long _msg_cbuyes;</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">    ..........</div><div style="font-family: 微軟雅黑; font-size: 14px; line-height: 21px;">   };</div>

Linux內核中,每個消息隊列都維護一個結構體,此結構體保存着消息隊列當前狀態信息,該結構體在頭文件linux/msg.h中定義。
3、ipc_perm內核數據結構
  1. struct ipc_perm{
  2. key_t key;
  3. uid_t uid;
  4. gid_t gid;
  5. .......
  6. };


結構體ipc_perm保存着消息隊列的一些重要的信息,比如說消息隊列關聯的鍵值,消息隊列的用戶id組id等。它定義在頭文件linux/ipc.h中。
常用函數:
系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到。 
  1. key_t ftok( const char * fname, int id );//參數一爲目錄名稱, 參數二爲id。如指定文件的索引節點號爲65538,換算成16進製爲0x010002,而你指定的ID值爲38,換算成16進製爲0x26,則最後的key_t返回值爲0x26010002。
  2. eg.key_t key = key =ftok(".", 1);
  3. int msgget(key_t key,int msgflag); //msgget用來創建和訪問一個消息隊列。程序必須提供一個鍵值來命名特定的消息隊列。
  4. eg.int msg_id = msgget(key, IPC_CREATE | IPC_EXCL | 0x0666);//根據關鍵字創建一個新的隊列(IPC_CREATE),如果隊列存在則出錯(IPC_EXCL),擁有對文件的讀寫執行權限(0666)。
  5. int msgsnd(int msgid,const void *msgptr,size_t msg_sz,int msgflg); //msgsnd函數允許我們把一條消息添加到消息隊列中。msgptr只想準備發送消息的指針,指針結構體必須以一個長整型變量開始。
  6. eg.struct msgmbuf{
  7. int mtype;
  8. char mtext[10];
  9. };
  10. struct msgmbuf msg_mbuf;
  11. msg_mbuf.mtype = 10;//消息大小10字節
  12. memcpy(msg_mbuf.mtext, "測試消息", sizeof("測試消息"));
  13. int ret = msgsnd(msg_id, &msg_mbuf, sizeof("測試消息"), IPC_NOWAIT);
  14. int msgrcv(int msgid, void *msgptr, size_t msg_sz, long int msgtype, int msgflg); //msgrcv可以通過msqid對指定消息隊列進行接收操作。第二個參數爲消息緩衝區變量地址,第三個參數爲消息緩衝區結構大小,但是不包括mtype成員長度,第四個參數爲mtype指定從隊列中獲取的消息類型。
  15. eg.int ret = msgrcv(msg_id, &msg_mbuf, 10, 10, IPC_NOWAIT | MSG_NOERROR);
  16. int msgctl(int msqid,int cmd,struct msqid_ds *buf); //msgctl函數主要是一些控制如刪除消息隊列等操作。 cmd值如下:
  17. IPC_STAT:獲取隊列的msgid_ds結構,並把它存到buf指向的地址。
  18. IPC_SET:將隊列的msgid_ds設置爲buf指向的msgid_ds。
  19. IPC_RMID:內核刪除消息隊列,最後一項填NULL, 執行操作後,內核會把消息隊列從系統中刪除。
消息隊列的本質
Linux的消息隊列(queue)實質上是一個鏈表,它有消息隊列標識符(queue ID)。 msgget創建一個新隊列或打開一個存在的隊列;msgsnd向隊列末端添加一條新消息;msgrcv從隊列中取消息, 取消息是不一定遵循先進先出的, 也可以按消息的類型字段取消息。

消息隊列與命名管道的比較
消息隊列跟命名管道有不少的相同之處,通過與命名管道一樣,消息隊列進行通信的進程可以是不相關的進程,同時它們都是通過發送和接收的方式來傳遞數據的。在命名管道中,發送數據用write,接收數據用read,則在消息隊列中,發送數據用msgsnd,接收數據用msgrcv。而且它們對每個數據都有一個最大長度的限制。
與命名管道相比,消息隊列的優勢在於,1、消息隊列也可以獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。2、同時通過發送消息還可以避免命名管道的同步和阻塞問題,不需要由進程自己來提供同步方法。3、接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默認地接收。

5. 信號量(Semaphore
信號量是一種計數器,用於控制對多個進程共享的資源進行的訪問。它們常常被用作一個鎖機制,在某個進程正在對特定的資源進行操作時,信號量可以防止另一個進程去訪問它。 
信號量是特殊的變量,它只取正整數值並且只允許對這個值進行兩種操作:等待(wait)和信號(signal)。(P、V操作,P用於等待,V用於信號) 
p(sv):如果sv的值大於0,就給它減1;如果它的值等於0,就掛起該進程的執行 
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行;如果沒有其他進程因等待sv而掛起,則給它加1 
簡單理解就是P相當於申請資源,V相當於釋放資源 
信號量頭文件: 
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <sys/sem.h>

內核爲每個信號量集合都維護一個semid_ds結構:
  1. struct semid_ds{
  2. struct ipc_perm sem_perm;
  3. unsigned short sem_nsems;
  4. time_t sem_otime;
  5. time_t sem_ctime;
  6. ...
  7. }
信號量數據結構:
  1. union semun{
  2. int val;
  3. struct semid_ds *buf;
  4. unsigned short *array;
  5. struct seminfo *__buf;
  6. }

信號量操作sembuf結構:
  1. struct sembuf{
  2. ushort sem_num;//信號量的編號
  3. short sem_op;//信號量的操作。如果爲正,則從信號量中加上一個值,如果爲負,則從信號量中減掉一個值,如果爲0,則將進程設置爲睡眠狀態,直到信號量的值爲0爲止。
  4. short sem_flg;//信號的操作標誌,一般爲IPC_NOWAIT。
  5. }

常用函數:
  1. int semget(key_t key, int num_sems, int sem_flags); //semget函數用於創建一個新的信號量集合 , 或者訪問一個現有的集合(不同進程只要key值相同即可訪問同一信號量集合)。第一個參數key是ftok生成的鍵值,第二個參數num_sems可以指定在新的集合應該創建的信號量的數目,第三個參數sem_flags是打開信號量的方式。
  2. eg.int semid = semget(key, 0, IPC_CREATE | IPC_EXCL | 0666);//第三個參數參考消息隊列int msgget(key_t key,int msgflag);第二個參數。
  3. int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); //semop函數用於改變信號量的值。第二個參數是要在信號集合上執行操作的一個數組,第三個參數是該數組操作的個數 。
  4. eg.struct sembuf sops = {0, +1, IPC_NOWAIT};//對索引值爲0的信號量加一。
  5. semop(semid, &sops, 1);//以上功能執行的次數爲一次。
  6. int semctl(int sem_id, int sem_num, int command,...); //semctl函數用於信號量集合執行控制操作,初始化信號量的值,刪除一個信號量等。 類似於調用msgctl(), msgctl()是用於消息隊列上的操作。第一個參數是指定的信號量集合(semget的返回值),第二個參數是要執行操作的信號量在集合中的索引值(例如集合中第一個信號量下標爲0),第三個command參數代表要在集合上執行的命令。
  7. IPC_STAT:獲取某個集合的semid_ds結構,並把它存儲到semun聯合體的buf參數指向的地址。
  8. IPC_SET:將某個集合的semid_ds結構的ipc_perm成員的值。該命令所取的值是從semun聯合體的buf參數中取到。
  9. IPC_RMID:內核刪除該信號量集合。
  10. GETVAL:返回集合中某個信號量的值。
  11. SETVAL:把集合中單個信號量的值設置成爲聯合體val成員的值。

6. 共享內存(Share Memory)
共享內存是在多個進程之間共享內存區域的一種進程間的通信方式,由IPC爲進程創建的一個特殊地址範圍,它將出現在該進程的地址空間(這裏的地址空間具體是哪個地方?)中。其他進程可以將同一段共享內存連接到自己的地址空間中。所有進程都可以訪問共享內存中的地址,就好像它們是malloc分配的一樣。如果一個進程向共享內存中寫入了數據,所做的改動將立刻被其他進程看到。 
共享內存是IPC最快捷的方式,因爲共享內存方式的通信沒有中間過程,而管道、消息隊列等方式則是需要將數據通過中間機制進行轉換。共享內存方式直接將某段內存段進行映射,多個進程間的共享內存是同一塊的物理空間,僅僅映射到各進程的地址不同而已,因此不需要進行復制,可以直接使用此段空間。
注意:共享內存本身並沒有同步機制,需要程序員自己控制。 
 共享內存頭文件:
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <sys/shm.h>
結構shmid_ds結構體(是不是很眼熟,看消息隊列的msgid_ds結構體):
  1. strcut shmid_ds{
  2. struct ipc_perm shm_perm;
  3. size_t shm_segsz;
  4. time_t shm_atime;
  5. time_t shm_dtime;
  6. ......
  7. }
共享內存函數定義:
  1. int shmget(key_t key,size_t size,int shmflg); //shmget函數用來創建一個新的共享內存段, 或者訪問一個現有的共享內存段(不同進程只要key值相同即可訪問同一共享內存段)。第一個參數key是ftok生成的鍵值,第二個參數size爲共享內存的大小,第三個參數sem_flags是打開共享內存的方式。
  2. eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三個參數參考消息隊列int msgget(key_t key,int msgflag);
  3. void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函數通過shm_id將共享內存連接到進程的地址空間中。第二個參數可以由用戶指定共享內存映射到進程空間的地址,shm_addr如果爲0,則由內核試着查找一個未映射的區域。返回值爲共享內存映射的地址。
  4. eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget獲得
  5. int shmdt(const void *shm_addr); //shmdt函數將共享內存從當前進程中分離。 參數爲共享內存映射的地址。
  6. eg.shmdt(shms);
  7. int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函數是控制函數,使用方法和消息隊列msgctl()函數調用完全類似。參數一shm_id是共享內存的句柄,cmd是向共享內存發送的命令,最後一個參數buf是向共享內存發送命令的參數。

消息隊列、信號量以及共享內存的相似之處:
它們被統稱爲XSI IPC,它們在內核中有相似的IPC結構(消息隊列的msgid_ds,信號量的semid_ds,共享內存的shmid_ds),而且都用一個非負整數的標識符加以引用(消息隊列的msg_id,信號量的sem_id,共享內存的shm_id,分別通過msgget、semget以及shmget獲得),標誌符是IPC對象的內部名,每個IPC對象都有一個鍵(key_t key)相關聯,將這個鍵作爲該對象的外部名。

XSI IPC和PIPE、FIFO的區別:
1、XSI IPC的IPC結構是在系統範圍內起作用,沒用使用引用計數。如果一個進程創建一個消息隊列,並在消息隊列中放入幾個消息,進程終止後,即使現在已經沒有程序使用該消息隊列,消息隊列及其內容依然保留。而PIPE在最後一個引用管道的進程終止時,管道就被完全刪除了。對於FIFO最後一個引用FIFO的進程終止時,雖然FIFO還在系統,但是其中的內容會被刪除。
2、和PIPE、FIFO不一樣,XSI IPC不使用文件描述符,所以不能用ls查看IPC對象,不能用rm命令刪除,不能用chmod命令刪除它們的訪問權限。只能使用ipcs和ipcrm來查看可以刪除它們。

7. 內存映射(Memory Map)
內存映射文件,是由一個文件到一塊內存的映射。內存映射文件與虛擬內存有些類似,通過內存映射文件可以保留一個地址的區域,
同時將物理存儲器提交給此區域,內存文件映射的物理存儲器來自一個已經存在於磁盤上的文件,而且在對該文件進行操作之前必須首先對文件進行映射。使用內存映射文件處理存儲於磁盤上的文件時,將不必再對文件執行I/O操作。每一個使用該機制的進程通過把同一個共享的文件映射到自己的進程地址空間來實現多個進程間的通信(這裏類似於共享內存,只要有一個進程對這塊映射文件的內存進行操作,其他進程也能夠馬上看到)。
使用內存映射文件不僅可以實現多個進程間的通信,還可以用於處理大文件提高效率。因爲我們普通的做法是把磁盤上的文件先拷貝到內核空間的一個緩衝區再拷貝到用戶空間(內存),用戶修改後再將這些數據拷貝到緩衝區再拷貝到磁盤文件,一共四次拷貝。如果文件數據量很大,拷貝的開銷是非常大的。那麼問題來了,系統在在進行內存映射文件就不需要數據拷貝?mmap()確實沒有進行數據拷貝,真正的拷貝是在在缺頁中斷處理時進行的,由於mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關係,直接將文件從硬盤拷貝到用戶空間,所以只進行一次數據拷貝。效率高於read/write。
內存映射頭文件:
  1. #include <sys.mman.h>
  2. void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); //mmap函數將一個文件或者其它對象映射進內存。 第一個參數爲映射區的開始地址,設置爲0表示由系統決定映射區的起始地址,第二個參數爲映射的長度,第三個參數爲期望的內存保護標誌,第四個參數是指定映射對象的類型,第五個參數爲文件描述符(指明要映射的文件),第六個參數是被映射對象內容的起點。成功返回被映射區的指針,失敗返回MAP_FAILED[其值爲(void *)-1]。
  3. int munmap(void* start,size_t length); //munmap函數用來取消參數start所指的映射內存起始地址,參數length則是欲取消的內存大小。如果解除映射成功則返回0,否則返回-1,錯誤原因存於errno中錯誤代碼EINVAL。
  4. int msync(void *addr,size_t len,int flags); //msync函數實現磁盤文件內容和共享內存取內容一致,即同步。第一個參數爲文件映射到進程空間的地址,第二個參數爲映射空間的大小,第三個參數爲刷新的參數設置。

共享內存和內存映射文件的區別:
內存映射文件是利用虛擬內存文件映射到進程的地址空間中去,在此之後進程操作文件,就像操作進程空間裏的地址一樣了,比如使用c語言的memcpy等內存操作的函數。這種方法能夠很好的應用在需要頻繁處理一個文件或者是一個大文件的場合,這種方式處理IO效率比普通IO效率要高
  共享內存是內存映射文件的一種特殊情況,內存映射的是一塊內存,而非磁盤上的文件。共享內存的主語是進程(Process),操作系統默認會給每一個進程分配一個內存空間,每一個進程只允許訪問操作系統分配給它的哪一段內存,而不能訪問其他進程的。而有時候需要在不同進程之間訪問同一段內存,怎麼辦呢?操作系統給出了 創建訪問共享內存的API,需要共享內存的進程可以通過這一組定義好的API來訪問多個進程之間共有的內存,各個進程訪問這一段內存就像訪問一個硬盤上的文件一樣。

內存映射文件與虛擬內存的區別和聯繫:
內存映射文件和虛擬內存都是操作系統內存管理的重要部分,兩者有相似點也有不同點。
聯繫:虛擬內存和內存映射都是將一部分內容加載到內存,另一部放在磁盤上的一種機制。對於用戶而言都是透明的。
區別:虛擬內存是硬盤的一部分,是內存和硬盤的數據交換區,許多程序運行過程中把暫時不用的程序數據放入這塊虛擬內存,節約內存資源。內存映射是一個文件到一塊內存的映射,這樣程序通過內存指針就可以對文件進行訪問。
虛擬內存的硬件基礎是分頁機制。另外一個基礎就是局部性原理(時間局部性和空間局部性),這樣就可以將程序的一部分裝入內存,其餘部分留在外存,當訪問信息不存在,再將所需數據調入內存。而內存映射文件並不是局部性,而是使虛擬地址空間的某個區域銀蛇磁盤的全部或部分內容,通過該區域對被映射的磁盤文件進行訪問,不必進行文件I/O也不需要對文件內容進行緩衝處理。

8. 套接字 
套接字機制不但可以單機的不同進程通信,而且使得跨網機器間進程可以通信。 
套接字的創建和使用與管道是有區別的,套接字明確地將客戶端與服務器區分開來,可以實現多個客戶端連到同一服務器。 
服務器套接字連接過程描述: 
首先,服務器應用程序用socket創建一個套接字,它是系統分配服務器進程的類似文件描述符的資源。 接着,服務器調用bind給套接字命名。這個名字是一個標示符,它允許linux將進入的針對特定端口的連接轉到正確的服務器進程。 然後,系統調用listen函數開始接聽,等待客戶端連接。listen創建一個隊列並將其用於存放來自客戶端的進入連接。 當客戶端調用connect請求連接時,服務器調用accept接受客戶端連接,accept此時會創建一個新套接字,用於與這個客戶端進行通信。 
客戶端套接字連接過程描述: 
客戶端首先調用socket創建一個未命名套接字,讓後將服務器的命名套接字作爲地址來調用connect與服務器建立連接。 
只要雙方連接建立成功,我們就可以像操作底層文件一樣來操作socket套接字實現通信。 
幾個基礎函數定義: 
  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int socket(it domain,int type,int protocal);
  4. int bind(int socket,const struct sockaddr *address,size_t address_len);
  5. int listen(int socket,int backlog);
  6. int accept(int socket,struct sockaddr *address,size_t *address_len);
  7. int connect(int socket,const struct sockaddr *addrsss,size_t address_len);
詳細請看:http://blog.csdn.net/a987073381/article/details/51869000
還記得消息隊列中的msgbuf結構嗎?在socket編程中也同樣適用,在socket編程中,一個服務可以接受多個客戶端的連接,可以爲每個客戶端設定一個消息類型,服務器和客戶端直接的通信可以通過此消息類型來發送和接受消息,而且多個客戶端之間也可以通過消息類型來區分。

參考:
《linux網絡編程》
《unix環境高級編程》

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