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)存原理

        共 5711字,需瀏覽 12分鐘

         ·

        2021-10-25 17:06

        在下方公眾號后臺回復(fù):面試手冊,可獲取杰哥匯總的 3 份面試 PDF 手冊。

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

        但有時候為了讓不同進(jìn)程之間進(jìn)行通信,需要讓不同進(jìn)程共享相同的物理內(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ù)生成,用于標(biāo)識系統(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)存標(biāo)識符,取決于shmflg的參數(shù)。失敗返回-1,并設(shè)置錯誤碼。

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

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

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


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

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

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

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

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

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

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


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

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

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

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

        進(jìn)程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;
        }

        進(jìn)程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;
        }

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

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

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

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

        在Linux內(nèi)核中,每個共享內(nèi)存都由一個名為 struct shmid_kernel 的結(jié)構(gòu)體來管理,而且Linux限制了系統(tǒng)最大能創(chuàng)建的共享內(nèi)存為128個。通過類型為 struct shmid_kernel 結(jié)構(gòu)的數(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 結(jié)構(gòu)體各個字段的作用,比如 shm_npages 字段表示共享內(nèi)存使用了多少個內(nèi)存頁。而 shm_pages 字段指向了共享內(nèi)存映射的虛擬內(nèi)存頁表項數(shù)組等。

        另外 struct shmid_ds 結(jié)構(gòu)體用于管理共享內(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)存的標(biāo)識符即可。否則就調(diào)用 newseg() 函數(shù)創(chuàng)建新的共享內(nèi)存。newseg() 函數(shù)的實現(xiàn)也比較簡單,就是創(chuàng)建一個新的 struct shmid_kernel 結(jié)構(gòu)體,然后設(shè)置其各個字段的值,并且保存到 shm_segs數(shù)組 中。

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

        shmat() 函數(shù)用于將共享內(nèi)存映射到本地虛擬內(nèi)存地址,由于 shmat() 函數(shù)的實現(xiàn)比較復(fù)雜,所以我們分段來分析這個函數(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 標(biāo)識符來找到共享內(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 結(jié)構(gòu),在內(nèi)存管理一章知道,vm_area_struct 結(jié)構(gòu)用于管理進(jìn)程的虛擬內(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;
        ?...
        }

        上面的代碼主要是設(shè)置剛創(chuàng)建的 vm_area_struct 結(jié)構(gòu)的各個字段,比較重要的是設(shè)置其 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ù),也就是說,當(dāng)發(fā)生頁缺失異常時將會調(diào)用此函數(shù)來恢復(fù)內(nèi)存的映射。

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

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

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

        推薦閱讀

        Cache 工作原理,Cache 一致性,你想知道的都在這里


        帶你破解 DDOS 攻擊的原理


        Https 協(xié)議簡析及中間人攻擊原理


        詳解 Tomcat 組成與工作原理!


        全局負(fù)載均衡、CDN內(nèi)容分發(fā)的原理與實踐

        瀏覽 30
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報
        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>
            一级色情片| 天天色天天射天天操 | 丝袜美腿一区 | 中文最新天堂8√ | 成人免费观看的毛片A片 | 婷婷精品在线 | free性hd性娇小丰满的出处 | 亚洲日韩三级片 | 青青视频偷拍 | 国产操骚逼视频 |