可执行文件的装载与进程

进程虚拟地址空间

程序与进程的区别?

  • 程序是一个静态的概念,就是一些预先编译好的指令和数据集合的一个文件

  • 进程则是一个动态的概念,它是程序运行时的一个过程,很多时候吧动态库也叫作运行时

每个程序被运行起来以后,它将拥有自己独立的虚拟地址空间(Virtual Address Space),这个虚拟地址空间的大小由计算机的硬件平台决定,**具体地说是由CPU的位数决定的。硬件决定了地址空间的最大理论上限,即硬件的寻址空间大小,**比如32位的硬件平台决定了虚拟地址空间的地址为0到232-1,即0x00000000~0xFFFFFFFF,也就是我们常说的4GB虚拟空间大小;而64位的硬件平台具有64位寻址能力,它的虚拟地址空间达到了264字节,即0x0000000000000000~0xFFFFFFFFFFFFFFFF,总共17179869184GB。

从程序的角度看,我们可以通过判断C语言程序中的指针所占的空间来计算虚拟地址空间的大小。一般来说,C语言指针大小的位数与虚拟地址空间的位数相同,如32位平台下的指针为32位,即4字节;64位平台下的指针为64位,即8字节。当然有些特殊情况下,这种规则不成立。

那么32位平台下的4GB虚拟空间,我们的程序是否可以任意使用呢?不行。因为程序在运行的时候处于操作系统的监管下,操作系统为了达到监控程序运行等一系列目的,进程的虚拟空间都在操作系统的掌握之中。进程只能使用那些操作系统分配给进程的地址,如果访问未经允许的空间,那么操作系统就会捕获到这些访问,将进程的这种访问当作非法操作,强制结束进程。我们经常在Windows下碰到”进程因非法操作需要关闭”或Linux下的”Segmentation fault”很多时候是因为访问了未经允许的地址。


那么这4GB的进程虚拟地址空间是怎样的分配状态?以Linux OS为例子,默认下,Linux操作系统将进程的虚拟地址空间做了如下分配:

编译与链接

整个4GB被划分成两个部分,其中操作系统占用了一部分,从地址0xC0000000到0xFFFFFFF,共计1GB,剩下3GB给进程使用

PAE

在32位CPU下,程序的使用空间能不能超过4GB?

两个角度:

  • 空间指虚拟地址空间

那么不能,因为32位的CPU只能使用32位的指针,它的最大寻址范围为0 到 4GB

  • 空间指计算机的内存空间

那么可以,Intel从1995年CPU开始采用36位的物理地址,可以访问高达64GB的无压力内存

从硬件层面,原先32位地址线只能访问最大4GB的物理内存,但是只从扩展至36位地址线后,Intel修改了页映射的方式,使得新的映射方式可以访问到更多的物理内存,Intel把这个地址扩展方式叫做PAE

对于普通应用程序来说正常情况下感觉不到它的存在,因为这主要是操作系统的事,在应用程序里,只有32位的虚拟地址空间。那么应用程序该如何使用这些大于常规的内存空间呢?一个很常见的方法就是操作系统提供一个窗口映射的方法,把这些额外的内存映射到进程地址空间中来。在Windows下,这种访问内存的操作方式叫做AWE(Address Windowing Extensions);而像Linux等UNIX类操作系统则采用mmap()系统调用来实现。

装载的方式

程序执行时所需要的指令和数据必须在内存中才能够正常运行,最简单的方法就是将程序运行所需要的指令和数据全都装入内存中,这样程序就可以顺利运行,这就是最简单的静态装入的方法。但是很多情况下程序所需要的内存数量大于物理内存的数量,当内存的数量不够时,根本的解决办法就是添加内存。程序运行时是有局部性原理,所以我们可以将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里面,这就是动态装入的基本原理。

覆盖装入(Overlay)和页映射(Paging)是两种很典型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想就是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中。

覆盖装入

覆盖装入:在没有发明虚拟存储之前使用比较广泛,现在已经几乎被淘汰了。覆盖装入的方法把挖掘内存嵌入的任务交给了程序员,程序员在编写程序的时候必须手工将程序分割成若干块,然后编写一个小的辅助代码来管理这些模块何时应该驻留内存而何时应该被替换掉。这个小的辅助代码就是所谓的覆盖管理器(Overlay Manager)。覆盖装入是典型的利用时间换取空间的方法。

