chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一個(gè)程序是如何運(yùn)行起來的

Linux閱碼場(chǎng) ? 來源:卯時(shí)卯刻 ? 作者:KINGYT ? 2021-10-12 17:48 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

相信很多同學(xué)都會(huì)有疑問,一個(gè)程序是如何運(yùn)行起來的,為什么我們?cè)趕hell中執(zhí)行了一個(gè)程序,它的main函數(shù)就會(huì)被調(diào)用呢?在main函數(shù)被調(diào)用之前及之后,又經(jīng)歷了什么呢?

今天我們就來詳細(xì)的說下這個(gè)問題。

還是和之前一樣,我畫了一張程序運(yùn)行的全景圖,在上圖中,一個(gè)程序運(yùn)行所經(jīng)歷的代碼段,我都標(biāo)注了其所在的git倉(cāng)庫(kù)、源文件、及函數(shù)名,想要自己看源碼的,可以參考下上圖中的這些信息。

我們先從整體上講一下這張圖。

linux下,我們一般都是通過shell來執(zhí)行程序的。

shell其實(shí)也是一個(gè)普通的程序,它也有自己的main函數(shù),它在正常運(yùn)行后,會(huì)通過調(diào)用read_command函數(shù),來等待用戶輸入命令。

在接收到用戶輸入的命令后,shell會(huì)先使用fork系統(tǒng)調(diào)用,創(chuàng)建一個(gè)子進(jìn)程,然后再在這個(gè)子進(jìn)程中,通過execve系統(tǒng)調(diào)用,執(zhí)行最終的用戶程序。

在子進(jìn)程執(zhí)行用戶程序期間,shell主進(jìn)程會(huì)調(diào)用waitpid函數(shù),阻塞等待子進(jìn)程的完成,子進(jìn)程完成之后,waitpid從阻塞狀態(tài)中返回,且status參數(shù)中會(huì)帶著子進(jìn)程的退出碼,這個(gè)退出碼會(huì)在后續(xù)的邏輯中被保存起來,供用戶查詢。

之后,shell主進(jìn)程進(jìn)入到下一次循環(huán),繼續(xù)等待用戶輸入命令并執(zhí)行。

以上就是shell的主體邏輯,對(duì)應(yīng)于上面全景圖中的藍(lán)色部分。

下面我們?cè)賮砜聪耹inux內(nèi)核中有關(guān)execve系統(tǒng)調(diào)用的代碼,也就是上面全景圖中的綠色部分。

shell通過execve系統(tǒng)調(diào)用,告知linux內(nèi)核,要在當(dāng)前進(jìn)程中執(zhí)行目標(biāo)程序,linux內(nèi)核經(jīng)過層層代碼,最終到達(dá)load_elf_binary函數(shù)。

該函數(shù)是整個(gè)系統(tǒng)調(diào)用中最核心的一段邏輯,它主要用來為目標(biāo)程序準(zhǔn)備各種執(zhí)行環(huán)境。

比如,映射代碼區(qū)、數(shù)據(jù)區(qū)等到當(dāng)前進(jìn)程的虛擬地址空間,將程序名、環(huán)境變量、程序參數(shù)、及各種其他數(shù)據(jù),有規(guī)律的壓入到新分配的棧中,等等。

之后,load_elf_binary函數(shù)會(huì)調(diào)用start_thread,進(jìn)而會(huì)調(diào)用start_thread_common函數(shù)。

在該函數(shù)里,會(huì)將返回到用戶區(qū)之后,要執(zhí)行的,用戶區(qū)程序的起始地址,設(shè)置到regs-》ip里,同時(shí)也會(huì)將上面新初始化好的,用戶堆棧的棧頂?shù)刂?,設(shè)置到regs-》sp里。

當(dāng)execve系統(tǒng)調(diào)用返回到用戶區(qū)之后,regs-》ip和regs-》sp里的值,會(huì)分別賦值到rip和rsp寄存器里,這樣指定的用戶程序就可以繼續(xù)執(zhí)行了。

這一流程我們?cè)谥暗奈恼?精致全景圖 | 系統(tǒng)調(diào)用是如何實(shí)現(xiàn)的 中講過,這里就不再贅述。

不過這里還是有一點(diǎn)需要注意,就是設(shè)置到regs-》ip中的地址,并不是我們自己程序的起始地址,而是動(dòng)態(tài)鏈接器 /lib64/ld-linux-x86-64.so.2 的起始地址。

之所以要設(shè)置動(dòng)態(tài)鏈接器的起始地址,是因?yàn)槲覀冃枰诜祷氐接脩魠^(qū)之后,讓其可以繼續(xù)為我們的程序準(zhǔn)備執(zhí)行環(huán)境,比如,幫忙加載程序依賴的各種動(dòng)態(tài)鏈接庫(kù)等。

