首页/新闻资讯/正文详情

嵌入式软件八股文(1)

发布时间:2026/11/4 7:19:34 来源:云帆数科 栏目:资讯中心
嵌入式软件八股文(1)
前言问题来源于嵌入式面经111题答案汇总(含技术答疑)_3大嵌入式项目源码分享—嵌入式Linux项目_牛客网https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864https://www.nowcoder.com/discuss/767788764180516864第一章 进程线程1.1 进程线程的基本概念1.1.1 什么是进程线程彼此有什么区别⭐⭐⭐⭐⭐进程与线程的概念在操作系统中进程Process是计算机中已运行程序的实体是操作系统进行“资源分配”的基本单位。每个进程都拥有独立的内存空间包括代码段、数据段、堆栈等和系统资源如文件描述符。线程Thread则是操作系统能够进行“运算调度”的最小单位它被包含在进程之中是进程中的实际运作单位。一个进程可以并发多个线程这些线程共享该进程的所有资源。从图里就能很清楚地看到外面那个蓝色的大框就是“进程工厂”它独占了代码、数据和文件资源而里面三个绿色的框就是“线程工人”他们共享外面的大环境但每个人手里都有自己专属的“寄存器和栈工具箱”来干活。判断结论进程与线程的核心区别在于“资源拥有权”与“调度开销”。进程是资源的“所有者”互相之间极其隔离安全性高但切换开销巨大线程是执行的“打工人”轻装上阵共享老板进程的资源切换极快且通信方便但“一损俱损”一个线程崩溃可能导致整个进程崩溃。参考回答“关于进程和线程我通常喜欢用‘工厂和工人’的模型来理解。进程就像是一座工厂操作系统分配给这个工厂独立的地皮也就是内存空间、各种设备和原材料系统资源。工厂本身是不干活的它只是提供一个运行环境。线程就是这座工厂里的流水线工人他们才是真正去干活、被 CPU 调度执行的最小单位。一个工厂里至少有一个工人主线程当然也可以招很多工人多线程一起并行干活。它们俩的区别在实际开发中主要体现在四个方面第一是资源分配。进程之间是完全隔离的每座工厂都有自己的大门互不干扰但同一个进程里的线程是共享这座工厂里的所有车间和原材料的比如代码段、数据段、堆区只有每个人自己干活用的工具箱也就是栈空间和寄存器是私有的。第二是开销大小。如果要创建一个进程或者在两个进程间切换操作系统得重新分配一大堆资源、切换页表这个开销非常大建新工厂很费劲但线程非常轻量级因为它们共享绝大部分资源所以线程的创建和上下文切换速度要快得多。第三是通信方式。进程之间因为互相隔离想要通信就得靠‘跨厂物流’也就是操作系统提供的 IPC 机制比如管道、消息队列或者共享内存比较麻烦而线程之间因为都在同一个内存空间里直接读写全局变量就能通信非常方便但也因此必须得自己加锁Mutex来处理并发同步的问题。第四是健壮性。如果一个进程崩了因为操作系统有保护机制通常不会波及到其他进程但如果在多线程程序里只要有一个线程因为野指针或者段错误崩了整个工厂都会直接停工也就是整个进程都会跟着崩溃。”1.1.2多进程、多线程的优缺点⭐⭐⭐⭐多进程与多线程优缺点的概念在并发编程模型中多进程Multi-processing依赖操作系统创建多个独立的内存空间侧重于系统资源的重兵隔离多线程Multi-threading则在同一个进程空间内开辟多条执行路径侧重于轻量级的调度和资源共享。判断结论多进程拼的是“绝对的稳定和安全”而多线程拼的是“极致的轻量和高效”。两者的优缺点是互为镜像的多进程的缺点开销大、通信难正是多线程的优点而多线程的缺点极度脆弱、并发同步难也正是多进程的优点。参考回答多进程最大的优点就是极其稳定。因为各个进程的内存是物理隔离的就像几个独立运作的工厂一个工厂发生了火灾崩掉了绝对不会‘火烧连营’影响其他工厂。但它的缺点也很明显就是太笨重了。不仅操作系统创建和切换进程的开销巨大而且它们之间想要通个信非常麻烦得靠管道或者共享内存这些专门的 IPC 机制。多线程则正好反过来。它的优点是极其轻量、通信极快。因为大家都在同一个工厂里干活共享代码和数据所以创建和切换的速度飞快传数据也只需要直接读写同一个全局变量就行。但它最大的缺点是极其脆弱。只要有一个工人操作失误比如搞出了个段错误或者野指针整个进程会立刻被操作系统整体封杀所有线程都得跟着陪葬。而且共享资源如果不加锁很容易引发数据被乱写乱盖的并发问题。所以在工程应用里如果是那种对稳定性要求极高、或者计算极其密集的任务比如 Chrome 浏览器的多标签页一般用多进程如果是遇到高并发、频繁被外部事件唤醒的 I/O 密集型任务比如 Web 服务器处理几万个用户的 HTTP 请求为了节省系统资源我们一般会毫不犹豫地选择多线程。”1.1.3什么时候用进程什么时候用线程⭐⭐⭐进程与线程选型的概念判断结论在实际工程架构中选择多进程还是多线程核心取决于两个维度的考量“任务的物理瓶颈在哪里CPU密集型 vs I/O密集型”以及“系统对容错率的要求有多高”。参考回答“我在实际做架构选型的时候通常会问自己两个问题这活儿是费脑子耗CPU还是费时间耗I/O这个模块崩了能不能连累整个系统第一种情况果断用多进程。如果任务是CPU密集型的比如做视频渲染、大规模的科学计算、或者图像处理。这种活儿需要一直霸占着 CPU 疯狂算那我会选择多进程尤其是为了吃满多核 CPU 的算力像 Python 因为有 GIL 锁多线程其实是伪并行的干这种活必须得多进程。另外如果这个模块对稳定性要求极高绝对不能因为一行代码越界就把整个系统带崩比如 Nginx 的 Master-Worker 架构或者 Chrome 浏览器的多标签页那哪怕开销大一点我也一定会用多进程把它们物理隔离起来。第二种情况果断用多线程。如果任务是I/O密集型的比如绝大多数的 Web 服务器、爬虫、或者频繁读写文件的程序。这种活儿绝大部分时间都在‘死等’网络响应或者硬盘读写CPU 其实闲得发慌。这时候我就会开多线程让 CPU 在等 I/O 的间隙快速切到别的线程去干活把时间管理做到极致。此外如果并发的任务之间需要频繁地共享大量内存数据用多线程可以直接读写同一块内存空间效率极高完全不用像多进程那样费劲地搞共享内存或者管道通信只要注意加好锁就行了。”1.1.4多进程、多线程同步通讯的方法⭐⭐⭐⭐⭐多进程与多线程同步及通信的概念因为进程和线程在操作系统底层的物理结构完全不同所以它们之间互相打交道的核心诉求也截然不同。判断结论多线程本来就共享同一个内存空间所以它的重点在于“同步”防打架多进程之间是物理隔离的所以它的重点在于“通信”送快递。进程间通信IPC的原理与必要性. Source: JavaGuide / 进程间通信IPC详解管道、消息队列、共享内存、Socket 与Binder ...多线程利用信号量进行同步的流程. Source: CSDN博客 / 多线程的同步与互斥互斥锁、条件变量、读写锁、自旋锁、信号量 ...参考回答 “聊到通信和同步我一般会把多线程和多进程分开来看。第一块多线程的同步机制重点是防打架。因为同一个进程里的线程都是‘一家人’天生就共享全局变量和堆区内存所以它们之间传数据根本不需要操作系统插手。但正因为这样如果两个人同时改一个变量就容易出 Bug所以多线程的核心是‘同步锁’互斥锁Mutex最简单粗暴。就像上公共厕所进去就反锁别人只能在外面死等。这保证了同一时刻只有一个线程能操作临界资源。条件变量Condition Variable一般配合互斥锁用。这就像是等外卖没必要一直开门看死循环占用 CPU而是直接让线程休眠等条件满足了外卖到了别的线程发个信号把它唤醒。信号量Semaphore像是个多坑位的停车场。可以设定坑位数量进去一辆车坑位减一出来加一。坑位为 0 时后面的线程就得排队阻塞。读写锁Read-Write Lock针对‘读多写少’的场景优化。只要没人写大家可以同时一起读但只要有人在写其他所有人无论是读还是写都得老老实实等着。第二块多进程的通信方式IPC重点是送快递。因为多进程之间有高墙内存隔离它们想说话必须得靠操作系统内核这个‘中间人’来传递管道Pipe最古老的方式数据像水流一样只能单向传输。常见的有匿名管道只能给父子进程用和命名管道没有血缘关系的进程也能用。消息队列Message Queue就像操作系统的快递驿站。A进程把数据打包成消息扔进队列B进程有空了自己按类型去取不用死等。共享内存Shared Memory这是最快的 IPC 方式。操作系统直接在物理内存里划出一块‘公共白板’映射到两个进程的地址空间里大家都能直接读写省去了数据来回拷贝的巨大开销。不过它往往必须配合信号量一起用防止大家同时在白板上乱画。套接字Socket这个最全能它不仅能让同一台电脑里的不同进程通信还能让网络上不同机器里的进程跨网通信我们平时写的各种网络服务端/客户端全靠它。”1.1.5进程的空间模型⭐⭐⭐⭐进程空间模型虚拟内存布局的概念在现代操作系统如 Linux中每个进程都会被分配一个独立的、连续的“虚拟地址空间”在 32 位系统中通常是 4GB。为了让进程有条不紊地运行操作系统把这个空间像切蛋糕一样划分成了几个职责分明、严格隔离的区域这就是进程的空间模型。进程的经典虚拟内存分层结构判断结论进程的空间模型体现了操作系统对内存管理的“绝对掌控”与“权限隔离”。自底向上从低地址到高地址它被严格划分为代码段、数据段、BSS 段、堆区、内存映射区、栈区最后是雷池一步不可逾越的内核空间。其中堆和栈是动态相对生长的这极大提高了内存利用率。参考回答“聊到进程的空间模型我脑子里一般会浮现出一个从下往上盖的大楼以 32 位 Linux 为例总共 4GB 大小。这栋楼分工非常明确大概可以切成这几个核心的楼层首先是最底层低地址部分主要用来存‘静态的死数据’代码段Text Segment这是给 CPU 看的机器指令比如我们写的if-else、函数。为了防止程序自己把代码给改崩了这个区域通常是只读的。数据段Data Segment专门用来存那些已经初始化了的全局变量和静态变量。BSS 段BSS Segment专门用来存没有初始化的全局变量和静态变量。操作系统为了省事会在程序运行前把这块区域统一清零。接着往上走就是程序的‘动态活动区’了这也是面试最爱考的地方 4.堆区Heap我们平时用malloc或者new动态申请的内存都在这儿。它是由程序员自己管理的由于不断地申请和释放堆区是由低地址向高地址往上生长的。 5.内存映射区Memory Mapping Segment夹在堆和栈中间。像我们用到的一些动态链接库比如.so文件或者用mmap映射的大文件都会塞在这个区域。 6.栈区Stack主要存函数的局部变量、函数参数、返回地址等等。栈区是操作系统自动管理的非常快。它的特点是由高地址向低地址往下生长。这种‘堆往上长、栈往下长’的相向而行设计能最大化地利用中间的空闲内存。最后在这个 3GB 的用户空间之上是最顶层的 1GB——内核空间Kernel Space。这是操作系统的‘私人领地’存着操作系统的代码和数据结构用户态的程序绝对不允许直接访问这里想进去必须得老老实实通过‘系统调用System Call。”1.1.6进程线程的状态转换图 什么时候阻塞什么时候就绪⭐⭐⭐进程状态转换的概念在操作系统中进程或线程的生命周期不是一通到底的而是要在多个状态之间来回横跳。最经典的就是“五态模型”新建、就绪、运行、阻塞、终止。面试中最常被追问的核心就是运行Running、就绪Ready和阻塞Blocked这三个状态之间的转换时机。带挂起功能的复杂进程状态转换图. Source: GitHub / linux_kernel_wiki/文章/Linux操作系统进程的状态和转换(五态模型).md ...“就绪Ready”意味着万事俱备只欠 CPU我在排队轮到我我就能跑而“阻塞Blocked/Waiting”意味着即使现在把 CPU 白送给你你也跑不动因为你在等某个外部事件发生我在等快递拿到快递我才会去排队。参考回答“聊状态转换我喜欢用‘去银行办业务’来举例子。柜台职员就是 CPU。我们重点说什么时候阻塞Blocked和什么时候就绪Ready。第一什么时候会进入‘阻塞Blocked’状态当你正在柜台办业务运行态时突然发现少带了身份证你必须得打电话让家里人送过来等待 I/O 读取或者你想办的业务需要经理签字但经理现在在忙等待互斥锁/信号量。这时候柜员不可能一直干等着你他会把你请到旁边的等候区。这就是阻塞。总结起来进入阻塞态通常是因为程序主动发起 I/O 请求读写磁盘、网络通讯、申请的锁被别人占了、或者代码里直接调了sleep()函数。这时候即使 CPU 空闲你也跑不了。第二什么时候会进入‘就绪Ready’状态进入就绪态也就是拿到号码牌坐在大厅里排队有三种经典情况从新建态过来刚刚进银行大门拿到了号码牌万事俱备就在大厅等叫号。从阻塞态过来刚才不是在等家里人送身份证吗现在身份证送到了I/O 操作完成或者拿到了锁或者sleep时间到了。此时你不能直接冲到柜台接着办你得重新拿个号回到大厅的就绪队列里接着排队。从运行态被‘踢’过来你正在柜台办业务但是操作系统规定每个人每次最多只能办 5 分钟时间片用完。时间一到无论办没办完柜员都会强行让你停下让你重新去后面排队。这也是由‘运行’退回到‘就绪’。”1.1.7父进程、子进程的关系以及区别⭐⭐⭐⭐父子进程的关系与区别的概念在 Linux/Unix 操作系统中所有的进程都像是一棵庞大的家族树。除了最顶层的祖先进程通常是init或systemd每一个新进程都必须由一个已经存在的进程“生”出来。这个负责生孩子的原进程就是父进程而通过fork()系统调用新创建出来的进程就是子进程。fork() 创建子进程的执行流分叉. Source: Made with Google AI父子进程生命周期终结时的特殊状态. Source: Made with Google AI子进程在刚出生的那一刻几乎是父进程的完美克隆体克隆了代码、数据、打开的文件描述符等。但从分叉的那一秒起它们就变成了两个拥有独立内存空间、互不干涉的独立实体。它们最核心的区别在于身份标识PID不同、fork()的返回值不同以及由于生命周期结束时间不同而可能引发的“孤儿Orphan”与“僵尸Zombie”状态。参考回答“聊到父子进程我们可以把它当成一种‘克隆机制’。在系统里你要想跑一个新程序通常得先让当前进程父进程调一下fork()硬生生把自己克隆一份出来这个新出来的就是子进程。它们俩的关系和区别面试里我一般从三个维度来答第一是怎么区分谁是谁的身份与返回值虽然他俩长得一样但身份证号绝对不同。子进程有自己全新的 PID同时它的 PPID父进程 ID会指向生它的那个进程。最有意思的是fork()这个函数的返回值它会‘调用一次返回两次’。在父进程的代码里它返回的是新生孩子的 PID而在子进程的代码里它返回的是 0。我们平时写代码就是靠这个返回值来写if-else让父子进程去干不同的活儿。第二家产是怎么分的写时复制 Copy-on-Write刚克隆完的时候子进程和父进程是共享同一块物理内存的这时候极其高效。但是只要有任何一方想去修改里面的数据操作系统就会立刻跳出来偷偷把那块内存复制一份给修改方。这叫‘写时复制COW’。所以从逻辑上讲它们的内存空间是完全独立、互不影响的。第三谁管谁送终生命周期绑定这就牵扯到著名的僵尸进程和孤儿进程了。正常情况下子进程干完活退出了它会留下一个‘尸体’退出状态码等着父进程调用wait()来替它收尸。如果子进程先死了但父进程太忙一直没去收尸那这个子进程就会变成僵尸进程Zombie白白占着系统的进程表条目。如果父进程先死了子进程还在跑那这个子进程就会变成无依无靠的孤儿进程Orphan。不过不用担心操作系统里的‘居委会大妈’init 进程或 systemd会自动收养它等它跑完替它收尸。”1.1.8什么是进程上下文、中断上下文⭐⭐进程上下文与中断上下文的概念在操作系统中“上下文Context”本质上就是 CPU 运行一段代码时所依赖的“环境快照”包括寄存器里的值、程序计数器、堆栈指针以及内存映射信息等。进程上下文是 CPU 正在执行某个具体进程任务时的环境状态中断上下文则是 CPU 响应外部硬件的突发请求强行打断当前任务去执行“中断处理程序ISR”时的环境状态。判断结论在面试中这两者最核心、最致命的区别只有一个——“能不能休眠阻塞”。处于进程上下文的代码可以休眠因为系统知道它是谁资源就绪后能准确唤醒它处于中断上下文的代码绝对不能休眠它是处理紧急事件的“无主状态”一旦休眠操作系统将无法进行调度唤醒直接导致内核崩溃Kernel Panic。参考回答“关于这俩概念我平时喜欢用‘上班打工’的场景来对比理解。进程上下文就像是你坐在工位上做自己的专属项目。无论是你自己敲代码运行在用户态还是你遇到权限不够、跑去找老板申请资源通过系统调用陷入内核态这本质上都是在为你这个具体的项目服务这就叫处于进程上下文。在这个状态下如果你申请的资源暂时没到位比如等硬盘读写你可以选择‘去睡一觉阻塞挂起’因为系统认识你知道这个项目是你的等资源到了系统自然会叫醒你接着干。中断上下文就像是办公室突然响起了火警或者接到了极其紧急的客服电话硬件触发了中断。这时候不管你手头正在写什么代码CPU 都必须立刻强行打断你放下一切去处理这个突发事件。处理这种紧急事件的环境就是中断上下文。在面试里这两者在开发规则上有一条不可逾越的红线中断上下文里绝对不能发生休眠、阻塞也不能去获取互斥锁。为什么呢因为中断是硬件随机触发的它不属于任何一个具体的进程它只是碰巧打断了当前正在跑的那个‘倒霉蛋’。如果在中断处理一半的时候你让它去‘睡一觉’或者死等资源操作系统的调度器会彻底懵圈——因为它根本不知道该把这个休眠行为记在谁的头上等资源好了也就不知道该去唤醒谁。所以在 Linux 内核开发里中断上下文的原则永远只有四个字快进快出绝不逗留。”1.1.9一个进程可以创建多少线程和什么有关⭐⭐进程最大线程数的概念一个进程绝对不可能无限制地创建线程。在操作系统中能创建多少个线程并没有一个固定的“标准答案”它是一个受制于虚拟内存寻址空间、系统内核参数限制以及物理硬件资源共同博弈的结果。判断结论在 32 位系统中创建线程的“天花板”通常是进程的虚拟内存大小内存被撑爆而在 64 位系统中虚拟内存大到几乎用不完此时的“天花板”则变成了操作系统的内核参数限制和物理内存RAM的大小。参考回答“面试官您好关于一个进程能创建多少线程我通常会分 32 位和 64 位两种情况来算这笔账第一重限制是虚拟内存空间和栈大小这也是 32 位系统的主要瓶颈。咱们知道同一个进程里的所有线程虽然共享堆区和代码段但每个线程都必须拥有自己独立的栈空间Stack。在 Linux 系统里默认的线程栈大小一般是 8MB可以通过ulimit -s查看。如果是 32 位系统给用户态程序的虚拟内存总共只有 3GB也就是 3072MB。如果除了代码和堆区剩下的全拿来建线程那做个简单的除法个。也就是说在 32 位系统默认配置下你大概建到三四百个线程的时候哪怕物理内存还空着很多程序也会因为‘虚拟地址空间被榨干’而报 OOMOut of Memory错误。如果想多建点就得在调pthread_create之前手动把栈大小改小比如改成 1MB。第二重限制是操作系统的内核参数这是 64 位系统的主要瓶颈。到了 64 位系统用户态虚拟内存动辄上百 TB刚才那种‘地皮不够用’的情况不存在了。但你依然不能无限建线程因为操作系统对‘人口数量’是有强制规定的pid_max限制哪怕是线程在 Linux 内核里也是一个轻量级进程LWP都需要分配一个唯一的 PID/TID。系统有个全局参数/proc/sys/kernel/pid_max默认通常是 32768。超过这个数系统就没号发给你了。threads-max限制内核还会硬性规定整个系统最多能允许存在多少个线程。max_map_count限制每个线程的栈都需要在内存里做映射如果进程拥有的内存映射区域数量超过了这个值也建不出新线程。第三重限制就是物理硬件CPU和物理内存。每个线程在内核里都要维护一个task_struct结构体这都是实打实要吃物理内存RAM的。另外如果真的建了几万个活跃线程CPU 光是做线程之间的上下文切换Context Switch就把算力耗尽了系统直接假死。所以总结下来能建多少个线程等于是在‘虚拟空间容量 / 栈大小’、‘内核限制数’和‘物理内存余量’这三个值里取那个最小的短板。”1.2 并发同步异步互斥阻塞非阻塞的理解1.2.1什么是线程同步和互斥⭐⭐⭐⭐⭐Write the text embed the widget/follow-ups.线程同步与互斥的概念在多线程编程中由于多个线程共享同一个进程的内存空间比如全局变量、堆区当它们并发运行时如果不加干预一定会把数据搞得一团糟。为了管理这种混乱操作系统引入了两个核心机制互斥Mutual Exclusion和同步Synchronization。很多初学者会把这两个词混为一谈但它们的核心目的截然不同互斥是为了“防打架竞争”保证同一时刻只有一个人能碰共享资源而同步是为了“打配合协作”保证多个线程按照特定的逻辑顺序依次执行。严格来说互斥只是同步的一种特殊情况。参考回答“关于线程的同步和互斥我通常用‘公共卫生间’和‘工厂流水线’这两个例子来区分它们。先说互斥Mutual Exclusion。它的核心是解决‘竞争’问题防打架。假设进程里有一个全局变量count。线程 A 和线程 B 都想去执行count。如果不加控制两人同时去读、同时去加最后的结果大概率是错的。互斥机制就像是给这个变量建了一个只有单个坑位的公共卫生间临界区。线程 A 进去了必须把门反锁上加互斥锁 Mutex这时候线程 B 就算再急也只能在门外死等阻塞。等线程 A 完事出来了解锁线程 B 才能进去。互斥不关心你们谁先来谁后到它的底线是同一时刻这个资源只能被一个人独占。再说同步Synchronization。它的核心是解决‘协作’问题打配合。有些场景下光有互斥是不够的。比如一条工厂流水线线程 A 负责生产零件线程 B 负责把零件打包。如果只用互斥锁线程 B 抢到了锁进去一看零件还没造出来呢只能退出来过了一会儿又进去抢还是没有这就白白浪费了大量 CPU 算力。同步机制比如条件变量或信号量就是用来安排顺序的。它会让线程 B 一开始就直接‘睡一觉’挂起等待等线程 A 把零件造好了主动发个信号把线程 B 叫醒“我完事了该你干活了”所以同步机制确保了线程之间能够按照我们期望的先后顺序有条不紊地交替执行。”1.2.2线程同步与阻塞的关系同步一定阻塞吗阻塞一定同步吗⭐⭐⭐⭐同步与阻塞的关系判断结论同步不一定阻塞阻塞也不一定同步。这两个概念在多线程编程中经常结伴出现比如用互斥锁不仅实现了同步还伴随着阻塞但它们在本质上是两个完全独立的概念维度。简单来说同步是“目的”为了协调多个线程的执行顺序或保护共享资源而阻塞是“手段/状态”线程主动或被动地交出 CPU 控制权进入休眠等待区。参考回答“在面试中我一般会明确指出它俩是一对‘交叉但不包含’的关系。第一同步一定阻塞吗答案是不一定。同步只是为了让大家按规矩办事但并不意味着等规矩的时候必须去‘睡觉’阻塞。最典型的例子就是自旋锁Spinlock和底层的原子操作比如 CAS比较并交换。假设线程 A 拿到了自旋锁线程 B 也想拿。此时线程 B 不会交出 CPU 进入阻塞态休眠而是会在原地写个while死循环疯狂地探测锁有没有被释放。这种做法虽然浪费了 CPU 的算力但省去了线程上下文切换的巨大开销。这就是非常典型的‘实现了同步但完全没有发生阻塞’。第二阻塞一定同步吗答案也是不一定。阻塞只是线程运行状态的一种改变从 Running 切到了 Blocked这完全可以是线程自己的私事跟别人没有任何关系。比如我的线程代码里直接写了一句sleep(10)或者我发起了一个读取本地硬盘大文件的请求I/O 阻塞。这时候操作系统会把我的线程剥夺 CPU 并挂起这确实是百分之百的阻塞但这仅仅是因为我自己需要等时间或者等硬盘我没有在和任何其他线程争抢资源也没有在和别人打配合。所以这种情况下‘发生了阻塞但跟同步毫无关系’。总结下来我们平时用的互斥锁Mutex属于‘阻塞式同步’自旋锁属于‘非阻塞式同步’而普通的 I/O 等待则是‘与同步无关的纯阻塞’。”1.2.3并发同步异步互斥阻塞非阻塞的理解⭐⭐⭐⭐⭐这六个词是操作系统和并发编程中最核心、但也最容易被初学者搞混的概念。要彻底理解它们最好的办法是把它们拆分到三个完全不同的维度来看。很多时候我们觉得乱是因为我们把描述“系统架构”的词和描述“通信方式”的词混为一谈了。CPU 状态经典同步阻塞 I/O 流程. Source: Less is more / 阻塞非阻塞与同步异步区别| Less is more参考回答“面试官您好这六个概念看似一团乱麻但我通常会把它们归类到三个不同的维度来理解维度一宏观执行架构并发并发Concurrency它的核心是‘管理多个任务的能力’。只要系统能让人‘感觉’多个任务在同时推进那就是并发。在单核 CPU 上它通过极快的时间片轮转你做一点我做一点交替执行来实现。这就好比一个厨师同时切菜、炖汤、炒饭虽然他只有两只手一个 CPU但他能在不同任务间快速切换。 *(注如果真有多个厨师同时颠勺那是并行 Parallelism并行是并发的一个子集。)*维度二资源访问与协作规则互斥与同步这两个词关注的是‘多个线程怎么共享资源’。 2.互斥Mutual Exclusion核心是‘防打架’。一条独木桥同一时刻只能走一个人。在代码里就是加锁Mutex我拿到锁在改数据的时候你必须在外面等着绝对不能两个线程同时改。 3.同步Synchronization核心是‘打配合’。不仅不能打架还得按照约定的先后顺序来。比如 A 线程负责下载图片B 线程负责渲染图片。B 必须等 A 明确发出‘下载完成’的信号后才能开始干活这种按顺序的协调就是同步。维度三函数调用与 CPU 状态同步/异步 vs 阻塞/非阻塞这两个是最容易被组成四象限矩阵来考的。它们关注的是‘你去找别人办事时的态度以及你等待时的状态’。(注意这里的‘同步’和刚才线程协作里的‘同步’字面一样但语境不同这里特指 I/O 或函数通信模型)同步Synchronous vs 异步Asynchronous——关注点是‘结果怎么拿到’消息通信机制。ul同步你去窗口买饭。点完菜后你必须死盯着窗口直到饭做好了端给你你才算拿到结果。调用者主动等待并获取结果异步你去点外卖。点完菜后你留了个电话然后就不管了。饭做好了外卖员会主动打电话通知你通过 Callback 回调函数或事件触发。被调用者准备好后主动通知阻塞Blocking vs 非阻塞Non-blocking——关注点是‘等结果时你的人CPU在干嘛’线程状态。ul阻塞等饭的过程中你啥事都不干就坐在椅子上睡觉。在操作系统里就是线程交出 CPU 控制权进入休眠状态不再消耗 CPU 算力。非阻塞等饭的过程中你不睡觉。你可能每隔一秒钟就跑去窗口问一句“好了没”轮询 Polling或者你转身去隔壁买杯奶茶。在系统里就是线程不交出 CPU即使调用的数据还没准备好函数也会立刻返回一个错误码线程继续往下跑。最经典的坑这四个词的组合。比如我们常说的同步非阻塞Sync-NonBlocking你调用者必须自己去拿饭同步但你不睡觉非阻塞于是你就像个神经病一样每隔一秒钟就去问一次老板“好了没”这会疯狂消耗 CPU 资源。再比如异步非阻塞Async-NonBlocking如 Node.js 或 Nginx 的模型你点了外卖异步等外卖员打电话然后你跑去打游戏了完全没闲着睡觉非阻塞。这是效率最高、最完美的并发处理模型。”1.3 孤儿进程、僵尸进程、守护进程的概念1.3.1基本概念⭐⭐⭐⭐⭐孤儿进程Orphan Process“爹妈先走被国家收养”。当一个子进程还在跑但它的父进程突然提前挂了或退出了这个子进程就会变成孤儿进程。不用害怕它并不会危害系统因为操作系统里的“居委会大妈”通常是 PID 为 1 的init进程或systemd进程会自动接管它当它退出时负责替它收尸。僵尸进程Zombie Process“人已经没了但户口没销占着坑不拉屎”。当一个子进程退出了它的代码和内存空间都被回收了但它在内核进程表里的task_struct退出状态码等信息还留着等待父进程来调用wait()读取。如果父进程死活不来收尸这个子进程就会变成僵尸进程。如果积攒太多会导致系统的 PID 耗尽再也创建不了新进程。守护进程Daemon Process“常年驻守后台的无敌打工人”。它是一种在后台默默运行、独立于所有终端Terminal的长期服务进程比如 Nginx、Mysql、系统的各种syslogd服务。它通常通过精心设计的“两次fork()”让自己彻底摆脱终端控制避免被用户关闭终端时误杀。参考回答“在面试里这三个概念我通常会结合它们的生命周期和危害来回答第一什么是孤儿进程字面意思就是父母进程先于子进程结束了。比如父进程闪退了留下子进程还在干活。在 Linux 里孤儿进程并不可怕因为系统内核有一个机制当一个进程发现自己被抛弃后它的父进程 IDPPID会被自动重置为 1即init或systemd进程。这就好比孤儿被国家收养了等这个子进程将来跑完退出时init进程会负责调用wait()帮它把尸体收了不会留下任何隐患。第二什么是僵尸进程僵尸进程就比较麻烦了。它是指子进程已经死了但父进程还没来得及或者根本不理睬去收尸。子进程在退出的一瞬间内核必须给它的父进程留一个交待比如我是正常退出的还是报错退出的返回值是什么。这个交待保存在内核的进程描述符里。如果父进程一直不调用wait()或waitpid()去读取这个信息内核就永远不敢把这个坑位注销。如果程序写得有问题不断产生大量僵尸进程系统的PID 资源很快就会被耗尽因为 PID 总数是有限的导致整个系统无法创建新进程。要消灭僵尸进程最直接的办法就是杀死它的父进程让父进程变成孤儿被init接管并顺手收尸或者在代码里正确编写sigaction捕获SIGCHLD信号并调用wait。第三什么是守护进程守护进程和上面两个‘异常状态’不同它是我们主动设计出来的‘后台常驻劳模’。比如我们在服务器上跑一个 Web 服务如果用户把远程 SSH 终端关了或者按了 CtrlC普通的进程就会收到SIGHUP信号直接跟着被杀死。而守护进程通过一系列标准的骚操作脱离控制终端、改变工作目录、重定向输入输出、甚至通过两次fork()彻底摆脱父进程让自己变成一个独立于终端的后台进程。它默默无闻地在后台运行直到系统关机才会结束。”1.3.2如何创建守护进程⭐⭐创建守护进程Daemon的概念守护进程是 Linux 操作系统中一种在后台长期运行、独立于控制终端且不受用户登录登出影响的特殊进程。要用 C/C 编写一个标准的守护进程不能直接写个死循环丢到后台必须严格遵循一套标准的系统调用组合拳。判断结论编写守护进程的核心工程逻辑可以总结为“摆脱控制终端、切断父子连累、重定向输入输出”。其中最精妙、也最常被面试官拷问的灵魂操作就是“两次fork()”其根本目的在于彻底斩断对终端的控制防止进程未来因为意外再次重新获得控制终端。参考回答“在面试里被问到怎么写一个守护进程我通常会把整个标准流程拆解为五个关键步骤并且重点解释为什么要这么做第一步创建子进程父进程退出 (fork())做法程序一启动先调用一次fork()然后父进程直接exit()退出。为什么要这么做这样能让程序立刻脱离前台。如果这个程序是在终端里被手动执行的父进程退出后终端会以为命令已经执行完了从而把命令行提示符还给用户让子进程在后台默默运行。第二步在子进程中创建新会话 (setsid())做法子进程调用setsid()系统调用。为什么要这么做这是最核心的一步默认情况下子进程继承了父进程的会话Session、进程组和控制终端。如果用户将来关掉了 SSH 远程终端终端会发送SIGHUP信号杀死所有相关进程。调用setsid()后子进程会成为新会话的“老大”Session Leader彻底摆脱原先的控制终端以后不管终端怎么关它都不会被误杀。第三步再次创建子进程并让第一代子进程退出第二次fork()做法刚当上会话老大的子进程再fork()一次然后自己退出留下孙子进程继续干活。为什么要这么做很多同学最容易卡在这里。在 Linux 系统里只有会话老大Session Leader才有权力重新打开或申请控制终端。为了彻底杜绝以后程序因为某些骚操作不小心又关联到一个控制终端上我们通过第二次fork()让孙子进程降级成“普通进程”不再是会话老大这样它就绝对、永远不可能再拥有控制终端了。第四步改变当前工作目录 (chdir())做法调用chdir(/)把当前工作目录切换到根目录/。为什么要这么做因为守护进程要跑很久。如果它启动时所在的目录是在某个挂载的 U 盘或者特定文件夹下万一系统后面想要卸载umount这个设备就会因为被这个守护进程的工作目录占用而卸载失败。切到根目录可以避免这种尴尬。第五步重设文件权限掩码 (umask()) 并重定向文件描述符做法调用umask(0)给文件创建权限松绑防止继承来的掩码导致后续创建的文件权限不符合预期。把标准输入0、标准输出1、标准错误2全部重定向到/dev/null或者日志文件。为什么要这么做守护进程是在后台运行的它没有终端如果它不小心往printf里写了东西系统找不到显示器就会报错。所以必须把这些输入输出流‘关掉或者扔进黑洞’。”1.3.3正确处理僵尸进程的方法⭐⭐⭐⭐1.子进程退出时向父进程发送SIGCHILD信号父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。2.把父进程杀掉。父进程死后僵尸进程成为孤儿进程过继给进程initinit始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。fork两次父进程fork一个子进程然后继续工作子进程fork一 个孙进程后退出那么孙进程被init接管孙进程结束后init会回收。不过子进程的回收 还要自己做。两次fork的原理是将子进程成为孤儿进程从而其的父进程变为init进程通过init进程可以处理僵尸进程。

