转载自:http://www.wowotech.net/kernel_synchronization/why-memory-barrier-2.html

 

在上一篇why memory barriers文档中,由于各种原因,有几个章节没有翻译。其实所谓的各种原因总结出一句话就是还没有明白那些章节所要表达的内容。当然,对于一个真正的热爱钻研的linuxer,不理解的那些章节始终都是一块心病。终于,在一个月黑风高的夜晚,我发了一封邮件给perfbook的作者Paul,请其指点一二。果然是水平越高越平易近人,很快,大神回复了,给出了一些他的意见,大意就是不必纠结于具体的细节,始终focus在几个基本的规则上就OK了。受此鼓舞,我还是坚持把剩余章节翻译出来,于是形成了本文。

注:有些特定CPU architecture章节实在是无心翻译,暂时TODO吧。

 

七、Example Memory-Barrier Sequences

This section presents some seductive but subtly broken uses of memory barriers. Although many of them will work most of the time, and some will work all the time on some specific CPUs, these uses must be avoided if the goal is to produce code that works reliably on all CPUs. To help us better see the subtle breakage, we first need to focus on an ordering-hostile architecture.

这一小节我们会分析几个有意思的memory barrier的例子。尽管这些代码大部分时间都可以正常工作,并且其中有些在某些特定平台的CPU上可以一直正常工作,但是,如果我们的目标是星辰大海(平台无关代码,即在所有的CPU上都可以正确运行),那么还是有必要看看这些微妙的错误是如何发生的。我们先来看看ordering-hostile architecture这个概念。

1、Ordering-Hostile Architecture

A number of ordering-hostile computer systems have been produced over the decades, but the nature of the hostility has always been extremely subtle, and understanding it has required detailed knowledge of the specific hardware. Rather than picking on a specific hardware vendor, and as a presumably attractive alternative to dragging the reader through detailed technical specifications, let us instead design a mythical but maximally memory-ordering-hostile
computer architecture.

在过去的数十年里,许多ordering-hostile的计算系统被生产出来,对于这类计算机系统,memory order非常精妙,需要大量的硬件知识才能理解它。与其选择一个特定的硬件平台,把读者带入特定的一些技术细节,不如做点看起来更有意思的事情:我们自己设计一个对memory order约束最弱的CPU architecture。

This hardware must obey the following ordering constraints [McK05a, McK05b]:

  1. Each CPU will always perceive its own memory accesses as occurring in program order.
  2. CPUs will reorder a given operation with a store only if the two operations are referencing different locations.
  3. All of a given CPU’s loads preceding a read memory barrier (smp_rmb()) will be perceived by all CPUs to precede any loads following that read memory barrier.
  4. All of a given CPU’s stores preceding a write memory barrier (smp_wmb()) will be perceived by all CPUs to precede any stores following that write memory barrier.
  5. All of a given CPU’s accesses (loads and stores) preceding a full memory barrier (smp_mb()) will be perceived by all CPUs to precede any accesses following that memory barrier.

这种硬件必须服从下面的规则:

(1) 对于每个CPU而言,从它自己的角度看,其内存访问的顺序总是符合program order的。

(2) 如果CPU要对一个指定的操作(load或者store)和另外的一个store操作进行重排的话,那么一定要符合指定的条件:即这两个操作的memory地址是不能有重叠区域的。

(3) 如果program order是先load A然后load B,这样的操作在CPU 0上执行的时候,从其自己来看memory系统的变化,当然是load A,然后load B(请参考前面的第一条规则)。但是,系统中的其他其他CPU如何看待CPU 0的操作呢(想像所有的CPU都是趴在总线上的观察者,不断的观察memory的变化)?根据前面文章描述的知识,可以肯定的是load A和load B的顺序是无法保证的。如果增加了个read memory barrier,那结果可就不一样了。假设CPU 0执行的是load A,然后read memory barrier,最后load B,那么总线上的所有CPU,包括CPU 0自己,看到的内存操作顺序都是load A,然后load B。

(4) 如果系统中所有的CPU都是潜伏在总线上的观察者,不断的观察memory的变化。那么任意一个给定的系统中的CPU在执行store代码的时候,都可以被系统中的所有CPU感知(包括它自己)。如果给定CPU执行的代码被write memory barrier分成两段:wmb之前的store操作,wmb之后的store操作,那么系统中所有的CPU的观察结果都是一样的,wmb之前的store操作先执行完毕,wmb之后的store操作随后被执行。

(5) 某一个CPU执行的全功能内存屏障指令之前的memory access的操作(load or store),必定先被系统中所有的CPU感知到,之后系统中的所有CPU才看到全功能内存屏障指令之后的memory access的操作(load or store)的执行结果。

Imagine a large non-uniform cache architecture (NUCA) system that, in order to provide fair allocation of interconnect bandwidth to CPUs in a given node, provided per-CPU queues in each node’s interconnect interface, as shown in Figure C.8. Although a given CPU’s accesses are ordered as specified by memory barriers executed by that CPU, however, the relative order of a given pair of CPUs’ accesses could be severely reordered, as we will see.

一个示例性的Ordering-Hostile Architecture如下图所示:

nuca

这个系统被称为non-uniform cache architecture (NUCA) 的系统,为了公平的分配Interconnect到各个CPU之间的带宽,我们在Node和interconnect器件之间的接口设计了per cpu的Message queue。尽管,一个指定CPU的内存访问可以通过memory barrier对访问顺序进行约定,但是,一对CPU上的内存访问可以被完全打乱重排,下面我们会详细描述。

2、Example 1

Table C.2 shows three code fragments, executed concurrently by CPUs 0, 1, and 2. Each of “a”, “b”, and “c” are initially zero.

下表列出了CPU 0、CPU 1和CPU 2各自执行的代码片段,a,b,c变量的初始值都是0。

CPU 0CPU 1CPU 2

a = 1;
smp_wmb();
b = 1;

 


while (b == 0);
c = 1;

 

z = c;
smp_rmb();
x = a;
assert(z == 0 || x == 1);

Suppose CPU 0 recently experienced many cache misses, so that its message queue is full, but that CPU 1 has been running exclusively within the cache, so that its message queue is empty. Then CPU 0’s assignment to “a” and “b” will appear in Node 0’s cache immediately (and thus be visible to CPU 1), but will be blocked behind CPU 0’s prior traffic. In contrast, CPU 1’s assignment to “c” will sail through CPU 1’s previously empty queue. Therefore, CPU 2 might well see CPU 1’s assignment to “c” before it sees CPU 0’s assignment to “a”, causing the assertion to fire, despite the memory barriers.

我们假设CPU 0最近经历了太多cache miss,以至于它的message queue满了。CPU 1比较幸运,它操作的内存变量在cacheline中都是exclusive状态,不需要发送和node 1进行交互的message,因此它的message queue是空的。对于CPU0而言,a和b的赋值都很快体现在了NODE 0的cache中(CPU1也是可见的),不过node 1不知道,因为message都阻塞在了cpu 0的message queue中。与之相反的是CPU1,其对c的赋值可以通过message queue通知到node1。这样导致的效果就是,从cpu2的角度看,它先观察到了CPU1上的c=1赋值,然后才看到CPU0上的对a变量的赋值,这也导致了尽管使用了memory barrier,CPU2上仍然遭遇了assert fail。之所以会fail,主要是在cpu2上发生了当z等于1的时候,却发现x等于0。

 Therefore, portable code cannot rely on this assertion not firing, as both the compiler and the CPU can reorder the code so as to trip the assertion.

