init_post


// @see Linux Kernel Development chapter 15: The Process Address Space
// @see UnderStandingKernel chapter 9: Process Address Space

struct task_struct {
    // ...
    struct mm_struct *mm, *active_mm;
    // ...
}

当一个task被调度执行时, task->mm loaded, then task->active_mm = task->mm;
kernel thread的task->mm = NULL, 当kernel thread被调度执行时,不load mm,而是保持之前的adress space不变,然后 task->active_mm = prev_task->mm;

struct mm_struct {
    struct list_head mmlist; // 所有task的mm连成一个链表
    atomic_t mm_users;       // 有几个task在用这个mm_struct
    atomic_t mm_count;       // 当一个task结束时,exit_mm()调用mmput(),mm_users--,当mm_users为0时,调用mmdrop(),mm_count--,
                             // 当mm_count为0时,free_mm()把这个mm_struct退回给mm_cachep slab cache.
                             // 所以mm_count正常情况下为1,当kernel也要用这个mm_struct时,mm_count就大于1了
                             // UnderStandingKernel讲的比较清楚: mm_struct原本归属的process算mm_count=1,如果这个mm_count被kernel thread用了,
                             // 那么mm_count=2,如果kernel_thread用的过程中,原来的process结束了,此时mm_count=1,kernel thread可以接着用,
                             // kernel thread也用完了,这个mm_struct才算没用了

    u64 start_code, end_code;
    u64 start_data, end_data;
    u64 start_brk, brk;
    u64 start_stack;
    u64 arg_start, arg_end;
    u64 env_start, env_end;  // 一个task占有的内存都在这了

    u64 rss;        // pages allocated
    u64 total_vm;   // total number of pages 这两个待弄清楚

    pgd_t *pgd; // 这个就是用来切换address space的吧

    struct vm_area_struct *mmap; // mm由vm_area组成
    int map_count;               // 有多少个vm_area
    struct rb_root mm_rb;        // vm_area组成的rb_tree
}

struct vm_area_struct {
    struct mm_struct *vm_mm;        // 这个area是哪个mm_struct的
    struct vm_area_struct *vm_next; // 对应mm_struct里的mmap,组成了一个单链表
    struct rb_node vm_rb;           // 对应mm_struct里的mm_rb,组成了一个rb_tree

    u64 vm_start;
    u64 vm_end;
    pgprot_t vm_page_prot;
    u64 vm_flags;               // 这个area的地址范围和权限

    struct vm_operations_struct *vm_ops; // 操作
}

struct vm_operations_struct {
    void (*open)(struct vm_area_struct *);  // 当这个area加到一个address space里时调用open
    void (*close)(struct vm_area_struct *); // 当这个area从一个address space里移除时调用close
    int (*falut)(struct vm_area_struct *, struct vm_fault *);
    *page_mkwrite
    *access
};


怎么创建一个vm_area_struct,然后把它加到mm_struct里?

u64 do_mmap(struct *file, u64 addr, u64 len, u64 prot, u64 flag, u64 offset);
// file可以为NULL,offset可以为0,这种情况叫anonymous mapping. 否则叫 file-backed mapping.

一个task调用do_mmap后,它的mm_struct里就多了一个(或者extend了现有的一个)vm_area.
user space调用mmap2(), mmap()


总结下 Process Address Space:
一个task本身(text,data,bss,...)以及它要申请使用的内存(heap, mmap, malloc)其实都是在操作vm_area,和物理内存没有实际关系.
当task的代码真的要访问内存时,由于page table里没有,就page fault了,kernel根据fault的地址可以判断出是在vm_area里,还是在vm_area外.
如果在vm_area里,那就分配物理内存,接着执行task代码,否则,那就给进程发信息SIGSEGV,接着进程就segmentation falut了.


再来看下 Documentation/x86/x86_64/mm.txt

0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm hole caused by [48:63] sign extension
ffff800000000000 - ffff80ffffffffff (=40 bits) guard hole
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)
... unused hole ...
ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0
ffffffffa0000000 - fffffffffff00000 (=1536 MB) module mapping space

到目前为止,我们知道

ffffffff80000000 - ffffffffa0000000 (=512 MB) kernel
ffff880000000000 - ffffc7ffffffffff (=64 TB)  direct mapping
ffffea0000000000 - ffffeaffffffffff (=1TB)    virtual memory map // 存放struct page, sizeof (struct page) = 56, 1TB/56*4K=73TB