如下,一个程序有主模块main,main分别调用A和B模块。但是AB之间不会互相调用,三个模块大小:1024字节,512字节,256字节

不考虑内存对齐,装载地址限制的情况,理论上运行这个程序需要1792个字节的内存

采用覆盖装入办法,这样安排:

由于AB之间相互调用依赖关系,可以把AB在内存中相互覆盖,两个模块共享内存,当main调用A,覆盖管理器保证将A从文件中读入内存,当main调用B,则覆盖管理器将B从文件中读入内存

页映射

页映射:是虚拟存储机制的一部分,它随着虚拟存储的发明而诞生。与覆盖装入的原理相似,页映射也不是一下子就把程序的所有数据和指令都装入内存,而是将内存和所有磁盘中的数据和指令按照”页(Page)”为单位划分成若干个页,以后所有的装载和操作的单位都是页。硬件规定页的大小有4096字节、8192字节、2MB、4MB等。

假设32位机器有16kb内存,每个页大小4096字节,共4个页

设程序所有指令与数据总和32kb,那么程序总共分为8个页,编号为P0-P7,那么16kb内存无法同时将32kb程序装入,那么就按照动态装入原理来进行

图示:

如果程序需要访问P4,那么装载管理器就必须做出选择,它必须放弃目前正在使用的4个内存页中的其中一个来装载P4

从操作系统角度看可执行文件的装载

进程的建立

从操作系统的角度来看,一个进程最关键的特征是它拥有独立的虚拟地址空间,这使得它有别于其它进程。很多时候一个程序被执行同时都伴随着一个新的进程的创建。创建一个进程,然后装载相应的可执行文件并且执行。在有虚拟存储的情况下,上述过程最开始只需要做三件事情:

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

  • 读取可执行文件头,建立虚拟地址空间与可执行文件的映射关系。

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

**(1). 首先是创建虚拟地址空间:**一个虚拟空间由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录(Page Directory)就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页错误的时候再进行设置。

**(2). 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系:**上面那一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该”缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪一个位置。这就是虚拟空间与可执行文件之间的映射关系。从某种角度来看,这一步是整个装载过程中最重要的一步,也是传统意义上”装载”的过程。

假设ELF可执行文件只有一个代码段.text,它的虚拟地址为0x08048000,文件大小为0x000e1
(十进制为225),由于虚拟存储的页映射都是以页为单位,在32位的Intel IA32下一般为4096字节,所以32位ELF的对齐粒度为0x1000(十进制4096),所以该.text段大小不到一个页,一旦该可执行文件被装载,可执行文件与执行该可执行文件进程的虚拟空间的映射关系为:

Linux中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA),将CPU指令寄存器设置成可执行文件入口,启动运行:操作系统通过设置CPU的指令寄存器将控制权交给进程,由此进程开始执行。

页错误

上面步骤执行完以后,可执行文件的真正指令和数据都没有被装入到内存中,操作系统只是通过可执行文件头部的信息建立起可执行文件和进程虚存之间的映射关系而已,上面例子中,程序入口地址为:0x08048000,刚好为.text段的起始地址,当CPU打算执行这个地址的指令时,发现页面0x08048000 - 0x08049000是个空的页面,于是它就认为这是一个页错误,CPU将控制权交给OS,OS有专门的页错误处理例程来处理。

进程虚存空间分布

ELF文件链接视图和执行视图

在一个正常的进程中,可执行文件中包含的往往不止代码段,还有数据段、BSS等,所以映射到进程虚拟空间的往往不止一个段。当段的数量增多时,就会产生空间浪费的问题。ELF文件被映射时,是以系统的页长度作为单位的,那么每个段在映射时的长度应该都是系统页长度的整数倍;如果不是,那么多余部分也将占用一个页。一个ELF文件中往往有十几个段,那么内存空间的浪费是可想而知的。

但OS实际上不关系可执行文件各个段所包含的实际内容,OS只关心一些跟装载相关的问题,最主要的是段的权限

ELF文件中,段的权限往往只有为数不多的几种组合,基本上是三种:

  • (1).以代码段为代表的权限为可读可执行的段;

  • (2).以数据段和BSS段为代表的权限为可读可写的段;

  • (3).以只读数据段为代表的权限为只读的段。

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

