Redian新闻
>
图解|RCU原理

图解|RCU原理

科技

Linux 内核设计了多种锁机制,比如 读写锁自旋锁 和 信号量 等。为什么要设计这么多锁机制呢?这是因为不同的锁机制适用于不同的场景,比如 读写锁 适用于读多写少的场景;而 信号量 适用于进程长时间占用锁,并且允许上下文切换的场景。

本文主要介绍一种 Linux 内核中性能非常高的锁机制:RCU锁机制

RCU 是 Read Copy Update 的缩写,中文意思是 读取复制更新。RCU锁机制 就是通过读取、复制和更新这三个操作来实现锁功能。在介绍 RCU锁 之前,我们先来看看下面的实例。

struct foo {
    int  a;
    char b;
    long c;
 };

struct foo *gbl_foo;

void foo_read(void)
{
    foo *fp = gbl_foo;
    if (fp != NULL)
        do_something(fp->a, fp->b, fp->c);
}

void foo_update(foo *new_fp)
{
    foo *old_fp = gbl_foo;
    gbl_foo = new_fp;
    free(old_fp);
}

假如有线程 A 和线程 B 同时执行 foo_read(),而另线程 C 执行 foo_update(),那么会出现以下几种情况:

  1. 线程 A 和线程 B 同时读取到旧的 gbl_foo 的指针。
  2. 线程 A 和线程 B 同时读取到新的 gbl_foo 的指针。
  3. 线程 A 和线程 B 有一个读取到新的 gbl_foo 的指针,另外一个读取到旧的 gbl_foo 的指针。

如果线程 A 或线程 B 在读取旧的 gbl_foo 数据还没完成时,线程 C 释放了旧的 gbl_foo 指针,那么将会导致程序奔溃。

也就是说,在不加锁的情况下,对公共数据的访问是危险的。当然,我们可以使用 读写锁信号量 或者 自旋锁 来对公共数据进行保护。但这些锁都有各自的弊端,比如:

  • 读写锁:对于写操作较多的场景,性能会非常差。
  • 信号量:上锁失败的进程将会切换上下文,从而导致系统的性能下降。
  • 自旋锁:获得锁的 CPU 将会阻塞其他 CPU 的允许,从而导致系统的并行能力下降。

那么有没有一种锁机制,对系统的性能影响不大的呢?所以,Linux 内核黑客们就创造出 RCU锁

RCU锁原理

如果能够保证所有使用某个公共数据的线程不再使用它,那么就可以安全删除此公共数据。

1. 宽限期

在上面的例子中,如果能够保证线程 A 和线程 B 不再使用旧数据,那么线程 C 就能安全删除旧数据。

如下图所示(旧数据对应对象A,新数据对应对象B):

rcu-timeline

从上图的时间线可以看出,线程 A 和线程 B 从 glb_foo 指针获取的都是对象 A 的引用。

提示:因为 glb_foo 指针在时间点 B 才被替换成对象 B,而线程 A 和线程 B 都是在时间点 B 前获取 glb_foo 指针指向的对象,所以它们获取到的都是对象 A 的引用。

而在 安全点 后,线程 A 和线程 B 便不再使用旧数据(对象A)。所以此时,线程 C 便可以安全释放旧数据(对象A)。

线程 A 和线程 B 使用旧数据的这段期间,被称为 宽限期。如下图所示:

grace-period

所以,RCU锁 的核心思想就是怎么确定 宽限期。因为确定宽限期后,就可以随心所欲地释放旧数据。

2. 宽限期确认

RCU锁 的原理虽然比较简单,但是实现却有点小复杂,主要是因为 宽限期 的确定比较麻烦。

为了能够确认 宽限期,使用 RCU 锁时有以下限制:

  • 使用 RCU 锁前,必须禁止内核抢占。
  • 在 RCU 锁保护的临界区中,不能使用可能触发调度的函数(如不能调用 alloc_pages 函数)。

由于在 RCU 临界区是禁止调度的,所以如果 CPU 发生了调度,就可以确定当前线程已经退出了临界区(也就是说当前线程不再引用旧对象)。如果所有的 CPU 都至少发生过一次调度,那么也就说明没有任何线程引用旧对象,此时就可以安全释放旧对象了。

所以,RCU 锁的核心原理是:在释放旧对象前,必须等待所有 CPU 核心至少调度一次。如下代码所示:

void foo_update(struct foo *new_fp)
{
    // 1. 将 gbl_foo 指向新对象
    spin_lock(&foo_mutex);
    foo *old_fp = gbl_foo;
    gbl_foo = new_fp;
    spin_unlock(&foo_mutex);

    // 2. 等待所有 CPU 核心至少调度一次
    synchronize_kernel();

    // 3. 释放旧对象
    free(old_fp);
}

