RISC-V RVWMO 内存模型解释

news2024/11/15 17:41:12

RISC-V RVWMO 内存模型解释

引言

本文介绍 RISC-V RVWMO 内存模型。RVWMO 内存模型定义了什么样的全局内存顺序才是合法的。本引言部分将解释为什么会出现不合法的全局内存顺序,以及为什么需要内存模型。

首先引起乱序的全局内存顺序(指令重排序)有两种原因,一种是软件编译器带来的,另外一种是硬件执行上带来的。

软件带来指令重排序很好理解,如下面的例子:

x = 1;
while(x)
	x = memory[0];
y = 1;

在程序编译时期编译器无法预知多核执行的动态性,因此编译器可以认为语句 y = 1 和 x 的值没有必然联系,因此 y = 1 可能比 x = 1 先执行,从而跳过循环,在编译器看来这并不违反语义。

第二种由硬件执行产生的指令重排序,在单核单线程程序中,程序的执行的顺序看起来应该总是和程序设计的顺序一致,但是在硬件实现的视角中,情况却完全不同,首先我们定义对于内存操作,有两种内存操作原语,即读内存和写内存。

我们定义一个完成一个读操作是瞬时原子的,因为读操作不需要通知其他线程,因此读操作一旦完成,其他线程默认均可见。但是写操作的情况却完全不同,一个写操作可能不是原子,这有点反直觉,但确实如此,一个写操作需要通知所有其他线程修改了写地址上的内容之后,一个写操作才算完成,称为写可见性。

如果读取了一个未完成的写操作,即写操作还没有对所有的线程都可见,此时就会发生读非原子的写操作,此时读写会发生重排序,通常情况下,处理器内核通常具有写缓冲区,例如下面的例子:

写缓冲区

程序依次发生的顺序为:

  1. 首先 P1 将 Flag1 = 1 写入缓冲区,此时 Flag1 = 1 对线程 P2 不可见。
  2. P2 将 Flag2 = 1 写入缓冲区,此时 Flag2 = 1 对线程 P1 不可见。
  3. P1 读取 Flag2 的值为 0,进入临界区。
  4. P2 读取 Flag1 的值为 0,进入临界区。
  5. Flag1 = 1 从写缓冲区写入内存。
  6. Flag2 = 1 从写缓冲区写入内存。

在这种情况下,因为写的非原子性,产生了乱序的读写,导致两个线程同时进入临界区。但是写缓冲区带来了屏蔽等待写入时间的效果(将指令执行的时间重叠)。

除了写缓冲区,硬件还有其他的实现,例如 Overlapped writes, Non−blocking reads 这些都会产生读写的重排序。

不同的 Relaxed Memory 内存模型允许不同情况的重排序,RISC-V 采用了 RVWMO 内存模型,方便软件产生最大程度上的优化,以及最大程度上的硬件实现自由。

程序顺序、全局内存顺序以及保留程序顺序

在多线程内存模型中,存在三种程序执行顺序,即程序顺序、全局内存顺序以及保留程序顺序。最容易理解的是程序顺序,一个程序顺序定义为程序在指令流中的顺序,也是程序设计者所编写的顺序,例如对于一个 Hart 看到的指令流:

(a) li t1, 1
(b) sw t1, 0(s0)
(c) lw a0, 0(s0)

则称在程序顺序中指令 (b) 先于指令 (c)

上文已经解释过假设有两个线程 A 和线程 B ,为什么线程 B 看到 A 的执行顺序与线程 A 的程序顺序完全不同。而全局内存顺序定义了所有线程所看到的内存的全局性事件列表,例如对于两个 Hart 的指令流:

Hart 0:
(a) li t1, 1
(b) sw t1, 0(s0)
Hart 1:
(c) li t1, 2
(d) sw t1, 0(s0)
(e) lw a0, 0(s0)

则对于线程0来说,一种可能的全局内存顺序为: (c)(d)(a)(b)(e) ,即在事件 (c)(d) 结束后,线程0观测到了线程1的 (a)(b) 发出的全局事件,因此指令 (e) 读取到的值为1。

保留程序顺序在原文中的定义为:

Preserved program order represents the subset of program order that must be respected within the global memory order.

简单的理解为,因为全局内存顺序无法保证和程序顺序相同,但是为了程序语义的正确,至少必须保证程序顺序的一个子集,称为保留程序顺序,在全局内存顺序中和程序顺序相同。

