找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

679

积分

0

好友

91

主题
发表于 18 小时前 | 查看: 0| 回复: 0

在嵌入式开发领域,扎实的底层技术功底是面试成功的基石。掌握C/C++、Linux系统以及硬件接口驱动开发这三大核心板块,能让你在竞争中脱颖而出。本文基于CVTE的实际面试考点,系统梳理了高频技术问题,无论你是应届生寻求突破,还是开发者准备跳槽,都能从中查漏补缺,高效备考。

当然,技术之路不仅需要学习,也需要交流。欢迎到 云栈社区 分享你的面试心得,探讨更多嵌入式开发的实战技巧。

一、C/C++面试题

1.1 函数指针和指针函数的区别

核心区别在于“本质是什么”:

  • 函数指针:本质是指针,指向一个函数的内存地址,语法为 返回值类型 (*指针名)(参数列表)。用途常为回调函数、实现多态(C语言)等,例如 int (*func_ptr)(int, int),表示指向参数为两个int、返回值为int的函数。
  • 指针函数:本质是函数,返回值为一个指针,语法为 返回值类型* 函数名(参数列表)。例如 int* func(int n),表示函数func接收int参数,返回int类型指针。

1.2 数组和指针的区别

  • 本质不同:数组是连续内存空间的集合,存储相同类型数据,大小固定;指针是存储内存地址的变量,大小为系统位数(32位4字节,64位8字节)。
  • 赋值与修改:数组名是常量地址,不能赋值(如 int arr[5]; arr = &arr[1];错误);指针变量可随意赋值不同地址。
  • 内存占用:数组占用的内存为“元素个数×元素大小”;指针仅占用存储地址的固定内存。
  • 运算规则:数组名+1偏移“一个元素大小”,指针+1同样偏移“指向类型的大小”,但数组名不可自增(arr++错误),指针可自增。
  • sizeof运算:sizeof(arr)得到数组总字节数;sizeof(ptr)得到指针本身大小。

1.3 引用的定义是什么

引用是C++特性,本质是变量的别名,语法为类型& 引用名 = 原变量。核心特性:

  • 必须在定义时初始化,且初始化后不能指向其他变量(与指针本质区别);
  • 引用本身不占用独立内存空间(编译器优化,底层可能通过指针实现,但语法层面无地址);
  • 对引用的操作等同于对原变量的操作。

1.4 引用的应用场景

  • 函数参数:避免值拷贝(尤其对于大型对象),同时可修改实参,例如 void swap(int& a, int& b)
  • 函数返回值:返回函数内非局部变量(避免返回指针的空悬问题),可实现链式调用,例如 string& append(string& s1, const string& s2)
  • 替代指针:简化语法,减少空指针风险,例如在类的成员函数、运算符重载中常用(如 operator+重载)。

1.5 引用和指针相比有什么不同

  • 初始化:引用必须初始化且不可改指向;指针可初始化也可后赋值,可改指向其他变量;
  • 空值:引用无空引用(语法不允许);指针有空指针(NULL/nullptr),存在空指针风险;
  • 内存占用:引用无独立内存(语法层面);指针占用固定内存(存储地址);
  • 运算:引用不支持算术运算(无意义);指针支持加减、偏移等算术运算;
  • 语法复杂度:引用语法简洁,无需解引用(*);指针需解引用、取地址(&)操作,语法稍复杂;
  • 底层实现:引用底层可能通过指针实现,但C++标准未规定,语法层面二者完全独立。

1.6 全局变量和局部变量的储存方式

  • 全局变量(包括静态全局变量):存储在数据段(.data/.bss);已初始化且非零的全局变量存在.data段,程序启动时加载,生命周期与程序一致;未初始化或初始化为零的全局变量存在.bss段,程序启动时由操作系统清零,生命周期与程序一致;静态全局变量(static修饰)同样存于数据段,仅作用于当前文件,不可跨文件访问。
  • 局部变量(包括函数参数):存储在栈(stack);函数调用时在栈上分配空间,函数执行结束后栈帧销毁,变量自动释放;栈空间由系统自动管理,大小固定(默认几MB),超出会导致栈溢出;静态局部变量(static修饰)特殊:存储在数据段,生命周期与程序一致,但作用域仅在当前函数内。

