精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Linux 下“Hello World”的幕后發生了什么

系統 Linux
今天我在想 —— 當你在 Linux 上運行一個簡單的 “Hello World” Python 程序時,發生了什么,來看一下吧。

print("hello world")

這就是在命令行下的情況:

$ python3 hello.py
hello world

但是在幕后,實際上有更多的事情在發生。我將描述一些發生的情況,并且(更重要的是)解釋一些你可以用來查看幕后情況的工具。我們將用 readelfstraceldddebugfs/procltracedd 和 stat。我不會討論任何只針對 Python 的部分 —— 只研究一下當你運行任何動態鏈接的可執行文件時發生的事情。

0、在執行 execve 之前

要啟動 Python 解釋器,很多步驟都需要先行完成。那么,我們究竟在運行哪一個可執行文件呢?它在何處呢?

1、解析 python3 hello.py

Shell 將 python3 hello.py 解析成一條命令和一組參數:python3 和 ['hello.py']

在此過程中,可能會進行一些如全局擴展等操作。舉例來說,如果你執行 python3 *.py ,Shell 會將其擴展到 python3 hello.py

2、確認 python3 的完整路徑

現在,我們了解到需要執行 python3。但是,這個二進制文件的完整路徑是什么呢?解決辦法是使用一個名為 PATH 的特殊環境變量。

自行驗證:在你的 Shell 中執行 echo $PATH。對我來說,它的輸出如下:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

當執行一個命令時,Shell 將會依序在 PATH 列表中的每個目錄里搜索匹配的文件。

對于 fish(我的 Shell),你可以在 這里 查看路徑解析的邏輯。它使用 stat 系統調用去檢驗是否存在文件。

自行驗證:執行 strace -e stat bash,然后運行像 python3 這樣的命令。你應該會看到如下輸出:

stat("/usr/local/sbin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/bin/python3", {st_mode=S_IFREG|0755, st_size=5479736, ...}) = 0

你可以觀察到,一旦在 /usr/bin/python3 找到了二進制文件,搜索就會立即終止:它不會繼續去 /sbin 或 /bin 中查找。

對 execvp 的補充說明

如果你想要不用自己重新實現,而運行和 Shell 同樣的 PATH 搜索邏輯,你可以使用 libc 函數 execvp(或其它一些函數名中含有 p 的 exec* 函數)。

3、stat 的背后運作機制

你可能在思考,Julia,stat 到底做了什么?當你的操作系統要打開一個文件時,主要分為兩個步驟:

  1. 它將 文件名 映射到一個包含該文件元數據的 inode
  2. 它利用這個 inode 來獲取文件的實際內容

stat 系統調用只是返回文件的 inode 內容 —— 它并不讀取任何的文件內容。好處在于這樣做速度非常快。接下來讓我們一起來快速了解一下 inode。(在 Dmitry Mazin 的這篇精彩文章 《磁盤就是一堆比特》中有更多的詳細內容)

$ stat /usr/bin/python3
  File: /usr/bin/python3 -> python3.9
  Size: 9           Blocks: 0          IO Block: 4096   symbolic link
Device: fe01h/65025d    Inode: 6206        Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-08-03 14:17:28.890364214 +0000
Modify: 2021-04-05 12:00:48.000000000 +0000
Change: 2021-06-22 04:22:50.936969560 +0000
 Birth: 2021-06-22 04:22:50.924969237 +0000

自行驗證:我們來實際查看一下硬盤上 inode 的確切位置。

首先,我們需要找出硬盤的設備名稱:

$ df
...
tmpfs             100016      604     99412   1% /run
/dev/vda1       25630792 14488736  10062712  60% /
...

看起來它是 /dev/vda1。接著,讓我們尋找 /usr/bin/python3 的 inode 在我們硬盤上的確切位置(在 debugfs 提示符下輸入 imap 命令):

$ sudo debugfs /dev/vda1
debugfs 1.46.2 (28-Feb-2021)
debugfs:  imap /usr/bin/python3
Inode 6206 is part of block group 0
    located at block 658, offset 0x0d00

我不清楚 debugfs 是如何確定文件名對應的 inode 的位置,但我們暫時不需要深入研究這個。

現在,我們需要計算硬盤中 “塊 658,偏移量 0x0d00” 處是多少個字節,這個大的字節數組就是你的硬盤。每個塊有 4096 個字節,所以我們需要到 4096 * 658 + 0x0d00 字節。使用計算器可以得到,這個值是 2698496