因此,可移植代码不能依赖assert总是succeed,编译器和CPU都会重排代码,导致assertion trigger。

3、 Example 2

Table C.3 shows three code fragments, executed concurrently by CPUs 0, 1, and 2. Both “a” and “b” are initially zero.

下表列出了CPU 0、CPU 1和CPU 2各自执行的代码片段,a,b变量的初始值都是0。

CPU 0CPU 1CPU 2

a = 1;

while (a == 0);

smp_mb(); 
b = 1;

 

y = b;
smp_rmb();
x = a;
assert(y == 0 || x == 1);

 

Again, suppose CPU 0 recently experienced many cache misses, so that its message queue is full, but that CPU 1 has been running exclusively within the cache, so that its message queue is empty. Then CPU 0’s assignment to “a” will appear in Node 0’s cache immediately (and thus be visible to CPU 1), but will be blocked behind CPU 0’s prior traffic. In contrast, CPU 1’s assignment to “b” will sail through CPU 1’s previously empty queue. Therefore, CPU 2 might well see CPU 1’s assignment to “b” before it sees CPU 0’s assignment to “a”, causing the assertion to fire, despite the memory barriers.

我们再次假设CPU0最近遇到太多cache miss,以至于它的message queue满了,而cpu1由于操作的变量cacheline都是exclusive状态,因此它的message queue是空的。同样的,CPU0上的对a的赋值会立刻被同在一个node中的CPU1观察到,但是,由于CPU0之前的一些协议交互塞满了其message queue,因此,对于node 0之外的CPUs则看不到CPU0对a的赋值。相反,CPU1的对b的赋值,由于其message queue是空的,因此,轻舟已过万重山,顺利到达其他node。对于CPU2而言,当执行assert的代码的时候,有可能执行的结果是y等于1并且x等于0,从而导致assertion fail。

In theory, portable code should not rely on this example code fragment, however, as before, in practice it actually does work on most mainstream computer systems.

理论上,上面的代码不是可移植代码,虽然在大部分的计算机系统上都能正常运行。

4、Example 3

Table C.4 shows three code fragments, executed concurrently by CPUs 0, 1, and 2. All variables are initially zero.

下表列出了CPU 0、CPU 1和CPU 2各自执行的代码片段,所有的变量的初始值都是0。

example3

Note that neither CPU 1 nor CPU 2 can proceed to line 5 until they see CPU 0’s assignment to “b” on line 3. Once CPU 1 and 2 have executed their memory barriers on line 4, they are both guaranteed to see all assignments by CPU 0 preceding its memory barrier on line 2. Similarly, CPU 0’s memory barrier on line 8 pairs with those of CPUs 1 and 2 on line 4, so that CPU 0 will not execute the assignment to “e” on line 9 until after its assignment to “a” is visible to both of the other CPUs. Therefore, CPU 2’s assertion on line 9 is guaranteed not to fire.

无论是CPU1还是CPU2,它们都不能越过smp_mb,直到观察到CPU0上的对b的赋值(在line3)。一旦CPU1和CPU2执行了smp_mb这条指令(line 4),这两个CPU都可以保证观察到CPU0上smb_wmb(line 2)之前的指令的执行结果。同样的,CPU0上在第8行的smp_mb(),和CPU1以及CPU2上的smp_mb(line 4)组成一对,可以保证CPU0不会执行e的赋值(line 9),直到其对a的赋值(line1)被CPU1和CPU2观察到。因此CPU2上assert不会fail。

 

八、 Memory-Barrier Instructions For Specific CPUs

Each CPU has its own peculiar memory-barrier instructions, which can make portability a challenge, as indicated by Table C.5. In fact, many software environments, including pthreads and Java, simply prohibit direct use of memory barriers, restricting the programmer to mutual-exclusion primitives that incorporate them to the extent that they are required. In the table, the first four columns indicate whether a given CPU allows the four possible combinations of loads and stores to be reordered. The next two columns indicate whether a given CPU allows loads and stores to be reordered with atomic instructions.

The seventh column, data-dependent reads reordered, requires some explanation, which is undertaken in the following section covering Alpha CPUs. The short version is that Alpha requires memory barriers for readers as well as updaters of linked data structures. Yes, this does mean that Alpha can in effect fetch the data pointed to before it fetches the pointer itself, strange but true. Please see: http://www.openvms.compaq.com/wizard/wiz_2637.html if you think that I am just making this up. The benefit of this extremely weak memory model is that Alpha can use simpler cache hardware, which in turn permitted higher clock frequency in Alpha’s heyday.

The last column indicates whether a given CPU has a incoherent instruction cache and pipeline. Such CPUs require special instructions be executed for self-modifying code.

Parenthesized CPU names indicate modes that are architecturally allowed, but rarely used in practice.

每种CPU都有自己特有的memory barrier指令,这也导致了想要写可移植代码比较困难。具体的信息可以参考下面的表格:

cpu mb

实际上,很多的软件环境,例如pthread、Java简单而粗暴的禁止了对memory barrier的直接使用,当然,对于有需要的场景,程序员也不能直接使用memory barrier,而是使用互斥原语,而互斥原语实际上是隐含了对memory barrier的使用。上面的表格描述了各种CPU对memory reorder行为的支持情况,第一列是CPU信息,后面的列是各种reorder的case,包括:

(1)load和store的4种组合。memory 访问指令就只有2种(对于RISC而言),load和store,运用简单的排列组合知识就可以知道有4种组合情况,load after store,load after load, store after load和store after store。

(2)内存访问是否可以和原子操作进行reorder。这类case有两种,一是是否允许store操作和原子操作进行reorder,另外一个是是否允许load操作和原子操作进行reorder。

(3)是否对有数据依赖关系的读操作进行重排。这个在绝大多数的CPU上都不支持,除了Alpha处理器。首先我们先解释一下什么是“data-dependent read”,请看下表:

CPU 0CPU 1
给一个结构体S的各个成员赋值

将指针变量ps的值设定为结构体S的地址
load  ps

load S结构体的值

对于CPU1而言,读取指针变量ps的值和读取S结构体的值是两个有依赖关系的load操作,如果不load ps,其实是无法获取S结构体的值的,这也就是传说中的“data-dependent read”。对于CPU 0上的写入操作,两个操作是向不同的memory地址中写入数据,因此没有依赖性。

为了合理约定memory order,我们需要添加一些memory barrier的代码:

CPU 0CPU 1
给一个结构体S的各个成员赋值
write memory barrier
将指针变量ps的值设定为结构体S的地址
load  ps
read memory barrier
load S结构体的值

