Loading... > 本篇是《深入理解计算机系统》的笔记。关于这本书前面的程序结构、硬件电路层我没怎么看,在计组里基本学过,没有深入研究。主要看了后面的Linux系统部分,顺带复习操作系统了。 ## 链接——位置无关代码PIC 为了使多个进程可以共享一个共享模块的副本,GNU使用加载而无需重定位的代码,即GCC使用`-fpic`选项生成PIC代码。 ### PIC数据引用 无论我们在内存的何处加载目标模块,数据段和代码段之间的距离总是保持不变。因此,代码段中任何指令和数据段中的任何变量之间的距离都是一个常量,与代码段和数据段在内存中的绝对位置无关。 编译器在代码段在骑士部分创建GOT表,所有被引用的函数和变量都有一个条目,使得他包含目标的正确的绝对地址。 所以在进行PIC数据引用的时候,只要通过GOT就能加载全局变量的地址,而编译器可以通过相对引用来访问GOT,从而实现了对数据的相对访问。 ### PIC函数调用 为了使进程在运行时调用由共享库定义的函数,由于编译器没有办法预测函数的运行时地址,所以它的共享模块可以加载到任何地址,GNU编译器使用了延时绑定技术,将过程地址的绑定推迟到第一次调用的时候进行。使用两个表,一个GOT,一个是PLT。GOT是数据段的一部分,PLT是代码段的。 PLT是个数组,其中0索引是一个特殊条目,负责跳转到动态连接器中。和PLT一起使用时,GOT0索引和1索引包含动态连接器在解析函数地址所需要的地址。GOT2索引是动态链接器在`ld-linux.so`的入口点。其余的对于被调函数,不过地址需要在运行的时候再解析,每个GOT项对应一个PLT项。初始时,GOT的项指向对应PLT项的第二条指令(一般是push 偏移量)。 ### **延时绑定** - 第一次调用,代码段通过call指令跳转到PLT表的某个表项。 - 执行PLT表项中第一条指令,根据GOT表项的数据进行间接跳转。而此时GOT表中存放的是对应的PLT项的下一条指令,所以又跳回了PLT表,相当于直接执行下一条指令。 - 执行PLT表项中第二条指令,进行push压入偏移量,偏移量表示函数对应在GOT表中的偏移。 - 执行PLT表项中第三条指令,跳转到PLT0索引处,将GOT1压栈,通过GOT2间接跳转到动态连接器中。动态链接器根据两个参数来决定函数的相对位置,并且重写GOT表项,同时执行函数。 - 第二次调用,同样是代码段通过call指令跳转到PLT表的某个表项。 - 执行PLT表项中第一条指令,根据GOT表项的数据进行间接跳转。但是和第一次不一样的是,此时的GOT表项中的数据是所执行函数的地址。所以直接执行函数。 ## 进程 关于进程调度这部分就不说了。 ### 进程控制 每个进程有唯一的进程ID,getpid函数可以返回调用进程的PID。getppid返回他的父进程的PID。 ```c #include <sys/types.h> #include <unistd.h> //返回类型`pid_t`是int型的。 pid_t getpid(void); pid_t getppid(void); ``` #### 进程的退出和创建 ```c #include <stdlib.h> //进程退出。 void exit(int status); ``` ### 子进程 父进程可以通过`fork`来创建子进程。 ```c #include <sys/types.h> #include <unistd.h> //子进程返回0,父进程返回子进程的PID,错误返回-1。 pid_t fork(void); ``` 子进程得到的虚拟地址控件和父进程是一样的,包括代码段和数据段,但是pid和父进程不同。通过fork函数的返回值可以判断当前是在父进程还是在子进程中。`pid_t==0`是子进程,但子进程的PID不为0。需要注意的是,父子进程是并发执行的,两个进程执行的速度不确定,但是父子进程是两个独立的进程,有自己的私有地址空间,变量保持独立。 #### 进程休眠 ```c #include <unistd.h> unsigned int sleep(unsigned int secs); //设置休眠时间,返回还要休眠的时间 int pause(void); //总是返回-1,收到型号后恢复。 ``` #### 进程加载 ```c #include <unistd.h> int execve(const char *filename, const char **argv, const char **envp); ``` 其中argv是参数列表,envp是环境变量列表。当main开始执行的时候,用户栈栈底往上依次是envp、argr、argc,然后是libc_start_main的栈帧,再往上是之后的main栈帧。 关于fork和execve函数的区别。fork是在新的子进程中运行相同的程序,是一个副本。而execve在当前进程上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是他不创建新的进程,pid是一样的。 #### 进程组 ```c #include <unistd.h> pid_t getpgrp(void); //返回当初进程的组进程id int setpgid(pid_t pid, pid_t pgid); //改变自己或其他进程的进程组 ``` ## 内存管理 [堆和内存管理](http://cjynet.top/2018/03/07/RCE%20with%20IDAPro/IDApro%E6%9D%83%E5%A8%81%E4%BB%A3%E7%A0%81%E7%A0%B4%E8%A7%A3%E6%8F%AD%E7%A7%98%E7%AC%94%E8%AE%B0%E4%B8%80/) [地址转换](http://cjynet.top/2017/08/06/windows/Win32ASM-1/) ``` ``` Last modification:January 16th, 2021 at 01:33 pm © 允许规范转载 Support 确定不打赏一下支持博主吗 ×Close Appreciate the author Sweeping payments Pay by AliPay