在動(dòng)態(tài)鏈接器為我們的程序準(zhǔn)備好執(zhí)行環(huán)境之后,它會(huì)從進(jìn)程堆棧的auxiliary vector區(qū),取出最終用戶程序的真正起始地址,并跳轉(zhuǎn)到該位置開始執(zhí)行。

auxiliary vector區(qū)存放的用戶程序的起始地址,是上面linux內(nèi)核初始化堆棧時(shí)設(shè)置的。

動(dòng)態(tài)鏈接器相關(guān)的代碼就是這些,它對(duì)應(yīng)于上面全景圖中紫色的部分。

在跳轉(zhuǎn)到我們自己程序的起始地址后,首先執(zhí)行的并不是我們寫的main函數(shù),而是glibc里名為_start的一段匯編代碼。

這段匯編代碼也比較簡(jiǎn)單,主要是從堆棧中獲取main函數(shù)所需的argc,argv等參數(shù),然后最終調(diào)用我們寫的main函數(shù)。

當(dāng)main函數(shù)返回之后,glibc里的后續(xù)代碼,會(huì)將main函數(shù)的返回值,當(dāng)作該進(jìn)程的退出碼,然后調(diào)用exit結(jié)束該進(jìn)程。

這些代碼對(duì)應(yīng)于上面全景圖中的粉色部分。

進(jìn)程調(diào)用exit退出之后,shell主進(jìn)程也會(huì)從waitpid的阻塞狀態(tài)中返回,然后繼續(xù)進(jìn)行下一次循環(huán)。

以上就是程序完整的啟動(dòng)和結(jié)束流程。

下面我們來看下具體的源碼實(shí)現(xiàn)。

注意,為了方便理解,很多代碼我們都做了刪減。

首先是shell部分,shell是一個(gè)普通的程序,它也有自己的main函數(shù):

372bd54e-22a0-11ec-82a8-dac502259ad0.png

該函數(shù)里調(diào)用了reader_loop:

373c6454-22a0-11ec-82a8-dac502259ad0.png

reader_loop的主體邏輯是,在while循環(huán)里不斷的使用read_command函數(shù)讀取用戶輸入的命令,然后使用execute_command執(zhí)行該命令。

execute_command函數(shù)經(jīng)過層層代碼后,會(huì)使用下圖中的fork,創(chuàng)建一個(gè)子進(jìn)程:

3788521a-22a0-11ec-82a8-dac502259ad0.png

然后在該子進(jìn)程中,使用execve系統(tǒng)調(diào)用,告知linux內(nèi)核,用當(dāng)前子進(jìn)程執(zhí)行新的用戶程序:

37c7b0b8-22a0-11ec-82a8-dac502259ad0.png

在shell主進(jìn)程中,會(huì)調(diào)用waitpid函數(shù),阻塞等待子進(jìn)程的完成:

37d9e45e-22a0-11ec-82a8-dac502259ad0.png

當(dāng)子進(jìn)程退出后,waitpid會(huì)從阻塞狀態(tài)中返回,并在status里攜帶子進(jìn)程的退出碼,之后shell主進(jìn)程又返回上面的read_command函數(shù),繼續(xù)等待用戶下一條命令的輸入。

以上就是bash的主體邏輯,對(duì)應(yīng)于上面全景圖中的藍(lán)色部分。

下面我們繼續(xù)看全景圖中的綠色部分,也就是linux內(nèi)核中有關(guān)execve的代碼。

當(dāng)shell的子進(jìn)程執(zhí)行execve函數(shù)時(shí),linux內(nèi)核中對(duì)應(yīng)的系統(tǒng)調(diào)用被觸發(fā):

37eb0efa-22a0-11ec-82a8-dac502259ad0.png

沿著函數(shù)的調(diào)用鏈,我們會(huì)找到一個(gè)名為do_execveat_common的函數(shù),在該函數(shù)中,會(huì)將目標(biāo)程序的文件名、環(huán)境變量、及各種程序參數(shù)等字符串,拷貝到新創(chuàng)建的用戶堆棧區(qū):

3822e884-22a0-11ec-82a8-dac502259ad0.png

此時(shí),新創(chuàng)建的堆棧區(qū)里內(nèi)容,就如上面全景圖中右下角的a1-a9, b1-b8部分構(gòu)成的二維網(wǎng)格區(qū)域里所示的內(nèi)容。

