System V IPC(進程間通信)機制詳解

文章目錄

  • 一、引言
  • 二、System V IPC的基本概念
    • 1、IPC結構的引入
    • 2、IPC標識符(IPC ID)
    • 3、S ystem V的優缺點
  • 三、共享內存(Shared Memory)
    • 1、共享內存的基本概念
    • 2、共享內存的創建(shmget)
    • 3、共享內存的附加(shmat)和分離(shmdt)
    • 4、共享內存的控制(shmctl)
    • 5、使用案例
  • 四、消息隊列(Message Queues)
    • 1、消息隊列的基本概念
    • 2、消息隊列的創建(msgget)
    • 3、消息的發送(msgsnd)和接收(msgrcv)
    • 4、消息隊列的控制(msgctl)
  • 五、信號量(Semaphores)
    • 1、信號量的基本概念
    • 2、信號量的創建(semget)
    • 3、信號量的初始化(semctl)
    • 4、信號量的P操作(semop)和V操作

一、引言

System V IPC是Linux中的一種進程間通信機制。它主要包括消息隊列、信號量和共享內存三種形式。這些機制都通過內核中的IPC設施來實現,允許進程之間進行高效的數據交換和同步。

  • 消息隊列:消息隊列允許一個進程向另一個進程發送一個具有特定類型(或稱為“消息類型”)的消息。發送者將消息放入隊列的尾部,而接收者則從隊列的頭部取出消息。消息隊列對于需要異步通信的場景特別有用。
  • 信號量:信號量是一個整數變量,主要用于控制對共享資源的訪問。通過操作信號量(如P操作和V操作),進程可以實現對共享資源的互斥訪問或同步操作。信號量在防止死鎖和保證系統穩定性方面起著重要作用。
  • 共享內存:共享內存允許兩個或多個進程共享一塊內存區域。通過映射同一塊物理內存到不同進程的地址空間,這些進程可以直接訪問該內存區域中的數據,從而實現高速的數據交換。共享內存是IPC機制中效率最高的一種。

二、System V IPC的基本概念

1、IPC結構的引入

System V IPC的基本概念中,IPC結構的引入是為了解決本地間不同進程之間的通信問題。在Linux系統中,多個進程可能需要相互協作、共享數據或資源,以及協調各自的工作。為了實現這些目標,System V IPC引入了幾種類型的IPC結構,包括消息隊列、共享內存和信號量。

這些IPC結構存在于內核中,而不是文件系統中,這意味著它們由內核直接管理,并且可以通過特定的系統調用來訪問和操作。與管道等其他通信機制不同,IPC結構的釋放不是由內核自動控制的,而是由用戶顯式控制。

System V IPC中的IPC結構并不為按名字為文件系統所知。因此我們不能使用ls命令看到他們,不能使用rm命令刪掉它們。因此我們在查看時使用ipcs命令,刪除時使用ipcrm命令。

在這里插入圖片描述

例如,我們可以使用while :; do ipcs -m ; sleep 1 ; done循環查看當前進程中的共享內存。

ipcrm命令用于刪除IPC設施。它可以根據設施的標識符或鍵來刪除消息隊列、信號量集或共享內存段。命令的基本語法是:

ipcrm [-M key | -m id | -Q key | -q id | -S key | -s id]

每個IPC結構都有一個唯一的標識符(ID),這是由系統在創建對象時分配的。此外,每個IPC結構還有一個關聯的key值,用于在不同進程之間唯一標識和訪問同一個IPC結構。通過KEY值,不同的進程可以打開或找到同一個IPC結構,從而實現進程間的通信。

總的來說,System V IPC結構的引入使得不同進程之間可以更加高效、靈活地進行通信和協作。這些IPC結構的存在使得進程間的數據交換、資源共享和同步變得更加簡單和可靠。

2、IPC標識符(IPC ID)

每個內核中的 IPC 結構(消息隊列、信號量或共享存儲段)都用一個非負整數的標識符加以引用。

在System V IPC中,IPC標識符(IPC ID)是一個非負整數,用于唯一地標識一個IPC結構。每個IPC結構(如消息隊列、信號量或共享內存段)在創建時都會被分配一個唯一的IPC ID。

當創建一個消息隊列、信號量或共享內存段時,系統調用(如 msgget(), semget(), shmget())會返回一個 IPC ID,如果成功的話。這個 IPC ID 是用來在后續的 IPC 操作中引用該 IPC 對象的。

這個IPC ID是操作系統范圍內的全局變量,只要具有相應的權限,任何進程都可以通過這個ID來訪問和操作相應的IPC結構。這種機制使得進程間的通信更加靈活和高效,因為進程可以直接通過IPC ID來引用和操作IPC結構,而無需通過文件路徑或其他標識符。

無論何時創建IPC結構,都應指定一個關鍵字,關鍵字的數據類型由系統規定為key_t,通常在頭文件<sys/types.h>中被規定為長整型。關鍵字由內核變換成標識符。

在System V IPC中,獲取IPC ID的通常方式是調用相應的get函數(如msggetsemgetshmget,分別是創-建消息隊列,信號量和共享內存的系統調用函數),并傳遞一個key值作為參數。這個key值是通過ftok函數從文件路徑和ID生成的唯一鍵。系統會根據這個key值來查找或創建對應的IPC結構,并返回其IPC ID。man 3 ftok

