八股文总结

发布时间:2026/6/10 11:04:22

八股文总结 1、通信协议相关1.1、I2C协议1、i2cdetect的核心原理向 I2C 总线的每个有效 7 位地址发送「地址 写位」帧通过检测从设备的ACK 应答判断地址是否有设备它会跳过 I2C 规范的保留地址输出中UU代表地址被内核驱动占用十六进制数字代表探测到的有效设备地址。2、linux驱动中如何进行i2c读写用什么apistruct i2c_client是 Linux 内核对单个 I2C 从设备的抽象是驱动操作 I2C 硬件的核心句柄。i2c_transferinti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum);//adapI2C 适配器从i2c_client-adapter获取//msgsI2C 消息数组struct i2c_msg每个消息代表一次读 / 写操作//num消息数量struct i2c_msgstructi2c_msg{__u16 addr;// 设备的I2C从地址7位/10位__u16 flags;// 标志位0写操作I2C_M_RD读操作__u16 len;// 数据长度__u8*buf;// 数据缓冲区写待发送数据读接收数据};读数据structi2c_client*client;structi2c_msgmsg[]{{.addrclient-addr,.flags0,.len1,.bufreg_addr,},{.addrclient-addr,.flagsI2C_M_RD,//读.lenlen,.bufdata,},};i2c_transfer(client-adapter,msg,ARRAY_SIZE(msg);写数据structi2c_client*client;structi2c_msgmsg{.addrclient-addr,.flags0,//写.lenlen1,.bufNULL,};msg.bufkmalloc(len1,GFP_KERNEL);if(!msg.buf){PERR(Allocate memory failed\n);return-ENOMEM;}msg.buf[0]reg_addr;memcpy(msg.buf[1],data,len);i2c_transfer(client-adapter,msg,1);3、linux应用层怎么进行i2c读写方式 1简单读写read/write应用层通过标准文件 IOopen/close/ioctl/read/write 操作这个设备文件就能间接实现对 I2C 总线上设备的读写open(“/dev/i2c-1”, O_RDWR)打开 I2C1 总线设备文件ioctl(fd, I2C_SLAVE, addr)设置要操作的 I2C 设备从地址read()/write()或ioctl(fd, I2C_RDWR, msgset)执行具体的读写操作close(fd)关闭设备文件。方式 2通用读写I2C_RDWR推荐通过ioctl(I2C_RDWR)实现原子化的多消息传输比如 “先写寄存器地址再读数据”这是应用层最通用、最可靠的 I2C 读写方式完全对应内核层的i2c_transfer。#includestdio.h#includefcntl.h#includeunistd.h#includeerrno.h#includesys/ioctl.h#includelinux/i2c-dev.h#includelinux/i2c.h#defineI2C_BUS/dev/i2c-1// I2C总线设备文件#defineI2C_DEV_ADDR0x48// I2C设备从地址/** * i2c_read_reg - 应用层读I2C设备寄存器 * fd: 已打开的I2C设备文件描述符 * reg: 寄存器地址 * buf: 接收数据的缓冲区 * len: 要读取的长度 * 返回0成功-1失败 */inti2c_read_reg(intfd,unsignedcharreg,unsignedchar*buf,intlen){structi2c_msgmsgs[2];structi2c_rdwr_ioctl_datamsgset;// 第1个消息写寄存器地址告诉设备要读哪个寄存器msgs[0].addrI2C_DEV_ADDR;msgs[0].flags0;// 写标志msgs[0].len1;msgs[0].bufreg;// 第2个消息读寄存器数据msgs[1].addrI2C_DEV_ADDR;msgs[1].flagsI2C_M_RD;// 读标志msgs[1].lenlen;msgs[1].bufbuf;// 组装消息集合msgset.msgsmsgs;msgset.nmsgs2;// 执行I2C读写原子操作if(ioctl(fd,I2C_RDWR,msgset)0){perror(i2c read reg failed);return-1;}return0;}/** * i2c_write_reg - 应用层写I2C设备寄存器 * fd: 已打开的I2C设备文件描述符 * reg: 寄存器地址 * buf: 要写入的数据缓冲区 * len: 要写入的长度 * 返回0成功-1失败 */inti2c_write_reg(intfd,unsignedcharreg,unsignedchar*buf,intlen){structi2c_msgmsg;structi2c_rdwr_ioctl_datamsgset;unsignedchar*write_buf;// 申请缓冲区寄存器地址 数据write_buf(unsignedchar*)malloc(1len);if(!write_buf){perror(malloc failed);return-1;}write_buf[0]reg;for(inti0;ilen;i){write_buf[1i]buf[i];}// 组装写消息msg.addrI2C_DEV_ADDR;msg.flags0;// 写标志msg.len1len;msg.bufwrite_buf;msgset.msgsmsg;msgset.nmsgs1;// 执行写操作if(ioctl(fd,I2C_RDWR,msgset)0){perror(i2c write reg failed);free(write_buf);return-1;}free(write_buf);return0;}intmain(){intfd;unsignedcharread_buf[1]{0};unsignedcharwrite_val0xAA;// 1. 打开I2C设备文件fdopen(I2C_BUS,O_RDWR);if(fd0){perror(open i2c dev failed);return-1;}// 2. 示例1往0x01寄存器写入0xAAif(i2c_write_reg(fd,0x01,write_val,1)0){printf(Write reg 0x01: 0x%02x success\n,write_val);}// 3. 示例2读取0x01寄存器的值if(i2c_read_reg(fd,0x01,read_buf,1)0){printf(Read reg 0x01: 0x%02x\n,read_buf[0]);}// 4. 关闭文件close(fd);return0;}2、驱动相关2.1、字符设备1、register_chrdevLinux 内核注册字符设备的传统函数核心作用是关联主设备号、设备名和操作函数集fops2、class_createLinux 内核驱动中创建设备类的核心函数核心作用是在 /sys/class/ 生成类目录为设备文件创建提供基础my_dev_classclass_create(THIS_MODULE,my_dev_class);if(IS_ERR(my_dev_class)){printk(KERN_ERRFailed to create device class\n);unregister_chrdev_region(my_dev_num,1);// 回滚释放设备号returnPTR_ERR(my_dev_class);}3、device_create核心作用是在 Linux 驱动中动态生成 /dev 下的设备节点替代手动 mknodmy_devicedevice_create(my_dev_class,// 所属类NULL,// 无父设备my_dev_num,// 设备号NULL,// 无私有数据DEV_NAME// 设备名);4、register_chrdev_region静态分配字符设备号的核心函数作用是向内核申请指定范围的设备号。使用流程MKDEV()组合设备号 → register_chrdev_region()申请 → 驱动卸载时unregister_chrdev_region()释放#includelinux/fs.h// 必须包含的头文件intregister_chrdev_region(dev_tfirst,// 要申请的起始设备号由MKDEV生成unsignedintcount,// 要申请的设备号数量连续的次设备号个数constchar*name// 设备名内核中识别用如mychrdev);5、alloc_chrdev_region现代 Linux 驱动动态分配字符设备号的标准接口内核自动分配可用主设备号无冲突风险。现代驱动标准流程alloc_chrdev_region()分配号 → cdev_add()注册驱动 → device_create()创建设备节点#includelinux/fs.h// 必须包含的头文件intalloc_chrdev_region(dev_t*dev,// 输出参数内核分配的起始设备号会写入这个指针unsignedintbaseminor,// 起始次设备号通常传0unsignedintcount,// 要分配的连续设备号数量constchar*name// 设备名出现在/proc/devices中);6、cdev_init核心作用是初始化 cdev 结构体将 file_operations 操作集绑定到 cdev.ops 字段。#includelinux/cdev.h// 必须包含的头文件voidcdev_init(structcdev*cdev,// 要初始化的cdev结构体指针conststructfile_operations*fops// 要绑定的设备操作集open/read/write等);7、cdev_add核心作用是将初始化好的 cdev 结构体注册到内核建立设备号与驱动操作集的映射。使用前必须完成分配设备号 → cdev_init()初始化 cdev → 设置cdev.owner三者缺一不可。#includelinux/cdev.h// 必须包含的头文件intcdev_add(structcdev*cdev,// 已初始化的cdev结构体指针dev_tdev,// 要关联的起始设备号由alloc_chrdev_region分配unsignedintcount// 关联的设备号数量和分配设备号时的count一致);2.2 文件系统3、C语言3.1、面试/笔试常见八股1、const核心是声明只读常量避免意外修改提升代码稳定性。#includeiostreamusing namespace std;intmain(){// 1. 修饰普通变量值完全不可改constintnum10;// num 20; // 报错assignment of read-only variable ‘num’// 2. 指向常量的指针指针可改指向的值不可改constint*p1num;// *p1 30; // 报错不能修改指向的值inta20;p1a;// 合法指针可以指向其他地址// 3. 常量指针指针不可改指向的值可改int*constp2a;*p230;// 合法修改指向的值// p2 num; // 报错指针本身不能指向新地址// 4. 常量指针指向常量指针和值都不可改constint*constp3num;// *p3 40; // 报错// p3 a; // 报错return0;}2、static核心作用是限制作用域和延长生命周期。1静态局部变量函数内生命周期贯穿程序运行作用域仅限函数内且仅第一次调用时初始化。2静态全局变量 / 函数作用域仅限当前.c/.cpp文件避免全局命名冲突。3、栈栈是 C 程序运行时由操作系统自动分配和释放的内存区域遵循后进先出LIFO 的原则像叠盘子最后放的最先拿。栈的核心特点自动管理函数调用时局部变量、函数参数、返回地址等会被压入栈函数执行结束后这些内容会被自动弹出释放无需程序员干预。空间有限栈的大小固定通常几 MB由系统限制超出会触发 “栈溢出”Stack Overflow。访问速度快栈的内存地址连续CPU 访问效率高。生命周期短栈中变量的生命周期仅限于所在函数 / 代码块出了作用域就会被销毁。栈溢出的典型场景递归过深栈的代码示例局部变量4、堆堆是程序运行时向操作系统申请的、由程序员手动控制生命周期的内存区域没有固定的访问顺序。堆的核心特点手动管理需通过malloc()/calloc()/realloc()申请内存通过free()释放内存若忘记free()会导致内存泄漏占用的内存直到程序结束才释放。空间灵活堆的大小仅受系统物理内存限制可动态分配大内存比如大数组、结构体。访问速度慢堆的内存地址不连续操作系统需查找空闲内存块访问效率低于栈。生命周期可控堆中内存的生命周期从malloc()开始到free()结束或程序退出。堆的常见错误内存泄漏忘记free()比如删除free§后每次调用heap_example()都会占用 16 字节堆内存直到程序退出。野指针释放内存后未置空指针后续误操作p会导致程序崩溃。重复释放对同一个堆指针多次调用free()会触发内存错误。5、大端和小端大端高字节存于内存低地址低字节存于内存高地址符合人类的阅读习惯从高位到低位。小端低字节存于内存低地址高字节存于内存高地址与人类阅读习惯相反但处理器读写效率更高。6、内存泄露内存泄露是指程序向系统申请内存后未在使用完毕后释放导致该部分内存被永久占用、系统可用内存持续减少的现象。Linux 中内存泄露的影响分场景用户态进程的内存泄露仅影响进程自身进程退出后系统会自动回收其所有内存属于可恢复泄露内核态的内存泄露会直接占用系统核心内存进程 / 模块退出后也无法回收内存会被永久消耗最终导致系统内存耗尽、OOM内存溢出崩溃属于不可恢复泄露也是嵌入式 Linux 开发中需要重点关注的类型。Linux 中内存泄露按运行空间分为两类核心差异和影响如下用户态 malloc/calloc/realloc/free、new/delete 仅占用进程虚拟内存不影响系统全局 进程退出后系统自动回收用户态malloc/calloc/realloc/free、new/delete仅占用进程虚拟内存不影响系统全局进程退出后系统自动回收内核态kmalloc/kzalloc/kfree、vmalloc/vfree、alloc_pages/__free_pages、slab 分配器占用系统物理内存全局可用内存持续减少仅能通过重启系统恢复常见用户态内存泄露原因忘记释放内存最基础错误malloc/calloc/realloc 申请后未调用 free尤其在函数返回前遗漏。指针覆盖导致内存地址丢失申请内存后指针被重新赋值如指向常量、新的内存地址原内存地址无法被访问无法释放。char*pmalloc(1024);phello;// 原malloc的1024字节地址丢失泄露分配 / 释放接口不匹配realloc 扩容失败返回 NULL 时原指针未保留new[] 申请的数组用 delete 释放C。条件分支导致未释放内存申请后在 if/for/while 分支中提前返回 / 退出未执行释放逻辑。全局指针指向的内存未释放全局指针申请内存后进程运行期间未主动释放直到进程退出才被系统回收。常用检测工具1、Valgrind 是 Linux 下最常用的动态调试工具其 memcheck 子工具可精准检测内存泄露、野指针、内存越界、双重释放等问题原理是通过模拟 CPU 执行跟踪每个内存地址的分配 / 释放。2、系统监控工具快速定位泄露进程用于快速发现哪个进程存在内存泄露无法定位具体代码行适合初步排查top/htop实时查看进程的 VIRT虚拟内存、RES物理内存若 RES 持续增长且不回落大概率存在泄露pmap查看进程的内存映射详情分析具体内存段的占用pmap -x 进程PIDps查看进程内存占用ps -aux --sort-%mem按内存占用降序排列。用户态泄露排查流程用 top/ps 定位内存持续增长的进程 PID用 pmap 分析进程的内存段确认是堆内存malloc还是其他内存段泄露编译程序时加 -g 调试选项用 Valgrind --leak-checkfull 运行定位具体的泄露代码行修复代码后重新用 Valgrind 验证确保无 definitely lost 类型的泄露。7、volatile 关键字用途防止编译器去过度优化。每次读取数据都是从内存中读取而不是从寄存器或缓存中读取变量的值。可以用一个通俗的比喻理解普通变量像你把重要文件抄在笔记本寄存器 / 缓存上后续只看笔记本不会核对抽屉里的原件内存哪怕原件被别人改了volatile 变量强制你每次看 / 改都必须去抽屉拿原件确保始终和内存中的真实值一致。使用场景在嵌入式中操作并行设备的硬件寄存器和存储器映射的硬件寄存器。在 ISR 中变量需要加 volatile编译器可能认为主函数没有修改该变量就从内存或 cache 读副本。多任务环境下各任务间共享的变量。总结volatile 核心作用是禁用编译器优化强制变量的读写操作直接访问内存保证变量值的 “实时性”核心使用场景硬件寄存器访问、中断共享变量、多线程共享变量仅解决可见性关键注意volatile 不保证原子性不能替代锁 / 原子操作仅在变量被 “异步修改” 时使用。

相关新闻