CPU1上的load ps操作要么看到旧指针,要么看到新指针。如果看到新的指针说明CPU0上的write memory barrier之后的赋值操作已经完成,在这样的条件下,CPU1上的rmb之后的load操作必然看到CPU0上wmb之前对结构体S成员赋的新值。由于数据的依赖性,对于大部分的CPU而言,去掉读一侧CPU1上的rmb也是没有问题的(使用单侧的memory barrier),CPU的硬件保证了读侧的load顺序。不幸的是对于Alpha而言,必须使用成对的memory barrier,即读的一侧需要使用阻止read depends类型的memory barrier。如果你觉得我在鬼扯,请去http://www.openvms.compaq.com/wizard/wiz_2637.html 下载Alpha的手册来仔细阅读。之所以采用了这么弱的memory model是为了在Alpha上使用更简单的cache硬件,而更简单的硬件使得Alpha可以采用更高的主频,从而让计算机跑的更快。

(4)最后一列是说明该CPU是否支持独立的icache和pipeline(这里的独立是相对于data cache而言,原文是incoherent,这里可能理解有误)。这种CPU需要为self-modifying的代码提供特殊的memory barrier指令

被括号围起来的CPU ARCH说明CPU虽然支持该mode,不过在实际中很少使用。

The common “just say no” approach to memory barriers can be eminently reasonable where it applies, but there are environments, such as the Linux kernel, where direct use of memory barriers is required. Therefore, Linux provides a carefully chosen least-common-denominator set of memory-barrier primitives, which are as follows:

在某些场合下,对memory barrier说“不”也是非常合理的,毕竟这个东东理解起来还是比较困难的,但是,有些场合,例如linux kernel中,有直接使用memory barrier的需求,就不能just say no了。对于linux kernel而言,它需要支持多种CPU体系结构的memory barrier的操作。虽然各自CPU arch有自己的memory barrier的设计,但是所有的CPU arch可以找到一个最小的集合,在这个集合中,每一个memory barrier的操作都可以被各个CPU arch所支持,它们是:

(1) smp_mb(): “memory barrier” that orders both loads and stores. This means that loads and stores preceding the memory barrier will be committed to memory before any loads and stores following the memory barrier.对load和store都有效的全功能memory barrier,执行了该memory barrier指令的CPU可以保证smp_mb之前的load和store操作将先于该指令之后的load或者store操作被提交到存储系统。

(2) smp_rmb(): “read memory barrier” that orders only loads.只规定load操作的memory barrier。

(3) smp_wmb(): “write memory barrier” that orders only stores.只规定store操作的memory barrier。

(4) smp_read_barrier_depends() that forces subsequent operations that depend on prior operations to be ordered. This primitive is a no-op on all platforms except Alpha. 如果前后两个memory access有依赖关系(例如前一个的操作是取后一个要操作memory的地址),那么smp_read_barrier_depends()这个memory barrier原语可以用来规定这两个有依赖关系的内存操作顺序。当然,除了Alpha,在其他的CPU上,该原语都是空。

(5) mmiowb() that forces ordering on MMIO writes that are guarded by global spinlocks. This primitive is a no-op on all platforms on which the memory barriers in spinlocks already enforce MMIO ordering. The platforms with a
non-no-op mmiowb() definition include some (but not all) IA64, FRV, MIPS, and SH systems. This primitive is relatively new, so relatively few drivers take advantage of it. mmiowb主要用于约束被spin lock保护的MMIO write操作顺序。当然,有些CPU architecture平台中,spinlock中的memory barrier操作已经保证了MMI的写入顺序,那么这个宏是空的。mmiowb是空的CPU architecture包括(但不限于)IA64, FRV, MIPS, and SH。这个原语比较新,因此在比较新的driver会使用它。

The smp_mb(), smp_rmb(), and smp_wmb() primitives also force the compiler to eschew any optimizations that would have the effect of reordering memory optimizations across the barriers. The smp_read_barrier_depends() primitive has a similar effect, but only on Alpha CPUs. See Section 14.2 for more information on use of these primitives.These primitives generate code only in SMP kernels, however, each also has a UP version (mb(), rmb(), wmb(), and read_barrier_depends(), respectively) that generate a memory barrier even in UP kernels. The smp_ versions should be used in most cases. However, these latter primitives are useful when writing drivers, because MMIO accesses must remain ordered even in UP kernels. In absence of memory-barrier instructions, both CPUs and compilers would happily rearrange these accesses, which at best would make the device act strangely, and could crash your kernel or, in some cases, even damage your hardware.

smp_mb(), smp_rmb(), 和 smp_wmb() 原语除了指导CPU的工作(规定cpu提交到到存储系统的操作顺序),它们对编译器也有作用(linux中的优化屏障barrier()),这些原语阻止了编译器为了优化而重排内存访问顺序。smp_read_barrier_depends有同样的效果,但是仅仅适用在Alpha处理器上。想要知道更多的关于这些原语使用的知识,请参考14.2章节。上面的这些smp_xxx的原语主要用在SMP的场合,在SMP的场景下会生成具体的memory barrier指令代码,在UP场合下,它们不会生成代码(仅仅是一个优化屏障而已)。smp_xxx的原语有对应的UP版本的memory barrier原语(mb(), rmb(), wmb(), 和read_barrier_depends()),这些UP版本的原语无论是SMP还是UP场合,都会生成具体的memory barrier指令代码。虽然大部分场景都是使用smp_xxx版本的memory barrier原语,但是,对于驱动工程师,由于需要规定MMIO的顺序(不仅适用SMP,也适用UP),因此,UP版本的memory barrier原语也经常使用。如果缺少了这些memory barrier原语,那么CPU和编译器都可以愉快的按照其自己的想法来对memory access顺序进行重排,而这样的行为轻则让设备行为异常,或者内核crash,重则造成硬件的损伤。

So most kernel programmers need not worry about the memory-barrier peculiarities of each and every CPU, as long as they stick to these interfaces. If you are working deep in a given CPU’s architecture-specific code, of course, all bets are off.
Furthermore, all of Linux’s locking primitives (spinlocks, reader-writer locks, semaphores, RCU, ...) include any needed barrier primitives. So if you are working with code that uses these primitives, you don’t even need to worry about Linux’s memory-ordering primitives.

因此,只要熟悉了linux kernel提供的architecture无关的memory barrier接口API(原语),那么大部分的内核开发者并不需要担心特定CPU上的memory barrier操作。当然,如果的你就是撰写特定CPU上的代码,上面的话不作数。

生活多么美好,开发kernel而不需要深入特定CPU的细节,稍等,还有令你心情更加愉悦的事情。其实内核中的所有的互斥原语(spinlocks, reader-writer locks, semaphores, RCU, ...)也都隐含了它们需要的memory barrier原语,因此,如果你的工作是直接使用这些互斥原语,那么你也根本不需要“鸟”那些linux提供的memroy barrier原语。

That said, deep knowledge of each CPU’s memory-consistency model can be very helpful when debugging, to say nothing of when writing architecture-specific code or synchronization primitives.
Besides, they say that a little knowledge is a very dangerous thing. Just imagine the damage you could do with a lot of knowledge! For those who wish to understand more about individual CPUs’ memory consistency models, the next sections describes those of the most popular and prominent CPUs. Although nothing can replace actually reading a given CPU’s documentation, these sections give a good overview.

