<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      整個虛擬內(nèi)存空間一分為二,一部分是用戶態(tài)地址空間,一部分是內(nèi)核態(tài)地址空間,這兩部分的分界線由 task_size 來定義。

      struct task_struct
      =>
      struct mm_struct    *mm;
      =>
      unsigned long task_size;    /* size of task vm space */
      =>
      #ifdef CONFIG_X86_32
      /*
       * User space process size: 3GB (default).
       */
      #define TASK_SIZE    PAGE_OFFSET
      #define TASK_SIZE_MAX    TASK_SIZE
      /*
      config PAGE_OFFSET
              hex
              default 0xC0000000
              depends on X86_32
      */
      #else
      /*
       * User space process size. 47bits minus one guard page.
      */
      #define TASK_SIZE_MAX  ((1UL << 47) - PAGE_SIZE)
      #define TASK_SIZE    (test_thread_flag(TIF_ADDR32) ? \
                IA32_PAGE_OFFSET : TASK_SIZE_MAX)
      ......
      
      // 當(dāng)執(zhí)行一個新的進(jìn)程的時候,會設(shè)置
      current->mm->task_size = TASK_SIZE;

      用戶態(tài)布局

      struct mm_struct

      mmap_base:
      malloc 申請一大塊內(nèi)存的時候,就是通過 mmap 在這里映射一塊區(qū)域到物理內(nèi)存;
      加載動態(tài)鏈接庫 so 文件,也是在這個區(qū)域里面,映射一塊區(qū)域到 so 文件。

      // 用戶態(tài)的堆、棧、內(nèi)存映射區(qū)等區(qū)域的統(tǒng)計信息和位置
      unsigned long mmap_base;  /* base of mmap area 虛擬地址空間中用于內(nèi)存映射的起始地址,從高地址到低地址增長 */
      unsigned long total_vm;    /* Total pages mapped 總共映射的頁數(shù) */
      unsigned long locked_vm;  /* Pages that have PG_mlocked set 被鎖定不能換出的頁數(shù) */
      unsigned long pinned_vm;  /* Refcount permanently increased 不能換出,也不能移動的頁數(shù) */
      unsigned long data_vm;    /* VM_WRITE & ~VM_SHARED & ~VM_STACK 存放數(shù)據(jù)的頁數(shù)*/
      unsigned long exec_vm;    /* VM_EXEC & ~VM_WRITE & ~VM_STACK 存放可執(zhí)行文件的頁數(shù) */
      unsigned long stack_vm;    /* VM_STACK 棧所占的頁數(shù) */
      unsigned long start_code, end_code, start_data, end_data; /* 可執(zhí)行代碼, 已初始化數(shù)據(jù)的開始和結(jié)束位置 */
      unsigned long start_brk, brk, start_stack; /* 堆的起始位置和堆當(dāng)前的結(jié)束位置;棧的起始位置,棧的結(jié)束位置在寄存器的棧頂指針中 */
      unsigned long arg_start, arg_end, env_start, env_end; /* 參數(shù)列表, 環(huán)境變量的位置,位于棧中最高地址的地方 */
      
      // 各區(qū)域的屬性
      struct vm_area_struct *mmap;    /* list of VMAs 用于將各區(qū)域串起來 */
      struct rb_root mm_rb; // 紅黑樹,快速查找、修改一個內(nèi)存區(qū)域

       

      struct vm_area_struct {
        /* The first cache line has the info for VMA tree walking. */
        unsigned long vm_start;    /* Our start address within vm_mm. */
        unsigned long vm_end;    /* The first byte after our end address within vm_mm. */
        /* linked list of VM areas per task, sorted by address */
        struct vm_area_struct *vm_next, *vm_prev;
        struct rb_node vm_rb;
        struct mm_struct *vm_mm;  /* The address space we belong to. */
        struct list_head anon_vma_chain; /* Serialized by mmap_sem &
                  * page_table_lock */
        struct anon_vma *anon_vma;  /* Serialized by page_table_lock */
        /* Function pointers to deal with this struct. */
        const struct vm_operations_struct *vm_ops;
        struct file * vm_file;    /* File we map to (can be NULL). */
        void * vm_private_data;    /* was vm_pte (shared mem) */
      } __randomize_layout;

      vm_start 和 vm_end 指定了該區(qū)域在用戶空間中的起始和結(jié)束地址。

      vm_next 和 vm_prev 將這個區(qū)域串在鏈表上。

      vm_rb 將這個區(qū)域放在紅黑樹上。vm_ops 里面是對這個內(nèi)存區(qū)域可以做的操作的定義。

      虛擬內(nèi)存區(qū)域可以映射到物理內(nèi)存,也可以映射到文件,映射到物理內(nèi)存的時候稱為匿名映射。

      anon_vma 中,anon 即 anonymous 匿名,映射到文件就需要有 vm_file 指定被映射的文件。

       

      那這些 vm_area_struct 是如何和上面的內(nèi)存區(qū)域關(guān)聯(lián)的呢?這個事情是在 load_elf_binary 里面實現(xiàn)的。

      沒錯,就是它。加載內(nèi)核的是它,啟動第一個用戶態(tài)進(jìn)程 init 的是它,fork 完了以后,調(diào)用 exec 運行一個二進(jìn)制程序的也是它。

      當(dāng) exec 運行一個二進(jìn)制程序的時候,除了解析 ELF 的格式之外,另外一個重要的事情就是建立內(nèi)存映射。

      static int load_elf_binary(struct linux_binprm *bprm)
      {
        ......
        // 設(shè)置內(nèi)存映射區(qū) mmap_base
        setup_new_exec(bprm);
        ......
        // 設(shè)置棧的 vm_area_struct, current->mm->arg_start = current->mm->start_stack指向棧底
        retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
               executable_stack); 
        ......
        // 將 ELF 文件中的代碼部分映射到內(nèi)存中來
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
              elf_prot, elf_flags, total_size); 
        ......
        // 設(shè)置堆的 vm_area_struct,current->mm->start_brk = current->mm->brk,即堆里面還是空的
        retval = set_brk(elf_bss, elf_brk, bss_prot);
        ......
        // 將依賴的 so 映射到內(nèi)存中的內(nèi)存映射區(qū)域。
        elf_entry = load_elf_interp(&loc->interp_elf_ex,
                    interpreter,
                    &interp_map_addr,
                    load_bias, interp_elf_phdata);
        ......
        current->mm->end_code = end_code;
        current->mm->start_code = start_code;
        current->mm->start_data = start_data;
        current->mm->end_data = end_data;
        current->mm->start_stack = bprm->p;
        ......
      }

      映射完畢后,什么情況下會修改呢?

      第一種情況是函數(shù)調(diào)用,涉及函數(shù)棧的改變,主要是改變棧頂指針。

      第二種情況是通過 malloc 申請一個堆內(nèi)的空間,當(dāng)然底層要么執(zhí)行 brk,要么執(zhí)行 mmap。

      brk 系統(tǒng)調(diào)用實現(xiàn)的入口是 sys_brk 函數(shù)。

      SYSCALL_DEFINE1(brk, unsigned long, brk)
      {
        unsigned long retval;
        unsigned long newbrk, oldbrk;
        struct mm_struct *mm = current->mm;
        struct vm_area_struct *next;
        ......
        newbrk = PAGE_ALIGN(brk); // brk新的堆頂位置
        oldbrk = PAGE_ALIGN(mm->brk); // mm->brk原來堆頂?shù)奈恢?/span>
        if (oldbrk == newbrk)
          goto set_brk;
      
        /* Always allow shrinking brk. */
        if (brk <= mm->brk) {
          if (!do_munmap(mm, newbrk, oldbrk-newbrk, &uf))
            goto set_brk;
          goto out;
        }
      
        /* Check against existing mmap mappings. */
        next = find_vma(mm, oldbrk);
        if (next && newbrk + PAGE_SIZE > vm_start_gap(next))
          goto out;
      
        /* Ok, looks good - let it rip. */
        if (do_brk(oldbrk, newbrk-oldbrk, &uf) < 0)
          goto out;
      
      set_brk:
        mm->brk = brk;
        ......
        return brk;
      out:
        retval = mm->brk;
        return retval
      }

      堆是從低地址向高地址增長的,首先要將原來的堆頂和現(xiàn)在的堆頂,都按照頁對齊地址,然后比較大小。如果兩者相同,說明這次增加的堆的量很小,還在一個頁里面,不需要另行分配頁,直接跳到 set_brk 那里,設(shè)置 mm->brk 為新的 brk 就可以了。

      如果發(fā)現(xiàn)新舊堆頂不在一個頁里面,說明要跨頁。如果新堆頂小于舊堆頂,說明是釋放內(nèi)存,至少要釋放一頁,于是調(diào)用 do_munmap 將這一頁的內(nèi)存映射去掉。

      如果堆將要擴(kuò)大,就要調(diào)用 find_vma。如果打開這個函數(shù),看到的是對紅黑樹的查找,找到的是原堆頂所在的 vm_area_struct 的下一個 vm_area_struct,看當(dāng)前的堆頂和下一個 vm_area_struct 之間還能不能分配一個完整的頁。如果不能,沒辦法只好直接退出返回,內(nèi)存空間都被占滿了。如果還有空間,就調(diào)用 do_brk 進(jìn)一步分配堆空間,從舊堆頂開始,分配計算出的新舊堆頂之間的頁數(shù)。

      static int do_brk(unsigned long addr, unsigned long len, struct list_head *uf)
      {
        return do_brk_flags(addr, len, 0, uf);
      }
      
      static int do_brk_flags(unsigned long addr, unsigned long request, unsigned long flags, struct list_head *uf)
      {
        struct mm_struct *mm = current->mm;
        struct vm_area_struct *vma, *prev;
        unsigned long len;
        struct rb_node **rb_link, *rb_parent;
        pgoff_t pgoff = addr >> PAGE_SHIFT;
        int error;
      
        len = PAGE_ALIGN(request);
        ......
        find_vma_links(mm, addr, addr + len, &prev, &rb_link,
                  &rb_parent);
        ......
        vma = vma_merge(mm, prev, addr, addr + len, flags,
            NULL, NULL, pgoff, NULL, NULL_VM_UFFD_CTX);
        if (vma)
          goto out;
        ......
        vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
        INIT_LIST_HEAD(&vma->anon_vma_chain);
        vma->vm_mm = mm;
        vma->vm_start = addr;
        vma->vm_end = addr + len;
        vma->vm_pgoff = pgoff;
        vma->vm_flags = flags;
        vma->vm_page_prot = vm_get_page_prot(flags);
        vma_link(mm, vma, prev, rb_link, rb_parent);
      out:
        perf_event_mmap(vma);
        mm->total_vm += len >> PAGE_SHIFT;
        mm->data_vm += len >> PAGE_SHIFT;
        if (flags & VM_LOCKED)
          mm->locked_vm += (len >> PAGE_SHIFT);
        vma->vm_flags |= VM_SOFTDIRTY;
        return 0;
      }

      在 do_brk 中,調(diào)用 find_vma_links 找到將來的 vm_area_struct 節(jié)點在紅黑樹的位置,找到它的父節(jié)點、前序節(jié)點。

      接下來調(diào)用 vma_merge,看這個新節(jié)點是否能夠和現(xiàn)有樹中的節(jié)點合并。

      如果地址是連著的,能夠合并,則不用創(chuàng)建新的 vm_area_struct 了,直接跳到 out,更新統(tǒng)計值即可。

      如果不能合并,則創(chuàng)建新的 vm_area_struct,既加到 anon_vma_chain 鏈表中,也加到紅黑樹中。

      內(nèi)核態(tài)布局

      在內(nèi)核里面,有兩個宏:

      __pa(vaddr) 返回與虛擬地址 vaddr 相關(guān)的物理地址;

      __va(paddr) 計算出對應(yīng)于物理地址 paddr 的虛擬地址。

      #define __va(x)      ((void *)((unsigned long)(x)+PAGE_OFFSET))
      #define __pa(x)    __phys_addr((unsigned long)(x))
      #define __phys_addr(x)    __phys_addr_nodebug(x)
      #define __phys_addr_nodebug(x)  ((x) - PAGE_OFFSET)

       

       

      posted on 2021-06-20 22:56  jingmojing  閱讀(186)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 亚洲gv猛男gv无码男同 | 高清不卡一区二区三区| 国产国语一级毛片| 国产最新进精品视频| 国自产拍偷拍精品啪啪一区二区| 亚洲精品色哟哟一区二区| 日韩无矿砖一线二线卡乱| 国产精品久久久久久久久软件| 无码日韩av一区二区三区| 欧美z0zo人禽交另类视频| 欧美日韩精品一区二区三区高清视频 | 亚洲精品综合网中文字幕| 国产高清在线不卡一区| bt天堂新版中文在线| 99久久婷婷国产综合精品青草漫画 | 国内自拍偷拍福利视频看看| 亚洲青青草视频在线播放| 真实单亲乱l仑对白视频| 国产伦精品一区二区三区| 国产成人精品久久一区二区| 制服 丝袜 亚洲 中文 综合| 中文字幕在线精品人妻| 亚洲成av人片不卡无码手机版| 国产精品流白浆无遮挡| 国产精品久久久久无码网站| 久久久久久久久久久久中文字幕 | 99久久精品美女高潮喷水| 成人一区二区三区在线午夜| 日韩一区在线中文字幕| 四虎影视国产精品永久在线| 国产精品视频不卡一区二区| 性做久久久久久久久| 内射老阿姨1区2区3区4区| 亚洲 欧美 影音先锋| 狠狠综合久久综合88亚洲| 国产日韩av二区三区| 麻豆亚洲精品一区二区| 国产精品无码无片在线观看3d| 久久96热在精品国产高清| 91久久国产成人免费观看| 污污内射在线观看一区二区少妇|