1.7 C中的内存分配方式有几种

共3种核心方式:

  • 栈上分配(自动分配/释放):局部变量、函数参数,由编译器自动管理,速度快,空间有限;
  • 数据段分配(静态分配):全局变量、静态变量(static),程序启动时分配,退出时释放,空间固定;
  • 堆上分配(动态分配):通过malloc/calloc/realloc申请,free释放,空间灵活,需手动管理,否则内存泄漏。

1.8 C中分配大内存应该使用哪种

应使用堆上动态分配(malloc/calloc/realloc)。原因:

  • 栈空间有限(默认几MB),大内存分配会导致栈溢出;
  • 堆空间远大于栈(可接近系统物理内存),适合存储大数组、大型结构体等;

注意:堆内存需手动free,避免内存泄漏;分配失败时返回NULL,需做错误判断。

1.9 C++中内存分配方式和C中内存分配方式有什么不同

C++兼容C的所有内存分配方式,同时新增面向对象的分配方式,核心差异:

  • 动态分配语法不同:C用malloc/free(函数调用,仅分配内存,不调用构造/析构);C++新增new/delete(运算符,分配内存时调用构造函数,释放时调用析构函数),对应数组有new[]/delete[]
  • 内存模型扩展:C++引入类对象,new会为对象分配内存并初始化(构造),delete会清理对象资源(析构),而malloc仅分配原始内存,不处理对象生命周期;
  • 新增内存分配机制:C++支持重载operator new/operator delete,自定义内存分配策略;还可使用placement new在已分配内存上构造对象;
  • 智能指针:C++11后提供unique_ptr/shared_ptr/weak_ptr,自动管理堆内存,减少内存泄漏风险,C语言无此特性。

1.10 C和C++结构体的不同

  • 成员权限:C的结构体成员默认公有权限,无访问控制;C++结构体可加public/private/protected权限修饰,支持封装;
  • 成员类型:C结构体仅能包含变量、函数指针;C++结构体可包含成员函数、构造函数、析构函数、静态成员、虚函数等;
  • 继承特性:C结构体不支持继承;C++结构体支持继承(默认公有继承),可继承其他结构体/类,实现多态;
  • 初始化方式:C结构体需通过赋值、memset或初始化列表;C++结构体可通过构造函数初始化;
  • 用途:C结构体仅用于封装数据;C++结构体可作为类使用(与类的唯一区别是默认继承权限和默认访问权限,类默认私有,结构体默认公有)。

1.11 什么是多态

多态是C++面向对象三大特性(封装、继承、多态)之一,指同一接口(函数名)在不同对象上表现出不同行为。核心目的是解耦,提高代码复用性和扩展性。多态分为两类:

  • 静态多态(编译期多态):通过函数重载、运算符重载实现,编译时确定调用哪个函数;
  • 动态多态(运行期多态):通过虚函数实现,运行时根据对象的实际类型确定调用哪个函数。

1.12 如何实现多态

动态多态(核心面试考点)实现条件:

  • 继承关系:子类继承父类;
  • 父类声明虚函数:在父类函数前加virtual关键字,例如 virtual void show()
  • 子类重写虚函数:子类函数与父类虚函数的函数名、参数列表、返回值完全一致(协变除外);
  • 父类指针/引用指向子类对象:通过父类指针/引用调用虚函数时,会触发动态绑定,调用子类重写的函数。

静态多态实现:通过函数重载(同一作用域内,函数名相同、参数列表不同)或运算符重载实现,编译时由编译器根据参数类型匹配函数。

1.13 构造函数能否是虚函数

不能。原因:

  • 虚函数的调用依赖于对象的虚函数表(vtable),而虚函数表是在对象构造时初始化的;
  • 构造函数的作用是初始化对象(包括虚函数表),若构造函数是虚函数,调用时需先访问虚函数表,但此时对象尚未构造完成,虚函数表不存在,逻辑矛盾;
  • 语法层面,C++不允许构造函数加virtual关键字。

