色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

你知道你寫的代碼是怎樣跑起來的嗎(上)

jf_78858299 ? 來源:開發內功修煉 ? 作者:開發內功修煉 ? 2023-05-05 14:36 ? 次閱讀

今天我們來思考一個簡單的問題,一個程序是如何在 Linux 上執行起來的?

我們就拿全宇宙最簡單的 Hello World 程序來舉例。

#include 
int main()
{
   printf("Hello, World!\\n");
   return 0;
}

我們在寫完代碼后,進行簡單的編譯,然后在 shell 命令行下就可以把它啟動起來。

# gcc main.c -o helloworld
# ./helloworld
Hello, World!

那么在編譯啟動運行的過程中都發生了哪些事情了呢?今天就讓我們來深入地了解一下。

一、理解可執行文件格式

源代碼在編譯后會生成一個可執行程序文件,我們先來了解一下編譯后的二進制文件是什么樣子的。

我們首先使用 file 命令查看一下這個文件的格式。

# file helloworld
helloworld: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), ...

file 命令給出了這個二進制文件的概要信息,其中 ELF 64-bit LSB executable 表示這個文件是一個 ELF 格式的 64 位的可執行文件。x86-64 表示該可執行文件支持的 cpu 架構。

LSB 的全稱是 Linux Standard Base,是 Linux 標準規范。其目的是制定一系列標準來增強 Linux 發行版的兼容性。

ELF 的全稱是 Executable Linkable Format,是一種二進制文件格式。Linux 下的目標文件、可執行文件和 CoreDump 都按照該格式進行存儲。

ELF 文件由四部分組成,分別是 ELF 文件頭 (ELF header)、Program header table、Section 和 Section header table。

圖片

接下來我們分幾個小節挨個介紹一下。

1.1 ELF 文件頭

ELF 文件頭記錄了整個文件的屬性信息。原始二進制非常不便于觀察。不過我們有趁手的工具 - readelf,這個工具可以幫我們查看 ELF 文件中的各種信息。

我們先來看一下編譯出來的可執行文件的 ELF 文件頭,使用 --file-header (-h) 選項即可查看。

# readelf --file-header helloworld
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401040
  Start of program headers:          64 (bytes into file)
  Start of section headers:          23264 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         11
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

ELF 文件頭包含了當前可執行文件的概要信息,我把其中關鍵的幾個拿出來給大家解釋一下。

  • Magic:一串特殊的識別碼,主要用于外部程序快速地對這個文件進行識別,快速地判斷文件類型是不是 ELF
  • Class:表示這是 ELF64 文件
  • Type:為 EXEC 表示是可執行文件,其它文件類型還有 REL(可重定位的目標文件)、DYN(動態鏈接庫)、CORE(系統調試 coredump文件)
  • Entry point address:程序入口地址,這里顯示入口在 0x401040 位置處
  • Size of this header:ELF 文件頭的大小,這里顯示是占用了 64 字節

以上幾個字段是 ELF 頭中對 ELF 的整體描述。另外 ELF 頭中還有關于 program headers 和 section headers 的描述信息。

  • Start of program headers:表示 Program header 的位置
  • Size of program headers:每一個 Program header 大小
  • Number of program headers:總共有多少個 Program header
  • Start of section headers: 表示 Section header 的開始位置。
  • Size of section headers:每一個 Section header 的大小
  • Number of section headers: 總共有多少個 Section header

1.2 Program Header Table

在介紹 Program Header Table 之前我們展開介紹一下 ELF 文件中一對兒相近的概念 - Segment 和 Section。

ELF 文件內部最重要的組成單位是一個一個的 Section。每一個 Section 都是由編譯鏈接器生成的,都有不同的用途。例如編譯器會將我們寫的代碼編譯后放到 .text Section 中,將全局變量放到 .data 或者是 .bss Section中。

但是對于操作系統來說,它不關注具體的 Section 是啥,它只關注這塊內容應該以何種權限加載到內存中,例如讀,寫,執行等權限屬性。因此相同權限的 Section 可以放在一起組成 Segment,以方便操作系統更快速地加載。