四级的page table, pgd里一个slot map着 4K * 512 (2M) * 512 (1G) * 512 = 512GB 内存, 四级page table总共能map 512G * 512 = 256TB 内存
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;

current->signal->flags |= SIGNAL_UNKILLABLE;

run_init_process(ramdisk_execute_command); // ramdisk_execute_command = "/init"
    init_filename = "/init";
    argv_init = {"/init"};
    envp_init = {"HOME=/", "TERM=linux", "BOOT_IMAGE=/bzImage"};
    kernel_execve(init_filename, argv_init, envp_init);
    // man execve, man execl
    // 可以看到平时在c程序里调用的exec functions最终都调用的是execve. execve对应着系统调用stub_execve
    // stub_execve最终调用了sys_execve, kernel_execve最终也是调用sys_execve
sys_execve(char *name, char *argv, char *envp, struct pt_regs *regs); => do_execve(filename, argv, envp, regs);
struct files_struct *displaced;
unshare_files(&displaced); // 把当前task.files copy一份出来替换掉当前的,原来的files保存在displaced里.
                           // 当do_execve结束时,会把displaced这个files_struct的引用计数-1,如果减到0了,就free掉
                           // init_post现在还是一个kernel thread,它的files用的是init_task的
                           // unshare_files调用unshare_fd完成copy工作,unshare_fd把files_struct完整copy了一份
                           // 这里比较有意思的是,如果task A调用execve执行了task B, A中open的file的B中还会保留.
                           // tools里的两个小程序 test-execve-fd 和 continue-read 验证了这一情况
接下来新分配一个linux_binprm并做了一系列权限检查.然后open了file.
struct file *file = open_exec(filename); // open_exec调用do_filp_open打开了filename,然后检查此文件是否是regular file,或者mountpoint是否要求NOEXEC
                                         // deny_write_access的逻辑是这样的: 一个可执行文件如果正在修改过程中,那么它是不可执行的的
                                         // inode.i_writecount这样来计数,每当打开这个文件来执行时,检查下writecount是否大于0,是的话说明有其它进程正在写,当前不可执行.
                                         // 如果没有其它进程在写,那就把writecount减1. 我们来猜测一下,当打开一个文件写的时候,也会检查一下writecount,如果小于0,说明正在执行中,不可写,否则就把writecount加1
                                         // 可以写个小程序验证一下,这个小程序就是简单的sleep 10秒,在sleep时调用echo写这个文件,此时就会报Text file busy.

sched_exec(); // sched部分还不是很明白,sched_exec的作用就是尽可能地把当前这个task转到另一个cpu上执行

bprm->file     = file;
bprm->filename = filename;
bprm->interp   = filename;

bprm_mm_init(bprm);
    bprm->mm = mm_alloc(); // mm_alloc分配并简单地初始化了一个mm_struct,并调用mm_alloc_pgd分配了一个pgd
                           // 新分配的这个pgd的初始化是在pgd_ctor()里完成的,其实就是个简单的copy,把init_level4_pgt从272项开始全都copy过来.
                           // 这里也可以看出,每新创建一个进程,都要使用一个page做pgd,kernel自身使用的其它pagetable(pud,pmd,pte)都会和进程共享.
    __bprm_mm_init(bprm); // 初始化一个vma做为stack,大小为一个page. vm_end = STACK_TOP_MAX = TASK_SIZE_MAX = 1 << 47 - PAGE_SIZE
        mm->stack_vm = mm->total_vm = 1;
        bprm->p = vm_end - sizeof(void *);

bprm->argc = count(argv...);
bprm->envc = count(envp...);

prepare_binprm(bprm); // uid,gid等权限检查,然后调用kernel_read读取file的前128字节到bprm->buf里.

/* 我们打印下bprm看看
// bochs: writemem "/tmp/bprm.memdump" 0xFFFF88000642F000 0xf0
./print_bprm /tmp/bprm.memdump
argc = 1, argv = 3
current top of mem = 0x7fffffffeff8
mm = 0xffff880006446000

// bochs writemem "/tmp/mm.memdump" 0xffff880006446000 0x350
./print_mm /tmp/mm.memdump
pgd = 0xffff88000642e000
分析一下这个pgd.
272 - 0x1a04000  // kenel idt map
402 - 0x7437000
465 - 0x6b22000
468 - 0x7be7000 // section mem map
511 - 0x1a05000 // kernel map
*/

