嵌入式Linux面试指南:从命令到驱动的能力清单

发布时间:2026/9/3 3:51:45
嵌入式Linux面试指南:从命令到驱动的能力清单 最近在帮朋友做嵌入式岗位的简历复盘发现一个很普遍的现象招聘 JD 上写着“熟悉 Linux 操作系统”但无论是投递应用开发、驱动开发还是 BSP 方向面试官问的内容差别非常大。有人把精力全花在背命令上结果面试考的是 fork 与 vfork 的差异有人啃了很久内核源码却被一个-print的 find 参数卡住。这篇文章我想结合企业招聘嵌入式岗位的真实考察点把“熟悉 Linux”拆成可以量化的能力清单。不论你是还在校的学生还是从单片机转向嵌入式 Linux 的开发者都可以对照这份清单检查自己的知识盲区。1. 招聘要求拆解“熟悉 Linux”到底在问什么嵌入式开发和纯后端开发不同Linux 对你来说不是“部署环境”而是“产品的一部分”。路由器、工控机、自动驾驶域控制器、医疗仪器、无人机飞控里面跑的几乎都是嵌入式 Linux 系统。所以企业写“熟悉 Linux 操作系统”背后其实包含三层意思。第一层是使用能力。你能不能用命令行完成日常开发能不能写脚本处理日志和批量修改配置能不能在系统出问题时快速定位原因。这一层考察的是熟练度不需要背几百条命令但高频命令必须做到条件反射。第二层是系统理解能力。你知不知道进程、线程、内存、文件系统在内核中大概是怎么运作的用户态和内核态有什么区别系统调用发生了什么这一层决定了你能否分析复杂的疑难问题而不是只会重启大法。第三层是开发能力。对于应用岗要求你能调用系统 API 写出健壮的多进程/多线程程序。对于驱动岗要求你了解内核模块机制、字符设备驱动框架、设备树和中断处理。这一层是企业筛选候选人的分水岭。很多初学者以为“熟悉 Linux”等于“会装系统 会几个命令”这恰恰是最大的误区。面试官真正想通过这个问题确认的是你有没有在 Linux 环境下解决过真实问题的经验而不是你有没有背过命令手册。2. Linux 基础命令从“见过”到“形成肌肉记忆”命令是 Linux 开发的地基但不需要贪多。嵌入式工程师不同于专职运维不需要掌握所有服务管理命令但下面这几类命令必须达到“不用查、直接写”的程度。2.1 文件操作与查看# 查看当前路径 pwd # 查看文件详细信息包括权限、属主、大小、时间 ls -l ls -ah # 创建多层目录 mkdir -p project/src/include # 复制、移动、删除 cp -r old_dir new_dir mv file1 file2 rm -rf build/ # 查看文件内容支持显示行号 cat -n main.c less main.c head -20 main.c tail -f app.log这里要特别强调tail -f。嵌入式设备现场调试时应用日志和内核日志是最重要的信息来源。tail -f可以实时滚动查看日志文件配合grep过滤关键信息是排查问题的第一板斧。2.2 查找与文本处理面试时经常让候选人现场写一个查找命令很多人会卡在find和grep的用法上。两者的区别要记清楚find是查找文件grep是查找文件内容。# 在 /home 目录下查找所有 .c 文件 find /home -name *.c # 查找文件并执行 ls 操作 find /home -name *.c -exec ls -l {} \; # 在 main.c 中查找包含 error 的行并显示行号 grep -n error main.c # 递归查找当前目录所有文件中包含 main 的行 grep -rn main . # 统计文件行数、单词数、字节数 wc -l main.c实际项目中我常用一条组合命令来快速定位问题grep -rn out of memory /var/log/ | tail -1002.3 查看系统资源与进程嵌入式设备资源有限CPU 占用率、内存剩余量、进程状态是每天都要关注的指标。# 动态查看进程和资源占用 top # 查看某个进程启动后的完整命令行 ps -ef | grep app # 端口占用情况 netstat -anp | grep 8080 # 查看内存信息 free -m # 查看 CPU 信息 cat /proc/cpuinfo # 查看内核日志 dmesg | tail -50面试时如果被问到“程序运行很卡怎么排查”这一套命令组合就能派上用场先用top看 CPU 和内存再用ps确认进程状态接着用free看系统内存是否有压力最后用dmesg看内核有没有报错。3. 文件系统、权限与磁盘管理嵌入式工程师经常要在板子上烧写文件系统、挂载 SD 卡或 EMMC 分区对文件系统的理解不能停留在“文件存哪里”的层面。3.1 权限的本质Linux 的权限模型可以拆成三组属主user、属组group、其他用户other每组又分为读r4、写w2、执行x1三种权限。# 修改文件权限为 rwxr-xr-x chmod 755 script.sh # 修改属主 chown root:root config.ini # 查看文件权限详情 ls -l config.inisetuid 位是嵌入式开发中经常遇到的坑。比如/usr/bin/sudo带有 s 权限普通用户才能以 root 身份执行。如果面试官问你“为什么用 ping 命令不需要 root 权限”答案就在于 ping 程序本身设置了 setuid 权限位。3.2 磁盘与挂载# 查看磁盘分区和使用情况 fdisk -l df -h # 挂载 U 盘到 /mnt/udisk mount /dev/sda1 /mnt/udisk # 卸载 umount /mnt/udisk # 查看文件系统类型 blkid /dev/sda1在嵌入式 Linux 开发中/etc/fstab是自动挂载的配置文件。如果板子重启后某个分区没有挂载上优先检查这个文件里的配置项。3.3 用户管理与日常运维命令企业环境下嵌入式 Linux 通常以普通用户登录开发而不是直接使用 root。创建用户、切换用户、查看历史记录都是基本操作。# 新建用户并指定家目录和 Shell sudo useradd -m -s /bin/bash embed # 设置密码 sudo passwd embed # 切换用户 su - embed # 查看当前用户 whoami # 查看命令历史 history关于命令历史我想多说一句。现场排查问题的时候history能帮你快速确认之前执行过哪些操作。如果板子上的某个服务突然不可用翻一下历史命令往往比看日志更快找到原因。4. Shell 脚本与构建系统工程效率的分水岭只会敲命令效率很低。当需要批量修改配置、自动化编译、收集日志时Shell 脚本是嵌入式工程师的基本功。4.1 一个简单的日志备份脚本#!/bin/bash # 文件路径~/backup_log.sh LOG_DIR/opt/app/log BACKUP_DIR/home/embed/backup # 创建备份目录 mkdir -p ${BACKUP_DIR} # 备份并加上日期后缀 cp -r ${LOG_DIR} ${BACKUP_DIR}/log_$(date %Y%m%d_%H%M%S) # 删除 7 天前的备份 find ${BACKUP_DIR} -name log_* -mtime 7 -exec rm -rf {} \; echo backup completed脚本中$(date %Y%m%d_%H%M%S)是命令替换用于生成时间戳。-mtime 7表示修改时间超过 7 天的文件。这两处是实际开发中最常用的写法。4.2 Makefile 入门嵌入式 Linux 应用开发中Makefile 是最常见的构建方式。不需要会写很复杂的 Makefile但至少能看懂、能改。# 文件路径~/project/Makefile CC gcc CFLAGS -Wall -g -O2 TARGET app SRCS main.c util.c OBJS $(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(TARGET) $(OBJS) .PHONY: clean需要理解几个关键点$表示目标文件$^表示所有依赖文件$表示第一个依赖文件。.PHONY声明clean是伪目标避免目录下有同名文件时 make 无法执行。很多应届生在简历上写“熟悉 Makefile”但面试时只能讲出“用来编译”这显然不够。至少要能解释目标、依赖和命令三者的关系。5. C 语言功底与系统编程聊完工具接下来是真正的硬核部分。嵌入式 Linux 开发的核心语言是 C企业考察的不仅是语法更是“用 C 操作操作系统资源”的能力。5.1 指针与内存是永远的重点嵌入式岗位的 C 语言面试题十题里有八题和指针、内存有关。#include stdio.h #include stdlib.h #include string.h void get_memory(char *p, int num) { p (char *)malloc(num); memset(p, 0x00, num); } int main(void) { char *str NULL; get_memory(str, 100); if (str NULL) { printf(str is still NULL\n); return -1; } strcpy(str, hello); printf(%s\n, str); free(str); return 0; }这段代码的问题是参数p是值传递在函数内部修改p不会影响外部的str最终str仍然是 NULL而且 malloc 的内存也泄漏了。正确写法需要传二级指针或使用返回值。指针这块还要理解数组名和指针的区别、函数指针、const 修饰指针的几种位置。这些不是背概念而是要在写代码时真正用对。5.2 文件 I/O 和标准 I/O 的差异嵌入式开发涉及读写配置文件、操作设备节点文件 I/O 是高频考点。#include stdio.h #include stdlib.h int main(void) { FILE *fp fopen(/etc/config.ini, r); if (fp NULL) { perror(fopen); return -1; } char line[256]; while (fgets(line, sizeof(line), fp) ! NULL) { printf(%s, line); } fclose(fp); return 0; }需要注意区分fopen/fread/fwrite和open/read/write。前者是 C 标准库函数带有缓冲区后者是系统调用直接进入内核。在操作设备节点、串口、GPIO 时通常会使用系统调用open/read/write。5.3 编译与调试工具链用 gcc 编译、用 gdb 调试、用 objdump 查看汇编这些是嵌入式 Linux 开发的日常操作。# 编译时输出警告信息生成调试信息 gcc -Wall -g main.c -o app # 使用 gdb 调试 gdb ./app在 gdb 内部常用的命令有break main run next step print var bt quit分享一个实际问题排查的经验程序莫名崩溃可以先ulimit -c unlimited开启 core dump然后gdb ./app core查看崩溃位置。这条路径非常实用很多面试官问“线上程序崩溃怎么办”时期待的就是这个思路。6. 多进程 / 多线程与同步嵌入式应用的核心战场嵌入式 Linux 应用几乎都是多任务系统进程和线程的知识直接决定代码质量。6.1 进程 vs 线程必须能讲清楚进程是资源分配的基本单位线程是 CPU 调度的基本单位。进程之间地址空间互相隔离一个进程崩溃不会直接导致另一个进程崩溃线程共享同一进程的地址空间通信效率高但也更容易出现同步问题。fork创建进程时会复制父进程的地址空间vfork创建进程时则共享地址空间直到子进程调用 exec。两者最大的区别是fork之后父子进程谁先执行不确定vfork保证子进程先执行。6.2 线程创建与同步示例下面用一个简单的生产者-消费者模型来演示多线程同步。#include stdio.h #include pthread.h #include unistd.h #define BUFFER_SIZE 8 int buffer[BUFFER_SIZE]; int count 0; pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_not_full PTHREAD_COND_INITIALIZER; pthread_cond_t cond_not_empty PTHREAD_COND_INITIALIZER; void *producer(void *arg) { int item 0; while (1) { pthread_mutex_lock(mutex); while (count BUFFER_SIZE) { pthread_cond_wait(cond_not_full, mutex); } buffer[count] item; printf(produce %d, count%d\n, item - 1, count); pthread_cond_signal(cond_not_empty); pthread_mutex_unlock(mutex); usleep(200000); } return NULL; } void *consumer(void *arg) { while (1) { pthread_mutex_lock(mutex); while (count 0) { pthread_cond_wait(cond_not_empty, mutex); } int item buffer[--count]; printf(consume %d, count%d\n, item, count); pthread_cond_signal(cond_not_full); pthread_mutex_unlock(mutex); usleep(300000); } return NULL; } int main(void) { pthread_t tid1, tid2; pthread_create(tid1, NULL, producer, NULL); pthread_create(tid2, NULL, consumer, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }编译时注意要加-lpthreadgcc -Wall -g producer_consumer.c -o pc -lpthread这段代码里有几个关键细节。第一while (count BUFFER_SIZE)要用while而不是if因为条件变量存在虚假唤醒。第二pthread_cond_wait会自动释放互斥锁所以在循环条件里重新检查非常重要。第三互斥锁和条件变量要配对使用生产者和消费者各自负责唤醒对方。6.3 进程间通信面试必问的考点进程间通信方式包括管道、消息队列、共享内存、信号量、信号、Socket。工程中最常用的组合是“共享内存 信号量”和“Socket”。共享内存传输效率最高但需要信号量保护临界区Socket 适合跨设备通信也适合本机进程通信。// 使用 ftok 生成 key key_t key ftok(/tmp, 0x66); // 创建共享内存 int shmid shmget(key, 4096, IPC_CREAT | 0666); // 挂载共享内存 char *addr (char *)shmat(shmid, NULL, 0); // 使用完毕后分离 shmdt(addr);如果面试官问“两个进程共享内存如何避免同时读写冲突”答案就是结合信号量或互斥锁。只答出共享内存而不说同步机制通常只能得到一半分数。7. 计算机网络协议栈的基本功企业招聘嵌入式岗位计算机网络是必考项因为设备几乎都要联网。7.1 TCP/IP 协议栈的理解程度需要掌握的基础知识包括应用层HTTP、MQTT、Modbus、DNS嵌入式最常打交道的协议。传输层TCP 三次握手、四次挥手、TCP 可靠传输原理、UDP 无连接特性。网络层IP 地址、子网掩码、网关、路由概念。链路层MAC 地址、以太网帧结构。重点是理解 TCP 和 UDP 的选择逻辑需要可靠传输、数据量大用 TCP对实时性要求高、允许少量丢失用 UDP。比如音视频传输通常选择 UDP而设备配网协议通常使用 TCP。7.2 Socket 编程示例#include stdio.h #include string.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h int main(void) { int sockfd socket(AF_INET, SOCK_STREAM, 0); if (sockfd 0) { perror(socket); return -1; } struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(8080); inet_pton(AF_INET, 192.168.1.100, server_addr.sin_addr); if (connect(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) { perror(connect); close(sockfd); return -1; } char buf[128] hello server; send(sockfd, buf, strlen(buf), 0); memset(buf, 0, sizeof(buf)); recv(sockfd, buf, sizeof(buf) - 1, 0); printf(recv: %s\n, buf); close(sockfd); return 0; }需要解释htons的作用把主机字节序转换为网络字节序。x86 架构是小端模式而网络传输使用大端模式所以端口和地址都需要做转换。7.3 常见网络排查命令# 测试网络连通性 ping 192.168.1.1 # 查看本机 IP 地址和 MAC 地址 ifconfig ip addr # 查看路由表 route -n ip route # 抓包分析 tcpdump -i eth0 -nn port 8080用tcpdump抓包时如果设备上没有这个工具可以用busybox tcpdump。很多嵌入式系统自带 BusyBox它提供了一系列精简版命令这也是嵌入式 Linux 和桌面 Linux 的一个差异点。8. 驱动开发方向内核模块与设备模型如果目标是嵌入式 Linux 驱动岗位要求会比应用高一个级别。需要理解内核模块机制、字符设备驱动框架、设备树、中断、并发与竞态。8.1 内核模块 Hello World#include linux/init.h #include linux/module.h static int __init hello_init(void) { printk(KERN_INFO hello module loaded\n); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO hello module unloaded\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(embed);对应的 Makefileobj-m : hello.o KERNEL_DIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules clean: $(MAKE) -C $(KERNEL_DIR) M$(PWD) clean编译和加载命令make sudo insmod hello.ko sudo rmmod hello dmesg | tail注意驱动模块的代码运行在内核空间不能直接调用用户空间的printf所以使用printk。加载模块后日志会写到内核缓冲区需要用dmesg查看。8.2 设备树的作用设备树Device Tree是描述硬件资源的文件用于把硬件信息传递给内核。在嵌入式 Linux 开发中新增一个外设时经常需要修改设备树。i2c1 { status okay; temp_sensor48 { compatible lm75; reg 0x48; }; };这段设备树描述的是I2C1 控制器使能总线上挂了一个地址为 0x48 的温度传感器 lm75。驱动通过compatible属性与设备节点匹配。如果只做应用开发不需要深入理解所有设备树细节但至少要能看懂设备树的基本结构因为调试时经常需要确认某个外设节点是否被正确配置。8.3 驱动开发和应用的考察差异考察方向应用开发驱动开发Linux 命令熟练使用会排查需要理解内核日志和 /proc、/sysC 语言指针、结构体、文件操作内核链表、内存屏障、并发控制进程/线程多线程同步、进程通信内核线程、工作队列、自旋锁网络Socket 编程、协议栈Netfilter、网络设备驱动硬件知识了解寄存器映射芯片手册、时序图、寄存器操作调试手段gdb、日志、straceftrace、kgdb、perf、动态打印从长期发展看驱动岗位的门槛更高但应用岗位的需求量更大。如果技术深度足够两者都可以形成很强的竞争力。比较务实的学习路径是先做应用开发逐步深入驱动方向。9. 工程化能力与问题排查思路企业招人不是找“会写代码”的人而是找“能解决问题”的人。面试和试用期考察的工程化能力往往体现在下面几个场景里。9.1 查看日志与分析问题嵌入式开发最常面临的问题是“现场复现不了板子日志看不全”。推荐的做法是应用日志、内核日志分开收集日志格式统一带上时间戳、模块名、级别关键路径打印日志但不要打太多防止影响性能。# 带时间戳查看内核日志 dmesg -T # 查看某个服务当前状态 systemctl status app # 实时跟踪应用日志 tail -f /var/log/app.log这里需要提醒一点不是所有嵌入式设备都使用 systemd。很多工控设备使用 BusyBox init 或 SysV init所以不要只会 systemctl 系列命令也要能读懂init.d下的启动脚本。9.2 内存问题的排查嵌入式设备内存有限内存泄漏是高频问题。排查思路是先用free观察内存变化趋势再用top或ps -o pid,vsz,rss,comm定位进程最终通过代码走查或 valgrind 找到泄漏点。ps -eo pid,rss,comm --sort-rss | head -10如果rss持续增长大概率存在泄漏。可以用 valgrind 做内存检查valgrind --leak-checkfull ./appvalgrind 在 X86 的 Ubuntu 上非常常用但在嵌入式板子上可能因为性能或空间限制跑不起来这时会改用dmalloc或自己实现内存池统计。9.3 崩溃问题的定位应用崩溃时常见错误是段错误Segmentation Fault。定位手段包括使用-g编译并开启 core dump在代码中增加日志二分定位崩溃位置使用 gdb 查看 backtrace检查空指针、数组越界、double free。# 开启 core dump ulimit -c unlimited # 运行程序直到崩溃 ./app # 用 gdb 加载 core 文件 gdb ./app core在 gdb 内部输入bt可以查看函数调用栈。栈信息会直接显示崩溃发生在哪个源文件和哪一行这是最有价值的调试信息。10. 常见问题与企业考察的典型场景下面整理一些面试和实际工作中出现频率很高的问题方便对照自检。问题现象企业想考察的底层能力解决思路死锁导致程序卡死多线程同步机制理解检查锁的获取顺序使用超时等待避免嵌套锁内存持续增长内存分配与释放、工具掌握使用 top 观察 RSS用 valgrind 检查泄漏端口被占用无法启动网络基础、文件描述符使用 netstat / ss 查看端口占用确认进程并释放LED 驱动没有反应设备树、寄存器操作、驱动框架检查设备树节点、compatible 匹配、register_* 调用开机后某个服务没启动启动流程、依赖关系、日志排查查看 bootlog、rcS 脚本、systemd 服务状态程序运行一段时间卡顿性能分析、调度、I/O使用 top、perf、strace 定位瓶颈从这些场景可以看到企业真正关心的不是你会背多少命令而是面对问题时能否快速建立排查路径。11. 学习路线建议与自测清单基于前面几个部分我整理了一份更适合实际求职的学习地图。11.1 第一阶段Linux 基础与命令行2-4 周重点学习文件系统、权限、进程管理、网络命令、Shell 基础。自测标准能在不给提示的情况下独立完成“编写一个日志清理脚本 定时执行”的任务。11.2 第二阶段C 语言与系统编程2-3 个月重点学习指针、内存管理、文件 I/O、多线程、进程间通信、Socket 编程。自测标准能独立实现“多线程日志系统”或“TCP 通信客户端/服务端”并用 gdb 完成断点调试。11.3 第三阶段嵌入式平台实战3-6 个月选择一块开发板如 IMX6ULL、STM32MP1、全志芯片进行实战。建议完成下面几个实验搭建交叉编译环境编译一个应用并部署到板子上运行编写一个使用多线程采集传感器数据并上报到服务器的应用阅读一个简单的字符设备驱动跑通 module 加载与设备节点读写使用设备树配置一个 GPIO 或 I2C 外设。自测标准能从零开始完成一个“传感器采集 网络上报 LED 控制”的综合项目并输出完整的调试笔记。11.4 第四阶段内核/驱动进阶按岗位方向选择如果走驱动方向需要深入学习并发与竞态、中断下半部、内核内存管理、设备驱动模型。如果走应用方向可以重点学习不同业务场景下的系统性能优化、可靠性设计、日志与监控方案。12. 企业视角的几点建议最后抛开技术本身从招聘视角再说几个观察。第一简历上不要只写“熟悉 Linux”而要写出具体能力。比如“熟悉 Linux 常用命令与 Shell 脚本”“掌握 Linux 多线程程序开发与死锁排查”“熟悉字符设备驱动开发流程能独立完成 GPIO 和 I2C 外设驱动”这些描述比空泛的“熟悉”有说服力得多。第二项目经验比证书重要。企业更愿意看到你做过什么具体的东西遇到过什么问题用什么手段解决的。哪怕项目很小只要能把“问题背景 → 分析过程 → 解决方案 → 验证结果”讲清楚都比罗列技术名词有含金量。第三要有自己的调试工具箱。面试时如果被问到“你怎么排查线上问题”能马上说出“先看日志、再看内存和进程状态、必要时用 gdb 或抓包”的候选人往往比只背概念的人更受欢迎。第四不要害怕“不会”。面试官问到一个不会的知识点诚实地表达“目前了解不深但我会通过什么方式去查和学习”这样的态度通常比强行编造更好。嵌入式 Linux 的知识边界很宽没有人能全部掌握。“熟悉 Linux 操作系统”并不是一个固定标准企业更看重的是你在实际开发中形成的思考方式、排查问题的能力和持续学习的习惯。想入行的话早点开始动手早点踩坑早点积累自己的排错经验比任何学习路线都更有效。希望这篇内容对你准备嵌入式岗位有所帮助。如果觉得有用可以收藏备用也欢迎在评论区聊聊你在学习和面试中遇到的 Linux 相关的问题。