GO笔记(一)之程序到进程的流程

2022-07-18  本文已影响0人  温岭夹糕

环境

centOS7

语言

c

1.前言(关于进程)

我们知道进程是指一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程,是操作系统处于执行状态的抽象,那么我们可以狭隘的概括为

程序=文件
进程=执行中的程序=程序+执行状态

程序仅仅是一堆代码,给人看的文本,机器看不懂(直看得懂01),我们想要将程序附加运行状态,就需要将它转为二进制代码,这个过程就是编译,编译完成后就可以运行创建进程

1.1编译过程

以c代码test.c为例

#include <stdio.h>

int main(int argc, char const *argv[])
{

    printf("hello 程序猿编码\n");

    return 0;
}

我们可以直接使用一个语句gcc -o test.c test将其编译,但其执行过程实际经历了四步

编译过程

-预处理(-o选项是重命名)
处理宏定义、文件包含和条件编译内容(特点是#开头,命令独占一行,不以" ; "结束)

gcc -E test.c -o test.i

-编译
将预处理文件转为特定的汇编代码(例如转为指令 mv ax 1)并不是转为二进制

gcc -S test.i -o test.s

-汇编
转为机器码,二进制,此时机器能读懂

gcc -c test.s -o test.o

-链接
使用连接器将目标文件和其他目标文件库文件等链接生成可执行文件

gcc test.o -o test

其中最重要的两步是汇编和链接,因为这里是单文件,我们再举一个coding来详细分析这两步(代码参考趣谈linux操作系统)
process.c


    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    
    extern int create_process (char* program, char** arg_list);
    
    
    int create_process (char* program, char** arg_list)
    {
        pid_t child_pid;
        child_pid = fork ();
        if (child_pid != 0)
            return child_pid;
        else {
            execvp (program, arg_list);
            abort ();
        }
   }

一个创建进程(fork显示创建子进程)的函数,并执行任务(execvp执行传入的指令)
createprocess.c


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

extern int create_process (char* program, char** arg_list);

int main ()
{
    char* arg_list[] = {
        "ls",
        "-l",
        "/etc/yum.repos.d/",
        NULL
    };
    create_process ("ls", arg_list);
    return 0;
}

负责调用,形成了一个引用关系
编译程序(只编译不链接),生成对应的.o文件

gcc -c -fPIC process.c
gcc -c -fPIC createprocess.c

从上头了解到这个文件是经过头文件的预处理和源文件结合生成的二进制文件,在Linux下,二进制文件也要有严格的格式,我们称为ELF(Executeable and Linkable Format 可执行与可链接格式)

1.2ELF文件格式类型

ELF文件是一种用于二进制文件、可执行文件、目标代码、共享库和core转存格式文件。是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。

elf文件有四种类型其中上条指令为第一种,格式为 elf格式

大致分为
1.ELF头(ELF Header),描述整个文件,由内核定义

2.节(Section,如.text,.data),存放代码和一些全局变量和静态变量等
只有全家变量,没有局部变量是因为局部变量是放在栈中,运行过程随时分配释放的,现在还没运行
3.节头表(section header table),存放节的元数据(位置,多少个节等),即节的目录

利用工具readelf查看编译文件 elfheader信息

readelf -h process.o
readelf -h

type显示是一个可重定位文件(为什么叫可重定位?因为加载到内存时,代码和变量都要被加载到一定位置,我们还无法确定内存中的具体位置和调用具体位置,到时候还要再修改,必须是要可重新定位的)

readelf -S process.o
查看section header信息

利用objdump查看反汇编文件信息(因为此时经过汇编到二进制的转换,这里不再截图,太长)

objdump -D process.o
objdump -h process.o
-h查看section header信息

其中VMA(Virtual Memory Address虚拟内存地址)和LMA(Load Memory Address加载内存地址)都为0相对地址,这也是一方面说明是可重定位(节rel.text和rel.data和重定位有关)

1.3链接

主程序的main函数中调用create_process函数,那它是如何知道这块砖的位置?我们知道create_process在另外一个编译文件里面,main函数根本不知道被调用的位置,所以他需要在rel.text里面标注,这个函数需要重定位。让这个函数作为库文件被重用,一种办法是静态链接.a文件

ar cr libstaticprocess.a process.o
gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
#实际情况有多个.o文件需要链接,当用这个静态库时,会把.o文件取出来链接
此时用readelf再看时staticprocess是一个可执行文件exec,这个文件是elf的第二种格式 elf格式

此时的节section是多个.o文件合并过的,这个文件可以马上被加载到内存里面(执行该文件),将小的 section 合成了大的段 segment,并且在最前面加一个段头表(Segment Header Table)
这个方法的缺点:
因为要链接时会将整个.o文件提取出来加载,因此被多个程序使用就会在内存中多份,静态链接库更新,二进制文件也要重新编译

因此引出第二种方法动态链接

gcc -shared -fPIC -o libdynamicprocess.so process.o
gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
export LD_LIBRARY_PATH=.
这是elf的第三种格式(共享对象文件),多了一个.interp的segment,里面是id-linux.so(动态连接器,运行时的链接动作都是他完成的)另外,ELF 文件中还多了两个 section,一个是.plt,过程链接表(Procedure Linkage Table,PLT),一个是.got.plt,全局偏移量表(Global Offset Table,GOT),链接过程为 动态链接程序.png

总结一下就是PLT是消息队列,GOT负责寻找职责拆分

1.4加载ELF文件到内存

通过系统调用exec调用load_elf_binary

参考

1.趣谈linux系统
2.gcc预编译过程
3.elf文件格式
4.LMA和VMA总结
5.readelf命令使用
6.objdum

上一篇下一篇

猜你喜欢

热点阅读