在這里插入圖片描述

ftok函數的意義在于為System V IPC 提供唯一的鍵值(key)。在創建共享內存、消息隊列等進程間通信的標識符時,通常需要指定一個ID值。這個ID值通常是通過ftok函數得到的。

ftok函數是Linux系統中提供的一種比較重要的進程間通信機制,它可以將一個已經存在的文件的路徑名和一個子序號(通常為非負整數)作為輸入,然后返回一個唯一的key_t類型的鍵值。這個鍵值在系統中是全局唯一的,可以用于標識和訪問特定的IPC結構(如共享內存、消息隊列)。

使用ftok函數時,需要確保指定的文件路徑下存在一個有效的文件,并且該文件在程序運行期間不會被刪除或移動。否則,ftok函數可能會返回錯誤或生成不同的鍵值,導致進程間通信失敗。

在System V IPC機制中,兩個或多個進程可以通過約定形成同樣的key,然后使用這個key來找到和訪問同一個共享內存或消息隊列。

需要注意的是,在System V IPC機制中,IPC ID是系統分配的,并且在系統重啟之前都是有效的。即使創建IPC結構的進程已經退出,只要沒有執行刪除操作或系統重啟,其他進程仍然可以通過IPC ID來訪問和操作該IPC結構。

當系統重啟時,由于內核會重新加載,所有的IPC對象都會被銷毀,因為它們的生命周期并不是永久性的,而是依賴于內核的運行狀態。

IPC標識符(IPC ID)是System V IPC中用于唯一標識IPC結構的非負整數。通過IPC ID,進程可以高效地訪問和操作IPC結構,實現進程間的通信和協作。

key與IPC ID的關系?

key是在內核角度用于區分共享內存的唯一性。我們以shmid為例,shmid是共享內存的ID。此處不明白,可先到后文共享內存處。

首先,key是長整型的,用于在進程間共享內存、信號量和消息隊列等系統資源之間進行標識和訪問。這個key值通常是通過ftok()函數根據給定的路徑名和標識符生成的。在shmget函數的調用中,key被用于指定要創建或訪問的共享內存段,也就是將keyshmid關聯起來。

shmid是共享內存段的用戶級標識符,它是一個非負整數,用于唯一地標識一個共享內存段。當通過shmget函數成功創建或打開一個共享內存段時,系統會返回一個shmid,進程可以使用這個shmid來進行后續的共享內存操作,如shmat(將共享內存附加到進程的地址空間)和shmdt(將共享內存從進程的地址空間中分離)。

因此,可以說key是創建或訪問共享內存段的“鑰匙”,而shmid則是成功創建或打開共享內存段后獲得的“通行證”。在共享內存的管理中,keyshmid共同確保了進程能夠正確地訪問和操作共享內存段。

類似文件inode和文件fd的關系