RISC-V RVWMO 模型定义了13条保留程序顺序的规则和3条公理,本文剩下的部分将讲解这13条规则和3条公理。

3条公理

加载值公理(Load Value Axiom)

加载值公理定义了一个具有 load 语义的指令可以返回值,原文如下:

Load Value Axiom: Each byte of each load i returns the value written to that byte by the store that is the latest in global memory order among the following stores:

  1. Stores that write that byte and that precede i in the global memory order
  2. Stores that write that byte and that precede i in program order

也就是说 load 指令可以返回的值必须是在全局内存顺序以及在当前线程程序顺序中的最后一条 store 指令所写入的值。

该公理引入 RVWMO 内存模型中对写原子性的规定,即一个线程可以读取自己一条还未完成的写入指令所写入的值,即 RVWMO 中的 store 指令是非原子写的(此时写可能对其他线程不可见)。这个规则允许硬件优化写入时间(非阻塞写),通常由一个写缓冲区(store buffer)实现,对于具体的写缓冲区的实现,读者可以参考计算机体系结构相关的书籍。

在 RISC-V 规范中给出了第一个示例:

示例1
上图的结果按时间顺序的解释为:

  • (a) 执行并将1写入自己的私有写缓冲区。
  • (b) 读取自己写缓冲区的值(store buffer forwarding)(之前 (a) 写入的)。
  • (c) 确保之前的读取已完成。
  • (d) 读取当前内存y的值为0。
  • (e) 执行并将1写入自己的私有写缓冲区。
  • (f) 读取自己写缓冲区的值(之前 (e) 写入的)。
  • (g) 确保之前的读取已完成。
  • (h) 读取当前内存x的值为0。
  • (a) 写缓冲区将1写入内存x。
  • (e) 写缓冲区将1写入内存x。

接下来规范中给出的一个更加极端的例子:

示例2
对于结果的解释为:

  • 线程1认为控制性依赖 (d) 循环不会对 (e)(f)(g) 产生影响,因为地址不同(即使其他线程写入了相同的地址,但是线程1无法预测是否真的有其他线程在写入),因此 (e)(f)(g) 允许先比 (d) 执行,也不会改变当前线程的程序顺序的语义,此时 (e) 写入自己的私有写缓冲区z=1。
  • (f) 读取自己写缓冲区的值(之前 (e) 写入的)。
  • (g) 读取内存中的x值为0。
  • (a) 执行写入私有写缓冲区x=1。
  • (b) 确保私有写缓冲区的任务已经全部写入,将x=1写入内存。
  • (c) 执行写入私有写缓冲区y=1,之后将y=1写入内存。
  • (d) 执行成功,读取到1,跳出循环。
  • (e) 预测成功,将z=1从缓冲区写入内存。

原子性公理(Atomicity Axiom)

Atomicity Axiom (for Aligned Atomics): If r and w are paired load and store operations generated by aligned LR and SC instructions in a hart h, s is a store to byte x, and r returns a value written by s, then s must precede w in the global memory order, and there can be no store from a hart other than h to byte x following s and preceding w in the global memory order.

RISC-V 提供了两种原子性指令,一种是 AMO ,AMO 把读和写两种操作打包为一种原子性操作,因此该指令本身就是原子的,而另外一种 LR/SC 指令则不同,该公理说明了什么是成对的 LR/SC (原子性),只有成对的 LR/SC , SC 操作才能成功。

该公理说若在线程 h 中 LRSC 是成对的,那么在两者之间除了线程 h 可以修改被保留的地址为,其他线程在全局内存序中均不允许修改。

但是,该公理允许在全局内存序上有下面的操作:

  1. 其他线程或线程 h 读取或修改了非 LR/SC 重叠的地址上的内存。
  2. 允许线程 h 读取或修改 LR/SC 重叠的地址上的内存。
  3. LR 和 SC 不必成对出现,可以嵌套。

第一条比较好理解,对于第二条,下面的例子中的 LR/SC 可能成对:

(a) lr.d a0, 0(s0)
(b) sd t1, 0(s0)
(c) sc.d t2, 0(s0)

对于第三条,下面的例子中的 LR/SC 可能成对:

(a) lr.w a0, 0(s0)
(b) sw t1, 4(s0)
(c) sc.w t2, 8(s0)

其中 (c) 可能和之前的 LR 成对, (a) 不影响 (c) 是否成功。

需要注意的是,如果在程序顺序中一对 LRSC 之间出现了另外一个 SC ,无论该 SC 是否成功失败,最后一个 SC 必将失败。