1.14 静态成员能否是虚函数

不能。原因:

  • 静态成员函数属于类,不属于对象,无this指针;而虚函数的调用需要this指针访问对象的虚函数表,确定调用哪个函数;
  • 静态成员函数在编译期确定调用地址,属于静态绑定;虚函数是运行期动态绑定,二者机制冲突;
  • 语法层面,C++禁止静态成员函数加virtual关键字。

1.15 析构函数能否是虚函数

可以,且建议父类析构函数设为虚函数。原因:

  • 若父类析构函数非虚函数,当用父类指针指向子类对象,调用delete时,仅会调用父类析构函数,子类析构函数不会被调用,导致子类资源泄漏;
  • 若父类析构函数设为虚函数,delete时会通过动态绑定调用子类析构函数,再调用父类析构函数,确保资源完全释放;

注意:子类析构函数无需显式加virtual,会自动继承父类虚析构函数的特性。

1.16 静态成员在类中的作用,如果是修饰函数,类又有什么不同

(1)静态成员(变量/函数)的作用

  • 属于类,不属于单个对象,所有对象共享静态成员,节省内存;
  • 可在无对象实例化的情况下,通过“类名::静态成员名”直接访问,适合实现工具类功能、共享计数器等。

(2)静态成员函数修饰类的差异

  • this指针:无法访问类的非静态成员(变量/函数),仅能访问静态成员;
  • 调用方式:可通过类名直接调用(ClassName::func()),也可通过对象调用,但优先通过类名调用;
  • 绑定方式:静态成员函数是静态绑定(编译期确定调用地址),不能作为虚函数;
  • 生命周期:静态成员函数与类共存亡,不依赖对象的创建与销毁。

1.17 泛型编程

泛型编程是一种不依赖具体数据类型的编程范式,核心思想是“编写通用代码,适配多种数据类型”,实现代码复用,同时保证类型安全(区别于void*的无类型安全)。C++中泛型编程的核心实现手段是模板(类模板、函数模板),此外还有STL(标准模板库),是泛型编程的典型应用。优势:

  • 代码复用性高,无需为不同类型重复编写逻辑;
  • 类型安全,编译时检查类型匹配,避免运行时错误。

1.18 类模板

类模板是泛型编程的核心工具,用于创建可适配多种数据类型的通用类,语法为:template <typename T> class 类名 { … };T为类型参数,可自定义名称)。核心特性:

  • 类型参数化:用类型参数T替代具体类型,实例化时指定T的具体类型(如 MyClass<int>MyClass<float>);
  • 实例化时机:类模板仅在实例化时才会生成具体的类代码,未实例化时不生成;
  • 可多参数:支持多个类型参数,例如 template <typename T1, typename T2> class Pair { … };
  • 应用场景:STL中的容器(vectorlistmap)、算法等均基于类模板实现,例如 vector<int>int类型的向量容器。

1.19 函数间如何共享内存

常见方式有5种:

  • 全局变量/静态全局变量:作用域覆盖多个函数,所有函数可直接读写,缺点是耦合度高;
  • 静态局部变量:仅在函数内可见,但生命周期与程序一致,可在多次函数调用间共享数据;
  • 指针参数:通过函数参数传递堆内存指针,多个函数可操作同一块堆内存,需注意内存释放时机;
  • 引用参数:C++特性,通过引用传递变量,函数内对引用的操作等同于对原变量操作,实现数据共享;
  • 类的成员变量:若函数是类的成员函数,可通过this指针访问类的成员变量,实现成员函数间的数据共享。

1.20 静态绑定和动态绑定

  • 静态绑定(编译期绑定):定义为编译时确定函数的调用地址,无需运行时判断;实现方式包括普通函数、静态成员函数、函数重载、非虚成员函数;优势是效率高,无运行时开销。
  • 动态绑定(运行期绑定):定义为运行时根据对象的实际类型确定函数的调用地址;实现方式为虚函数(父类指针/引用指向子类对象时);优势是支持多态,提高代码扩展性,缺点是有轻微运行时开销(访问虚函数表)。

