ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

网易有道嵌入式笔试经验:C语言与Linux考点全梳理

网易有道嵌入式笔试经验:C语言与Linux考点全梳理 1. 笔试全貌网易有道嵌入式岗位到底在考什么我是2023届参加网易有道提前批的投的岗位是嵌入式软件工程师。先说结论这场笔试的难度比我想象中要正统得多没有那种偏门到离谱的脑筋急转弯但也绝不是背背八股文就能过的水平。笔试形式是牛客网在线笔试双机位监控时间是两个小时左右题量大概在40道选择题加两道编程题这个量级。选择题覆盖的范围包括C语言、数据结构、操作系统、计算机组成原理、计算机网络和嵌入式Linux基础问答题则集中在嵌入式相关的场景题编程题是两道LeetCode中等偏上难度的算法题。整体来看它在通用软件基础和嵌入式专业深度之间找了一个平衡点这对科班出身但嵌入式方向经验不深的人来说是个好消息——它不要求你写过多少驱动但要求你把计算机基础打得非常扎实。这个考察思路其实和网易有道的业务形态有关。有道做的是学习硬件、词典笔这类产品嵌入式软件工程师实际要面对的是资源受限的MCU/MPU平台、音频处理、低功耗管理、通信协议栈这些真问题。所以笔试不会考什么高大上的深度学习推理框架部署它考的都是你有没有能力在一个跑着Linux的小盒子上写出稳定不崩的C代码这类基本功。提前批的好处是即使笔试挂了正式批还能再投一次所以心态上可以稍微放松一点。但也别太放松提前批的HC通常比正式批少而且笔试成绩是会留档的面试官能看到你的答题情况笔试答得太拉垮面试环节会被追问。1.1 题型分布与分值节奏先拆解再作答我当时拆解完题单把题型分成了四块模块题量核心考察点建议用时C语言与内存10-12题指针、结构体、位运算、内存布局15分钟操作系统与并发8-10题进程线程、锁、中断、死锁15分钟计算机组成与网络8-10题大小端、总线协议、TCP/IP10分钟嵌入式Linux6-8题交叉编译、驱动模型、设备树10分钟选择题总共建议控制在50分钟内解决。剩下的时间留给编程题。这里有个很重要的策略不要在一道没把握的选择题上死磕超过三分钟先标记跳过去做完所有会做的再回来。我当时的第一直觉是嵌入式笔试应该重硬件轻算法但实际上编程题占比相当高。两道题一道是链表相关的操作一道是带状态转移的模拟题难度和牛客网上的中等题差不多不涉及特别复杂的动态规划但边界条件比较恶心考的就是你写代码够不够严谨。1.2 时间分配上的一个关键教训这里必须分享一个我踩过的坑我一开始在选择题上花得太久了有一道关于volatile的题我想了三分钟还拿不准结果后面编程题第一题写完之后第二题只来得及写个思路框架编译都没跑通就交了。复盘之后我的建议是编程题至少留足60分钟而且要先把两道题都读完先做自己更有把握的那道把该拿的分拿稳。笔试系统是按用例给分的部分通过也有分所以哪怕思路不完整也要把暴力解的框架写上去千万别留白。2. C语言与内存管理嵌入式笔试的硬骨头C语言这部分在网易有道的笔试里占比最高这符合嵌入式软件工程师的岗位定位——你不会写C后面的东西全都不用谈。但它的考察方式不是让你默写语法而是把语法、内存模型和实际工程问题揉在一起考非常考验你是不是真的理解C语言在底层是怎么运作的。2.1 指针、数组和字符串这些题目专治以为自己会了指针和数组的关系是必考题而且考法有固定的套路。比如它给你一段代码定义一个二维数组int a[3][4]问你sizeof(a)、sizeof(a[0])、sizeof(a[0][0])、a 1的值分别是多少。这种题考的不只是sizeof更是数组名在不同表达式里的类型退化规则。还有一类高频题是字符串函数实现。笔试里不会让你完整手写strcpy但会给你如下代码char *strcpy(char *dest, const char *src) { char *ret dest; while (*dest *src) ; return ret; }然后问你几个问题为什么返回值是char*而不是void如果dest和src指向重叠的内存区域会怎样如果src不是以\0结尾会发生什么这里的坑点在于strcpy是典型的未定义行为UB聚集地源字符串末尾没有终止符、目标缓冲区空间不足、内存区域重叠这三个都是真实项目中会碰到的致命问题。网易有道的题目会把这些场景包装成具体的函数调用让你判断结果是崩溃随机值还是正常。我给一个很实用的判断口诀看到指针操作先问三个问题——这个指针指向哪里那片内存有多大谁负责释放它把这三个问题答清楚了指针题基本不会错。2.2 结构体对齐字节对齐规则的完全拆解结构体对齐几乎是嵌入式笔试的必考题因为硬件寄存器映射、通信协议帧格式、文件系统结构体都依赖对齐规则。先看一个经典例子struct example { char c; // 1字节 int i; // 4字节 char d; // 1字节 };这个结构体在32位平台上的sizeof是多少答案不是6而是12。原因是编译器会按最大对齐数这里是4字节对齐char占1字节后紧跟着3字节填充int占4字节最后的char占1字节后再补3字节让整个结构体大小变成最大成员对齐数4的整数倍。但笔试一般不会只考这么简单的。它会继续扩展加#pragma pack(1)之后sizeof变成多少加__attribute__((packed))之后呢结构体里有double时按几字节对齐如果你写的是ARM Cortex-M平台默认对齐方式是8字节还是4字节这些细节一个比一个容易踩坑。这里的关键理解是结构体对齐不是C语言标准强制的而是ABI应用二进制接口规定的。同一个结构体在不同编译选项下大小可能不同所以做通信协议时必须有意识地控制填充字节要么手动添加保留字段要么显式使用打包属性。我的习惯是涉及跨平台传输的结构体一律显式加packed或手动计算偏移绝不在线上Debug时才发现结构体大小对不上。2.3 存储类修饰符static、const、volatile的嵌入式场景这三个关键字在嵌入式笔试里总是被问到但问法非常结合实际。volatile几乎必考因为它在嵌入式里的核心应用就是访问硬件寄存器。看这段代码void delay_ms(int ms) { for (volatile int i 0; i ms * 1000; i); }如果去掉volatile编译器在O2优化下很可能直接把这个空循环优化掉导致delay变成1纳秒。这个例子非常直观地说明了volatile的本质——告诉编译器这个变量的值可能随时被外部因素改变硬件寄存器、中断服务程序、多线程共享变量每次读取都必须从内存重新加载不要放到寄存器里缓存。static的考点则集中在三处修饰全局变量时限定作用域为本文件、修饰局部变量时延展生命周期到整个程序运行期、修饰函数时使其外部不可见。面试官很喜欢让你分析这段代码的输出void counter(void) { static int count 0; count; printf(%d , count); } int main() { for (int i 0; i 3; i) counter(); return 0; }输出是1 2 3而不是1 1 1。关键在于static局部变量的初始化只发生在首次执行时之后地址固定、值保持直到程序退出。const在嵌入式里最常考的坑是const int *p、int *const p、const int *const p三者的区别。口诀是从右往左读遇到*停。const int *p表示p指向的值不能通过p修改但p本身可以改指int *const p表示p本身不可变但指向的值可以改const int *const p两者都不可变。2.4 位运算与寄存器操作一道几乎必考的应用题嵌入式笔试里位运算很少单独考而是放在一个具体的场景里给你一个32位寄存器地址0x40021000要求把第3位到第7位设置为10101其他位保持不变。标准的写法是*(volatile uint32_t *)0x40021000 ~(0x1F 3); *(volatile uint32_t *)0x40021000 | (0x15 3);第一行先按位取反再与运算把第3到第7位清零第二行把目标值左移3位后或运算写入。这个写法背后的思路是先清零、再置位是硬件寄存器操作里最核心的套路。笔试还有一种考法用宏定义实现取一个数某几位的值或者判断一个数是否是2的幂。判断2的幂的标准解法是(n (n - 1)) 0 n ! 0这个要背熟并能解释清楚原理——一个2的幂次方数在二进制表示中只有一位是1减1之后这一位变成0后续所有位变成1两者相与必为0。3. 操作系统与并发从裸机思维到系统思维的转变有道的职位描述里写了会涉及Linux环境开发和RTOS所以笔试里操作系统相关内容是重头戏。很多人做嵌入式开发习惯停留在裸机前后台的思维模式但笔试考察的是你能否理解多任务环境下资源竞争和调度的本质。3.1 进程与线程概念题的正确打开方式进程和线程的区别是基础中的基础但笔试不会直接问你有什么区别而是给你一个多线程程序问你某个全局变量最终是多少。这就涉及线程安全问题多个线程同时对全局变量执行操作最终结果不一定等于线程数因为不是原子操作它包含读-改-写三步多个线程交错执行时会产生丢失更新。正确的理解模型是进程是资源分配的最小单位线程是CPU调度的最小单位同一进程的线程共享地址空间所以通信效率高但同步成本高不同进程地址空间隔离一个崩溃不影响另一个但通信要走IPC管道、消息队列、共享内存、socket。嵌入式里经常是多线程共享内存互斥锁的组合。我强烈建议把pthread_create的四个参数和pthread_mutex_lock/unlock的配对使用记牢笔试编程题的第二个空经常让你补充线程创建函数的参数或者指出代码中哪里缺少了锁。3.2 互斥、信号量与死锁场景题的高发区网易的笔试选择题很喜欢这样出系统中有两个任务任务A持有资源1等待资源2任务B持有资源2等待资源1。问系统处于什么状态答案显然是死锁。但更进阶的考法是问如何避免基本答法有四种破坏互斥条件、破坏持有并等待条件、破坏非抢占条件、破坏循环等待条件。在嵌入式场景中最实用的是资源有序分配法——所有任务都按同样的顺序申请资源循环等待自然被打破。这里有个初学者容易混淆的概念互斥锁和信号量。互斥锁强调所有权谁锁谁解不能跨线程解锁二值信号量更像通知机制一个任务释放give另一个任务获取take常用于中断和任务间的同步。这个区别在笔试的判断题里出现过我当时选了互斥锁可以在任意线程解锁这个错误选项还是有点可惜的。3.3 中断上下文与临界区嵌入式岗位的独特考点非嵌入式岗位的笔试很少考中断细节但嵌入式是必考。核心考点包括中断服务函数ISR里不能调用哪些函数不能调用printf、malloc、free等非可重入函数也不能调用会导致阻塞的函数。原因是ISR运行在中断上下文栈空间受限且中断可能随时嵌套任何不确定的行为都可能导致系统崩溃。临界区保护有哪几种方式关中断/开中断、调度锁、互斥锁、原子操作。其中关中断适合保护非常短的临界区因为它影响实时性——关中断时间过长会导致高优先级中断无法及时响应。中断标志位的判断cmp指令如何影响标志位跳转指令BEQ什么时候执行。基础的cmp r0, r1比较的是两个寄存器的值如果相等则置位Z标志BEQ在Z标志为1时跳转。这种汇编层面的考点提醒我们嵌入式笔试是可能考汇编的至少要能读懂常见的汇编指令。我给个实用建议关于中断你要能讲清楚中断上半部/下半部或者中断、软中断、tasklet、工作队列在Linux语境下的区别这在笔试问答题中很加分。3.4 优先级反转一道能区分水平的题优先级反转是嵌入式系统里的经典问题高优先级任务C在等一个信号量这个信号量被低优先级任务A持有而A正在等中优先级任务B让出CPU。因为B的优先级高于AB可以持续抢占A导致C虽然优先级最高却一直得不到执行。解决方案有优先级继承和优先级天花板两种。FreeRTOS的互斥信号量xSemaphoreCreateMutex在实现上采用了优先级继承机制而uC/OS II使用的是优先级天花板。笔试问答题如果给你一段描述让你判断是哪种问题、给出解决方案你至少要能说出优先级继承这个词并解释其核心思想当前持有信号量的低优先级任务临时提升到等待该信号量的最高优先级任务的优先级让它可以快速完成临界区并释放信号量。4. 计算机组成与嵌入式硬件基础不要忽略的底层素养如果操作系统是嵌入式软件工程师的上层建筑那么计算机组成就是地基。网易有道的笔试透露出的信息是他们希望候选人不只是一个能跑Linux的API调用者还要懂硬件是如何工作的。4.1 大小端与新唐/STM32平台实践大小端的基础题是32位整数0x12345678在小端模式下存储时内存低地址到高地址依次是78 56 34 12大端模式则是12 34 56 78。笔试喜欢逆着考给你一段内存dump78 56 34 12问你这是什么字节序、这个数是多少。更进阶的考点是实际工程中的字节序问题STM32默认小端而网络字节序是大端所以用STM32做网络通信时要把主机字节序转换成网络字节序用htonl、htons、ntohl、ntohs这些接口。在做自定义通信协议时一定要在协议文档里明确字节序否则就会出现我发了0x1234对方收到0x3412这种经典事故。4.2 常见总线协议UART、I2C、SPI从应试到实战这三种总线是嵌入式笔试的常客它们的对比表格建议直接背下来特性UARTI2CSPI信号线数2TX、RX2SDA、SCL4SCLK、MOSI、MISO、CS通信方式异步串行同步半双工同步全双工主从结构点对点多主多从一主多从速率较低100k/400k/1M可达数十MHz主要应用调试口、GPS传感器、EEPROMFlash、显示屏笔试的考法通常不是直接问区别而是给场景选总线比如现在要在板子上挂8个温度传感器希望占用IO最少同时支持多设备寻址选什么总线正确答案是I2C因为它用地址寻址只需两根线就能挂多个设备。而要接一个最高速率50MHz的Flash芯片选什么正确答案是SPI因为速率高、全双工。4.3 嵌入式系统的存储布局与启动流程这部分这几年考察比重上升尤其是结合了MCU和嵌入式Linux两个方向。MCU方向的典型问题是__attribute__((section(noinit)))修饰的变量放在哪个段它的特点是上电不复位初值常用于掉电保存数据。还有经典的链接脚本问题text段、data段、bss段、heap、stack分别存什么、加载在哪里、运行在哪里。Linux方向则考启动流程的顺序BootROM - BootloaderU-Boot- 内核解压 - 内核初始化 - 挂载根文件系统 - 启动init进程。每个阶段的职责要能说清楚比如U-Boot的职责是初始化DDR、加载内核镜像到内存、设置启动参数并跳转内核初始化则包含中断向量表设置、内存管理单元初始化、设备驱动加载。4.4 网络基础笔试里的隐藏科目按常理嵌入式软件工程师不太会考太多网络但网易有道做的是智能硬件设备联网是基本功所以TCP/IP相关知识也占了几个选择题。考点集中在TCP三次握手和四次挥手的状态转换、TCP和UDP的区别、IP地址与子网掩码的计算、HTTP的基础方法。这些只要把计算机网络教材过一遍就没问题不用刻意刷题。5. 嵌入式Linux与驱动校招笔试中最拉分的部分这一部分是我觉得最有区分度的地方。纯应用开发背景的人没碰过内核搞单片机的人没跑过Linux真正两方面都有涉猎的人在这部分能拉开很大差距。网易有道的嵌入式软件工程师偏系统软件方向所以Linux相关的题目占了不少比重。5.1 交叉编译与工具链环境理解是基础交叉编译是嵌入式Linux开发的入门概念笔试必考。直接背定义在x86主机上编译生成ARM目标平台的可执行文件这个过程就是交叉编译。需要设置的环境变量包括CC、CROSS_COMPILE、ARCH典型的交叉编译工具链前缀是arm-linux-gnueabihf-gcc。笔试的考法一般有两种一是给出file命令输出让你判断该可执行文件是不是ARM架构的二是问你在开发板上运行程序报cannot execute binary file: Exec format error的原因——大概率是拿x86编译的产物拷到ARM板子上跑了。这里我建议你实际做一次完整的交叉编译从工具链安装到静态编译一个helloworld再到板端运行笔试里很多抽象概念你一旦做过了印象会非常深。5.2 设备树从看不太懂到能说清楚设备树Device Tree是Linux内核中描述硬件信息的数据结构笔试选择题经常给出一个设备树节点让你分析某个外设的寄存器基地址和中断号。看这段典型示例serial40001000 { compatible arm,pl011; reg 0x40001000 0x1000; interrupts 0 12 4; clock-frequency 24000000; };compatible是驱动匹配的关键字符串reg描述寄存器起始地址和长度interrupts描述中断配置。笔试考你对设备树的直观理解比如修改设备树后是否需要重新编译内核答案是只需要编译设备树二进制dtb不用重编整个内核因为设备树和内核镜像是在bootloader阶段加载的。5.3 驱动模型字符设备驱动的基本盘网易笔试对驱动的考察不会太深但至少会考到字符设备驱动、块设备驱动、网络设备驱动的区别和Linux驱动使用的基本接口。字符设备是最基础的驱动类型它的核心操作包括init初始化设备、open打开设备、read读数据、write写数据、ioctl控制命令、release释放设备。配合的函数包括register_chrdev、class_create、device_create等。你要能在代码填空里补上file_operations结构体的正确赋值static struct file_operations fops { .owner THIS_MODULE, .open my_open, .read my_read, .write my_write, .ioctl my_ioctl, .release my_release, };这段代码我曾经在笔试中遇到过类似的变体建议直接背下来。5.4 根文件系统与系统启动需要理解的关键链路嵌入式Linux的根文件系统涉及几个概念rootfs、initramfs、initrd、busybox、NFS挂载。笔试容易考的是内核挂载根文件系统失败时会怎样常见答案是内核panic或者进入initramfs的应急shell。还有一个高频考点是各常见目录的用途/proc是内存中的伪文件系统/sys也是伪文件系统用于暴露内核对象/dev存放设备节点。这里有一个非常实操的理解方法自己动手用 buildroot 编一个最小的根文件系统看到整个流程之后笔试中的概念就不再是死记硬背了。6. 编程题实战边界条件与贪心策略网易有道笔试的编程题没有特别多的算法难题但它很会考边界条件。第一道题是链表相关的操作题第二道是模拟题都需要你对自己的代码有很强的自测意识。6.1 链表题的边界条件陷阱常见的链表题解法比如删除链表中重复节点判断链表是否有环核心思路都不难难的是边界条件while (cur ! NULL cur-next ! NULL) { // ... }这段代码里如果写成while (cur-next ! NULL)当cur本身就是空指针时会直接段错误。另一个高频坑删除头节点时如果直接head head-next而不释放旧节点就会内存泄漏如果链表只有一个节点删除后head必须置空。笔试系统通常不会提示你内存泄漏但会有空指针异常用例写代码时一定要养成每一步都问一下会不会为空的习惯。6.2 模拟题的状态机思维第二道模拟题很典型给你一段指令序列模拟某个设备的状态变化。这种题的核心是定义清晰的状态转移表而不是上来就写if-else嵌套。比如设备有STOPPED、RUNNING、PAUSED三种状态在RUNNING状态下收到PAUSE指令进入PAUSED在PAUSED下收到RESUME回到RUNNING在STOPPED下收到RUN进入RUNNING。我建议使用switch-case而不是一连串的if因为switch-case的状态转移更清晰不易遗漏分支。提交之前把空输入超长指令非法指令这些边界情况逐一遍历一遍。6.3 调试技巧与作答策略在线笔试的编辑器一般没有单步调试功能最好在本地IDE里把代码跑通再粘贴过去。不要在考场上打开LeetCode临时刷题来不及的。我自己的习惯是每道题先写一个暴力解保底再考虑优化如果优化思路不清晰就留在最后暴力解能过一部分用例也好过留白。7. 从笔试到面试复盘建议与备考路径笔试结束后到面试通知之间通常有一到两周的间隔这段时间的复盘比盲目刷题更重要。我的做法是把笔试中拿不准的题目全部记下来逐题查资料、补盲区。因为面试官很可能从你的笔试记录里挑题追问——尤其是那些选错了的题。7.1 备考资料与刷题排序复习优先级我建议这样排第一优先级C语言指针、内存、位运算、链表、结构体对齐——每天保证2小时第二优先级操作系统进程线程、同步互斥、死锁、中断——每天1.5小时第三优先级嵌入式Linux交叉编译、驱动模型、设备树、文件系统——每天1小时第四优先级计算机网络和计算机组成大小端、总线协议、TCP/IP——每天0.5小时编程题刷剑指Offer和LeetCode热题100的前80题足够重点是链表、二叉树、栈队列、模拟题这些基础类型动态规划和图论在笔试中考得少不需要投入太多时间。7.2 经验之谈笔试暴露出来的想当然我这次笔试暴露出的最大问题就是想当然。比如结构体对齐那道题我想当然地认为32位机上char后面紧跟int会自动错开不需要填充结果答案是12而不是8。这只是基础不牢的一个缩影。嵌入式软件工程师笔试真正想筛选的是那些对底层机制有敬畏心的人——你写的每一行C代码最终都要跑在受限的硬件上任何想当然都可能变成线上崩溃。还有一点别忽略项目经验的梳理。笔试只是门槛真正决定offer的是面试环节。面试官大概率会追问你简历上写的嵌入式项目比如你是否自己动手画过原理图、是否做过RTOS移植、是否调过I2C/SPI设备驱动、是否遇到过内存问题。我建议在等面试通知的间隙把自己做过的项目按项目背景-技术方案-遇到的问题-解决过程-最终成果的结构整理成文档讲给别人听一遍直到能流畅表达。7.3 一个具体的方向大模型与嵌入式的交叉最近行业里将大模型部署到嵌入式板中这个话题很热有几个热搜词也在聊这个方向。我个人的观点是校招笔试大概率不会考这个因为大模型部署涉及的是推理框架如TinyML、ONNX Runtime、TensorRT Lite和模型量化INT8、混合精度这些比较垂直的技术而校招考察的是候选人的基础素养。但面试环节可能会提及尤其是有道这类做智能硬件的公司你要能说出在资源受限设备上部署大模型的核心瓶颈是内存带宽、算力和功耗这样的观点最好还能举出量化和剪枝这两个具体手段这会让面试官觉得你有技术敏感度。7.4 心态与节奏校招是一场长跑笔试只是其中一环。我见过很多人因为某场笔试发挥不好就陷入自我怀疑其实完全没必要——网易有道有提前批和正式批提前批挂了还能再投正式批理论上你至少有两次笔试机会。此外笔试成绩不理想不代表你能力不行可能只是策略失误比如时间分配不合理。把每场笔试的教训都沉淀下来下一场自然会更好。最后分享一个小技巧笔试结束后立刻用手机把拿不准的题目拍下来或者在本地做一个错题文档。这份文档不仅对后续面试有用对你整个秋招的复习路径也有指导意义——你会发现各个公司考察的知识点高度重合你做过的错题整理后面基本能覆盖大多数笔试题。
返回列表