渐近性公理(Progress Axiom)

原文如下:

Progress Axiom: No memory operation may be preceded in the global memory order by an infinite sequence of other memory operations.

渐近性公理较好理解,它确保了一个内存操作应该在有限的时间内完成(被所有的线程可见)。如果渐近性公理不成立,那么用户将无法实现自旋锁等一些同步操作,因为可能一次写入永远都不会对其他线程可见。

如果一个具有缓存的系统,应该通过缓存一致性协议来确保在有限的时间内,内存操作对所有线程可见。

13条规则

在介绍13条规则之前,我们首先制定一个通用约定:

  1. 指令 a 和指令 b 都是内存操作指令(而不是IO区域)
  2. 在程序顺序中, a 先于 b
  3. ab 满足下面任意一条规则,则必须保证在保留程序顺序(当然也在全局内存顺序)中, a 也先于 b

重叠地址顺序(Overlapping-Address Orderings)(1-3)

Rule 1: b is a store, and a and b access overlapping memory addresses

规则1说若b是一个 store 指令,并且 ab 有重叠的地址,那么 a 就不能排到 b 后面。这是显然的,因为若调换两个指令的执行顺序,则连程序顺序语义都得不到保证。但是若 a 是一个 store 指令 b 是一个 load 指令,则不受该规则约束, load 可以先于 store 出现在全局内存序中,因为硬件通常需要实现写缓冲区。

Rule 2: a and b are loads, x is a byte read by both a and b, there is no store to x between a and b in program order, and a and b return values for x written by different memory operations

规则2说若 ab 都是 load 指令,并且 ab 有重叠的地址,并且在程序顺序中 ab 之间没有地址重叠的 store 指令,并且 ab 的值是由不同 store 指令写入的。这条规则十分抽象,但至少应遵守的一条规则是一个新的 load 指令不应该比一个旧的 load 指令返回一个更旧的值,这条规则被称为 CoRR (Coherence for Read-Read pairs)规则。

规范中给出了两个具体的例子分别解释了规则2的第二句话和第三句话,首先看例子1:

例子1
给出的结果解释是:

  • (d) 指令由于某种原因暂停了执行。
  • (e) 执行,并放到写缓冲区。
  • (f) 执行并读取写缓冲区的值。
  • (g)(h)(i) 分别按顺序执行。
  • (a)(b)(c) 按顺序执行,并将 x=1 和 y=1 依次从写缓冲区写入内存。
  • (d) 指令跳过写缓冲区,读取内存中y=1。
  • (e) 将写缓冲区的值写回内存。

对应的全局内存顺序为 (f)(i)(a)(c)(d)(e) ,注意到虽然 (f) 排在 (d) 之前,但是 (f) 却比 (d) 读取的值要新,因此不违反 CoRR 规则,在 (f)(d) 之间夹入了一条在程序顺序上和两者地址重合的 load 指令,该重排序是合法的。

读者可能疑惑,在全局内存顺序中 load 指令的返回值并不遵循全局内存顺序的读写顺序,毕竟 load 的返回值和全局内存顺序并无直接关系,全局内存顺序无法唯一的决定 load 的返回值,具体详见上文加载值公理。

第二个例子:

例子2
假设 z 的值已经被其他线程写入v,若全局内存序为 (h)(k)(a)(c)(d)(g) ,注意虽然 (h)(g) 之前,但是 (h) 并没有比 (g) 读到更旧的值,因此也不违反 CoRR 规则。

但是,假设 z 的值已经被其他线程写入v不成立, (g)(h) 可能读到不同的值,此时必须要求 (g)(h) 前执行,即值是由不同 store 指令写入的。

Rule 3: a is generated by an AMO or SC instruction, b is a load, and b returns a value written by a

规则3定义了 AMO 和 SC 指令的原子性,若 a 是 AMO 或者 SC 指令, bload 指令,并且 b 返回由 a 写入的值。上文说过 RVWMO 允许普通的 store 非原子性写,但是 AMO 和 SC 指令必须具有原子性写,在 a 对所有线程可见之前,b 都不应该读取 a 写入的值。

最后需要提醒读者的一点是,重叠地址指的是部分重叠就算是重叠,因为存在不同位宽的内存操作指令。

内存栅栏(Fences)(4)

Rule 4: There is a FENCE instruction that orders a before b