核心区别:绑定时机不同,静态绑定依赖编译期类型,动态绑定依赖运行期对象实际类型。

二、Linux面试题

2.1 Linux的内存分配方式

Linux内存分配分为用户空间和内核空间分配,核心方式:

(1)用户空间分配:

  • 栈分配:局部变量、函数参数,自动分配/释放,大小受限于ulimit -s配置;
  • 堆分配:通过malloc/calloc/realloc(C)、new/delete(C++),底层调用系统调用brk()/mmap()brk()调整进程数据段(.data)边界,适合小内存分配;mmap()映射匿名内存区域(无文件关联),适合大内存分配(通常超过128KB),避免内存碎片。

(2)内核空间分配:

  • 伙伴系统:管理物理内存页框(4KB/8KB等),分配连续页框,用于大内存块分配;
  • slab分配器:基于伙伴系统,将大页框拆分为小对象,用于内核对象(如进程控制块PCB)的频繁分配/释放,减少碎片;
  • vmalloc():分配非连续物理内存,映射到连续虚拟内存,适合内核态大内存分配,开销比伙伴系统高。

2.2 Linux的进程和线程的区别

核心区别:资源拥有与调度单位不同:

  • 进程:操作系统资源分配的基本单位(拥有独立的地址空间、PCB、文件描述符、信号处理等);进程间切换开销大(需切换地址空间、刷新TLB等);进程间通信复杂(需借助IPC机制)。
  • 线程:操作系统调度的基本单位(轻量级进程),共享所属进程的资源(地址空间、文件描述符、内存等);仅拥有独立的栈、寄存器、程序计数器(PC);线程间切换开销小(无需切换地址空间);线程间通信简单(可直接访问共享内存)。

关系:一个进程至少包含一个线程(主线程),多个线程共享进程资源,共同完成进程任务。

2.3 进程间通信的方式

Linux支持多种IPC方式,按常用度排序:

  • 管道(Pipe)/有名管道(FIFO):基于文件描述符,适用于父子进程/同主机进程间字节流通信;
  • 消息队列:内核维护的消息链表,进程可按类型发送/接收消息,无需同步,支持非阻塞通信;
  • 共享内存:多个进程映射同一块物理内存,直接读写,通信效率最高,需配合同步机制(信号量)避免竞争;
  • 信号量(Semaphore):用于进程/线程间的同步与互斥,不是传递数据,而是控制资源访问(如实现临界区保护);
  • 信号(Signal):用于处理异常或异步事件(如Ctrl+C发送SIGINT信号),传递简单通知,不能携带大量数据;
  • 套接字(Socket):支持跨主机进程通信(TCP/UDP),也可用于本地进程间通信(Unix域套接字)。

2.4 有名管道和无名管道

(1)无名管道(Pipe)

  • 创建方式:pipe(int fd[2])系统调用,返回两个文件描述符(fd[0]读,fd[1]写);
  • 特性:仅支持父子进程/兄弟进程间通信(基于fork继承文件描述符);无文件名,不占文件系统节点;半双工通信(需双向通信需创建两个管道);
  • 生命周期:随进程终止而释放(进程关闭所有文件描述符后,管道自动销毁)。

(2)有名管道(FIFO)

  • 创建方式:mkfifo(const char* pathname, mode_t mode)系统调用或mkfifo命令,在文件系统中创建一个FIFO文件节点;
  • 特性:支持任意同主机进程间通信(只要知道FIFO文件名,可打开读写);有文件名,占文件系统节点;半双工通信;
  • 生命周期:文件节点存在于文件系统,需手动删除(rm命令),管道内容随进程读写销毁。

共同点:均基于字节流,遵循“先进先出”(FIFO),无数据结构,需手动同步读写节奏。

2.5 进程有几种状态