$ sudo dd if=/dev/vda1 bs=1 skip=2698496 count=256 2>/dev/null | hexdump -C
00000000  ff a1 00 00 09 00 00 00  f8 b6 cb 64 9a 65 d1 60  |...........d.e.`|
00000010  f0 fb 6a 60 00 00 00 00  00 00 01 00 00 00 00 00  |..j`............|
00000020  00 00 00 00 01 00 00 00  70 79 74 68 6f 6e 33 2e  |........python3.|
00000030  39 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |9...............|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000060  00 00 00 00 12 4a 95 8c  00 00 00 00 00 00 00 00  |.....J..........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 2d cb 00 00  |............-...|
00000080  20 00 bd e7 60 15 64 df  00 00 00 00 d8 84 47 d4  | ...`.d.......G.|
00000090  9a 65 d1 60 54 a4 87 dc  00 00 00 00 00 00 00 00  |.e.`T...........|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

好極了!我們找到了 inode!你可以在里面看到 python3,這是一個很好的跡象。我們并不打算深入了解所有這些,但是 Linux 內核的 ext4 inode 結構 指出,前 16 位是 “模式”,即權限。所以現在我們將看一下 ffa1 如何對應到文件權限。

  • ffa1 對應的數字是 0xa1ff,或者 41471(因為 x86 是小端表示)
  • 41471 用八進制表示就是 0120777
  • 這有些奇怪 - 那個文件的權限肯定可以是 777,但前三位是什么呢?我以前沒見過這些!你可以在 inode 手冊頁 中找到 012 的含義(向下滾動到“文件類型和模式”)。這里有一個小的表格說 012 表示 “符號鏈接”。

我們查看一下這個文件,確實是一個權限為 777 的符號鏈接:

$ ls -l /usr/bin/python3
lrwxrwxrwx 1 root root 9 Apr  5  2021 /usr/bin/python3 -> python3.9

它確實是!耶,我們正確地解碼了它。

4、準備復刻

我們尚未準備好啟動 python3。首先,Shell 需要創建一個新的子進程來進行運行。在 Unix 上,新的進程啟動的方式有些特殊 - 首先進程克隆自己,然后運行 execve,這會將克隆的進程替換為新的進程。

自行驗證: 運行 strace -e clone bash,然后運行 python3。你應該會看到類似下面的輸出:

clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f03788f1a10) = 3708100

3708100 是新進程的 PID,這是 Shell 進程的子進程。

這里有些工具可以查看進程的相關信息:

  • pstree 會展示你的系統中所有進程的樹狀圖
  • cat /proc/PID/stat 會顯示一些關于該進程的信息。你可以在 man proc 中找到這個文件的內容說明。例如,第四個字段是父進程的PID。

新進程的繼承

新的進程(即將變為 python3 的)從 Shell 中繼承了很多內容。例如,它繼承了:

  1. 環境變量:你可以通過 cat /proc/PID/environ | tr '\0' '\n' 查看
  2. 標準輸出和標準錯誤的文件描述符:通過 ls -l /proc/PID/fd 查看
  3. 工作目錄(也就是當前目錄)
  4. 命名空間和控制組(如果它在一個容器內)
  5. 運行它的用戶以及群組
  6. 還有可能是我此刻未能列舉出來的更多東西

5、Shell 調用 execve

現在我們準備好啟動 Python 解釋器了!

自行驗證:運行 strace -f -e execve bash,接著運行 python3。其中的 -f 參數非常重要,因為我們想要跟蹤任何可能產生的子進程。你應該可以看到如下的輸出:

[pid 3708381] execve("/usr/bin/python3", ["python3"], 0x560397748300 /* 21 vars */) = 0

第一個參數是這個二進制文件,而第二個參數是命令行參數列表。這些命令行參數被放置在程序內存的特定位置,以便在運行時可以訪問。

那么,execve 內部到底發生了什么呢?

6、獲取該二進制文件的內容

我們首先需要打開 python3 的二進制文件并讀取其內容。直到目前為止,我們只使用了 stat 系統調用來獲取其元數據,但現在我們需要獲取它的內容。

讓我們再次查看 stat 的輸出:

$ stat /usr/bin/python3
  File: /usr/bin/python3 -> python3.9
  Size: 9           Blocks: 0          IO Block: 4096   symbolic link
Device: fe01h/65025d    Inode: 6206        Links: 1
...

該文件在磁盤上占用 0 個塊的空間。這是因為符號鏈接(python3.9)的內容實際上是存儲在 inode 自身中:在下面顯示你可以看到(來自上述 inode 的二進制內容,以 hexdump 格式分為兩行輸出)。

00000020  00 00 00 00 01 00 00 00  70 79 74 68 6f 6e 33 2e  |........python3.|
00000030  39 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |9...............|

因此,我們將需要打開 /usr/bin/python3.9 。所有這些操作都在內核內部進行,所以你并不會看到其他的系統調用。

