当内核开始“排队”:信号量与锁机制的底层哲学【华为根技术】
当内核开始“排队”:信号量与锁机制的底层哲学
作者:Echo_Wish
一、引子:并发的世界,不是你想的那么简单
我们先来聊个接地气的场景。
想象你去食堂打饭。窗口只有一个阿姨,但来了好几个人。
每个人都想快点打完饭,可现实是——只能一个一个来。
有人插队,就乱套了。
有人不走,还堵着窗口。
这,其实就是并发问题的现实版本。
放到操作系统里,多个线程也像排队打饭的同学:
- 都想访问同一个“窗口”(比如共享变量、IO资源);
- 谁先谁后得有规矩,不然数据乱成一锅粥;
- 如果有人卡死(死锁),整个系统都跟着崩。
所以,内核里要有一套“秩序机制”来让大家文明排队。
这套机制,就是我们今天要聊的主角——
信号量(Semaphore) 和 锁机制(Lock)。
它们看起来冰冷,但背后其实有着很深的“哲学”:
如何在一个多线程的世界里,让每个线程都能“有序地自由”?
二、原理讲解:信号量与锁的“道与术”
🧠 信号量:共享资源的“通行证”
信号量(Semaphore)是一种计数型同步机制。
它就像发放“通行证”:
- 拿到证的线程可以访问资源;
- 没拿到的,只能乖乖等待。
举个简单例子:
如果系统里有 3 个打印机,信号量初始值为 3。
每当一个线程使用打印机时,信号量减 1;
当线程用完释放资源,信号量加 1。
信号量归零,就说明“打印机都忙着呢”,其他人必须等。
所以,信号量本质是计数型的同步控制器。
🔒 锁机制:互斥的极致哲学
锁(Lock)比信号量更“霸道”一点。
它的意思是:
“这个资源现在归我用,别人一律不许碰。”
锁主要分为两类:
- 互斥锁(Mutex):同一时刻只能有一个线程访问。
- 自旋锁(Spinlock):忙等机制,不放弃CPU,只不断检查锁状态。
在内核中,锁的哲学更像是——
“让竞争变有序,让协作不冲突。”
三、实战代码:看鸿蒙内核如何“排队打饭”
我们用一个简化版的鸿蒙风格代码来看看信号量和锁的差异。
1️⃣ 信号量示例(共享资源)
#include "los_sem.h"
#include "los_task.h"
UINT32 g_semId;
VOID TaskPrint(UINT32 param)
{
(VOID)param;
while (1) {
LOS_SemPend(g_semId, LOS_WAIT_FOREVER); // 获取信号量
printf("Task %u 打印机开始工作...\n", LOS_CurTaskIDGet());
LOS_TaskDelay(50);
printf("Task %u 打印机工作结束\n", LOS_CurTaskIDGet());
LOS_SemPost(g_semId); // 释放信号量
}
}
VOID Example_Semaphore(VOID)
{
LOS_SemCreate(2, &g_semId); // 允许同时2个任务使用资源
LOS_TaskCreate(NULL, "TaskA", TaskPrint, NULL, 1, NULL);
LOS_TaskCreate(NULL, "TaskB", TaskPrint, NULL, 1, NULL);
LOS_TaskCreate(NULL, "TaskC", TaskPrint, NULL, 1, NULL);
}
运行结果可能是这样的:
Task A 打印机开始工作...
Task B 打印机开始工作...
Task C 等待中...
这里信号量是 2,所以允许两个任务同时使用资源。
C 任务没抢到,就得排队。
2️⃣ 互斥锁示例(独占资源)
#include "los_mux.h"
#include "los_task.h"
UINT32 g_mutexId;
VOID TaskWrite(UINT32 param)
{
(VOID)param;
while (1) {
LOS_MuxPend(g_mutexId, LOS_WAIT_FOREVER); // 加锁
printf("Task %u 开始写文件\n", LOS_CurTaskIDGet());
LOS_TaskDelay(100);
printf("Task %u 写完文件\n", LOS_CurTaskIDGet());
LOS_MuxPost(g_mutexId); // 解锁
}
}
VOID Example_Mutex(VOID)
{
LOS_MuxCreate(&g_mutexId);
LOS_TaskCreate(NULL, "TaskA", TaskWrite, NULL, 1, NULL);
LOS_TaskCreate(NULL, "TaskB", TaskWrite, NULL, 1, NULL);
}
输出结果:
Task A 开始写文件
Task A 写完文件
Task B 开始写文件
Task B 写完文件
互斥锁保证了“同一时间只能有一个线程在写文件”,避免了数据被同时修改的风险。
四、场景应用:从驱动到调度,处处是同步的艺术
鸿蒙内核(LiteOS内核)中,信号量与锁的身影几乎无处不在。
📍 驱动层(Device Driver):
驱动访问共享硬件寄存器时,一定要加锁。
否则两个任务同时读写寄存器,分分钟炸掉。
📍 任务调度(Task Scheduling):
任务切换时要保护全局队列,这种地方通常用自旋锁——因为不能休眠,只能“忙等”。
📍 IPC通信(进程间通信):
信号量是任务同步的常客,像“信号灯”,告诉另一个任务“我干完了,你上”。
📍 文件系统与内存管理:
锁在这些场景中就像“交通红绿灯”,控制访问顺序。
换句话说:
内核同步,是操作系统秩序感的根基。
五、Echo_Wish式思考:锁,是内核的“哲学课”
很多人觉得信号量、锁,就是些死板的并发控制工具。
但我一直觉得,它们更像是一种“哲学思考的具象化”。
操作系统在处理线程竞争时,其实是在解决一个永恒问题:
个体自由与系统秩序之间的平衡。
- 信号量允许“有限的并行”,体现“合作的自由”;
- 互斥锁强调“独占的秩序”,防止“混乱的自由”;
- 而自旋锁,就是在“高效与公平”之间做选择。
有时候我觉得,内核的世界就像人类社会的缩影——
没有规矩,必然混乱;
规矩太多,又会压抑效率。
信号量和锁,就是内核在混沌与秩序之间找到的那条“中庸之道”。
✨结语
在鸿蒙内核的深处,每一个任务切换、每一次资源访问,
都可能有信号量和锁在默默维持秩序。
它们不像算法那么耀眼,却是系统稳定的“哲学基石”。
正如我们生活中需要规则一样,
操作系统也需要“同步”的智慧——
让每个线程都能有序地自由运行,不越界、不冲突。
- 点赞
- 收藏
- 关注作者
评论(0)