欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux之systemV共享內(nèi)存方式

 更新時間:2025年04月29日 11:00:12   作者:s_little_monster_  
這篇文章主要介紹了Linux之systemV共享內(nèi)存方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、工作原理

操作系統(tǒng)在物理內(nèi)存上申請一塊空間,然后將申請到的空間通過頁表映射到進程地址空間mm_struct的共享區(qū)中,然后返回虛擬地址供程序使用,如果多個進程申請的是同一塊物理空間,那么它們就可以進行通信由于同一時間可能有多組進程進行通信,所以系統(tǒng)當(dāng)中可能存在多個共享內(nèi)存塊,所以操作系統(tǒng)要把這些內(nèi)存管理起來,所以內(nèi)核中會有一個結(jié)構(gòu)體來描述共享內(nèi)存

二、系統(tǒng)調(diào)用接口

1、申請共享內(nèi)存

(一)key的獲取

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
  • 返回值:成功ftok 會將proj_idpathname對應(yīng)的文件信息結(jié)合起來生成最終的key值,失敗返回-1
  • pathname:已經(jīng)存在的文件或目錄的路徑名
  • proj_id:由用戶指定的非零整數(shù),通常是一個單字符(因為只有低 8 位會被使用),

只要這兩個才參數(shù)一致,那么生成的key值就一定一致

key值是我們少見的由我們自己傳參生成的固定的值,Linux在涉及內(nèi)存方面的時候,通常是操作系統(tǒng)代為處理,這里其實是因為假設(shè)我們有兩個進程AB,操作系統(tǒng)生成一個key給到A,但是B也需要這個key,但是操作系統(tǒng)是不知道哪兩個進程要建立信道進行通信的,只有程序員知道我們哪兩個進程要建立信道進行通信,因為在建立信道之前進程之間是相互獨立的,所以就不能由操作系統(tǒng)來分配key

(二)共享內(nèi)存的申請

shmget函數(shù)的主要作用是在內(nèi)核中創(chuàng)建或獲取共享內(nèi)存段的標識符

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

返回值:成功返回一個非負整數(shù),即該共享內(nèi)存段的標識碼(shmid),失敗返回-1

  • key:key是一個數(shù)字,它是不同共享內(nèi)存讓自己具備唯一性的標識
  • size:共享內(nèi)存大小,單位為字節(jié),一般最好是4096的整數(shù)倍,它按照頁申請,即需要4097字節(jié)就申請4096*2個字節(jié)

shmflg是一個標識符

shmflg含義作用
IPC_CREAT創(chuàng)建標志如果指定的共享內(nèi)存段不存在,就創(chuàng)建一個新的共享內(nèi)存段;若已存在,則直接返回其標識符,常用于在不確定共享內(nèi)存是否存在時創(chuàng)建或獲取它
IPC_EXCL排他標志需和 IPC_CREAT 一起使用,若共享內(nèi)存已存在,shmget 調(diào)用失敗,errno 設(shè)為 EEXIST,可確保新創(chuàng)建共享內(nèi)存
IPC_NOWAIT非阻塞標志當(dāng)操作不能立即完成時,不阻塞調(diào)用進程,直接返回錯誤,例如在獲取共享內(nèi)存時,若當(dāng)前資源無法立即獲取,不等待而直接返回錯誤
SHM_HUGETLB大頁標志嘗試使用大頁內(nèi)存分配共享內(nèi)存段,大頁內(nèi)存可減少頁表項數(shù)量,降低內(nèi)存管理開銷,提高系統(tǒng)性能,適合處理大量數(shù)據(jù)
SHM_NORESERVE不保留交換空間標志不預(yù)先為共享內(nèi)存段保留交換空間,正常創(chuàng)建共享內(nèi)存時,系統(tǒng)會預(yù)留交換空間以防內(nèi)存不足,使用此標志可節(jié)省交換空間,但可能導(dǎo)致內(nèi)存緊張時出現(xiàn)頁面交換問題
權(quán)限標志(如 0600、0666 等)權(quán)限設(shè)置類似文件權(quán)限設(shè)置,用于規(guī)定共享內(nèi)存段的訪問權(quán)限,0600 表示只有所有者有讀寫權(quán)限,0666 表示所有用戶都有讀寫權(quán)限