copy_strings_kernel(1, &bprm->filename, bprm); // 上边我们知道当前的stack top = 0x7fffffffeff8, 这里把filename copy到stack里
                                               // 上边分配了一个vma做为stack,大小是一个page,但实际上并没有map物理内存,这里copy_strings时把物理内存给map上了
                                               // calc-pgt计算可知 pgd(255) -> pud(511) -> pmd(511) -> pte(510)
                                               // 最终找到stack的这个page在0x19ff000, bochs里view memory 可以看到, 在 0x19fff2处, 是 "/init"

bprm->exec = bprm->p;
copy_strings(bprm->envc, envp, bprm);
copy_strings(bprm->argc, argv, bprm); // 把envp和argv都copy到stack上去.

// copy_strings值得一看,由于stack是从高地址向低地址的,而平时访问内存是从低地址向高地址的,所以要把envp和argv copy到stack上,要倒过来copy.
// 倒的方法也挺简单的,就是 argv + argc,然后循环着把argc--,每次计算argv[argc]字串的长度,把栈指针下移这么多,然后copy就好了.

OK, 现在我们知道一个进程的开头一点的内存布局了.
/*
 __________     0x7fffffffffff
|          |    0x7fffffffeff8 (stack top)
|          |    filename
|          |    envp[N]
|          |    envp[...]
|          |    envp[1]
|          |    envp[0]
|          |    argv[N]
|          |    argv[...]
|          |    argv[1]
|          |    argv[0]
*/

再理一下 struct linux_binprm {
    struct file *file;                  // 调用open_exec打开的可执行文件
    char buf[BINPRM_BUF_SIZE = 128];    // prepare_binprm时读取file的前128字节

    struct mm_struct *mm;               // bprm_mm_init时分配的,新程序的pagetable也是在这里分配并初始化的. 注意pagetable虽然有了,但是并没有使用呢.
    struct vm_area_struct *vma;         // 当前指向stack,默认大小是1个page,如果argv,envp比较大,可能会再分配
    u64 vma_pages;

    u64 p; // 指向栈底

    int argc, envc; // argv, envp参数个数

    char *filename; // 可执行文件的名字
    char *interp;   // 可执行文件的名字

    int recursion_depth; // script嵌套调用不能太深,这个值记录着嵌套几次了
}