Linux进程有5种核心状态(基于ps命令查看):

  • 运行态(R,Running/Runnable):进程正在CPU上运行,或在就绪队列中等待CPU调度;
  • 可中断睡眠态(S,Interruptible Sleep):进程等待某个事件(如IO完成、信号),可被信号唤醒(如kill信号);
  • 不可中断睡眠态(D,Uninterruptible Sleep):进程等待关键IO(如磁盘IO),不可被信号唤醒,仅能等待事件完成,避免数据不一致;
  • 僵尸态(Z,Zombie):进程已终止,但父进程未调用wait()/waitpid()回收其PCB资源,进程残留仅PCB;
  • 停止态(T,Stopped):进程被信号暂停(如SIGSTOP),可通过SIGCONT信号恢复运行。

2.6 如何杀死一个进程

通过kill/killall/pkill命令,核心是向进程发送信号,常用信号与命令:

(1)基本命令

  • kill -信号值 进程ID:指定进程ID发送信号,例如 kill -9 1234
  • killall 进程名:按进程名杀死所有同名进程,例如 killall nginx
  • pkill 进程名:按进程名匹配杀死进程,支持模糊匹配,例如 pkill -f test

(2)常用信号

  • 15SIGTERM):默认信号,温和终止,允许进程清理资源后退出;
  • 9SIGKILL):强制终止,进程无机会清理资源,用于无法正常退出的进程;
  • 2SIGINT):等同于Ctrl+C,中断进程运行。

2.7 如何使用命令查看所有进程的状态

常用命令:

  • ps aux:最常用,查看系统所有进程的详细状态;a:显示所有用户的进程;u:显示进程所有者、CPU/内存占用等详细信息;x:显示无控制终端的进程(如后台进程)。
  • ps -ef:显示所有进程的父子关系(PPID列显示父进程ID),适合追踪进程来源。
  • top:动态实时查看进程状态,按CPU/内存占用排序,支持交互操作(如k杀死进程、P按CPU排序)。
  • htoptop的增强版,界面更友好,支持鼠标操作(需手动安装)。

2.8 如何修改一个文件的权限

通过chmod命令,修改文件/目录的读(r)、写(w)、执行(x)权限,分两种方式:

(1)符号法(常用,易理解)
语法:chmod [用户类型][操作符][权限] 文件名

  • 用户类型:u(所有者)、g(所属组)、o(其他用户)、a(所有用户);
  • 操作符:+(添加权限)、-(移除权限)、=(设置权限);
  • 权限:r(读,4)、w(写,2)、x(执行,1);
  • 示例:chmod u+x,g=rw,o-r test.txt(给所有者加执行权,所属组设为读写,其他用户移除读权)。

(2)数字法(简洁)
语法:chmod 权限数字 文件名

  • 权限数字由三位组成(分别对应ugo),每位为r/w/x的权限值之和;
  • 示例:chmod 755 test.sh(所有者rwx,所属组rx,其他用户rx,常用脚本权限);chmod 644 test.txt(所有者rw,其他用户r,常用文件权限)。

2.9 如何修改一个文件的属性

文件属性包括所有者、所属组、创建/修改时间、隐藏属性等,对应命令:

  • 修改所有者和所属组:chown命令;语法:chown 所有者:所属组 文件名;示例:chown user1:group1 test.txt(将文件所有者改为user1,所属组改为group1);chown -R user1:group1 dir(递归修改目录及内容的权限)。
  • 修改文件时间戳:touch命令;语法:touch -d “2024-01-01 12:00” test.txt(修改文件的访问时间和修改时间);若文件不存在,touch会创建空文件。
  • 修改隐藏属性(ext文件系统):chattr命令;示例:chattr +i test.txt(添加不可修改属性,禁止删除/修改);chattr -i test.txt(取消属性);查看隐藏属性:lsattr test.txt

2.10 修改权限的命令中,三种权限分别代表什么

三种权限对应读(r)、写(w)、执行(x),分别作用于文件和目录,含义不同:

(1)对文件的权限

  • r(读权限,值4):允许读取文件内容(如catmore命令);
  • w(写权限,值2):允许修改文件内容(如vim编辑、echo写入),但不允许删除文件(删除权限由目录决定);
  • x(执行权限,值1):允许将文件作为可执行程序运行(如脚本、二进制文件)。