在文件系統中,inode(索引節點)和文件描述符(fd)各自扮演了不同的角色,而在共享內存管理中,keyshmid也有類似的關系。

  • 文件inode:在Linux系統中,inode是文件系統用于存儲文件元數據(如權限、所有者、大小、創建時間等)的數據結構。每個文件(或目錄)在文件系統中都有一個唯一的inode與之關聯。這個inode是從內核角度區分文件的唯一性標識。
  • 文件描述符(fd:文件描述符是一個非負整數,用于在用戶空間程序中引用一個打開的文件。當進程打開一個文件時,內核會分配一個文件描述符給該進程,進程通過這個文件描述符來進行文件的讀寫等操作。

類似地,在共享內存管理中:

  • keykey是用于在內核角度區分共享內存的唯一性標識。它通常通過ftok函數生成,或者由程序員直接指定。在創建共享內存段時,key被用來確定要創建或訪問的是哪個共享內存段。
  • shmidshmid(共享內存標識符)是一個非負整數,用于在用戶空間程序中引用一個已創建的共享內存段。當成功創建一個共享內存段后,系統會返回一個shmid給調用進程。進程通過這個shmid來進行后續的共享內存操作,如附加(shmat)、分離(shmdt)和刪除(shmctl)。

因此,keyinode都是從內核角度區分資源(文件或共享內存)的唯一性標識,而shmid和文件描述符(fd)則是從用戶空間角度引用這些資源的標識符。這樣的設計使得內核可以高效地管理資源,同時允許用戶空間程序以更加靈活和直觀的方式使用這些資源。

系統為每一個IPC結構設置一個了ipc_perm結構。

struct ipc_perm
{__key_t __key;				/* Key.  */__uid_t uid;					/* Owner's user ID.  */__gid_t gid;					/* Owner's group ID.  */__uid_t cuid;					/* Creator's user ID.  */__gid_t cgid;					/* Creator's group ID.  */__mode_t mode;				/* Read/write permission.  */unsigned short int __seq;			/* Sequence number.  */
};

在創建IPC對象(如通過msgget(), semget(), shmget()等系統調用)時,內核會為該對象分配一個ipc_perm結構,并初始化其中的字段。除了__seq字段(它通常由內核管理以跟蹤對象的創建和刪除),其他字段都由創建者或具有適當權限的進程來設置。

為什么要設計該結構體呢?

系統為每一個IPC(進程間通信)結構設置一個ipc_perm結構,是因為這個結構用于描述IPC對象的權限和所有權信息:

  1. 權限管理ipc_perm結構中的uid(用戶ID)、gid(組ID)和mode(訪問模式)字段用于定義哪些用戶可以訪問、修改或刪除IPC對象。這種權限管理機制確保了系統的安全性和穩定性,防止未授權的進程訪問或篡改IPC對象。
  2. 所有權跟蹤ipc_perm結構中的uidgid字段還用于跟蹤IPC對象的所有者。這有助于系統管理員識別和管理IPC對象,例如查找和刪除不再需要的IPC對象。
  3. 統一接口:在Linux系統中,多種IPC機制(如消息隊列、信號量和共享內存)都使用類似的接口和數據結構。為每種IPC結構都設置一個ipc_perm結構,可以確保這些IPC機制在權限管理和所有權跟蹤方面具有一致的接口和行為。
  4. 內核管理:IPC對象是在內核中創建的,因此內核需要一種方式來跟蹤和管理這些對象的權限和所有權。ipc_perm結構為內核提供了一種方便的方式來存儲和檢索這些信息。當系統創建一個新的IPC對象(如一個消息隊列或信號量集)時,它會在內核中為該對象分配內存,并初始化一個ipc_perm結構來保存該對象的權限和所有權信息。這個ipc_perm結構通常作為IPC對象特定數據結構(如msgid_dssemid_dsshmid_ds)的一部分。內核中,所有IPC對象的ipc_perm結構被組織成一個數組,以便內核能夠快速地根據IPC對象的標識符(如消息隊列的ID)找到對應的ipc_perm結構。

3、S ystem V的優缺點

  1. 訪問計數和垃圾回收:System V IPC結構(如消息隊列、信號量和共享內存)在系統范圍內起作用,但它們沒有訪問計數機制。這意味著,即使不再有任何進程引用這些IPC結構,它們也不會被自動刪除。這可能導致系統資源的浪費,因此需要我們顯式地刪除不再需要的IPC結構。
  2. 文件系統不可見:System V IPC結構并不通過文件系統來管理,因此它們對于傳統的文件操作命令(如ls、rmchmod)是不可見的。這增加了管理和調試的復雜性,因為需要使用專門的命令(如ipcsipcrm)來列出、刪除和修改IPC結構的屬性。
  3. 編程接口復雜性:System V IPC提供了豐富的功能,但也引入了復雜的編程接口。與基于文件的IPC機制(如管道和FIFO)相比,使用System V IPC需要更多的系統調用和更復雜的編程技術。
  4. 不支持文件描述符:由于System V IPC結構不是通過文件系統來管理的,因此它們沒有文件描述符。這限制了使用基于文件描述符的I/O函數(如selectpoll)來監控多個IPC結構的能力。
  5. 標識符的動態分配:System V IPC結構的標識符是在系統啟動時動態分配的,并且與創建時的系統狀態有關。這增加了在多個進程之間共享IPC結構標識符的難度,因為需要某種形式的通信或配置文件來傳遞這些標識符。

此外,隨著Unix和類Unix系統的發展,出現了其他IPC機制,如POSIX IPC(包括消息隊列、信號量和共享內存),它們在某些方面提供了更好的抽象和更簡單的編程接口。這些新機制可能更適合某些應用場景,并減少了System V IPC的一些限制。


三、共享內存(Shared Memory)

1、共享內存的基本概念

共享存儲允許兩個或多個進程共享一給定的內存區。由于數據不需要在客戶機和服務器之 間復制,所以這是最快的一種通信方式。

**共享內存無進程間協調機制。**這意味著,當多個進程或線程訪問和修改同一塊共享內存區域時,它們必須自己管理對這塊內存的訪問,以防止數據沖突和不一致。

具體來說,當一個進程(我們稱之為寫入方)正在向共享內存寫入數據時,另一個進程(我們稱之為讀取方)可能同時嘗試讀取這塊內存。由于共享內存沒有內置的同步機制,讀取方可能會讀取到寫入方還未完全寫入的數據,或者讀取到寫入方寫入過程中的中間狀態,從而導致數據的不一致性和錯誤。

內核為每個共享存儲段設置了一個shmid_ds結構:

在這里插入圖片描述

shmid_ds結構體用于描述一個共享內存段的屬性。當系統創建一個共享內存段時,內核會為該段分配一個shmid_ds結構體來保存其相關信息。這個結構體包含了共享內存段的權限、大小、時間戳、附加到該段的進程數等信息。

2、共享內存的創建(shmget)

我們通常使用此函數來創建或者獲取當前key對應的共享內存,當新建一個共享內存時,我們會初始化shmid_ds結構體的部分成員:

在這里插入圖片描述

參數

  • key: 這是一個鍵值,用于唯一地標識一個共享內存段。多個進程可以通過這個鍵值來訪問同一個共享內存段。
  • size: 這是要分配的共享內存段的大小(以字節為單位)。即,我們可以通過該參數來設置共享內存的大小。Linux中,共享內存的大小以4KB為基本單位。若size的值為4097,共享內存實際大小為8KB。我們只能用4097B。如果正在存訪一個現存的共享內存,則將size指定為0。
  • shmflg: 這個標志位用于控制shmget的行為。它可以是以下值的組合:
    • IPC_CREAT: 如果指定的共享內存段不存在,則創建它。
    • IPC_EXCL: 與 IPC_CREAT 一起使用時,如果指定的共享內存段已經存在,則調用失敗。
    • 權限位(如 0666):這些位指定了新創建的共享內存段的權限。這些權限位與文件系統的權限類似,但它們的解釋略有不同(特別是對于組和其他用戶)。

返回值

  • 如果成功,shmget 返回一個非負整數,這個整數是共享內存段的標識符(也稱為“鍵”或“句柄”)。
  • 如果失敗,shmget 返回 -1,并設置全局變量 errno 以指示錯誤原因。

使用案例:shmid = shmget(key, size, IPC_EXCL | IPC_CREAT | 0666 );

3、共享內存的附加(shmat)和分離(shmdt)

在System V IPC機制中,shmatshmdt是用于附加和分離共享內存段的函數。一旦創建了一個共享存儲段,進程就可調用shmat將其連接到它的地址空間中。

shmat(附加共享內存)

shmat是 “shared memory attach” 的縮寫,它的功能是將共享內存區域附加到指定的進程地址空間中。一旦附加成功,進程就可以像訪問自己的內存一樣訪問共享內存。

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

參數說明:

  • shmid:由shmget返回的共享內存標識符。
  • shmaddr:這是一個可選參數,指定了共享內存附加到進程地址空間的地址。如果設置為NULL,則由系統選擇地址。
  • shmflg:一組標志,用于控制附加操作的行為。通常設置為0。

如果成功,shmat返回一個指向共享內存段的指針。如果失敗,返回-1并設置errno

shmdt(分離共享內存)

shmdt函數的功能是將之前附加到進程的共享內存段從進程地址空間中分離。一旦分離,進程就不能再訪問這塊共享內存了。

當對共享存儲段的操作已經結束時,則調用shmdt脫接該段。注意,這并不從系統中刪除 其標識符以及其數據結構。該標識符仍然存在,直至某個進程(一般是服務器)調用 shmctl(帶命令IPC_RMID)特地刪除它。

函數的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

參數shmaddr是由shmat返回的指向共享內存段的指針。如果成功,shmdt返回0。如果失敗,返回-1并設置errno

4、共享內存的控制(shmctl)

共享內存的控制通常使用shmctl函數來實現。這個函數的全稱是"shared memory control",用于控制共享內存段的屬性、狀態以及執行一些管理操作。

在這里插入圖片描述

參數說明:

  • shmid:共享內存標識符,即要控制的共享內存段的標識符。
  • cmd:控制命令,指定了要執行的操作。常見的命令包括:
    • IPC_STAT:獲取共享內存段的狀態信息,并將其保存在buf中。
    • IPC_SET:設置共享內存段的狀態信息為buf中的值。
    • IPC_RMID:刪除共享內存段。因為每個共享存儲段有一個連接計數 (shm_nattchshmid_ds結構中),所以除非使用該段的最后一個進程終止或與該段脫接,否則 不會實際上刪除該存儲段。不管此段是否仍在使用,該段標識符立即被刪除 ,所以不能再用 shmat與該段連接。此命令只能由下列兩種進程執行 :一種是其有效用戶I D等于shm_perm.cuidshm_perm.uid的進程;另一種是具有超級用戶特權的進程。
  • buf:一個指向shmid_ds結構體的指針,用于傳遞或接收共享內存段的狀態信息。這個結構體包含了共享內存的大小、擁有者ID和組ID、權限設置、最后訪問和修改的時間等信息。

調用shmctl函數并不會直接清除共享內存中的數據,它只是控制共享內存的屬性和狀態。例如,你可以使用IPC_SET命令來修改共享內存的權限,或者使用IPC_RMID命令來刪除不再需要的共享內存段。

進程使用共享內存時是如何知道共享內存大小呢

System V共享內存中,當進程想要使用共享內存時,它們會先通過shmget函數來獲取共享內存的標識符(shmid),然后再通過該標識符以及其他相關信息來操作共享內存。

具體來說,shmget函數在創建或獲取共享內存時,會返回一個共享內存的標識符(shmid)。這個標識符是唯一的,并且可以用來引用特定的共享內存段。同時,shmget函數還接受一個size參數,用于指定共享內存的大小(以字節為單位)。當創建新的共享內存段時,這個size參數就是新段的大小;而當獲取已經存在的共享內存段時,這個參數實際上是被忽略的,因為段的大小已經由之前創建它的進程確定了。

一旦進程獲取了共享內存的標識符(shmid),它就可以使用其他相關的函數來操作這個共享內存段了。但是,如何知道這個共享內存段的大小呢?

使用shmctl函數和IPC_STAT命令來獲取共享內存的狀態信息shmctl函數是一個通用的控制函數,可以用于執行各種與共享內存相關的操作。當使用IPC_STAT命令調用shmctl時,它會返回一個shmid_ds結構體,其中包含了共享內存的各種狀態信息,包括大小、權限、連接數等。進程可以通過這種方式來獲取共享內存的大小。

5、使用案例

假設我們有一個客戶端一個服務端,我們需要客戶端向服務端輸入內容。

下面代碼,我們使用FIFO做輔助,來進行讀取控制。

Comm.hpp

#pragma once
#include "Fifo.hpp"
#include <unistd.h>
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <cstring>
#include <string>using namespace std;const char *pathname = "/home/zyb/study_code";
const int proj_id = 0x66;
const int defaultsize = 4097; // 單位是字節
// 我們可以通過size大小,來設置共享內存的值。
// OS中,共享內存的大小以4KB為基本單位。若size為4097,共享內存實際大小為8KB。std::string ToHex(key_t k)
{char buffer[1024];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}
key_t GetShmKeyOrDie()
{key_t k = ftok(pathname, proj_id);if (k < 0){std::cerr << "ftok error, errno : " << errno << " , error string : " << strerror(errno) << std::endl;exit(1);}return k;
}
int CreateShmOrDie(key_t key, int size, int flag)
{int shmid = shmget(key, size, flag);if (shmid < 0){std::cerr << "shmget error, errno : " << errno << " , error string : " << strerror(errno) << std::endl;exit(2);}return shmid;
}// IPC_CREAT :   不存在就創建,存在就獲取
// IPC_EXCL:     存在就出錯返回
//  IPC_EXCL|IPC_CREAT:  不存在就創建,存在就出錯返回
int CreateShm(key_t key, int size)
{return CreateShmOrDie(key, size, IPC_EXCL | IPC_CREAT | 0666 /*指定共享內存的默認權限*/);
}
int GetShm(key_t key, int size)
{return CreateShmOrDie(key, size, IPC_EXCL);
}void DeleteShm(int shmid)
{int n = shmctl(shmid, IPC_RMID, nullptr);if (n < 0){std::cerr << "shmctl error, errno : " << errno << " , error string : " << strerror(errno) << std::endl;}else{std::cout << "shmctl delete shm success, shmid: " << shmid << std::endl;}
}void ShmDebug(int shmid)
{struct shmid_ds shmds;int n = shmctl(shmid, IPC_STAT, &shmds);if (n < 0){std::cerr << "shmctl error, errno : " << errno << " , error string : " << strerror(errno) << std::endl;return;}// 共享內存大小std::cout << "shmds.shm_segsz: " << shmds.shm_segsz << std::endl;// 當前附加到該共享內存段的進程數std::cout << "shmds.shm_nattch:" << shmds.shm_nattch << std::endl;// 表示共享內存段的“更改時間” 通常以時間戳(從1970年1月1日開始的秒數)的形式存儲std::cout << "shmds.shm_ctime:" << shmds.shm_ctime << std::endl;// 共享內存的key值std::cout << "shmds.shm_perm.__key:" << ToHex(shmds.shm_perm.__key) << std::endl;
}void *ShmAttach(int shmid)
{void *addr = shmat(shmid, nullptr, 0);if ((long long int)addr == -1){std::cerr << "shmat error" << std::endl;return nullptr;}return addr;
}void ShmDetach(void *addr)
{int n = shmdt(addr);if (n < 0){std::cerr << "shmdt error" << std::endl;}
}

Fifo.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>using namespace std;#define Mode 0666
#define Path "./fifo"class Fifo
{
public:Fifo(const string &path = Path) : _path(path){umask(0);int n = mkfifo(_path.c_str(), Mode);if (n == 0){cout << "mkfifo success" << endl;}else{cerr << "mkfifo failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}~Fifo(){int n = unlink(_path.c_str());if (n == 0){cout << "remove fifo file " << _path << " success" << endl;}else{cerr << "remove failed, errno: " << errno << ", errstring: " << strerror(errno) << endl;}}private:string _path; // 文件路徑+文件名
};class Sync
{
public:Sync() : rfd(-1), wfd(-1){}void OpenReadOrDie(){rfd = open(Path, O_RDONLY);if (rfd < 0)exit(1);}void OpenWriteOrDie(){wfd = open(Path, O_WRONLY);if (wfd < 0)exit(1);}bool Wait(){bool ret = true;uint32_t c = 0;ssize_t n = read(rfd, &c, sizeof(uint32_t));if (n == sizeof(uint32_t)){std::cout << "server wakeup, begin read shm..." << std::endl;}else if (n == 0){ret = false;}else{return false;}return ret;}void Wakeup(){uint32_t c = 0;ssize_t n = write(wfd, &c, sizeof(c));assert(n == sizeof(uint32_t));std::cout << "wakeup server..." << std::endl;}~Sync() {}private:int rfd;int wfd;
};#endif

ShmServer.cc

#include "Comm.hpp"int main()
{// 1. 獲取keykey_t key = GetShmKeyOrDie();std::cout << "key: " << ToHex(key) << std::endl;// sleep(2);// 2. 創建共享內存int shmid = CreateShm(key, defaultsize);// ShmDebug(shmid);// 3. 將共享內存和進程進行掛接(關聯)char *addr = (char *)ShmAttach(shmid);std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;// sleep(2);// 4. 引入管道Fifo fifo;Sync syn;syn.OpenReadOrDie();// 通信for (;;){if (!syn.Wait())break;cout << " shm content : " << addr << endl;}ShmDetach(addr);std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;sleep(2);// 3. 刪除共享內存DeleteShm(shmid);return 0;
}

ShmClient.cc

#include "Comm.hpp"int main()
{key_t key = GetShmKeyOrDie();std::cout << "key: " << ToHex(key) << std::endl;// sleep(2);int shmid = GetShm(key, defaultsize);std::cout << "shmid: " << shmid << std::endl;// sleep(2);char *addr = (char *)ShmAttach(shmid);std::cout << "Attach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;// sleep(5);memset(addr, 0, defaultsize);Sync syn;syn.OpenWriteOrDie();// 通信for (char c = 'A'; c <= 'Z'; c++){addr[c - 'A'] = c;sleep(1);syn.Wakeup();}ShmDetach(addr);std::cout << "Detach shm success, addr: " << ToHex((uint64_t)addr) << std::endl;sleep(2);return 0;
}

最后我們思考:共享內存是所有進程間通信中速度最快的。為什么呢

在這里插入圖片描述

在共享內存模型中,進程A和進程B都可以直接訪問同一塊物理內存區域(即“共享區”)。這意味著數據不需要通過系統調用或其他中間層進行復制或傳輸,從而減少了數據傳輸的開銷。

共享內存通過允許進程直接訪問同一塊物理內存區域,減少了數據傳輸和I/O操作的開銷,降低了延遲,從而提高了進程間通信的效率。

當A進程需要與B進程通信時,只需要把共享區的虛擬地址與物理地址的映射寫入兩進程的頁表中。因此,進程A可以對該物理地址直接進行寫入;而B進程則是通過頁表的映射關系,從該物理地址直接進行讀取。
在這里插入圖片描述

傳統的進程間通信(IPC)機制,如管道、消息隊列等,通常涉及到內核空間和用戶空間之間的數據拷貝,這會產生大量的I/O操作。而共享內存允許進程直接訪問內存中的數據,從而避免了這種I/O開銷。

上圖中的管道,我們在使用時,雖然是使用內核緩沖區來進行操作,并沒有將數據寫入到磁盤中,但進行讀寫的兩個進程,當發送方將數據寫入管道時,數據會被拷貝到內核緩沖區中。然后,當接收方從管道中讀取數據時,數據會從內核緩沖區被拷貝到接收方的用戶空間。這個過程中,數據只被拷貝了兩次:一次是從發送方的用戶空間到內核緩沖區,另一次是從內核緩沖區到接收方的用戶空間。


四、消息隊列(Message Queues)

1、消息隊列的基本概念

消息隊列本質上是一個隊列,隊列中存放的是一個個消息。而隊列是一個數據結構,具有先進先出的特點,它存放在內核中并由消息隊列標識符標識。我們稱消息隊列標識符為“”隊列ID”。msgget用于創建一個新隊列或打開一個現存的隊列。 msgsnd用于將新消息添加到隊列尾端。每個消息包含一個正長整型類型字段,一個非負長度以及實際 數據字節(對應于長度),所有這些都在將消息添加到隊列時,傳送給msgsndmsgrcv用于從隊列中取消息。我們并不一定要以先進先出次序取消息,也可以按消息的類型字段取消息。

每個隊列中都有一個msqid_ds結構與其相關:

在這里插入圖片描述

上述結構規定了消息隊列的當前狀態。

2、消息隊列的創建(msgget)

我們使用消息隊列首先就需要mssget函數,用來打開一個消息隊列或創建一個新的隊列。

在這里插入圖片描述

該函數的參數與共享內存相似,我們不再贅述。

若執行成功,則返回非負隊列ID。此后,此值就可被用于消息隊列的其他函數。

3、消息的發送(msgsnd)和接收(msgrcv)

這兩個函數都需要消息隊列ID(msqid)以及特定的結構體struct msgbuf作為參數。

在這里插入圖片描述

msgsnd函數用于將一個新的消息寫入隊列。為了發送消息,調用進程對消息隊列進行寫入時必須有寫權能。

  • msgp:指向要發送消息的指針,該消息應該是msgbuf結構體的實例。
  • msgsz:消息的大小(不包括mtype字段)。

msgrcv函數用于從消息隊列中讀取消息。接收消息時必須有讀權能。

  • msgp:指向用于存儲接收到的消息的緩沖區的指針,該緩沖區應該是msgbuf結構體的實例。

  • msgsz:緩沖區中mtext字段的最大大小。

  • msgtyp:要接收的消息的類型。如果msgtyp為0,則接收隊列中的第一個消息。如果msgtyp大于0,則接收具有相同類型的第一個消息。如果msgtyp小于0,則接收類型小于或等于msgtyp絕對值的最低類型消息。

其中msgp結構體定義如下:

struct msgbuf {long mtype;       /* 消息類型,必須大于0 */char mtext[1];    /* 消息數據,實際大小由msgsz指定 */
};

注意,該結構當中的第二個成員mtext即為待發送的信息,當我們定義該結構時,mtext的大小可以自己指定。

4、消息隊列的控制(msgctl)

msgctl函數類似共享內存的shmctl,也可以取出消息隊列的結構,設置消息隊列結構,刪除消息隊列。參數同shmctl,第一個參數表示對哪個消息隊列進行操作:

在這里插入圖片描述


五、信號量(Semaphores)

1、信號量的基本概念

信號量是一個計數器,用于多進程對共享數據對象的存取,其值表示某個共享資源的可用數量。當一個進程或線程需要訪問這個共享資源時,它必須先請求信號量,并等待信號量變為可用。

為了獲得共享資源,進程需要執行下列操作:

  1. 測試控制該資源的信號量。
  2. 若此信號量的值為正,則進程可以使用該資源。進程將信號量值減 1,表示它使用了一 個資源單位。
  3. 若此信號量的值為 0,則進程進入睡眠狀態,直至信號量值大于 0。若進程被喚醒后, 它返回至(第( 1 )步)。

當進程不再使用由一個信息量控制的共享資源時,該信號量值增 1。如果有進程正在睡眠等待此信號量,則喚醒它們。 為了正確地實現信息量,信號量值的測試及減 1操作應當是原子操作。為此,信號量通常是在內核中實現的。

內核為每個信號量設置了一個semid_ds結構體:

在這里插入圖片描述

2、信號量的創建(semget)

返回值是信號量集ID:

在這里插入圖片描述

3、信號量的初始化(semctl)

該函數包含了多種信號量操作:

在這里插入圖片描述

semctl函數常用于對信號量集進行各種操作,包括設置信號量的初始值(即初始化)。

4、信號量的P操作(semop)和V操作

  • P操作:也稱為“等待”操作。它用于請求訪問共享資源。如果信號量的值大于0,則將其減1并允許進程繼續執行;如果信號量的值為0,則進程將被阻塞,直到信號量的值變為大于0。這通常通過semop函數實現,并設置信號量的值semval為0(如果semval不為0,則阻塞或報錯),然后將其值加1(即semval為0時可以立即通過,否則等待)。
  • V操作:也稱為“信號”或“發布”操作。它用于釋放共享資源。當進程完成共享資源的使用后,它會將信號量的值加1,以表示該資源現在可用。這同樣通過semop函數實現,并設置信號量的值減1(但只有在信號量值大于或等于要減去的值時才能立即返回,否則進程需要等待)。

在Linux系統中,使用信號量通常涉及以上提到的四個步驟:創建信號量、初始化信號量、進行P/V操作以及(在不再需要時)刪除信號量。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/12302.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/12302.shtml
英文地址,請注明出處:http://en.pswp.cn/web/12302.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++:并發保護

一、前言 本文將會通過保護一個數據討論&#xff1a;互斥鎖、雙重檢查鎖、 std::once_flag 類、 std::call_once() 函數、單例模式、使用局部靜態變量實現單例模式等。 二、保護共享數據 假設我們需要某個共享數據&#xff0c;而它創建起來開銷不菲。因為創建它可能需要建立…

vim中的替換

:[range]s/pattern/replacement/flags 這里各部分的含義是&#xff1a; :[range]&#xff1a;可選的行范圍&#xff0c;用于指定在哪些行之間進行替換。如果省略&#xff0c;則默認為當前行。例如&#xff0c;1,10 表示在第1行到第10行之間替換&#xff0c;% 表示在整個文件中…

python的文件操作及函數式編程介紹

五、文件操作 1、讀取鍵盤輸入 input 獲取標準輸入&#xff0c;數據類型統一為字符串 #!/usr/bin/python # -*- coding: UTF-8 -*- str input("請輸入&#xff1a;") print&#xff08;"你輸入的內容是: ", str&#xff09; 這會產生如下的對應著輸入的…

KeyShot 2023.3 Pro for mac/win:完美融合3D渲染與動畫制作

在當今數字化時代&#xff0c;視覺內容的創作和表現越來越受到重視。無論是產品設計、建筑規劃&#xff0c;還是影視特效&#xff0c;都需要具備出色的3D渲染和動畫制作工具來展現創意和想法。而作為業內領先的3D渲染和動畫制作軟件之一&#xff0c;KeyShot 2023.3 Pro在這個領…

電腦剛開機的時候不卡,用一會就變卡頓了…怎么回事?

前言 昨天咱們聊到舊電腦更換了固態硬盤之后&#xff0c;開機就會變快的事情&#xff0c;這個確實是可行且有效的升級電腦辦法之一。 看完這篇之后&#xff0c;切莫著急升級電腦硬件配置&#xff0c;因為這里的坑比你想象的還要多。 從機械硬盤測試的數據和固態硬盤的測試數…

類與面向對象編程(Object-Oriented Programming, OOP)

類與面向對象編程&#xff08;Object-Oriented Programming, OOP&#xff09;&#xff08;一&#xff09; 對象比較&#xff1a;‘is’ 對比 ‘’ 當我還是個孩子的時候&#xff0c;我們的鄰居擁有一對雙胞胎貓咪。它們看起來幾乎一模一樣—同樣的炭黑色毛發和同樣銳利的綠色…

2024年港口危貨安全管理證報名條件

危化品安全員證報考條件 1、職業道德良好&#xff0c;身體健康&#xff0c;年齡不超過60周歲(法定代表人除外). 2、建筑施工企業的在職人員。 3、學歷及要求: (1)建筑施工企業主要負責人應為大專以上學歷&#xff0c;需大專以上學歷&#xff0c;除企業法人外&#xff0c;其他…

linux進階高級配置,你需要知道的有哪些(11)-YUM倉庫服務與PXE網絡裝機

1、基于RPM包構建的軟件更新機制 可以自動解決依賴關系 所有軟件包由集中的YUM軟件倉庫提供 2、軟件倉庫的提供方式 FTP服務&#xff1a;ftp://...... HTTP服務&#xff1a;http://...... 本地目錄&#xff1a;file:///...... 3、客戶端YUM命令 &#xff08;1&#xff0…

從CSDN搬家到微信公眾號

博主將會在微信公眾號里不斷輸出精品內容&#xff0c;陪伴大家共同成長。 如果你對博主的經歷感興趣&#xff0c;或者對博主的IT技術感興趣&#xff0c;歡迎關注我的微信公眾號&#xff0c;閱讀我的技術文章&#xff0c;免費獲取各種IT資源。也可以加我的微信成為我的好友&…

檔案數字化加工是如何利用檔案的

檔案數字化加工是將紙質檔案轉化為數字形式&#xff0c;并進行后續的加工和利用。通過檔案數字化加工&#xff0c;可以實現以下幾個方面的利用&#xff1a; 1. 存儲和保護&#xff1a;數字化檔案可以將大量的紙質檔案存儲在數字存儲介質中&#xff0c;從而節省空間和維護成本。…

TypeScript學習日志-第二十五天(編寫發布訂閱模式)

編寫發布訂閱模式 這是更具訂閱模式寫的代碼 可以理解訂閱模式的思想 interface I {events:Map<string,Function[]>once:(event:string,callback:Function)>void // 觸發一次on:(event:string,callback:Function)>void // 訂閱emit:(event:string,...args:any[])…

FileUpload控件

FileUpload控件是一個在Web應用程序中常用的界面元素&#xff0c;它允許用戶從客戶端選擇文件&#xff0c;并將該文件上傳到Web服務器。以下是關于FileUpload控件的一些詳細信息&#xff1a; 基本功能&#xff1a;FileUpload控件通常顯示為一個文本框和一個“瀏覽”按鈕。用戶…

面試算法之哈希專題

贖金信 class Solution { public:bool canConstruct(string ransomNote, string magazine) {// 小寫字母int r_cnt[26];int m_cnt[26];for(int i 0; i< magazine.size(); i) {m_cnt[magazine[i]-a]; // 統計}// 對比for(int i 0; i< ransomNote.size(); i) {if(m_cnt[r…

使用vant-ui+vue3實現一個可復用的評星組件

如圖所示 有兩種情況 一種是5顆星 一種是3顆星 官網上只提供了圖標類型的 并沒有加文字 https://femessage-vant.netlify.app/#/zh-CN/ 自己結合兩種情況 在全局注冊了此組件(后續還會持續更新代碼~) <template><div class"vant_rate_wrapper"><van…

【Javaer學習Python】 1、Django安裝

安裝 Python 和 PyCharm 的方法就略過了&#xff0c;附一個有效激活PyCharm的鏈接&#xff1a;https://www.quanxiaoha.com/pycharm-pojie/pycharm-pojie-20241.html 1、安裝Django # 安裝Django pip install Django# 查看當前版本 python -m django --version 5.0.62、創建項…

HTML常用標簽-表格標簽

表格標簽 1 常規表格2 單元格跨行3 單元格跨行 1 常規表格 table標簽 代表表格 thead標簽 代表表頭 可以省略不寫 tbody標簽 代表表體 可以省略不寫 tfoot標簽 代表表尾 可以省略不寫 tr標簽 代表一行 td標簽 代表行內的一格 th標簽 自帶加粗和居中效果的td 代碼 <h…

探索數據結構:堆的具體實現與應用

?? 歡迎大家來到貝蒂大講堂?? &#x1f388;&#x1f388;養成好習慣&#xff0c;先贊后看哦~&#x1f388;&#x1f388; 所屬專欄&#xff1a;數據結構與算法 貝蒂的主頁&#xff1a;Betty’s blog 1. 堆的概念 堆(Heap)是計算機科學中一類特殊的數據結構。堆通常是一個…

C++ QT設計模式 (第二版)

第3章 Qt簡介 3.2 Qt核心模塊 Qt是一個大庫&#xff0c;由數個較小的庫或者模塊組成&#xff0c;最為常見的如下&#xff1a;core、gui、xml、sql、phonon、webkit&#xff0c;除了core和gui&#xff0c;這些模塊都需要在qmake的工程文件中啟用 QTextStream 流&#xff0c;Qdat…

在buildroot中自動給kernel打補丁

我的這個buildroot是管理在git上面的&#xff0c;所以這里我直接使用git format-patch 生成patch。 下面我詳細列舉一下步驟 1&#xff0c;將沒有修改的kernel復制出來一份&#xff0c;進入kernel目錄&#xff0c;執行git init&#xff0c;add所有文件并commit 2&#xff0c…

2024年高考倒計時精品網頁

2024年高考倒計時精品網頁 前言效果圖部分代碼領取源碼下期更新預報 前言 隨著季風輕輕掠過&#xff0c;歲月如梭&#xff0c;再次迎來了這個屬于青春與夢想交匯的時刻——高考。這是一場知識的較量&#xff0c;更是一次意志的考驗。在這最后的沖刺階段&#xff0c;每一刻都顯…