该规则比显然,一个 FENCE 指令指定了全局内存序的要求,该指令具有八个可以设置的位,指定了在内存栅栏前的需要定序的指令和之后需要定序的指令,具体请参考 FENCE 指令说明。

显式同步(Explicit Synchronization)(5-8)

Rule 5: a has an acquire annotation

规则5规定了一个“获取”语义,正如字面意思“获取”经常是获取锁进入临界区(Critical section),因此不能把获取锁之后的操作拿到获取锁之前来执行,否则就会违反临界区原则。

RISC-V 提供了两种实现“获取”语义的方法,一种是原子指令的 aq 位,另外一种由 FENCE R,RW 提供。

例子1
上图中的例子使用 aq 位实现“获取”语义,注意 aq 位规定临界区内的指令不能在临界区外(之前)执行,并没有规定临界区外(之前)的指令能不能在临界区内执行,因此两个无关的 storeload 指令在临界区内执行是允许的。

例子2
上图中使用 FENCE R,RW 定序临界区,和 aq 位不同的是,该指令具有额外的要求,无关的 ld 指令不能再临界区内执行,相比来看 aq 位具有更贴近的“获取”语义,允许硬件有额外的优化。

Rule 6: b has a release annotation

规则6和规则5相似,只是位置不同,一个“释放”语义,通常是释放锁,不允许临界区内的操作在临界区外执行(之后),同样也提供了2中方案,一是设置原子指令的 rl 位,另外是使用 FENCE RW,W 定序。

Rule 7: a and b both have RCsc annotations

一个 RCsc 记号指的是一个 AMO 、LR或是SC指令具有 aq 或是 rl 标记。具有该记号的指令不能被重排序,规则5、6、7共同组成了临界区规则。

因为 RVWMO 目前还没有 RCpc 记号的指令,因此关于 RCpc 和 RCsc 的细节请见后文参考资料。

Rule 8: a is paired with b

规则8目前成对的指令只有 LR 和 SC 指令,规定 LR 和 SC 不能重排序。

句法依赖(Syntactic Dependencies)(9-11)

Rule 9: b has a syntactic address dependency on a
Rule 10: b has a syntactic data dependency on a
Rule 11: b is a store, and b has a syntactic control dependency on a

规则9和10从直觉上来讲是合理的。 a 指令确定了 b 指令的地址,或者执行 b 需要 a 的结果,又或者是 bstore 指令, a 控制 b 的执行,那么 a 指令就需要在 b 指令执行之前执行。

需要强调的是,这里的依赖指的是句法依赖,而不是语义依赖,换句话说,即使重排序从结果上是正确的,但是如果是句法依赖,该规则也不允许重排序,例如:

例子
上图中,尽管 xor a2, a1, a1 的值永远是0,重排序两个 load 指令从语义上看起来没问题,但是句法依赖却不允许这种重排序。该规则提供一个轻量级的定序语义,相反若使用 FENCE R,R 则会定序所有的 load 指令。

对于控制依赖也具有相同的句法依赖性,例如:

例子1
尽管 next 标签总是执行,但也存在对于第一条指令的依赖,同理:

例子2
尽管 bne 指令从语义上讲没有实际的作用效果,但是 next 标签的指令也存在对于第一条指令的依赖。

SC 指令的成功和失败也会产生依赖:

例子

假设不规定SC 指令的依赖,则一种可能的执行结果是,线程为了保证 (b)(c) 成对执行成功,先让 (d)(e)(f)(a) 执行,此时 (c) 可以提前返回 0,然后执行 (b)(c) 其中 (c) 执行成功。规则11不允许这样的执行。

其他例如 CSR 指令、浮点指令也存在依赖情况,具体请参考规范说明。

流水线依赖(Pipeline Dependencies)(12-13)

Rule 12: b is a load, and there exists some store m between a and b in program order such that m has an address or data dependency on a, and b returns a value written by m

流水线依赖描述了一种传递性依赖,规则12指明了 load 只有当之前的 store 指令的地址和数据都确定了, load 才可以提前读 store 指令写入的值。

例子1
这里例子中 (d) 一定是比 (f) 先执行的,只有执行了 (d) 那么 (e) 的数据才准备好, (f) 才能执行,这一般受限于处理器设计中的流水线依赖,因此叫做流水线依赖。

下面的例子情况则完完全不同:

例子2

虽然上一个例子 (d)(e)(g) 之间有流水线依赖,但是这里例子中额外的 (f) 打破了这种依赖, (f) 的地址和数据可以先准备好, (g) 允许读 (f) 写入的数据。