(2)对目录的权限

  • r(读权限):允许查看目录内的文件列表(如ls命令);
  • w(写权限):允许在目录内创建、删除、重命名文件/目录(如touchrmmv命令);
  • x(执行权限):允许进入目录(如cd命令),无x权限时,即使有r权限也无法访问目录内文件。

2.11 VIM是什么

VIM是Linux/Unix系统下的高效文本编辑器,是VI编辑器的增强版,支持语法高亮、代码补全、多窗口编辑、宏录制等功能,是开发、运维必备工具。核心特性:

  • 模式化编辑:分为命令模式、编辑模式、末行模式,不同模式下操作不同;
  • 轻量级:无需图形界面,纯命令行操作,启动速度快;
  • 可定制化:通过.vimrc配置文件自定义快捷键、语法配色、插件等;
  • 跨平台:支持Linux、Windows、macOS等系统。

2.12 VIM中按什么进入编辑模式

VIM默认启动进入命令模式,需按对应按键切换到编辑模式,常用按键:

  • i:在当前光标位置插入内容(最常用);
  • I:在当前行的行首插入内容;
  • a:在当前光标位置后一位追加内容;
  • A:在当前行的行尾追加内容;
  • o:在当前行下方新建一行并插入内容;
  • O:在当前行上方新建一行并插入内容;
  • s:删除当前光标位置的字符并进入插入模式;
  • S:删除当前行内容并进入插入模式。

退出编辑模式需按Esc键,返回命令模式。

2.13 VIM中如何查找

在命令模式下操作,常用查找命令:

  • 正向查找:输入/关键词,按回车开始查找,例如/printf;按n键查找下一个匹配项,N键查找上一个匹配项。
  • 反向查找:输入?关键词,按回车开始查找,例如?printf;按n键查找上一个匹配项,N键查找下一个匹配项。
  • 查找当前光标所在单词:按*键,正向查找当前单词(全词匹配);按#键,反向查找当前单词(全词匹配)。
  • 取消查找高亮:在命令模式下输入:noh(no highlight)。

2.14 VIM中如何定位

命令模式下的定位操作,快速跳转至目标位置:

(1)行定位

  • 输入:行号,按回车跳转至指定行,例如:100跳转至第100行;
  • gg:跳转至文件首行;G:跳转至文件末行;
  • nG:跳转至第n行(n为数字),例如50G跳转至第50行。

(2)列定位

  • 0(数字0):跳转至当前行首;$:跳转至当前行尾;
  • ^:跳转至当前行第一个非空格字符。

(3)屏幕定位

  • H:跳转至当前屏幕首行;M:跳转至当前屏幕中间行;L:跳转至当前屏幕末行;
  • Ctrl+f:向下翻一屏;Ctrl+b:向上翻一屏。

2.15 如何检查内存状态

常用命令:

  • free:查看系统内存使用概况;选项-h:以人类可读格式显示(KB/MB/GB),例如 free -h;输出包括总内存(total)、已用(used)、空闲(free)、缓冲(buffers)、缓存(cache)。
  • top/htop:动态查看内存占用,按M键按内存占用排序,可查看单个进程的内存使用情况。
  • vmstat:查看虚拟内存状态,包括内存交换(swap)、IO等,例如 vmstat 2 5(每2秒刷新一次,共5次)。
  • cat /proc/meminfo:查看内存详细信息(内核态数据),包括内存大小、缓存、交换分区等,适合深入分析。
  • sar -r:查看内存使用历史统计,例如 sar -r 1 10(每1秒统计一次,共10次),适合排查内存泄漏。

2.16 如何进行内核同步

