<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 5711字,需瀏覽 12分鐘

           ·

          2021-10-25 17:06

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

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

          但有時(shí)候?yàn)榱俗尣煌M(jìn)程之間進(jìn)行通信,需要讓不同進(jìn)程共享相同的物理內(nèi)存,Linux通過 共享內(nèi)存 來實(shí)現(xiàn)這個(gè)功能。下面先來介紹一下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)識(shí)系統(tǒng)的唯一IPC資源。

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

          下面通過一個(gè)例子來介紹一下共享內(nèi)存的使用方法。在這個(gè)例子中,有兩個(gè)進(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;
          }

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

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

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

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

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

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

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

          通過前面的例子可知,要使用共享內(nèi)存,首先需要調(diào)用 shmget() 函數(shù)來創(chuàng)建或者獲取一塊共享內(nèi)存。shmget() 函數(shù)的實(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ù)的實(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)識(shí)符即可。否則就調(diào)用 newseg() 函數(shù)創(chuàng)建新的共享內(nèi)存。newseg() 函數(shù)的實(shí)現(xiàn)也比較簡單,就是創(chuàng)建一個(gè)新的 struct shmid_kernel 結(jié)構(gòu)體,然后設(shè)置其各個(gè)字段的值,并且保存到 shm_segs數(shù)組 中。

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

          shmat() 函數(shù)用于將共享內(nèi)存映射到本地虛擬內(nèi)存地址,由于 shmat() 函數(shù)的實(shí)現(xiàn)比較復(fù)雜,所以我們分段來分析這個(gè)函數(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)識(shí)符來找到共享內(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)))?//?獲取一個(gè)空閑的虛擬內(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;
          ?}

          上面的代碼主要找到一個(gè)可用的虛擬內(nèi)存地址,如果在調(diào)用 shmat() 函數(shù)時(shí)沒有指定了虛擬內(nèi)存地址,那么就通過 get_unmapped_area() 函數(shù)來獲取一個(gè)可用的虛擬內(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)建一個(gè) 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)的各個(gè)字段,比較重要的是設(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ā)生頁缺失異常時(shí)將會(huì)調(diào)用此函數(shù)來恢復(fù)內(nèi)存的映射。

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

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

          shm_nopage() 函數(shù)是當(dāng)發(fā)生內(nèi)存缺頁異常時(shí)被調(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)存的頁表項(xiàng)
          ?if?(!pte_present(pte))?{???//?如果內(nèi)存頁不存在
          ??if?(pte_none(pte))?{
          ???spin_unlock(&shm_lock);
          ???page?=?get_free_highpage(GFP_HIGHUSER);?//?申請(qǐng)一個(gè)新的物理內(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)建頁表項(xiàng)
          ??shp->shm_pages[idx]?=?pte;??????????????????????//?保存共享內(nèi)存的頁表項(xiàng)
          ?}?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)存缺頁時(shí),申請(qǐng)新的物理內(nèi)存頁,并映射到共享內(nèi)存中。由于使用共享內(nèi)存時(shí)會(huì)映射到相同的物理內(nèi)存頁上,從而不同進(jìn)程可以共用此塊內(nèi)存。

          推薦閱讀

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


          帶你破解 DDOS 攻擊的原理


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


          詳解 Tomcat 組成與工作原理!


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

          瀏覽 30
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日批免费视频观看 | 国产av无码婷婷 国产av无码网站 | 午夜视频一区 | а天堂中文在线官网 | 日韩黄色小视频 |