Rule 13: b is a store, and there exists some instruction m between a and b in program order such that m has an address dependency on a

规则13说若 b 是一个 store 指令,并且 ab 在程序顺序中存在其他指令的地址依赖于 a 的结果,那么 ab 不能重排序,看下面的例子:

例子
(f)(d) 先执行,存在一种情况当 a1 = s0 的时候,此时 (e) 无法读取到在 (f) 之前写入的值了,因为这样违反加载值公理。因此 (d) 一定比 (f) 先执行。

IO内存

对于内存和IO内存,RISC-V 定义了物理内存属性(Physical Memory Attributes,PMAs)来描述一段内存区域,具体可参考 RISC-V 特权规范。

对于IO内存,加载值公理和原子性公理不再适用,在IO内存中,读操作可能有明显的副作用(side effects),并且读操作的值可能不是由 store 指令写入的,这取决于IO设备。但是IO内存依然遵循下面的保留程序顺序:

  1. ab 依旧遵循13条保留程序顺序规则,但是 ab 要么都是内存操作,要么都是 IO 操作,不能一个是内存操作一个是 IO 操作。
  2. ab 对重叠的 IO 地址进行操作。
  3. ab 访问相同的强顺序IO区域(strongly-ordered)。
  4. ab 访问的IO区域,有一个通道是通道1。
  5. ab 访问相同的IO区域通过相同的通道,除了通道0。

RISC-V 的 FENCE 指令区别 IO 操作和内存操作,处理器实现应该总是假设,当设备收到操作指令之后,设备可能立刻访问之前的内存操作,此时就需要 FENCE 指令进行定序:

实例

假设 0(a1) 是一个IO地址,而 0(a0) 是一个内存地址,为了确保设备能够正确读取第一条指令存入的值,需要使用 FENCE W, O 定序。

参考资料

  1. S. V. Adve and K. Gharachorloo. Shared memory consistency models: A tutorial.
    Computer, 29(12):66–76, 1996.
  2. A. Waterman and K. Asanović, editors. The RISC-V Instruction Set Manual,
    Volume I: Unprivileged ISA, Version 20191213. December 13, 2019.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1414560.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI作画工具 stable-diffusion-webui 一键安装工具(A1111-Web-UI-Installer)

安装 下载最新版本确保你的 NVIDIA 显卡驱动程序是最新的(起码不能太老)启动安装程序在欢迎屏幕上单击下一步在屏幕上,选择要安装的内容如果你已经安装了 Python 3.10 和 Git,那么可以取消选中如果你不知道这些是什么&#xff0c…

精品基于Uniapp+springboot助农管理系统App农产品积分购物商城

《[含文档PPT源码等]精品基于Uniappspringboot助农管理系统App》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功! 软件开发环境及开发工具: 开发语言:Java 后台框架:springboot、ssm 安卓…

解析MySQL生产环境CPU使用率过高的排查与解决方案

引言 在生产环境中,MySQL作为一个关键的数据库组件,其性能对整个系统的稳定性至关重要。然而,有时候我们可能会遇到MySQL CPU使用率过高的问题,这可能导致系统性能下降,应用页面访问减慢,甚至影响到用户体…

数据结构与算法——队列

概述 计算机科学中,queue 是以顺序的方式维护的一组数据集合,在一端添加数据,从另一端移除数据。添加的一端称为尾,移除的一端称为头。 功能 插入offer(value : E) : boolean  取值并移除poll() : E  取值peek() : E  判断…

Redis案例-微信抢红包

Redis案例-微信抢红包 1、业务描述 ​ 微信红包,一个人能发红包,红包的分发规则,红包能被几个人抢,超过24小时没有人领取自动退回原账户,红包详情页面有每个人的抢红包记录,包括金额大小和时间&#xff0…

统计学-R语言-7.3

文章目录 前言总体方差的检验一个总体方差的检验两个总体方差比的检验 非参数检验总体分布的检验正态性检验的图示法Shapiro-Wilk和K-S正态性检验总体位置参数的检验 练习 前言 本篇文章继续对总体方差的检验进行介绍。 总体方差的检验 一个总体方差的检验 在生产和生活的许多…

使用BootStrapBlazor组件搭建Bootstarp风格的Winform界面

项目地址https://gitee.com/zhang_jie_sc/my-blazor-winforms 1.安装Bootstrap.Blazor.Templates 模板 在power shell中输入dotnet new install Bootstrap.Blazor.Templates::7.6.1,安装7.6.1是因为版本8以后就要强制使用net8.0了,很多语法不一样&…