内核同步用于解决多CPU、多进程/线程对内核资源(如全局变量、硬件设备)的并发访问冲突,常用机制:

  • 自旋锁(Spinlock):适用于短时间持有锁的场景,线程获取锁失败时不会阻塞,而是循环等待(自旋),避免上下文切换开销;注意:持有自旋锁时不能睡眠(如调用schedule()),否则会导致死锁。
  • 互斥锁(Mutex):适用于长时间持有锁的场景,线程获取锁失败时会阻塞,进入等待队列,释放CPU资源;支持优先级继承,避免优先级反转问题。
  • 信号量(Semaphore):分为计数信号量和二值信号量(类似互斥锁),用于控制多个线程对资源的访问数量;线程获取信号量失败时阻塞,释放时唤醒等待线程。
  • 读写锁(Read-Write Lock):区分读操作和写操作,多个线程可同时持有读锁,仅允许一个线程持有写锁;提高读多写少场景的并发效率。
  • 原子操作(Atomic Operations):基于CPU指令实现(如CAS指令),无需锁,适用于简单变量的增减(如计数器),避免竞争条件。

三、嵌入式面试题

3.1 IIC通信方式

IIC(Inter-Integrated Circuit,集成电路间总线)是一种串行半双工同步通信总线,由飞利浦公司开发,广泛用于嵌入式设备间近距离通信(如传感器、EEPROM、LCD驱动)。核心特性:

  • 总线结构:仅两根线(SDA:串行数据线,SCL:串行时钟线),支持多主多从模式;
  • 寻址方式:7位地址(主流)或10位地址,从设备通过地址区分,无片选线;
  • 同步方式:由主设备产生SCL时钟,控制通信节奏,从设备同步接收/发送数据;
  • 电平标准:通常为TTL电平(3.3V/5V),SDA和SCL需外接上拉电阻(4.7KΩ/10KΩ),空闲时为高电平;
  • 优势:布线简单、占用IO少、支持多从设备;缺点:通信速率较低(标准模式100Kbps,快速模式400Kbps,高速模式3.4Mbps)。

3.2 中断概念

中断是嵌入式系统中处理异步事件的机制:当外部设备(如按键、传感器、定时器)发生特定事件时,主动向CPU发送请求信号,CPU暂停当前正在执行的程序,转而去执行对应事件的处理程序(中断服务函数ISR),处理完成后返回原程序继续执行。核心作用:提高CPU利用率,避免CPU轮询等待外部事件,实现实时响应(如紧急按键、故障报警)。分类:

  • 硬件中断:由外部设备触发(如GPIO电平变化、UART接收数据);
  • 软件中断:由程序主动触发(如系统调用、异常处理)。

3.3 中断过程

完整中断过程分为5步,即“中断请求-响应-处理-返回”:

  • 中断请求(IRQ):外部设备发生事件,通过中断线向CPU发送请求信号,CPU检测到有效请求后,准备响应;
  • 中断响应:CPU暂停当前指令执行,保存上下文(PC指针、寄存器值、标志位)到栈中,关闭同级/低级中断(避免嵌套冲突),根据中断向量表找到对应中断服务函数(ISR)的地址;
  • 中断处理:CPU跳转到ISR地址,执行中断服务函数(如读取传感器数据、处理按键事件);
  • 中断返回:ISR执行完成后,恢复之前保存的上下文(寄存器、PC指针),开启中断使能;
  • 恢复执行:CPU返回原程序被中断的位置,继续执行后续指令。

3.4 中断能否传参

不能直接传参。原因:

  • 中断服务函数(ISR)的调用由硬件触发,而非程序主动调用,无法像普通函数那样传递参数;
  • ISR执行时,CPU上下文(寄存器、栈)处于特殊状态,传递参数可能破坏上下文,导致系统不稳定。

替代方案:

  • 使用全局变量/静态变量:ISR与主程序通过全局变量共享数据(如ISR读取传感器数据存入全局变量,主程序读取该变量);
  • 使用硬件寄存器:ISR通过读取外设寄存器获取事件相关信息(如UART接收缓冲区、GPIO状态寄存器);
  • 中断控制器参数:部分MCU的中断控制器支持配置中断触发条件(如上升沿/下降沿),间接传递事件类型信息。

3.5 IIC数据帧格式