2、將共享內(nèi)存段連接到進程地址空間

shmat函數(shù)的核心作用是在調(diào)用進程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,在調(diào)用shmget函數(shù)時,雖然創(chuàng)建或獲取了共享內(nèi)存段的標識符,但進程還不能直接訪問該共享內(nèi)存,只有通過shmat函數(shù)將共享內(nèi)存段附加到進程的地址空間后,進程才能像訪問普通內(nèi)存一樣訪問共享內(nèi)存段中的數(shù)據(jù)

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 返回值:成功返回一個指針,指向共享內(nèi)存的起始地址,失敗返回 -1
  • shmid:共享內(nèi)存段的標識符,用于指定要附加的共享內(nèi)存段,即shmget的返回值
  • shmaddr:指定共享內(nèi)存段要附加到的進程地址空間的地址,如果為 NULL,則由系統(tǒng)自動選擇合適的地址
  • shmflg:標識符
類型含義作用
SHM_RDONLY共享內(nèi)存只讀以只讀模式將共享內(nèi)存段附加到進程的地址空間,進程只能讀取共享內(nèi)存中的數(shù)據(jù),不能進行寫操作,增強數(shù)據(jù)安全性,適用于多進程共享只讀數(shù)據(jù)的場景
SHM_RND地址舍入當(dāng) shmaddr 參數(shù)不為 NULL 時,將 shmaddr 向下舍入到一個合適的內(nèi)存邊界(通常是系統(tǒng)頁面大小的整數(shù)倍),保證共享內(nèi)存的正確附加
SHM_REMAP重新映射如果共享內(nèi)存段已經(jīng)被附加到進程的地址空間,使用此標志可以重新映射該共享內(nèi)存段,常用于更新共享內(nèi)存的映射關(guān)系
SHM_EXEC可執(zhí)行權(quán)限允許在共享內(nèi)存段上執(zhí)行程序指令,不過并非所有系統(tǒng)都支持該標志,適用于需要在共享內(nèi)存中執(zhí)行代碼的特殊場景
SHM_COPY創(chuàng)建私有副本某些系統(tǒng)支持該標志,嘗試創(chuàng)建共享內(nèi)存段的一個私有副本,后續(xù)對該副本的修改不會影響其他進程看到的共享內(nèi)存內(nèi)容,用于實現(xiàn)進程對共享內(nèi)存的獨立修改
SHM_ANON匿名共享內(nèi)存部分系統(tǒng)支持該標志,用于創(chuàng)建匿名共享內(nèi)存段,此時 shmid 參數(shù)會被忽略,可結(jié)合 shmaddr 使用,常用于父子進程間的內(nèi)存共享

3、將內(nèi)存共享段與當(dāng)前進程脫離

當(dāng)進程調(diào)用shmat函數(shù)將共享內(nèi)存段附加到自己的地址空間后,系統(tǒng)會在進程的虛擬地址空間和共享內(nèi)存段的物理內(nèi)存之間建立映射關(guān)系,使得進程可以像訪問普通內(nèi)存一樣訪問共享內(nèi)存,而shmdt函數(shù)的核心作用就是解除這種映射關(guān)系

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
  • 返回值:成功返回0,失敗返回-1
  • shmaddrshmat返回的指針

4、控制共享內(nèi)存

通過cmd控制共享內(nèi)存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 返回值:成功返回0,失敗返回-1
  • shmid:同上
  • cmd:將要采取的動作