由于 Segment 和 Section 翻譯成中文的話,意思太接近了,非常不利于理解。所以本文中我就直接使用 Segment 和 Section 原汁原味的概念,而不是將它們翻譯成段或者是節,這樣太容易讓人混淆了。

Program headers table 就是作為所有 Segments 的頭信息,用來描述所有的 Segments 的。

使用 readelf 工具的 --program-headers(-l)選項可以解析查看到這塊區域里存儲的內容。

# readelf --program-headers helloworld
Elf file type is EXEC (Executable file)
Entry point 0x401040
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
     FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
     0x0000000000000268 0x0000000000000268  R      0x8
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
     0x000000000000001c 0x000000000000001c  R      0x1
   [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
     0x0000000000000438 0x0000000000000438  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
     0x00000000000001c5 0x00000000000001c5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000402000 0x0000000000402000
     0x0000000000000138 0x0000000000000138  R      0x1000
  LOAD           0x0000000000002e10 0x0000000000403e10 0x0000000000403e10
     0x0000000000000220 0x0000000000000228  RW     0x1000
  DYNAMIC        0x0000000000002e20 0x0000000000403e20 0x0000000000403e20
     0x00000000000001d0 0x00000000000001d0  RW     0x8
  NOTE           0x00000000000002c4 0x00000000004002c4 0x00000000004002c4
     0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x0000000000002014 0x0000000000402014 0x0000000000402014
     0x000000000000003c 0x000000000000003c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002e10 0x0000000000403e10 0x0000000000403e10
     0x00000000000001f0 0x00000000000001f0  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   06     .dynamic 
   07     .note.gnu.build-id .note.ABI-tag 
   08     .eh_frame_hdr 
   09     
   10     .init_array .fini_array .dynamic .got

上面的結果顯示總共有 11 個 program headers。

對于每一個段,輸出了 Offset、VirtAddr 等描述當前段的信息。Offset 表示當前段在二進制文件中的開始位置,FileSiz 表示當前段的大小。Flag 表示當前的段的權限類型, R 表示可都、E 表示可執行、W 表示可寫。

在最下面,還把每個段是由哪幾個 Section 組成的給展示了出來,比如 03 號段是由“.init .plt .text .fini” 四個 Section 組成的。

1.3 Section Header Table

和 Program Header Table 不一樣的是,Section header table 直接描述每一個 Section。這二者描述的其實都是各種 Section ,只不過目的不同,一個針對加載,一個針對鏈接。

使用 readelf 工具的 --section-headers (-S)選項可以解析查看到這塊區域里存儲的內容。

# readelf --section-headers helloworld
There are 30 section headers, starting at offset 0x5b10:

Section Headers:
  [Nr] Name              Type             Address           Offset
    Size              EntSize          Flags  Link  Info  Align
  ......
  [13] .text             PROGBITS         0000000000401040  00001040
    0000000000000175  0000000000000000  AX       0     0     16
  ......
  [23] .data             PROGBITS         0000000000404020  00003020
    0000000000000010  0000000000000000  WA       0     0     8
  [24] .bss              NOBITS           0000000000404030  00003030
    0000000000000008  0000000000000000  WA       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)

結果顯示,該文件總共有 30 個 Sections,每一個 Section 在二進制文件中的位置通過 Offset 列表示了出來。Section 的大小通過 Size 列體現。

在這 30 個Section中,每一個都有獨特的作用。我們編寫的代碼在編譯成二進制指令后都會放到 .text 這個 Section 中。另外我們看到 .text 段的 Address 列顯示的地址是 0000000000401040。回憶前面我們在 ELF 文件頭中看到 Entry point address 顯示的入口地址為 0x401040。這說明,程序的入口地址就是 .text 段的地址。

另外還有兩個值得關注的 Section 是 .data 和 .bss。代碼中的全局變量數據在編譯后將在在這兩個 Section 中占據一些位置。如下簡單代碼所示。

//未初始化的內存區域位于 .bss 段
int data1 ;     

//已經初始化的內存區域位于 .data 段
int data2 = 100 ;  

//代碼位于 .text 段
int main(void)
{
 ...
}

1.4 入口進一步查看