其中,黃色區(qū)域里存放的是程序參數(shù) 。/a.out hello world,藍(lán)色區(qū)域里存放的是環(huán)境變量 SHLVL=2, HOME=/, TERM=linux, PWD=/,橘黃色區(qū)域里存放的是要執(zhí)行的程序文件名 。/a.out。

這些內(nèi)容和我們執(zhí)行的測(cè)試程序,及其所處的環(huán)境也正好一樣:

3857cfe0-22a0-11ec-82a8-dac502259ad0.png

繼續(xù)沿著內(nèi)核函數(shù)調(diào)用鏈,我們最終會(huì)來到load_elf_binary函數(shù),該函數(shù)是整個(gè)系統(tǒng)調(diào)用的核心。

由于linux上執(zhí)行的程序基本上都是elf格式,所以內(nèi)核選擇的加載函數(shù)是load_elf_binary,看這個(gè)函數(shù)時(shí),可以參考elf格式的man文檔:

https://man.archlinux.org/man/elf.5

該函數(shù)比較復(fù)雜,我對(duì)其做了大量刪減,并添加了很多注釋:

387b14fa-22a0-11ec-82a8-dac502259ad0.png

該函數(shù)最后會(huì)調(diào)用start_thread函數(shù),進(jìn)而會(huì)調(diào)用start_thread_common函數(shù):

38a84222-22a0-11ec-82a8-dac502259ad0.png

這個(gè)函數(shù)重點(diǎn)需要注意的是對(duì)regs-》ip和regs-》sp的賦值,其作用在load_elf_binary函數(shù)的截圖中已經(jīng)注釋過了,就是在返回到用戶區(qū)之后,這兩個(gè)字段的值會(huì)被分別拷貝到rip和rsp寄存器里,所以這里的賦值,就相當(dāng)于在返回用戶區(qū)之后,對(duì)rip和rsp寄存器的賦值,這個(gè)在 精致全景圖 | 系統(tǒng)調(diào)用是如何實(shí)現(xiàn)的 有講。

到這里內(nèi)核部分的代碼就都已經(jīng)結(jié)束了。

由load_elf_binary函數(shù)截圖中可見,regs-》ip中設(shè)置的地址是elf_entry,即動(dòng)態(tài)鏈接器的起始地址,而不是我們自己程序的起始地址。

原因是,我們還需要?jiǎng)討B(tài)鏈接器繼續(xù)幫我們準(zhǔn)備執(zhí)行環(huán)境,比如幫我們加載程序依賴的動(dòng)態(tài)鏈接庫(kù)等。

所以在execve系統(tǒng)調(diào)用返回到用戶區(qū)之后,代碼流程就進(jìn)入到了動(dòng)態(tài)鏈接器里的邏輯,即上面全景圖中的紫色區(qū)域:

38b7ad3e-22a0-11ec-82a8-dac502259ad0.png

上圖中的_start是動(dòng)態(tài)鏈接器的起始執(zhí)行地址,這個(gè)可以通過下面的方式來確認(rèn):

38fd1e50-22a0-11ec-82a8-dac502259ad0.png

在_start函數(shù)中,先將rsp寄存器的值,即上面內(nèi)核新初始化的堆棧的棧頂?shù)刂?,賦值到rdi中,然后再使用call指令,調(diào)用_dl_start函數(shù)。

之所以要賦值到rdi寄存器中,是因?yàn)?a href="http://www.brongaenegriffin.com/soft/data/21-24/" target="_blank">c語言的calling convention約定好的,用此方式來傳遞參數(shù)。

再看_dl_start函數(shù):

391105f0-22a0-11ec-82a8-dac502259ad0.png

該函數(shù)調(diào)用了_dl_start_final,返回一個(gè)地址,這個(gè)地址就是我們自己程序的起始地址。

再看_dl_start_final:

393b5134-22a0-11ec-82a8-dac502259ad0.png

該函數(shù)又調(diào)用了_dl_sysdep_start:

3974678a-22a0-11ec-82a8-dac502259ad0.png

在這里,動(dòng)態(tài)鏈接器通過內(nèi)核初始化的堆棧區(qū)中的auxiliary vector,找到最終用戶程序的起始執(zhí)行地址。

再之后,動(dòng)態(tài)鏈接器的函數(shù)調(diào)用鏈依次退出,最終返回到上面的_start函數(shù)。

_start函數(shù)之后會(huì)順序執(zhí)行_dl_start_user,相關(guān)代碼也在上面的_start函數(shù)的截圖里。

其邏輯是,先將rax中的值,即_dl_start函數(shù)返回的最終用戶程序的起始地址,賦值到r12寄存器中,然后再jmp到r12寄存器指向的地址,即開始執(zhí)行最終的用戶程序邏輯。

