C 语言基础(嵌入式 Linux 的核心语言,必考)

这是嵌入式岗位的基本功,面试官会深挖细节,避免停留在 “会用” 的层面。

指针与数组

核心问题:指针和数组的区别?char *p 和 char p[] 的差异?函数指针的定义和用途(比如回调函数)?

  • char *p:是指针变量,存储的是一个内存地址,指向字符型数据;可以被重新赋值(如 p = p+1)。
  • char p[]:是字符数组,数组名 p 是常量指针,指向数组首元素地址,不能被重新赋值(如 p++ 会报错)。

扩展问题:野指针的成因和规避方法?空指针和未初始化指针的区别?const 修饰指针的几种情况(const char *p / char const *p / char *const p)?

  • char *p = “hello”:字符串 “hello” 存放在常量区,p 存放在栈区,指向常量区地址;修改 *p 会触发段错误。
  • char p[] = “hello”:字符串 “hello” 存放在栈区,可以通过 p[0] = ‘H’ 修改内容。
  • 函数传参时,void func(char p[]) 等价于 void func(char *p),数组名退化为指针。

内存管理

核心问题:堆、栈、静态区、全局区、常量区的区别?malloc/free 和 new/delete 的区别?

内存管理 存储内容 生命周期 分配/释放方式 特点
栈(stack) 局部变量、函数参数、返回值 随函数调用创建,函数结束销毁 编译器自动分配 / 释放 空间小(几 MB)、速度快、先进后出
堆(heap) malloc/new 分配的内存 手动分配后,直到 free/delete 或程序结束才释放 程序员手动分配 / 释放 空间大(GB 级)、速度慢、易内存泄漏
全局 / 静态区 全局变量、static 变量 程序启动时分配,程序结束时释放 编译器自动分配 / 释放 所有函数共享
常量区 字符串常量、const 修饰的全局变量 程序启动时分配,程序结束时释放 编译器自动分配 / 释放 只读,修改会触发段错误

扩展问题:内存泄漏的原因?如何排查嵌入式系统中的内存泄漏(比如 valgrind 的使用,或自定义内存池)?内存碎片的危害和解决方法?

结构体与位运算

