Lazy loaded image
🥳嵌入式 Linux开发
用面试拷问嵌入式技术栈
Words 2461Read Time 7 min
2025-3-19
2025-3-21
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的内容。
  • 进程创建流程
      1. fork()生成子进程(与父进程共享代码段和数据段)。
      1. execve()擦除子进程资源,加载新程序映像(a.out)。
      1. 若这一步失败(如文件损坏),返回错误并终止子进程。

4. 内核处理可执行文件

  • ELF文件解析
    • ELF(Executable and Linkable Format)文件包含头信息(Entry Point地址、段头表、程序头表)。
    • 内核读取ELF Header,验证是否合法(魔数 0x7F 0x45 0x4C 0x46)。
    • 识别程序类型(静态链接还是动态链接)。
  • 加载流程
    • 根据程序头表(Program Headers)将代码段(.text)、数据段(.data)等内容映射到内存。
    • 如果是动态链接程序,加载器会进一步处理(见下文)。

5. 动态链接和库加载

  • 动态链接过程
      1. 加载器检查程序的 .interp 段,找到动态链接器路径(如 /lib/ld-linux-x86-64.so.2)。
      1. 动态链接器(ld.so)初始化进程环境,解析未定的符号引用。
      1. 加载共享库(如 libc.so)到内存,重定位地址以完成链接。
  • 显式行为
    • 使用 ldd a.out 可以查看依赖的动态库。
    • 若某个库缺失,程序会在这一步崩溃(提示 error while loading shared libraries)。

6. 设置进程环境

  • 用户态初始化
    • 栈内存布局初始化:存放环境变量(envp)、命令行参数(argv)和参数个数(argc)。
    • 堆(通过brk系统调用初始分配)和全局变量的内存分配。
  • 程序入口点
    • _start(由C运行时库 crt0.o 提供)调用全局构造函数(C++的全局对象)和 main 函数。
    • 最终main函数被调用。

7. 程序执行阶段

  • 用户态运行
    • 执行main函数中的代码(用户编写的逻辑)。
    • 访问系统调用(如printfwrite调用)通过软中断(int 0x80syscall指令)进入内核态。
  • 结束流程
      1. main 返回后,控制权交还给 _start
      1. 调用C运行时库的 exit(),触发 exit_group 系统调用终止进程。
      1. 资源回收(关闭文件描述符、释放内存、通知父进程)由内核处理。

实际调试验证

使用 strace 工具跟踪系统调用:
输出的关键部分
这段输出展示了:
  1. execve调用加载程序。
  1. 动态链接器加载 libc 库。
  1. write系统调用输出字符串。
  1. 程序正常退出。

编程语言

 
 
 
  1. 小智学长资料包嵌入式-常用知识&面试题库
极氪 嵌入式一面 面经
全程面试一小时四十分钟,两个面试官轮番上阵,部门主要做车端底层软件和中间件。面试难度整体不大,主要深挖在蔚来的实习项目,八股问题相对常见,无奈问题量很大,最后已经很疲劳了。 Intern&ourea_app
  1. 介绍-下cgroup 原理及其难点
  1. 项目需要加载哪些配置文件
  1. 懒汉和饿汉单例模式有什么区别
  1. 为什么项目使用懒汉单例(看起来饿汉单例更合适)
  1. 还了解哪些设计模式(单例、工厂、装饰者)
  1. 如果一个appCPU占用率超过限定值,cgroup是如何进行限制的,服务会被kil掉吗?
  1. 如何解析 coredump(minidump)
  1. minidump解析原理是什么
  1. 使用perf进行性能分析,如何生成火焰图?能否实时生成火焰图?
OS
  1. select和epoll的区别
  1. 实现一个线程池分为哪些步骤
  1. 互斤锁和自旋锁的区别
Network:
  1. TCP和UDP的区别
  1. 介绍一下TCP四次挥手
  1. 为什么需要TIMEWAIT状态
  1. TIMEWAIT时间是多长(2MSL)
C++:
  1. 多态实现原理
  1. 虚函数表是在什么时候创建的2
  1. 从编译器角度来看、静态多态(函数重载)原理是什么
  1. STLvector中push back和emplace_back的区别
  1. map和unordered_map的区别、以及适用场景
  1. 如何使Map中的Key按照自定义规则排序
  1. #include<>和""的区别
  1. 深拷贝和浅拷贝的区别
  1. strcpy会造成什么安全问题
  1. strcpy和 memcpy的区别
  1. 使用memcpy会造成哪些隐患
  1. memcpy和memmove 的区别
  1. delete能否用于释放整型变量
  1. fork和vfork的区别
 
上一篇
Linux驱动基础08-详解GPIO和pinctrl子系统设备树
下一篇
Vim 编辑器使用指南

Comments
Loading...