zoukankan      html  css  js  c++  java
  • 程序员的自我修养——第六章

    进程与程序:

          程序是一个静态的概念,它就是这些预编译好的指令和数据集合的一个文件;进程则是一个动态的概念,它是程序运行时的一个过程,很多时候把动态库叫做运行时也有一定的含义。

         一般来来说,C语言指针大小的位数与虚拟空间的位数相同,如果32位平台下指针为32位,即4字节;64位平台下的指针为64位,即8字节。

    Intel自从1995年的Pentium Pro CPU开始采用了36位的物理地址,也就是可以访问高达64GB的物理内存。Intel把这个地址扩展方式叫做PAE(Physical Address Extension)。

    应用程序如何使用这些大于常规内存的空间?

    一个很常见的方法就是操作系统提供一个窗口映射的方法,把这些额外的内存呢映射到进程地址空间中来。在Windows下,这个访问内存的操作方式叫做AWE(Address Windowing Extensions);在Linux中采用mmap()系统调用来实现。

    覆盖装入和页映射是两种很典型的动态装载方法,它们都利用了程序的局部性原理。

    程序的局部性原理:是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理又表现为:时间局部性和空间局部性。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。空间局部性是指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问。

    源文档 <http://baike.baidu.com/view/3253308.htm>

    从操作系统的角度,一个进程最关键的特征是它拥有独立的虚拟地址空间,这使得它有别于其他进程。

    一个程序被执行同时都伴随着一个新进程的创建:创建一个进程,然后装载相应的可执行文件并且执行。该工程需要做三件事:

    ·创建一个独立的虚拟地址空间

      将虚拟空间的各个页映射至相应的物理空间,实际上只是分配了一个页目录(Page Directory)就可以了

    ·读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系

      建立虚拟地址空间与可执行文件的映射关系。当发生缺页故障时,操作系统应该知道当前所需要的页在可执行文件中的哪个位置,这就是虚拟空间与可执行文件之间的映射关系。

      这种映射关系只是保存在操作系统内部的一个数据构。Linux中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA,Virtual Memory Area);在Windows中将这个叫做虚拟段(Virtual Section)。

    ·将CPU的指令寄存器设置成可执行文件的入口地址,启动并运行

    页错误的处理:当发生Page Fault的时候,操作系统查询VMA,计算出页面在可执行文件中的偏移,然后在物理内存中分配一个物理页面,将进程中该页与分配的物理页间建立映射关系,然后把控制权再还给进程,进程从刚才页错误的位置开始重新开始运行。

    ELF文件中, 段的权限只有为数不多的几种组合:

    ·以代码段为代表的权限为可读可执行的段

    ·以数据段和BSS段为代表的权限为可读可写的段

    ·以只读数据段为代表的权限为只读的段。

    对于相同权限的段,把它们合并到一起当做一个段进行映射。

    root@ubuntu:~/Desktop/ezCode# readelf -S SectionMapping.elf

    There are 29 section headers, starting at offset 0x868dc:

    Section Headers:

      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al

      [ 0]                   NULL            00000000 000000 000000 00      0   0  0

      [ 1] .note.ABI-tag     NOTE            080480f4 0000f4 000020 00   A  0   0  4

      [ 2] .note.gnu.build-i NOTE            08048114 000114 000024 00   A  0   0  4

      [ 3] .rel.plt          REL             08048138 000138 000028 08   A  0   5  4

      [ 4] .init             PROGBITS        08048160 000160 000030 00  AX  0   0  4

      [ 5] .plt              PROGBITS        08048190 000190 000050 00  AX  0   0  4

      [ 6] .text             PROGBITS        080481e0 0001e0 065d5c 00  AX  0   0 16

      [ 7] __libc_freeres_fn PROGBITS        080adf40 065f40 000b57 00  AX  0   0 16

      [ 8] .fini             PROGBITS        080aea98 066a98 00001c 00  AX  0   0  4

      [ 9] .rodata           PROGBITS        080aeac0 066ac0 0191b0 00   A  0   0 32

      [10] __libc_subfreeres PROGBITS        080c7c70 07fc70 000030 00   A  0   0  4

      [11] __libc_atexit     PROGBITS        080c7ca0 07fca0 000004 00   A  0   0  4

      [12] .eh_frame         PROGBITS        080c7ca4 07fca4 0054d8 00   A  0   0  4

      [13] .gcc_except_table PROGBITS        080cd17c 08517c 00011a 00   A  0   0  1

      [14] .tdata            PROGBITS        080cef8c 085f8c 000010 00 WAT  0   0  4

      [15] .tbss             NOBITS          080cef9c 085f9c 000018 00 WAT  0   0  4

      [16] .ctors            PROGBITS        080cef9c 085f9c 00000c 00  WA  0   0  4

      [17] .dtors            PROGBITS        080cefa8 085fa8 00000c 00  WA  0   0  4

      [18] .jcr              PROGBITS        080cefb4 085fb4 000004 00  WA  0   0  4

      [19] .data.rel.ro      PROGBITS        080cefb8 085fb8 000030 00  WA  0   0  4

      [20] .got              PROGBITS        080cefe8 085fe8 00000c 04  WA  0   0  4

      [21] .got.plt          PROGBITS        080ceff4 085ff4 000020 04  WA  0   0  4

      [22] .data             PROGBITS        080cf020 086020 000740 00  WA  0   0 32

      [23] .bss              NOBITS          080cf760 086760 001b9c 00  WA  0   0 32

      [24] __libc_freeres_pt NOBITS          080d12fc 086760 000018 00  WA  0   0  4

      [25] .comment          PROGBITS        00000000 086760 00006c 01  MS  0   0  1

      [26] .shstrtab         STRTAB          00000000 0867cc 000110 00      0   0  1

      [27] .symtab           SYMTAB          00000000 086d64 0083e0 10     28 974  4

      [28] .strtab           STRTAB          00000000 08f144 00758a 00      0   0  1

    Key to Flags:

      W (write), A (alloc), X (execute), M (merge), S (strings)

      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

      O (extra OS processing required) o (OS specific), p (processor specific)

    ELF可执行文件中有一个专门的数据结构叫做程序头表(Program Header Table)用来保存“Segment”的信息。

    root@ubuntu:~/Desktop/ezCode# readelf -l SectionMapping.elf

    Elf file type is EXEC (Executable file)

    Entry point 0x80481e0

    There are 6 program headers, starting at offset 52

    Program Headers:

      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

      LOAD           0x000000 0x08048000 0x08048000 0x85296 0x85296 R E 0x1000

      LOAD           0x085f8c 0x080cef8c 0x080cef8c 0x007d4 0x02388 RW  0x1000

      NOTE           0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R   0x4

      TLS            0x085f8c 0x080cef8c 0x080cef8c 0x00010 0x00028 R   0x4

      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

      GNU_RELRO      0x085f8c 0x080cef8c 0x080cef8c 0x00074 0x00074 R   0x1

     Section to Segment mapping:

      Segment Sections...

       00     .note.ABI-tag .note.gnu.build-id .rel.plt .init .plt .text __libc_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit .eh_frame .gcc_except_table

       01     .tdata .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs

       02     .note.ABI-tag .note.gnu.build-id

       03     .tdata .tbss

       04    

       05     .tdata .ctors .dtors .jcr .data.rel.ro .got

    Elf32_Phdr结构的几个成员与使用readelf -l 命令打印文件头表显示的结果一一对应。

    /usr/src/linux/include/linux/elf.h

    typedef struct elf32_phdr{

    Elf32_Word p_type;

    Elf32_Off p_offset;

    Elf32_Addr p_vaddr;

    Elf32_Addr p_paddr;

    Elf32_Word p_filesz;

    Elf32_Word p_memsz;

    Elf32_Word p_flags;

    Elf32_Word p_align;

    } Elf32_Phdr;

    Elf32_Phdr->p_type

    段的类型,它能告诉我们这个段里存放着什么用途的数据。此字段的值是在elf.h中定义了一些常量。例如1(PT_LOAD)表示是可加载的段,这样的段将被读入程序的进程空间成为内存映像的一部分。段的种类再不断增加,例如7(PT_TLS)在以前就没有定义,它表示用于线程局部存储。

    Elf32_Phdr->p_flags

    段的属性。它用每一个二进制位表示一种属,相应位为1表示含有相应的属性,为0表示不含那种属性。其中最低位是可执行位,次低位是可写位,第三低位是可读位。如果这个字段的最低三位同时为1那就表示这个段中的数据加载以后既可读也可写而且可执行的。同样在elf.h文件中也定义了一此常量(PF_X、 PF_W、PF_R)来测试这个字段的属性,做为一个好习惯应该尽量使用这此常量。

    Elf32_Phdr->p_offset

    该段在文件中的偏移。这个偏移是相对于整个文件的。

    Elf32_Phdr->p_vaddr

    该段加载后在进程空间中占用的内存起始地址。

    Elf32_Phdr->p_paddr

    该段的物理地地址。这个字段被忽略,因为在多数现代操作系统下物理地址是进程无法触及的。

    Elf32_Phdr->p_filesz

    该段在文件中占用的字节大小。有些段可能在文件中不存在但却占用一定的内存空间,此时这个字段为0。

    Elf32_Phdr->p_memsz

    该段在内存中占用的字节大小。有些段可能仅存在于文件中而不被加载到内存,此时这个字段为0。

    Elf32_Phdr->p_align

    对齐。现代操作系统都使用虚拟内存为进程序提供更大的空间,分页技术功不可没,页就成了最小的内存分配单位,不足一页的按一页算。所以加载程序数据一般也从一页的起始地址开始,这就属于对齐。

    源文档 <http://hi.baidu.com/zengzhaonong/blog/item/3b9f5e347f52c24d251f14b9.html>

    在Linux下,我们可以通过查看“/proc”来查看进程的虚拟空间分布

    root@ubuntu:~/Desktop/ezCode# ./SectionMapping.elf  &

    [1] 2458

    root@ubuntu:~/Desktop/ezCode# cat  /proc/2458/maps

    00da3000-00da4000 r-xp 00000000 00:00 0          [vdso]

    08048000-080ce000 r-xp 00000000 08:01 209272     /root/Desktop/ezCode/SectionMapping.elf

    080ce000-080d0000 rw-p 00085000 08:01 209272     /root/Desktop/ezCode/SectionMapping.elf

    080d0000-080d2000 rw-p 00000000 00:00 0

    08bb6000-08bd8000 rw-p 00000000 00:00 0          [heap]

    bfc81000-bfca2000 rw-p 00000000 00:00 0          [stack]

    第一列表示VMA的地址范围; 第二列是VMA的权限, rwx, “p”表示(COW),“s”表示共享,第三列表示偏移,表示VMA对应的Segment在映像文件中的偏移,第四列表示映像文件所在设备的主次设备号,第五列表示文件的节点号。最后一列表示映像文件的路径。

    操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间;基本原则是将相同权限属性的、有相同映像文件的映射成一个VMA。

    一个进程可以分为如下几种VMA区域:

    ·代码VMA,rx,有映像文件

    ·数据VMA,rwx,有映像文件

    ·堆VMA,rwx,无映像文件,匿名,可向上扩展

    ·栈VMA,rx,无映像文件,匿名,可向下扩展

    进程虚拟空间

    Linux内核中ELF可执行文件的装载/load_elf_binary()函数解析

    源文档 <http://www.the2ndmoon.net/weblog/?p=313>

    ELF文件的装载过程:

    fork   ->   execve() -> sys_execve() -> do_execve()

    do_execve() 读取文件的前128个字节判断文件的格式(一般根据魔数来判断,比如elf的头四个字节为:0x7F, e, l, f)。

    然后调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理过程,对于elf则调用load_elf_binary():

    ·检查ELF可执行文件格式的有效性

    ·寻找动态链接的“.interp”段,设置动态连接器路径

    ·根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。

    ·根据ELF进程环境,比如进程启动是EDX寄存器的地址应该是DT_FINI的地址。

    ·将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,静态ELF可执行文件为e_entry所指的地址,对于动态ELF入口点为动态连接器。

    Load_elf_binary()执行完毕,返回至do_execve()再返回至sys_execve(),最后一步的系统调用返回地址改成了被装在的ELF程序入口地址。当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,新程序开始执行。

    Windows PE文件的装载 略。

  • 相关阅读:
    【EFCORE笔记】客户端与服务端求值&跟踪与非跟踪查询
    【EFCORE笔记】预先加载&显式加载&延迟加载
    【EFCORE笔记】在远程查询数据
    【EFCORE笔记】远程数据查询支持
    【EFCORE笔记】元素操作&集合运算&转换类型
    【EFCORE笔记】生成操作&相等比较&串联运算
    【EFCORE笔记】联接运算与数据分组
    【EFCORE笔记】投影运算与数据分区
    【EFCORE笔记】排序&集运算&筛选&限定
    【EFCORE笔记】团队环境中的迁移
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2486650.html
Copyright © 2011-2022 走看看