至于rax中的值,為什么是_dl_start函數(shù)返回的地址,這個(gè)其實(shí)也是 c calling convention 中的約定,感興趣可以自己查下。

以上就是動(dòng)態(tài)鏈接器的全部邏輯,其對(duì)應(yīng)于全景圖中的紫色部分。

最后,邏輯進(jìn)入到了全景圖中的粉色部分。

動(dòng)態(tài)鏈接器從內(nèi)核設(shè)置的auxiliary vector中,獲取的用戶程序的起始地址,還并不是我們的main函數(shù),而是glibc中一段名為_start的代碼,這個(gè)可以通過下面的方式確認(rèn):

39c36312-22a0-11ec-82a8-dac502259ad0.png

該_start代碼段內(nèi)容如下:

39f2140a-22a0-11ec-82a8-dac502259ad0.png

它從堆棧中獲取到argc和argv,然后調(diào)用__libc_start_main:

3a222078-22a0-11ec-82a8-dac502259ad0.png

在__libc_start_main里,才真正的調(diào)用了我們寫的main函數(shù)。

當(dāng)main函數(shù)返回之后,__libc_start_main里用main函數(shù)返回的值,作為該進(jìn)程的退出碼,然后調(diào)用exit退出當(dāng)前進(jìn)程。

當(dāng)該進(jìn)程退出后,shell主進(jìn)程也從waitpid的阻塞狀態(tài)返回,并攜帶用戶程序的退出碼。

在上面全景圖這個(gè)示例中,返回碼為99:

3a58d370-22a0-11ec-82a8-dac502259ad0.png

之后,shell主進(jìn)程又進(jìn)入到下一次循環(huán),繼續(xù)等待用戶命令并執(zhí)行,也就是說,又進(jìn)入到全景圖中的藍(lán)色部分。

至此,在linux上執(zhí)行程序的流程,就形成了一個(gè)完整閉環(huán)。

你,學(xué)廢了嗎?

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 程序
    +關(guān)注

    關(guān)注

    117

    文章

    3841

    瀏覽量

    85106
  • Shell
    +關(guān)注

    關(guān)注

    1

    文章

    374

    瀏覽量

    25310