比如有两个段分别叫.text & .init,包含的分别是程序的可执行代码和初始化代码,并且权限一样,都是可读可执行,假设.text为4097字节,.init为512字节,两个段分别映射的话就要占3个页面,但如果能够合并成一起映射的话只用占两个页面:

图示:

ELF文件引入了一个概念叫“Segment”,一个“Segment”包含一个或多个属性类似的“Section”,将.text & .init段合并在一起看做是一个“Segment”,那么装载的时候就可以将它们看做一个整体一起映射,也就是映射以后再进程虚存空间中只有一个相应的VMA

“Segment”的概念实际上从装载的角度重新划分了ELF的各个段,在将目标文件链接成可执行文件的时候,链接器会尽量把一样权限属性的段分配在同一空间

例子:

SectionMapping.c

#include<stdlib.h>int main()
{while (1){sleep(1000);}return 0;}

使用静态链接的方式将其编译链接成可执行文件,得到可执行文件SectionMapping.elf,


gcc -static SectionMapping.c -o SectionMapping.elf

再使用readelf查看:


readelf -S SectionMapping.elfThere are 33 section headers, starting at offset 0xcdbd0:节头:[号] 名称              类型             地址              偏移量大小              全体大小          旗标   链接   信息   对齐[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .note.ABI-tag     NOTE             0000000000400190  000001900000000000000020  0000000000000000   A       0     0     4[ 2] .note.gnu.build-i NOTE             00000000004001b0  000001b00000000000000024  0000000000000000   A       0     0     4
readelf:警告: [ 3]: Link field (0) should index a symtab section.[ 3] .rela.plt         RELA             00000000004001d8  000001d80000000000000228  0000000000000018  AI       0    20     8[ 4] .init             PROGBITS         0000000000400400  000004000000000000000017  0000000000000000  AX       0     0     4[ 5] .plt              PROGBITS         0000000000400418  0000041800000000000000b8  0000000000000000  AX       0     0     8[ 6] .text             PROGBITS         00000000004004d0  000004d0000000000008f590  0000000000000000  AX       0     0     16[ 7] __libc_freeres_fn PROGBITS         000000000048fa60  0008fa600000000000001523  0000000000000000  AX       0     0     16[ 8] __libc_thread_fre PROGBITS         0000000000490f90  00090f90000000000000108f  0000000000000000  AX       0     0     16[ 9] .fini             PROGBITS         0000000000492020  000920200000000000000009  0000000000000000  AX       0     0     4[10] .rodata           PROGBITS         0000000000492040  00092040000000000001924c  0000000000000000   A       0     0     32[11] .stapsdt.base     PROGBITS         00000000004ab28c  000ab28c0000000000000001  0000000000000000   A       0     0     1[12] .eh_frame         PROGBITS         00000000004ab290  000ab290000000000000a530  0000000000000000   A       0     0     8[13] .gcc_except_table PROGBITS         00000000004b57c0  000b57c0000000000000008e  0000000000000000   A       0     0     1[14] .tdata            PROGBITS         00000000006b6120  000b61200000000000000020  0000000000000000 WAT       0     0     8[15] .tbss             NOBITS           00000000006b6140  000b61400000000000000040  0000000000000000 WAT       0     0     8[16] .init_array       INIT_ARRAY       00000000006b6140  000b61400000000000000010  0000000000000008  WA       0     0     8[17] .fini_array       FINI_ARRAY       00000000006b6150  000b61500000000000000010  0000000000000008  WA       0     0     8[18] .data.rel.ro      PROGBITS         00000000006b6160  000b61600000000000002d94  0000000000000000  WA       0     0     32[19] .got              PROGBITS         00000000006b8ef8  000b8ef800000000000000f8  0000000000000000  WA       0     0     8[20] .got.plt          PROGBITS         00000000006b9000  000b900000000000000000d0  0000000000000008  WA       0     0     8[21] .data             PROGBITS         00000000006b90e0  000b90e00000000000001af0  0000000000000000  WA       0     0     32[22] __libc_subfreeres PROGBITS         00000000006babd0  000babd00000000000000048  0000000000000000  WA       0     0     8[23] __libc_IO_vtables PROGBITS         00000000006bac20  000bac2000000000000006a8  0000000000000000  WA       0     0     32[24] __libc_atexit     PROGBITS         00000000006bb2c8  000bb2c80000000000000008  0000000000000000  WA       0     0     8[25] __libc_thread_sub PROGBITS         00000000006bb2d0  000bb2d00000000000000008  0000000000000000  WA       0     0     8[26] .bss              NOBITS           00000000006bb2e0  000bb2d800000000000016f8  0000000000000000  WA       0     0     32[27] __libc_freeres_pt NOBITS           00000000006bc9d8  000bb2d80000000000000028  0000000000000000  WA       0     0     8[28] .comment          PROGBITS         0000000000000000  000bb2d80000000000000029  0000000000000001  MS       0     0     1[29] .note.stapsdt     NOTE             0000000000000000  000bb30400000000000014cc  0000000000000000           0     0     4[30] .symtab           SYMTAB           0000000000000000  000bc7d0000000000000a998  0000000000000018          31   678     8[31] .strtab           STRTAB           0000000000000000  000c716800000000000068ed  0000000000000000           0     0     1[32] .shstrtab         STRTAB           0000000000000000  000cda550000000000000176  0000000000000000           0     0     1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),l (large), p (processor specific)

