2017-2018-1 20155222 《信息安全系统设计基础》第10周 Linux下的IPC机制
2017-2018-1 20155222 《信息安全系統設計基礎》第10周 Linux下的IPC機制
IPC機制
在linux下的多個進程間的通信機制叫做IPC(Inter-Process Communication),它是多個進程之間相互溝通的一種方法。在linux下有多種進程間通信的方法:半雙工管道、命名管道、消息隊列、信號、信號量、共享內存、內存映射文件,套接字等等。使用這些機制可以為linux下的網絡服務器開發提供靈活而又堅固的框架。
以上內容引用自CSDN
共享內存
共享內存是在多個進程之間共享內存區域的一種進程間的通信方式,由IPC為進程創建的一個特殊地址范圍,它將出現在該進程的地址空間(這里的地址空間具體是哪個地方?)中。其他進程可以將同一段共享內存連接到自己的地址空間中。所有進程都可以訪問共享內存中的地址,就好像它們是malloc分配的一樣。如果一個進程向共享內存中寫入了數據,所做的改動將立刻被其他進程看到。
共享內存是IPC最快捷的方式,因為共享內存方式的通信沒有中間過程,而管道、消息隊列等方式則是需要將數據通過中間機制進行轉換。共享內存方式直接將某段內存段進行映射,多個進程間的共享內存是同一塊的物理空間,僅僅映射到各進程的地址不同而已,因此不需要進行復制,可以直接使用此段空間。
注意:共享內存本身并沒有同步機制,需要程序員自己控制。
共享內存頭文件:
#include <sys/types.h> #include <sys/stat.h> #include <sys/shm.h>結構shmid_ds結構體(是不是很眼熟,看消息隊列的msgid_ds結構體):
strcut shmid_ds{ struct ipc_perm shm_perm; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; ...... }共享內存函數定義:
int shmget(key_t key,size_t size,int shmflg); //shmget函數用來創建一個新的共享內存段, 或者訪問一個現有的共享內存段(不同進程只要key值相同即可訪問同一共享內存段)。第一個參數key是ftok生成的鍵值,第二個參數size為共享內存的大小,第三個參數sem_flags是打開共享內存的方式。 eg.int shmid = shmget(key, 1024, IPC_CREATE | IPC_EXCL | 0666);//第三個參數參考消息隊列int msgget(key_t key,int msgflag); void *shmat(int shm_id,const void *shm_addr,int shmflg); //shmat函數通過shm_id將共享內存連接到進程的地址空間中。第二個參數可以由用戶指定共享內存映射到進程空間的地址,shm_addr如果為0,則由內核試著查找一個未映射的區域。返回值為共享內存映射的地址。 eg.char *shms = (char *)shmat(shmid, 0, 0);//shmid由shmget獲得 int shmdt(const void *shm_addr); //shmdt函數將共享內存從當前進程中分離。 參數為共享內存映射的地址。 eg.shmdt(shms); int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//shmctl函數是控制函數,使用方法和消息隊列msgctl()函數調用完全類似。參數一shm_id是共享內存的句柄,cmd是向共享內存發送的命令,最后一個參數buf是向共享內存發送命令的參數。管道
管道實際是用于進程間通信的一段共享內存,創建管道的進程稱為管道服務器,連接到一個管道的進程為管道客戶機。一個進程在向管道寫入數據后,另一進程就可以從管道的另一端將其讀取出來。
管道的特點:
1、管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
2、只能用于父子進程或者兄弟進程之間(具有親緣關系的進程)。比如fork或exec創建的新進程,在使用exec創建新進程時,需要將管道的文件描述符作為參數傳遞給exec創建的新進程。當父進程與使用fork創建的子進程直接通信時,發送數據的進程關閉讀端,接受數據的進程關閉寫端。
3、單獨構成一種獨立的文件系統:管道對于管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬于某種文件系統,而是自立門戶,單獨構成一種文件系統,并且只存在與內存中。
4、數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,并且每次都是從緩沖區的頭部讀出數據。
管道的實現機制:
管道是由內核管理的一個緩沖區,相當于我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩沖區不需要很大,它被設計成為環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。
管道只能在本地計算機中使用,而不可用于網絡間的通信。
pipe函數原型:
#include <unistd.h> int pipe(int file_descriptor[2]);//建立管道,該函數在數組上填上兩個新的文件描述符后返回0,失敗返回-1。 eg.int fd[2] 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將不再保證寫入的原子性。
命名管道(FIFO)
命名管道是一種特殊類型的文件,它在系統中以文件形式存在。這樣克服了管道的弊端,他可以允許沒有親緣關系的進程間通信。
創建管道的兩個系統調用原型:
具體操作方法只要創建了一個命名管道然后就可以使用open、read、write等系統調用來操作。創建可以手工創建或者程序中創建。
int mknod(const char *path, mode_t mode, dev_t dev); //第一個參數表示你要創建的文件的名稱,第二個參數表示文件類型,第三個參數表示該文件對應的設備文件的設備號。只有當文件類型為 S_IFCHR 或 S_IFBLK 的時候該文件才有設備號,創建普通文件時傳入0即可。 eg.mknod(FIFO_FILE,S_IFIFO|0666,0);管道和命名管道的區別:
對于命名管道FIFO來說,IO操作和普通管道IO操作基本一樣,但是兩者有一個主要的區別,在命名管道中,管道可以是事先已經創建好的,比如我們在命令行下執行
mkfifo myfifo
就是創建一個命名通道,我們必須用open函數來顯示地建立連接到管道的通道,而在管道中,管道已經在主進程里創建好了,然后在fork時直接復制相關數據或者是用exec創建的新進程時把管道的文件描述符當參數傳遞進去。
一般來說FIFO和PIPE一樣總是處于阻塞狀態。也就是說如果命名管道FIFO打開時設置了讀權限,則讀進程將一直阻塞,一直到其他進程打開該FIFO并向管道寫入數據。這個阻塞動作反過來也是成立的。如果不希望命名管道操作的時候發生阻塞,可以在open的時候使用O_NONBLOCK標志,以關閉默認的阻塞操作。
信號 (signal)
#include <sys/types.h> #include <signal.h> void (*signal(int sig,void (*func)(int)))(int); //用于截取系統信號,第一個參數為信號,第二個參數為對此信號掛接用戶自己的處理函數指針。返回值為以前信號處理程序的指針。 eg.int ret = signal(SIGSTOP, sig_handle);由于signal不夠健壯,推薦使用sigaction函數。
int kill(pid_t pid,int sig); //kill函數向進程號為pid的進程發送信號,信號值為sig。當pid為0時,向當前系統的所有進程發送信號sig。 int raise(int sig);//向當前進程中自舉一個信號sig, 即向當前進程發送信號。 #include <unistd.h> unsigned int alarm(unsigned int seconds); //alarm()用來設置信號SIGALRM在經過參數seconds指定的秒數后傳送給目前的進程。如果參數seconds為0,則之前設置的鬧鐘會被取消,并將剩下的時間返回。使用alarm函數的時候要注意alarm函數的覆蓋性,即在一個進程中采用一次alarm函數則該進程之前的alarm函數將失效。 int pause(void); //使調用進程(或線程)睡眠狀態,直到接收到信號,要么終止,或導致它調用一個信號捕獲函數。消息隊列(Message queues)
消息隊列是內核地址空間中的內部鏈表,通過linux內核在各個進程直接傳遞內容,消息順序地發送到消息隊列中,并以幾種不同的方式從隊列中獲得,每個消息隊列可以用IPC標識符唯一地進行識別。內核中的消息隊列是通過IPC的標識符來區別,不同的消息隊列直接是相互獨立的。每個消息隊列中的消息,又構成一個獨立的鏈表。
消息隊列克服了信號承載信息量少,管道只能承載無格式字符流。
消息隊列頭文件:
消息緩沖區結構:
struct msgbuf{ long mtype; char mtext[1];//柔性數組 };在結構中有兩個成員,mtype為消息類型,用戶可以給某個消息設定一個類型,可以在消息隊列中正確地發送和接受自己的消息。mtext為消息數據,采用柔性數組,用戶可以重新定義msgbuf結構。例如:
struct msgbuf{ long mtype; char mtext[1];//柔性數組 };當然用戶不可隨意定義msgbuf結構,因為在linux中消息的大小是有限制的,在linux/msg.h中定義如下:
define MSGMAX 8192
消息總的大小不能超過8192個字節,包括mtype成員(4個字節)。
2、msqid_ds內核數據結構
Linux內核中,每個消息隊列都維護一個結構體,此結構體保存著消息隊列當前狀態信息,該結構體在頭文件linux/msg.h中定義。
3、ipc_perm內核數據結構
結構體ipc_perm保存著消息隊列的一些重要的信息,比如說消息隊列關聯的鍵值,消息隊列的用戶id組id等。它定義在頭文件linux/ipc.h中。
常用函數:
系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到。
消息隊列的本質
Linux的消息隊列(queue)實質上是一個鏈表,它有消息隊列標識符(queue ID)。 msgget創建一個新隊列或打開一個存在的隊列;msgsnd向隊列末端添加一條新消息;msgrcv從隊列中取消息, 取消息是不一定遵循先進先出的, 也可以按消息的類型字段取消息。
消息隊列與命名管道的比較
消息隊列跟命名管道有不少的相同之處,通過與命名管道一樣,消息隊列進行通信的進程可以是不相關的進程,同時它們都是通過發送和接收的方式來傳遞數據的。在命名管道中,發送數據用write,接收數據用read,則在消息隊列中,發送數據用msgsnd,接收數據用msgrcv。而且它們對每個數據都有一個最大長度的限制。
與命名管道相比,消息隊列的優勢在于,1、消息隊列也可以獨立于發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。2、同時通過發送消息還可以避免命名管道的同步和阻塞問題,不需要由進程自己來提供同步方法。3、接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默認地接收。
轉載于:https://www.cnblogs.com/20155222lzj/p/7901068.html
總結
以上是生活随笔為你收集整理的2017-2018-1 20155222 《信息安全系统设计基础》第10周 Linux下的IPC机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库MYSQL学习系列二
- 下一篇: centos 7 安装docker 并设