核心问题:结构体为什么要内存对齐?如何手动设置对齐方式(#pragma pack)?位域的用途(比如寄存器配置)?

扩展问题:大端序和小端序的区别?如何用 C 语言判断系统的大小端?

  • 大端序(Big-Endian):高位字节存放在低地址,低位字节存放在高地址(符合人类阅读习惯)。
  • 小端序(Little-Endian):低位字节存放在低地址,高位字节存放在高地址(嵌入式 CPU 常用,如 ARM)。

函数与预处理

核心问题:inline 函数的作用和限制?宏定义和函数的区别?#define 和 typedef 的差异?

扩展问题:递归函数的优缺点?嵌入式系统中使用递归需要注意什么(栈溢出风险)?

二、 Linux 系统编程(嵌入式 Linux 面试的核心模块)

这部分直接考察你对 Linux 内核用户态接口的掌握程度,高频问题集中在进程、线程、IO、通信。

进程管理

核心问题:进程和线程的区别?进程的 5 种状态(创建、就绪、运行、阻塞、终止)?fork() 和 vfork() 的区别?

维度 进程 线程
资源分配 操作系统资源分配的基本单位,有独立的地址空间、文件描述符、内存空间 操作系统调度的基本单位,共享进程的地址空间、文件描述符等资源
开销 创建 / 销毁、切换开销大 创建 / 销毁、切换开销小
通信方式 需借助 IPC(管道、共享内存、消息队列等) 直接读写进程内的全局变量(需同步机制)
独立性 进程崩溃互不影响 一个线程崩溃会导致整个进程崩溃
  • Linux中没有真正意义上的线程,线程是通过轻量级进程 (LWP) 实现的。

扩展问题:孤儿进程和僵尸进程的成因?如何避免僵尸进程(wait()/waitpid()、信号 SIGCHLD)?

进程间通信(IPC)

核心问题:Linux 下有哪些 IPC 方式?各自的优缺点是什么?

IPC 方式 优点 缺点 适用场景
匿名管道 简单、无格式限制 只能父子 / 兄弟进程通信、半双工 父子进程间数据传输
命名管道 可实现任意进程通信 半双工、需文件系统支持 无亲缘关系进程间通信
共享内存 速度最快(直接访问内存) 需同步机制(信号量)、无数据格式 大量数据高频传输
消息队列 有消息格式、可按类型读取 数据大小有限制、速度慢于共享内存 少量数据异步传输
信号量 高效同步互斥 不能传输数据 进程 / 线程间同步(如共享内存互斥)
Socket 可跨网络通信 开销大、速度慢 跨主机 / 跨进程网络通信

管道(匿名管道 / 命名管道)、消息队列、共享内存、信号量、Socket 的优缺点和适用场景?

扩展问题:共享内存为什么需要信号量同步?Socket 属于哪一层的通信?和其他 IPC 方式的本质区别?

线程管理与同步

核心问题:线程的创建函数(pthread_create)参数含义?线程同步的方式有哪些(互斥锁 pthread_mutex、条件变量 pthread_cond、自旋锁)?

扩展问题:死锁的 4 个必要条件?如何避免死锁?互斥锁和自旋锁的适用场景区别?

文件 IO

核心问题:标准 IO(fopen/fread/fwrite)和系统 IO(open/read/write/lseek)的区别?文件描述符的概念?

扩展问题:水平触发(LT)和边缘触发(ET)的差异?epoll 的底层实现原理(红黑树 + 就绪链表)?高频命令:lsof 查看文件描述符占用、strace 追踪系统调用。

  • 水平触发(LT):只要文件描述符就绪,就会一直触发事件(适合新手,不易丢数据)。
  • 边缘触发(ET):仅当文件描述符状态变化时触发事件(效率更高,需一次性读完数据)。

IO多路复用: select/poll/epoll 的区别?

特性 select poll epoll
底层实现 位图 链表 红黑树 + 就绪链表
文件描述符上限 受限于 FD_SETSIZE(默认 1024) 无上限 无上限
效率 轮询所有描述符,O (n) 轮询所有描述符,O (n) 只处理就绪描述符,O (1)
触发方式 水平触发(LT) 水平触发(LT) 水平触发(LT)/ 边缘触发(ET)
内存拷贝 每次调用拷贝用户态到内核态 每次调用拷贝用户态到内核态 内核态与用户态共享内存,无需拷贝

僵尸进程: 什么是僵尸进程? 如何避免?

**定义: **进程退出后, 父进程未调用 wait()/waitpid() 回收其资源 (PCB) , 该进程就会变成僵尸进程.
**危害: **占用进程号, 导致系统无法创建新进程.
**避免方法: **

  • 父进程主动调用 wait()/waitpid() 回收子进程资源.
  • 父进程通过 signal(SIGCHLD, SIG_IGEN) 忽略 SIGCHLD 信号, 内核会自动回收子进程资源.
  • 父进程fork两次, 让孙子进程成为孤儿进程, 由 init 进程 (PID=1) 回收.

三、 嵌入式 Linux 系统开发(区分普通 Linux 和嵌入式 Linux 的关键)

这部分考察你对嵌入式系统构建和启动流程的理解,校招重点在 U-Boot、内核、根文件系统。

Bootloader(以 U-Boot 为例)

核心问题:U-Boot 的作用?U-Boot 的启动流程(Stage1:硬件初始化;Stage2:加载内核和根文件系统)?

系统启动流程:嵌入式 Linux 系统的完整启动流程是什么?

  • 核心答案
    上电 → Bootloader 初始化 → 加载内核 → 内核初始化 → 挂载根文件系统 → 启动 init 进程 → 启动应用程序
  • 上电:CPU 复位,执行 ROM 中的固化代码。
  • Bootloader 初始化
    Stage1:硬件初始化(关闭看门狗、初始化时钟、内存、串口),为 Stage2 准备运行环境。
    Stage2:加载内核镜像和根文件系统到内存,启动内核。
  • 内核初始化:解压内核、初始化硬件驱动、挂载根文件系统。
  • 启动 init 进程:内核启动用户空间第一个进程 init(PID=1),init 进程负责启动其他系统服务。
  • 启动应用程序:通过 init 配置文件(如 /etc/inittab)启动用户应用。

扩展问题:如何移植 U-Boot 到新的开发板?U-Boot 的常用命令(tftp、nand write、bootm)?

Linux 内核

核心问题:Linux 内核的组成部分(进程调度、内存管理、文件系统、设备驱动、网络协议栈)?如何裁剪内核(make menuconfig)?

扩展问题:内核镜像 zImage 和 uImage 的区别?vmlinuz 是什么?内核模块(ko 文件)的加载和卸载命令(insmod/rmmod/lsmod)?

根文件系统

核心问题:根文件系统的作用?嵌入式中常见的根文件系统类型(ramdisk、yaffs2、ubifs、ext4)?

扩展问题:如何制作根文件系统(busybox 工具的使用)?根文件系统挂载失败会导致什么问题?

四、 驱动开发基础(嵌入式 Linux 岗位的核心竞争力)

校招不会要求写复杂驱动,但会考察驱动框架和基础概念,重点是字符设备驱动。

驱动基础概念

核心问题:Linux 驱动的分层模型?设备文件和驱动的关联方式(主设备号 / 次设备号)?

扩展问题:驱动运行在用户态还是内核态?内核态和用户态的区别?驱动程序崩溃会影响整个系统吗?

字符设备驱动

核心问题:字符设备驱动的注册流程(alloc_chrdev_region → cdev_init → cdev_add)?file_operations 结构体的作用?

扩展问题:open/read/write 函数在驱动中的对应关系?如何实现设备的读写操作?

平台设备驱动 & 设备树

核心问题:什么是平台设备驱动?platform_device 和 platform_driver 的匹配方式?

扩展问题:设备树(DTB)的作用?为什么要引入设备树(替代传统的板级文件 mach-xxx.c)?如何在设备树中定义一个 GPIO 节点?

中断处理

核心问题:中断的上下半部机制?为什么要分上下半部?

扩展问题:tasklet 和工作队列的区别?软中断和硬中断的差异?

五、 Makefile & 编译构建

嵌入式开发离不开编译构建,校招会考察基础语法和交叉编译。

Makefile 基础

核心问题:Makefile 的基本结构(目标、依赖、命令)?伪目标(.PHONY)的作用?变量的定义和使用(@、@、@^、$< 等自动变量)?

扩展问题:如何编写一个多文件的 Makefile?make clean 的作用?

交叉编译

核心问题:什么是交叉编译?为什么嵌入式系统需要交叉编译?交叉编译器的命名规则(比如 arm-linux-gnueabihf-gcc)?

扩展问题:如何配置交叉编译环境?-I、-L、-l 参数的含义?

六、 调试工具 & 问题排查

面试官会关注你解决实际问题的能力,高频考察调试工具的使用。

调试工具

核心问题:gdb 的常用命令(break、next、step、print、backtrace)?如何用 gdbserver 调试嵌入式板卡上的程序?

扩展问题:dmesg 命令的作用?如何查看内核打印信息?ltrace 和 strace 的区别?

问题排查

高频问题:程序运行崩溃如何排查?外设驱动加载失败的常见原因?网络不通的排查步骤?

七、 项目经验 & 开放性问题(校招的加分项)

这部分是面试官的重点,会围绕你简历上的项目深挖,核心考察解决问题的思路。

高频问题

项目中你负责的模块?用到了哪些 Linux 系统编程 / 驱动开发的知识?
项目中遇到的最大难点是什么?你是如何分析和解决的?
项目中有没有遇到过内存泄漏 / 死锁 / 程序崩溃的问题?如何定位和解决的?
如果让你重新设计这个项目,你会做哪些优化?

开放性问题

嵌入式 Linux 系统的启动流程?(上电 → Bootloader → 内核初始化 → 挂载根文件系统 → 启动 init 进程)
如何优化嵌入式 Linux 系统的启动时间?

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