看到,一共33个段(Section)

可以使用readelf来查看ELF的“Segment”

描述“Segment”的结构叫做程序头


readelf -l SectionMapping.elfElf 文件类型为 EXEC (可执行文件)
Entry point 0x400a30
There are 6 program headers, starting at offset 64程序头:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignLOAD           0x0000000000000000 0x0000000000400000 0x00000000004000000x00000000000b584e 0x00000000000b584e  R E    0x200000LOAD           0x00000000000b6120 0x00000000006b6120 0x00000000006b61200x00000000000051b8 0x00000000000068e0  RW     0x200000NOTE           0x0000000000000190 0x0000000000400190 0x00000000004001900x0000000000000044 0x0000000000000044  R      0x4TLS            0x00000000000b6120 0x00000000006b6120 0x00000000006b61200x0000000000000020 0x0000000000000060  R      0x8GNU_STACK      0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000  RW     0x10GNU_RELRO      0x00000000000b6120 0x00000000006b6120 0x00000000006b61200x0000000000002ee0 0x0000000000002ee0  R      0x1Section to Segment mapping:段节...00     .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata .stapsdt.base .eh_frame .gcc_except_table 01     .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data __libc_subfreeres __libc_IO_vtables __libc_atexit __libc_thread_subfreeres .bss __libc_freeres_ptrs 02     .note.ABI-tag .note.gnu.build-id 03     .tdata .tbss 04     05     .tdata .init_array .fini_array .data.rel.ro .got 

可以看到有6个Segment,从装载角度,只需要关注两个“LOAD”类型的Segment,因为只有它是需要被映射的

上图中,SectionMapping.elf被重新划分成三个部分,有一些段被归入可读可执行,统一映射到一个VMA0,另外一部分可读可写的,被映射到VMA1,还有部分段在程序装载时没有被映射的,是一些包含调试信息和字符串表等段,这些段在程序执行时没有用,所以不需要被映射,所有相同属性的Section被归类到一个Segment,并且映射到同一个VMA。

所以总的来说,“Segment” & “Section”是从不同角度来划分同一个ELF文件,这个在ELF中被称为不同的视图,从“Section”角度来看ELF文件就是链接视图,从“Segment”角度来看就是执行视图

堆和栈

在操作系统中,VMA除了被用来映射可执行文件的各个Segment之外,还有其他作用,操作系统通过使用VMA来对进程的地址空间进行管理,进程在执行的时候需要用到栈和堆,事实上它们在进程的虚拟空间中的表现也是以VMA形式存在的,通过查看/proc来查看进程的虚拟空间分布


$ ./SectionMapping.elf &[1] 2447$ cat /proc/2447/maps00400000-004b6000 r-xp 00000000 08:01 1180179                            /home/mrlin/桌面/project/p5/SectionMapping.elf
006b6000-006bc000 rw-p 000b6000 08:01 1180179                            /home/mrlin/桌面/project/p5/SectionMapping.elf
006bc000-006bd000 rw-p 00000000 00:00 0
01695000-016b8000 rw-p 00000000 00:00 0                                  [heap]
7ffd1367e000-7ffd1369f000 rw-p 00000000 00:00 0                          [stack]
7ffd13701000-7ffd13704000 r--p 00000000 00:00 0                          [vvar]
7ffd13704000-7ffd13706000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

