关于地址
程序没有加载前的地址(程序)
程序编译好之后, 内部有地址的概念吗?
有的, 当程序在编译好后, 内部的诸如变量, 函数等等都变成的地址;
而这个地址其实就是虚拟地址, 但是为了做区分我们把他叫做逻辑地址
程序加载后的地址(进程)
我们程序在加载进内存后, 他的内部依旧是使用的虚拟地址, 他的数据在物理内存上可以随意存储(因为有页表), 程序的每一条代码同样也是要占据物理内存的。 于是我们就有了这么一张图
那么现在问题来了, 我们该如何去执行第一条指令呢?
之前我们在将进程替换的时候提到过, 程序在编译的时候, 可执行程序的开头会有一个表头, 这个表头里就记录了一个entry:入口地址
, 这个入口地址是在程序编译的时候形成的所以他是逻辑地址
CPU内有一个寄存器叫EIP/PC
在进程创建的时候还会形成test_struct
也就是PCB, 这里面有两个属性 一个是pwd
他指向了当前进程的工作目录 还有一个是exe
他指向的是该进程对应的可执行程序。
我们在执行这个程序的时候, 做的第一件事就是将这个程序的entry地址
加载到CPU的PC中, 然后CPU就直接去代码段开始执行
然后通过页表将虚拟地址转化为物理地址。 由于我们是先加载的内核数据结构, 还没有加载该进程的代码, 所以此时就会发生缺页中断
此时再去加载该进程的代码, 加载完后, 我们的程序就具备了物理地址, 此时就将该物理地址填入对应的页表中,然后我们就继续执行我们的代码, 我们的CPU是可以识别代码的长度
的, 所以每执行完一个代码后, 就直接在PC上加上刚刚执行的代码的长度后就可以直接指向下一个代码的地址
,然后再继续通过页表去访问物理地址, 当我们读取到call
指令的时候, 发现他是一个函数调用
这个函数调用带的数据是一个虚拟地址, 我们在执行这个指令的时候, 就可以直接在虚拟地址空间处找到该地址, 然后根据页表去访问对应的物理地址
我们可以发现我们在代码中使用的所有地址全部都是虚拟地址
我们的编译器在编译的时候就考虑好了虚拟地址空间
, 进程在设计的时候也考虑好了虚拟地址空间
, 两者之间一结合, 这就是编译器和操作系统协同的最重要的表现之一
动态库的地址
库内部的函数的编址并不是按照虚拟地址空间的绝对地址来编址的, 而是以相对于库的位置的偏移量来编址。
编址完后库加载到内存中, 通过页表在共享区的随便一个地址
建立映射, 我们就只需要系统记住该库在虚拟地址中的起始地址在哪里, 未来如果我们调用某个方法, 当系统识别到他是一个库函数调用后, 就去找对应的库的地址在哪, 在根据这个库的地址通过偏移量去找该函数调用。
而这个地址就是靠我们gcc的-fPIC
形成的