search_binary_handler(bprm,regs); // break point 0x1166247
    // kernel支持的binfmt都注册在链表 formats (0x1a3c490) 里.
    struct linux_binfmt {
        struct list_head lh;
        struct module *module;
        int (*load_binary)(struct linux_binfmt *, struct pt_regs *regs);
        int (*load_shlib)(struct *file);
        int (*core_dump)(struct coredump_params *cprm);
        u64 min_coredump;
    }
    // 我们看下当前都注册了哪些binfmt
    0x1a47bc0 (compat_elf_format)   fs/compat_binfmt_elf.c
    0x1a47b80 (elf_format)          fs/binfmt_elf.c
    0x1a47b40 (script_format)       fs/binfmt_script.c
    // search_binary_handler 遍历 formats, 调用其load_binary方法,如果返回值大于0,则认为load成功,然后会把file inode.i_writecount加1,
    // binfmt_script的load方法看起来简单一些,先看这个
    load_script(struct linux_binfmt *bprm, struct pt_regs *regs);
        // script有两个限制: 1) 文件内容以#!开头 2) #!后边使用的interpreter可以还是script,但嵌套不能太多
        // 它会把inode.i_writecount加1,所以即使script正在执行中,也可以修改
        bprm->file = NULL; // 知道这是个script之后,文件的其它内容就不关心了
        // load_script分析文件内容的第一行,找到interpreter和optional args.
        // 分析的过程其实是这样的:
        <?php
            $line1 = trim(substr($line1, 2));
            $line1 = explode(" \t", $line1);
            $i_name = array_shift($line1);
            $i_arg  = implode(" ", $line1);
        ?>
        // 接下来把args和interperter加到栈里,对于init来说,现在栈的情况是这样的
        /*
            "/init"                 (filename)
            "BOOT_IMAGE=/bzImage"
            "TERM=linux"
            "HOME=/"                (envp)
            "/init"
            "/bin/sh"               (argv) // bin/sh没有其它参数了
        */
        bprm->interp = "/bin/sh";
        file = open_exec("/bin/sh");
        bprm->file = file;
        prepare_binprm(bprm);  // 读取/bin/sh的前128字节
        search_binary_handler(bprm, regs);
        // 最终还是要调用二进制的可执行文件
    load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs);
    /*
        man elf
        @see http://www.sco.com/developers/gabi/latest/contents.html
        @see http://www.x86-64.org/documentation/abi.pdf
        @see https://www.technovelty.org/linux/plt-and-got-the-key-to-code-sharing-and-dynamic-libraries.html

        一个elf文件由4部分组成, ELF header, program header table, section header table, 其它
        文件开头就是ELF header, e_ehsize记录着ELF header的长度, e_phoff记录着program header table的位置, e_shoff记录着section header table的位置
        这两个table里每一项的大小都是固定的,分别用e_phentsize,e_shentsize表示,每个table里条目的数量用e_phnum和e_shnum表示
        e_entry记录着程序执行的入口,e_type记录着这是个可执行文件,还是个shared object,e_machine记录着这个文件能在哪个架构下执行,比如x86_64
        section header table里的每一项都有一个名称,这个名称存储在一个section里,e_shstrndx记录着这个section在table中的位置.

        program header entry的p_offset记录着这个segment在文件中的位置,p_filesz记录着这个segment的大小.
        p_type记录着此segment的类型,如果是PT_LOAD,p_memsz记录着当把这个segment load到内存时占的大小, p_vaddr记录着加载到内存时虚拟地址是多少.
        p_flags记录着这个segment的读写执行权限.

        section header table的sh_offset记录着这个section在文件中的位置, sh_size记录着这个section的大小.
        sh_type记录着此section的类型, sh_name记录着section的名称.
    */
        上边了解了elf之后,我们知道elf64 header的大小是64字节,我们已经读取了文件的前128字节,因此已经拿到了完整的header.
        load_elf_binary首先通过检验header来确认这是一个elf格式的文件.
        然后根据 e_phnum 和 sizeof(struct elf_phdr) 计算出 program_header_table 的大小,并把这个table从文件读取到内存里.
        接下来先过一遍table找出PT_INTERP,再过第二遍找出PT_GNU_STACK.先看PT_INTERP.
        上边我们已经知道PT_INTERP segment里其实就一个文件路径,对普通的可执行文件来说,就是"/lib64/ld-linux-x86-64.so.2".
        struct file *interpreter = open_exec("/lib64/ld-linux-x86-64.so.2");
        拿到file后,读取它的前128字节并转成elf64 header. 应该下边就用上了.
        不过当前执行的是busybox的bash程序,busybox是静态编译的,所以没有PT_INTERP.
        再看PT_GNU_STACK,如果这个header里明确说了stack是可执行的,那么executable_stack = EXSTACK_ENABLE_X, 否则, executable_stack = EXSTACK_DISABLE_X;

        flush_old_exec(bprm);
            bprm->mm->exec_file = bprm->file;
            current->mm = bprm->mm;
            activate_mm(); // load mm->pgd to CR3, now use the new pagetable
            arch_pick_mmap_layout();
                // mmap_is_legacy()追踪bochs执行得到以下结果
                //    current->personality & ADDR_COMPAT_LAYOUT = 0
                //    rlimit stack = 0x800000 = 8M
                //    sysctl_legacy_va_layout = 0
                // 最终 mmap_is_legacy() 返回 0
                mm->mmap_base = mmap_base();
                    MIN_GAP = 0x8000000 = 128M
                    TASK_SIZE = 0x7ffffffff000
                    mmap_base = TASK_SIZE - MIN_GAP = 0x7ffff7fff000
                    // UnderStandingKernel Chapter 20: Program Execution Flexible memory region layout 有讲这个mmap_base
                    // 不过它说的是x86的,user address space共有3G空间,在classical layout里,1G以下用作code,data,bss,heap,1G以上用作mapping,这样heap的大小肯定是小于1G的
                    // flexible layout的话,stack限制一个大小,比如128M,余下的空间给heap和mapping共用,heap从低地址向高地址增长,mapping从高地址向低地址走.
                mm->get_unmapped_area = arch_get_unmapped_area_topdown;
                mm->unmap_area = arch_unmap_area_topdown;
            bprm->mm = NULL;

        setup_new_exec(bprm);
            current->comm = "/init";
            current->mm->task_size = TASK_SIZE = 0x7ffffffff000
            flush_signal_handlers();
            flush_old_files(current->files); // 前边我们知道exec出来的task完全copy了原来的files,在这里把close_on_exec里的fd都关闭了

        setup_arg_pages(...);
        current->mm->start_stack = bprm->p; // 原来的stack的virt addr是固定的,setup_arg_pages之后,stack addr就变成一个随机的了
                                            // shift_arg_pages的注释里写的清楚一些,刚开始的地址固定的stack是临时的,当stack确定下来后,就要使用新的stack
                                            // 由于stack的地址变了,pagetable要修改一下
                                            // 在最开始的时候,__bprm_mm_init分配了1个page大小的vma,后来不知道在哪个地方又加了1个page,在这里又硬编码的把stack的vma加了32个page,
                                            // 最终现在的stack有0x22=34个page,不过实际使用的物理内存只有一个page.
                                            // print_mm也可以看到total_vm=0x22, stack_vm=0x22, nr_ptes=0x1

        接下来再过一遍program header table,找出来PT_LOAD segments,调用elf_map把vma做好.这个循环结束后,code,data,bss,brk都知道了.
        这里需要注意的是,elf_map时是按p_filesz做的map,如果p_memsz > p_filesz,那么多出来的就是bss,这一块是没做map的.
        所以接下来的set_brk()检查这一情况,如果有bss,就把map补上.
        // 我们编译的busybox有两个PT_LOAD
        // 第1个p_vaddr=0x400000,filesz=memsz=0x238 pages, 权限是RX,显然是busybox的.text
        // 第2个p_vaddr=0x837ee0,filesz=0x1c57, memsz = 0x77a8, 权限是RW,显然这是busybox的.data,memsz多出来的是.bss, 大小是 0x5b51
        // 在做map里,第1个好说,vma从0x400000到0x638000,
        //      print_mm可以看到 total_vm=0x25a, shared_vm=0x238, exec_vm=0x238,stack_vm=0x22
        // 第二个在map .data时,虽然2个page就够了,但由于p_vaddr起始值不在page boundary上,所以需要3个page,从0x837000到0x83a000,
        //      print_mm可以看到 total_vm=0x25d, shared_vm=0x23b, exec_vm=0x238,stack_vm=0x22
        // bss从0x837ee0 + 0x1c57 = 0x839b37 到 0x839b37 + 0x5b51 = 0x83f688, vma显然要从0x839000-0x840000,共7个page,不过这个范围和上边的.data的vma在0x839000这个page上是重的,
        //      print_mm可以看到 total_vm=0x263, shared_vm=0x23b, exec_vm=0x238,stack_vm=0x22,只多了6个page,不是7个
        // 查看mm->mmap链表可以看到,第一个vma从0x400000-0x638000,第二个vma从0x837000-0x83a000,第三个从0x83a000到0x8400000
        // 最终 mm->start_brk = brk = 0x840000
        上边在map .data时,最后一个page 0x839000既包含了一部分.data,也包含了一部分.bss,由于这个page是map在.data的vma里的,
        当用到这块数据时,会从文件里把整个这4K读出来,显然.bss对应的那部分数据是不应该有的,所以set_brk之后,会检查这种情况,并把bss对应的那一部分内存给清0.
        清0的这个操作一执行,由于pagetable还没有,会page_fault,然后kernel会把pagetable建好,把file里的这个page给读取到内存里,接下来清0就能正常执行了.
        // print_mm会看到nr_ptes=2,查看pgd会看到0x839000这个page的map已经做好了

        如果有PT_INTERP的话(很多情况下会有的,因为多数程序都不是静态编译的),就调用load_elf_interp().

        set_binfmt(&elf_format);
            current->mm->binfmt = &elf_format;

        arch_setup_additional_pages(bprm, !!elf_interpreter);
            // @see http://man7.org/linux/man-pages/man7/vdso.7.html
            // vdso在arch/x86/vdso/vdso.so,编译时直接编译进bzImage了.
            // 在init_vdso_vars里,kernel分配出一块大小为vdso.so大小的内存,然后把vdso.so全部copy过去,这些pages保存在变量vdso_pages里.
            // bochs查看vdso_size = 0x1000, vdso_pages = 0x740c528 => 0xffffea000019d620
            // 之前我们知道0xffffea....这个address space就是用来存储struct pages的,一个struct page大小是0x38字节, 0x19d620/0x38=0x761c,这是pfn,对应的物理内存是0x761c000
            // bochs里一查,果然是.
            // 接下来的VEXTERN(x)看的不是很明白,但应该是修改vdso.so里的symbol的地址.
            arch_setup_additional_pages在比stack更高的地址范围内找一块,分配一个vma给vdso.
            接下来的install_special_mapping把这个vma加到mm里.
            // 注意,虽然vdso已经在内存里了,但是这个时间点并没有做pagetable map,vdso所在的pages被存储到了vma->vm_private_data里,
            // 当page_fault时,调用special_mapping_vmops.special_mapping_fault找到vdso的page.
            我们已经知道vdso_size=0x1000,所以新加的这个vma使得mm->total_vm=0x264. (多了1个page)

        create_elf_tables(bprm, &loc->elf_ex, load_addr, interp_load_addr);
            create_elf_tables首先再次调整stack的地址,然后把platform的字符串(我们的情况是x86_64)copy到stack上
            这个copy会导致page fault,从而新加了一个pte
            接下来产生一个16字节的随机内容,也copy到stack上
            再然后填充current->mm->auxv,auxv是个u64的数组,读取/proc/PID/auxv就能看到数组内容
            接下来把argc,argv,envp,elf_info(auxv) copy到stack上.
            // 这里要区别的是: 前边刚开始的stack里边存放着argv,envp的字符串,这里的argv,envp存放的是指针.也就是说到这里,才形成了c语言的main函数的参数
            int main(int argc, char *argv[], char *envp[]);
            /* 现在的stack布局是这样的
                 __________
                |          |    0x7fffXXXXXXXX (stack top)
                |          |    filename    string
                |          |    envp[N]     string
                |          |    envp[...]   string
                |          |    envp[1]     string
                |          |    envp[0]     string
                |          |    argv[N]     string
                |          |    argv[...]   string
                |          |    argv[1]     string
                |          |    argv[0]     string
                ...
                |          |    platform string (x86_64)
                |          |    random bytes (16字节)
                |          |    elf_info
                |          |    ...
                |          |    elf_info
                |          |    envp[LAST]  NULL
                |          |    envp[N]     ptr
                |          |    ...
                |          |    envp[0]     ptr
                |          |    argv[LAST]  NULL
                |          |    argv[N]     ptr
                |          |    ...
                |          |    argv[0]     ptr
                |          |    argc
            */

        更新current->mm的start_code,end_code,start_data,end_data....等

        arch_random_brk(current->mm); // 原来的brk是根据.data和.bss计算出来的,这里弄一个随机的brk

        ELF_PLAT_INIT(regs, reloc_func_desc);

        start_thread(regs, elf_entry, bprm->p);

