首页
论坛
电子技术基础
模拟技术
可编程器件
嵌入式系统与MCU
工程师职场
最新帖子
问答
版主申请
每月抽奖
商城免费换礼
社区有奖活动
博客
下载
评测
视频
文库
芯语
资源
IIC Shanghai 2026
行业及技术活动
嵌入式设计资源库
杂志免费订阅
EE直播间
白皮书
小测验
在线研讨会
免费在线工具
厂商资源中心
论坛
博文
电子工程专辑
电子技术设计
国际电子商情
资料
白皮书
研讨会
芯语
文库
登录|注册
登录
用户116683
修改
文章:
82
阅读:
204859
评论:
22
赞:
1826
好友
私信
个人主页
文章
82
原创
0
阅读
204859
评论
22
赞
1826
原创
Hello World 背后的真实故事(转)
2011-9-9 17:23
1825
27
27
分类:
工程师职场
*
原作者
:Ant?nio Augusto M. Fr?hlich
*
原文链接
:
http://www.lisha.ufsc.br/~guto/teaching/os/exercise/hello.html
*
译者
:杨文博 <
http://blog.solrex.cn
>
*
译文链接
:
http://share.solrex.cn/os/hello_cn.html
*
最后更新时间
: 2008 年 2 月 28 日
我们计算机科学专业的大多数学生至少都接触过一回著名的 "Hello World"程序。相比一个典型的应用程序——几乎总是有一个带网络连接的
源代码
让我们先看一下 Hello World 的源代码:
1. #include <stdio.h>
2. int main(void)
3. {
4. printf("Hello World!\n");
5. return 0;
6.
7. }
第 1 行指示编译器去包含调用 C 语言库(libc)函数 printf 所需要的头文件声明。
第 3 行声明了 main函数,看起来好像是我们程序的入口点(在后面我们将看到,其实它不是)。它被声明为一个不带参数(我们这里不准备理会命令行参数)且会返回一个整 型值给它的父进程(在我们的例子里是 shell)的函数。顺便说一下,shell 在调用程序时对其返回值有个约定:子进程在结束时必须返回一个 8比特数来代表它的状态:0 代表正常结束,0~128 中间的数代表进程检测到的异常终止,大于 128 的数值代表由信号引起的终止。
从第 4 行到第 8 行构成了 main 函数的实现,即调用 C 语言库函数 printf 输出 "Hello World!\n" 字符串,在结束时返回 0 给它的父进程。
简单,非常简单!
编译
现在让我们看看 "Hello World" 的编译过程。在下面的讨论中,我们将使用非常流行的 GNU 编译器(gcc)和它的二进制辅助工具(binutils)。我们可以使用下面命令来编译我们的程序:
# gcc -Os -c hello.c
这样就生成了目标文件 hello.o,来看一下它的属性:
# file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
给出的信息告诉我们 hello.o 是个可重定位的目标文件(relocatable),为 IA-32(IntelArchitecture 32) 平台编译(在这个练习中我使用了一台标准 PC),保存为 ELF(Executable and LinkingFormat) 文件格式,并且包含着符号表(not stripped)。
顺便:
# objdump -hrt hello.o
hello.o: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000011 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000048 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000048 2**2
ALLOC
3 .rodata.str1.1 0000000d 00000000 00000000 00000048 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000033 00000000 00000000 00000055 2**0
CONTENTS, READONLY
SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .rodata.str1.1 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000011 main
00000000 *UND* 00000000 puts
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000004 R_386_32 .rodata.str1.1
00000009 R_386_PC32 puts
这告诉我们 hello.o 有 5 个段:
(译者注:在下面的解释中读者要分清什么是 ELF 文件中的段(section)和进程中的段(segment)。比如 .text 是ELF 文件中的段名,当程序被加载到内存中之后,.text 段构成了程序的可执行代码段。其实有时候在
中文
环境下也称 .text段为代码段,要根据上下文分清它代表的意思。)
1. .text: 这是 "Hello World" 编译生成的可执行代码,也就是说这个程序对应的 IA-32 指令序列。.text 段将被加载程序用来初始化进程的代码段。
2. .data:"Hello World" 的程序里既没有初始化的全局变量也没有初始化的静态局部变量,所以这个段是空的。否则,这个段应该包含变量的初始值,运行前被装载到进程的数据段。
3. .bss: "Hello World" 也没有任何未初始化的全局或者局部变量,所以这个段也是空的。否则,这个段指示的是,在进程的数据段中除了上文的 .data 段内容,还有多少字节应该被分配并赋 0。
4. .rodata: 这个段包含着被标记为只读 "Hello World!\n"字符串。很多操作系统并不支持进程(运行的程序)有只读数据段,所以 .rodata段的内容既可以被装载到进程的代码段(因为它是只读的),也可以被装载到进程的数据段(因为它是数据)。因为编译器并不知道你的操作系统所 使用的策略,所以它额外生成了一个 ELF 文件段。
5. .comment:这个段包含着 33 字节的注释。因为我们在代码中没有写任何注释,所以我们无法追溯它的来源。不过我们将很快在下面看到它是怎么来的。
它也给我们展示了一个符号表(symbol table),其中符号 main 的地址被设置为 00000000,符号 puts未定义。此外,重定位表(relocation table)告诉我们怎么样去在 .text段中去重定位对其它段内容的引用。第一个可重定位的符号对应于 .rodata 中的 "Hello World!\n" 字符串,第二个可重定位符号puts,代表了使用 printf 所产生的对一个 libc 库函数的调用。为了更好的理解 hello.o 的内容,让我们来看看它的汇编代码:
1. # gcc -Os -S hello.c -o -
2. .file "hello.c"
3. .section .rodata.str1.1,"aMS",@progbits,1
4. .LC0:
5. .string "Hello World!"
6. .text
7. .align 2
8. .globl main
9. .type main,@function
10. main:
11. pushl %ebp
12. movl %esp, %ebp
13. pushl $.LC0
14. call puts
15. xorl %eax, %eax
16. leave
17. ret
18. .Lfe1:
19. .size n,.Lfe1-n
20. .ident "GCC: (GNU) 3.2 20020903 (Red Hat
Linux
8.0 3.2-7)"
从汇编代码中我们可以清楚的看到 ELF 段标记是怎么来的。比如,.text 段是 32 位对齐的(第 7 行)。它也揭示了.comment 段是从哪儿来的(第 20 行)。因为我们使用 printf来打印一个字符串,并且我们要求我们优秀的编译器对生成的代码进行优化(-Os),编译器用(应该更快的) puts 调用来取代 printf调用。不幸的是,我们后面将会看到我们的 libc 库的实现会使这种优化变得没什么用。
那么这段汇编代码会生成什么代码呢?没什么意外之处:使用标志字符串地址的标号 .LCO 作为参数的一个对 puts 库函数的简单调用。
连接
下面让我们看一下 hello.o 转化为可执行文件的过程。可能会有人觉得用下面的命令就可以了:
# ld -o hello hello.o -lc
ld: warning: cannot find entry symbol _start; defaulting to 08048184
不过,那个警告是什么意思?尝试运行一下!
是的,hello 程序不工作。让我们回到那个警告:它告诉我们连接器(ld)不能找到我们程序的入口点 _start。不过 main难道不是入口点吗?简短的来说,从程序员的角度来看 main 可能是一个 C 程序的入口点。但实际上,在调用 main之前,一个进程已经执行了一大堆代码来“为可执行程序清理房间”。我们通常情况下从编译器或者操作系统提供者那里得到这些外壳程序 (surrounding code,译者注:比如 CRT)。
下面让我们试试这个命令:
# ld -static -o hello -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -lgcc
现在我们可以得到一个真正的可执行文件了。使用静态连接(staticlinking)有两个原因:一,在这里我不想深入去讨论动态连接库 (dynamiclibraries)是怎么工作的;二,我想让你看看在我们库(libc 和 libgcc)的实现中,有多少不必要的代码将被添加到 "HelloWorld" 程序中。试一下这个命令:
# find hello.c hello.o hello -printf "%f\t%s\n"
hello.c 84
hello.o 788
hello 445506
你也可以尝试 "nm hello" 和 "objdump -d hello" 命令来得到什么东西被连接到了可执行文件中。
想了解动态连接的更多内容,请参考
Program Library HOWTO
装载和运行
在一个遵循 POSIX(Portable Operating System Interface)标准的操作系统(OS)上,装载一个程序是由父进程发起 fork 系统调用来复制自己,然后刚生成的子进程发起 execve系统调用来装载和执行要运行的程序组成的。无论何时你在 shell 中敲入一个外部命令,这个过程都会被实施。你可以使用 truss 或者trace 命令来验证一下:
# strace -i hello > /dev/null
[????????] execve("./hello", ["hello"], [/* 46 vars */]) = 0
...
[08053d44] write(1, "Hello World!\n", 13) = 13
...
[0804e7ad] _exit(0) = ?
除了 execve 系统调用,上面的输出展示了打印函数 puts 中的 write 系统调用,和用 main 的返回值(0)作为参数的 exit 系统调用。
为了解 execve 实施的装载过程背后的细节,让我们看一下我们的 ELF 可执行文件:
# readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x80480e0
There are 3 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x55dac 0x55dac R E 0x1000
LOAD 0x055dc0 0x0809edc0 0x0809edc0 0x01df4 0x03240 RW 0x1000
NOTE 0x000094 0x08048094 0x08048094 0x00020 0x00020 R 0x4
Section to Segment mapping:
Segment Sections...
00 .init .text .fini .rodata __libc_atexit __libc_subfreeres .note.ABI-tag
01 .data .eh_frame .got .bss
02 .note.ABI-tag
输出显示了 hello 的整体结构。第一个程序头对应于进程的代码段,它将从文件偏移 0x000000 处被装载到映射到进程地址空间的0x08048000 地址的物理内存中(虚拟内存机制)。代码段共有 0x55dac 字节大小而且必须按页对齐(0x1000,page-aligned)。这个段将包含我们前面讨论过的 ELF 文件中的 .text 段和 .rodata段的内容,再加上在连接过程中生成的附加的段。正如我们预期,它被标志为:只读(R)和可执行(X),不过禁止写(W)。
第二个程序头对应于进程的数据段。装载这个段到内存的方式和上面所提到的一样。不过,需要注意的是,这个段占用的文件大小是 0x01df4字节,而在内存中它占用了 0x03240 字节。这个差异主要归功于 .bss 段,它在内存中只需要被赋0,所以不用在文件中出现(译者注:文件中只需要知道它的起始地址和大小即可)。进程的数据段仍然需要按页对齐 (0x1000,page-aligned)并且将包含 .data 和 .bss段。它将被标识为可读写(RW)。第三个程序头是连接阶段产生的,和这里的讨论没有什么关系。
如果你有一个 proc 文件系统,当你得到 "Hello World" 时停止进程(提示: gdb,译者注:用 gdb 设置断点),你可以用下面的命令检查一下是不是如上所说:
# cat /proc/`ps -C hello -o pid=`/maps
08048000-0809e000 r-xp 00000000 03:06 479202 .../hello
0809e000-080a1000 rw-p 00055000 03:06 479202 .../hello
080a1000-080a3000 rwxp 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0
第一个映射的区域是这个进程的代码段,第二个和第三个构成了数据段(data + bss + heap),第四个区域在 ELF文件中没有对应的内容,是程序栈。更多和正在运行的 hello 进程有关的信息可以用 GNU 程序:time, ps 和/proc/pid/stat 得到。
程序终止
当 "Hello World" 程序运行到 main 函数中的 return语句时,它向我们在段连接部分讨论过的外壳函数传入了一个参数。这些函数中的某一个发起 exit 系统调用。这个 exit系统调用将返回值转交给被 wait系统调用阻塞的父进程。此外,它还要对终止的进程进行清理,将其占用的资源还给操作系统。用下面命令我们可以追踪到部分过程:
# strace -e trace=process -f sh -c "hello; echo $?" > /dev/null
execve("/bin/sh", ["sh", "-c", "hello; echo 0"], [/* 46 vars */]) = 0
fork() = 8321
[pid 8320] wait4(-1, <unfinished ...>
[pid 8321] execve("./hello", ["hello"], [/* 46 vars */]) = 0
[pid 8321] _exit(0) = ?
<... wait4 resumed> [WIFEXITED(s) && WEXITSTATUS(s) == 0], 0, NULL) = 8321
--- SIGCHLD (Child exited) ---
wait4(-1, 0xbffff06c, WNOHANG, NULL) = -1 ECHILD (No child processes)
_exit(0)
结束
这个练习的目的是让计算机专业的新生注意这样一个事实:一个 Java Applet 的运行并不是像魔法一样(无中生有的),即使在最简单的程序背后也有很多系统软件的支撑。如果您觉得这篇文章有用并且想提供建议来改进它,请
发电子邮件给我
。
常见问题
这一节是为了回答学生们的常见问题。
* 什么是 "libgcc"? 为什么它在连接的时候被包含进来?
编译器内部的函数库,比如 libgcc,是用来实现目标平台没有直接实现的语言元素。举个例子,C 语言的模运算符 ("%")在某个平台上可能无法映射到一条汇编指令。可能用一个函数调用实现比让编译器为其生成内嵌代码更受欢迎(特别是对一些内存受限的计算机来说,比 如微控制器)。很多其它的基本运算,包括除法、乘法、字符串处理(比如 memory copy)一般都会在这类函数库中实现。
工程师专属征文!分享知识经验,就能拿奖励~
最新发表
推荐阅读
明星博主
原创博文
年度排行
博文排行
博文评论
FPGA/CPLD
MCU/ 嵌入式
模拟
电源/新能源
测试测量
通信
智能手机
处理器与DSP
PCB
汽车电子
消费电子
智能硬件
物联网
软件与OS
采购与分销
供应链管理
工程师职场
EDA/ IP/ 设计与制造
无人机
机器人/ AI
医疗电子
工业电子
管理
写博文
点赞(
27
)
收藏
分享到:
上一篇:
程序运行的原理(转)
下一篇:
从源代码编译和安装程序(转)
PARTNER CONTENT
换一换>
更多>
STC32车规级MCU中国芯赋能潍柴玉柴,铸就重型柴油机尾气后处理“中国方案”
STC
2026-04-29
不用改PCB不用搭环境!带16bit ADC 的全国产正向设计2837x系列DSP来了,工程师直呼太香了
2026-04-09
STC32车规级MCU中国芯赋能潍柴玉柴,铸就重型柴油机尾气后处理“中国方案”
2026-04-29
文章评论
(
0
条评论)
登录
后参与讨论
您需要登录后才可以评论
登录
|
立即注册
发布
用户116683
修改
文章:
82
阅读:
204859
评论:
22
赞:
1826
好友
私信
个人主页
文章
82
原创
0
阅读
204859
评论
22
赞
1826
最新评论
更多
linkissrj : 这个问题好像在中国无解,最终导致朝代更迭 所以好奇,人类看似不是地球上的原生态物种?可又是从哪儿来的呢 ...
自做自受 ...
评论博文
2026-5-5
【2026拆解】一款3天线的无线路由器,极致 ...
自做自受 : 极致降本=极致降质=极致垃圾=极致污染=极致害己 这个问题好像在中国无解,最终导致朝代更迭 ...
linkissrj
评论博文
2026-5-5
【2026拆解】一款3天线的无线路由器,极致 ...
呵呵,俺还保留着几十年前做电工时最简单的试电笔,就一个电阻和一个发光管串联,只看有电没电就是了,有时忘带了或应急,就用手 ...
自做自受 ...
评论博文
2026-5-4
【2026拆解】一款多功能测电笔,带显示屏的 ...
最新
博文
功利主义&实用主义&马基雅维利主义 ...
海尔智家将注销A股100%库存股,每股收 ...
【2026拆解】一款可充电的小夜灯,远 ...
资料下载
本周热帖
PWM-IC
DC-DC可调升降压,大功率
锂电池管理,
第12章 化学机械抛光(Chemical Mecha ...
《第13章 工艺集成》中英文对照文档( ...
【CW32L012 开发板 32 位 MCU 入门首 ...
《EDA简史》+ 读后感想
【2026拆解】10年前的手持式血糖仪, ...
最新资讯
芯语最新
剑指行业Top 5!国科微车载芯片战略 ...
开源AI势头强劲:成本上升推动向小型 ...
从Kindle到AR眼镜:一家厂商的“隐形 ...
5nm舱驾融合芯片落地,芯擎科技重新定 ...
从“盲人眼镜”到“物理AI”,如何用 ...
无刷电机控制方法之换相
拆解三星家庭影院DVD播放机HT-350K: ...
国内召回|本田摩托车销售(上海)有 ...
Lattice计划收购固件大厂AMI;苹果考 ...
DER-931测评
EE直播间
更多
芯品星期三 — MCU专场
直播时间: 05月13日 15:00
【瑞萨RA MCU软件架构与开发实践】第一讲:FSP 分层模型与设计哲学:解读 FSP的 BSP、HAL与中间件架构
直播时间: 05月20日 19:30
【瑞萨RA MCU软件架构与开发实践】第二讲:掌握图形化配置器与代码生成:使用FSP 配置器快速构建复杂外设应用
直播时间: 05月27日 19:30
【瑞萨RA MCU软件架构与开发实践】第三讲:FSP 在 Keil /VSCode 下的高效开发实践
直播时间: 06月03日 19:30
在线研讨会
更多
借助MEMS开关在高带宽内存和GPU中实现AI环回测试
超高速ADC在高端仪器仪表及通信领域的应用
PLCA 协调器冗余机制,全面提升 10BASE‑T1S 多点网络的系统可靠性
新思科技汽车电子芯片功能安全性端到端解决方案介绍
热门
推荐
当产线等料时,你需要一个能“救火”的供应商
为你的BOM表,多找一个可靠的备份
“傻瓜式”高密度电源设计
如何应对超快速的频率跳变需求?
我要评论
0
27
分享到微信
分享到微博
分享到QQ
点击右上角,分享到朋友圈
我知道啦
请使用浏览器分享功能
我知道啦
关闭
站长推荐
/4
2026年社区活动汇总
为了方便大家查找2026年最新活动,板哥将活动都汇总在这里啦
捕捉瞬息信号,贯通光通信:超高速ADC的技术突破与应用前沿 ...
凡参与研讨会均有机会获得礼品套装哦~
2026分享优质内容!一起分300000E币!
你的每一条帖子和回复,都是社区的星光!发得越多,奖励越多! 活动时间:2026年全年(或发完30万E币为止!)
2026年第一季拆解:洞见的电子设计与工程美学
拆解是洞察、审美与创造的一个新启点。新的一年,我们期待更多“老炮”和“新手”,参与这场探索硬件本质的旅程。
首页
论坛
电子技术基础
模拟技术
可编程器件
嵌入式系统与MCU
工程师职场
最新帖子
问答
版主申请
每月抽奖
商城免费换礼
社区有奖活动
博客
下载
评测
视频
文库
芯语
资源
IIC Shanghai 2026
行业及技术活动
嵌入式设计资源库
杂志免费订阅
EE直播间
白皮书
小测验
在线研讨会
免费在线工具
厂商资源中心
帖子
博文
返回顶部
×
文章评论(0条评论)
登录后参与讨论