行文至此,你肯定忍不住提出疑问:那为何我们还要专门描写一个特定CPU上memory barrier的章节呢?生活固然美好,但是,深入掌握每一个CPU的内存一致性模型对调试内核代码的时候帮助很大。当然,如果你是需要些特定CPU的代码或者同步原语,那更不必说了,该cpu的memory order和memory barrier的相关知识必须拿下。

除此之外,一知半解(掌握一点memory barrier的知识)是一个很危险的事情,想象一下,如果你掌握了大量的memory barrier知识所带来的破坏力(作者这里要表达什么我也搞不清楚,如果有理解的帮忙提点一下)。虽然有些工程师使用linux提供的原语快乐的工作着,但是,总是有些工程师期望能够理解更多关于各种CPU上的memory order和memory barrier相关的知识,我们下面的小节就是为你们准备的。下面的小节将描述各种主流CPU的memory barrier指令。需要注意的是:我们这里是概述,如果想要了解更多,可以下载具体CPU的文档来仔细研读。

1、 Alpha

It may seem strange to say much of anything about a CPU whose end of life has been announced, but Alpha is interesting because, with the weakest memory ordering model, it reorders memory operations the most aggressively. It therefore has defined the Linuxkernel memory-ordering primitives, which must work on all CPUs, including Alpha. Understanding Alpha is therefore surprisingly important to the Linux kernel hacker.

看起来现在讨论一个已经宣布死亡的CPU是有点奇怪,不过Alpha是一个非常有意思的产品,因为这款CPU有着世界上最弱的内存访问顺序模型。为了性能,Alpha处理器不择手段,丧心病狂的对内存操作进行重排。由于linux内核必须在包括Alpha在内的各种处理器上工作,因此linux下的memory barrier的原语其实是根据这款CPU而定义的。因此,对于那些内核狂热分子,理解Alpha是有非常重要的意义的。

The difference between Alpha and the other CPUs is illustrated by the code shown in Figure C.9. This smp_wmb() on line 9 of this figure guarantees that the element initialization in lines 6-8 is executed before the element is added to the list on line 10, so that the lock-free search will work correctly. That is, it makes this guarantee on all CPUs except Alpha.

Alpha has extremely weak memory ordering such that the code on line 20 of Figure C.9 could see the old garbage values that were present before the initialization on lines 6-8.

Alpha和其他处理器的不同之处可以通过下面的代码描述:

1 struct el *insert(long key, long data)
2 {
3 struct el *p;
4 p = kmalloc(sizeof(*p), GFP_ATOMIC);
5 spin_lock(&mutex);
6 p->next = head.next;
7 p->key = key;
8 p->data = data;
9 smp_wmb();
10 head.next = p;
11 spin_unlock(&mutex);
12 }
13
14 struct el *search(long key)
15 {
16 struct el *p;
17 p = head.next;
18 while (p != &head) {
19 /* BUG ON ALPHA!!! */
20 if (p->key == key) {
21 return (p);
22 }
23 p = p->next;
24 };
25 return (NULL);
26 }

在上面的代码片段中,第9行的smp_wmb可以保证6~8行的结构体成员初始化代码先执行,之后才执行第10行的代码,也就是讲结构体加入链表的代码。只有这样,才能保证没有使用lock的链表搜索操作的正确性。上面的代码在任何的处理器上都可以正确运行,除了Alpha。

Alpha处理器有着非常非常弱的内存访问顺序模型,这使得上面第20行代码的通过指针load key成员的操作可以看到旧的垃圾数据而不是6~8行的初始化的新值。

Figure C.10 shows how this can happen on an aggressively parallel machine with partitioned caches, so that alternating caches lines are processed by the different partitions of the caches. Assume that the list header head will be processed by cache bank 0, and that the new element will be processed by cache bank 1. On Alpha, the smp_wmb() will guarantee that the cache invalidates performed by lines 6-8 of Figure C.9 will reach the interconnect before that of line 10 does, but makes absolutely no guarantee about the order in which the new values will reach the reading CPU’s core. For example, it is possible that the reading CPU’s cache bank 1 is very busy, but cache bank 0 is idle. This could result in the cache invalidates for the new element being delayed, so that the reading CPU gets the new value for the pointer, but sees the old cached values for the new element. See the Web site called out earlier for more information, or, again, if you think that I am just making all this up.

怎么会这样呢?我们通过下面这幅图片向您展示这个问题是如何发生的:

alpha

上图展示的是一个使用分区cache的并行计算系统,这种系统的cache分成两个bank,不同的cache bank处理不同的cacheline。例如A cache line被bank 0处理,那么A+1的cache line会被bank 1处理。假设链表头head由cache bank 0来处理,而具体结构体的成员数据由bank 1来处理。在Alpha处理器上,smp_wmb() 可以确保上面6~8行的结构体成员初始化代码产生的invalidate message要早于第10行程序产生的invalidate message到达互连器件,但是,对于发起读操作那一侧的CPU core而言,并不能保证这些新值可以到达reading CPU core。例如说有可能发起读操作那一侧的CPU 的cache bank 1非常忙,但是bank0比较闲,这也就导致了结构体成员的invalidate message被推迟了,在这种情况下,CPU读到了新的指针数值,但是却看到了旧的数据。再强调一次,如果你觉得我在鬼扯,请去网上下载Alpha的手册来仔细阅读。

One could place an smp_rmb() primitive between the pointer fetch and dereference. However, this imposes unneeded overhead on systems (such as i386, IA64, PPC, and SPARC) that respect data dependencies on the read side. A smp_read_barrier_depends() primitive has been added to the Linux 2.6 kernel to eliminate overhead on these systems. This primitive may be used as shown on line 19 of Figure C.11.

当然,你也可以在获取结构体指针代码和通过指针访问结构体成员代码之间插入一个smp_rmb原语,但是,对于某些系统(例如i386、IA64、PPC和SPARC)而言,会带来不必要的开销。因为,在这些系统中,两个有依赖关系的load操作是可以保证访问顺序的,即便没有smp_rmb原语,cpu也不会大量其memory access的顺序。为了解决这个问题,从2.6的内核开始,新增一个smp_read_barrier_depends()的原语,具体代码如下:

1 struct el *insert(long key, long data)
2 {
3 struct el *p;
4 p = kmalloc(sizeof(*p), GFP_ATOMIC);
5 spin_lock(&mutex);
6 p->next = head.next;
7 p->key = key;
8 p->data = data;
9 smp_wmb();
10 head.next = p;
11 spin_unlock(&mutex);
12 }
13
14 struct el *search(long key)
15 {
16 struct el *p;
17 p = head.next;
18 while (p != &head) {
19 smp_read_barrier_depends();
20 if (p->key == key) {
21 return (p);
22 }
23 p = p->next;
24 };
25 return (NULL);
26 }

对于大部分CPU而言,smp_read_barrier_depends()是空操作,因此不会影响性能,在Alpha上,smp_read_barrier_depends()又可以保证有依赖关系的load的操作顺序,因此保证了程序逻辑上的正确性。

