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>

        從用戶(hù)態(tài)、內(nèi)核態(tài)、全局變量、BSS函數(shù)看進(jìn)程運(yùn)行狀態(tài)

        共 11069字,需瀏覽 23分鐘

         ·

        2022-02-25 12:37

        點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號(hào)

        重磅干貨,第一時(shí)間送達(dá)

        轉(zhuǎn)載:一口Linux

        收集項(xiàng)目組需求的時(shí)候,我們知道一個(gè)進(jìn)程要運(yùn)行起來(lái)需要以下的內(nèi)存結(jié)構(gòu)。

        用戶(hù)態(tài):

        • 代碼段、全局變量、BSS

        • 函數(shù)棧

        • 內(nèi)存映射區(qū)

        內(nèi)核態(tài):

        • 內(nèi)核的代碼、全局變量、BSS

        • 內(nèi)核數(shù)據(jù)結(jié)構(gòu)例如 task_struct

        • 內(nèi)核棧

        • 內(nèi)核中動(dòng)態(tài)分配的內(nèi)存

        現(xiàn)在這些事是不是已經(jīng)都有了著落?

        我畫(huà)了一個(gè)圖,總結(jié)一下進(jìn)程運(yùn)行狀態(tài)在 32 位下對(duì)應(yīng)關(guān)系。


        對(duì)于 64 位的對(duì)應(yīng)關(guān)系,只是稍有區(qū)別,我這里也畫(huà)了一個(gè)圖,方便你對(duì)比理解。



        用戶(hù)態(tài)和內(nèi)核態(tài)的劃分

        進(jìn)程的虛擬地址空間,其實(shí)就是站在項(xiàng)目組的角度來(lái)看內(nèi)存,所以我們就從 task_struct 出發(fā)來(lái)看。這里面有一個(gè) struct mm_struct 結(jié)構(gòu)來(lái)管理內(nèi)存。

        struct mm_struct		*mm;

        在 struct mm_struct 里面,有這樣一個(gè)成員變量:

        unsigned long task_size;		/* size of task vm space */

        我們之前講過(guò),整個(gè)虛擬內(nèi)存空間要一分為二,一部分是用戶(hù)態(tài)地址空間,一部分是內(nèi)核態(tài)地址空間,那這兩部分的分界線(xiàn)在哪里呢?這就要 task_size 來(lái)定義。

        對(duì)于 32 未來(lái)的系統(tǒng),內(nèi)核里面是這樣定義 TASK_SIZE 答:

        #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í)行一個(gè)新的進(jìn)程的時(shí)候,會(huì)做以下的設(shè)置:

        current->mm->task_size = TASK_SIZE;

        對(duì)于 32 位系統(tǒng),最大能夠?qū)ぶ?2^32=4G,其中用戶(hù)態(tài)虛擬地址空間是 3G,內(nèi)核態(tài)是 1G。

        對(duì)于 64 位置系統(tǒng),虛擬地址只使用了 48 位。就像代碼里面寫(xiě)的一樣,1 左移了 47 位,就相當(dāng)于 48 位地址空間一半的位置,0x0000800000000000,然后減去一個(gè)頁(yè),就是 0x00007FFFFFFFF000,共 128T。同樣,內(nèi)核空間也是 128T。內(nèi)核空間和用戶(hù)空間之間隔著很大的空隙,以此來(lái)進(jìn)行隔離。

        用戶(hù)態(tài)布局

        我們先來(lái)看用戶(hù)態(tài)虛擬空間的布局。

        之前我們講了用戶(hù)態(tài)虛擬空間里面有幾類(lèi)數(shù)據(jù),例如代碼、全局變量、堆、棧、內(nèi)存映射區(qū)等。在 struct mm_struct 里面,有下面這些變量定義了這些區(qū)域的統(tǒng)計(jì)信息和位置。

        unsigned long mmap_base;	/* base of mmap area */
        unsigned long total_vm; /* Total pages mapped */
        unsigned long locked_vm; /* Pages that have PG_mlocked set */
        unsigned long pinned_vm; /* Refcount permanently increased */
        unsigned long data_vm; /* VM_WRITE & ~VM_SHARED & ~VM_STACK */
        unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE & ~VM_STACK */
        unsigned long stack_vm; /* VM_STACK */
        unsigned long start_code, end_code, start_data, end_data;
        unsigned long start_brk, brk, start_stack;
        unsigned long arg_start, arg_end, env_start, env_end;

        其中,total_vm 是總共映射的頁(yè)的數(shù)目。我們知道,這么大的虛擬地址空間,不可能都有真實(shí)內(nèi)存對(duì)應(yīng),所以這里是映射的數(shù)目。當(dāng)內(nèi)存吃緊的時(shí)候,有些頁(yè)可以換出到硬盤(pán)上,有的頁(yè)因?yàn)楸容^重要,不能換出。locked_vm 就是被鎖定不能換出,pinned_vm 是不能換出,也不能移動(dòng)。

        data_vm 是存放數(shù)據(jù)的頁(yè)的數(shù)目,exec_vm 是存放可執(zhí)行文件的頁(yè)的數(shù)目,stack_vm 是棧所占的頁(yè)的數(shù)目。

        start_code 和 end_code 表示可執(zhí)行代碼的開(kāi)始和結(jié)束位置,start_data 和 end_data 表示已初始化數(shù)據(jù)的開(kāi)始位置和結(jié)束位置。

        start_brk 是堆的起始位置,brk 是堆當(dāng)前的結(jié)束位置。前面咱們講過(guò) malloc 申請(qǐng)一小塊內(nèi)存的話(huà),就是通過(guò)改變 brk 位置實(shí)現(xiàn)的。

        start_stack 是棧的起始位置,棧的結(jié)束位置在寄存器的棧頂指針中。

        arg_start 和 arg_end 是參數(shù)列表的位置, env_start 和 env_end 是環(huán)境變量的位置。它們都位于棧中最高地址的地方。

        mmap_base 表示虛擬地址空間中用于內(nèi)存映射的起始地址。一般情況下,這個(gè)空間是從高地址到低地址增長(zhǎng)的。前面咱們講 malloc 申請(qǐng)一大塊內(nèi)存的時(shí)候,就是通過(guò) mmap 在這里映射一塊區(qū)域到物理內(nèi)存。咱們加載動(dòng)態(tài)鏈接庫(kù) so 文件,也是在這個(gè)區(qū)域里面,映射一塊區(qū)域到 so 文件。

        這下所有用戶(hù)狀態(tài)的區(qū)域的位置基本上都描述清楚了。整個(gè)布局就像下面這張圖這樣。雖然 32 位和 64 位置的空間相差很大,但是區(qū)域的類(lèi)別和布局是相似的。


        除了位置信息之外,struct mm_struct 里面還專(zhuān)門(mén)有一個(gè)結(jié)構(gòu) vm_area_struct,來(lái)描述這些區(qū)域的屬性。

        struct vm_area_struct *mmap;		/* list of VMAs */
        struct rb_root mm_rb;

        這里面一個(gè)是單鏈表,用于將這些區(qū)域串起來(lái)。另外還有一個(gè)紅黑樹(shù)。又是這個(gè)數(shù)據(jù)結(jié)構(gòu),在進(jìn)程調(diào)度的時(shí)候我們用的也是紅黑樹(shù)。它的好處就是查找和修改都很快。這里用紅黑樹(shù),就是為了快速查找一個(gè)內(nèi)存區(qū)域,并在需要改變的時(shí)候,能夠快速修改。

        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ū)域在用戶(hù)空間中的起始和結(jié)束地址。vm_next 和 vm_prev 將這個(gè)區(qū)域串在鏈表上。vm_rb 將這個(gè)區(qū)域放在紅黑樹(shù)上。vm_ops 里面是對(duì)這個(gè)內(nèi)存區(qū)域可以做的操作的定義。

        虛擬內(nèi)存區(qū)域可以映射到物理內(nèi)存,也可以映射到文件,映射到物理內(nèi)存的時(shí)候稱(chēng)為匿名映射,anon_vma 中,anoy 就是 anonymous,匿名的意思,映射到文件就需要有 vm_file 指定被映射的文件。

        那這些 vm_area_struct 是如何和上面的內(nèi)存區(qū)域關(guān)聯(lián)的呢?

        這個(gè)事情是在 load_elf_binary 里面實(shí)現(xiàn)的。沒(méi)錯(cuò),就是它。加載內(nèi)核的是它,啟動(dòng)第一個(gè)用戶(hù)態(tài)進(jìn)程 init 的是它,fork 完了以后,調(diào)用 exec 運(yùn)行一個(gè)二進(jìn)制程序的也是它。

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

        static int load_elf_binary(struct linux_binprm *bprm)
        {
        ......
        setup_new_exec(bprm);
        ......
        retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
        executable_stack);
        ......
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
        elf_prot, elf_flags, total_size);
        ......
        retval = set_brk(elf_bss, elf_brk, bss_prot);
        ......
        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;
        ......
        }

        load_elf_binary 會(huì)完成以下的事情:

        • 調(diào)用 setup_new_exec,設(shè)置內(nèi)存映射區(qū) mmap_base;

        • 調(diào)用 setup_arg_pages,設(shè)置棧的 vm_area_struct,這里面設(shè)置了 mm->arg_start 是指向棧底的,current->mm->start_stack 就是棧底;

        • elf_map 會(huì)將 ELF 文件中的代碼部分映射到內(nèi)存中來(lái);

        • set_brk 設(shè)置了堆的 vm_area_struct,這里面設(shè)置了 current->mm->start_brk = current->mm->brk,也即堆里面還是空的;

        • load_elf_interp 將依賴(lài)的 so 映射到內(nèi)存中的內(nèi)存映射區(qū)域。

        最終就形成下面這個(gè)內(nèi)存映射圖。

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

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

        第二種情況是通過(guò) malloc 申請(qǐng)一個(gè)堆內(nèi)的空間,當(dāng)然底層要么執(zhí)行 brk,要么執(zhí)行 mmap。關(guān)于內(nèi)存映射的部分,我們后面的章節(jié)講,這里我們重點(diǎn)看一下 brk 是怎么做的。

        brk 系統(tǒng)調(diào)用實(shí)現(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);
        oldbrk = PAGE_ALIGN(mm->brk);
        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

        前面我們講過(guò)了,堆是從低地址向高地址增長(zhǎng)的,sys_brk 函數(shù)的參數(shù) brk 是新的堆頂位置,而當(dāng)前的 mm->brk 是原來(lái)堆頂?shù)奈恢谩?/span>

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

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

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

        如果還有空間,就調(diào)用 do_brk 進(jìn)一步分配堆空間,從舊堆頂開(kāi)始,分配計(jì)算出的新舊堆頂之間的頁(yè)數(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 找到將來(lái)的 vm_area_struct 節(jié)點(diǎn)在紅黑樹(shù)的位置,找到它的父節(jié)點(diǎn)、前序節(jié)點(diǎn)。接下來(lái)調(diào)用 vma_merge,看這個(gè)新節(jié)點(diǎn)是否能夠和現(xiàn)有樹(shù)中的節(jié)點(diǎn)合并。如果地址是連著的,能夠合并,則不用創(chuàng)建新的 vm_area_struct 了,直接跳到 out,更新統(tǒng)計(jì)值即可;如果不能合并,則創(chuàng)建新的 vm_area_struct,既加到 anon_vma_chain 鏈表中,也加到紅黑樹(shù)中。

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

        用戶(hù)態(tài)虛擬空間分析完畢,接下來(lái)我們分析內(nèi)核態(tài)虛擬空間。

        內(nèi)核態(tài)的虛擬空間和某一個(gè)進(jìn)程沒(méi)有關(guān)系,所有進(jìn)程通過(guò)系統(tǒng)調(diào)用進(jìn)入到內(nèi)核之后,看到的虛擬地址空間都是一樣的。

        這里強(qiáng)調(diào)一下,千萬(wàn)別以為到了內(nèi)核里面,咱們就會(huì)直接使用物理內(nèi)存地址了,想當(dāng)然地認(rèn)為下面討論的都是物理內(nèi)存地址,不是的,這里討論的還是虛擬內(nèi)存地址,但是由于內(nèi)核總是涉及管理物理內(nèi)存,因而總是隱隱約約發(fā)生關(guān)系,所以這里必須思路清晰,分清楚物理內(nèi)存地址和虛擬內(nèi)存地址。

        在內(nèi)核態(tài),32 位和 64 位的布局差別比較大,主要是因?yàn)?32 位內(nèi)核態(tài)空間太小了。

        我們來(lái)看 32 位的內(nèi)核態(tài)的布局。


        32 位的內(nèi)核態(tài)虛擬地址空間一共就 1G,占絕大部分的前 896M,我們稱(chēng)為直接映射區(qū)。

        所謂的直接映射區(qū),就是這一塊空間是連續(xù)的,和物理內(nèi)存是非常簡(jiǎn)單的映射關(guān)系,其實(shí)就是虛擬內(nèi)存地址減去 3G,就得到物理內(nèi)存的位置。

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

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

        • __va(paddr) 則計(jì)算出對(duì)應(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)

        但是你要注意,這里虛擬地址和物理地址發(fā)生了關(guān)聯(lián)關(guān)系,在物理內(nèi)存的開(kāi)始的 896M 的空間,會(huì)被直接映射到 3G 至 3G+896M 的虛擬地址,這樣容易給你一種感覺(jué),是這些內(nèi)存訪(fǎng)問(wèn)起來(lái)和物理內(nèi)存差不多,別這樣想,在大部分情況下,對(duì)于這一段內(nèi)存的訪(fǎng)問(wèn),在內(nèi)核中,還是會(huì)使用虛擬地址的,并且將來(lái)也會(huì)為這一段空間建設(shè)頁(yè)表,對(duì)這段地址的訪(fǎng)問(wèn)也會(huì)走上一節(jié)我們講的分頁(yè)地址的流程,只不過(guò)頁(yè)表里面比較簡(jiǎn)單,是直接的一一對(duì)應(yīng)而已。

        這 896M 還需要仔細(xì)分解。在系統(tǒng)啟動(dòng)的時(shí)候,物理內(nèi)存的前 1M 已經(jīng)被占用了,從 1M 開(kāi)始加載內(nèi)核代碼段,然后就是內(nèi)核的全局變量、BSS 等,也是 ELF 里面涵蓋的。這樣內(nèi)核的代碼段,全局變量,BSS 也就會(huì)被映射到 3G 后的虛擬地址空間里面。具體的物理內(nèi)存布局可以查看 /proc/iomem。

        在內(nèi)核運(yùn)行的過(guò)程中,如果碰到系統(tǒng)調(diào)用創(chuàng)建進(jìn)程,會(huì)創(chuàng)建 task_struct 這樣的實(shí)例,內(nèi)核的進(jìn)程管理代碼會(huì)將實(shí)例創(chuàng)建在 3G 至 3G+896M 的虛擬空間中,當(dāng)然也會(huì)被放在物理內(nèi)存里面的前 896M 里面,相應(yīng)的頁(yè)表也會(huì)被創(chuàng)建。

        在內(nèi)核運(yùn)行的過(guò)程中,會(huì)涉及內(nèi)核棧的分配,內(nèi)核的進(jìn)程管理的代碼會(huì)將內(nèi)核棧創(chuàng)建在 3G 至 3G+896M 的虛擬空間中,當(dāng)然也就會(huì)被放在物理內(nèi)存里面的前 896M 里面,相應(yīng)的頁(yè)表也會(huì)被創(chuàng)建。

        896M 這個(gè)值在內(nèi)核中被定義為 high_memory,在此之上常稱(chēng)為“高端內(nèi)存”。這是個(gè)很籠統(tǒng)的說(shuō)法,到底是虛擬內(nèi)存的 3G+896M 以上的是高端內(nèi)存,還是物理內(nèi)存 896M 以上的是高端內(nèi)存呢?

        這里仍然需要辨析一下,高端內(nèi)存是物理內(nèi)存的概念。它僅僅是內(nèi)核中的內(nèi)存管理模塊看待物理內(nèi)存的時(shí)候的概念。前面我們也說(shuō)過(guò),在內(nèi)核中,除了內(nèi)存管理模塊直接操作物理地址之外,內(nèi)核的其他模塊,仍然要操作虛擬地址,而虛擬地址是需要內(nèi)存管理模塊分配和映射好的。

        假設(shè)咱們的電腦有 2G 內(nèi)存,現(xiàn)在如果內(nèi)核的其他模塊想要訪(fǎng)問(wèn)物理內(nèi)存 1.5G 的地方,應(yīng)該怎么辦呢?如果你覺(jué)得,我有 32 位的總線(xiàn),訪(fǎng)問(wèn)個(gè) 2G 還不小菜一碟,這就錯(cuò)了。

        首先,你不能使用物理地址。你需要使用內(nèi)存管理模塊給你分配的虛擬地址,但是虛擬地址的 0 到 3G 已經(jīng)被用戶(hù)態(tài)進(jìn)程占用去了,你作為內(nèi)核不能使用。因?yàn)槟銓?xiě) 1.5G 的虛擬內(nèi)存位置,一方面你不知道應(yīng)該根據(jù)哪個(gè)進(jìn)程的頁(yè)表進(jìn)行映射;另一方面,就算映射了也不是你真正想訪(fǎng)問(wèn)的物理內(nèi)存的地方,所以你發(fā)現(xiàn)你作為內(nèi)核,能夠使用的虛擬內(nèi)存地址,只剩下 1G 減去 896M 的空間了。

        于是,我們可以將剩下的虛擬內(nèi)存地址分成下面這幾個(gè)部分。

        在 896M 到 VMALLOC_START 之間有 8M 的空間。

        VMALLOC_START 到 VMALLOC_END 之間稱(chēng)為內(nèi)核動(dòng)態(tài)映射空間,也即內(nèi)核想像用戶(hù)態(tài)進(jìn)程一樣 malloc 申請(qǐng)內(nèi)存,在內(nèi)核里面可以使用 vmalloc。假設(shè)物理內(nèi)存里面,896M 到 1.5G 之間已經(jīng)被用戶(hù)態(tài)進(jìn)程占用了,并且映射關(guān)系放在了進(jìn)程的頁(yè)表中,內(nèi)核 vmalloc 的時(shí)候,只能從分配物理內(nèi)存 1.5G 開(kāi)始,就需要使用這一段的虛擬地址進(jìn)行映射,映射關(guān)系放在專(zhuān)門(mén)給內(nèi)核自己用的頁(yè)表里面。

        PKMAP_BASE 到 FIXADDR_START 的空間稱(chēng)為持久內(nèi)核映射。使用 alloc_pages() 函數(shù)的時(shí)候,在物理內(nèi)存的高端內(nèi)存得到 struct page 結(jié)構(gòu),可以調(diào)用 kmap 將其在映射到這個(gè)區(qū)域。

        FIXADDR_START 到 FIXADDR_TOP(0xFFFF F000) 的空間,稱(chēng)為固定映射區(qū)域,主要用于滿(mǎn)足特殊需求。

        在最后一個(gè)區(qū)域可以通過(guò) kmap_atomic 實(shí)現(xiàn)臨時(shí)內(nèi)核映射。假設(shè)用戶(hù)態(tài)的進(jìn)程要映射一個(gè)文件到內(nèi)存中,先要映射用戶(hù)態(tài)進(jìn)程空間的一段虛擬地址到物理內(nèi)存,然后將文件內(nèi)容寫(xiě)入這個(gè)物理內(nèi)存供用戶(hù)態(tài)進(jìn)程訪(fǎng)問(wèn)。給用戶(hù)態(tài)進(jìn)程分配物理內(nèi)存頁(yè)可以通過(guò) alloc_pages(),分配完畢后,按說(shuō)將用戶(hù)態(tài)進(jìn)程虛擬地址和物理內(nèi)存的映射關(guān)系放在用戶(hù)態(tài)進(jìn)程的頁(yè)表中,就完事大吉了。這個(gè)時(shí)候,用戶(hù)態(tài)進(jìn)程可以通過(guò)用戶(hù)態(tài)的虛擬地址,也即 0 至 3G 的部分,經(jīng)過(guò)頁(yè)表映射后訪(fǎng)問(wèn)物理內(nèi)存,并不需要內(nèi)核態(tài)的虛擬地址里面也劃出一塊來(lái),映射到這個(gè)物理內(nèi)存頁(yè)。但是如果要把文件內(nèi)容寫(xiě)入物理內(nèi)存,這件事情要內(nèi)核來(lái)干了,這就只好通過(guò) kmap_atomic 做一個(gè)臨時(shí)映射,寫(xiě)入物理內(nèi)存完畢后,再 kunmap_atomic 來(lái)解映射即可。

        32 位的內(nèi)核態(tài)布局我們看完了,接下來(lái)我們?cè)賮?lái)看 64 位的內(nèi)核布局。

        其實(shí) 64 位的內(nèi)核布局反而簡(jiǎn)單,因?yàn)樘摂M空間實(shí)在是太大了,根本不需要所謂的高端內(nèi)存,因?yàn)閮?nèi)核是 128T,根本不可能有物理內(nèi)存超過(guò)這個(gè)值。

        64 位的內(nèi)存布局如圖所示。


        64 位的內(nèi)核主要包含以下幾個(gè)部分。

        從 0xffff800000000000 開(kāi)始就是內(nèi)核的部分,只不過(guò)一開(kāi)始有 8T 的空檔區(qū)域。

        從 __PAGE_OFFSET_BASE(0xffff880000000000) 開(kāi)始的 64T 的虛擬地址空間是直接映射區(qū)域,也就是減去 PAGE_OFFSET 就是物理地址。虛擬地址和物理地址之間的映射在大部分情況下還是會(huì)通過(guò)建立頁(yè)表的方式進(jìn)行映射。

        從 VMALLOC_START(0xffffc90000000000)開(kāi)始到 VMALLOC_END(0xffffe90000000000)的 32T 的空間是給 vmalloc 的。

        從 VMEMMAP_START(0xffffea0000000000)開(kāi)始的 1T 空間用于存放物理頁(yè)面的描述結(jié)構(gòu) struct page 的。

        從 __START_KERNEL_map(0xffffffff80000000)開(kāi)始的 512M 用于存放內(nèi)核代碼段、全局變量、BSS 等。這里對(duì)應(yīng)到物理內(nèi)存開(kāi)始的位置,減去 __START_KERNEL_map 就能得到物理內(nèi)存的地址。這里和直接映射區(qū)有點(diǎn)像,但是不矛盾,因?yàn)橹苯佑成鋮^(qū)之前有 8T 的空當(dāng)區(qū)域,早就過(guò)了內(nèi)核代碼在物理內(nèi)存中加載的位置。

        到這里內(nèi)核中虛擬空間的布局就介紹完了。


        13個(gè)你一定要知道的PyTorch特性

        解讀:為什么要做特征歸一化/標(biāo)準(zhǔn)化?

        一文搞懂 PyTorch 內(nèi)部機(jī)制

        張一鳴:每個(gè)逆襲的年輕人,都具備的底層能力


        關(guān)


        ,學(xué)西學(xué)學(xué)運(yùn)營(yíng)護(hù)號(hào)樂(lè)質(zhì)結(jié)識(shí),關(guān)[],學(xué)習(xí)進(jìn)!



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

        手機(jī)掃一掃分享

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

        手機(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>
            韩国三级片在线视频 | 国产18一19sex性护士 | 中文字幕网站无码视频在线观看 | 日韩插插插 | 国产女人喷水视频 | 一级a做一级a做片性视频爱小说 | 另类少妇 | 国产精品久久久久久久久久下载 | 美女视频黄a视频全免费观看 | 一色逼毛 |