類型含義作用
IPC_STAT獲取狀態(tài)信息將與 shmid 關(guān)聯(lián)的共享內(nèi)存段的當(dāng)前狀態(tài)信息復(fù)制到 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中,這個結(jié)構(gòu)體包含了如共享內(nèi)存段的大小、所有者、權(quán)限、創(chuàng)建時間、最后訪問時間等信息
IPC_SET設(shè)置狀態(tài)信息使用 buf 指向的 struct shmid_ds 結(jié)構(gòu)體中的值來更新與 shmid 關(guān)聯(lián)的共享內(nèi)存段的部分狀態(tài)信息,可以更新的信息包括共享內(nèi)存段的所有者、權(quán)限等
IPC_RMID刪除共享內(nèi)存段標記與 shmid 關(guān)聯(lián)的共享內(nèi)存段為刪除狀態(tài),當(dāng)最后一個使用該共享內(nèi)存段的進程分離它之后,系統(tǒng)會真正釋放該共享內(nèi)存段所占用的資源,此時 buf 參數(shù)會被忽略,通常傳遞 NULL
IPC_INFO獲取系統(tǒng)共享內(nèi)存信息獲取系統(tǒng)范圍內(nèi)的共享內(nèi)存資源信息,這些信息會被存儲在一個由系統(tǒng)定義的特定結(jié)構(gòu)體中(通常不是 struct shmid_ds),buf 應(yīng)指向該結(jié)構(gòu)體,用于接收信息
SHM_INFO獲取共享內(nèi)存段信息獲取系統(tǒng)中共享內(nèi)存段的相關(guān)統(tǒng)計信息,返回一個包含這些統(tǒng)計信息的結(jié)構(gòu)體,同樣,buf 要指向合適的結(jié)構(gòu)體來接收數(shù)據(jù)
SHM_STAT通過索引獲取共享內(nèi)存狀態(tài)類似于 IPC_STAT,但不是通過 shmid 來指定共享內(nèi)存段,而是通過一個索引值,可以通過這種方式遍歷系統(tǒng)中的共享內(nèi)存段
  • buf:指向一個保存著共享內(nèi)存的模式狀態(tài)和訪問權(quán)限的結(jié)構(gòu)體struct shmid_ds

三、開始通信

由于共享內(nèi)存通信沒辦法做到同步和互斥,我們通過加入命名管道的方式來形成同步與互斥的效果,普通的共享內(nèi)存通信就調(diào)用完接口直接寫直接讀就行,比較簡單,因為共享內(nèi)存是可以通過指針直接讀的,所以我們升級一下

1、comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_FILE "./myfifo"
#define MODE 0664

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR,
    FIFO_READ_ERR
};

using namespace std;

Log log;

const int size = 4096; 
const string pathname="/home/slm";
const int proj_id = 0x6667;

//封裝一個獲取Key值的函數(shù)
key_t GetKey()
{
	//使用ftok函數(shù)獲取key值
    key_t k = ftok(pathname.c_str(), proj_id);
    if(k < 0)
    {
        exit(1);
    }
    //返回生成的key值
    return k;
}
//封裝一個建立共享內(nèi)存區(qū)的函數(shù)
int GetShareMemHelper(int flag)
{
	//調(diào)用獲取key值,然后在物理內(nèi)存上申請共享內(nèi)存
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if(shmid < 0)
    {
        exit(2);
    }
	//返回shmid
    return shmid;
}
//用于創(chuàng)建一個新的共享內(nèi)存段,如果指定的共享內(nèi)存段已經(jīng)存在,函數(shù)會失敗
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
//用于獲取一個共享內(nèi)存段,如果指定的共享內(nèi)存段不存在,函數(shù)會創(chuàng)建一個新的共享內(nèi)存段;如果已經(jīng)存在,則直接返回該共享內(nèi)存段的標識符
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT); 
}

