当前位置: 首页 > news >正文

READ_ONCE、smp_store_release在io_uring中实例分析

文章目录

  • 1. READ_ONCE
    • 1.1 定义
    • 1.2 READ_ONCE使用场景
    • 1.3 为什么用READ_ONCE而不直接用volatile定义变量
  • 2. smp_store_release
    • 2.1 定义
    • 2.2 smp_store_release和smp_load_acquire使用场景
      • 2.2.1 标志位模型
      • 2.2.2 环形缓冲模型
  • 3. io_uring中sq的tail读写分析
    • 3.1 用户层和内核层数据结构
    • 3.2 用户写sq->ktail
    • 3.3 内核读sq.tail

内核:5.15

1. READ_ONCE

1.1 定义

#define READ_ONCE(x)							\
({									\compiletime_assert_rwonce_type(x);				\__READ_ONCE(x);							\
})
#define __READ_ONCE(x)	(*(const volatile __unqual_scalar_typeof(x) *)&(x))

编译时会检查操作数的类型,如果不是基本类型编译就报错:

#define compiletime_assert_rwonce_type(t)					\compiletime_assert(__native_word(t) || sizeof(t) == sizeof(long long),	\"Unsupported access size for {READ,WRITE}_ONCE().")#define __native_word(t) \(sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long))

从上面可见基本类型为char、short、int、long、long long。

#ifndef __READ_ONCE
#define __READ_ONCE(x)	(*(const volatile __unqual_scalar_typeof(x) *)&(x))
#endif

__READ_ONCE的核心是volatile。稍微提一下,volatile原本是在寄存器硬件易变时使用的,后来也扩展到其他使用了,但内核文档中反对广泛直接使用volatile作用于变量的定义处。

1.2 READ_ONCE使用场景

  • 中断上下文会更改的变量
  • 多线程环境更改的变量,多核心时
  • CPU不感知即会变化的变量,如硬件相关的寄存器,DMAC更改的内存
  • 用户空间和内核空间映射的内存,如io_uring中的head/tail等

怎么记?凡是超乎编译器按照单CPU的逻辑处理的变量就需要使用,甚至还需要处理乱序问题。以上4点中的最后一点,用户空间和内核空间的编译都是独立的,互相都感知不到对方的读或写存在,内核的编译器可能认为没有地方会修改tail,因此就不从内存或cache读,而使用寄存器中的值,这种进行了优化也可定是会有问题的。

1.3 为什么用READ_ONCE而不直接用volatile定义变量

编译器需要对代码进行优化,一旦变量直接使用了volatile修饰定义的变量,就意味编译器放弃了对它的任何使用地方的优化。而READ_ONCE是临时地转换为volatile,切实不能按照单CPU的逻辑进行优化的地方才使用,属于更灵活的使用方法,简单说为了优化最大化。

2. smp_store_release

2.1 定义

#ifndef smp_store_release
#define smp_store_release(p, v)						\
do {									\compiletime_assert_atomic_type(*p);				\barrier();							\WRITE_ONCE(*p, v);						\
} while (0)
#endif

和WRITE_ONCE相比,除了编译时的变量类型检查外,多了内存屏障,可确保先后的顺序。

2.2 smp_store_release和smp_load_acquire使用场景

使用场景多为生成者消费者模型。

2.2.1 标志位模型

static int shared_flag = 0;
static int shared_data = 0;void producer(void) {// 生产数据(确保写入操作不会被重排序到flag之后)shared_data = 123;// 发布标志位(release语义保证数据写入对其他核可见)smp_store_release(&shared_flag, 1);
}void consumer(void) {// 获取标志位(acquire语义保证读取到最新值)int flag = smp_load_acquire(&shared_flag);if (flag == 1) {// 此时shared_data的值必然为生产者写入的123printk("Consumed data: %d\n", shared_data);}
}

2.2.2 环形缓冲模型

Circular Buffers的使用场景如下:

  • 生产者
spin_lock(&producer_lock);unsigned long head = buffer->head;
/* The spin_unlock() and next spin_lock() provide needed ordering. */
unsigned long tail = READ_ONCE(buffer->tail);
if (CIRC_SPACE(head, tail, buffer->size) >= 1) {/* insert one item into the buffer */struct item *item = buffer[head];produce_item(item);smp_store_release(buffer->head,(head + 1) & (buffer->size - 1));/* wake_up() will make sure that the head is committed before* waking anyone up */wake_up(consumer);
}spin_unlock(&producer_lock);

CPU必须在head索引使其对消费者可用之前写入新项目的内容,同时CPU必须在唤醒消费者之前写入修改后的head索引。

  • 消费者
spin_lock(&consumer_lock);/* Read index before reading contents at that index. */
unsigned long head = smp_load_acquire(buffer->head);
unsigned long tail = buffer->tail;if (CIRC_CNT(head, tail, buffer->size) >= 1) {/* extract one item from the buffer */struct item *item = buffer[tail];consume_item(item);/* Finish reading descriptor before incrementing tail. */smp_store_release(buffer->tail,(tail + 1) & (buffer->size - 1));
}spin_unlock(&consumer_lock);

CPU在读取新元素之前确保索引是最新的,然后在写入新的尾指针之前应确保CPU已完成读取该元素,这将擦除该元素。

3. io_uring中sq的tail读写分析

3.1 用户层和内核层数据结构