IIC数据帧以“起始条件”开始,“停止条件”结束,标准帧格式(7位地址)如下:

  • 起始条件(S):主设备拉低SDA线,且在SCL高电平时保持低电平,标志通信开始;
  • 从设备地址+读写位:8位数据,前7位为从设备地址,第8位为读写位(0:写,1:读);
  • 应答位(ACK):从设备接收地址后,拉低SDA线(在SCL高电平时),向主设备反馈接收成功;若未应答(NACK,SDA保持高电平),主设备终止通信;
  • 数据字节:主设备向从设备写数据(或从设备向主设备读数据),每次传输8位数据,字节间需加应答位(ACK/NACK);
  • 停止条件(P):主设备拉低SDA线,在SCL高电平时释放SDA线(拉回高电平),标志通信结束。

补充:读数据时,主设备在接收最后一个字节后发送NACK,告知从设备停止发送数据,随后发送停止条件。

3.6 UART通信波特率

波特率(Baud Rate)是UART通信中数据传输的速率单位,表示每秒传输的符号数(对于UART,每个符号对应1位数据,因此波特率=比特率),单位为bps(Bits Per Second)。核心作用:主从设备必须约定相同的波特率,否则数据传输会出错(如波特率不匹配导致乱码)。

  • 常用波特率:9600、19200、38400、57600、115200(最常用)、230400、460800等。
  • 波特率误差:MCU的UART波特率由时钟频率和分频系数计算得出,误差需控制在±3%以内(部分设备允许±5%),否则会导致数据接收错误。例如,115200波特率,时钟频率为72MHz时,分频系数需精准配置。

3.7 UART有几根线

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)通信分为三线制(最常用)和二线制,核心线为:

  • 三线制(全双工):TXD:发送数据线,设备向外部发送数据;RXD:接收数据线,设备从外部接收数据;GND:地线,统一电平参考,避免干扰(必须连接,否则无法正常通信);适用于双向通信场景(如MCU与模块、PC的通信)。
  • 二线制(半双工):仅用TXD和GND,通过软件控制切换收发方向(同一时间只能发或收);适用于资源紧张、单向通信为主的场景(如简单传感器数据上传)。

补充:部分UART扩展了RTS/CTS流控制线(硬件流控),此时为五线制,但嵌入式场景中极少使用。

3.8 UART同步还是异步

UART是异步通信,核心特点是无需时钟线同步,主从设备通过约定相同的波特率、数据位、校验位、停止位实现数据同步。与同步通信(如IIC、SPI)的区别:

  • 异步通信:无时钟线,通过约定波特率同步,每个数据帧包含起始位、数据位、校验位、停止位,容错性较强(允许轻微波特率误差);
  • 同步通信:有时钟线(如IIC的SCL、SPI的SCK),由主设备提供时钟,数据传输速率更高,同步精度更强,但布线多一根时钟线。

3.9 UART数据帧格式

UART数据帧为异步帧,格式可配置,标准帧结构(从低到高传输)如下:

  • 起始位(1位):低电平,标志数据帧开始(空闲时TXD为高电平,拉低表示起始);
  • 数据位(5-9位,常用8位):传输的有效数据,可选择LSB(最低位)先传或MSB(最高位)先传(默认LSB);
  • 校验位(0-1位,可选):用于校验数据传输是否出错;无校验(N):无校验位;奇校验(O):数据位+校验位的总1的个数为奇数;偶校验(E):数据位+校验位的总1的个数为偶数;
  • 停止位(1-2位):高电平,标志数据帧结束;可配置为1位、1.5位或2位,1位停止位为最常用配置,停止位时长可抵消轻微的波特率误差,保证数据同步准确。

以上就是对嵌入式开发中C/C++、Linux及硬件相关核心知识点的梳理。理解这些问题背后的原理,而不仅仅是背诵答案,才能让你在面试中游刃有余。在实际项目中灵活运用这些知识,将是你技术实力的最好证明。如果你在求职路上遇到了更多问题,不妨来 云栈社区 的面试求职板块看看,或许能找到更多启发。




上一篇:Linux与MySQL缓存机制:如何解决预读失效与缓存污染
下一篇:详解云原生网络三大件:VPC、安全组与弹性网卡在阿里云的实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-25 20:31 , Processed in 0.249448 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表