相关推荐

74-基于52单片机三层电梯控制系统设计
74-基于52单片机三层电梯控制系统设计

单片机型号(STC89C52) 目录 一、摘要 二、设计要求 三、原理图 四、说明书预览 五、QA 作者简介:指导学生毕业设计并参与学生毕业答辩指导,有较为丰富的相关经验。期待与各位高校教师、企业讲师以及同行交流合作 主要内容:人工智能与大… · 2026/11/4 7:19:34

Tibo 的 28 天计划 DAY28:Banked 2/2,第二份储存重置为挑战收官
Tibo 的 28 天计划 DAY28:Banked 2/2,第二份储存重置为挑战收官

🔥 个人主页: 杨利杰YJlio ❄️ 个人专栏: 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》 《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》 《超简单:用Python让Excel飞起来》… · 2026/11/4 7:19:34

QML自定义输入框:自动补全与聚焦展开搜索框
QML自定义输入框:自动补全与聚焦展开搜索框

目录 前言 弹层为什么不能放进 ColumnLayout 示例一 自动补全 数据流 键盘上下选词 命中文字高亮 面板展开与逐行浮入 底部状态小字 示例二 聚焦展开搜索框 用单个 Text 实现浮动标签 聚焦外发光 区分 textEdited 与 textChanged 控制面板弹出 搜索历史的去重与置顶 清除按钮与… · 2026/11/4 7:19:34