第一列是VMA的地址范围,第二列是VMA的权限(r-可读,w-可写,x-可执行,p-私有,s-共享),第三列是偏移,表示VMA对应的Segment在映像文件中的偏移,第四列表示映像文件所在设备的主设备号与次设备号,第五列表示映像文件的节点号,最后一列是映像文件的路径

上面输出中,只有前两个是映射到可执行文件中的两个Segment,剩余三个段的文件所在设备主设备号与次设备号及文件节点号都是0,表示它们没有映射到文件中,这种VMA叫做匿名虚拟内存区域

有两个区域为堆和栈,这两个VMA几乎在所有的进程中存在,这里面有个VMA叫[vdso],它是一个内核的模块,进程可以通过访问这个VMA来跟内核进行通信

进程虚拟地址空间:

操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间,基本原则是将相同权限属性的,有相同映像文件的映射成一个VMA,一个进程基本可以分为如下几种VMA区域:

  • 代码VMA,权限只读,可执行,有映像文件

  • 数据VMA,权限可读写,可执行,有映像文件

  • 堆VMA,权限可读写,可执行,无映像文件,匿名,可向上扩展

  • 栈VMA,权限可读写,不可执行,无映像文件,匿名,可向下扩展

Linux内核装载ELF过程

当我们在Linux系统的bash下输入一个命令执行某个ELF程序时:

  • 首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。execve()系统调用被声明在/usr/include/unistd.h中。Glibc对execve()系统调用进行了包装,提供了execl()、execlp()、execle()、execv()和execvp()等5个不同形式的exec系列API,它们只是在调用的参数形式上有所区别,但最终都会调用到execve()这个系统中。

在进入execve()系统调用之后,Linux内核就开始进行真正的装载工作:

  • 在内核中,execve()系统调用相应的入口是sys_execve(),sys_execve()进行一些参数的检查复制之后,调用do_execve()。do_execve()会首先查找被执行的文件,如果找到文件,则读取文件的前128个字节,目的是判断文件的格式,每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,常常被称做魔数(Magic Number),通过对魔数的判断可以确定文件的格式和类型。比如ELF的可执行文件格式的头4个字节为0x7F、’E’、’L’、’F’;而Java的可执行文件格式的头4个字节为’c’、’a’、’f’、’e’;如果被执行的是Shell脚本或perl、python等这种解释型语言的脚本,那么它的第一行往往是”#!/bin/sh”或”#!/usr/bin/perl”或”#!/usr/bin/python”,这时候前两个字节’#’和”!”就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序的路径。

当do_execve()读取了这128个字节的文件头部以后,然后调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理过程。Linux中所有被支持的可执行文件格式都有相应的装载处理过程,search_binary_handle()会通过判断文件头部的魔数确定文件的格式,并且调用相应的装载处理过程。比如ELF可执行文件的装载处理过程叫做load_elf_binary();a.out可执行文件的装载处理过程叫做load_aout_binary();而装载可执行脚本程序的处理过程叫做load_script()。

load_elf_binary()的主要步骤是:

  • (1). 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量。

  • (2). 寻找动态链接的”.interp”段,设置动态链接器路径。

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

  • (4). 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址。

  • (5). 将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点就是动态链接器。

当load_elf_binary()执行完毕,返回至do_execve()再返回sys_execve()时,上面的第5步中已经把系统调用的返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件加载完成。