acct_update_integrals(current);
free_bprm(bprm);
回过头来说kernel_execve, kernel_execve调用sys_execve返回后,判断返回值,如果为0,则跳到int_ret_from_sys_call执行.
int_ret_from_sys_call接着跳到retint_swapgs执行, reint_swapgs又跳到restore_args执行.
关键在最后一句 iret. 我们看下现在的stack:
0x513ee8        // busybox entry point
0x33            // CS
0x200           // eflags (IF=1,DF=1)
0x7fff0ab60670  // stack top
0x2b            // SS
iret之后,要执行0x513ee8处的指令,我们知道还没有做pagetable的map,所以就page fault了,kernel处理完page fault后,回来继续执行.

进入bash后,我们看下init进程的maps.
mount -o proc none /proc
cat /proc/1/maps
00400000-00638000                   r-xp 00000000 00:01 4182            /bin/busybox        (busybox的第一个PT_LOAD .text)
00837000-0083a000                   rw-p 00237000 00:01 4182            /bin/busybox        (busybox的第二个PT_LOAD .data加一部分的.bss)
0083a000-00840000                   rw-p 00000000 00:00 0                                   (busybox的第二个PT_LOAD的memsz比filesz多出来的部分.bss)
01508000-0152b000                   rw-p 00000000 00:00 0               [heap]              (brk原本应该在.bss后边,但是arch_random_brk把它给调整了)
7fff4cb56000-7fff4cb77000           rw-p 00000000 00:00 0               [stack]             (stack top原本应该在0x7fffffffeff8处,经过几次random之后,就成了这个地址了)
7fff4cbff000-7fff4cc00000           r-xp 00000000 00:00 0               [vdso]              (vdso的地址要stack_top之上,也是个随机地址)
ffffffffff600000-ffffffffff601000   r-xp 00000000 00:00 0               [vsyscall]          (这个不知道是在哪里map上的)