截图转代码工具实战:从视觉到可执行代码的完整指南
截图转代码工具实战:从视觉到可执行代码的完整指南

1. 这个工具到底在解决什么问题 第一次看到“截图转代码”这个概念的时候,我脑子里蹦出来的第一个念头是:这不就是把设计稿丢进去、代码吐出来吗,前端圈喊了这么多年的“设计稿一键生成”又来了?但真正上手用了一段时间之后&#… · 2026/11/4 8:00:40

向量数据库核心技术解析与选型实践
向量数据库核心技术解析与选型实践

1. 向量数据库的本质与核心价值 第一次接触向量数据库是在处理一个商品推荐系统时遇到的痛点场景。当时我们的MySQL里存着200万条商品数据,每次用户搜索"夏季连衣裙"时,传统的关键词匹配只能返回标题含这三个字的商品,而大量语义相… · 2026/11/4 8:00:40

Node-RED 集成 InfluxDB 实战:时序数据写入与查询避坑指南
Node-RED 集成 InfluxDB 实战:时序数据写入与查询避坑指南

简介:面向 Node-RED 与 InfluxDB 使用者的数据写入与查询节点资源,解决在 Node-RED 流程中对接 InfluxDB 时间序列数据库的问题。节点通过配置节点中的版本选项,兼容 InfluxDB 1.x、1.8 与 2.0,分别调用对应客户端库完成 writePoi… · 2026/11/4 8:00:40