接下來,我們想再查看一下我們前面提到的程序入口 0x401040,看看它到底是啥。我們這次再借助 nm 命令來進一步查看一下可執行文件中的符號及其地址信息。-n 選項的作用是顯示的符號以地址排序,而不是名稱排序。

# nm -n helloworld
     w __gmon_start__
     U __libc_start_main@@GLIBC_2.2.5
     U printf@@GLIBC_2.2.5
......                 
0000000000401040 T _start
......
0000000000401126 T main

通過以上輸出可以看到,程序入口 0x401040 指向的是 _start 函數的地址,在這個函數執行一些初始化的操作之后,我們的入口函數 main 將會被調用到,它位于 0x401126 地址處。

二、用戶進程的創建過程概述

在我們編寫的代碼編譯完生成可執行程序之后,下一步就是使用 shell 把它加載起來并運行之。一般來說 shell 進程是通過fork+execve來加載并運行新進程的。一個簡單加載 helloworld 命令的 shell 核心邏輯是如下這個過程。

// shell 代碼示例
int main(int argc, char * argv[])
{
 ...
 pid = fork();
 if (pid==0){ // 如果是在子進程中
  //使用 exec 系列函數加載并運行可執行文件
  execve("helloworld", argv, envp);
 } else {
  ...
 }
 ...
}

shell 進程先通過 fork 系統調用創建一個進程出來。然后在子進程中調用 execve 將執行的程序文件加載起來,然后就可以調到程序文件的運行入口處運行這個程序了。

在上一篇文章[《Linux進程是如何創建出來的?》]中,我們詳細介紹過了 fork 的工作過程。這里我們再簡單過一下。

這個 fork 系統調用在內核入口是在 kernel/fork.c 下。

//file:kernel/fork.c
SYSCALL_DEFINE0(fork)
{
 return do_fork(SIGCHLD, 0, 0, NULL, NULL);
}

在 do_fork 的實現中,核心是一個 copy_process 函數,它以拷貝父進程(線程)的方式來生成一個新的 task_struct 出來。

//file:kernel/fork.c
long do_fork(...)
{
 //復制一個 task_struct 出來
 struct task_struct *p;
 p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace);

 //子任務加入到就緒隊列中去,等待調度器調度
 wake_up_new_task(p);
 ...
}

在 copy_process 函數中為新進程申請 task_struct,并用當前進程自己的地址空間、命名空間等對新進程進行初始化,并為其申請進程 pid。

//file:kernel/fork.c
static struct task_struct *copy_process(...)
{
 //復制進程 task_struct 結構體
 struct task_struct *p;
 p = dup_task_struct(current);
 ...

 //進程核心元素初始化
 retval = copy_files(clone_flags, p);
 retval = copy_fs(clone_flags, p);
 retval = copy_mm(clone_flags, p);
 retval = copy_namespaces(clone_flags, p);
 ...

 //申請 pid && 設置進程號
 pid = alloc_pid(p->nsproxy->pid_ns);
 p->pid = pid_nr(pid);
 p->tgid = p->pid;
 ......
}

執行完后,進入 wake_up_new_task 讓新進程等待調度器調度。

不過 fork 系統調用只能是根據當的 shell 進程再復制一個新的進程出來。這個新進程里的代碼、數據都還是和原來的 shell 進程的內容一模一樣。

要想實現加載并運行另外一個程序,比如我們編譯出來的 helloworld 程序,那還需要使用到 execve 系統調用。

三. Linux 可執行文件加載器

其實 Linux 不是寫死只能加載 ELF 一種可執行文件格式的。它在啟動的時候,會把自己支持的所有可執行文件的解析器都加載上。并使用一個 formats 雙向鏈表來保存所有的解析器。其中 formats 雙向鏈表在內存中的結構如下圖所示。

我們就以 ELF 的加載器 elf_format 為例,來看看這個加載器是如何注冊的。在 Linux 中每一個加載器都用一個 linux_binfmt 結構來表示。其中規定了加載二進制可執行文件的 load_binary 函數指針,以及加載崩潰文件 的 core_dump 函數等。其完整定義如下

//file:include/linux/binfmts.h
struct linux_binfmt {
 ...
 int (*load_binary)(struct linux_binprm *);
 int (*load_shlib)(struct file *);
 int (*core_dump)(struct coredump_params *cprm);
};

