资讯动态

从内核到App:嵌入式Linux与安卓驱动开发全链路实战

发布时间:2026/10/27 18:10:47 来源:尧图企业网站定制
嵌入式Linux 安卓 驱动开发这个组合凡是投过BSP板级支持包相关岗位的人都不会陌生。几乎每一家做智能硬件、车载、物联网设备的公司招聘JD里都会挂着类似的关键词面试时也会反复问到底层驱动的实现链路。你可能会发现一个现象很多做应用开发的人转驱动岗难的不是语法而是脑子里没有一张完整的“从Linux内核到安卓应用”的路径图。这篇内容想跟你聊的就是这么一件事——我基于一个贴近企业真实需求的传感器驱动项目来拆解从内核驱动的编写、设备树配置到安卓HAL层封装、JNI上抛、Framework服务集成再到App最终读到数据完整走一遍。不管你是刚入行的嵌入式工程师还是想往BSP方向转的应用开发又或者是正在准备秋招的应届生这套思路都能直接套用特别是面试时怎么把项目讲成亮点这部分我会毫无保留地分享。1. 这个项目的核心思路从Linux内核到安卓应用的一条完整链路1.1 为什么说驱动开发不只是“写内核模块”很多人一提到驱动开发第一反应就是写一个字符设备驱动然后用insmod加载进去在/dev下看到一个节点觉得这就完事了。但真实的企业场景完全不是这样。尤其是安卓设备的BSP工程师你面对的是一条纵向贯通的技术栈Linux内核驱动负责操作硬件HAL层负责向上提供稳定的接口Framework层通过JNI调用Native代码最终App通过标准API拿数据。这条链路里每一环都有独立的坑。内核驱动写得再好如果HAL层的接口设计不合理Framework层集成时就会非常别扭HAL层如果直接暴露了太多的Linux细节应用层又没法做到“硬件无关”。所以我在做这个项目时给自己定了一个原则要做一个完整的“端到端”功能而不是某个孤立的模块。项目采用的是经典温湿度传感器方案以SHT30或HDC1080这类I2C接口的传感器为例。选择传感器作为载体原因很实际I2C驱动是驱动开发里最典型、面试出现频率最高的总线类型硬件原理不复杂同时又涉及设备树、probe机制、寄存器读写、数据上报等几乎所有核心知识点。把I2C驱动吃透了后面再做SPI、USB甚至GPU驱动思路都是相通的。1.2 实战项目的选型逻辑如何把多个技术点串联成一个项目如果只是写一个传感器驱动这个项目还是太单薄。我在实际做的时候给项目增加了两个关键工程点第一个是根文件系统使用NFS挂载。开发阶段如果把rootfs每次都用dd烧写进SD卡或eMMC每改一次代码就要重新烧写一次效率极低。用NFS把开发板上的根文件系统挂载到Ubuntu宿主机上内核编译完直接拷贝到NFS共享目录开发板重启即生效迭代速度能提升好几倍。这里有一个新手必踩的坑新版内核默认NFS协议版本是v4很多老旧的开发板或者特定Ubuntu版本里NFSv4的支持不全挂载时会卡在VFS: Unable to mount root fs。我用的是nfsvers3参数强制走v3后面会详细讲。第二个工程点是安卓侧的框架集成。传感器驱动如果只做到Linux用户态的C程序能读数据其实还只完成了一半。安卓系统有自己的硬件抽象层规范你需要实现一个HAL模块再通过JNI封装成Java接口注册为系统级服务让App可以直接调用。这个过程中你会接触到hw_get_module、hardware.h、JNI方法映射、SystemServer注册等一堆听起来陌生但实际有规律可循的机制。选型里还有一个容易被忽略的点芯片平台。我建议有条件的话用瑞芯微RK3399或者NXP i.MX6ULL这类资料丰富、社区活跃的芯片。原因很简单这类平台的BSP源码和相关讨论最多遇到问题时搜索一下基本都有解决方案。如果手里只有全志或联发科的板子也没关系驱动框架是通用的只是设备树和引脚配置不同而已。2. 项目前期准备环境、源码与开发板选择的几个细节2.1 环境搭建的第一步交叉编译工具链与内核源码树嵌入式Linux驱动开发的第一步不是写代码而是把交叉编译环境弄好。很多新人在自己的笔记本上编译模块拿到开发板上直接insmod然后报错invalid module format其实就是内核版本不一致或者编译器版本不匹配导致的。我使用的组合是Ubuntu 18.04/20.04 arm-linux-gnueabihf-工具链针对ARM 32位平台。如果是RK3399这类ARM 64位平台就用aarch64-linux-gnu-。关键点在于交叉编译工具链的大版本必须和开发板商提供的工具链保持一致否则模块里的__this_module符号和内核的modversions对不上。内核源码树建议直接使用开发板厂商提供的版本而不是从kernel.org下载。因为厂商会打上很多芯片相关的patch比如电源管理、GPU、显示等。用官方内核源码去编译模块很多时候能顺利编译通过但加载时会出现Unknown symbol或版本magic不匹配的问题。这里给一段编译驱动的Makefile示例适用于大多数内核模块开发场景obj-m : tmp_sensor.o KERNELDIR ? /home/user/rk3399-kernel CROSS_COMPILE ? aarch64-linux-gnu- PWD : $(shell pwd) all: $(MAKE) ARCHarm64 CROSS_COMPILE$(CROSS_COMPILE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) ARCHarm64 CROSS_COMPILE$(CROSS_COMPILE) -C $(KERNELDIR) M$(PWD) clean注意ARCH变量要和目标平台一致32位ARM平台写ARCHarm64位写ARCHarm64。这个变量写错的话编译时会出现一大堆asm/types.h找不到之类的报错原因就是内核在寻找架构相关的头文件时找不到对应的目录。2.2 NFS根文件系统挂载的完整配置与v3协议问题NFS挂载根文件系统的思路是开发板上通过bootargs参数告诉内核根设备是NFS内核在init阶段通过网卡访问宿主机导出的目录将其作为根文件系统使用。宿主机需要安装NFS服务sudo apt update sudo apt install nfs-kernel-server sudo mkdir -p /srv/nfs/rootfs然后在/etc/exports里添加一行/srv/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check,insecure)这里的小细节很多。no_root_squash是必须的否则开发板的root用户在NFS共享目录里会被映射为nobody导致systemd等进程无法正常启动你会在启动日志里看到一堆Permission denied。insecure这个选项允许客户端使用大于1024的端口进行NFS连接部分板卡的内核NFS客户端会使用随机高位端口不加上这个选项可能连接失败。开发板u-boot的bootargs需要指定NFS作为根设备我实际使用的参数是setenv bootargs root/dev/nfs nfsroot192.168.1.100:/srv/nfs/rootfs,prototcp,nfsvers3 ip192.168.1.50:192.168.1.100::255.255.255.0::eth0:off rw init/sbin/init把nfsvers3显式写清楚很关键。我踩过这个坑在某个新内核版本里NFS客户端默认尝试NFSv4.2挂载而宿主机的export配置没有启用NFSv4支持结果内核日志反复打印VFS: Unable to mount root fs via NFS, trying to continue这样的错误。换成nfsvers3之后一次通过。内核侧还需要确认以下配置项已开启CONFIG_ROOT_NFSy CONFIG_NFS_V3y CONFIG_NFS_V3_ACLy CONFIG_NFS_FSy如果内核里这些配置没有打开即使bootargs写对了也会在挂载阶段直接报错。建议加载模块前先用menuconfig检查一遍别等到启动失败了再回头查。2.3 开发板上的文件系统骨架准备NFS共享目录里光放一套rootfs不够还需要准备最基本的系统二进制比如busybox。对于只做驱动验证和HAL开发的场景不推荐直接拿安卓的system.img来挂载——因为安卓的init和SELinux策略会让调试变得非常痛苦。我更倾向于准备一套精简的busybox根文件系统先在内核态把驱动跑通然后再过渡到完整的安卓系统上做HAL集成。为什么后面还要切到安卓系统因为HAL层运行在安卓用户空间需要binder、servicemanager、JNI环境等基础服务这些在精简rootfs里是不存在的。开发阶段先用busybox rootfs验证内核驱动这一步把硬件层面的问题全部消除之后再进入安卓系统你就只需要关注HAL和Framework层的逻辑排错范围一下缩小了大半。3. 核心实现一个可以写进简历的驱动开发全流程3.1 字符设备驱动框架与设备号管理内核驱动最基础的部分是字符设备。不管底层硬件是什么最终面向用户态时通常通过文件操作接口open/read/ioctl来交互。先看一段最核心的框架代码#include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/slab.h #include linux/i2c.h #include linux/uaccess.h #include linux/mutex.h #define TMP_SENSOR_MAJOR 0 // 动态分配主设备号 #define TMP_SENSOR_MINOR 0 #define TMP_SENSOR_DEV_NUM 1 static int tmp_sensor_major TMP_SENSOR_MAJOR; static struct cdev tmp_sensor_cdev; static struct class *tmp_sensor_class; static int tmp_sensor_open(struct inode *inode, struct file *filp) { struct i2c_client *client NULL; client container_of(inode-i_cdev, struct i2c_client, dev); filp-private_data client; return 0; }这里有一个重要概念主设备号和次设备号。主设备号描述设备类型次设备号区分同类设备的不同实例。我用主设备号0告诉内核动态分配这样避免手动指定时冲突。实际生成的设备节点由devtmpfs或者mdev自动在/dev目录创建这个机制比手动mknod方便得多但要保证内核配置里有CONFIG_DEVTMPFSy。file_operations结构体的注册是驱动对外能力的声明static const struct file_operations tmp_sensor_fops { .owner THIS_MODULE, .open tmp_sensor_open, .read tmp_sensor_read, .release tmp_sensor_release, };用户空间调用read的时候会进入tmp_sensor_read函数。这个函数里要做两件最重要的事情一是从client结构体即私有数据中拿到I2C总线的操作句柄二是通过I2C协议读取传感器寄存器里的温湿度原始值再拷贝给用户空间的缓冲区。3.2 Platform驱动与设备树硬件描述与驱动解耦的现代方式在旧版内核里硬件信息通常直接写死在驱动代码中比如硬编码I2C总线号、设备地址、中断引脚。这种方式一旦换了板子就得改驱动代码重新编译。新版内核使用设备树DTB来描述硬件资源驱动通过compatible属性与设备节点自动绑定。设备树中I2C传感器节点的写法如下i2c1 { status okay; tmp_sensor48 { compatible example,tmp_sensor; reg 0x48; interrupt-parent gpio1; interrupts 18 IRQ_TYPE_EDGE_FALLING; }; };compatible里的“example,tmp_sensor”必须与驱动中的of_match_table完全一致。reg属性表示I2C设备地址是0x48。interrupts描述中断引脚为GPIO1组的第18脚下降沿触发。驱动里匹配设备树的代码是这样static const struct of_device_id tmp_sensor_of_match[] { { .compatible example,tmp_sensor, }, { } }; MODULE_DEVICE_TABLE(of, tmp_sensor_of_match);当内核解析设备树发现匹配的compatible后会调用驱动里的probe函数。这也是I2C驱动比裸字符设备多出来的关键结构。probe函数会收到一个i2c_client结构体里面包含了总线适配器、设备地址等现成的硬件上下文。传统的register_chrdev_region、cdev_add等注册过程通常在probe里完成这样就实现了“设备树描述硬件、驱动处理逻辑”的分离。发言里老是听到“平台设备”、“总线设备驱动模型”其实就是这套机制Linux把设备和驱动分开匹配设备来了叫probe设备移除叫remove。这也是面试环节最爱深挖的点。3.3 数据上报与并发控制从内核到用户态的正确姿势驱动读I2C传感器数据一般有两种方式轮询和中断。温湿度传感器这类低速设备用轮询就行每隔几百毫秒读一次。我采用read函数同步读取模式用户态App调用read时如果当前没有数据就通过等待队列让进程挂起直到传感器数据准备好再唤醒。这涉及一个很关键的内核编程原则睡眠安全。在等待队列中睡眠时进程并没有占用CPU所以整个系统不受影响。但如果你的read函数里用了自旋锁持有期间睡眠那就直接panic了。我在实际项目中用mutex保护临界区用wait_event_interruptible实现阻塞等待这是低速设备驱动的标准组合。数据从内核拷贝到用户态必须用copy_to_user不能直接memcpy。原因很简单内核空间和用户空间的地址映射不同直接访问用户传进来的指针会引发段错误甚至内核崩溃。等以后做了mmap方式的驱动也就是把内核缓冲区映射到用户空间就可以减少一次拷贝但字符设备的传统方式仍然是copy_to_user。static ssize_t tmp_sensor_read(struct file *filp, char __user *buf, size_t count, loff_t *offset) { struct i2c_client *client filp-private_data; unsigned char data[6] {0}; int ret; ret i2c_smbus_read_i2c_block_data(client, 0x00, 6, data); if (ret 0) { dev_err(client-dev, read sensor data failed\n); return ret; } if (copy_to_user(buf, data, sizeof(data))) return -EFAULT; return sizeof(data); }注意这里读取的是SHT30在0x00命令下的数据块总共6字节前两个字节是温度原始值后两个是湿度中间可能还有CRC校验。实际业务中你还需要根据传感器的数据手册换算成温度和湿度值不要直接拿原始值上报给应用层。实操中一定还遇到一个问题把这个.ko文件交叉编译出来后insmod到开发板发现/dev/tmp_sensor节点并没有出现。这时候不要慌先检查是不是devtmpfs没挂载或者模块在注册时class_create失败。一个稳的习惯是先在busybox rootfs里用mdev或者手动mknod确认节点能出现再往上走。4. 安卓侧的封装从HAL到Framework再到App4.1 HAL层设计为什么安卓硬件访问要绕这么一圈安卓系统不会允许App直接去open/dev下的节点而是定义了一套硬件抽象层规范。HAL的作用是让上层Framework不必关心底层内核驱动怎么实现在RK3399上跑的传感器驱动和在骁龙平台上跑的对Framework来说接口应该是一致的。HAL模块首先要实现一个入口函数hw_get_module它会根据硬件模块ID比如SENSOR_HARDWARE_MODULE_ID去/system/lib/hw/目录下找对应的.so文件。所以HAL代码的目录结构是有讲究的编译生成的库文件名必须符合命名规则例如sensors.default.so、sensors.rk30board.so否则模块加载直接失败。一个最基本的传感器HAL模块结构如下struct hw_module_methods_t tmp_sensor_module_methods { .open tmp_sensor_device_open, }; struct hw_module_t HAL_MODULE_INFO_SYM { .tag HARDWARE_MODULE_TAG, .module_api_version SENSOR_MODULE_API_VERSION_1_0, .hal_api_version HARDWARE_HAL_API_VERSION, .id SENSOR_HARDWARE_MODULE_ID, .name TMP Sensor HAL, .methods tmp_sensor_module_methods, .dso NULL, .reserved {0}, };HAL_MODULE_INFO_SYM这个符号名是hardware.h里定义的宏严格规定不能改。我从新人时期就吃过这个亏随手改成了my_module_info结果hw_get_module永远找不到这个库。HAL层的代码一头是标准接口另一头就是open(/dev/tmp_sensor)这种系统调用。到这里Linux驱动和安卓系统之间的桥梁就算搭上了。有一个非常实际的建议HAL层的dev_open建议在open失败时做两次重试中间间隔100ms。因为安卓系统启动早期SELinux策略可能还没完全加载设备节点的权限没有立刻生效直接open会失败。这种重试机制不会影响正式运行时的性能但能减少启动阶段的偶发异常。4.2 JNI与Framework层把C函数暴露成Java接口Framework层是一个Java世界但它需要通过JNI调用到C/C世界的HAL。JNI的作用就是注册一组native方法让Java层的类可以调用到C函数。这一层的代码通常写在frameworks/base/services/core/jni/目录下。JNI方法注册的代码核心如下static jint tmp_sensor_init(JNIEnv *env, jobject clazz) { struct sensors_module_t *module; hw_get_module(SENSOR_HARDWARE_MODULE_ID, (hw_module_t const**)module); // 初始化设备 return 0; } static const JNINativeMethod gMethods[] { { nativeInit, ()I, (void *)tmp_sensor_init }, { nativeRead, ()[B, (void *)tmp_sensor_read }, { nativeExit, ()V, (void *)tmp_sensor_exit }, }; int register_android_server_TmpSensorService(JNIEnv *env) { return jniRegisterNativeMethods(env, android/server/TmpSensorService, gMethods, NELEM(gMethods)); }Java层的TmpSensorService是一个系统服务通过SystemServer的main方法注册到servicemanager里之后App通过Binder可以获取到该服务实例再调用其方法拿到数据。Binder是安卓最核心的IPC机制它解决了多进程之间的函数调用和数据传递问题。虽然系统服务的内部实现可以简化到“用static方法直接封装native函数”但正规的架构里还是应当通过Binder服务暴露给App这样可以让服务运行在独立的系统进程中权限控制也更清晰。常见的一个坑是SystemServer启动时会按照固定的顺序启动服务如果在某个阶段调用还没初始化的服务进程会卡死或者直接crash。我在项目里是把TmpSensorService放在了比较靠后的阶段并且捕获了初始化异常避免因为传感器设备不存在导致SystemServer启动失败。4.3 App层读取数据的完整路径与权限问题App通过Context.getSystemService拿到TmpSensorService之后调用getTemperature()方法实际上会经历一层一层的调用链App → Binder → SystemServer → Java方法 → JNI → native方法 → HAL → 字符设备节点 → 内核i2c驱动 → 传感器硬件。这个链路说出来在面试中本身就是个亮点很多面试者能讲Linux字符设备能讲安卓四大组件但能把整条调用链串起来的很少。面试官会认为你真的理解BSP工程师在整个系统里的位置。权限问题在这个环节会冒出来。安卓的SELinux会使能态策略严格控制进程能访问的资源包括设备节点。你会在logcat里看到类似这样的错误avc: denied { read } for pid1234 commtmp_sensor_app nametmp_sensor devtmpfs scontextu:r:untrusted_app:s0 tcontextu:object_r:device:s0 tclasschr_file解决办法是在te文件里添加allow规则。如果是自定义HAL并由system_server进程调用一般只需要在system_app.te或者system_server.te里加上对tmp_sensor设备的访问许可。如果你没有权限改SELinux的sepolicy可以通过setenforce 0先关掉强制模式验证功能逻辑但要清楚这只是开发期的临时手段正式系统里一定要把selinux策略补上。5. 调试与问题排查实录这些坑大概率你也会遇到5.1 insmod失败与设备节点不出现的排查方法新手最容易撞到的几个报错我列一份排查顺序表现象排查方向常见原因insmod报Exec format error架构不匹配检查ARCH、CROSS_COMPILE是否与板子一致32位板子加载了64位模块insmod报Invalid module format内核版本/配置不一致模块针对的内核源码树与当前内核源码版本不同insmod成功但/dev下没有节点devtmpfs未启用或mdev未配置确认CONFIG_DEVTMPFSy后重启open设备节点报Permission denied设备节点权限或SELinux手动chmod 666 /dev/tmp_sensor测试cat /dev/tmp_sensor返回乱码用户态缓冲区与内核数据格式不匹配检查read返回值与数据长度使用dmesg是最直接的排查手段。insmod失败时先看dmesg的输出大部分模块注册错误都会在日志里有明确线索。我习惯在驱动代码里加一些dev_info打印比如probe成功、read失败、i2c通信异常时打印state信息线上调试阶段再统一用dev_dbg替换可以减少很多无用功。5.2 NFS挂载卡死的排查细节NFS挂载失败时内核会反复重试。你会在串口看到网络文件系统挂载失败的打印但最关键的还是要看它卡在哪一步是ARP解析不通还是NFS RPC请求被拒还是VFS挂载超时如果是网络链路不通检查bootargs里ip参数是否配置正确。ip参数的格式是开发板IP:服务器IP:网关:掩码::网卡名:off。很多板子有两个网卡名字不是eth0而是eth1甚至lan0必须要认真看开发板原理图和u-boot的启动打印。如果网络通了但NFS挂载报No such file or directory先去宿主机上手动验证一下共享目录是否存在且权限正确sudo exportfs -ra showmount -e 192.168.1.100直接在开发板上用mount命令挂载NFS目录来排查mount -t nfs -o nolock,prototcp,nfsvers3 192.168.1.100:/srv/nfs/rootfs /mnt这个命令可以帮你把“根文件系统挂载的问题”和“内核config问题”剥离开。如果手动mount都不行就排除了内核的rootfs挂载路径问题集中排查NFS服务和网络配置。5.3 安卓SELinux权限导致的打开节点失败有一个典型场景内核驱动和HAL都验证通过App在logcat里也拿到了服务但read数据时返回Permission denied。很多人在这一步排查很久才怀疑到SELinux。我的排查习惯是先执行setenforce 0看功能是否恢复如果恢复再用adb shell dmesg | grep avc抓取详细的denied信息然后把缺失的权限补到对应te文件里重新编译boot.img或直接编译进vendor。生产环境的做法当然是把规则正式加进sepolicy但在项目验证阶段用setenforce 0快速确认是SELinux导致的问题效率最高。5.4 RTSP与流数据缓存类项目的延伸思路驱动开发里另一个高频需求方向是多媒体。很多车载、盒子项目里都涉及“安卓缓存RTSP流”这种场景。你以为RTSP是应用层播放器的事不归驱动工程师管但事实上这个需求往下探会触及dma-buf、Ion内存管理、硬件编解码器等底层机制。RTSP流本身的解析和缓存策略一般在应用层做例如用ijkplayer或ExoPlayer的底层库在Native层做缓冲。但真正要做得稳你还需要理解GOP的结构、I帧的位置、关键帧对齐等概念否则缓存出来的文件在拖动进度条时必花屏。驱动工程师在这个领域的切入点往往是buffer分配的优化用ion分配物理连续内存通过dma-buf机制把buffer fd传给编解码器或显示器避免内存搬运带来的性能损耗。所以要提醒想走驱动的同学别把自己的视野局限在GPIO和I2C上多媒体方向一样需要底层功底而且薪资天花板更高。6. 如何把项目讲成面试加分项Offer收割的实战心法6.1 面试官究竟在考察什么面试官问驱动开发的题表面考的是API和机制实际考的是系统化的理解能力和排查问题的路径。这四个维度是我总结了多次面试后得出的一是代码能力不要求你现场写完全能编译的驱动但要能画出字符设备的骨架、说出file_operations里read流程的关键步骤。二是硬件理解能力比如I2C时序、设备树里reg和interrupts怎么来的、i2c_smbus_read_i2c_block_data和i2c_transfer的区别。三是系统级思维例如open一个设备节点后到底发生了什么这中间经过VFS、设备驱动模型、总线匹配、drvier的probe等一系列环节。四是排错能力跟你聊线上问题怎么定位、dmesg怎么看、复现步骤怎么设计。6.2 项目陈述的叙事结构问题定位优先于功能实现很多人在面试里描述项目时张嘴就是“我实现了一个温湿度传感器驱动”这其实是最弱的开场。建议按照“问题定位-方案选型-落地实现-量化结果”的顺序来讲。以本项目为例你说“开发阶段每次调试rootfs要重新烧写emmc一次要几分钟严重拖慢进度”这个开头展现了工程痛点然后引出NFS挂载rootfs的方案顺带抛出一个后续排错的bug——NFSv4挂载失败通过强制nfsvers3解决。这样面试官会觉得你有真正的工程嗅觉。接着讲传感器驱动时不要只讲功能重点讲设计取舍为什么用设备树而不是平台数据注册为什么read里用阻塞式IO和mutex为什么HAL层要做两次open重试每一处都说清楚“不这样做会发生什么”这样才能展示深度。6.3 高频面试题与参考要点速查表我整理了一份在BSP方向面试中出现频率极高的题目清单附上简述思路面试题参考回答要点字符设备驱动开发完整流程module_init → register_chrdev → cdev_add → class_create → device_create设备树匹配probe的过程compatible字段与of_match_table匹配后内核遍历设备链表调用probe为什么驱动读取要阻塞式避免忙等浪费CPU通过等待队列实现进程睡眠与唤醒copy_to_user能做中断上下文吗不能可能睡眠中断语境下应使用调度延迟或workqueue自旋锁和互斥锁的选择临界区短、不能睡眠用自旋锁临界区长、可能睡眠用互斥锁HAL层的作用屏蔽不同SoC的硬件差异给Framework提供统一接口Binder机制的概要基于内核binder驱动通过mmap零拷贝传递数据完成跨进程调用insmod后没有设备节点怎么办检查devtmpfs、class、device_create是否成功、dmesg日志这些题如果每个你都能讲出“为什么”再配合项目里的具体细节面试拿下面试官的把握就很大了。其实面试官也清楚驱动开发对新人来说上手门槛不低他们要的并不是你什么都会而是你“知道自己会什么、不会什么并且有完整的排查思路”。最后分享一个我个人的习惯每次到一个新平台开发驱动我都会先花半小时把Linux内核里现成的同类驱动源码读一遍比如drivers/i2c/里的驱动、drivers/hwmon/里的传感器驱动然后在此基础上做裁剪和适配而不是从头造轮子。这套方法让我在项目的每个阶段都稳扎稳打希望对你也有用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