当前位置:   article > 正文

ARM64 linux并发与同步之内存屏障_arm 内存屏障

arm 内存屏障

1.2 内存屏障

1.2.1 概念理解

在这里插入图片描述
在这里插入图片描述
原理部分比较苦涩难懂,我们先不过多详细介绍这部分的由来和经过,接下来着重讲解什么用途和实现;

ARM64架构中提供了3条内存屏障指令。

  • 数据存储屏障(Data Memory Barrier, DMB)指令。
  • 数据同步屏障(Data Synchronization Barrier, DSB)指令。
  • 指令同步屏障(Instruction Synchronization Barrier, ISB)指令。

学习过程中对这三条指令的含义还是有所困惑的,接下来介绍下:

数据存储屏障(Data Memory Barrier, DMB)指令: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的访问指令,DMB指令保证的是DMB指令之前的所有内存访问指令和DMB指令之后的所有内存访问指令的顺序。也就是说,DMB指令之后的内存访问不会被处理器重排到DMB指令前面。DMB指令不会保证内存访问指令在内存屏障指令之前必须完成它仅仅保证内存屏障指令前后的内存访问指令的执行顺序。DMB指令仅仅影响内存访问指令、数据高速缓存指令,以及高速缓存管理指令等,并不会影响其他指令的顺序。
在这里插入图片描述
**数据同步屏障(Data Synchronization Barrier, DSB)指令:**比DMB指令要严格一些,仅当所有在它前面的访问指令都执行完毕后,才会执行在它后面的指令,即任何指令都要等待DSB指令前面的访问指令完成。位于此指令前的所有缓存,如分支预测和TLB维护操作需全部完成

**指令同步屏障(Instruction Synchronization Barrier, ISB)指令:**比DMB指令和DSB指令严格,刷新流水线和预取缓冲区后,才会从高速缓存或者内存中预取ISB指令之后的指令。ISB指令通常用来保证上下文切换的效果,如ASID(address space ID)更改,TLB维护操作和C15寄存器的修改等。

Note: DMB和DSB指令可以带参数,后续有遇到我们再补充说明,感兴趣的也可自行查阅;

1.2.2 接口说明

在这里插入图片描述

在ARM64 Linux内核中实现内存屏障函数的代码如下:

<kernel/linux/linux-5.15.73/arch/arm64/include/asm/barrier.h>

#define isb()		asm volatile("isb" : : : "memory")
#define dmb(opt)	asm volatile("dmb " #opt : : : "memory")
#define dsb(opt)	asm volatile("dsb " #opt : : : "memory")

#define mb()		dsb(sy)
#define rmb()		dsb(ld)
#define wmb()		dsb(st)

#define dma_mb()	dmb(osh)
#define dma_rmb()	dmb(oshld)
#define dma_wmb()	dmb(oshst)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
1.2.3 linux中案例说明

例1:在一个网卡驱动中发送数据包。把网络数据包写入缓冲区后,由DMA引擎负责发送,wmb()函数保证在DMA传输之前,数据被完全写入缓冲区中。

<kernel/linux/linux-5.15.73/drivers/net/ethernet/realtek/8139too.c>

static netdev_tx_t rtl8139_start_xmit (struct sk_buff *skb,
					     struct net_device *dev)
{
	struct rtl8139_private *tp = netdev_priv(dev);
	void __iomem *ioaddr = tp->mmio_addr;
	unsigned int entry;
	unsigned int len = skb->len;
	unsigned long flags;

	/* Calculate the next Tx descriptor entry. */
	entry = tp->cur_tx % NUM_TX_DESC;

	/* Note: the chip doesn't have auto-pad! */
	if (likely(len < TX_BUF_SIZE)) {
		if (len < ETH_ZLEN)
			memset(tp->tx_buf[entry], 0, ETH_ZLEN);
		skb_copy_and_csum_dev(skb, tp->tx_buf[entry]); // 写入TxStatus以触发DMA传输
		dev_kfree_skb_any(skb);
	} else {
		dev_kfree_skb_any(skb);
		dev->stats.tx_dropped++;
		return NETDEV_TX_OK;
	}

	spin_lock_irqsave(&tp->lock, flags);
	/*
	 * Writing to TxStatus triggers a DMA transfer of the data
	 * copied to tp->tx_buf[entry] above. Use a memory barrier
	 * to make sure that the device sees the updated data.
	 */
	wmb(); //使用一条内存屏障指令以保证设备可以看到这些更新后的数据
	RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
		   tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));

	tp->cur_tx++;

	if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
		netif_stop_queue (dev);
	spin_unlock_irqrestore(&tp->lock, flags);

	netif_dbg(tp, tx_queued, dev, "Queued Tx packet size %u to slot %d\n",
		  len, entry);

	return NETDEV_TX_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

例2: Linux内核里面的睡眠和唤醒接口函数也运用了内存屏障指令,通常一个进程因为等待某些事件需要睡眠,如调用wait_event()函数。睡眠者的代码片段如下。

for (;;) { 

	set_current_state(TASKJJNINTERRUPTIBLE);

	if (event_indicated)

		break;

schedule();

/****************************************************************************/

#define set_current_state(state_value)					\
	do {								\
		debug_normal_state_change((state_value));		\
		smp_store_mb(current->__state, (state_value));		\
	} while (0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

set_current_state()函数在修改进程的状态时隐含插入了内存屏障函数smp_mb()。smp_store_mb函数最终调用的是mb();

唤醒者通常会调用wake_up()函数,它在修改task状态之前也隐含地插入内存屏障函数 smp_rmb()。

wake_up() —> __wake_up_common() -----> wq_entry->func—> autoremove_wake_function() —> try_to_wake_up() ------> smp_rmb()
在这里插入图片描述

  • 睡眠者:CPUI在更改当前进程current->state后,插入一条内存屏障指令,保证加载唤醒标记load event_indicated不会出现在修改current->state之前。
  • 唤醒者:CPU2在唤醒标记store操作和把进程状态修改成RUNNING的store操作之间插入写屏障,保证唤醒标记event indicated的修改能被其他CPU看到。

感谢学习,有什么问题可以评论区讨论学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/1007779
推荐阅读
相关标签
  

闽ICP备14008679号