1. 程式人生 > >2017-2018-1 學號20155209 《信息安全系統設計基礎》第十周學習總結

2017-2018-1 學號20155209 《信息安全系統設計基礎》第十周學習總結

參數表 發送信號 分離 類型 生成 重新 [1] 接受 進程間通信

linux進程間的IPC機制

共享內存

  • 共享內存是在多個進程之間共享內存區域的一種進程間的通信方式,由IPC為進程創建的一個特殊地址範圍,它將出現在該進程的地址空間(這裏的地址空間具體是哪個地方?)中。其他進程可以將同一段共享內存連接到自己的地址空間中。所有進程都可以訪問共享內存中的地址,就好像它們是malloc分配的一樣。如果一個進程向共享內存中寫入了數據,所做的改動將立刻被其他進程看到。
  • 共享內存是IPC最快捷的方式,因為共享內存方式的通信沒有中間過程,而管道、消息隊列等方式則是需要將數據通過中間機制進行轉換。共享內存方式直接將某段內存段進行映射,多個進程間的共享內存是同一塊的物理空間,僅僅映射到各進程的地址不同而已,因此不需要進行復制,可以直接使用此段空間。
    註意:共享內存本身並沒有同步機制,需要程序員自己控制。
  • 共享內存頭文件:
#include <sys/types.h> 
#include <sys/stat.h>
#include <sys/shm.h> 
  • 結構shmid_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是向共享內存發送命令的參數。

管道

  • 管道實際是用於進程間通信的一段共享內存,創建管道的進程稱為管道服務器,連接到一個管道的進程為管道客戶機。一個進程在向管道寫入數據後,另一進程就可以從管道的另一端將其讀取出來。
    管道的特點:
  • 管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;
  • 只能用於父子進程或者兄弟進程之間(具有親緣關系的進程)。比如fork或exec創建的新進程,在使用exec創建新進程時,需要將管道的文件描述符作為參數傳遞給exec創建的新進程。當父進程與使用fork創建的子進程直接通信時,發送數據的進程關閉讀端,接受數據的進程關閉寫端。
  • 單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。
  • 數據的讀出和寫入:一個進程向管道中寫的內容被管道另一端的進程讀出。寫入的內容每次都添加在管道緩沖區的末尾,並且每次都是從緩沖區的頭部讀出數據。
  • 管道的實現機制:
    管道是由內核管理的一個緩沖區,相當於我們放入內存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩沖區不需要很大,它被設計成為環形的數據結構,以便管道可以被循環利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會等待,直到另一端的進程取出信息。當兩個進程都終結的時候,管道也自動消失。
  • 管道只能在本地計算機中使用,而不可用於網絡間的通信。
  • 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]中讀數據。寫入與讀取的順序原則是先進先出。

    FIFO

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

#include <sys/types.h> 
#include <sys/stat.h> 
int mkfifo(const char *filename,mode_t mode); //建立一個名字為filename的命名管道,參數mode為該文件的權限(mode%~umask),若成功則返回0,否則返回-1,錯誤原因存於errno中。
eg.mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
  • 創建了一個命名管道然後就可以使用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);  

信號

  • 信號機制是unix系統中最為古老的進程之間的通信機制,用於一個或幾個進程之間傳遞異步信號。信號可以有各種異步事件產生,比如鍵盤中斷等。shell也可以使用信號將作業控制命令傳遞給它的子進程。
  • 簡單使用方法定義:
#include <sys/types.h> 
#include <signal.h> 
void (*signal(int sig,void (*func)(int)))(int); //用於截取系統信號,第一個參數為信號,第二個參數為對此信號掛接用戶自己的處理函數指針。返回值為以前信號處理程序的指針。
eg.int ret = signal(SIGSTOP, sig_handle);
  • 推薦使用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); //使調用進程(或線程)睡眠狀態,直到接收到信號,要麽終止,或導致它調用一個信號捕獲函數。 

消息隊列

  • 消息隊列是內核地址空間中的內部鏈表,通過linux內核在各個進程直接傳遞內容,消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲得,每個消息隊列可以用IPC標識符唯一地進行識別。內核中的消息隊列是通過IPC的標識符來區別,不同的消息隊列直接是相互獨立的。每個消息隊列中的消息,又構成一個獨立的鏈表。
  • 消息隊列克服了信號承載信息量少,管道只能承載無格式字符流。
  • 消息隊列頭文件:
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/msg.h> 
  • 消息緩沖區結構:
struct msgbuf{
    long mtype;
    char mtext[1];//柔性數組
}
  • 在結構中有兩個成員,mtype為消息類型,用戶可以給某個消息設定一個類型,可以在消息隊列中正確地發送和接受自己的消息。mtext為消息數據,采用柔性數組,用戶可以重新定義msgbuf結構。
struct msgbuf{
    long mtype;
    char mtext[1];//柔性數組
}
  • 消息隊列的本質
    Linux的消息隊列(queue)實質上是一個鏈表,它有消息隊列標識符(queue ID)。 msgget創建一個新隊列或打開一個存在的隊列;msgsnd向隊列末端添加一條新消息;msgrcv從隊列中取消息, 取消息是不一定遵循先進先出的, 也可以按消息的類型字段取消息。

2017-2018-1 學號20155209 《信息安全系統設計基礎》第十周學習總結