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

          共 5568字,需瀏覽 12分鐘

           ·

          2021-10-29 07:38

          在Linux系統(tǒng)中,每個(gè)進(jìn)程都有獨(dú)立的虛擬內(nèi)存空間,也就是說(shuō)不同的進(jìn)程訪問(wè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通過(guò) 共享內(nèi)存 來(lái)實(shí)現(xiàn)這個(gè)功能。下面先來(lái)介紹一下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 位,則以只讀方式連接此段,否則以讀寫(xiě)方式連接此段。

          函數(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)存通過(guò) 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)存使用例子

          下面通過(guò)一個(gè)例子來(lái)介紹一下共享內(nèi)存的使用方法。在這個(gè)例子中,有兩個(gè)進(jìn)程,分別為 進(jìn)程A 和 進(jìn)程B,進(jìn)程A 創(chuàng)建一塊共享內(nèi)存,然后寫(xiě)入數(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;
          }

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

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

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

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

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

          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)存頁(yè)。而 shm_pages 字段指向了共享內(nèi)存映射的虛擬內(nèi)存頁(yè)表項(xiàng)數(shù)組等。

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

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

          通過(guò)前面的例子可知,要使用共享內(nèi)存,首先需要調(diào)用 shmget() 函數(shù)來(lái)創(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)比較簡(jiǎ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)也比較簡(jiǎ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ù)雜,所以我們分段來(lái)分析這個(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;

          上面這段代碼主要通過(guò) shmid 標(biāo)識(shí)符來(lái)找到共享內(nèi)存描述符,上面說(shuō)過(guò)系統(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í)沒(méi)有指定了虛擬內(nèi)存地址,那么就通過(guò) get_unmapped_area() 函數(shù)來(lái)獲取一個(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;
          ?}

          上面的代碼主要通過(guò)調(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ù),也就是說(shuō),當(dāng)發(fā)生頁(yè)缺失異常時(shí)將會(huì)調(diào)用此函數(shù)來(lái)恢復(fù)內(nèi)存的映射。

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

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

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


          瀏覽 41
          點(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>
                  性爱豹妹RR一092 | 日韩探花 | 无码人妻一区二区三区蜜桃视频 | 大鸡巴在线 | 亚洲无码播放 |