每個文件都由硬盤上的一系列的  構成。我知道我系統中的每個塊是 4096 字節,所以一個文件的最小大小是 4096 字節 —— 甚至如果文件只有 5 字節,它在磁盤上仍然占用 4KB。

自行驗證:我們可以通過 debugfs 找到塊號,如下所示:(再次說明,我從 Dmitry Mazin 的《磁盤就是一堆比特》文章中得知這些步驟)。

$ debugfs /dev/vda1
debugfs:  blocks /usr/bin/python3.9
145408 145409 145410 145411 145412 145413 145414 145415 145416 145417 145418 145419 145420 145421 145422 145423 145424 145425 145426 145427 145428 145429 145430 145431 145432 145433 145434 145435 145436 145437

接下來,我們可以使用 dd 來讀取文件的第一個塊。我們將塊大小設定為 4096 字節,跳過 145408 個塊,然后讀取 1 個塊。

$ dd if=/dev/vda1 bs=4096 skip=145408 count=1 2>/dev/null | hexdump -C | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  c0 a5 5e 00 00 00 00 00  |..>.......^.....|
00000020  40 00 00 00 00 00 00 00  b8 95 53 00 00 00 00 00  |@.........S.....|
00000030  00 00 00 00 40 00 38 00  0b 00 40 00 1e 00 1d 00  |....@.8...@.....|
00000040  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  68 02 00 00 00 00 00 00  68 02 00 00 00 00 00 00  |h.......h.......|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  a8 02 00 00 00 00 00 00  a8 02 40 00 00 00 00 00  |..........@.....|
00000090  a8 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |..@.............|

你會發現,這樣我們得到的輸出結果與直接使用 cat 讀取文件所獲得的結果完全一致。

$ cat /usr/bin/python3.9 | hexdump -C | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  c0 a5 5e 00 00 00 00 00  |..>.......^.....|
00000020  40 00 00 00 00 00 00 00  b8 95 53 00 00 00 00 00  |@.........S.....|
00000030  00 00 00 00 40 00 38 00  0b 00 40 00 1e 00 1d 00  |....@.8...@.....|
00000040  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  68 02 00 00 00 00 00 00  68 02 00 00 00 00 00 00  |h.......h.......|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  a8 02 00 00 00 00 00 00  a8 02 40 00 00 00 00 00  |..........@.....|
00000090  a8 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |..@.............|

關于魔術數字的額外說明

這個文件以 ELF 開頭,這是一個被稱為“魔術數字magic number”的標識符,它是一種字節序列,告訴我們這是一個 ELF 文件。在 Linux 上,ELF 是二進制文件的格式。

不同的文件格式有不同的魔術數字。例如,gzip 的魔數是 1f8b。文件開頭的魔術數字就是 file blah.gz 如何識別出它是一個 gzip 文件的方式。

我認為 file 命令使用了各種啟發式方法來確定文件的類型,而其中,魔術數字是一個重要的特征。

7、尋找解釋器

我們來解析這個 ELF 文件,看看里面都有什么內容。

自行驗證:運行 readelf -a /usr/bin/python3.9。我得到的結果是這樣的(但是我刪減了大量的內容):

$ readelf -a /usr/bin/python3.9
ELF Header:
    Class:                             ELF64
    Machine:                           Advanced Micro Devices X86-64
...
->  Entry point address:               0x5ea5c0
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
->      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
        ...
->        1238: 00000000005ea5c0    43 FUNC    GLOBAL DEFAULT   13 _start

從這段內容中,我理解到:

  1. 請求內核運行 /lib64/ld-linux-x86-64.so.2 來啟動這個程序。這就是所謂的動態鏈接器,我們將在隨后的部分對其進行討論。
  2. 該程序制定了一個入口點(位于 0x5ea5c0),那里是這個程序代碼開始的地方。

接下來,讓我們一起來聊聊動態鏈接器。

8、動態鏈接

好的!我們已從磁盤讀取了字節數據,并啟動了這個“解釋器”。那么,接下來會發生什么呢?如果你執行 strace -o out.strace python3,你會在 execve 系統調用之后觀察到一系列的信息:

execve("/usr/bin/python3", ["python3"], 0x560af13472f0 /* 21 vars */) = 0
brk(NULL)                       = 0xfcc000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=32091, ...}) = 0
mmap(NULL, 32091, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f718a1e3000
close(3)                        = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 l\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=149520, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f718a1e1000
...
close(3)                        = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3

這些內容初看可能讓人望而生畏,但我希望你能重點關注這一部分:openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0" ...。這里正在打開一個被稱為 pthread 的 C 語言線程庫,運行 Python 解釋器時需要這個庫。

自行驗證:如果你想知道一個二進制文件在運行時需要加載哪些庫,你可以使用 ldd 命令。下面展示的是我運行后的效果:

$ ldd /usr/bin/python3.9
    linux-vdso.so.1 (0x00007ffc2aad7000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2fd6554000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2fd654e000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f2fd6549000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2fd6405000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f2fd63d6000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f2fd63b9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2fd61e3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2fd6580000)

你可以看到,第一個列出的庫就是 /lib/x86_64-linux-gnu/libpthread.so.0,這就是它被第一個加載的原因。

關于 LD_LIBRARY_PATH

說實話,我關于動態鏈接的理解還有些模糊,以下是我所了解的一些內容:

  • 動態鏈接發生在用戶空間,我的系統上的動態鏈接器位于 /lib64/ld-linux-x86-64.so.2. 如果你缺少動態鏈接器,可能會遇到一些奇怪的問題,比如這種 奇怪的“文件未找到”錯誤
  • 動態鏈接器使用 LD_LIBRARY_PATH 環境變量來查找庫
  • 動態鏈接器也會使用 LD_PRELOAD 環境變量來覆蓋你想要的任何動態鏈接函數(你可以使用它來進行 有趣的魔改,或者使用像 jemalloc 這樣的替代品來替換默認內存分配器)
  • strace 的輸出中有一些 mprotect,因為安全原因將庫代碼標記為只讀
  • 在 Mac 上,不是使用 LD_LIBRARY_PATH(Linux),而是 DYLD_LIBRARY_PATH

你可能會有疑問,如果動態鏈接發生在用戶空間,我們為什么沒有看到大量的 stat 系統調用在 LD_LIBRARY_PATH 中搜索這些庫,就像 Bash 在 PATH 中搜索那樣?

這是因為 ld 在 /etc/ld.so.cache 中有一個緩存,因此所有之前已經找到的庫都會被記錄在這里。你可以在 strace 的輸出中看到它正在打開緩存 - openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

在 完整的 strace 輸出 中,我仍然對動態鏈接之后出現的一些系統調用感到困惑(什么是 prlimit64?本地環境的內容是如何介入的?gconv-modules.cache 是什么?rt_sigaction 做了什么?arch_prctl 是什么?以及 set_tid_address 和 set_robust_list 是什么?)。盡管如此,我覺得已經有了一個不錯的開頭。

旁注:ldd 實際上是一個簡單的 Shell 腳本!

在 Mastodon 上,有人 指出ldd 實際上是一個 Shell 腳本,它設置了 LD_TRACE_LOADED_OBJECTS=1 環境變量,然后啟動程序。因此,你也可以通過以下方式實現相同的功能:

$ LD_TRACE_LOADED_OBJECTS=1 python3
    linux-vdso.so.1 (0x00007ffe13b0a000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f01a5a47000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f01a5a41000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f2fd6549000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2fd6405000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f2fd63d6000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f2fd63b9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2fd61e3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2fd6580000)

事實上,ld 也是一個可以直接運行的二進制文件,所以你也可以通過 /lib64/ld-linux-x86-64.so.2 --list /usr/bin/python3.9 來達到相同的效果。

關于 init 和 fini

讓我們來談談這行 strace 輸出中的內容:

set_tid_address(0x7f58880dca10) = 3709103

這似乎與線程有關,我認為這可能是因為 pthread 庫(以及所有其他動態加載的庫)在加載時得以運行初始化代碼。在庫加載時運行的代碼位于 init 區域(或者也可能是 .ctors 區域)。

自行驗證:讓我們使用 readelf 來看看這個:

$ readelf -a /lib/x86_64-linux-gnu/libpthread.so.0
...
  [10] .rela.plt         RELA             00000000000051f0  000051f0
       00000000000007f8  0000000000000018  AI       4    26     8
  [11] .init             PROGBITS         0000000000006000  00006000
       000000000000000e  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000006010  00006010
       0000000000000560  0000000000000010  AX       0     0     16
...

這個庫沒有 .ctors 區域,只有一個 .init。但是,.init 區域都有些什么內容呢?我們可以使用 objdump 來反匯編這段代碼:

$ objdump -d /lib/x86_64-linux-gnu/libpthread.so.0
Disassembly of section .init:

0000000000006000 <_init>:
    6000:       48 83 ec 08             sub    $0x8,%rsp
    6004:       e8 57 08 00 00          callq  6860 <__pthread_initialize_minimal>
    6009:       48 83 c4 08             add    $0x8,%rsp
    600d:       c3

所以它在調用 __pthread_initialize_minimal。我在 glibc 中找到了 這個函數的代碼,盡管我不得不找到一個較早版本的 glibc,因為在更近的版本中,libpthread 不再是一個獨立的庫

我不確定這個 set_tid_address 系統調用是否實際上來自 __pthread_initialize_minimal,但至少我們知道了庫可以通過 .init 區域在啟動時運行代碼。

這里有一份關于 .init 區域的 elf 手冊的筆記:

$ man elf

.init 這個區域保存著對進程初始化代碼有貢獻的可執行指令。當程序開始運行時,系統會安排在調用主程序入口點之前執行該區域中的代碼。

在 ELF 文件中也有一個在結束時運行的 .fini 區域,以及其他可以存在的區域 .ctors / .dtors(構造器和析構器)。

好的,關于動態鏈接就說這么多。

9、轉到 _start

在動態鏈接完成后,我們進入到 Python 解釋器中的 _start。然后,它將執行所有正常的 Python 解析器會做的事情。

我不打算深入討論這個,因為我在這里關心的是關于如何在 Linux 上運行二進制文件的一般性知識,而不是特別針對 Python 解釋器。

10、寫入字符串

不過,我們仍然需要打印出 “hello world”。在底層,Python 的 print 函數調用了 libc 中的某個函數。但是,它調用了哪一個呢?讓我們來找出答案!

自行驗證:運行 ltrace -o out python3 hello.py

$ ltrace -o out python3 hello.py
$ grep hello out
write(1, "hello world\n", 12) = 12

看起來它確實在調用 write 函數。

我必須承認,我對 ltrace 總是有一些疑慮 —— 與我深信不疑的 strace 不同,我總是不完全確定 ltrace 是否準確地報告了庫調用。但在這個情況下,它似乎有效。并且,如果我們查閱 cpython 的源代碼,它似乎在一些地方確實調用了 write() 函數,所以我傾向于相信這個結果。

什么是 libc?

我們剛剛提到,Python 調用了 libc 中的 write 函數。那么,libc 是什么呢?它是 C 的標準庫,負責許多基本操作,例如:

  • 用 malloc 分配內存
  • 文件 I/O(打開/關閉文件)
  • 執行程序(像我們之前提到的 execvp
  • 使用 getaddrinfo 查找 DNS 記錄
  • 使用 pthread 管理線程

在 Linux 上,程序不一定需要使用 libc(例如 Go 就廣為人知地未使用它,而是直接調用了 Linux 系統調用),但是我常用的大多數其他編程語言(如 node、Python、Ruby、Rust)都使用了 libc。我不確定 Java 是否也使用了。

你能通過在你的二進制文件上執行 ldd 命令,檢查你是否正在使用 libc:如果你看到了 libc.so.6 這樣的信息,那么你就在使用 libc。

為什么 libc 重要?

你也許在思考 —— 為何重要的是 Python 調用 libc 的 write 函數,然后 libc 再調用 write 系統調用?為何我要著重提及 libc 是調用過程的一環?

我認為,在這個案例中,這并不真的很重要(根據我所知,libc 的 write 函數與 write 系統調用的映射相當直接)。

然而,存在不同的 libc 實現,有時它們的行為會有所不同。兩個主要的實現是 glibc(GNU libc)和 musl libc。

例如,直到最近,musl 的 getaddrinfo這是一篇關于這個問題引發的錯誤的博客文章

關于 stdout 和終端的小插曲

在我們的程序中,stdout(1 文件描述符)是一個終端。你可以在終端上做一些有趣的事情!例如:

  1. 在終端中運行 ls -l /proc/self/fd/1。我得到了 /dev/pts/2 的結果。
  2. 在另一個終端窗口中,運行 echo hello > /dev/pts/2
  3. 返回到原始終端窗口。你應會看到 hello 被打印出來了!

暫時就到這兒吧!

希望通過上文,你對 hello world 是如何打印出來的有了更深的了解!我暫時不再添加更多的細節,因為這篇文章已經足夠長了,但顯然還有更多的細節可以探討,如果大家能提供更多的細節,我可能會添加更多的內容。如果你有關于我在這里沒提到的程序內部調用過程的任何工具推薦,我會特別高興。

我很期待看到一份 Mac 版的解析

我對 Mac OS 的一個懊惱是,我不知道如何在這個級別上解讀我的系統——當我打印 “hello world”,我無法像在 Linux 上那樣,窺視背后的運作機制。我很希望看到一個深度的解析。

我所知道的一些在 Mac 下的對應工具:

  • ldd -> otool -L
  • readelf -> otool
  • 有人說你可以在 Mac 上使用 dtruss 或 dtrace 來代替 strace,但我尚未有足夠的勇氣關閉系統完整性保護來讓它工作。
  • strace -> sc_usage 似乎能夠收集關于系統調用使用情況的統計信息,fs_usage 則可以收集文件使用情況的信息。

延伸閱讀

一些附加的鏈接:

責任編輯:龐桂玉 來源: Linux中國
相關推薦

2010-02-07 09:00:29

AndroidLinux Kerne

2009-08-11 10:32:23

什么是Groovy

2018-01-12 15:32:55

大數據DBA數據庫管理員

2021-12-16 15:58:48

Linux內存微軟

2021-02-25 10:02:32

開機鍵Linux內存

2020-08-17 12:47:07

Mozilla裁員瀏覽器

2019-11-12 14:41:41

Redis程序員Linux

2017-09-06 16:20:51

2010-01-07 13:27:22

Linux驅動程序

2019-08-26 09:35:25

命令ping抓包

2021-01-18 08:23:23

內存時底層CPU

2021-04-11 10:40:16

Git軟件開發

2014-12-19 10:07:10

C

2017-11-23 17:45:46

Yii框架IntelYii框架深度剖析

2015-07-03 09:27:43

網絡閏秒

2019-09-16 17:16:29

Hadoop數據湖數據結構

2022-06-03 08:12:52

InnoDB插入MySQL

2020-09-01 11:40:01

HTTPJavaTCP

2020-08-20 11:50:31

語言類型轉換代碼

2021-11-23 23:31:43

C語言數據類型系統
點贊
收藏

51CTO技術棧公眾號

麻豆传媒视频在线观看| 国产成人亚洲欧洲在线| 日韩成人一区| 亚洲狼人国产精品| 狠狠色噜噜狠狠色综合久| 大伊香蕉精品视频在线| 蜜桃视频污在线观看| 夜夜嗨av一区二区三区网站四季av| 日韩欧美精品网站| 亚洲精品一区二| 精品久久国产视频| 午夜亚洲影视| 久久久精品久久久久| 国产毛片视频网站| 韩国三级av在线免费观看| 久久91精品久久久久久秒播| 久久久久久久久综合| 五月婷婷之婷婷| 欧美aaaaaaa| 国产视频视频一区| 91亚洲精品久久久久久久久久久久| 538精品在线观看| 国产区精品区| 欧美成人aa大片| 国产成人手机视频| 55av亚洲| 亚洲精品成人精品456| 日本精品免费| 婷婷在线免费视频| 久久99精品国产.久久久久久| 97超级碰碰人国产在线观看| 精品呦交小u女在线| 99re在线视频上| 欧美色图17p| 欧美人妖在线观看| 欧美一区二区三区色| 国产成人无码av在线播放dvd| 中文在线观看免费| 激情文学综合丁香| 欧美专区在线观看| 懂色av.com| 91国语精品自产拍| 最近2019年中文视频免费在线观看 | 成年网站在线播放| 视频一区 中文字幕| 麻豆精品一区二区综合av| 中文字幕亚洲色图| 91网站免费视频| 欧美aaaaa级| 亚洲精品一区二区三区99| 初高中福利视频网站| 中文成人在线| 欧美电影在线免费观看| 日韩精品你懂的| 中国字幕a在线看韩国电影| 久久久99免费| 欧美日韩在线观看一区| 青青久草在线| 国产性色一区二区| 日韩精品另类天天更新| 国产美女性感在线观看懂色av| 91麻豆蜜桃一区二区三区| 国内不卡一区二区三区| 成人精品在线播放| av一区二区三区黑人| 国产另类自拍| 亚洲欧洲成人在线| 免费的黄色av| 国产一区啦啦啦在线观看| 成人黄色影片在线| 国产三级漂亮女教师| 国产一区二区三区av电影| 91久久极品少妇xxxxⅹ软件| 超碰人人人人人人| 成人妖精视频yjsp地址| 国产精品亚洲综合| 深夜视频在线免费| 日韩电影在线观看一区| 国产精品久久久久久久久久久久| 成年人视频免费| 日韩精品一级中文字幕精品视频免费观看 | 亚洲成人综合视频| 91黄色小网站| 日韩成人在线电影| 日韩精品综合一本久道在线视频| 国产xxxx视频| av中文字幕一区二区| 久久久精品网站| 日本熟妇色xxxxx日本免费看| 免播放器亚洲| 亚洲精品日韩激情在线电影| 欧美成人高潮一二区在线看| 天堂中文在线官网| 久久精品视频一区二区| 一区二区三区四区五区视频| 色呦呦在线视频| 一本色道久久综合亚洲aⅴ蜜桃 | 精品一区二区三区四| 国产欧美三级| 国产一区二区在线播放| 亚洲精品人妻无码| 国产欧美日韩三级| 毛片在线视频观看| 美女100%一区| 亚洲欧美另类久久久精品2019| 日韩精品久久一区二区| 原纱央莉成人av片| 91精品婷婷国产综合久久性色| av av在线| 欧美国产美女| 欧日韩不卡在线视频| 国产jzjzjz丝袜老师水多| 久久久久久久电影| 国产乱子伦精品无码专区| 全球最大av网站久久| 亚洲精品国产精品乱码不99按摩| 中文字幕日韩专区| 国产精品久久久久久亚洲色 | 超级白嫩亚洲国产第一| 欧美日韩国产三级| 中文字幕在线免费看线人| 中文在线日韩| 国产精品女主播视频| 久久久久久久久久久久久久av| 久久国产人妖系列| 欧美另类一区| 久久av色综合| 91.com视频| 在线观看亚洲大片短视频| 99热在线精品观看| 成人一区二区三区四区| 久操视频在线| 欧美精品在线一区二区三区| 非洲一级黄色片| 日韩视频免费| 国产在线播放一区二区| 综合图区亚洲| 在线电影国产精品| 国产又粗又猛又爽又黄的视频四季| 一区二区91| 国产伦精品一区二区三区视频黑人 | 亚洲成a人片| 国产精品一区二区久激情瑜伽| 日韩欧美精品在线观看| 樱花草www在线| 97成人超碰| 欧美精品色一区二区三区| 精品成人无码一区二区三区| 免费亚洲婷婷| 精品日本一区二区| 9999精品成人免费毛片在线看 | 午夜精品久久久久久久99黑人 | 亚洲一区二区久久| 久久久久99精品成人片我成大片| 北条麻妃国产九九精品视频| 国产一级做a爰片久久毛片男| 我要色综合中文字幕| 欧美成人激情视频免费观看| 精品人妻一区二区三区三区四区 | 农村妇女一区二区| 在线观看国产精品91| 做爰视频毛片视频| 国产精品免费视频一区| 天天干天天av| 欧美一区二区三区久久精品茉莉花 | 日本五十熟hd丰满| 91女厕偷拍女厕偷拍高清| 男人用嘴添女人下身免费视频| 欧美男人操女人视频| 日本高清久久天堂| av电影在线观看| 91精品在线观看入口| 久草视频免费在线播放| 成人蜜臀av电影| 情侣黄网站免费看| 区一区二视频| 91影院未满十八岁禁止入内| a在线视频v视频| 亚洲天堂av电影| 91亚洲国产成人精品一区| 欧美激情777| 一本到不卡免费一区二区| 中文在线一区二区三区| 曰本一区二区三区视频| 国产精品精品国产| www在线观看播放免费视频日本| 精品日韩欧美在线| 香蕉影院在线观看| 中文字幕中文乱码欧美一区二区| 午夜诱惑痒痒网| 99成人在线| 亚洲人成网站在线播放2019| 亚洲一级大片| 国产精品久久久久99| 性爱视频在线播放| 亚洲天堂一区二区三区| 国产丝袜视频在线观看| 精品久久久久久中文字幕一区奶水| 国产精品天天干| 国产成a人亚洲精| 日本黄网免费一区二区精品| 成人自拍视频| 欧美在线视频a| 18视频在线观看| 亚洲午夜av久久乱码| 国产wwwxxx| 在线观看视频一区二区 | 久久高清精品| 久久草.com| 久久久91麻豆精品国产一区| 一区二区三区回区在观看免费视频| 国产男女裸体做爰爽爽| 一本一本大道香蕉久在线精品| 欧美毛片在线观看| 中文字幕第一页久久| 欧美一级片黄色| 国产乱码精品一区二区三区五月婷 | 高清国产在线一区| 黄色日韩网站| 日韩av电影国产| av中文字幕在线看| 久久亚洲春色中文字幕| 无码人妻一区二区三区免费| 一区二区三区国产豹纹内裤在线 | www黄色网址| 欧美日韩免费在线视频| 欧美三日本三级少妇三99| 69视频在线| 精品无人区乱码1区2区3区在线| 99久久亚洲精品日本无码 | 久久精品国产精品亚洲| 黄视频在线观看免费| 亚洲精品福利在线| 欧洲成人一区二区三区| 日韩视频免费观看高清完整版在线观看 | aaa国产一区| wwwww在线观看| 国产一区日韩二区欧美三区| the porn av| 日本免费新一区视频| 情侣黄网站免费看| 亚洲欧美日韩国产| 青青草视频在线免费播放| 亚洲成人一区| 加勒比海盗1在线观看免费国语版| 99精品一区| 一本一本久久a久久精品综合妖精| 国产一区二区欧美| 久久久综合亚洲91久久98| 里番精品3d一二三区| 国产精品日韩一区二区| 98视频精品全部国产| 欧美国产日韩一区二区| 精精国产xxxx视频在线| 精品国产一区二区三区久久| 欧美jizz18hd性欧美| 精品国模在线视频| 在线免费看v片| 91九色综合| 国产成人精品电影久久久| 欧美裸体视频| 国产成人av网| 亚洲天堂网站| 亚洲一区二区在线| 9l视频自拍蝌蚪9l视频成人| 国产一区国产精品| 欧美禁忌电影网| 亚洲一区3d动漫同人无遮挡| **女人18毛片一区二区| japanese在线播放| 亚洲欧洲一区二区天堂久久| 欧美日韩亚洲一| 97久久夜色精品国产| 一区二区三区的久久的视频| 一区二区免费不卡在线| 超薄肉色丝袜足j调教99| 激情综合久久| 成年人网站大全| 精品无码三级在线观看视频| 中文字幕一区二区三区人妻在线视频| aaa国产一区| 992在线观看| 亚洲国产va精品久久久不卡综合| 亚洲日本视频在线观看| 欧美在线你懂得| 国产夫妻性生活视频| 精品亚洲一区二区三区在线播放| 成人在线免费视频| 日韩av在线免费观看| 黄色av免费在线观看| 麻豆成人在线看| 忘忧草在线影院两性视频| 国产一区二区丝袜| 欧美天堂影院| 久久av秘一区二区三区| 亚洲免费高清| 国内外成人免费在线视频| 粉嫩av一区二区三区粉嫩| 性猛交娇小69hd| 亚洲国产视频直播| 黄色av一区二区| 精品88久久久久88久久久| 91精品国产91久久久久游泳池| 欧美日本精品在线| 成人亚洲视频| 久久久福利视频| 欧美 日韩 国产精品免费观看| 国产极品美女高潮无套久久久| 国产精品91一区二区| 国产三级生活片| 成人av在线影院| 国精品人伦一区二区三区蜜桃| 同产精品九九九| 国产男男gay网站| 在线日韩中文字幕| 蜜桃视频在线观看播放| 97人人模人人爽视频一区二区| 精品精品99| 欧美性大战久久久久xxx| 国产精品18久久久久久久网站| 一级在线观看视频| 欧美性猛xxx| 免费观看成年人视频| 粗暴蹂躏中文一区二区三区| 羞羞影院欧美| 麻豆成人av| 一区二区激情| 成人在线电影网站| 一区二区三区小说| 国产精品无码久久av| 综合欧美国产视频二区| 3d欧美精品动漫xxxx无尽| 精品国产一区二区三区麻豆小说 | 无码播放一区二区三区| 国产精品中文字幕日韩精品| www.com.av| 欧美美女一区二区在线观看| 国产三级视频在线| 日本不卡免费高清视频| 亚洲精品无吗| 99蜜桃臀久久久欧美精品网站| av不卡免费电影| 国产第一页在线播放| 天涯成人国产亚洲精品一区av| 精品黑人一区二区三区在线观看| 久久久国产视频| 成人精品在线| 久久综合亚洲精品| 国产成人aaa| 日韩av电影网| 国产视频久久久久久久| 中文字幕一区二区人妻| 日韩精品成人一区二区三区| 无码成人精品区在线观看| 亚洲制服丝袜在线| 亚洲第一视频在线| 欧美国产日韩中文字幕在线| 在线观看视频一区二区三区 | 亚洲国产欧美国产综合一区| 在线观看一区二区三区四区| 亚洲一区电影777| 污污网站免费在线观看| 国产精品999999| 婷婷亚洲图片| 亚洲自拍偷拍精品| 欧美日韩亚洲国产一区| 中文字幕日韩经典| xxxx性欧美| 日韩有吗在线观看| 日本xxxxxxxxxx75| 精品一二三四区| 国产精品久久久精品四季影院| 日韩三级高清在线| 九色porny丨首页入口在线| 日本一区二区三区在线视频| 捆绑紧缚一区二区三区视频| 印度午夜性春猛xxx交| 亚洲成人网在线观看| 成人天堂yy6080亚洲高清 | 在线日韩中文| www.av天天| 欧美一区二区三区四区五区| 1234区中文字幕在线观看| 欧美一区二区影视| 国产一区二区三区观看| 国产一区二区三区影院| 少妇激情综合网| 51社区在线成人免费视频| 国产成人久久777777| 一区在线播放视频| 天堂成人在线观看| 国产精品视频一区二区高潮| 国产一在线精品一区在线观看| 亚洲做受高潮无遮挡| 一片黄亚洲嫩模| 黄色在线播放| wwwxx欧美| 免费日本视频一区| 日本精品一区二区三区在线播放视频| 影音成人av|