了解WPF控件:ToggleButton和Separator常用属性与用法(十三)

掌握WPF控件:熟练ToggleButton和Separator常用属性(十三) ToggleButton 一个按钮类UI元素,它的特点是拥有两种状态:选中(Checked)和未选中(Unchecked)。当用户单击Togg…

pve8.1 安装、创建centos7虚拟机及配置

之前创建虚拟机centos7时,硬盘分配太大了,做成模板后无法进行修改了,安装完pve8.1后,强迫症犯了重新创建一下顺便记录一下配置过程。由于目前centos7还是生产用的比较多的版本所以本次还是安装centos7.9版本。 一、下载镜像 下载…

使用 CDC MinIO 汇入端为 CockroachDB 保持持久数据

CockroachDB 数据库迅速崭露头角,作为一个坚韧且可扩展的分布式 SQL 数据库。它从其昆虫名字的坚持不懈中汲取灵感,即使面对硬件故障,CockroachDB 也能保证高可用性。其分布式架构横跨多个节点,类似于其昆虫原型的适应性。 凭借强…

dos攻击与ddos攻击的区别

①DOS攻击: DOS:中文名称是拒绝服务,一切能引起DOS行为的攻击都被称为dos攻击。该攻击的效果是使得计算机或网络无法提供正常的服务。常见的DOS攻击有针对计算机网络带宽和连通性的攻击。 DOS是单机于单机之间的攻击。 DOS攻击的原理&#…

JavaScript学习-原型和原型链

原型和原型链 示例代码 //创建一个Person类 class Person {constructor(name) {this.name name;}drink() {console.log(喝水);} } //创建一个Teacher类,继承Person class Teacher extends Person {constructor(name, subject) {super(name);this.subject subjec…

详细解读vcruntime140_1.dll修复的手段,如何快速解决vcruntime140_1.dll丢失问题

当出现“无法找到vcruntime140_1.dll”或程序“未能正常启动”时,这通常指示系统中缺失了一个关键文件:vcruntime140_1.dll。作为Visual C Redistributable组件的一部分,这个小文件在很多用Visual Studio编译的C程序运行时发挥着重要作用。解…

go语言(十八)---- goroutine

一、goroutine package mainimport ("fmt""time" )func main() {//用go创建承载一个形参为空,返回值为空的一个函数go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")//退出当前goroutinefmt…

P1042 [NOIP2003 普及组] 乒乓球 Java版最简单题解!

为什么说最简单,因为本人就是一个算法小白,只学过一点数据结构,打算备战蓝桥杯的,网上说备战蓝桥杯就去刷洛谷,早有听闻洛谷很难,今天一看算是真的被打醒了,对于小白是真的太难了。(;༎ຶД༎ຶ…

在Idea中使用git查看历史版本

idea查git历史 背景查看步骤总结 背景 有好几次同事到我电脑用idea查看git管理的历史记录,每次都说我的idea看不了历史版本,叫我到他电脑上去看,很晕,为什么,原来是我自己把显示历史文件的视图覆盖了,下面我们来一起学…

Python open函数

在Python编程中,open()函数是一个重要的文件操作函数,用于打开文件并进行读取、写入、追加等操作。本文将深入探讨open()函数的用法、语法、文件模式、示例代码,并探讨其在实际编程中的应用场景。 什么是open()函数? open()函数…

【阻塞队列】阻塞队列的模拟实现及在生产者和消费者模型上的应用

文章目录 📄前言一. 阻塞队列初了解🍆1. 什么是阻塞队列?🍅2. 为什么使用阻塞队列?🥦3. Java标准库中阻塞队列的实现 二. 阻塞队列的模拟实现🍚1. 实现普通队列🍥2. 实现队列的阻塞功…

Python.五.文件

1.文件读取的操作 1.文件的打开 open(name,mode,encoding) name:是要打开目标文件名的字符串,可以包含文件所在的具体路径 mode:设置打开文件的模式:只读 r 、写入 w 、追加 a encoding:编码格式 UTF-8 fopen("C:/test.txt"…

XSS_Labs靶场通关笔记

每一关的方法不唯一;可以结合源码进行分析后构造payload; 通关技巧(四步): 1.输入内容看源码变化; 2.找到内容插入点; 3.测试是否有过滤; 4.构造payload绕过 第一关 构造paylo…