其中 ELF 的加載器 elf_format 中規定了具體的加載函數,例如 load_binary 成員指向的就是具體的 load_elf_binary 函數。這就是 ELF 加載的入口。

//file:fs/binfmt_elf.c
static struct linux_binfmt elf_format = {
 .module  = THIS_MODULE,
 .load_binary = load_elf_binary,
 .load_shlib = load_elf_library,
 .core_dump = elf_core_dump,
 .min_coredump = ELF_EXEC_PAGESIZE,
};

加載器 elf_format 會在初始化的時候通過 register_binfmt 進行注冊。

//file:fs/binfmt_elf.c
static int __init init_elf_binfmt(void)
{
 register_binfmt(&elf_format);
 return 0;
}

而 register_binfmt 就是將加載器掛到全局加載器列表 - formats 全局鏈表中。

//file:fs/exec.c
static LIST_HEAD(formats);

void __register_binfmt(struct linux_binfmt * fmt, int insert)
{
 ...
 insert ? list_add(&fmt->lh, &formats) :
   list_add_tail(&fmt->lh, &formats);
}

Linux 中除了 elf 文件格式以外還支持其它格式,在源碼目錄中搜索 register_binfmt,可以搜索到所有 Linux 操作系統支持的格式的加載程序。

# grep -r "register_binfmt" *
fs/binfmt_flat.c: register_binfmt(&flat_format);
fs/binfmt_elf_fdpic.c: register_binfmt(&elf_fdpic_format);
fs/binfmt_som.c: register_binfmt(&som_format);
fs/binfmt_elf.c: register_binfmt(&elf_format);
fs/binfmt_aout.c: register_binfmt(&aout_format);
fs/binfmt_script.c: register_binfmt(&script_format);
fs/binfmt_em86.c: register_binfmt(&em86_format);

將來在 Linux 在加載二進制文件時會遍歷 formats 鏈表,根據要加載的文件格式來查詢合適的加載器。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11313

    瀏覽量

    209756
  • 代碼
    +關注

    關注

    30

    文章

    4793

    瀏覽量

    68703
  • helloworld
    +關注

    關注

    0

    文章

    13

    瀏覽量

    4372