It is also possible to implement a software barrier that could be used in place of smp_wmb(), which would force all reading CPUs to see the writing CPU’s writes in order. However, this approach was deemed by the Linux community to impose excessive overhead on extremely weakly ordered CPUs such as Alpha. This software barrier could be implemented by sending inter-processor interrupts (IPIs) to all other CPUs. Upon receipt of such an IPI, a CPU would execute a memory-barrier instruction, implementing a memory-barrier shootdown. Additional logic is required to avoid deadlocks. Of course, CPUs that respect data dependencies would define such a barrier to simply be smp_wmb(). Perhaps this decision should be revisited in the future as Alpha fades off into the sunset.

The Linux memory-barrier primitives took their names from the Alpha instructions, so smp_mb() is mb, smp_rmb() is rmb, and smp_wmb() is wmb. Alpha is the only CPU where smp_read_barrier_depends() is an smp_mb() rather than
a no-op.

增加一个smp_read_barrier_depends的原语带来了一定的复杂度,毕竟,arch无关的memory barrier接口多了一个。基本上这种接口的原则就是:如果一个接口可以解决问题的不要使用2个。当然,社区的人不是没有想过这个问题,他们也曾经提出一种software barrier方案可以不修改读CPU一侧的行为,同时保证了所有的读一侧的CPUs看到正确的写入侧的CPU的写入顺序。这个方案是这样的:

(1)对于其他CPU而言,software barrier就是smp_wmb

(2)对于Alpha而言,software barrier是送往其他CPU的IPI中断。收到该中断的CPU都执行一个memory barrier指令

不过由于这个方案对于类似Alpha这样采用比较弱内存顺序模型的CPU而言,开销太大,因此,linux社区否定了这个方案。不过,随着Alpha处理器的衰落,未来这个方案可以拿出来再讨论一下。

linux kernel中的memory barrier原语的命名来自Alpha处理器的memory barrier汇编指令,smp_mb()就是mb,smp_rmb()就是rmb,smp_wmb()就是wmb。Alpha处理器是唯一定义smp_read_barrier_depends() 原语是smp_mb() 的处理器,其他处理上,smp_read_barrier_depends() 原语没有任何操作。

2、AMD64

TODO

3、ARMV7-A/R

