Linux进程地址空间第二讲

文章探讨了程序在编译前后以及加载过程中的地址概念,涉及逻辑地址、虚拟地址、入口地址、页表等。重点讲述了如何通过入口地址和页表实现代码执行,以及动态库如何通过偏移量编址并进行函数调用的过程。
摘要由CSDN通过智能技术生成

在这里插入图片描述

关于地址

程序没有加载前的地址(程序)

程序编译好之后, 内部有地址的概念吗?
有的, 当程序在编译好后, 内部的诸如变量, 函数等等都变成的地址;
在这里插入图片描述
在这里插入图片描述
而这个地址其实就是虚拟地址, 但是为了做区分我们把他叫做逻辑地址

程序加载后的地址(进程)

我们程序在加载进内存后, 他的内部依旧是使用的虚拟地址, 他的数据在物理内存上可以随意存储(因为有页表), 程序的每一条代码同样也是要占据物理内存的。 于是我们就有了这么一张图

在这里插入图片描述
那么现在问题来了, 我们该如何去执行第一条指令呢?

之前我们在将进程替换的时候提到过, 程序在编译的时候, 可执行程序的开头会有一个表头, 这个表头里就记录了一个entry:入口地址, 这个入口地址是在程序编译的时候形成的所以他是逻辑地址
CPU内有一个寄存器叫EIP/PC 在进程创建的时候还会形成test_struct也就是PCB, 这里面有两个属性 一个是pwd他指向了当前进程的工作目录 还有一个是exe 他指向的是该进程对应的可执行程序。

我们在执行这个程序的时候, 做的第一件事就是将这个程序的entry地址加载到CPU的PC中, 然后CPU就直接去代码段开始执行
在这里插入图片描述
然后通过页表将虚拟地址转化为物理地址。 由于我们是先加载的内核数据结构, 还没有加载该进程的代码, 所以此时就会发生缺页中断此时再去加载该进程的代码, 加载完后, 我们的程序就具备了物理地址, 此时就将该物理地址填入对应的页表中,然后我们就继续执行我们的代码, 我们的CPU是可以识别代码的长度的, 所以每执行完一个代码后, 就直接在PC上加上刚刚执行的代码的长度后就可以直接指向下一个代码的地址,然后再继续通过页表去访问物理地址, 当我们读取到call指令的时候, 发现他是一个函数调用
在这里插入图片描述
这个函数调用带的数据是一个虚拟地址, 我们在执行这个指令的时候, 就可以直接在虚拟地址空间处找到该地址, 然后根据页表去访问对应的物理地址

我们可以发现我们在代码中使用的所有地址全部都是虚拟地址

我们的编译器在编译的时候就考虑好了虚拟地址空间, 进程在设计的时候也考虑好了虚拟地址空间, 两者之间一结合, 这就是编译器和操作系统协同的最重要的表现之一
在这里插入图片描述

动态库的地址

在这里插入图片描述

库内部的函数的编址并不是按照虚拟地址空间的绝对地址来编址的, 而是以相对于库的位置的偏移量来编址。
在这里插入图片描述
编址完后库加载到内存中, 通过页表在共享区的随便一个地址建立映射, 我们就只需要系统记住该库在虚拟地址中的起始地址在哪里, 未来如果我们调用某个方法, 当系统识别到他是一个库函数调用后, 就去找对应的库的地址在哪, 在根据这个库的地址通过偏移量去找该函数调用。

而这个地址就是靠我们gcc的-fPIC形成的
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值