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

        共 15575字,需瀏覽 32分鐘

         ·

        2021-09-18 23:06

        點(diǎn)擊上方 藍(lán)字關(guān)注我們



        在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 <stdio.h>
        #include <unistd.h>
        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/shm.h>

        #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, NULL0);
            if (addr <= 0) {
                printf("failed to map share memory\n");
                return -1;
            }
            
            sprintf(addr, "%s""Hello World\n");
            
            return 0;
        }

        進(jìn)程B

        #include <stdio.h>
        #include <string.h>
        #include <unistd.h>
        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/shm.h>

        #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, NULL0);
            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(&current->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(&current->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(&current->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(&current->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ù)只是申請了進(jìn)程的虛擬內(nèi)存空間,而共享內(nèi)存的物理空間并沒有申請,那么在什么時(shí)候申請物理內(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); // 申請一個(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í),申請新的物理內(nèi)存頁,并映射到共享內(nèi)存中。由于使用共享內(nèi)存時(shí)會(huì)映射到相同的物理內(nèi)存頁上,從而不同進(jìn)程可以共用此塊內(nèi)存。

        往期推薦

        結(jié)合中斷分析TCP/IP協(xié)議棧在Linux內(nèi)核中的運(yùn)行時(shí)序

        深入理解零拷貝技術(shù)

        熬夜淦了近3W字的 Docker 教程,從入門到精通(建議收藏)

        推薦一款 GitHub 星標(biāo) 11.5K 的命令行文件傳輸神器(開源免費(fèi),支持 10GB 大文件)

        Postman 使用教程

        搞定 Linux Shell 文本處理工具,看完這篇還不夠~

        開源版“微信”,了解一下~

        HAProxy容器化實(shí)踐

        萬字長文:Kubernetes 創(chuàng)建 Pod 時(shí),背后到底發(fā)生了什么?

        60,000 毫秒內(nèi)對 Linux 進(jìn)行性能診斷

        有收獲,點(diǎn)個(gè)在看 
        瀏覽 56
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        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>
            亚洲成人aaa | 麻豆91蜜桃传媒在线观看 | 国产高潮视频在线观看 | 欧美日韩激情四射 | 特级婬片A片AAA毛片AA做头 | 女的被c到爽哭视频观看 | 在线看污视频网站 | 日本淫乱视频 | jlzz国产丝袜18老师女人 | 五月天色欲 |