可执行文件的装载与进程相关推荐

  1. 【《程序员的自我修养---链接装载于库》读书笔记】可执行文件的装载与进程

    系列文章目录 [<程序员的自我修养-链接装载于库>读书笔记]初探ELF [<程序员的自我修养-链接装载于库>读书笔记]windows PE/COFF [<程序员的自我修养 ...

  2. 程序员的自我修养--链接、装载与库笔记:可执行文件的装载与进程

    可执行文件只有装载到内存以后才能被CPU执行. 1. 进程虚拟地址空间 程序和进程有什么区别:程序(或者狭义上讲可执行文件)是一个静态的概念,它就是一些预先编译好的指令和数据集合的一个文件:进程则是一 ...

  3. 程序员的自我修养六可执行文件的装载与进程

    可执行文件只有装载到内存以后才能被CPU执行 6.1进程虚拟地址空间 程序和进程的区别: 程序:是一个静态概念,它就是一些预先编译好的指令和数据集合的一个文件. 进程:是一个动态概念,它是程序运行时的 ...

  4. 程序员自我修养阅读笔记——可执行文件的装载过程

    1 可执行文件的装载过程 1.1 进程虚拟地址空间   一个可执行文件被装载到内存变成程序后(进程和程序的区别在于一个是静态的一个是动态的,程序就是菜谱,进程就是厨师参考菜谱做菜的过程),拥有自己独立 ...

  5. Linux可执行文件如何装载进虚拟内存

    开篇先抛出几个问题,之后逐个击破: 什么是进程的虚拟地址空间?为什么进程要有自己的虚拟地址空间,这样做有什么好处? 我们都听说过页映射,什么是页映射,操作系统为什么要以页映射方式将程序映射到进程地址空 ...

  6. 【读书笔记】【程序员的自我修养 -- 链接、装载与库(二)】进程虚拟地址空间、装载与动态链接、GOT、全局符号表、共享库的组织、DLL、C++与动态链接

    文章目录 前言 介绍 可执行文件的装载与进程 进程虚拟地址空间 装载方式 操作系统对可执行文件的装载 进程虚存空间分布 ELF文件的链接视图和执行视图 堆和栈 Linux 内核装载ELF & ...

  7. 腾讯朋友力荐书籍:程序员的自我修养:链接、装载与库

    后台开发需要学习底层知识,只有底层知识掌握了,学一些中间件是信手捏来,中间件也是跑在底层的操作系统上.<<程序员的自我修养:链接.装载与库>>对学习底层知识非常有帮助,腾讯的朋 ...

  8. ELF文件装载链接过程及hook原理

    ELF文件格式解析 可执行和可链接格式(Executable and Linkable Format,缩写为ELF),常被称为ELF格式,在计算机科学中,是一种用于执行档.目的档.共享库和核心转储的标 ...

  9. 程序员的自我修养——链接、装载与库

    1.温故而知新--操作系统概念 北桥:连接高速芯片 系统调用接口:以软件中断的方式提供,如Linux使用0x80号中断作为系统调用接口 多任务系统:进程隔离 设备驱动 直接使用物理内存的弊端 地址空间 ...

最新文章

  1. input输入框为number类型时,去掉上下小箭头
  2. 微软亚洲研究院副院长刘铁岩:AI如何助力节能减排?
  3. NetDevOps — netmiko
  4. origin数据平滑_独门绝技!Origin挑战绘制细胞分化轨迹热图
  5. Python 技术篇-全局与当前socket超时连接时间设置方法实例演示,查看socket超时连接时间
  6. 卡顿人生,如何拯救?
  7. bzoj 3399: [Usaco2009 Mar]Sand Castle城堡
  8. 火山PC大漠插件源码开源学习--木塔老师
  9. EPSON ME office 700FW打印机废墨收集垫已到使用寿命解决办法
  10. Python opencv局部直方图均衡增强
  11. 思科ccnp认证工程师必看
  12. 合并果子问题(贪心算法)
  13. 渗透H5棋牌游戏-棋牌游戏开发
  14. C++程序设计基础之(第三章)函数
  15. OLTP vs OLAP 区别和联系
  16. 快手如何直播引流?快手直播推广方法分享
  17. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现
  18. L1-3 打折 (5 分)——2018
  19. 微信小程序 滚动列表(无限滚动)
  20. HAUT OJ 1011:圆柱体表面积

热门文章

  1. BB10请求接收NFC事件
  2. RFID技术在快餐行业运营管理中的应用
  3. SN与TI,LS、HC与HCT的区别
  4. 电池比较的黑莓,Android,苹果和Windows智能手机
  5. MySql 获取表的字段名
  6. Python3-word文档操作(二):利用python获取word文档的内容
  7. windows 10关闭IIS服务器
  8. 100张经典信息可视化图表,让你脑洞大开
  9. Intellij IDEA 接口实现的快捷键
  10. windows7安装配置jdk1.8