收藏 人收藏

    評論

    相關推薦

    MotorControl Workbench生成的代碼是開環的嗎,為什么電機跑起來很容易受到外力導致停機?

    請問各位高手 MotorControl Workbench 生成的代碼是開環的嗎?為什么我的電機跑起來很容易受到外力導致停機,我想讓它不停機,請問有什么好的辦法嗎 ?
    發表于 03-21 07:12

    請問HVMotorCtrl+PfcKit_v1.7/HVPM_sensorless_2833x代碼能不能讓電機跑起來?需要修改哪些參數?

    ,現在想測試一下代碼能不能讓電機跑起來,從level1——level6,不知道從哪個level可以讓電機跑起來,聽說比較危險,不知道需要改什
    發表于 06-13 05:19

    請問stm32f103工程代碼如何在stm32f407芯片跑起來

    如題:1、stm32f103工程代碼如何在stm32f407芯片跑起來?2、要做哪些修改?
    發表于 09-04 09:27

    如何讓的ESP32跑起來

    ESP32是了國內樂鑫科技推出的Wifi&藍牙物聯網MCU,而最近項目正好在用ESP32,所以我們今天就來分享下,如何讓的ESP32跑起來,并應用于更多實際項目。1ESP32簡...
    發表于 07-16 06:57

    怎樣讓自己編譯的uboot跑起來

    小目標:讓自己編譯的uboot跑起來參考:wiki.friendlyarm.com/wiki/index.php/NanoPi_NEO首先熟悉一下板子和開發流程。維基主要參考《使用全志原廠BSP
    發表于 11-08 06:37

    程序能跑起來就是很好的c代碼

    程序能跑起來并不見得代碼就是很好的c代碼了,衡量代碼的好壞應該從以下幾個方面來添加鏈接描述看:海風教育投訴1,
    發表于 11-23 08:00

    在板子系統跑起來后怎么查看屏幕驅動

    請問一下,在板子系統跑起來后怎么查看屏幕驅動
    發表于 01-10 06:24

    如何讓u-boot跑起來

    如何讓u-boot跑起來
    發表于 01-26 08:26

    如何讓的ESP32跑起來

    ESP32是了國內樂鑫科技推出的Wifi&藍牙物聯網MCU,而最近項目正好在用ESP32,所以我們今天就來分享下,如何讓的ESP32跑起來,并應用于更多實際項目。1ESP32簡介ESP32
    發表于 02-10 06:25

    STM32如何區分程序跑起來用的是HSE還是HSI呢?

    方法去區別HSE和HSI的話,我的問題就來了:燒到030f4并測到晶振有起振的程序(main下死循環),燒到030rb,晶振不起振了,但是通過仿真發現程序還在跑的。所以問一下大家,是怎么確定HSE跑起來了?由于程序會認為改錯配置,導致跑HSI,所以問大家
    發表于 05-05 10:47

    Zynq 7015 linux跑起來之導入之BOOT.bin生成詳解

    本文主要介紹Zynq 7015 linux跑起來之導入之BOOT.bin生成,具體的跟隨小編一起來了解一下。
    的頭像 發表于 06-27 10:01 ?7504次閱讀

    FreeRTOS_003 _讓系統在板子跑起來

    FreeRTOS_003_讓系統在板子跑起來
    的頭像 發表于 03-14 11:25 ?2781次閱讀
    FreeRTOS_003 _讓系統在板子<b class='flag-5'>上</b><b class='flag-5'>跑起來</b>

    windows安裝ubuntu并讓pioneer1應用程序跑起來的過程

    本文介紹在windows下安裝ubuntu并且讓pioneer1的應用程序跑起來的全過程。雖然安裝ubuntu不是本文重點,但是還是啰嗦地一遍吧。
    的頭像 發表于 10-23 10:41 ?2375次閱讀
    windows安裝ubuntu并讓pioneer1應用程序<b class='flag-5'>跑起來</b>的過程

    代碼是如何跑起來的?

    今天我們來思考一個簡單的問題,一個程序是如何在 Linux 執行起來的?
    的頭像 發表于 12-08 15:50 ?896次閱讀

    知道代碼怎樣跑起來的嗎(下)

    今天我們來思考一個簡單的問題,一個程序是如何在 Linux 執行起來的? 我們就拿全宇宙最簡單的 Hello World 程序來舉例。
    的頭像 發表于 05-05 14:36 ?473次閱讀
    <b class='flag-5'>你</b><b class='flag-5'>知道</b><b class='flag-5'>你</b><b class='flag-5'>寫</b>的<b class='flag-5'>代碼</b>是<b class='flag-5'>怎樣</b><b class='flag-5'>跑起來</b>的嗎(下)
    主站蜘蛛池模板: 秋霞网在线伦理免费| 肉肉的各种姿势高H细文| 日韩欧无码一区二区三区免费不卡| 24小时日本在线| 蜜柚视频网在线观看免费| 最新国产精品视频免费看| 欧美精品XXXXBBBB| WWW久久只有这里有精品| 人妖xxhdxx| 国产亚洲制服免视频| 亚洲欧洲免费三级网站| 精品精品国产yyy5857香蕉| 在线观看成年人免费视频| 美女脱精光让男生桶下面| 国产精品久久久久影院| 亚洲午夜一区二区电影院| 久久亚洲AV成人无码动态图| 中文字幕欧美一区| 男女全黄h全肉细节文| xxxxhdvideos动漫| 中文字幕本庄优花喂奶| 亚洲免费视频日本一区二区| 免费观看的毛片| 精品一产品大全| 国产欧美一区二区三区久久| 亚洲午夜精品一区二区公牛电影院| 涩涩网站在线看| 久久国产亚洲电影天堂| 99久久久国产精品免费调教| 色欲天天天综合网免费| 欧美日韩精品一区二区三区高清视频| 久久青草免费91线频观看站街| 国内精品久久久久影院男同志| 99久久综合| 97se se| 天美传媒在线观看完整高清| 久久久这里有精品999| 第七色男人天堂| 亚洲一在线| 亚洲高清国产拍精品5g| 欧美高跟镣铐bdsm视频|