進程間通信:共享內存概念及代碼

前言

接下討論的IPC機制,它們最初由System V版本的Unix引入。由於這些機制都出現在同一個版本中並且有着相似的編程接口,所以它們被稱爲System V IPC機制。接下來的內容包括:

信號量:用於管理對資源的訪問。

共享內存:用於在程序之間高效地共享數據。

消息隊列:在程序之間傳遞數據。

操作系統中的同步和異步:https://blog.csdn.net/qq_38289815/article/details/81012826

進程間通信:管道和命名管道(FIFO)  https://blog.csdn.net/qq_38289815/article/details/104742682

進程間通信:信號量  https://blog.csdn.net/qq_38289815/article/details/104762940

進程間通信:消息隊列  https://blog.csdn.net/qq_38289815/article/details/104786412

 

共享內存

共享內存允許兩個不相關的進程訪問同一個邏輯內存。共享內存是在兩個正在運行的進程之間傳遞數據的一種非常有效的方式。不同進程之間共享的內存安排爲同一段物理內存。

共享內存是由IPC爲進程創建的一個特殊的地址範圍,它將出現在該進程的地址空間中。其他進程可以將同一段共享內存連接到它們自己的地址空間中,所有進程都可以訪問共享內存中的地址,就好像它們是由用C語言函數malloc()分配的內存一樣。如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。

特別提醒:共享內存並未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取。所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。

 

Linux數字權限含義解釋

在學習Linux編程時,常會用到755、777這些數字設置權限下面我來詳細的介紹644、755、777這些數字所代表的含義Linux系統使用9個比特的權限標誌來表示文件的權限。在終端中鍵入ll或ls -l查看目錄下的文件:

圖片中所展示的數字,從左至右,1-3位數字代表文件所有者的權限,4-6位數字代表同組用戶的權限,7-9數字代表其他用戶的權限。具體的權限是由數字來表示的,讀取(r)的權限等於4;寫入(w)的權限等於2;執行(x)的權限等於1

通過4、2、1的組合,得到以下幾種權限:0 (沒有權限);4 (讀取權限);5 (4+1 | 讀取+執行);6 (4+2 | 讀取+寫入);7 (4+2+1 | 讀取+寫入+執行)。以755爲例:
1-3位7等於4+2+1,rwx,所有者具有讀取、寫入、執行權限;
4-6位5等於4+1+0,r-x,同組用戶具有讀取、執行權限但沒有寫入權限;
7-9位5,同上,也是r-x,其他用戶具有讀取、執行權限但沒有寫入權限。

 

共享內存的使

與信號量一樣,在Linux中也提供了一組函數接口用於使用共享內存,而且使用共享共存的接口還與信號量的非常相似,而且比使用信號量的接口來得簡單。定義如下:

#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);

 

shmget()

shmget函數用來創建共享內存,它的原型爲:

int shmget(key_t key, size_t size, int shmflg);
創建成功返回一個非負整數,即共享內存標識符;失敗返回-1。

第一個參數,與信號量的semget函數一樣,程序需要提供一個參數key(非0整數),它有效地爲共享內存段命名,shmget函數成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1。

第二個參數,size以字節爲單位指定需要共享的內存容量。

第三個參數,shmflg是權限標誌,如果要想在key標識的共享內存不存在時創建它的話,可以與IPC_CREAT做按位或操作。共享內存的權限標誌與文件的讀寫權限一樣

不相關的進程可以通過該函數的返回值訪問同一共享內存,它代表程序可能要使用的某個資源,程序對所有共享內存的訪問都是間接的,程序先通過調用shmget函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget函數的返回值),只有shmget函數才直接使用信號量鍵,所有其他的信號量函數使用由semget函數返回的信號量標識符。

權限標誌對共享內存非常有用,因爲它們允許一個進程創建的共享內存可以被共享內存的創建者所擁有的進程寫入,同時其他用戶創建的進程只能讀取該共享內存。我們可以利用這個功能來提供一種有效的對數據進行只讀訪問的方法,通過將數據放入共享內存並設置它的權限,就可以避免其他用戶修改它。

 

shmat()

第一次創建完共享內存時,它還不能被任何進程訪問,shmat()函數的作用就是啓動對該共享內存的訪問,並把共享內存連接到進程的地址空間。它的原型爲:

void *shmat(int shm_id, const void *shm_addr, int shmflg);
shmat調用成功時返回一個指向共享內存第一個字節的指針;調用失敗時返回-1。

第一個參數,shm_id是由shmget()函數返回的共享內存標識。

第二個參數,shm_addr指定共享內存連接到當前進程中的地址位置,通常是一個空指針,表示讓系統來選擇共享內存出現的地址。

第三個參數,shm_flg是一組標誌位,通常爲0。一般很少需要控制共享內存連接的地址,通常都是讓系統來選擇一個地址,否則就會使應用程序對硬件的依賴性過高。

共享內存的讀寫權限由它的屬主(共享內存的創建者)、它的訪問權限和當前進程的屬主決定。共享內存的訪問權限類似於文件的訪問權限。這個規則有個例外,當shmflg & SHM_RDONLY(它使得連接的內存只讀)爲true時,此時即使該共享內存的訪問權限允許寫操作,它也不能被寫入。

 

shmdt()

shmdt函數用於將共享內存從當前進程中分離。注意,將共享內存分離並不是刪除它,只是使該共享內存對當前進程不再可用。它的原型爲:

int shmdt(const void *shmaddr);
參數shmaddr是shmat()函數返回的地址指針,調用成功時返回0,失敗時返回-1.

 

shmctl()