2017县级行政区shp数据实战:从字段解析到空间分析与制图
2017县级行政区shp数据实战:从字段解析到空间分析与制图

简介:这份资源是面向GIS从业者、地理数据分析人员及规划相关专业学生的1:100万比例尺中国2017年县级行政区矢量边界数据集,可用于人口统计、城市规划、环境保护、灾害管理与交通规划等空间分析场景,帮助读者快速获得统一、规范的县级行政单元… · 2026/11/4 8:00:40

低代码AI平台加速机器人训练:从原理到实践
低代码AI平台加速机器人训练:从原理到实践

1. 项目背景与核心价值在AI技术快速发展的当下,机器人训练领域正面临着一个关键矛盾:专业团队需要高度定制化的训练环境,而中小企业则渴望低门槛的AI赋能方案。CosmicBrain AI正是瞄准这一市场空白,通过低代码方式重构了传统机器人… · 2026/11/4 8:00:40

Zero远控_04:纯Python轻量远程控制框架,无依赖嵌入式远控方案
Zero远控_04:纯Python轻量远程控制框架,无依赖嵌入式远控方案

简介:本资源是「Zero远控」系列教程的第四部分,面向网络安全初学者、嵌入式开发学习者及远程控制技术实践者,聚焦远程控制核心原理与Qt框架下的C实现。内容涵盖客户端/服务器双端架构设计、TCP通信建模、身份验证流程、AES加密传输机制及跨平… · 2026/11/4 7:59:40

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码