原文標(biāo)題:精致全景圖 | 程序是如何運(yùn)行起來的

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    單片機(jī)里的程序運(yùn)行方式

    RAM)中,并建立個(gè)它的運(yùn)行環(huán)境(當(dāng)然這里邊還有內(nèi)存映射,虛擬內(nèi)存,連接與加載,等些其他東西),準(zhǔn)備執(zhí)行。 由以上可知,單片機(jī)上的程序
    發(fā)表于 01-16 06:57

    MCU代碼需要搬到RAM中才能運(yùn)行嗎?不這樣做會(huì)有什么不妥嘛?

    大部分單片機(jī)的代碼直接在nor flash中運(yùn)行,少部分需要加載到ram中。 nor flash可以直接尋址一個(gè)字節(jié),可以找到個(gè)指令的具體地址,因此可以直接
    發(fā)表于 12-04 07:39

    Linux 下交叉編譯實(shí)戰(zhàn):跑起來你的第一個(gè) STM32 程序

    起來你的第一個(gè)STM32程序。、準(zhǔn)備工作在開始之前,需要準(zhǔn)備:1、Linux開發(fā)環(huán)境Ubuntu、Debian或其他主流發(fā)行版都可以。2、ARMGCC交叉編譯工具
    的頭像 發(fā)表于 11-24 19:04 ?679次閱讀
    Linux 下交叉編譯實(shí)戰(zhàn):跑<b class='flag-5'>起來</b>你的第<b class='flag-5'>一個(gè)</b> STM32 <b class='flag-5'>程序</b>

    如何自己設(shè)計(jì)個(gè)基于RISC-V的SoC架構(gòu),最后可以在FPGA上跑起來?

    如何自己設(shè)計(jì)個(gè)基于RISC-V的SoC架構(gòu),最后可以在FPGA上跑起來
    發(fā)表于 11-11 08:03

    使用Nuclei Studio IDE計(jì)算程序運(yùn)行時(shí)間

    時(shí)間函數(shù)_gettimeofday()的系統(tǒng)時(shí)間,該函數(shù)的C語言代碼如下所示: 由C代碼可知,tp存儲(chǔ)了兩個(gè)變量tv_sec和tv_usec,前者代表秒數(shù)s,后者表示微秒us,在想要運(yùn)行程序起始
    發(fā)表于 10-28 08:25

    大數(shù)組程序無法運(yùn)行怎么解決?

    主控是103,程序中定義個(gè)const類型 128k只讀數(shù)組,放在flash上,程序無法運(yùn)行,堆棧都初始化不了,在keil編譯下正常,在rt
    發(fā)表于 09-15 06:21

    同樣的代碼在官方開發(fā)板上運(yùn)行正常,在自己板子上就跑不起來,怎么辦?

    UART交互,CLI代碼所在目錄為:SDK安裝目錄examplesperipheralcli。如果這2個(gè)程序運(yùn)行正常,說明你的焊接問題
    的頭像 發(fā)表于 05-12 15:26 ?688次閱讀
    同樣的代碼在官方開發(fā)板上<b class='flag-5'>運(yùn)行</b>正常,在自己板子上就跑不<b class='flag-5'>起來</b>,怎么辦?

    如何在 樹莓派 上編寫和運(yùn)行 C 語言程序?

    在本教程中,我將討論C編程語言是什么,C編程的用途,以及如何在RaspberryPi上編寫和運(yùn)行C程序。本文的目的是為您介紹在RaspberryPi上進(jìn)行C編程的基礎(chǔ)知識(shí)。如果您想深入了解C編程
    的頭像 發(fā)表于 03-25 09:28 ?1087次閱讀
    如何在 樹莓派 上編寫和<b class='flag-5'>運(yùn)行</b> C 語言<b class='flag-5'>程序</b>?

    零基礎(chǔ)入門:如何在樹莓派上編寫和運(yùn)行Python程序?

    在這篇文章中,我將為你簡(jiǎn)要介紹Python程序是什么、Python程序可以用來做什么,以及如何在RaspberryPi上編寫和運(yùn)行個(gè)簡(jiǎn)單的
    的頭像 發(fā)表于 03-25 09:27 ?1854次閱讀
    零基礎(chǔ)入門:如何在樹莓派上編寫和<b class='flag-5'>運(yùn)行</b>Python<b class='flag-5'>程序</b>?

    STM32F103RBT6開發(fā)板每次程序都得重新燒錄才能正常運(yùn)行,如何解決?

    每次板子都得重新燒錄程序才能運(yùn)行起來。第次燒錄完程序后,能正常運(yùn)行,等我把開關(guān)斷開再打開,
    發(fā)表于 03-11 07:40

    FLASHXIP程序跑不起來是怎么回事?

    1、環(huán)境是: Nuclei Studio IDE for C/C++ Developers Version: 2023-10 2、開發(fā)板是正點(diǎn)原子達(dá)芬奇 這是跑不起來程序,不知道是什么原因還望高手指導(dǎo)
    發(fā)表于 03-07 14:20

    用Labview寫個(gè)電子稱的485串口程序

    關(guān)鍵詞:Labview + 串口程序 232、485串口通訊是最常見的儀器儀表通訊方式之,本文詳細(xì)介紹,用Labview編寫個(gè)電子秤的485串口
    的頭像 發(fā)表于 03-06 09:54 ?1767次閱讀
    用Labview寫<b class='flag-5'>一</b><b class='flag-5'>個(gè)</b>電子稱的485串口<b class='flag-5'>程序</b>

    用DLPA3000+DLPC3478+MSP430+SII1161(HDMI Reciever)設(shè)計(jì)了產(chǎn)品,如何將板子運(yùn)行起來呢?

    SII1161上有個(gè)EEPROM, 里面的文件我們可以自己搞定; 2. MSP430 上帶了個(gè)SPI FLASH,這里面的程序從那里獲取呢?不燒錄是不是板子就運(yùn)行
    發(fā)表于 02-28 06:39

    KS-Soft:站式集合20個(gè)TCP/IP實(shí)用程序

    IP-Tools 在個(gè)程序中提供了許多 TCP/IP 實(shí)用程序。 這個(gè)屢獲殊榮的程序可以在Windows XP/7/8/10,Window
    的頭像 發(fā)表于 02-11 11:09 ?699次閱讀
    KS-Soft:<b class='flag-5'>一</b>站式集合20<b class='flag-5'>個(gè)</b>TCP/IP實(shí)用<b class='flag-5'>程序</b>

    用5509A寫個(gè)用MCBSP和AIC23采集和播放音頻的程序,在運(yùn)行的時(shí)候發(fā)出了很大的雜音,為什么?

    AIC23提供。(經(jīng)過測(cè)試,F(xiàn)SR=FSX=44.1K) 3.音頻采樣率為44.1K,DSP MODE,一個(gè)字長(zhǎng)16bit。 4.用BYPASS方式運(yùn)行,沒有任何雜音,很清晰。 5.LINEIN不接電
    發(fā)表于 02-05 07:12