//-----------------用户层--------------------
struct io_uring_sq { //用户层squnsigned *khead;//指向内核io_uring中的head成员 -->11unsigned *ktail;...unsigned sqe_head;//用户层缓存 22unsigned sqe_tail;...
};//-------------------内核----------------------
struct io_ring_ctx {//内核环上下文...struct io_rings *rings; ==》在下面...unsigned cached_sq_head;//内核层缓存 33...
};struct io_rings {
struct io_uring sq, cq; ==》在下面
};struct io_uring {u32 head ____cacheline_aligned_in_smp;//被用户层指向 <--11u32 tail ____cacheline_aligned_in_smp;
};

值得说明的是:

  • 用户层和内核层均尽可能得使用cache变量来代替内核中映射的共享变量如上注释为22/33处,从而减少使用内存屏障带来的同步开销。
  • 内核的struct io_uring结构中head和tail变量均使用____cacheline_aligned_in_smp对齐,从而使得用户态修改tail和内核态修改head能错开不在同一个cache line中,减少各自变更带给彼此cache一致性同步开销。

3.2 用户写sq->ktail

在这里插入图片描述

提交展开如下:

	ret = io_uring_submit(&ring);》__io_uring_submit_and_wait》__io_uring_flush_sq 》if (!(ring->flags & IORING_SETUP_SQPOLL))*sq->ktail = tail;                               (1)elseio_uring_smp_store_release(sq->ktail, tail);     (2)》__io_uring_submit 》__sys_io_uring_enter                                    (3)》无需进入则直接返回                                         (4)

分析__io_uring_submit中的逻辑,发现case(1)会进入(3),而 (2)则进入 (4)。

  • 标注(2)设置了IORING_SETUP_SQPOLL标志,会启动内核线程来提交,无须系统调用进入。对于共享全局变量(sq->ktail指向)用户进程和内核线程一个读一个写,写者可以使用上面的生产者模型,使用了用户层的smp_store_release API。
  • 标注(1)进入(3)执行系统调用,和内核对sq的tail的读访问是串行的,因而不必加内存屏障,直接使用*sq->ktail = tail而未使用WRITE_ONCE,待进一步分析。

3.3 内核读sq.tail

直接搜索到的结果为:

static inline unsigned int io_sqring_entries(struct io_ring_ctx *ctx)
{struct io_rings *rings = ctx->rings;/* make sure SQ entry isn't read before tail */return smp_load_acquire(&rings->sq.tail) - ctx->cached_sq_head;
}static inline bool io_sqring_full(struct io_ring_ctx *ctx)
{struct io_rings *r = ctx->rings;return READ_ONCE(r->sq.tail) - ctx->cached_sq_head == ctx->sq_entries;
}

既有smp_load_acquire又有READ_ONCE,再往上捋一捋。

  • 先看READ_ONCE调用栈:
io_uring_enter》io_sqpoll_wait_sq》io_sqring_full》READ_ONCE

系统调用下来是串行的,使用READ_ONCE就可以了,不必使用更多cache同步开销smp_load_acquire。

  • 再看smp_load_acquire调用栈:
    内核线程会调用io_sqring_entries,因此使用消费者模型的smp_load_acquire:
io_sq_thread》__io_sq_thread》io_submit_sqes》io_sqring_entries》smp_load_acquire

系统调用io_uring_enter也会调用io_sqring_entries,这里应该是可以用READ_ONCE,但不适合上面的内核线程,作为同一个函数,使用smp_load_acquire可同时适用两种情形。

io_uring_enter》io_submit_sqes》io_sqring_entries》smp_load_acquire
http://www.dtcms.com/a/426837.html

相关文章:

  • C/C++数据结构之用数组实现栈
  • Linux timekeeping
  • macOS 下安装 zsh、zsh-syntax-highlighting、powerlevel9k、nerd-font
  • CarveMe:代谢模型构建
  • windows显示驱动开发-调试间接显示驱动程序(二)
  • 企业平台网站建设制作一个网站平台
  • LinuxC++——etcd分布式键值存储系统入门
  • 使用arcgis提取评价指标时,导出数据是负数-9999
  • VUE3+element plus 实现表格行合并
  • LinuxC++——etcd分布式键值存储系统API(libetcd-cpp-api3)下载与二次封装
  • Electron vue项目 打包 exe文件2
  • 【开题答辩全过程】以 springboot高校创新创业课程体系的设计与实现为例,包含答辩的问题和答案
  • package.json详解
  • iOS 应用上架全流程解析,苹果应用发布步骤、ipa 上传工具、TestFlight 测试与 App Store 审核经验
  • QGIS + ArcGIS Pro 下载常见卫星影像及 ESRI Wayback 历史影像
  • Hexo搭建/部署个人博客教程
  • 中山 网站建设发布平台是什么
  • Qt操作Windows平板上摄像头
  • 外贸建站哪好asp网站打开很慢的原因
  • rknn yolo11 推理
  • 虚幻基础:容器
  • 开发环境windows安装oracle 19c并连接数据库
  • 虚幻基础:角色攻击
  • 手机上怎么查看网站设计淮安品牌网站建设
  • go协程的前世今生
  • GO学习2:基本数据类型 与 转换
  • 南京网站开发联系南京乐识昆明餐饮网站建设
  • 3D打印技术如何重塑PEM双极板的制造范式?
  • Excel工作表自动追加工具项目总结报告
  • AR技术赋能航空制造:开启智能装配新时代