shmctl用來控制共享內存,它的原型爲:

int shmctl(int shm_id, int command, struct shmid_ds *buf);
成功時返回0,失敗時返回-1。

第一個參數,shm_id是shmget()函數返回的共享內存標識符。

第二個參數,command是要採取的操作,它可以取下面的三個值 :

        IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。

        IPC_SET:如果進程有足夠的權限,就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值

        IPC_RMID:刪除共享內存段

第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。shmid_ds結構 至少包括以下成員:

struct shmid_ds
{
    uid_t shm_perm.uid;
    uid_t shm_perm.gid;
    mode_t shm_perm.mode;
};

 

使用共享內存完成進程間通信

下面就以兩個不相關的進程來說明進程間如何利用共享內存來進行通信。其中一個文件shm1.c(消費者)創建共享內存,並讀取其中的信息,另一個文件shm2.c(生產者)將連接一個已有的共享內存段,向共享內存中寫入數據。

#ifndef _SHM_COM_H_HEADER   //shm_com.h
#define _SHM_COM_H_HEADER

#define TEXT_SZ 2048
 
struct shared_use_st
{
    int written_by_you;       // 作爲一個標誌,非0:表示可讀,0:表示可寫
    char some_text[TEXT_SZ];  // 記錄寫入 和 讀取 的文本
};
 
#endif
#include <stddef.h>  //shm1.c
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "shm_com.h"
 
int main(int argc, char **argv)
{
	int running = 1;
    void *shared_memory = NULL;
    struct shared_use_st *shared_stuff; // 指向shm
    int shmid; // 共享內存標識符
	
	srand((unsigned int)getpid());
	
    // 創建共享內存
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 將共享內存連接到當前進程的地址空間
    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
 
    printf("\nMemory attached at %p\n", shared_memory);
    printf("Memory attched at %d\n", *(int*)shared_memory);
 
    // 設置共享內存
    shared_stuff = (struct shared_use_st*)shared_memory; // 注意:shm有點類似通過 malloc() 獲取到的內存,所以這裏需要做個 類型強制轉換
    shared_stuff->written_by_you = 0;
    while (running) // 讀取共享內存中的數據
    {
        // 沒有進程向內存寫數據,有數據可讀取
        if (shared_stuff->written_by_you == 1)
        {
            printf("You wrote: %s", shared_stuff->some_text);
            sleep(1);
 
            // 讀取完數據,設置written使共享內存段可寫
            shared_stuff->written_by_you = 0;
 
            // 輸入了 end,退出循環(程序)
            if (strncmp(shared_stuff->some_text, "end", 3) == 0)
            {
                running = 0;
            }
        }
    }
 
    // 把共享內存從當前進程中分離
    if (shmdt(shared_memory) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 刪除共享內存
    if (shmctl(shmid, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
 
    exit(EXIT_SUCCESS);
    reutrn 0;
}
#include <unistd.h>  //shm2.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
 
int main(int argc, char **argv)
{
    int running = 1;
    void *shared_memory = NULL;
    struct shared_use_st *shared_stuff; // 指向shm
    char buffer[BUFSIZ];
    int shmid; // 共享內存標識符
 
    // 創建共享內存
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }
 
    // 將共享內存連接到當前的進程地址空間
    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1)
    {
        fprintf(stderr, "shmat failed\n");
        exit(EXIT_FAILURE);
    }
 
    printf("Memory attched at %p\n", shared_memory);
    printf("Memory attched at %d\n", *(int*)shared_memory);
 
    // 設置共享內存
    shared_stuff = (struct shared_use_st *)shared_memory;
    while (running) // 向共享內存中寫數據
    {
        // 數據還沒有被讀取,則等待數據被讀取,不能向共享內存中寫入文本
        while (shared_stuff->written_by_you == 1)
        {
            sleep(1);
            printf("Waiting...\n");
        }
 
        // 向共享內存中寫入數據
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
 
        // 寫完數據,設置written使共享內存段可讀
        shared_stuff->written_by_you = 1;
 
        // 輸入了end,退出循環(程序)
        if (strncmp(buffer, "end", 3) == 0)
        {
            running = 0;
        }
    }
 
    // 把共享內存從當前進程中分離
    if (shmdt(shared_memory) == -1)
    {
        fprintf(stderr, "shmdt failed\n");
        exit(EXIT_FAILURE);
    }
 
    exit(EXIT_SUCCESS);
    return 0;
}

實驗解析

第一個程序shm1創建共享內存段,然後它連接到自己的地址空間中。我們在共享內存的開始處使用一個結構體shared_use_st。該結構體中有個標誌written_by_you,當共享內存中有數據寫入時,就設置這個標誌。這個標誌被設置時,程序就從共享內存中讀取文本,將它打印出來,然後清除這個標誌表示已經讀完數據。我們用一個特殊字符串end來退出循環。接下來,程序分離共享內存段並刪除它。

第二個程序shm2使用相同的鍵1234來取得並連接同一個共享內存段。然後它提示用戶輸入一些文本。如果標誌written_by_you被設置,shm2就知道客戶進程還未讀完上一次的數據,因此就繼續等待。當其他進程清除了這個標誌後,shm2寫入數據並設置該標誌。它還使用字符串end來終止並分離共享內存段。

注意,這裏提供了非常簡陋的同步標誌written_by_you,它包括一個非常缺乏效率的忙等待(不停地循環)。這可以使得我們的示例比較簡單,但在實際編程中,應該使用信號量或通過傳遞消息、生成信號的方式來提供應用程序讀、寫部分之間的一種更有效的同步機制。

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