type
date
slug
category
icon
password
操作系统
内存管理
可执行文件的section组成(如代码段、数据段、BSS段)和处理器内存布局。
网络原理
处理器架构
编译器原理
嵌入式系统可执行文件格式说明
嵌入式开发面临处理器平台和软件生态的碎片化与多样性。为兼顾性价比,系统配置灵活多变:CPU架构各异(ARM、RISC-V、x86),ARM架构下又分不同系列(如STM32的Cortex-M和Raspberry Pi/i.MX的Cortex-A);存储容量不同,决定使用RTOS或Linux;启动方式多样,微控制器(如STM32、AVR)从Flash启动,而应用处理器(如Raspberry Pi、i.MX)从外部存储器启动。
因此,嵌入式系统生成的二进制文件格式也多种多样:
.out (a.out)
:Unix系统原始可执行格式,assembler output缩写,现已少用,但可能在部分嵌入式工具链中保留。包含段头部表(text/data/bss)和符号表等。
.exe (PE)
:Windows标准可执行格式,采用复杂的PE结构,不适用于裸机环境,依赖完整OS。
.axf (ARM Executable Format)
:ARM工具链(Keil/ARMCC)生成的ELF变种,内嵌调试信息和交叉引用数据,专为ARM调试优化。
.elf (Executable and Linkable Format)
:Unix-like系统标准格式,嵌入式GCC工具链默认生成,支持动态链接,保留符号表和调试信息,包含程序头描述加载方式。
.exe
和.elf
分别对应Windows和Linux下编译的可执行文件,需要操作系统加载器支持。嵌入式微控制器通常生成.axf
或.elf
,由于缺乏加载器支持且存储空间有限,需转换为.bin
或.hex
。转换目的在于精简文件(去除调试信息和符号表)并适应无OS环境下的静态烧录。这决定了烧录器生态普遍支持HEX/BIN直接烧录。操作系统和裸机环境下程序的运行原理
操作系统下运行可执行文件主要包括加载和运行两个过程。加载过程是根据链接确定的起始地址,将程序从ROM加载到不同内存段中。程序运行则是CPU从内存读取并执行指令的过程。对于操作系统,需考虑虚拟地址和物理地址管理。
裸机环境下,由于缺乏程序运行环境,需借助第三方工具将编译好的可执行文件烧录到内存的指定RAM物理地址。
命令行中输入 ./a.out
后发生了什么
在Linux系统中,当用户在命令行中输入
./a.out
时,程序的运行需要经过从用户指令到内核介入、再到进程创建和程序执行的多个步骤。以下是详细的流程分析:流程总览
用流程图和主要步骤总结整个过程:
详细步骤解释
1. Shell解析命令
当用户在终端输入
./a.out
时:- Shell的行为:
- Shell将
./a.out
解析为对当前目录下可执行文件a.out
的路径引用(而非全局路径)。
2. 权限和路径检查
- 路径检查
- 如果未指定绝对路径(如
/home/user/a.out
),Shell会检查当前目录是否包含该文件。 - 如果文件不存在:提示
No such file or directory
。
- 执行权限验证:
- 使用
stat
系统调用检查文件元数据(权限位)。 - 若文件有可执行权限(
x
位),则继续;否则返回错误。 - 如果无执行权限:提示
Permission denied
。
- 文件类型验证:
- 必须是一个二进制可执行文件或脚本(如Shell脚本)。
- 通过文件头部(ELF Header)判断是否为合法可执行文件。
3. 加载器创建新进程
- 核心系统调用:
fork()
:复制当前Shell进程创建一个新进程。execve()
:替换新进程的代码和数据为a.out
的内容。
- 进程创建流程:
fork()
生成子进程(与父进程共享代码段和数据段)。execve()
擦除子进程资源,加载新程序映像(a.out
)。- 若这一步失败(如文件损坏),返回错误并终止子进程。
4. 内核处理可执行文件
- ELF文件解析:
- ELF(Executable and Linkable Format)文件包含头信息(Entry Point地址、段头表、程序头表)。
- 内核读取ELF Header,验证是否合法(魔数
0x7F 0x45 0x4C 0x46
)。 - 识别程序类型(静态链接还是动态链接)。
- 加载流程:
- 根据程序头表(Program Headers)将代码段(
.text
)、数据段(.data
)等内容映射到内存。 - 如果是动态链接程序,加载器会进一步处理(见下文)。
5. 动态链接和库加载
- 动态链接过程:
- 加载器检查程序的
.interp
段,找到动态链接器路径(如/lib/ld-linux-x86-64.so.2
)。 - 动态链接器(
ld.so
)初始化进程环境,解析未定的符号引用。 - 加载共享库(如
libc.so
)到内存,重定位地址以完成链接。
- 显式行为:
- 使用
ldd a.out
可以查看依赖的动态库。 - 若某个库缺失,程序会在这一步崩溃(提示
error while loading shared libraries
)。
6. 设置进程环境
- 用户态初始化:
- 栈内存布局初始化:存放环境变量(
envp
)、命令行参数(argv
)和参数个数(argc
)。 - 堆(通过
brk
系统调用初始分配)和全局变量的内存分配。
- 程序入口点:
_start
(由C运行时库crt0.o
提供)调用全局构造函数(C++的全局对象)和main
函数。- 最终
main
函数被调用。
7. 程序执行阶段
- 用户态运行:
- 执行
main
函数中的代码(用户编写的逻辑)。 - 访问系统调用(如
printf
的write
调用)通过软中断(int 0x80
或syscall
指令)进入内核态。
- 结束流程:
main
返回后,控制权交还给_start
。- 调用C运行时库的
exit()
,触发exit_group
系统调用终止进程。 - 资源回收(关闭文件描述符、释放内存、通知父进程)由内核处理。
实际调试验证
使用
strace
工具跟踪系统调用:输出的关键部分:
这段输出展示了:
execve
调用加载程序。
- 动态链接器加载
libc
库。
write
系统调用输出字符串。
- 程序正常退出。
编程语言
- 小智学长资料包嵌入式-常用知识&面试题库
极氪 嵌入式一面 面经
全程面试一小时四十分钟,两个面试官轮番上阵,部门主要做车端底层软件和中间件。面试难度整体不大,主要深挖在蔚来的实习项目,八股问题相对常见,无奈问题量很大,最后已经很疲劳了。
Intern&ourea_app
- 介绍-下cgroup 原理及其难点
- 项目需要加载哪些配置文件
- 懒汉和饿汉单例模式有什么区别
- 为什么项目使用懒汉单例(看起来饿汉单例更合适)
- 还了解哪些设计模式(单例、工厂、装饰者)
- 如果一个appCPU占用率超过限定值,cgroup是如何进行限制的,服务会被kil掉吗?
- 如何解析 coredump(minidump)
- minidump解析原理是什么
- 使用perf进行性能分析,如何生成火焰图?能否实时生成火焰图?
OS
- select和epoll的区别
- 实现一个线程池分为哪些步骤
- 互斤锁和自旋锁的区别
Network:
- TCP和UDP的区别
- 介绍一下TCP四次挥手
- 为什么需要TIMEWAIT状态
- TIMEWAIT时间是多长(2MSL)
C++:
- 多态实现原理
- 虚函数表是在什么时候创建的2
- 从编译器角度来看、静态多态(函数重载)原理是什么
- STLvector中push back和emplace_back的区别
- map和unordered_map的区别、以及适用场景
- 如何使Map中的Key按照自定义规则排序
- #include<>和""的区别
- 深拷贝和浅拷贝的区别
- strcpy会造成什么安全问题
- strcpy和 memcpy的区别
- 使用memcpy会造成哪些隐患
- memcpy和memmove 的区别
- delete能否用于释放整型变量
- fork和vfork的区别
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/embedded_linux_interview
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!