1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文搞定 | Linux 共享內(nèi)存原理

        共 5568字,需瀏覽 12分鐘

         ·

        2021-10-29 07:38

        在Linux系統(tǒng)中,每個進程都有獨立的虛擬內(nèi)存空間,也就是說不同的進程訪問同一段虛擬內(nèi)存地址所得到的數(shù)據(jù)是不一樣的,這是因為不同進程相同的虛擬內(nèi)存地址會映射到不同的物理內(nèi)存地址上。

        但有時候為了讓不同進程之間進行通信,需要讓不同進程共享相同的物理內(nèi)存,Linux通過 共享內(nèi)存 來實現(xiàn)這個功能。下面先來介紹一下Linux系統(tǒng)的共享內(nèi)存的使用。

        共享內(nèi)存使用

        1. 獲取共享內(nèi)存

        要使用共享內(nèi)存,首先需要使用 shmget() 函數(shù)獲取共享內(nèi)存,shmget() 函數(shù)的原型如下:

        int?shmget(key_t?key,?size_t?size,?int?shmflg);


        • 參數(shù) key 一般由 ftok() 函數(shù)生成,用于標識系統(tǒng)的唯一IPC資源。

        • 參數(shù) size 指定創(chuàng)建的共享內(nèi)存大小。

        • 參數(shù) shmflg 指定 shmget() 函數(shù)的動作,比如傳入 IPC_CREAT 表示要創(chuàng)建新的共享內(nèi)存。

        函數(shù)調(diào)用成功時返回一個新建或已經(jīng)存在的的共享內(nèi)存標識符,取決于shmflg的參數(shù)。失敗返回-1,并設置錯誤碼。

        2. 關聯(lián)共享內(nèi)存

        shmget() 函數(shù)返回的是一個標識符,而不是可用的內(nèi)存地址,所以還需要調(diào)用 shmat() 函數(shù)把共享內(nèi)存關聯(lián)到某個虛擬內(nèi)存地址上。shmat() 函數(shù)的原型如下:

        void?*shmat(int?shmid,?const?void?*shmaddr,?int?shmflg);


        • 參數(shù) shmid 是 shmget() 函數(shù)返回的標識符。

        • 參數(shù) shmaddr 是要關聯(lián)的虛擬內(nèi)存地址,如果傳入0,表示由系統(tǒng)自動選擇合適的虛擬內(nèi)存地址。

        • 參數(shù) shmflg 若指定了 SHM_RDONLY 位,則以只讀方式連接此段,否則以讀寫方式連接此段。

        函數(shù)調(diào)用成功返回一個可用的指針(虛擬內(nèi)存地址),出錯返回-1。

        3. 取消關聯(lián)共享內(nèi)存

        當一個進程不需要共享內(nèi)存的時候,就需要取消共享內(nèi)存與虛擬內(nèi)存地址的關聯(lián)。取消關聯(lián)共享內(nèi)存通過 shmdt() 函數(shù)實現(xiàn),原型如下:

        int?shmdt(const?void?*shmaddr);


        • 參數(shù) shmaddr 是要取消關聯(lián)的虛擬內(nèi)存地址,也就是 shmat() 函數(shù)返回的值。

        函數(shù)調(diào)用成功返回0,出錯返回-1。

        共享內(nèi)存使用例子

        下面通過一個例子來介紹一下共享內(nèi)存的使用方法。在這個例子中,有兩個進程,分別為 進程A 和 進程B,進程A 創(chuàng)建一塊共享內(nèi)存,然后寫入數(shù)據(jù),進程B 獲取這塊共享內(nèi)存并且讀取其內(nèi)容。

        進程A

        #include?
        #include?
        #include?
        #include?
        #include?

        #define?SHM_PATH?"/tmp/shm"
        #define?SHM_SIZE?128

        int?main(int?argc,?char?*argv[])
        {
        ????int?shmid;
        ????char?*addr;
        ????key_t?key?=?ftok(SHM_PATH,?0x6666);

        ????shmid?=?shmget(key,?SHM_SIZE,?IPC_CREAT|IPC_EXCL|0666);
        ????if?(shmid?0)?{
        ????????printf("failed?to?create?share?memory\n");
        ????????return?-1;
        ????}

        ????addr?=?shmat(shmid,?NULL,?0);
        ????if?(addr?<=?0)?{
        ????????printf("failed?to?map?share?memory\n");
        ????????return?-1;
        ????}

        ????sprintf(addr,?"%s",?"Hello?World\n");

        ????return?0;
        }

        進程B

        #include?
        #include?
        #include?
        #include?
        #include?
        #include?

        #define?SHM_PATH?"/tmp/shm"
        #define?SHM_SIZE?128

        int?main(int?argc,?char?*argv[])
        {
        ????int?shmid;
        ????char?*addr;
        ????key_t?key?=?ftok(SHM_PATH,?0x6666);

        ????char?buf[128];

        ????shmid?=?shmget(key,?SHM_SIZE,?IPC_CREAT);
        ????if?(shmid?0)?{
        ????????printf("failed?to?get?share?memory\n");
        ????????return?-1;
        ????}

        ????addr?=?shmat(shmid,?NULL,?0);
        ????if?(addr?<=?0)?{
        ????????printf("failed?to?map?share?memory\n");
        ????????return?-1;
        ????}

        ????strcpy(buf,?addr,?128);
        ????printf("%s",?buf);

        ????return?0;
        }

        測試時先運行進程A,然后再運行進程B,可以看到進程B會打印出 “Hello World”,說明共享內(nèi)存已經(jīng)創(chuàng)建成功并且讀取。

        共享內(nèi)存實現(xiàn)原理

        我們先通過一幅圖來了解一下共享內(nèi)存的大概原理,如下圖:

        通過上圖可知,共享內(nèi)存是通過將不同進程的虛擬內(nèi)存地址映射到相同的物理內(nèi)存地址來實現(xiàn)的,下面將會介紹Linux的實現(xiàn)方式。

        在Linux內(nèi)核中,每個共享內(nèi)存都由一個名為 struct shmid_kernel 的結構體來管理,而且Linux限制了系統(tǒng)最大能創(chuàng)建的共享內(nèi)存為128個。通過類型為 struct shmid_kernel 結構的數(shù)組來管理,如下:

        struct?shmid_ds?{
        ?struct?ipc_perm??shm_perm;?/*?operation?perms?*/
        ?int???shm_segsz;?/*?size?of?segment?(bytes)?*/
        ?__kernel_time_t??shm_atime;?/*?last?attach?time?*/
        ?__kernel_time_t??shm_dtime;?/*?last?detach?time?*/
        ?__kernel_time_t??shm_ctime;?/*?last?change?time?*/
        ?__kernel_ipc_pid_t?shm_cpid;?/*?pid?of?creator?*/
        ?__kernel_ipc_pid_t?shm_lpid;?/*?pid?of?last?operator?*/
        ?unsigned?short??shm_nattch;?/*?no.?of?current?attaches?*/
        ?unsigned?short???shm_unused;?/*?compatibility?*/
        ?void????*shm_unused2;?/*?ditto?-?used?by?DIPC?*/
        ?void???*shm_unused3;?/*?unused?*/
        };

        struct?shmid_kernel
        {
        ?
        ?struct?shmid_ds??u;
        ?/*?the?following?are?private?*/
        ?unsigned?long??shm_npages;?/*?size?of?segment?(pages)?*/
        ?pte_t???*shm_pages;?/*?array?of?ptrs?to?frames?->?SHMMAX?*/?
        ?struct?vm_area_struct?*attaches;?/*?descriptors?for?attaches?*/
        };

        static?struct?shmid_kernel?*shm_segs[SHMMNI];?//?SHMMNI等于128

        從注釋可以知道 struct shmid_kernel 結構體各個字段的作用,比如 shm_npages 字段表示共享內(nèi)存使用了多少個內(nèi)存頁。而 shm_pages 字段指向了共享內(nèi)存映射的虛擬內(nèi)存頁表項數(shù)組等。

        另外 struct shmid_ds 結構體用于管理共享內(nèi)存的信息,而 shm_segs數(shù)組 用于管理系統(tǒng)中所有的共享內(nèi)存。

        shmget() 函數(shù)實現(xiàn)

        通過前面的例子可知,要使用共享內(nèi)存,首先需要調(diào)用 shmget() 函數(shù)來創(chuàng)建或者獲取一塊共享內(nèi)存。shmget() 函數(shù)的實現(xiàn)如下:

        asmlinkage?long?sys_shmget?(key_t?key,?int?size,?int?shmflg)
        {
        ?struct?shmid_kernel?*shp;
        ?int?err,?id?=?0;

        ?down(¤t->mm->mmap_sem);
        ?spin_lock(&shm_lock);
        ?if?(size?0?||?size?>?shmmax)?{
        ??err?=?-EINVAL;
        ?}?else?if?(key?==?IPC_PRIVATE)?{
        ??err?=?newseg(key,?shmflg,?size);
        ?}?else?if?((id?=?findkey?(key))?==?-1)?{
        ??if?(!(shmflg?&?IPC_CREAT))
        ???err?=?-ENOENT;
        ??else
        ???err?=?newseg(key,?shmflg,?size);
        ?}?else?if?((shmflg?&?IPC_CREAT)?&&?(shmflg?&?IPC_EXCL))?{
        ??err?=?-EEXIST;
        ?}?else?{
        ??shp?=?shm_segs[id];
        ??if?(shp->u.shm_perm.mode?&?SHM_DEST)
        ???err?=?-EIDRM;
        ??else?if?(size?>?shp->u.shm_segsz)
        ???err?=?-EINVAL;
        ??else?if?(ipcperms?(&shp->u.shm_perm,?shmflg))
        ???err?=?-EACCES;
        ??else
        ???err?=?(int)?shp->u.shm_perm.seq?*?SHMMNI?+?id;
        ?}
        ?spin_unlock(&shm_lock);
        ?up(¤t->mm->mmap_sem);
        ?return?err;
        }

        shmget() 函數(shù)的實現(xiàn)比較簡單,首先調(diào)用 findkey() 函數(shù)查找值為key的共享內(nèi)存是否已經(jīng)被創(chuàng)建,findkey() 函數(shù)返回共享內(nèi)存在 shm_segs數(shù)組 的索引。如果找到,那么直接返回共享內(nèi)存的標識符即可。否則就調(diào)用 newseg() 函數(shù)創(chuàng)建新的共享內(nèi)存。newseg() 函數(shù)的實現(xiàn)也比較簡單,就是創(chuàng)建一個新的 struct shmid_kernel 結構體,然后設置其各個字段的值,并且保存到 shm_segs數(shù)組 中。

        shmat() 函數(shù)實現(xiàn)

        shmat() 函數(shù)用于將共享內(nèi)存映射到本地虛擬內(nèi)存地址,由于 shmat() 函數(shù)的實現(xiàn)比較復雜,所以我們分段來分析這個函數(shù):

        asmlinkage?long?sys_shmat?(int?shmid,?char?*shmaddr,?int?shmflg,?ulong?*raddr)
        {
        ?struct?shmid_kernel?*shp;
        ?struct?vm_area_struct?*shmd;
        ?int?err?=?-EINVAL;
        ?unsigned?int?id;
        ?unsigned?long?addr;
        ?unsigned?long?len;

        ?down(¤t->mm->mmap_sem);
        ?spin_lock(&shm_lock);
        ?if?(shmid?0)
        ??goto?out;

        ?shp?=?shm_segs[id?=?(unsigned?int)?shmid?%?SHMMNI];
        ?if?(shp?==?IPC_UNUSED?||?shp?==?IPC_NOID)
        ??goto?out;

        上面這段代碼主要通過 shmid 標識符來找到共享內(nèi)存描述符,上面說過系統(tǒng)中所有的共享內(nèi)存到保存在 shm_segs 數(shù)組中。

        ?if?(!(addr?=?(ulong)?shmaddr))?{
        ??if?(shmflg?&?SHM_REMAP)
        ???goto?out;
        ??err?=?-ENOMEM;
        ??addr?=?0;
        ?again:
        ??if?(!(addr?=?get_unmapped_area(addr,?shp->u.shm_segsz)))?//?獲取一個空閑的虛擬內(nèi)存空間
        ???goto?out;
        ??if(addr?&?(SHMLBA?-?1))?{
        ???addr?=?(addr?+?(SHMLBA?-?1))?&?~(SHMLBA?-?1);
        ???goto?again;
        ??}
        ?}?else?if?(addr?&?(SHMLBA-1))?{
        ??if?(shmflg?&?SHM_RND)
        ???addr?&=?~(SHMLBA-1);???????/*?round?down?*/
        ??else
        ???goto?out;
        ?}

        上面的代碼主要找到一個可用的虛擬內(nèi)存地址,如果在調(diào)用 shmat() 函數(shù)時沒有指定了虛擬內(nèi)存地址,那么就通過 get_unmapped_area() 函數(shù)來獲取一個可用的虛擬內(nèi)存地址。

        ?spin_unlock(&shm_lock);
        ?err?=?-ENOMEM;
        ?shmd?=?kmem_cache_alloc(vm_area_cachep,?SLAB_KERNEL);
        ?spin_lock(&shm_lock);
        ?if?(!shmd)
        ??goto?out;
        ?if?((shp?!=?shm_segs[id])?||?(shp->u.shm_perm.seq?!=?(unsigned?int)?shmid?/?SHMMNI))?{
        ??kmem_cache_free(vm_area_cachep,?shmd);
        ??err?=?-EIDRM;
        ??goto?out;
        ?}

        上面的代碼主要通過調(diào)用 kmem_cache_alloc() 函數(shù)創(chuàng)建一個 vm_area_struct 結構,在內(nèi)存管理一章知道,vm_area_struct 結構用于管理進程的虛擬內(nèi)存空間。

        ?shmd->vm_private_data?=?shm_segs?+?id;
        ?shmd->vm_start?=?addr;
        ?shmd->vm_end?=?addr?+?shp->shm_npages?*?PAGE_SIZE;
        ?shmd->vm_mm?=?current->mm;
        ?shmd->vm_page_prot?=?(shmflg?&?SHM_RDONLY)???PAGE_READONLY?:?PAGE_SHARED;
        ?shmd->vm_flags?=?VM_SHM?|?VM_MAYSHARE?|?VM_SHARED
        ????|?VM_MAYREAD?|?VM_MAYEXEC?|?VM_READ?|?VM_EXEC
        ????|?((shmflg?&?SHM_RDONLY)???0?:?VM_MAYWRITE?|?VM_WRITE);
        ?shmd->vm_file?=?NULL;
        ?shmd->vm_offset?=?0;
        ?shmd->vm_ops?=?&shm_vm_ops;

        ?shp->u.shm_nattch++;?????/*?prevent?destruction?*/
        ?spin_unlock(&shm_lock);
        ?err?=?shm_map(shmd);
        ?spin_lock(&shm_lock);
        ?if?(err)
        ??goto?failed_shm_map;

        ?insert_attach(shp,shmd);??/*?insert?shmd?into?shp->attaches?*/

        ?shp->u.shm_lpid?=?current->pid;
        ?shp->u.shm_atime?=?CURRENT_TIME;

        ?*raddr?=?addr;
        ?err?=?0;
        out:
        ?spin_unlock(&shm_lock);
        ?up(¤t->mm->mmap_sem);
        ?return?err;
        ?...
        }

        上面的代碼主要是設置剛創(chuàng)建的 vm_area_struct 結構的各個字段,比較重要的是設置其 vm_ops 字段為 shm_vm_ops,shm_vm_ops 定義如下:

        static?struct?vm_operations_struct?shm_vm_ops?=?{
        ?shm_open,??/*?open?-?callback?for?a?new?vm-area?open?*/
        ?shm_close,??/*?close?-?callback?for?when?the?vm-area?is?released?*/
        ?NULL,???/*?no?need?to?sync?pages?at?unmap?*/
        ?NULL,???/*?protect?*/
        ?NULL,???/*?sync?*/
        ?NULL,???/*?advise?*/
        ?shm_nopage,??/*?nopage?*/
        ?NULL,???/*?wppage?*/
        ?shm_swapout??/*?swapout?*/
        };

        shm_vm_ops 的 nopage 回調(diào)為 shm_nopage() 函數(shù),也就是說,當發(fā)生頁缺失異常時將會調(diào)用此函數(shù)來恢復內(nèi)存的映射。

        從上面的代碼可看出,shmat() 函數(shù)只是申請了進程的虛擬內(nèi)存空間,而共享內(nèi)存的物理空間并沒有申請,那么在什么時候申請物理內(nèi)存呢?答案就是當進程發(fā)生缺頁異常的時候會調(diào)用 shm_nopage() 函數(shù)來恢復進程的虛擬內(nèi)存地址到物理內(nèi)存地址的映射。

        shm_nopage() 函數(shù)實現(xiàn)

        shm_nopage() 函數(shù)是當發(fā)生內(nèi)存缺頁異常時被調(diào)用的,代碼如下:

        static?struct?page?*?shm_nopage(struct?vm_area_struct?*?shmd,?unsigned?long?address,?int?no_share)
        {
        ?pte_t?pte;
        ?struct?shmid_kernel?*shp;
        ?unsigned?int?idx;
        ?struct?page?*?page;

        ?shp?=?*(struct?shmid_kernel?**)?shmd->vm_private_data;
        ?idx?=?(address?-?shmd->vm_start?+?shmd->vm_offset)?>>?PAGE_SHIFT;

        ?spin_lock(&shm_lock);
        again:
        ?pte?=?shp->shm_pages[idx];?//?共享內(nèi)存的頁表項
        ?if?(!pte_present(pte))?{???//?如果內(nèi)存頁不存在
        ??if?(pte_none(pte))?{
        ???spin_unlock(&shm_lock);
        ???page?=?get_free_highpage(GFP_HIGHUSER);?//?申請一個新的物理內(nèi)存頁
        ???if?(!page)
        ????goto?oom;
        ???clear_highpage(page);
        ???spin_lock(&shm_lock);
        ???if?(pte_val(pte)?!=?pte_val(shp->shm_pages[idx]))
        ????goto?changed;
        ??}?else?{
        ???...
        ??}
        ??shm_rss++;
        ??pte?=?pte_mkdirty(mk_pte(page,?PAGE_SHARED));???//?創(chuàng)建頁表項
        ??shp->shm_pages[idx]?=?pte;??????????????????????//?保存共享內(nèi)存的頁表項
        ?}?else
        ??--current->maj_flt;??/*?was?incremented?in?do_no_page?*/

        done:
        ?get_page(pte_page(pte));
        ?spin_unlock(&shm_lock);
        ?current->min_flt++;
        ?return?pte_page(pte);
        ?...
        }

        shm_nopage() 函數(shù)的主要功能是當發(fā)生內(nèi)存缺頁時,申請新的物理內(nèi)存頁,并映射到共享內(nèi)存中。由于使用共享內(nèi)存時會映射到相同的物理內(nèi)存頁上,從而不同進程可以共用此塊內(nèi)存。


        瀏覽 41
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲国产精品18久久久久久 | a亚洲网站 | 日韩精品一级毛片免费视频 | 免费无码精品久久久嫩青 | 潘金莲激情呻吟欲求不满 | 久久久久久久精 | 亚洲欧美国产精品专区久久 | 视频一区二 | 中国特级黄色一级片 | chinesexxxxhd老头 |