foo_update() 函数释放旧对象的步骤如下:

  1. 使用新对象替换旧对象,在替换前必须使用自旋锁进行保护,避免多个 CPU 同时修改 gbl_foo 指针的值。
  2. 等待所有 CPU 核心至少调度一次。
  3. 由于所有 CPU 核心都至少调度过一次,那么可以确认现在没有线程引用旧对象,所以可以安全释放旧对象。

3. RCU临界区

通过前面的分析可知,在 RCU 临界区中是不能发生调度的。要保证临界区不发生调度,首先要确保在临界区中不能调用可能触发调度的函数,如:alloc_pages()。这点需要 RCU 使用者自己保证。

另外一点要保证的是,内核不能发生抢占,这点可以通过调用 preempt_disable() 函数实现。内核定义了一个名为 rcu_read_lock() 的宏,如下所示:

#define rcu_read_lock()  preempt_disable()

可以看出, rcu_read_lock() 宏其实就是 preempt_disable() 函数的别名。所以,使用 RCU 锁时,可以使用 rcu_read_lock() 宏对临界区进行保护。

当退出临界区时,需要调用 rcu_read_unlock() 把内核抢占打开。rcu_read_unlock() 的定义如下:

#define rcu_read_unlock() preempt_enable()

可以看出,rcu_read_unlock() 宏就是 preempt_enable() 的别名。

所以,当我们使用 RCU 锁对临界区进行保护时,必须将需要保护的代码放置在 rcu_read_lock() 和 rcu_read_unlock() 之间,如下所示:

void foo_read(void)
{
    // 1. 保护临界区
    rcu_read_lock();
  
    foo *fp = gbl_foo;
    if (fp != NULL)
        do_something(fp->a, fp->b, fp->c);
  
    // 2. 退出临界区
    rcu_read_unlock();
}


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
PyTorch统治学术论文,TensorFlow只占4%,LeCun:还能为啥?Marcus by Goldman Sachs Savings 银行账户民乐首演!世界弦乐合奏课|RIVERS OF RESONANCE 河流的共鸣电还原CO2制乙醇:Cu/CuNC双位点界面,实现低过电位下的高效C-C偶联 | NSR精品投行|Rothschild&Co 2023毕业生计划火热招聘中,base上海SRCNN、DRCN、FSRCNN、ESPCN、SRGAN、RED…你都掌握了吗?一文总结超分辨率分析必备经典模型(一)【$100 充值奖励,新老用户均可参与】Marcus by Goldman Sachs Savings 银行账户由视频想到的:意识由左脑负责这种认识对不对另说,这种简单联系无论在定义上还是理论陈述上都非常粗糙。AMINO三月新鲜事:合伙人在INSEAD新加坡和法国校区同步演讲|Replit30秒创建一个ChatGPT应用荣昌生物2022年年报点评:RC18授权有望实现,RC48临床有望加速【东吴医药朱国广团队】精品投行|Rothschild&Co 2024 暑期实习已开启,年薪$120,000新冠患者心肌炎发生率较低,心梗或微梗死多见 | Circulation研究【图解】全市划分为4个林地保护利用区域,图解2023-2035年上海市森林和林地保护利用规划→英伟达CUDA垄断地位难保:PyTorch不断拆塔,OpenAI已在偷家顶级投行|RBC 2023暑期实习生招聘倒计时,留学生抓紧投递内学框架与外学旨趣立即了解|2023年中国赴美出境攻略“不速之客”来我家Cops Rescued Minor Forced Into Marriage, Then Sent Her Back Home西雅图年度房市总结|Redmond 最受欢迎,Bothell、Issaquah 成为后起之秀万玛才旦:我的使命感在于对历史与现状的了解|纪念西雅图看房日记|Redmond 微软附近挑空设计的自住学区房所有的情绪都值得被理解|免费预咨询LeCun吴恩达开直播,疾呼GPT-5不能停!LeCun:干脆管制凤头鹦鹉6个月AORUS GeForce RTX™ 2080 Ti XTREME WATERFORCE 11G观众短评|“虽然可爱但很cult,虽然cult但也很可爱”CubeFS在大数据和机器学习的探索和实践丨ArchSummit峰会实录Jay Alammar再发新作:超高质量图解Stable Diffusion,看完彻底搞懂「图像生成」原理美国与中国科技战略竞争前沿告急给小驴补个足球,跟物理学家唱个反调2023招聘季 | Barclays (US)已开放Equity Research实习万字详解|从软件复杂度的角度去理解DDD剽窃、作弊,ChatGPT竟偷偷生成了12篇署名论文!Marcus炮轰Ta变成CheatGPT无解|恋爱脑还是为绿卡?中国富家女留学生未婚先孕,被甩还坚持生娃!网友热议: 没底线?PyTorch统治学术论文!TensorFlow只占4%,LeCun:还能为啥?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。