class Init
{
public:
    Init()
    {
        // 創(chuàng)建管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
		// 銷毀管道
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};
#endif

2、processa.cpp

#include "comm.hpp"

extern Log log;

int main()
{
	//創(chuàng)建管道
    Init init;
    //調(diào)用 CreateShm 函數(shù)創(chuàng)建一個共享內(nèi)存段,并返回該共享內(nèi)存段的標識符shmid
    int shmid = CreateShm();
    //調(diào)用 shmat 函數(shù)將共享內(nèi)存段附加到當(dāng)前進程的地址空間,shmid 是共享內(nèi)存段的標識符
    //nullptr 表示讓系統(tǒng)自動選擇附加地址,0 表示默認附加標志
    //返回值是共享內(nèi)存段在進程地址空間中的起始地址,將其強制轉(zhuǎn)換為char*類型并賦值給shmaddr
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);
    //以只讀模式打開命名管道文件FIFO_FILE,由于是以只讀模式打開,該操作會阻塞
    //直到有其他進程以寫模式打開同一個命名管道
    int fd = open(FIFO_FILE, O_RDONLY); 
    if (fd < 0)
    {
        exit(FIFO_OPEN_ERR);
    }
    //定義shmds用于存儲共享內(nèi)存段的狀態(tài)信息
    struct shmid_ds shmds;
    while(true)
    {
	//定義一個字符變量c,調(diào)用read函數(shù)從命名管道文件描述符fd中讀取1個字節(jié)的數(shù)據(jù)到c中
	//并將實際讀取的字節(jié)數(shù)存儲在s中,如果能讀取到說明通信進程給我們發(fā)信號了,有內(nèi)容
	//那我們就讀取共享內(nèi)存部分
        char c;
        ssize_t s = read(fd, &c, 1);
        if(s == 0) break;
        else if(s < 0) 
        {
            exit(FIFO_READ_ERR);
        }
		//可以直接通過指針訪問共享內(nèi)存
        cout << "client say@ " << shmaddr << endl; 
        sleep(1);
		//調(diào)用shmctl函數(shù),使用IPC_STAT命令獲取共享內(nèi)存段的狀態(tài)信息,并將其存儲在shmds結(jié)構(gòu)體中
		//然后將結(jié)構(gòu)體shmds部分信息打印出來
        shmctl(shmid, IPC_STAT, &shmds);
        cout << "shm size: " << shmds.shm_segsz << endl;
        cout << "shm nattch: " << shmds.shm_nattch << endl;
        printf("shm key: 0x%x\n",  shmds.shm_perm.__key);
        cout << "shm mode: " << shmds.shm_perm.mode << endl;
    }
	//取消鏈接
    shmdt(shmaddr);
    //調(diào)用shmctl函數(shù),使用IPC_RMID命令將共享內(nèi)存段標記為待刪除狀態(tài)
    //當(dāng)所有附加進程都分離后,系統(tǒng)會自動刪除該共享內(nèi)存段
    shmctl(shmid, IPC_RMID, nullptr);
    close(fd);
    return 0;
}

3、processb.cpp

#include "comm.hpp"

int main()
{
	//調(diào)用GetShm函數(shù)獲取一個共享內(nèi)存段的標識符shmid
	//在參數(shù)相同、無特殊情況的情況下,shmid也相同
    int shmid = GetShm();
    char *shmaddr = (char*)shmat(shmid, nullptr, 0);

    int fd = open(FIFO_FILE, O_WRONLY); 
    if (fd < 0)
    {
        exit(FIFO_OPEN_ERR);
    }
    // 一旦有了共享內(nèi)存,掛接到自己的地址空間中,直接把他當(dāng)成你的內(nèi)存空間來用即可
    // 不需要調(diào)用系統(tǒng)調(diào)用接口
    while(true)
    {
        cout << "Please Enter@ ";
        fgets(shmaddr, 4096, stdin);
        //在管道寫入一個字符來通知服務(wù)器共享內(nèi)存有新數(shù)據(jù)
        write(fd, "c", 1); 
    }
	//調(diào)用shmdt函數(shù)將共享內(nèi)存段從當(dāng)前進程的地址空間分離,釋放相關(guān)的資源
    shmdt(shmaddr);
    close(fd);
    return 0;
}

四、其他問題

key是在操作系統(tǒng)內(nèi)標定共享內(nèi)存唯一性的,而shmid是在進程內(nèi)標定資源唯一性的,二者雖然都是標定唯一性,但是使用范圍不同,并且雖然共享內(nèi)存屬于文件系統(tǒng),但是shmid和文件描述符兼容性不好,共享內(nèi)存這方面單獨搞了一套類似于文件描述符表的規(guī)則

共享內(nèi)存的生命周期隨內(nèi)核,除非用戶釋放或者內(nèi)核重啟,共享內(nèi)存才會釋放

ipcs -m命令可以查看操作系統(tǒng)中所有的共享內(nèi)存,其中perms是權(quán)限位,nattch是和當(dāng)前共享內(nèi)存關(guān)聯(lián)的進程個數(shù)

共享內(nèi)存是沒有存在同步互斥這樣的保護機制的,它是所有進程通信方式中最快的,因為相較其他方式,它的拷貝更少

共享內(nèi)存內(nèi)部的數(shù)據(jù)由用戶自己維護

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論