The ARM family of CPUs is extremely popular in embedded applications, particularly for power-constrained applications such as cellphones. There have nevertheless been multiprocessor implementations of ARM for more than five years. Its memory model is similar to that of Power (see Section C.7.6, but ARM uses a different set of memorybarrier instructions [ARM10]:

(1) DMB (data memory barrier) causes the specified type of operations to appear to have completed before any subsequent operations of the same type. The “type” of operations can be all operations or can be restricted to only writes (similar to the Alpha wmb and the POWER eieio instructions). In addition, ARM allows cache coherence to have one of three scopes: single processor, a subset of the processors (“inner”) and global (“outer”).

(2)DSB (data synchronization barrier) causes the specified type of operations to actually complete before any subsequent operations (of any type) are executed. The “type” of operations is the same as that of DMB. The DSB instruction was called DWB (drain write buffer or data write barrier, your choice) in early versions of the ARM architecture.

(3)ISB (instruction synchronization barrier) flushes the CPU pipeline, so that all instructions following the ISB are fetched only after the ISB completes. For example, if you are writing a self-modifying program (such as a JIT), you should execute an ISB after between generating the code and executing it.

ARM处理器广泛应用在嵌入式设备中,特别是对功耗要求比较高的应用场景,例如手机。然而,只是在最近5年才除了了支持多核架构的ARM CPU。ARM的内存模型类似Power处理器(参考本章第六小节),不过ARM使用了不同的memory barrier指令,具体如下:

(1)DMB (data memory barrier) 。DMB指令可以在指定的作用域内(也就是shareability domain啦),约束指定类型的内存操作顺序。更详细的表述是这样的,从指定的shareability domain内的所有观察者角度来看,CPU先完成内存屏障指令之前的特定类型的内存操作,然后再完成内存屏障指令之后的内存操作。DMB指令需要提供两个参数:一个shareability domain,另外一个是access type。具体的内存操作类型(access type)可能是:store或者store + load。如果只是约束store操作的DMB指令,其效果类似Alpha处理器的wmb,或者类似POWER处理器的eieio指令。shareability domain有三种:单处理器、一组处理器(inner)和全局(outer)。

(2)DSB(data synchronization barrier)。DSB和DMB不一样,DSB更“狠”一些,同时对CPU的性能杀伤力更大一些。DMB是“看起来”如何如何,DSB是真正的对指令执行的约束。也就是说,首先执行完毕DSB之前的指定的内存访问类型的指令,等待那些指定类型的内存访问指令执行完毕之后,对DSB之后的指定类型内存访问的指令才开始执行。具体DSB的参数和DMB类似,这里就不赘述了。顺便一提的是,在ARM早期版本中,DSB被称为DWB(全称是drain write buffer 或者 data write barrier都OK,随你选择)。不过DWB并不能完全反应其功能,因为这条指令需要同步cache、TLB、分支预测以及显式的load/store内存操作,由此可见,DSB是一个更合适的名字。

(3)ISB (instruction synchronization barrier)。就对CPU的性能杀伤力而言,如果说DSB是原子弹,那么ISB杀更大,是属于氢弹了。ISB会flush cpu pipeline(也就是将指令之前的所有stage的pipeline都设置为无效),这会导致ISB指令之后的指令会在ISB指令完成之后,重新开始取指,译码……,因此,整个CPU的流水线全部会重新来过。可以举一个具体的应用例子:如果你要写一个自修改的程序(该程序会修改自己的执行逻辑,例如JIT),那么你需要在代码生成和执行这些生成的代码之间插入一个ISB。

None of these instructions exactly match the semantics of Linux’s rmb() primitive, which must therefore be implemented as a full DMB. The DMB and DSB instructions have a recursive definition of accesses ordered before and after the barrier, which has an effect similar to that of POWER’s cumulativity.

这些指令并不能精准的命中linux kernel中的rmb()原语,因此,对于ARM处理器,其rmb世界上是用mb(即全功能memory barrier)来实现的。在约束内存访问顺序上,DMB和DSB指令有一个递进的效果(DSB强于DMB),其概念类似POWER处理器上的cumulativity。

ARM also implements control dependencies, so that if a conditional branch depends on a load, then any store executed after that conditional branch will be ordered after the load. However, loads following the conditional branch will not be guaranteed to be ordered unless there is an ISB instruction between the branch and the load. Consider the following example:

程序的执行流中难免会遇到控制依赖关系(control dependencies),例如:在A指令执行结果满足某些条件的情况下,才执行B指令。ARM是这样处理控制依赖关系的:如果条件调整指令依赖某个load操作,那么任何该条件跳转指令之后的store操作将会在load操作之后完成(也就是说,control dependencies其效果类似smp_mb)。需要注意的是,在条件跳转指令之后的load操作没有这个限制,我们可以参考下面的代码:

1 r1 = x;
2 if (r1 == 0)
3 nop();
4 y = 1;
5 r2 = z;
6 ISB();
7 r3 = z;

In this example, load-store control dependency ordering causes the load from x on line 1 to be ordered before the store to y on line 4. However, ARM does not respect load-load control dependencies, so that the load on line 1 might well happen after the load on line 5. On the other hand, the combination of the conditional branch on line 2 and the ISB instruction on line 6 ensures that the load on line 7 happens after the load on line 1. Note that inserting an additional ISB instruction somewhere between lines 3 and 4 would enforce ordering between lines 1 and 5.

在这个例子中,load-store类型的控制依赖关系可以保证第四行的store y在第一行的load x之后执行。不过,ARM处理器对load-load类型的控制依赖关系并没有施加任何的内存访问顺序的保证,因此,第五行的load z操作可能先于第一行的load x执行。另一方面,结合第二行的条件跳转以及第六行的ISB指令,可以保证第七行的load z的操作在第一行的load x之后执行。请注意:在第三行和第四行之间插入额外的ISB可以确保第一行的load x和第五行load z之间的操作顺序。

4、IA64

IA64 offers a weak consistency model, so that in absence of explicit memory-barrier instructions, IA64 is within its rights to arbitrarily reorder memory references [Int02b]. IA64 has a memory-fence instruction named mf, but also has “half-memory fence” modifiers to loads, stores, and to some of its atomic instructions [Int02a]. The acq modifier prevents subsequent memory-reference instructions from being reordered before the acq, but permits prior memory-reference instructions to be reordered after the acq, as fancifully illustrated by Figure C.12. Similarly, the rel modifier prevents prior memory-reference instructions from being reordered after the rel, but allows subsequent memory-reference instructions to be reordered before the rel.

IA64提供了较弱的一致性模型,因此,在缺少memory barrier指令的情况下,IA64可以在许可范围内任意的重排内存的访问顺序。IA64提供了一条叫做mf的指令,此外还提供了支持half-memory fence功能的load、store以及一些原子操作。acq这个修饰符阻止了后面的内存访问指令越过acq修饰那条指令之前执行,不过,acq并不阻止前面的内存访问指令在acq修饰那条指令之后执行。具体可以参考下图:

acq

LD,  ACQ是一条隐含half-memory fence功能的load操作,该指令之前的内存操作可以delay至该指令之后执行,反之则不可以。类似的,还有一个rel的修饰符,也是提供了支持half-memory fence功能,不同的是rel是约束rel修饰的那条指令之前的内存访问操作不得越过该指令之后执行。

These half-memory fences are useful for critical sections, since it is safe to push operations into a critical section, but can be fatal to allow them to bleed out. However, as one of the only CPUs with this property, IA64 defines Linux’s semantics of memory ordering associated with lock acquisition and release.

half-memory fences应用在临界区上是非常合适的,将临界区的代码框在acq和rel定义的区域内,这样,临界区的代码的内存访问操作不会溢出。做为唯一一个支持这个属性的CPU(其实不是唯一的,ARMv8中也支持了,呵呵),IA64定义了linux中lock acquisition和release的在内存访问顺序上的语义。

The IA64 mf instruction is used for the smp_rmb(), smp_mb(), and smp_wmb() primitives in the Linux kernel. Oh, and despite rumors to the contrary, the “mf” mnemonic really does stand for “memory fence”.

对于IA64这个CPU architecture而言,linux中的smp_rmb(), smp_mb(), 和 smp_wmb() 这三个原语都是对应mf汇编指令。有些关于mf的谣言,不过实际上,mf就是memory fence的简写。

Finally, IA64 offers a global total order for “release” operations, including the “mf” instruction. This provides the notion of transitivity, where if a given code fragment sees a given access as having happened, any later code fragment will also see that earlier access as having happened. Assuming, that is, that all the code fragments involved correctly use memory barriers.

这段不是非常理解,TODO。

5、PA-RISC

Although the PA-RISC architecture permits full reordering of loads and stores, actual CPUs run fully ordered [Kan96]. This means that the Linux kernel’s memory-ordering primitives generate no code, however, they do use the gcc memory attribute to disable compiler optimizations that would reorder code across the memory barrier.

PA-RISC处理器保证了load和store的顺序,也就是说,实际上该CPU在执行程序的时候,内存访问是完全符合programmer order的,这也就是意味着在linux kernel的所有memory barrier的原语都是空的,没有任何代码。当然,必要的优化屏障还是需要的。

6、POWER / PowerPC

TODO

7、SPARC RMO, PSO, and TSO

TODO

8、x86

TODO

9、zSeries

TODO

 

九、Are Memory Barriers Forever?

There have been a number of recent systems that are significantly less aggressive about out-of-order execution in general and re-ordering memory references in particular. Will this trend continue to the point where memory barriers are a thing of the past?
The argument in favor would cite proposed massively multi-threaded hardware architectures, so that each thread would wait until memory was ready, with tens, hundreds, or even thousands of other threads making progress in the meantime. In such an architecture, there would be no need for memory barriers, because a given thread would simply wait for all outstanding operations to complete before proceeding to the next instruction. Because there would be potentially thousands of other threads, the CPU would be completely utilized, so no CPU time would be wasted.

在最近几个计算机系统上出现这样的现象:这些系统在out-of-order excution方面变得没有那么激进,特别是在对内存访问的重排方面。这些变化会不会导致memory barrier变成一个仅仅能从计算机历史找到的名词呢?

同意memory barrier退出历史舞台的那些人会认为目前硬件进入multi-threaded时代(注意这里的thread不是软件的thread),并且硬件线程数是越来越多,即便是一个线程上等待内存操作而暂停工作,但是系统中还有10个,几百个甚至上千其他的线程在继续前进。在这样的CPU架构下,根本没有memory barrier存在的必要了,因为对于任何一个线程,它都是等待该指令的操作完成之后,然后处理下一条指令。由于整个CPU系统存在数千个硬件线程可以使用,因此也没有浪费CPU的资源。

The argument against would cite the extremely limited number of applications capable of scaling up to a thousand threads, as well as increasingly severe realtime requirements, which are in the tens of microseconds for some applications. The realtimeresponse requirements are difficult enough to meet as is, and would be even more difficult to meet given the extremely low single-threaded throughput implied by the massive multi-threaded scenarios.

反对的人认为这将大大限制了应用程序的数目。同时,如果仅仅依靠硬件多线程,应用程序很难扩展到上千个线程(这里的线程是软件线程)。此外,也会大大增加实时响应时间,在有些应用中,甚至是几十个微妙的delay。实时系统响应时间的需求本来就很难满足,如果在multi-thread硬件情况下,single-thread的througput非常低,这时候响应时间更加难以符合实时系统的需要。

Another argument in favor would cite increasingly sophisticated latency-hiding hardware implementation techniques that might well allow the CPU to provide the illusion of fully sequentially consistent execution while still providing almost all of the performance advantages of out-of-order execution. A counter-argument would cite the increasingly severe power-efficiency requirements presented both by battery-operated devices and by environmental responsibility.
Who is right? We have no clue, so are preparing to live with either scenario.

还有另外的论点也是支持干掉memory barrier,这种观点认为虽然processor和memory的速度有差异,但是复杂度不断增加的latency-hiding硬件实现技术最终可以达成这样的效果:CPU可以提供一个完全顺序执行的假象,同时又不失乱序执行的性能优势。反对的声音认为这会增加功耗,不适合那些使用电池供电的设备,同时也不环保。

谁对谁错?我们也不知道,也许我们两种场景都会遇到。

 

十、Advice to Hardware Designers

There are any number of things that hardware designers can do to make the lives of software people difficult. Here is a list of a few such things that we have encountered in the past, presented here in the hope that it might help prevent future such problems:

硬件设计者如果设计思考不周,的确是有能力让软件工程师的生活变得异常的悲摧,做为一名软件工程师,在过去的工作中的确是遇到了一些这样的案例,我把它们罗列下来,希望能够防止其他硬件工程师犯同样的错误:

1. I/O devices that ignore cache coherence.

IO设备忽略了缓存一致性

This charming misfeature can result in DMAs from memory missing recent changes to the output buffer, or, just as bad, cause input buffers to be overwritten by the contents of CPU caches just after the DMA completes. To make your system work in face of such misbehavior, you must carefully flush the CPU caches of any location in any DMA buffer before presenting that buffer to the I/O device. Similarly, you need to flush the CPU caches of any location in any DMA buffer after DMA to that buffer completes. And even then, you need to be very careful to avoid pointer bugs, as even a misplaced read to an input buffer can result in corrupting the data input!

硬件工程师在设计I/O device的时候可能会build-in一个DMA控制器来辅助设备和memory之间的数据搬移。假设是从memory中搬移数据到设备上,假设硬件设计者不考虑cache coherence,那么DMA在搬运input memory的时候,将不能体现其最新的数据值(最新的在cache中)。在从设备搬移数据到memory中的时候会更糟糕,因为有可能会被CPU cache中的数据覆盖掉。在这种情况下,如果想让系统可以正常运作,你必须非常小心的操作DMA buffer,例如在DMA开始读取DMA buffer之前首先flush这段buffer的cache。即便如此,你仍然需要非常仔细,避免出现指针错误,因为即便是一个对DMA buffer错位的读操作都可以导致输入数据的破坏。

2. External busses that fail to transmit cache-coherence data.

外部总线无法发送cache-coherence数据

This is an even more painful variant of the above problem, but causes groups of devices—and even memory itself—to fail to respect cache coherence. It is my painful duty to inform you that as embedded systems move to multicore architectures, we will no doubt see a fair number of such problems arise. Hopefully these problems will clear up by the year 2015.

这是上一个问题的变种,而且更加令人痛苦。这个错误会导致一组设备,甚至是memory自己无法遵守cache coherence。因为痛苦的经历过,所以有责任告诉你,在从嵌入式系统转移的multicore体系结构的时候,毫无疑问,我们会遇到大量的这样的问题,希望这些问题能在2015年内解决掉。

3. Device interrupts that ignore cache coherence.

设备中断场景中忘记考虑cache coherence

This might sound innocent enough — after all, interrupts aren’t memory references, are they? But imagine a CPU with a split cache, one bank of which is extremely busy, therefore holding onto the last cacheline of the input buffer. If the corresponding I/O-complete interrupt reaches this CPU, then that CPU’s memory reference to the last cache line of the buffer could return old data, again resulting in data corruption, but in a form that will be invisible in a later crash dump. By the time the system gets around to dumping the offending input buffer, the DMA will most likely have completed.

这个错误听起来有些无辜,毕竟中断不是内存引用,不是吗?不过,设想一下,CPU使用了分bank的缓存机制,一个bank非常繁忙,因此,cpu无法将最新的数据写入cacheline。如果在这个时候,I/O完成的中断到达该CPU,然后该CPU会访问buffer中的数据,但是得到旧的值,从而导致data corruption,更可怕的是在随后的system crash的时候,你并不知道之前在interrupt handler中从buffer中copy数据发生了错误。当系统crash的时候再dump 恼人的输入的DMA buffer,DMA操作早已经完成了。

4. Inter-processor interrupts (IPIs) that ignore cache coherence.

IPI忘记考虑缓存一致性

This can be problematic if the IPI reaches its destination before all of the cache lines in the corresponding message buffer have been committed to memory.

正确的顺序应该是首先将对应message buffer中的cache line提交到内存,然后指定的CPU收到IPI中断,如果顺序反了就会存在问题。

5. Context switches that get ahead of cache coherence.

没有处理缓存一致性就进行了上下文切换

If memory accesses can complete too wildly out of order, then context switches can be quite harrowing. If the task flits from one CPU to another before all the memory accesses visible to the source CPU make it to the destination CPU, then the task could easily see the corresponding variables revert to prior values, which can fatally confuse most algorithms.

如果内存访问操作被严重的乱序执行,那么上下文切换会处理一些令人烦恼的问题。比如说一个task从一个CPU迁移到另外一个CPU上执行,虽然完成了切换,但是source CPU可见的内存操作在destination CPU上不可见,在这种情况下,执行程序很容易就看到这样的怪事:明明已经读到新值了,但是再次访问同样的内存,得到的确是旧值,这样内存顺序是任何算法都无法承受的。

6. Overly kind simulators and emulators.

模拟器和仿真器无法仿真真实的硬件环境

It is difficult to write simulators or emulators that force memory re-ordering, so software that runs just fine in these environments can get a nasty surprise when it first runs on the real hardware. Unfortunately, it is still the rule that the hardware is more devious than are the simulators and emulators, but we hope that this situation changes.

撰写支持乱序执行的模拟器和仿真器还是有些困难的,因此,如果软件可以正确的在仿真环境中运行正常,或许在真实硬件上运行的时候会遇到各种奇奇怪怪的问题。很遗憾,真实的硬件永远都是比模拟器和仿真器更加复杂,曲折,因此这个情况始终存在,希望后续会慢慢好转。

Again, we encourage hardware designers to avoid these practices!

再次强调,希望硬件设计者避免发生上述错误。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 排序算法面试题复习

    排序是数据结构中很重要的算法, 说起来是算法, 个人感觉也就是种思想,明白每种算法的设计思想。 就很简单了。 就比不要去借鉴别人算法, 看起来苦涩难懂。 算法最忌讳死记硬背。 所以对于算法静下心来,慢慢来, 多思考多分析。这样才扎实,在以后算法的扩展, 算法的优化才…...

    2024/4/27 23:45:30
  2. UML类图中箭头和线条的含义和用法

    UML类图中箭头和线条的含义和用法 https://blog.csdn.net/qq_15646957/article/details/56280099...

    2024/4/28 6:51:51
  3. logback.xml

    <?xml version="1.0" encoding="UTF-8" ?><logger name="org.springframework.jdbc.core.JdbcTemplate" level="debug"/><logger name="org.springframework.jdbc.core.JdbcTemplate" level="debug&q…...

    2024/4/28 3:40:39
  4. 使用tensorflow1.12从零开始训练自己的yolo3

    有些步骤我不会解释为什么这么做,你只需要知道你这么做能得到什么就行,如果都要进行解释,一来你们不想看,二来有可能你根本就看不懂,,,,对于小白来说,我们都是懒人对吧,不懒的话你来看啥?(狗头护体) **本文介绍如何制作数据集、修改代码、不加载预权重从头跑自己的…...

    2024/4/28 7:51:36
  5. MNIST手写数字识别实验(SVM)

    实验接上回KNN的实验 这是SVM的方法选择使用sklearn中已经实现的SVM的方法 sklearn中SVM的相关参考资料: 中文文档 SVM的API 详细了解怎么使用各个接口,参数怎么调整的话要去看官方的文档。中文文档介绍了一些使用经验。 和上次的方法类似,我们先进行采样,进行数据量比较小…...

    2024/4/28 17:00:29
  6. Cavas网页所有源码

    Cavas网页所有源码–许俊活 这是CSS样式 body,html {text-align: center;padding-top: 20px;/*margin: 0;*/}canvas {box-shadow: 0 0 10px #333;margin: 0 auto;/*position: absolute;left: 0; border: 1px solid red;*/}//网页主体代码 <body onload="draw()"&g…...

    2024/4/27 23:57:13
  7. a线程输出,b县城消费的列子

    代码主体 package test;import java.util.ArrayList; import java.util.LinkedList; import java.util.List;/*** @className TryDemo* @Description TODO* @Author HZHEN* @Date 2020/6/17 15:17**/ public class TryDemo {private LinkedList linkedList= new LinkedList();/…...

    2024/4/28 10:42:08
  8. Leetcode 188. 买卖股票的最佳时机 IV C++

    Leetcode 188. 买卖股票的最佳时机 IV 题目 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 测试样例 示例 1: …...

    2024/4/28 5:12:50
  9. Leetcode刷题之旅(每日一题)--面试题 16.18. 模式匹配(这也叫中等难度???)

    题目描述:思路:说实话完全没有思路。甚至于看了题解也没有思路。先看一下简单的几种容易理解的特殊情况。1.当value长度为0时候,当pattern长度为0或者pattern都是a或都是b时候返回true,否则返回false。2.当value长度不为0时候,pattern长度为0则返回false,pattern长度为1时…...

    2024/4/28 16:33:10
  10. 机器学习之XGBoosting

    https://www.cnblogs.com/zongfa/p/9324684.html...

    2024/4/28 7:19:12
  11. 应用halton序列生成均匀散点图

    前言一门课的作业要用RBF-DQ方法计算流场。简单来讲就是要在无网格的条件下用高精度格式实现流场的仿真计算。生成散点图时,用蒙特卡洛的方法得到的散点图分布不是很均匀,于是想到一种叫做halton的序列。本篇将运用halton序列生成均匀的散点图。halton序列原理点下面的链接看…...

    2024/4/28 2:19:18
  12. 用sqoop从Mysql多表/单表导入hdfs命令

    一、 sqoop命令从Mysql单表导入hdfs命令 bin/sqoop import \ --connect jdbc:mysql://10.160.000.117:3306/phjr_data \ --m 1 \ --username mysql \ --password minmin@123 \ --table t_fgw_qt_lhszzxx \ --target-dir /smxaq/PHJR/phjr_data/t_fgw_qt_lhszzxx \ --as-textfi…...

    2024/4/28 6:34:09
  13. LeetCode-Algorithms-[Easy][树]617. 合并二叉树

    给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 示例 1:来源:力扣(…...

    2024/4/24 14:46:15
  14. Easyui之Tree组件

    Tree组件 1、主题:将MySQL中的数据转入Tree组件 2、准备工作: 在WebContent下新建一些文件夹(Folder) 导入所需的资料$(function(){$(#tt).tree({url:tree_data1.json}); })然后根据数据库表中的字段名来写实体类entity: package com.xieying.entity;/**** @author 莹酱**…...

    2024/4/28 13:53:51
  15. 【LeetCode刷题】1431. 拥有最多糖果的孩子

    给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。示例 1:输入…...

    2024/4/28 23:07:41
  16. 前端下载EXCEL文件,后端返回文件流的处理

    转载:https://www.cnblogs.com/coconutGirl/p/12605562.html 前端下载EXCEL文件,后端返回文件流的处理 axios({url: 下载接口URL,method: post,data: {},responseType: blob }).then((res) => {// data就是接口返回的文件流let data = res.dataif (data) {let attrs = res…...

    2024/4/24 14:46:12
  17. 从零手写VIO(四)

    对从零手写VIO第四次作业进行总结文章目录1 信息矩阵与边缘化1.1 信息矩阵1.2 marg掉ξ12 BA信息矩阵计算2.1 雅克比计算2.2 H的结构2.3 验证零空间维度 作业:1 信息矩阵与边缘化 1.1 信息矩阵 由图中能够看到,我们的优化变量有6个,分别为ξ1,ξ2,ξ3,L1,L2,L3,误差项有9个,所以…...

    2024/4/24 14:46:11
  18. React+umi+antd实践

    React+umi+antd实践 登录界面登录页面还没有做登录验证功能。直接点击登录可以通过history.push跳转到home页面。 function goToHome() {history.push(/home); }主页主页主要是实现了layout布局的导航条,以及Table中数据的删除和添加,添加功能目前是写死的数据添加,明天会实…...

    2024/4/24 14:46:10
  19. StringTable的特性

    StringTable的特性常量池中的字符串仅是符号,第一次用到时才变为对象 利用串池的机制,来避免重复创建字符串对象 字符串变量拼接的原理是StringBuilder (1.8) 字符串常量拼接的原理是编译期优化 可以使用 intern方法,主动将串池中还没有的字符串对象放入串池 1.8 将这个字…...

    2024/4/24 14:46:09
  20. golang入门day3 函数(式编程)

    函数 : Go 语言 用 func 关键字来定义 1 go 程序中 至少有一个 函数 main函数 2 Go语言 函数不能重载 语法格式func FuncName( 参数.... ) (返回值...){ //ToDo.....return a,b... }package mainimport "fmt"//函数可以定义不使用 func A(){fmt.Println(&q…...

    2024/4/15 3:05:25

最新文章

  1. go语言实现简单认证样例

    目录 1、代码实现样例 2、postman调用 1、代码实现样例 package mainimport ("net/http""strings""github.com/dgrijalva/jwt-go""github.com/gin-gonic/gin" )var (// 密钥&#xff0c;用于验证 JWT 令牌signingKey []byte("…...

    2024/4/28 23:28:56
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. ssm框架中各层级介绍

    1、Spring&#xff08;业务逻辑层&#xff09;&#xff1a; Spring框架提供了依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;等功能&#xff0c;可以帮助管理Java应用程序中的对象依赖关系和提供横切关注点的支持。 在SSM框架中&#xff0c;S…...

    2024/4/27 13:24:33
  4. Java-运算符

    运算符 Java语言支持如下运算符&#xff1a; 算术运算符&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;%&#xff0c;&#xff0c;--复制运算符&#xff1a;关系运算符&#xff1a;>, <, >, <, , !instanceof逻辑运算符&#xff1a;&&…...

    2024/4/27 0:55:38
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/28 13:52:11
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/28 3:28:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/28 13:51:37
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/28 15:57:13
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/28 1:22:35
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57