当内核开始“排队”:信号量与锁机制的底层哲学【华为根技术】

举报
Echo_Wish 发表于 2025/10/23 21:53:39 2025/10/23
【摘要】 当内核开始“排队”:信号量与锁机制的底层哲学

当内核开始“排队”:信号量与锁机制的底层哲学

作者: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式思考:锁,是内核的“哲学课”

很多人觉得信号量、锁,就是些死板的并发控制工具。
但我一直觉得,它们更像是一种“哲学思考的具象化”。

操作系统在处理线程竞争时,其实是在解决一个永恒问题:
个体自由与系统秩序之间的平衡。

  • 信号量允许“有限的并行”,体现“合作的自由”;
  • 互斥锁强调“独占的秩序”,防止“混乱的自由”;
  • 而自旋锁,就是在“高效与公平”之间做选择。

有时候我觉得,内核的世界就像人类社会的缩影——
没有规矩,必然混乱;
规矩太多,又会压抑效率。
信号量和锁,就是内核在混沌与秩序之间找到的那条“中庸之道”。


✨结语

在鸿蒙内核的深处,每一个任务切换、每一次资源访问,
都可能有信号量和锁在默默维持秩序。

它们不像算法那么耀眼,却是系统稳定的“哲学基石”。
正如我们生活中需要规则一样,
操作系统也需要“同步”的智慧——
让每个线程都能有序地自由运行,不越界、不冲突。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。