-
Notifications
You must be signed in to change notification settings - Fork 0
/
searchindex.xml
351 lines (351 loc) · 82 KB
/
searchindex.xml
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<?xml version="1.0" encoding="utf-8" standalone="yes"?><search><entry><title>bitcoin介绍</title><url>https://reisen1969.github.io/post/bitcoin/</url><categories/><tags><tag>bitcoin</tag><tag>blockchain</tag></tags><content type="html"> 本文是对https://learnmeabitcoin.com/ 的翻译
比特币如何工作? 比特币是一个诞生于2009年的电子支付系统.它允许你给世界上任何一个人转账, 你不需要征得任何人的许可就能拥有/创建账户.
它是作为现代金融体系的一种解决方案而创建的,在现代金融体系中, 有少数几家大银行控制谁获得账户以及处理哪些交易。这意味着对货币的控制是集中的,我们必须相信银行会负责任地行事。
银行被我们信任,才能进行存钱和电子转账的业务,但是银行却在信贷泡沫的浪潮中放贷, 几乎没有储备金(reserve). —- 中本聪
中心化的银行和2007年的金融危机激发了比特币的发展.比特币是一个支付系统, 它可以在没有中央控制的情况下运行.它是由中本聪匿名设计的,与2009年1月发布.
任何人都可以运行该程序或使用该系统.
下面是它工作原理的简单解释.
比特币是什么? 比特币只是一个计算机程序,你可以下载运行它.
当您运行该程序时,它将连接到其他正在运行该程序的计算机,它们将开始共享一个文件。这个文件称为区块链,此文件实际上是一个巨大的交易列表(list of transactions)
该文大量使用了 transaction 一词, 我将它翻译为 “交易”
当一个新的交易进入网络时,它会在计算机之间进行转发,直到每个人都拥有该交易的副本。大约每隔10分钟,网络上的一台随机计算机(节点)将把它们收到的最新交易添加到区块链上,并与网络上的其他所有人共享更新。
因此, 比特币程序创建了一个大型计算机网络,这些计算机彼此通信,共享一个文件,并用新的交易更新它。
处理双重消费 在比特币出现之前, 通过计算机进行电子交易是可能的 , 然而, 可以在计算机网络中插入冲突的交易。例如,您可以创建两个使用相同数字货币的独立交易,并将这两个交易同时发送到网络。 这就是所谓的“双重消费”。
因此,设计一个去中心化的电子支付系统,会遇到一个问题,即确定哪些交易是“先”进行的,当拥有一个由所有独立运行的计算机组成的网络时,这是一件很难做到的事情。有些计算机将首先接收绿色交易,而有些计算机将首先接收红色交易。
谁来决定哪一个“先”出现,并且应该是写入文件的唯一一个? 比特币通过迫使节点在写入文件之前将它们收到的所有交易保存在内存中来解决这个问题。然后,每隔10分钟,网络上的一个随机节点将把它们的内存中的交易添加到文件中。
然后,这个更新后的文件与网络共享,节点将接受更新后文件中的交易为“正确的”,从它们的内存中删除任何冲突的交易。因此,不会将双重花费交易写到文件中,所有节点都可以按照彼此的协议更新它们的文件。
向文件中添加交易的过程称为"挖矿", 它基本上是网络范围内的竞争,不能由网络上的单个节点控制.
矿工如何工作? 首先,每个节点将它们收到的最新交易存储在它们的内存池中,内存池只是它们计算机上的临时内存。然后,任何节点都可以尝试从它们的内存池中挖掘交易到文件(区块链)。
为此,节点将从其内存池中将交易收集到一个称为块的容器中,然后使用算力尝试将该交易块添加到区块链。
那么,这种算力从何而来呢?要将这个块添加到区块链,您必须将您的交易块提供给一个称为哈希函数的东西。哈希函数基本上是一个小型计算机程序,它可以接收任意数量的数据,打乱它,然后吐出一个完全随机(但唯一)的数字。
要将块成功添加到区块链,这个数字(块哈希)必须低于目标值,这是网络上所有人都同意的阈值。
如果得到的块哈希不低于目标,可以对块内的数据进行小的调整,并将其再次通过散列函数。这将产生一个完全不同的数字,很可能会低于目标。如果没有,需要调整块并重试。
综上所述,矿工使用算力尽可能快地执行哈希计算,并尝试成为网络上第一个获得低于目标的块哈希的计算机。如果您成功了,您可以将您的交易块添加到区块链上,并与网络的其他部分共享它。
尽管任何人都可以尝试挖掘区块,但在家用电脑上这样做已不再具有竞争力。现在有专门的硬件被设计成尽可能快(和高效)地执行哈希计算,这意味着现在大部分的挖矿工作都是由那些有专门硬件和廉价电力的人来完成的。
比特币的来源 为了激励人们使用算力,尝试在区块链上添加新的交易块,每个新块提供固定数量的比特币。因此,如果你能够成功挖出一个区块,你就能够“发送”给自己这些新的比特币作为你努力的奖励。
这种比特币被称为区块奖励,这也是为什么这个过程被称为“挖矿”.
为什么这个文件被叫做"区块链"? 正如我们所看到的,交易不会单独添加到文件中——它们被收集在一起并以块的形式添加。每个新块都建立在现有块的基础上,因此文件由一系列块组成.
此外,网络上的每个节点将始终采用它们接收到的最长区块链作为区块链的“官方”版本。这意味着矿工总是试图在已知最长区块链的“顶端”上构建,因为不属于最长链的任何区块都不会被其他节点视为有效。
因此,如果有人想重写交易的历史,他们将需要重建一个更长的区块链,以创建一个新的最长链,以供其他节点采用。然而,要实现这一目标,单个矿机的计算机算力需要超过网络中其他矿机的算力之和。
因此,网络的共同努力使得任何个人都很难“跑赢”整个网络并重写区块链。
交易如何进行的? 您可以认为区块链是一个又一个的保险箱,我们称之为输出(outputs)。这些输出只是容纳不同数量比特币的容器。
当你进行比特币交易时,你选择一些输出并解锁它们,然后创建新的输出并对其添加新锁。
因此,当你“发送”某人比特币时,你实际上是将一定数量的比特币放入一个新的保险箱,并在保险箱上上锁,只有你“发送”比特币的目标才能解锁。 例如,如果我想给你发送一些比特币,我会从区块链中选择一些我可以解锁的输出,并从中创建一个只有你可以解锁的新输出。此外,如果我不想将我解锁的所有比特币发送给你,我会创建一个额外的输出作为我的“改变”,并将其锁定在自己身上。
继续向前,如果你想把你的比特币发送给别人,你将重复这个过程,选择现有的输出(你可以解锁),并从它们创建新的输出。因此,比特币交易形成了一个类似图表的结构,比特币的移动通过一系列交易连接起来。
最后,当一个交易被挖掘到区块链上时,在该交易中用完(消耗)的输出不能在另一个交易中使用,而新创建的输出将可在未来的交易中移动。
观察下图, 原先的一个输出(10),被分割成了2 5 2 1几个新输出, 新的输出可以在未来被使用.
如何拥有自己的比特币? 为了能够“接收”比特币,你需要有自己的一组密钥。这组密钥就像你的账号和密码,只不过在比特币中它们被称为你的公钥和私钥。 例如, 时,我将把您的公钥放在输出(保险箱)上的锁中。当你想将比特币发送给其他人时,你可以使用你的私钥来解锁这个输出。
那么在哪里可以获得公钥和私钥呢?在密码学的帮助下,你可以自己生成它们。 简而言之,您的私钥只是一个大随机数,而您的公钥是由这个私钥计算出来的数字。但聪明的部分是;您可以将您的公钥提供给其他人,但他们无法从中计算出私钥。
现在,当你想要解锁分配给你的公钥的比特币时,你使用你的私钥来创建所谓的数字签名。这个数字签名证明你是公钥的所有者(因此可以解锁比特币),而不必透露你的私钥。这个数字签名也只对创建它的交易有效,所以它不能用来解锁锁定在同一公钥上的其他比特币。
该系统被称为“公开密钥密码术”,自19781年以来一直可用。比特币利用这个系统允许任何人创建密钥来安全地发送和接收比特币,而不需要中央机构颁发帐户和密码。
综合 要开始使用比特币,您需要生成自己的私钥和公钥。您的私钥只是一个非常大的随机数,而您的公钥是从它计算出来的。这些密钥可以在您的计算机上轻松生成,甚至可以在计算器这样简单的东西上生成。大多数人使用比特币钱包来帮助生成和管理密钥。 要接收比特币,你需要把你的公钥给想给你发送比特币的人。这个人会创建一个交易,解锁他们拥有的比特币,并创建一个新的比特币“保险箱”,把你的公钥放在锁里。 然后,这笔交易被发送到比特币网络上的任何节点,在那里,它从一台计算机中继到另一台计算机,直到网络上的每个节点都拥有该交易的副本。从这里开始,每个节点都有机会尝试和挖掘它们在区块链上收到的最新事务。 这个挖掘过程涉及到一个节点从它的内存池中收集事务到一个块中,并重复地将该块数据通过哈希函数(每次都有轻微的调整),以尝试获得低于目标值的块哈希。 第一个找到低于目标的块散列的矿工将把该块添加到其区块链中,并将该块广播到网络上的其他节点。每个节点还将把这个块添加到它们的区块链(从它们的内存池中删除任何冲突的事务),并重新启动挖掘进程,以尝试在链中的这个新块之上进行构建。 最后,挖掘该区块的矿工将在该区块中放置自己的特殊交易,这允许他们收集一定数量的不存在的比特币。这个区块奖励作为节点继续构建区块链的激励,同时在整个比特币网络中分发新币。
结尾 比特币是一种与世界各地的其他计算机共享安全文件的计算机程序。这个安全文件由交易组成,这些交易使用密码学来允许人们发送和接收数字保险箱。因此,这创建了一个任何人都可以使用的电子支付系统,并且运行时没有一个中央控制点。 自2009年1月发布以来,比特币网络一直在不间断地运行。2019年,比特币网络处理了超过1.12亿笔交易,总共转移了15,577,763,114,629.34美元(15.58万亿美元)2。 比特币程序本身也在积极开发中,自发布以来,有超过600人对代码做出了贡献。这是因为该软件是“开源”的,这意味着任何人都可以查看代码并对其进行改进。
[比特币白皮书][https://bitcoin.org/bitcoin.pdf ]
[bitcoin core 源码][https://github.com/bitcoin/bitcoin/ ]
获取更多 Beginners Guide - Sometimes you just need a complete walkthrough of the basics. This is the shortest and simplest guide I could write; I wrote it in 2015 as I was learning how Bitcoin works for the first time. Technical Guide - A more complete and in-depth guide to how Bitcoin works. Good for programmers. Blockchain Explorer - You can get a feel for how bitcoin works by just browsing the data and seeing how it all connects together. It’s like opening the bonnet of a car and looking inside. Videos (YouTube) - These are deep explanations of the mechanics of bitcoin from the perspective of a programmer. These video lessons will get you going if you want to code stuff with bitcoin. Code (GitHub) - Example code snippets for common bitcoin stuff. 打赏本文的原作者Greg
3Beer3irc1vgs76ENA4coqsEQpGZeM5CTd</content></entry><entry><title>kernel config配置</title><url>https://reisen1969.github.io/post/kernel_config/</url><categories/><tags><tag>kernel</tag></tags><content type="html"> todo</content></entry><entry><title>leetcode刷题</title><url>https://reisen1969.github.io/post/leetcode/</url><categories/><tags><tag>C</tag><tag>CPP</tag><tag>leetcode</tag></tags><content type="html"> 算法框架 各种遍历 数组的遍历 void traverse(int[] arr) { for (int i = 0; i < arr.length; i++) { // 迭代访问 arr[i] } } 链表的遍历 /* 基本的单链表节点 */ class ListNode { int val; ListNode next; } void traverse(ListNode head) { for (ListNode p = head; p != null; p = p.next) { // 迭代访问 p.val } } void traverse(ListNode head) { // 递归访问 head.val traverse(head.next); } 二叉树遍历 /* 基本的 N 叉树节点 */ class TreeNode { int val; TreeNode[] children; } void traverse(TreeNode root) { for (TreeNode child : root.children) traverse(child); } void traverse(TreeNode root) { // 前序位置 traverse(root.left); // 中序位置 traverse(root.right); // 后序位置 } 滑动窗口 /* 滑动窗口算法框架 */ void slidingWindow(string s) { unordered_map<char, int> window , need; int left = 0, right = 0; while (right < s.size()) { // c 是将移入窗口的字符 char c = s[right]; // 增大窗口 right++; // 进行窗口内数据的一系列更新 //将拿到的数据放入窗口 ... /*** debug 输出的位置 ***/ printf("window: [%d, %d)\n", left, right); /********************/ // 判断左侧窗口是否要收缩 while (window needs shrink) { // d 是将移出窗口的字符 char d = s[left]; // 缩小窗口 left++; // 进行窗口内数据的一系列更新 //将窗口的数据移除 ... } } } STL的部分使用</content></entry><entry><title>riscv裸机</title><url>https://reisen1969.github.io/post/qemu1/</url><categories/><tags><tag>qemu</tag></tags><content type="html"> https://mth.st/blog/riscv-qemu/
使用virit机器启动, -s 表示 启动gdb server 默认端口1234 -S 表示CPU不立即开始运行
/qemu-system-riscv64 -machine virt -nographic -s -S 编译riscv程序
.section .init .global _start _start: li s1, 0x10000000 # s1 := 0x1000_0000 la s2, message # s2 := <message> addi s3, s2, 14 # s3 := s2 + 14 1: lb s4, 0(s2) # s4 := (s2) sb s4, 0(s1) # (s1) := s4 addi s2, s2, 1 # s2 := s2 + 1 blt s2, s3, 1b # if s2 < s3, branch back to 1 .section .data message: .string "Hello, world!\n" $ riscv64-none-elf-as loop.s -g -o loop.elf $ riscv64-none-elf-objcopy -O binary loop.elf loop.img
$ riscv64-none-elf-ld -o loop.linked.elf loop.elf $ riscv64-none-elf-objcopy -O binary loop.linked.elf loop.linked.img $ diff loop.img loop.linked.img
riscv64-none-elf-ld –verbose > qemu-riscv64-virt.ld
riscv64-none-elf-ld -T qemu-riscv64-virt.ld -o loop.linked.elf loop.elf
看支持的硬件 qemu-system-riscv64 -machine help</content></entry><entry><title>C++并发</title><url>https://reisen1969.github.io/post/cpp_concurrency/</url><categories/><tags><tag>C</tag><tag>CPP</tag></tags><content type="html"> 多线程编程 并发:只存在一个处理器
并行:存在多个处理器
并行是并发的子集,统称为并发
进程(英语:process),是指计算机中已运行的程序。进程为曾经是分时系统的基本运作单位。在面向进程设计的系统(如早期的UNIX,Linux 2.4及更早的版本)中,进程是程序的基本执行实体;
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
在默认的情况下,我们写的代码都是在进程的主线程中运行,除非开发者在程序中创建了新的线程。
当我们只有一个处理器时,所有的进程或线程会分时占用这个处理器。但如果系统中存在多个处理器时,则就可能有多个任务并行的运行在不同的处理器上。
任务会在何时占有处理器,通常是由操作系统的调度策略决定的
多线程API 由操作系统提供.
C++ 11 中,增加了多线程的支持
thread 启动一个新线程
创建线程 thread
join :主线程等待这个线程结束
detach 让线程独立运行 ,成为守护线程
访问共享数据的代码片段称之为临界区(critical section)
mutex互斥加锁
条件量
wait
notify_all
本质上是线程间 共享的全局flag
#include <iostream> #include <string> #include <thread> #include <mutex> #include <condition_variable> std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false; void worker_thread() { // Wait until main() sends data std::unique_lock lk(m); cv.wait(lk, []{return ready;}); // after the wait, we own the lock. std::cout << "Worker thread is processing data\n"; data += " after processing"; // Send data back to main() processed = true; std::cout << "Worker thread signals data processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); cv.notify_one(); } int main() { std::thread worker(worker_thread); data = "Example data"; // send data to the worker thread { std::lock_guard lk(m); ready = true; std::cout << "main() signals data ready for processing\n"; } cv.notify_one(); // wait for the worker { std::unique_lock lk(m); cv.wait(lk, []{return processed;}); } std::cout << "Back in main(), data = " << data << '\n'; worker.join(); } future future对象用来存储异步任务的执行结果,获取到这个对象的时候,可能异步任务还没执行.
wait()来等待该异步任务结束
get()获取该异步任务的结果
async 假设你需要一个长时间的运算,但并不迫切的需要这个值,可以启动一个新线程来执行这个计算,但是thread 并没有机制获得返回值.
可以使用async启动一个异步任务,而且会返回一个 future对象.
#include <future> #include <iostream> int find_the_answer_to_ltuae() { return 42; } void do_other_stuff() {} int main() { std::future<int> the_answer=std::async(find_the_answer_to_ltuae); do_other_stuff(); std::cout<<"The answer is "<<the_answer.get()<<std::endl; } async是否会启动一个新线程, 标准没有定义,由编译器决定,
如果一定要启动新线程,可以加入launch::async参数来说明.
std::launch::async 在独立线程上执行
std::launch::deferred 在wait或者get调用时执行 ,并不是独立线程
std::launch::async|std::launch::deferred 在wait或者get调用时执行 ,并独立线程执行
auto f6=std::async(std::launch::async,Y(),1.2); // 在新线程上执行 auto f7=std::async(std::launch::deferred,baz,std::ref(x)); // 在wait()或get()调用时执行 auto f8=std::async( std::launch::deferred | std::launch::async, baz,std::ref(x)); // 实现选择执行方式 auto f9=std::async(baz,std::ref(x)); f7.wait(); // 调用延迟函数 promise 上面的例子,异步任务的结果是通过 return 返回的
但在一些时候,我们可能不能这么做:在得到任务结果之后,可能还有一些事情需要继续处理,例如清理工作。
异步任务不再直接返回计算结果,而是增加了一个promise对象来存放结果。
在任务计算完成之后,将总结过设置到promise对象上。一旦这里调用了set_value,其相关联的future对象就会就绪
#include <vector> #include <thread> #include <future> #include <numeric> #include <iostream> #include <chrono> void accumulate(std::vector<int>::iterator first, std::vector<int>::iterator last, std::promise<int> accumulate_promise) { int sum = std::accumulate(first, last, 0); accumulate_promise.set_value(sum); // Notify future } void do_work(std::promise<void> barrier) { std::this_thread::sleep_for(std::chrono::seconds(1)); barrier.set_value(); } int main() { // Demonstrate using promise<int> to transmit a result between threads. std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 }; std::promise<int> accumulate_promise; std::future<int> accumulate_future = accumulate_promise.get_future(); std::thread work_thread(accumulate, numbers.begin(), numbers.end(), std::move(accumulate_promise)); // future::get() will wait until the future has a valid result and retrieves it. // Calling wait() before get() is not needed //accumulate_future.wait(); // wait for result std::cout << "result=" << accumulate_future.get() << '\n'; work_thread.join(); // wait for thread completion // Demonstrate using promise<void> to signal state between threads. std::promise<void> barrier; std::future<void> barrier_future = barrier.get_future(); std::thread new_work_thread(do_work, std::move(barrier)); barrier_future.wait(); new_work_thread.join(); } packaged_task packaged_task绑定到一个函数或者可调用对象上。当它被调用时,它就会调用其绑定的函数或者可调用对象。并且,可以通过与之相关联的future来获取任务的结果。调度程序只需要处理packaged_task,而非各个函数。
任务不会自己启动,你必须调用它.
#include <iostream> #include <cmath> #include <thread> #include <future> #include <functional> // unique function to avoid disambiguating the std::pow overload set int f(int x, int y) { return std::pow(x,y); } void task_lambda() { std::packaged_task<int(int,int)> task([](int a, int b) { return std::pow(a, b); }); std::future<int> result = task.get_future(); task(2, 9); std::cout << "task_lambda:\t" << result.get() << '\n'; } void task_bind() { std::packaged_task<int()> task(std::bind(f, 2, 11)); std::future<int> result = task.get_future(); task(); std::cout << "task_bind:\t" << result.get() << '\n'; } void task_thread() { std::packaged_task<int(int,int)> task(f); std::future<int> result = task.get_future(); std::thread task_td(std::move(task), 2, 10); task_td.join(); std::cout << "task_thread:\t" << result.get() << '\n'; } int main() { task_lambda(); task_bind(); task_thread(); } C++内存模型 上文中提到的知识是以互斥体为中心的。为了避免竞争条件,是保证任何时候只有一个线程可以进入临界区
那些策略都是基于锁(lock-based)的:一旦有一个线程进入临界区,其他线程只能等待。
那有没有一种策略可以让其他线程不用等待,实现更好的并发呢?
答案是肯定的,这称之为免锁(lock-free)策略。不过实现这种策略要更麻烦一些,要对C++内存模型有更深入的理解,而这也是下面所要讲解的内容。
内存模型主要包含了下面三个部分:
原子操作:顾名思义,这类操作一旦执行就不会被打断,你无法看到它的中间状态,它要么是执行完成,要么没有执行。 操作的局部顺序:一系列的操作不能被乱序。 操作的可见性:定义了对于共享变量的操作如何对其他线程可见。 事实上,开发者编写的代码和最终运行的程序往往会存在较大的差异,而运行结果与开发者预想一致,只是一种“假象”罢了。
之所以会产生差异,原因主要来自下面三个方面:
编译器优化 CPU out-of-order执行 CPU Cache不一致性 Memory Reorder “Memory Reorder”包含了编译器和处理器两种类型的乱序。
X = 0, Y = 0; Thread 1: X = 1; // ① r1 = Y; // ② Thread 2: Y = 1; r2 = X; 线程1中事件发生的顺序虽然是先①后②,但是对于线程2来说,它看到结果可能却是先②后①。当然,线程1看线程2也是一样的。
甚至,当今的所有硬件平台,没有任何一个会提供完全的顺序一致(sequentially consistent)内存模型,因为这样做效率太低了。
不同的编译器和处理器对于Memory Reorder有不同的偏好,但它们都遵循一定的原则,那就是:不能修改单线程的行为(Thou shalt not modify the behavior of a single-threaded program.)。在这个基础上,它们可以做各种类型的优化。
sequenced-before sequenced-before 单线程上的关系,这是一个非对称,可传递的成对关系
int i = 7; // ① i++; // ② 这里的 ① sequenced-before ② 。
happens-before happens-before关系是sequenced-before关系的扩展,因为它还包含了不同线程之间的关系。
如果A happens-before B,则A的内存状态将在B操作执行之前就可见,这就为线程间的数据访问提供了保证。
同样的,这是一个非对称,可传递的关系。
如果A happens-before B,B happens-before C。则可推导出A happens-before C。
synchronizes-with synchronizes-with描述的是一种状态传播(propagate)关系。如果A synchronizes-with B,则就是保证操作A的状态在操作B执行之前是可见的。
下文中我们将看到,原子操作的acquire-release具有synchronized-with关系。
除此之外,对于锁和互斥体的释放和获取可以达成synchronized-with关系,还有线程执行完成和join操作也能达成synchronized-with关系。
最后,借助 synchronizes-with 可以达成 happens-before 关系。
原子 可能是使用的互斥量实现,也可能是使用的对应硬件平台的原子指令实现.
is_lock_free() 可以用来查询是否是无锁的.
函数 #_flag #_bool 指针类型 整形类型 说明 test_and_set Y 将flag设为true并返回原先的值 clear Y 将flag设为false is_lock_free Y Y Y 检查原子变量是否免锁 load Y Y Y 返回原子变量的值 store Y Y Y 通过一个非原子变量的值设置原子变量的值 exchange Y Y Y 用新的值替换,并返回原先的值 compare_exchange_weak compare_exchange_strong Y Y Y 比较和改变值 fetch_add, += Y Y 增加值 fetch_sub, -= Y Y 减少值 ++, – Y Y 自增和自减 fetch_or, |= Y 求或并赋值 fetch_and, &= Y 求与并赋值 fetch_xor, ^= Y 求异或并赋值 memory_order 原子操作中,都支持一个类型为 std::memory_order 的可选参数。这个参数是一个枚举类型,可能的取值如下:
typedef enum memory_order { memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst } memory_order; 首先,并非每一种memory_order对于每一个原子操作都有意义。它们的使用需要有特定的配合。
我们可以根据原子操作是否读写数据分为“Read”,“Write”以及“Read-Modify-Write”(读、修改、写)三类,下面是这些操作的分类。
Operation Read Write Read-Modify-Write test_and_set Y clear Y is_lock_free Y load Y store Y exchange Y compare_exchange_strong Y compare_exchange_weak Y fetch_add, += Y fetch_sub, -= Y fetch_or, |= Y ++,– Y fetch_and, &= Y fetch_xor, ^= Y 而对于每一个分类,有意义的memory_order参数如下。
Operation Order Read memory_order_relaxed memory_order_consume memory_order_acquire memory_order_seq_cst Write memory_order_relaxed memory_order_release memory_order_seq_cst Read-modify-write memory_order_relaxed memory_order_acq_rel memory_order_seq_cst 当多个线程中包含了多个原子操作,这些原子操作因为其memory_order的选择不一样,将导致运行时不同的内存模型强度。从强至弱,有三种情况:
Sequential Consistency:顺序一致性,简称 seq-cst。 Acquire and Release:获取和释放,简称 acq-rel。 Relaxed:松散模型。 seq-cst 模型 当使用原子操作而又不指定memory_order时将使用默认的内存顺序:memory_order_seq_cst,因此调用这些函数时指定或者不指定这个值效果是一样的。
这是最严格的内存模型,seq-cst 有两个保证:
程序指令与源码顺序一致 所有线程的所有操作存在一个全局的顺序 这意味着:所有关于原子操作的代码都不会被乱序,你可以列出线程交错的所有可能性,即便每次执行交错的结果会不一样。但对于任意一次来说,其执行的顺序必属于这些可能性中的一个。而且,对于某一个单次执行来说,所有线程看到的顺序是一致的。
在这种模型下,每个线程中所有操作的先后关系,其顺序对于所有线程都是可见的。因此它是所有线程的全局同步。
这种模型很容易理解,但缺点是它的性能较差。因为为了实现顺序一致需要添加很多手段来对抗编译器和CPU的优化
#include<bits/stdc++.h> std::atomic<bool> x,y; std::atomic<int> z; void write_x_then_y() { x.store(true); // ① y.store(true); // ② } void read_y_then_x() { while(!y.load()); // ③ if(x.load()) // ④ ++z; // ⑤ } int main() { x=false; y=false; z=0; std::thread a(write_x_then_y); std::thread b(read_y_then_x); a.join(); b.join(); assert(z.load()!=0); // ⑥ } 发生在线程a中的时序也将同步到线程b中。对于y的store和load操作构成了synchronized-with关系。
1 happens-before 2 happens-before 3 happens-before 4
acq-rel 模型 memory_order_release对应了写操作,memory_order_acquire对应了读操作,memory_order_acq_rel对应了既读又写。
同一个原子变量上的acquire和release操作将引入synchronizes-with关系。除此之外,将不再有全局的一致顺序。
同一个对象上的原子操作不允许被乱序。 release操作禁止了所有在它之前的读写操作与在它之后的写操作乱序。 acquire操作禁止了所有在它之前的读操作与在它之后的读写操作乱序。 #include<bits/stdc++.h> std::atomic<bool> x,y; std::atomic<int> z; void write_x_then_y() { x.store(true, std::memory_order_relaxed); // ① y.store(true, std::memory_order_release); // ② } void read_y_then_x() { while(!y.load(std::memory_order_acquire)); // ③ if(x.load(std::memory_order_relaxed)) ++z; // ④ } int main() { x=false; y=false; z=0; std::thread a(write_x_then_y); std::thread b(read_y_then_x); a.join(); b.join(); assert(z.load()!=0); // ⑤ } relaxed 模型 在进行原子操作时,指定memory_order_relaxed时将使用relaxed模型。这是最弱的内存模型。
这个模型下唯一可以保证的是:对于特定原子变量存在全局一致的修改顺序,除此以外不再有其他保证。这意味着,即便是同样的代码,不同的线程可能会看到不同的执行顺序。
#include<bits/stdc++.h> std::atomic<bool> x,y; std::atomic<int> z; void write_x_then_y() { x.store(true, std::memory_order_relaxed); // ① y.store(true, std::memory_order_relaxed); // ② } void read_y_then_x() { while(!y.load(std::memory_order_relaxed)); // ③ if(x.load(std::memory_order_relaxed)) // ④ ++z; // ⑤ } int main() { x=false; y=false; z=0; std::thread a(write_x_then_y); std::thread b(read_y_then_x); a.join(); b.join(); assert(z.load()!=0); // ⑥ } 这里的assert是可能会触发的。
尽管所有操作都是原子的,但是所有的事件不要求存在一个全局顺序 同一个线程内部有happens-before规则,但是线程之间可能会看到不同的顺序 另外需要说明的是:这里问题的发生只是理论上的可能。如果你将上面这个代码片段编译和运行,估计你运行100次也碰不到问题的发生。但是,这并不表示问题不存在,它只是很难发生而已。而这也恰恰是并发系统难以开发的原因之一:很多问题在绝大部分时候都不会出现,当在极少数时候发生的时候,又很难被理解。
relaxed模型约束太小,因此常常需要结合Fence来一起使用
Fence Fence这个单词的中文翻译就是“栅栏”,它就像一个屏障一样,使得其前后的代码不能穿越。
Fence有三种情况:
full fence:指定了memory_order_seq_cst或者memory_order_acq_rel。 acquire fence:指定了memory_order_acquire。 release fence:指定了memory_order_release。 不同类型的Fence对于乱序的保护是不一样的。我们可以将读和写的交错分成下面四种情况:
① Load-Load:读接着读 ② Load-Store:先读后写 ③ Store-Load:先写后读 ④ Store-Store:写接着写 full fence可以防止①②④三种情况下,但是不能防止第③种情况下。
acquire fence阻止了所有在它之前的读操作与在它之后的读写操作乱序
release fence阻止了所有在它之前的读写操作与在它之后的写操作乱序
使用fence修改relaxed的代码
std::atomic<bool> x,y; std::atomic<int> z; void write_x_then_y() { x.store(true, std::memory_order_relaxed); // ① std:: (std::memory_order_release); y.store(true, std::memory_order_relaxed); // ② } void read_y_then_x() { while(!y.load(std::memory_order_relaxed)); // ③ std::atomic_thread_fence(std::memory_order_acquire); if(x.load(std::memory_order_relaxed)) ++z; // ④ } mutex和Fence 之前我们介绍了互斥体mutex:拿到mutex锁的线程将拥有唯一进入临界区的资格。
除了保证互斥之外,其实mutex的加锁和解锁之间也起到了“栅栏”的作用。因为在栅栏里面的代码是怎么都不会被优化乱序到栅栏之外(但不保证栅栏之外的内容进入到栅栏之中)。
如下图的三种情况,第一种可能会被优化成第二种。但是第二种情况不会被优化成第三种:
GCC官方的解释: http://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync
Sequentially Consistent 顺序一致性 -Thread 1- -Thread 2- y = 1 if (x.load() == 2) x.store (2); assert (y == 1) 断言一定能成功,在线程1中,y的存储 happens-before x的存储
线程2看到的也一样.
a = 0 y = 0 b = 1 -Thread 1- -Thread 2- x = a.load() while (y.load() != b) y.store (b) ; while (a.load() == x) a.store(1) ; 线程2循环,等待y的值发生变化,然后改变a
线程1正在等待a发生变化
-Thread 1- -Thread 2- -Thread 3- y.store (20); if (x.load() == 10) { if (y.load() == 10) x.store (10); assert (y.load() == 20) assert (x.load() == 10) y.store (10) } 两个断言都一定成功
Relaxed宽松顺序 这个模型消除了 “再发生之前"的限制, 减少了同步
-Thread 1- y.store (20, memory_order_relaxed) x.store (10, memory_order_relaxed) -Thread 2- if (x.load (memory_order_relaxed) == 10) { assert (y.load(memory_order_relaxed) == 20) /* assert A */ y.store (10, memory_order_relaxed) } -Thread 3- if (y.load (memory_order_relaxed) == 10) assert (x.load(memory_order_relaxed) == 10) /* assert B */ 两个断言可能会失败.
不存在任何的happens-before, 任何一个线程都不依赖另一个线程的顺序.
唯一存在的顺序:线程2看到的线程1中一个变量的值(如x),那么它就无法看到x的更早的值.
-Thread 1- x.store (1, memory_order_relaxed) x.store (2, memory_order_relaxed) -Thread 2- y = x.load (memory_order_relaxed) z = x.load (memory_order_relaxed) assert (y <= z) 断言一定成功
线程2只能看到最新值,无法看到旧值.
在一定时间内,realxed load可以看到另一个线程的realxed store,这意味着,realxed 操作需要刷新缓存
当编程者只是希望一个变量是原子的,而不需要和其他线程进行同步时,就可以使用realxed模式
Acquire/Release 获得释放一致性 这个模式是前两种的混合, 它和sequentially consistent 相似,
它只对依赖的变量应用happens-before关系
同一个对象上的原子操作不允许被乱序。 release操作禁止了所有在它之前的读写操作与在它之后的写操作乱序。 acquire操作禁止了所有在它之前的读操作与在它之后的读写操作乱序。 -Thread 1- y.store (20, memory_order_release); -Thread 2- x.store (10, memory_order_release); -Thread 3- assert (y.load (memory_order_acquire) == 20 && x.load (memory_order_acquire) == 0) -Thread 4- assert (y.load (memory_order_acquire) == 0 && x.load (memory_order_acquire) == 10) -Thread 1- y = 20; x.store (10, memory_order_release); -Thread 2- if (x.load(memory_order_acquire) == 10) assert (y == 20); 线程1中,对y的写操作,不能乱序
线程2中,对y的读操作,不能乱序
断言一定成功
Consume 消费 比较特殊,不推荐使用
-Thread 1- n = 1 m = 1 p.store (&n, memory_order_release) -Thread 2- t = p.load (memory_order_acquire); assert( *t == 1 && m == 1 ); -Thread 3- t = p.load (memory_order_consume); assert( *t == 1 && m == 1 ); 线程2中的断言成功
因为m的存储发生在p.store之前.
线程3中的断言失败
全面总结 -Thread 1- -Thread 2- -Thread 3- y.store (20); if (x.load() == 10) { if (y.load() == 10) x.store (10); assert (y.load() == 20) assert (x.load() == 10) y.store (10) } 如果是顺序一致性同步的话,必须在系统中刷新所有可见变量,以至于所有线程看到相同的状态.
断言成立
如果是acq rel模式的话,只需要同步涉及的两个线程,这意味着同步值不能与其他线程交换.
线程1和线程2因为 x.load 同步,所以断言成立
线程2和线程3因为 y.load同步,但是线程1和线程3没有同步,所以x的值不一定是10,断言可能会失败
如果是relaxed,所有断言都有可能失败,因为根本没有同步.
混合模式 -Thread 1- y.store (20, memory_order_relaxed) x.store (10, memory_order_seq_cst) -Thread 2- if (x.load (memory_order_relaxed) == 10) { assert (y.load(memory_order_seq_cst) == 20) /* assert A */ y.store (10, memory_order_relaxed) } -Thread 3- if (y.load (memory_order_acquire) == 10) assert (x.load(memory_order_acquire) == 10) /* assert B */ 首先,不要这样做
1. 16472131 2. 82526461 3. 93023508 4. 09183796 5. 46548500 6. 32016383</content></entry><entry><title>并行编程</title><url>https://reisen1969.github.io/post/parallel/</url><categories/><tags><tag>多核</tag></tags><content type="html"> 参考:C++11中的内存模型
https://paul.pub/cpp-memory-model/
多核CPU架构 顺序一致性Sequential Consistency 每个处理器的执行顺序和代码中的顺序(program order)一样。 所有处理器都只能看到一个单一的操作执行顺序。 实际上还是相当于同一时间只有一个线程在工作,这种保证导致了程序是低效的,无法充分利用上多核的优点。
全存储排序(Total Store Ordering, 简称TSO) 在处理核心中增加写缓存,一个写操作只要写入到本核心的写缓存中就可以返回
松弛型内存模型(Relaxed memory models) 在松散型的内存模型中,编译器可以在满足程序单线程执行结果的情况下进行重排序(reorder),程序的执行顺序就不见得和代码中编写的一样了
内存栅栏(memory barrier) 由于有了缓冲区的出现,导致一些操作不用到内存就可以返回继续执行后面的操作,为了保证某些操作必须是写入到内存之后才执行,就引入了内存栅栏(memory barrier,又称为memory fence)操作。内存栅栏指令保证了,在这条指令之前所有的内存操作的结果,都在这个指令之后的内存操作指令被执行之前,写入到内存中。也可以换另外的角度来理解内存栅栏指令的作用:显式的在程序的某些执行点上保证SC。
关系术语 sequenced-before sequenced-before用于表示单线程之间,两个操作上的先后顺序,这个顺序是非对称、可以进行传递的关系。
它不仅仅表示两个操作之间的先后顺序,还表示了操作结果之间的可见性关系。两个操作A和操作B,如果有A sequenced-before B,除了表示操作A的顺序在B之前,还表示了操作A的结果操作B可见。
happens-before 与sequenced-before不同的是,happens-before关系表示的不同线程之间的操作先后顺序,同样的也是非对称、可传递的关系。
如果A happens-before B,则A的内存状态将在B操作执行之前就可见。在上一篇文章中,某些情况下一个写操作只是简单的写入内存就返回了,其他核心上的操作不一定能马上见到操作的结果,这样的关系是不满足happens-before的。
synchronizes-with synchronizes-with关系强调的是变量被修改之后的传播关系(propagate),即如果一个线程修改某变量的之后的结果能被其它线程可见,那么就是满足synchronizes-with关系的。
显然,满足synchronizes-with关系的操作一定满足happens-before关系了。
C++11内存模型 enum memory_order { memory_order_relaxed, //宽松 memory_order_consume, memory_order_acquire, //用来修饰一个读操作,表示在本线程中,所有后续的关于此变量的内存操作都必须在本条原子操作完成后执行。 memory_order_release, //用来修饰一个写操作,表示在本线程中,所有之前的针对该变量的内存操作完成后才能执行本条原子操作。 memory_order_acq_rel, //同时包含memory_order_acquire和memory_order_release标志 memory_order_seq_cst //一致性内存模型 默认 }; 原子操作分为三类
读load: memory_order_relaxed memory_order_consume memory_order_acquire memory_order_seq_cst
写store: memory_order_relaxed memory_order_release memory_order_seq_cst
读-改-写 :以上全部
memory_order_relaxed 宽松顺序
针对一个变量的读写操作是原子操作; 不同线程之间针对该变量的访问操作先后顺序不能得到保证,即有可能乱序。 x = 0; y = 0; // Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D 可能会出现r1 = r2 = 42 的结果
这个内存模型的典型应用是计数器递增
memory_order_acquire 获得操作
用来修饰一个读操作,表示在本线程中,所有后续的关于此变量的内存操作都必须在本条原子操作完成后执行
如 lock()
memory_order_release 释放操作
用来修饰一个写操作,表示在本线程中,所有之前的针对该变量的内存操作完成后才能执行本条原子操作。
如unlock()
memory_order_acq_rel memory_order_consume 消费操作
上方的acq和rel的粒度太大了
#include <thread> #include <atomic> #include <cassert> #include <string> std::atomic<std::string*> ptr; int data; void producer() { std::string* p = new std::string("Hello"); data = 42; ptr.store(p, std::memory_order_release); } void consumer() { std::string* p2; while (!(p2 = ptr.load(std::memory_order_consume))) ; assert(*p2 == "Hello"); // 绝无出错: *p2 从 ptr 携带依赖 assert(data == 42); // 可能也可能不会出错: data 不从 ptr 携带依赖 } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); } memory_order_seq_cst // 线程 1 : x.store(1, std::memory_order_seq_cst); // A y.store(1, std::memory_order_release); // B // 线程 2 : r1 = y.fetch_add(1, std::memory_order_seq_cst); // C r2 = y.load(std::memory_order_relaxed); // D // 线程 3 : y.store(3, std::memory_order_seq_cst); // E r3 = x.load(std::memory_order_seq_cst); // F 与volatile的关系 [https://www.codedump.info/post/20191214-cxx11-memory-model-1/]:
C++线程库 并发:只存在一个处理器
并行:存在多个处理器
并行是并发的子集,统称为并发
进程(英语:process),是指计算机中已运行的程序。进程为曾经是分时系统的基本运作单位。在面向进程设计的系统(如早期的UNIX,Linux 2.4及更早的版本)中,进程是程序的基本执行实体;
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
在默认的情况下,我们写的代码都是在进程的主线程中运行,除非开发者在程序中创建了新的线程。
当我们只有一个处理器时,所有的进程或线程会分时占用这个处理器。但如果系统中存在多个处理器时,则就可能有多个任务并行的运行在不同的处理器上。
任务会在何时占有处理器,通常是由操作系统的调度策略决定的
多线程API 由操作系统提供.
C++ 11 中,增加了多线程的支持
创建线程 thread
join :主线程等待这个线程结束
detach 让线程独立运行 ,成为守护线程
访问共享数据的代码片段称之为临界区(critical section)
互斥锁 mutex 互斥量 加锁 , 加锁和解锁是有代价的 ,锁的粒度尽量要小
我们用锁的粒度(granularity)来描述锁的范围。细粒度(fine-grained)是指锁保护较小的范围,粗粒度(coarse-grained)是指锁保护较大的范围。出于性能的考虑,我们应该保证锁的粒度尽可能的细。并且,不应该在获取锁的范围内执行耗时的操作,例如执行IO。如果是耗时的运算,也应该尽可能的移到锁的外面。
条件变量 wait
notify_all
本质上是线程间 共享的全局flag
异步 使用async 而不是 thread 启动一个任务
它会返回一个future对象。future用来存储异步任务的执行结果
需要注意的是,默认情况下,async是启动一个新的线程,还是以同步的方式(不启动新的线程)运行任务,这一点标准是没有指定的,由具体的编译器决定。如果希望一定要以新的线程来异步执行任务,可以通过launch::async来明确说明。
future 来存储一个异步任务的执行结果, 哪怕这个异步任务还没开始执行,
就可以定义一个 future来接受 这个异步任务的结果
需要进行一次长时间的运算,但是你现在不急的要,可以启动一个新线程来执行这个运算,
但是 thread 并没有机制获取返回值, 这里就需要async 来实现了.
C++ 中的算法可以加上一个 新参数,并行执行算法</content></entry><entry><title>常用的网站</title><url>https://reisen1969.github.io/post/web/</url><categories/><tags><tag>网站</tag></tags><content type="html"> 观察编译器背后行为的网站 https://godbolt.org/ https://www.godbolt.ms/
cpp的手册 https://en.cppreference.com/w/cpp
在线编译器 https://www.onlinegdb.com/online_c++_compiler
在线正则表达式 https://regex101.com/
ascii画图 https://asciiflow.com/legacy/</content></entry><entry><title>面试</title><url>https://reisen1969.github.io/post/interview/</url><categories/><tags><tag>interview</tag></tags><content type="html"> C/C++相关 static 关键字 修饰局部变量, 修饰全局变量, 修饰成员变量, 修饰普通函数, 修饰成员函数, 修饰类,this指针等 const 关键字 修饰变量,说明该变量不可以被改变; 修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer); 修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改; 修饰成员函数,说明该成员函数内不能修改成员变量。 宏和常量的区别 宏定义 #define const 常量 宏定义,相当于字符替换 常量声明 预处理器处理 编译器处理 无类型安全检查 有类型安全检查 不分配内存 要分配内存 存储在代码段 存储在数据段 可通过 #undef 取消 不可取消 内存 栈stack, 只要出现花括号,就会开辟新栈 堆heap 未初始化全局变量bss, 初始化全局变量,存放全局数据和静态数据,又可细分为可读和可写区 代码段text,只读 内存泄漏 堆栈溢出
指针和引用 引用和指针的区别, 引用只是别名,不需要分配内存空间,指针是变量,需要分配内存空间
引用和指针的尺寸, 引用不占内存,指针的尺寸和cpu的字长有关.
右值引用,表示什么意思?有什么用?[完美转发是什么?] 可以实现移动语义,实现移动构造函数,避免深拷贝
STL中好多地方都用到了这个,如unique_ptr只支持移动构造函数
深拷贝和浅拷贝? inline关键字 有什么用?原理?优缺点? 和宏对比 类相关 如果不写构造函数,默认的构造函数是什么? 有两个:拷贝构造 赋值构造
初始化列表是什么?有什么好处?
静态类
多态 C++的多态是什么,实现的原理是什么? 编译器厂商都是用虚函数和虚表实现的
纯虚函数是什么? 构造函数和析构函数是否可以是虚函数?为什么? STL 简单介绍一下STL 一些容器的特性以及使用场景 vector 动态数组 ,size和capacity ,以及扩容机制,扩容完之后迭代器失效等等
list 双向链表
map和unordered_map
介绍一下迭代器
new delete 智能指针
如何避免临时对象生成 (可能是过时的)
临时对象一般是在栈上生成的,源码中不可见的对象,会多调用一次构造和析构函数
传参和返回时,尽量使用引用传递而不是值传递, 实参和形参要类型匹配,不要发生隐式类型转换,比如使用显式关键字explicit 单片机相关 C51等简单的单片机,问一下中断的概念
STM32等ARM核的高级单片机
中断 中断向量表 DMA等问题
QT相关 父类被析构时,需不需要手动析构子类对象,为什么? 对象树机制
操作系统相关</content></entry><entry><title>C++刷题记录</title><url>https://reisen1969.github.io/post/c++test/</url><categories/><tags><tag>C</tag><tag>CPP</tag></tags><content type="html"> 一些算法 技巧 对于算法来说,迭代器的区间都是前闭后开 计算某个区间之和accumulate 从迭代器获得index : i - num.begin() distance(num.begin(),i) 操作string isalnum,判断单个字符是否是 字母或数字 tolower 单个字符转小写 牛顿法平方根 public int mySqrt(int x) { int start = 0, end = x; int res = -1; while (start <= end) { int mid = start + (end - start)/2; if ((long)mid * mid <= x) { start = mid + 1; res = mid; } else { end = mid -1; } } return res; } cin读入逗号分隔的数字 #include<bits/stdc++.h> using namespace std; int main() { vector<int> data; string s; getline(cin,s); istringstream input; input.str(s); for(string line;getline(input,line,',');) { data.push_back(stoi(line)); } int sum = 0; for(auto i:data) sum+=i; cout<<sum; return 0; } leetcode版本的链表与使用 #include<bits/stdc++.h> using namespace std; struct ListNode { int val; ListNode *next; ListNode() : val(0), next(nullptr) {} ListNode(int x) : val(x), next(nullptr) {} ListNode(int x, ListNode *next) : val(x),next(next) {} }; int main() { ListNode* head = new ListNode(0); ListNode* point = head; for(int i = 1; i < 10; i++) { point->next = new ListNode(i); point = point->next; } point = head; while(1) { cout<< point->val << endl; if (point->next) point = point->next; else break; } return 0; }</content></entry><entry><title>riscv 启动分析</title><url>https://reisen1969.github.io/post/riscvhead/</url><categories/><tags><tag>riscv</tag><tag>kernel</tag></tags><content type="html"> 简单分析了下 relocate 和 setup_vm
首先是 setup_vm , 它设置了两个页表:
early_pg_dir,将内核自身处于的连续物理内存区域映射到位于PAGE_OFFSET的虚拟内存地址上 而FIXMAP映射并没有映射全部的FIXMAP,而是仅仅影射了FIX_FDT部分,让后续的代码可以访问设备树。
trampoline_pg_dir 将PAGE_OFFSET后长为PMD_SIZE的区域映射到load_pa,即内核起始被装载后的起始物理内存地址。
call relocate的地方:
/* Enable virtual memory and relocate to virtual address */ la a0, swapper_pg_dir //a0存储页表的物理地址 XIP_FIXUP_OFFSET a0 call relocate_enable_mmu 参数a0存储 页表的物理地址
//a0此时存储页表的物理地址 .align 2 #ifdef CONFIG_MMU .global relocate_enable_mmu relocate_enable_mmu: /* Relocate return address */ la a1, kernel_map //a1 = kermap的地址 XIP_FIXUP_OFFSET a1 REG_L a1, KERNEL_MAP_VIRT_ADDR(a1) la a2, _start //a2 = 镜像的地址 sub a1, a1, a2 //a1 = a1 - a2 //80001012 add ra, ra, a1 //得到ra的虚拟地址 //80201014 /* Point stvec to virtual address of intruction after satp write */ la a2, 1f //a2 = label1 的地址 add a2, a2, a1 //计算1f的新地址? //8000101e csrw CSR_TVEC, a2 //mtec //80201020 /* Compute satp for kernel page tables, but don't load it yet */ srl a2, a0, PAGE_SHIFT //a2 = a0>>PAGE_SHIFT 12 应该是PPN //80201024 la a1, satp_mode //a1 = satp_mode的地址 REG_L a1, 0(a1) or a2, a2, a1 //这里拼凑了stap寄存器 //80201032 /* * Load trampoline page directory, which will cause us to trap to * stvec if VA != PA, or simply fall through if VA == PA. We need a * full fence here because setup_vm() just wrote these PTEs and we need * to ensure the new translations are in use. */ la a0, trampoline_pg_dir //a0 = t表的地址 XIP_FIXUP_OFFSET a0 srl a0, a0, PAGE_SHIFT //取t表地址的高部分 //8000103c or a0, a0, a1 //或 satp_mode //8000103e sfence.vma //80001040 csrw CSR_SATP, a0 //设置CSR_SATP寄存器 //80001044 .align 2 1: /* Set trap vector to spin forever to help debug */ la a0, .Lsecondary_park csrw CSR_TVEC, a0 //80001050 /* Reload the global pointer */ .option push .option norelax la gp, __global_pointer$ .option pop /* * Switch to kernel page tables. A full fence is necessary in order to * avoid using the trampoline translations, which are only correct for * the first superpage. Fetching the fence is guaranteed to work * because that first superpage is translated the same way. */ csrw CSR_SATP, a2 //8000105c sfence.vma ret //80001064 trampoline_pg_dir表实际上是多余的,</content></entry><entry><title>常用的正则表达式</title><url>https://reisen1969.github.io/post/regex/</url><categories/><tags><tag>正则</tag></tags><content type="html"> 本文记录一下浏览代码时比较常用的正则表达式
在线练习网站
单词边界 \b 匹配单词的开头和结尾
re: \bhi str: history 匹配前两个字符
re: ry\b str: history 匹配后两个字符
re: \bhistory\b str: history 匹配整个字符
什么能被称为边界?
字母和 [空格 汉字 标点] 之间的分界,但是空格 汉字 标点之间不能算是分界
单词非边界 \B 匹配的结果不能是字母和 [空格 汉字 标点] 之间的分界
re: ry\B str: history 无法匹配
re: \Bst\B str: history 可以匹配到中间的字符
匹配文件的开头 \A 只能匹配文件的开头
匹配文件的结尾 \Z 只能匹配文件的结尾
匹配一行的开头 ^ 匹配一行的结尾 $ 匹配某个函数 main_\w+_start\(\) main_AA_start() main_BB_start() main_CC_start()</content></entry><entry><title>linux相关工具</title><url>https://reisen1969.github.io/post/linux_tools/</url><categories/><tags><tag>linux</tag></tags><content type="html"> find - 递归地在层次目录中处理文件 find [path...] [expression] -type b:特殊块文件 缓冲的 c:特殊字符文件 不缓冲 d:目录 p:命名管道 f:普通文件 l:符号链接 s:套接字 //设置递归深度 -maxdepth 1 //在当前目录下搜索 Videos的文件夹 默认递归 find ./ -type d -name "Videos" grep - 打印匹配给定模式的行 grep [options] PATTERN [FILE...] grep [options] [-e PATTERN | -f FILE] [FILE...] -r 递归 -n 在输出的每行前面加上它所在的文件中它的行号 -i 忽略大小写 -I 处理一个二进制文件,但是认为它不包含匹配的内容。 //递归的查找 main 字符串 grep -rn main</content></entry><entry><title>shell脚本用法</title><url>https://reisen1969.github.io/post/shell/</url><categories/><tags><tag>linux</tag><tag>shell</tag></tags><content type="html"> 分支 if [[ $1 == 'qd' ]]; then echo '1' elif [[ $1 == 'qi' ]]; then echo '2' elif [[ $1 == 'ob' ]]; then echo '3' else echo 'error' fi 传入参数 使用 $1 $2 $3 $4表示
命令行里的循环 while true ; do ./a.out ; done</content></entry><entry><title>使用qemu启动riscv64 kernel</title><url>https://reisen1969.github.io/post/riscv_kernel/</url><categories/><tags><tag>riscv</tag><tag>kernel</tag><tag>qemu</tag></tags><content type="html"> 在这里只是记录一下,方便以后查阅
本文的内容来源
riscv官方网页
解决文件系统问题
编译busybox
9p
获取源码 git clone https://github.com/qemu/qemu git clone https://github.com/torvalds/linux git clone https://git.busybox.net/busybox 编译qemu ./configure --target-list=riscv64-softmmu --enable-virtfs make -j $(nproc) 编译kernel //这里设置的工具链,以自己环境里的为准 //生成配置文件 make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig //开始编译 make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j $(nproc) //编译方法2 make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig make -j $(nproc) ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- vmlinux 编译Busybox make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig //Check Settings - Build static binary (no shared libs) make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- install CONFIG_PREFIX=./rootfs //这里选择将busybox安装到本目录下的./rootfs文件夹下 制作根文件系统 //制作文件系统 $ cd youer_workspace mkdir rootfs $ cd rootfs $ dd if=/dev/zero of=rootfs.img bs=1M count=50 $ mkfs.ext2 -L riscv-rootfs rootfs.img //将它挂载到 mnt/rootfs $ sudo mkdir /mnt/rootfs $ sudo mount rootfs.img /mnt/rootfs //将busybox编译出来的内容复制到rootfs.img文件系统中 $ sudo cp -ar your_busybox/rootfs/* /mnt/rootfs //创建根目录 $ sudo mkdir /mnt/rootfs/{dev,home,mnt,proc,sys,tmp,var} $ sudo chown -R -h root:root /mnt/rootfs //检查文件系统 $ df /mnt/rootfs Filesystem 1K-blocks Used Available Use% Mounted on /dev/loop5 49584 1704 45320 4% /mnt/rootfs $ mount | grep rootfs riscv64-linux/rootfs/rootfs.img on /mnt/rootfs type ext2 (rw,relatime) //卸载文件系统 $ sudo umount /mnt/rootfs $ sudo rmdir /mnt/rootfs 使用根文件系统启动内核 qemu-system-riscv64 -nographic -machine virt \ -kernel linux/arch/riscv/boot/Image -append "root=/dev/vda ro console=ttyS0" \ -drive file=your_rootfs.img,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -fsdev local,id=p9fs,path=./share,security_model=mapped \ -device virtio-9p-pci,fsdev=p9fs,mount_tag=p9 挂载共享目录 mount p9 -t 9p /mnt</content></entry><entry><title>C语言运算符优先级</title><url>https://reisen1969.github.io/post/c_order/</url><categories/><tags><tag>C</tag><tag>CPP</tag></tags><content type="html"> 优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右 – () 圆括号 (表达式)/函数名(形参表) – . 成员选择(对象) 对象.成员名 – -> 成员选择(指针) 对象指针->成员名 – 2 - 负号运算符 -表达式 右到左 单目运算符 ~ 按位取反运算符 ~表达式 单目运算符 ++ 自增运算符 ++变量名/变量名++ 单目运算符 – 自减运算符 –变量名/变量名– 单目运算符 ***** 取值运算符 *指针变量 单目运算符 & 取地址运算符 &变量名 单目运算符 ! 逻辑非运算符 !表达式 单目运算符 (类型) 强制类型转换 (数据类型)表达式– – sizeof 长度运算符 sizeof(表达式) – 3 / 除 表达式/表达式 左到右 双目运算符 ***** 乘 表达式*表达式 双目运算符 % 余数(取模) 整型表达式%整型表达式 双目运算符 4 + 加 表达式+表达式 左到右 双目运算符 - 减 表达式-表达式 双目运算符 5 « 左移 变量«表达式 左到右 双目运算符 » 右移 变量»表达式 双目运算符 6 > 大于 表达式>表达式 左到右 双目运算符 >= 大于等于 表达式>=表达式 双目运算符 < 小于 表达式<表达式 双目运算符 <= 小于等于 表达式<=表达式 双目运算符 7 == 等于 表达式==表达式 左到右 双目运算符 != 不等于 表达式!= 表达式 双目运算符 8 & 按位与 表达式&表达式 左到右 双目运算符 9 ^ 按位异或 表达式^表达式 双目运算符 10 | 按位或 表达式|表达式 双目运算符 11 && 逻辑与 表达式&&表达式 双目运算符 12 || 逻辑或 表达式||表达式 双目运算符 13 ?: 条件运算符 表达式1?表达式2: 表达式3 右到左 三目运算符 14 = 赋值运算符 变量=表达式 右到左 – /= 除后赋值 变量/=表达式 – *= 乘后赋值 变量*=表达式 – %= 取模后赋值 变量%=表达式 – += 加后赋值 变量+=表达式 – -= 减后赋值 变量-=表达式 – «= 左移后赋值 变量«=表达式 – »= 右移后赋值 变量»=表达式 – &= 按位与后赋值 变量&=表达式 – ^= 按位异或后赋值 变量^=表达式 – |= 按位或后赋值 变量|=表达式 – 15 , 逗号运算符 表达式,表达式,… 左到右 – 说明:
同一优先级的运算符,运算次序由结合方向所决定。 简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符</content></entry><entry><title>qemu梳理(riscv64用户模式)</title><url>https://reisen1969.github.io/post/qemu0/</url><categories/><tags><tag>qemu</tag></tags><content type="html"> 函数执行流程 main cpu_loop cpu_exec tb_gen_code gen_intermediate_code translator_loop riscv_tr_translate_insn decode_opc decode_insn16 trans_add(/target/riscv/insn_trans/trans_rvi.c.inc) gen_arith(/target/riscv/translate.c) decode文件的内容和用途 从上到下的顺序,是:
Fields字段 # Fields: %rs3 27:5 %rs2 20:5 %rs1 15:5 %rd 7:5 %sh5 20:5 %sh6 20:6 表示参与指令的rs和rd在编码中的位置
immediate立即数 # immediates: %imm_i 20:s12 %imm_s 25:s7 7:5 %imm_b 31:s1 7:1 25:6 8:4 !function=ex_shift_1 %imm_j 31:s1 12:8 20:1 21:10 !function=ex_shift_1 %imm_u 12:s20 !function=ex_shift_12 和Fields作用差不多,表示各种立即数在指令编码中的位置
Argument sets参数集 &empty &b imm rs2 rs1 &i imm rs1 rd &j imm rd 表示每一种类型的指令编码中 参与的立即数和寄存器
python脚本会根据这些生成对应的结构体,如:
typedef struct { } arg_empty; typedef struct { int imm; int rs1; int rd; } arg_i; typedef struct { int imm; int rd; } arg_j; Formats指令格式 # Formats 32: @r ....... ..... ..... ... ..... ....... &r %rs2 %rs1 %rd @i ............ ..... ... ..... ....... &i imm=%imm_i %rs1 %rd @b ....... ..... ..... ... ..... ....... &b imm=%imm_b %rs2 %rs1 @s ....... ..... ..... ... ..... ....... &s imm=%imm_s %rs2 %rs1 @u .................... ..... ....... &u imm=%imm_u %rd @j .................... ..... ....... &j imm=%imm_j %rd 每行都是32个. 表示指令的编码格式
用空格将每部分分开
最后面的是 寄存器 和 立即数 等,它们的字段在上文中已确认
详细的指令集 这部分基本上就是把指令集从官方文档中搬了过来,
python脚本会根据这些指令生成trans_xxx的函数声明,具体的函数定义需要自己编写.
宏 MAKE_64BIT_MASK 位于/include/qemu/bitops.h
#define MAKE_64BIT_MASK(shift, length) \ (((~0ULL) >> (64 - (length))) << (shift)) 0ULL: unsinged long long zero 64-bit的无符号0
作用:制造一个64位的掩码,该掩码从第shift位开始,长度是length,如下:
0000 ....0 11 .... .111 0.... 000 |--length--|--shift---| FIELD 位于/include/hw/registerfields.h
#define FIELD(reg, field, shift, length) \ enum { R_ ## reg ## _ ## field ## _SHIFT = (shift)}; \ enum { R_ ## reg ## _ ## field ## _LENGTH = (length)}; \ enum { R_ ## reg ## _ ## field ## _MASK = \ MAKE_64BIT_MASK(shift, length)}; 这个宏定义了三个枚举 ,各自只有一个成员
R_reg_field_SHIFT = shift
R_reg_field_LENGTH = length
R_reg_field_MASK = 对应length和shift的掩码
重要的结构体 CPUArchState(某个arch的cpu状态) 根据不同的guest,会被定义为其它名称,如:CPURISCVState
事实上,CPUArchState 被定义于 target/riscv/cpu.h, 每种guest的这个结构体都不一样
struct CPUArchState { target_ulong gpr[32]; //32个gpr寄存器,每个寄存器长32 target_ulong gprh[32]; /* 128-bit寄存器的高64位,每个寄存器长32 */ uint64_t fpr[32]; //32个浮点寄存器 /* assume both F and D extensions */ /* vector coprocessor state. */ uint64_t vreg[32 * RV_VLEN_MAX / 64] QEMU_ALIGNED(16); target_ulong vxrm; target_ulong vxsat; target_ulong vl; target_ulong vstart; target_ulong vtype; bool vill; target_ulong pc; target_ulong load_res; target_ulong load_val; target_ulong frm; target_ulong badaddr; uint32_t bins; target_ulong guest_phys_fault_addr; target_ulong priv_ver; target_ulong bext_ver; target_ulong vext_ver; /* RISCVMXL, but uint32_t for vmstate migration */ uint32_t misa_mxl; /* current mxl */ uint32_t misa_mxl_max; /* max mxl for this cpu */ uint32_t misa_ext; /* current extensions */ uint32_t misa_ext_mask; /* max ext for this cpu */ uint32_t xl; /* current xlen */ /* 128-bit helpers upper part return value */ target_ulong retxh; uint32_t features; #ifdef CONFIG_USER_ONLY uint32_t elf_flags; #endif #ifndef CONFIG_USER_ONLY target_ulong priv; /* This contains QEMU specific information about the virt state. */ target_ulong virt; target_ulong geilen; target_ulong resetvec; target_ulong mhartid; /* * For RV32 this is 32-bit mstatus and 32-bit mstatush. * For RV64 this is a 64-bit mstatus. */ uint64_t mstatus; uint64_t mip; uint64_t miclaim; uint64_t mie; uint64_t mideleg; target_ulong satp; /* since: priv-1.10.0 */ target_ulong stval; target_ulong medeleg; target_ulong stvec; target_ulong sepc; target_ulong scause; target_ulong mtvec; target_ulong mepc; target_ulong mcause; target_ulong mtval; /* since: priv-1.10.0 */ /* Machine and Supervisor interrupt priorities */ uint8_t miprio[64]; uint8_t siprio[64]; /* AIA CSRs */ target_ulong miselect; target_ulong siselect; /* Hypervisor CSRs */ target_ulong hstatus; target_ulong hedeleg; uint64_t hideleg; target_ulong hcounteren; target_ulong htval; target_ulong htinst; target_ulong hgatp; target_ulong hgeie; target_ulong hgeip; uint64_t htimedelta; /* Hypervisor controlled virtual interrupt priorities */ target_ulong hvictl; uint8_t hviprio[64]; /* Upper 64-bits of 128-bit CSRs */ uint64_t mscratchh; uint64_t sscratchh; /* Virtual CSRs */ /* * For RV32 this is 32-bit vsstatus and 32-bit vsstatush. * For RV64 this is a 64-bit vsstatus. */ uint64_t vsstatus; target_ulong vstvec; target_ulong vsscratch; target_ulong vsepc; target_ulong vscause; target_ulong vstval; target_ulong vsatp; /* AIA VS-mode CSRs */ target_ulong vsiselect; target_ulong mtval2; target_ulong mtinst; /* HS Backup CSRs */ target_ulong stvec_hs; target_ulong sscratch_hs; target_ulong sepc_hs; target_ulong scause_hs; target_ulong stval_hs; target_ulong satp_hs; uint64_t mstatus_hs; /* Signals whether the current exception occurred with two-stage address translation active. */ bool two_stage_lookup; target_ulong scounteren; target_ulong mcounteren; target_ulong sscratch; target_ulong mscratch; /* temporary htif regs */ uint64_t mfromhost; uint64_t mtohost; uint64_t timecmp; /* physical memory protection */ pmp_table_t pmp_state; target_ulong mseccfg; /* machine specific rdtime callback */ uint64_t (*rdtime_fn)(uint32_t); uint32_t rdtime_fn_arg; /* machine specific AIA ireg read-modify-write callback */ #define AIA_MAKE_IREG(__isel, __priv, __virt, __vgein, __xlen) \ ((((__xlen) & 0xff) << 24) | \ (((__vgein) & 0x3f) << 20) | \ (((__virt) & 0x1) << 18) | \ (((__priv) & 0x3) << 16) | \ (__isel & 0xffff)) #define AIA_IREG_ISEL(__ireg) ((__ireg) & 0xffff) #define AIA_IREG_PRIV(__ireg) (((__ireg) >> 16) & 0x3) #define AIA_IREG_VIRT(__ireg) (((__ireg) >> 18) & 0x1) #define AIA_IREG_VGEIN(__ireg) (((__ireg) >> 20) & 0x3f) #define AIA_IREG_XLEN(__ireg) (((__ireg) >> 24) & 0xff) int (*aia_ireg_rmw_fn[4])(void *arg, target_ulong reg, target_ulong *val, target_ulong new_val, target_ulong write_mask); void *aia_ireg_rmw_fn_arg[4]; /* True if in debugger mode. */ bool debugger; /* * CSRs for PointerMasking extension */ target_ulong mmte; target_ulong mpmmask; target_ulong mpmbase; target_ulong spmmask; target_ulong spmbase; target_ulong upmmask; target_ulong upmbase; #endif target_ulong cur_pmmask; target_ulong cur_pmbase; float_status fp_status; /* Fields from here on are preserved across CPU reset. */ QEMUTimer *timer; /* Internal timer */ hwaddr kernel_addr; hwaddr fdt_addr; /* kvm timer */ bool kvm_timer_dirty; uint64_t kvm_timer_time; uint64_t kvm_timer_compare; uint64_t kvm_timer_state; uint64_t kvm_timer_frequency; }; CPUState(通用上的CPU状态) 里面会保存tb缓存
struct CPUState { /*< private >*/ DeviceState parent_obj; /*< public >*/ int nr_cores; int nr_threads; struct QemuThread *thread; #ifdef _WIN32 HANDLE hThread; #endif int thread_id; bool running, has_waiter; struct QemuCond *halt_cond; bool thread_kicked; bool created; bool stop; bool stopped; /* Should CPU start in powered-off state? */ bool start_powered_off; bool unplug; bool crash_occurred; bool exit_request; bool in_exclusive_context; uint32_t cflags_next_tb; /* updates protected by BQL */ uint32_t interrupt_request; int singlestep_enabled; int64_t icount_budget; int64_t icount_extra; uint64_t random_seed; sigjmp_buf jmp_env; QemuMutex work_mutex; QSIMPLEQ_HEAD(, qemu_work_item) work_list; CPUAddressSpace *cpu_ases; int num_ases; AddressSpace *as; MemoryRegion *memory; CPUArchState *env_ptr; IcountDecr *icount_decr_ptr; /* Accessed in parallel; all accesses must be atomic */ TranslationBlock *tb_jmp_cache[TB_JMP_CACHE_SIZE]; struct GDBRegisterState *gdb_regs; int gdb_num_regs; int gdb_num_g_regs; QTAILQ_ENTRY(CPUState) node; /* ice debug support */ QTAILQ_HEAD(, CPUBreakpoint) breakpoints; QTAILQ_HEAD(, CPUWatchpoint) watchpoints; CPUWatchpoint *watchpoint_hit; void *opaque; /* In order to avoid passing too many arguments to the MMIO helpers, * we store some rarely used information in the CPU context. */ uintptr_t mem_io_pc; /* Only used in KVM */ int kvm_fd; struct KVMState *kvm_state; struct kvm_run *kvm_run; struct kvm_dirty_gfn *kvm_dirty_gfns; uint32_t kvm_fetch_index; uint64_t dirty_pages; /* Used for events with 'vcpu' and *without* the 'disabled' properties */ DECLARE_BITMAP(trace_dstate_delayed, CPU_TRACE_DSTATE_MAX_EVENTS); DECLARE_BITMAP(trace_dstate, CPU_TRACE_DSTATE_MAX_EVENTS); DECLARE_BITMAP(plugin_mask, QEMU_PLUGIN_EV_MAX); #ifdef CONFIG_PLUGIN GArray *plugin_mem_cbs; /* saved iotlb data from io_writex */ SavedIOTLB saved_iotlb; #endif /* TODO Move common fields from CPUArchState here. */ int cpu_index; int cluster_index; uint32_t tcg_cflags; uint32_t halted; uint32_t can_do_io; int32_t exception_index; /* shared by kvm, hax and hvf */ bool vcpu_dirty; /* Used to keep track of an outstanding cpu throttle thread for migration * autoconverge */ bool throttle_thread_scheduled; bool ignore_memory_transaction_failures; /* Used for user-only emulation of prctl(PR_SET_UNALIGN). */ bool prctl_unalign_sigbus; struct hax_vcpu_state *hax_vcpu; struct hvf_vcpu_state *hvf; /* track IOMMUs whose translations we've cached in the TCG TLB */ GArray *iommu_notifiers; }; ArchCPU 每一种arc都有一个这样的结构体,包含了上面两个结构体
struct ArchCPU { /*< private >*/ CPUState parent_obj; /*< public >*/ CPUNegativeOffsetState neg; CPURISCVState env; char *dyn_csr_xml; char *dyn_vreg_xml; /* Configuration Settings */ RISCVCPUConfig cfg; }; 函数 env_cpu 在cpu_loop函数中调用
/** * env_archcpu(env) * @env: The architecture environment * * Return the ArchCPU associated with the environment. */ static inline ArchCPU *env_archcpu(CPUArchState *env) { return container_of(env, ArchCPU, env); } 这个函数用于获取当前环境env的对应的CPUState
随便写写 关于tb寻找和生成 cpu_exec函数,是主循环,在没发生异常和中断的情况下会一直执行tb块
首先会使用tb_lookup在缓存中根据pc值等参数在缓存中寻找下一个tb块
如果没找到,就会根据相同的参数生成新的tb块</content></entry><entry><title>VIM技巧(持续更新)</title><url>https://reisen1969.github.io/post/vim/</url><categories/><tags><tag>vim</tag></tags><content type="html"> 简单记录一下vim的技巧
打开nerdtree 目录 命令行下:
vim ./ vim中:
:e %:h //打开当前文件所在目录 :e ./ //貌似这样也可以 在nerdtree目录下 s //默认打开一个垂直的分割窗口 t //打开一个新的tab窗口 操作tab page gt //默认移动到下一个tab 1gt //打开第一个tab 2gt //打开第二个tab 操作分割窗口 :sp //横向打开分割窗口 :vsp //垂直打开分割窗口 ctrl w + 方向键 //在分割窗口之间移动 内置grep搜索 :vim[grep][!] /{pattern}/[g][j] {file} ... :vim[grep][!] {pattern} {file} ... vimgrep可以简写为vim 举例
在当前目录下的linux-user文件夹下 的c文件中搜索 main( 字符串 :vimgrep main( ./linux-user/*.c 在当前目录下递归寻找 :vimgrep main( ./**/*.c 更多
:cnext, :cn # 当前页下一个结果 :cprevious, :cp # 当前页上一个结果 :clist, :cl # 使用 more 打开 Quickfix 窗口 :copen, :cope, :cw # 打开 Quickfix 窗口,列出所有结果 :ccl[ose] # 关闭 Quickfix 窗口。 lvimgrep 命令 lvimgrep 与 vimgrep 搜索命令基本一样,不同点在于搜索结果不是显示在 Quickfix 中而是显示在 location-list 中 移动 使用大括号在空行之间移动
迅速查找字符串 将光标移动到字符串上然后输入
shift + 8 Ctags使用 生成tags 数据库文件
ctags -R . //递归生成当前根目录的tags文件 vim可以自动匹配tags文件
使用:
ctrl + ] //跳转到定义处 ctrl + T //返回到跳转前的位置 ctrl + W + ] //分割当前窗口,并在新窗口中显示跳转到的定义 ctrl + O //返回之前的位置 :ts //列出所有匹配的标签 CScope使用 生成数据库文件
cscope -Rbq 在vim中:
cscope 命令:(以下都已:cs开头) add : 添加一个新的数据库 (Usage: add file|dir [pre-path] [flags]) find : 查询一个模式 (Usage: find a|c|d|e|f|g|i|s|t name) a: Find assignments to this symbol c: Find functions calling this function d: Find functions called by this function 查找被这个函数调用的函数 e: Find this egrep pattern f: Find this file 查找这个文件 g: Find this definition 查找定义 i: Find files #including this file 查找include了这个文件的所有文件 s: Find this C symbol 查找C符号 t: Find this text string 查找这个文本字符串 help : 显示此信息 (Usage: help) kill : 结束一个连接 (Usage: kill #) reset: 重置所有连接 (Usage: reset) show : 显示连接 (Usage: show) :s替换字符串 :s/helllo/sky/ 替换当前行第一个hello为sky :s/helllo/sky/g 替换当前行的所有hello为sky :n,$s/hello/sky 替换第n行开始到最后一行的第一个hello为sky :n,$s/hello/sky/g 替换第n行开始到最后一行的所有hello为sky :%s/hello/sky 替换每一行的第一个hello为sky :%s/hello/sky/g 替换每一行的所有hello为sky 寄存器 访问寄存器 "registers 正则表达式 全字搜索
/\<word\></content></entry><entry><title>从ELF谈起</title><url>https://reisen1969.github.io/post/elf/</url><categories/><tags><tag>elf</tag><tag>linux</tag></tags><content type="html"> 本文信息来源:
又是一期硬核内容:ELF文件格式
What’s the difference of section and segment in ELF file format
ELF Sections & Segments and Linux VMA Mappings
ELF简介 ELF全称 executable and linkable format 精灵
是一种linux下常用的可执行文件 对象 共享库的标准文件格式
还有许多其他可执行文件格式 PE Mach-O COFF COM
内核中处理elf相关代码参考: binfmt_elf.c
elf中的数据按照Segment(段)和Section(节)两个概念进行划分
ELF文件格式 ELF Header 架构 ABI版本等基础信息 program header table的位置和数量 section header table的位置和数量 Program header table 每个表项定义了一个segment 每个segment可包含多个section Section header table 每个表项定义了一个section readelf命令 可用readelf命令来展示elf文件的相关信息
用法如下:
用法:readelf <选项> elf-文件 显示关于 ELF 格式文件内容的信息 Options are: -a --all Equivalent to: -h -l -S -s -r -d -V -A -I -h --file-header Display the ELF file header -l --program-headers Display the program headers --segments An alias for --program-headers -S --section-headers Display the sections' header --sections An alias for --section-headers -g --section-groups Display the section groups -t --section-details Display the section details -e --headers Equivalent to: -h -l -S 比如,使用readelf来查看date的信息
readelf -l /bin/date 输出
Elf 文件类型为 DYN (Position-Independent Executable file) Entry point 0x38c0 There are 13 program headers, starting at offset 64 程序头: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8 INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x00000000000028a8 0x00000000000028a8 R 0x1000 LOAD 0x0000000000003000 0x0000000000003000 0x0000000000003000 0x0000000000010001 0x0000000000010001 R E 0x1000 LOAD 0x0000000000014000 0x0000000000014000 0x0000000000014000 0x0000000000005cf0 0x0000000000005cf0 R 0x1000 LOAD 0x0000000000019ff0 0x000000000001aff0 0x000000000001aff0 0x00000000000010b0 0x0000000000001268 RW 0x1000 DYNAMIC 0x000000000001ab98 0x000000000001bb98 0x000000000001bb98 0x00000000000001f0 0x00000000000001f0 RW 0x8 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000040 0x0000000000000040 R 0x8 NOTE 0x0000000000000378 0x0000000000000378 0x0000000000000378 0x0000000000000044 0x0000000000000044 R 0x4 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000040 0x0000000000000040 R 0x8 GNU_EH_FRAME 0x0000000000018000 0x0000000000018000 0x0000000000018000 0x0000000000000454 0x0000000000000454 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000019ff0 0x000000000001aff0 0x000000000001aff0 0x0000000000001010 0x0000000000001010 R 0x1 Section to Segment mapping: 段节... 00 01 .interp 02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 06 .dynamic 07 .note.gnu.property 08 .note.gnu.build-id .note.ABI-tag 09 .note.gnu.property 10 .eh_frame_hdr 11 12 .init_array .fini_array .data.rel.ro .dynamic .got 可知:
在加载到内存中时,程序被分成了13个Segment(从PHDR到GNU_RELRO) 每个Segment都包含了1个或者更多的Section Segment vs Section Segment 包含着运行时需要的信息
用于告诉操作系统,段应该被加载到虚拟内存中的什么位置?每个段都有那些权限?(read, write, execute)
每个Segment主要包含加载地址 文件中的范围 内存权限 对齐方式等信息
Section 包含着链接时需要的信息
用于告诉链接器,elf中每个部分是什么,哪里是代码,哪里是只读数据,哪里是重定位信息
每个Section主要包含Section类型 文件中的位置 大小等信息
链接器会把Section放入Segment中
Segment和Section的关系 相同权限的Section会放入同一个Segment,例如.text和.rodata section 一个Segment包含许多Section,一个Section可以属于多个Segment 链接脚本 运行
ld --verbose 可以看到本系统中所用的脚本
我的Archlinux 5.16.13-arch1-1的链接脚本一部分是这样:
.gnu.version_r : { *(.gnu.version_r) } .rela.dyn : { *(.rela.init) *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) *(.rela.fini) *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) *(.rela.ctors) *(.rela.dtors) *(.rela.got) *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) *(.rela.ifunc) } 表示:
.gnu.version_r Section 会被放入 .gnu.version_r Segment
.rela.init等一大堆的Section,会被放入 .rela.dyn Segment
汇编中的伪指令全部都是Section,要等链接之后才会有Segment
NASM中 .section 和.segment 这两个是等效的,都表示 Section
ELF文件分类 可执行文件(ET_EXEC) 可直接运行的程序,必须包含segment
对象文件(ET_REL,*.o) 需要与其他对象文件链接,必须包含section
动态库(ET_DYN,*.so) 与其他对象文件/可执行文件链接
必须同时包含segment和section
ELF的内存映射 查看内存映射情况 cat /proc/[pid]/maps 比如运行
cat /proc/self/maps 查看cat本身的内存映射
563b04d75000-563b04d77000 r--p 00000000 fe:00 6294104 /usr/bin/cat 563b04d77000-563b04d7c000 r-xp 00002000 fe:00 6294104 /usr/bin/cat 563b04d7c000-563b04d7f000 r--p 00007000 fe:00 6294104 /usr/bin/cat 563b04d7f000-563b04d80000 r--p 00009000 fe:00 6294104 /usr/bin/cat 563b04d80000-563b04d81000 rw-p 0000a000 fe:00 6294104 /usr/bin/cat 563b058b6000-563b058d7000 rw-p 00000000 00:00 0 [heap] 7f5f7324b000-7f5f73837000 r--p 00000000 fe:00 6364075 /usr/lib/locale/locale-archive 7f5f73837000-7f5f7383a000 rw-p 00000000 00:00 0 7f5f7383a000-7f5f73866000 r--p 00000000 fe:00 6294921 /usr/lib/libc.so.6 7f5f73866000-7f5f739dc000 r-xp 0002c000 fe:00 6294921 /usr/lib/libc.so.6 7f5f739dc000-7f5f73a30000 r--p 001a2000 fe:00 6294921 /usr/lib/libc.so.6 7f5f73a30000-7f5f73a31000 ---p 001f6000 fe:00 6294921 /usr/lib/libc.so.6 7f5f73a31000-7f5f73a34000 r--p 001f6000 fe:00 6294921 /usr/lib/libc.so.6 7f5f73a34000-7f5f73a37000 rw-p 001f9000 fe:00 6294921 /usr/lib/libc.so.6 7f5f73a37000-7f5f73a46000 rw-p 00000000 00:00 0 7f5f73a70000-7f5f73a92000 rw-p 00000000 00:00 0 7f5f73a92000-7f5f73a94000 r--p 00000000 fe:00 6294911 /usr/lib/ld-linux-x86-64.so.2 7f5f73a94000-7f5f73abb000 r-xp 00002000 fe:00 6294911 /usr/lib/ld-linux-x86-64.so.2 7f5f73abb000-7f5f73ac6000 r--p 00029000 fe:00 6294911 /usr/lib/ld-linux-x86-64.so.2 7f5f73ac7000-7f5f73ac9000 r--p 00034000 fe:00 6294911 /usr/lib/ld-linux-x86-64.so.2 7f5f73ac9000-7f5f73acb000 rw-p 00036000 fe:00 6294911 /usr/lib/ld-linux-x86-64.so.2 7ffec90a6000-7ffec90c8000 rw-p 00000000 00:00 0 [stack] 7ffec918d000-7ffec9191000 r--p 00000000 00:00 0 [vvar] 7ffec9191000-7ffec9193000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] 从左到右为:
虚拟地址的起始和结束 该内存映射的类型flag r(read) w(write) x(execute) p(private) s(shared) 实际对象在该内存映射上相对于起始的偏移量 major:minor: the major and minor number pairs of the device holding the file that has been mapped. 映射文件的索引节点号码 该内存映射文件的名称</content></entry><entry><title>关于我</title><url>https://reisen1969.github.io/about.html</url><categories/><tags/><content type="html"> a righteoux</content></entry></search>