從裸機啟動開始運行一個C++程序(十三)

前序文章請看:
從裸機啟動開始運行一個C++程序(十二)
從裸機啟動開始運行一個C++程序(十一)
從裸機啟動開始運行一個C++程序(十)
從裸機啟動開始運行一個C++程序(九)
從裸機啟動開始運行一個C++程序(八)
從裸機啟動開始運行一個C++程序(七)
從裸機啟動開始運行一個C++程序(六)
從裸機啟動開始運行一個C++程序(五)
從裸機啟動開始運行一個C++程序(四)
從裸機啟動開始運行一個C++程序(三)
從裸機啟動開始運行一個C++程序(二)
從裸機啟動開始運行一個C++程序(一)

圖形模式

我們前面章節所有的指令,都是在顯卡的文字模式下運行的,通過給顯存中直寫字符的方式來輸出文字。

照理說,保持著文字模式我們也可以進入IA-32e模式并執行64位指令,但我們最終的目標是運行C++程序,為了可以更好地發揮C++的作用,筆者打算后續用C++實現一套簡單的UI繪制API,因此,咱們需要使用圖形模式。

回到MBR

我們在MBR中首先有這么一段指令:

; 調用0x10號BIOS中斷,清屏
mov al, 0x03
mov ah, 0x00
int 0x10 

當時筆者一直將它解釋位清屏,其實0x10中斷的威力遠不如此,al中配置的0x03其實是讓顯卡進入文字模式。由于我們發起此終端是讓顯卡重新進入了一次文字模式,因此顯存會被重新初始化,進而達到的清屏的目的。

那么我們此時還可以調用這個中斷,讓顯卡進入圖形模式。當然,圖形模式中有不同的分辨率、色域等等,這些需要顯卡的支持。但它們有一套VGA標準模式,是所有顯卡都會支持的,我們這里選擇通用模式中配置最高模式,這個模式下支持320×200分辨率,256色。開啟此模式的方法很簡單,只需要讓al0x13,然后調用0x10中斷即可開啟。我們把MBR最前面的清屏指令改成下面這樣:

; 開啟320*200分辨率256色圖形模式(此時顯存0xa0000~0xaf9ff)
mov al, 0x13
mov ah, 0x00
int 0x10 

由于此模式下,顯存也發生了改變,所以我們把GDT中,顯存對應的2號段也做對應的更改:

; 2號段
; 基址0xa0000,上限0xaf9ff,覆蓋所有顯存
mov [es:0x10], word 0xf9ff ; Limit=0x00f9ff,這是低16位
mov [es:0x12], word 0x0000 ; Base=0x0a0000,這是低16位
mov [es:0x14], byte 0x0a   ; 這是Base的高8位
mov [es:0x15], byte 1_00_1_001_0b ; P=1, DPL=0, S=1, Type=001b, A=0
mov [es:0x16], byte 0_1_00_0000b  ; G=0, D/B=1, AVL=00, Limit的高4位是0000
mov [es:0x17], byte 0      ; 這是Base的高8

圖形模式的像素點陣

當然,在此模式下,顯存不再被解釋為ASCII了,而是每個字節表示一個像素點的顏色。既然是一個字節,自然也就支持一共256種顏色。這256種顏色理論上可以自行通過調色盤來進行更改的,但我們為了簡化問題,就使用默認的這256種顏色。

這256種顏色如下:
默認256色

上圖是按16×16來排布的,因此用十六進制很好找,比如說0x46就是上圖中第五行的第七個顏色(0起始)。

當進入了圖形模式后,原本我們寫的一些g_cursor_info之類的東西肯定是都不適用了,而且也沒辦法直接輸出文字。不過在此之前,我們還是先來驗證一下是否正常啟用了圖形模式,咱們在主函數中設置一些像素點的顏色看看效果:

void Entry() {for (int i = 0; i < 200; i++) {for (int j = 0; j < 320; j++) {int offset = i * 320 + j;SetVMem(offset, j & 0x0f); // 縱向條紋}}
}

執行效果如下:
縱向條紋

雖然看著有點暈,但至少說明我們圖形模式是OK了。趁熱打鐵,咱們寫一些工具,用于在圖形模式上繪制點和矩形:

#include <stdint.h>
extern void SetVMem(long addr, uint8_t data);// 設置畫布(背景色)
void SetBackground(uint8_t color) {for (int i = 0; i < 320 * 200; i++) {SetVMem(i, color);}
}// 畫一個點
void DrawPoint(int x, int y, uint8_t color) {SetVMem(y * 320 + x, color);
}// 畫一個矩形
void DrawRect(int x, int y, int width, int length, uint8_t color) {for (int i = 0; i < width; i++) {for (int j = 0; j < length; j++) {DrawPoint(x + i, y + j, color);}}
}void Entry() {// 背景設置為白色SetBackground(0x0f);// 畫兩個矩形DrawRect(50, 30, 40, 40, 0x28); // 紅色正方形DrawRect(85, 20, 10, 30, 0x30); // 綠色條狀
}

運行效果如下:
運行效果

可以看到,圖形之間的遮蓋關系也是符合我們預期的,綠色的會覆蓋紅色的部分,因為它的代碼更靠后。

字體文件

那么,圖形模式下我們要想輸出文字該怎么辦?這時候就需要將文字轉換為點陣集,也就是繪制字體。

舉例來說,對于'A'字符,我們可以繪制一個8×16的點陣:

{0b00000000,0b00011000,0b00011000,0b00011000,0b00011000,0b00100100,0b00100100,0b00100100,0b00100100,0b01111110,0b01000010,0b01000010,0b01000010,0b11100111,0b00000000,0b00000000,
}

上面字體配置中,按照二進制位來表示當前這個像素要不要渲染。比如我們可以嘗試一下:

// 繪制字符
void DrawCharacter(int x, int y, char ch, uint8_t color) {// 目前無視ch,只繪制'A'(void)ch;uint8_t font[] = {0b00000000,0b00011000,0b00011000,0b00011000,0b00011000,0b00100100,0b00100100,0b00100100,0b00100100,0b01111110,0b01000010,0b01000010,0b01000010,0b11100111,0b00000000,0b00000000,};// 開始繪制for (int i = 0; i < 16; i++) {for (int j = 0; j < 8; j++) {// 如果當前位置是否需要渲染,則設置顏色if (font[i] & (1 << (7 - j))) {SetVMem(x + j + (y + i) * 320, color);}}}
}void Entry() {// 背景設置為白色SetBackground(0x0f);// 寫個字符DrawCharacter(50, 50, 'A', 0x30); // 綠色的A
}

運行結果如下:
運行結果2

因此,如法炮制,我們只需要設置其他的字符的字體即可。這里采用這樣的方法,我們在工程中添加font.c,專門用于保存所有ASCII對應的繪制字體,然后再根據此方法去改造putchar函數,即可實現在圖形模式中輸出文本。

這里的相關代碼會放在附件中(13-1),讀者可以自行取用,正文中則不再贅述。不過這里需要注意,因為字體文件比較大,所以讀盤的時候要多讀幾個扇區,否則字體文件裝載不全,已經在附件中呈現,讀者請自行檢驗。下面是運行效果:

圖形模式下的文字輸出

分頁機制

由于咱們現在一直都是在內核態上運行程序的,并沒有任何用戶態進程的存在,所以可能大家并沒有這種體會。但是,我們設想一下,假如我們真的寫了一個成熟的操作系統,這個操作系統不可能說把自己加載完了就hlt在那里了。我們肯定是要去操作它,讓它執行其他的應用程序。

但只要你去執行一個應用程序,那么就要給這個應用程序去分配必要的內存。應用程序只能訪問它這一片空間,而不能夠越界。我們能想到最簡單的辦法就是給每一個進程分一個段,等它結束時再回收這個段,以便用于以后繼續分配。

但這樣一來有一個問題,隨著越來越多的進程運行然后釋放,我們的內存可能就被劃分為了許多碎片,而段的分配是連續的,假如說明明此時可用空間是夠的,但是連續的可用空間不足,那就沒辦法分段。

所以為了解決這個問題,386引入了「頁機制」,簡單來說,就是讓程序使用「虛擬地址」,由對應的頁地址部件將其轉化為物理地址后再讀入內存。而這種訪問方式的最小單位是4KB,被稱為「頁」。換句話說,物理內存中只有4KB內是連續的,頁之間可能是不連續的,但在用戶程序看來,卻是完全連續的,因為用戶程序看到的是虛擬地址。

不過研究分頁機制會跟本文的主題偏離,因此我們在這里不做過于詳細的展開,讀者只需要知道這一機制引入的原因,以及配置方法即可,我們的目的是進入IA-32e模式,然后執行64位的指令而已,還沒必要大動干戈去調度用戶程序。因此這里只會簡單配置一個頁表,然后讓程序正常運行即可。

一個頁是4KB,而且要求必須是4KB對其的,也就是說頁的起始位置不可以是任意的,而必須是4KB的整數倍,也就是說地址的低12位都是0。而且,既然我們要管理這些頁,內核至少要知道,當前分配了哪些頁,他們的物理地址在哪里,一些內部配置是什么樣的。這就需要我們來維護一張「頁表」。

頁表

頁表中應當包含頁的物理地址,以及一些其他的配置項。在IA-32模式中,一個頁表項占4字節,詳情如下:

二進制位符號意義
0P存在位
1RW只讀or可寫
2US權限級別
3PWT通寫
4PCD高速緩存禁止
5A訪問
6D臟頁
7PS頁尺寸
8G全局
9-11AVL保留位
12-31Base(12-31)頁首地址的12-31位

由于頁地址要求低12位必須為0,所以頁表項中也只需要記錄高20位即可,剩下的那12位留給了頁的配置。由于我們不涉及內核調度的問題,因此很多配置項我們都可以忽略,只需要關注PRW即可,其它項暫且都置0就好了。

但是這里有另一個問題,32位尋址空間最多有4GB,一個頁是4KB,也就是說最多我們可以有1048576個頁,而每個頁表項占4字節,那么光是頁表都要有4194304字節,也就是4MB的空間。

而實際情況是,計算機的物理內存可能根本沒有這么大,很多頁是虛擬的,在不用的時候可能被操作系統寫入了硬盤中,等需要時再做換頁,但它的頁表項卻是必須在內存中的,這就活活占用了4MB的死空間。在386那個年代可能物理內存也就幾MB吧,這顯然是不能接受的。

于是乎,我們想了一個辦法,就是把頁表進行分層,首先,我只占用4KB的大小,先做一個頁目錄,然后頁目錄項在指向一個子頁表,頁表中再去配置實際的頁。這樣一來有兩個好處,第一,如果我只用到一少部分的頁配置,我就不用占用太多空間,因為我可以選擇只配置其中一部分頁表。第二,頁表本身也可以作為活躍的頁,跟硬盤進行交換,所以內存中只有頁目錄這4KB的死空間被占用而已,這個大小是比較能接受的。

另一個問題就是,一但CPU開啟分頁模式后,通過段寄存器和偏移地址計算出的線性地址就不再是物理地址,而是要通過頁的轉換成為物理地址。但咱們現在內核已經加載進去了,開啟分頁模式之后,我們得保證后續指令能夠正常運行,就得讓這時的線性地址正好等于物理地址才行,否則經過轉換后地址飛了,就不能正常執行后續指令了。因此,我們至少得把低1MB的空間都分好頁,并且保證這些頁是連續的,正好映射到物理內存中。而1MB的空間按照每頁4KB來算的話,咱們得配256個頁。

頁配置

接下來,我們將在kernel.nas中配置頁表,至于頁表的位置無所謂,你選一個沒有被占用的就好了,這里我們以0x20000為例。

不過既然要在0x20000這個位置寫東西,咱們就得有個段來支持才行,索性我們就配一個0x0地址起始的輔助段,方便我們操作這部分空間。在MBR中添加:

; 4號段-輔助段
; 基址0x0000,上限0xfffff 
mov [es:0x20], word 0x00ff ; Limit=0x00ff,這是低16位
mov [es:0x22], word 0x0000 ; Base=0x0a0000,這是低16位
mov [es:0x24], byte 0x00   ; 這是Base的高8位
mov [es:0x25], byte 1_00_1_001_0b ; P=1, DPL=0, S=1, Type=001b, A=0
mov [es:0x26], byte 1_1_00_0000b  ; G=1, D/B=1, AVL=00, Limit的高4位是0000
mov [es:0x27], byte 0      ; 這是Base的高8

同時記得修改GDT的長度:

; 下面是gdt信息的配置(暫且放在0x07f00的位置)
mov ax, 0x07f0
mov es, ax
mov [es:0x00], word 39      ; 因為目前配了5個段,長度為40,所以limit為39
mov [es:0x02], dword 0x7e00 ; GDT配置表的首地址

回到kernel.nas,按照之前的規劃,0x20000~0x20fff的位置就是我們的頁目錄。由于咱們只需要配256個頁,所以只需要用一張頁表即可覆蓋,那我們就選0x21000~0x21fff作為這個頁表。

那么我們在頁目錄的起始配一個指向0x21000位置頁表的頁目錄項,代碼如下:

; 選取0x20000作為頁目錄的地址
;0x20000開始的4096字節都是頁目錄
; 頁目錄的第一項指向一個頁表
; 頁表范圍是0x21_000~0x21_fff
mov [es:0x20000], word 00100001_000_0_0_0_0_0_0_0_1_1b ; P=1, RW=1, US=0, PWT=0, PCD=0, A=0, D=0, G=0, PAT=0, AVL=000, Base=0x00021_000(取前20位)

然后我們再來配置這張頁表,由于要分256個頁,所以我們通過一個循環來計算頁首地址以及寫入頁表項,每次循環時,頁的基址應該加4KB,配置項保持不變。讓低1MB的內存空間正好映射到前256個頁表項中,也就是把0x0~0xfff分給0號頁,0x1000~0x1fff分給1號頁,以此類推。代碼如下:

; 接下來要把0x21000~0x213ff范圍里的256個頁表項進行配置(正好對應低1MB)
mov ecx, 256
mov ebx, 0x21000 ; 頁表項地址
mov edx, 0x00000_003 ; 頁表配置項值,從0地址開始.l1:mov [es:ebx], edxadd ebx, 4add edx, 0x0001_000 ; 相當于頁物理地址+4KBloop .l1

配置好頁表項后,還需要告訴CPU我們的頁目錄配置在哪里了,IA-32架構的CR3寄存器就是做這件事的,它用來保存頁目錄首地址。

; 將頁目錄首地址寫入CR3寄存器中
mov eax, 00100000_00000000_0_0_00b ; PCD=0, PWT=0, BASE=0x00020_000
mov cr3, eax

做好一切準備后,我們就可以開啟分頁模式了,開啟分頁模式的方法是更改CR0的第31位(也就是最高位),將其置1,然后CPU就會立即進入分頁模式。

; 開啟CR0的第31位(最高位)以開啟分頁機制
mov eax, cr0
or eax, 0x80000000
mov cr0, eax 

我們保持后續所有流程不變,如果還能正常看到輸出,證明我們的分頁是沒問題的。

運行結果

到此步為止的實例代碼,筆者會放在附件中(13-2),讀者可自行參考。

AMD64模式

目前咱們已經做好了萬全的準備,接下來我們就是要進軍64位了。在此之前我們來介紹一下相關背景知識。

IA-32e架構

筆者在前面章節曾經介紹過IA-64和AMD64的愛恨情仇,AMD64架構是由AMD最先提出,在IA-32架構的基礎上進行擴展的64位指令集,向下兼容IA-32模式,但IA-64并不兼容IA-32。由于它是由IA-32模式擴展來的,并且兼容IA-32,因此這種工作模式也被稱作IA-32e(IA-32 Extension)模式,或IA-32擴展模式。

首先是硬件擴充,原本的一些寄存器被重新擴展至64位,并以r開頭(re-extend,再次擴展的意思),例如rax,它的低32位就是eax。除此之外,該架構還額外提供了8個通用寄存器,分別命名為r8~r15。它們也可以拆出32位寄存器來用,例如r8的低32位是r8d,低16位是r8w,低8位是r8b

rax,rbx,rcx,rdx寄存器結構示意圖如下:
rax

rsp,rbp,rsi,rdi寄存器結構示意圖如下:
rsi

r8~r15寄存器結構示意圖如下:
r8

相信大家用起來都不會太陌生。與此同時,ip也擴展位rip,用于加載64位指令。

接下來的問題就是,如何使CPU進入IA-32e模式,并執行64位指令?這里還有一段路程要走,大家稍安勿躁。

4層分頁

我們知道IA-32e模式是要支持64位指令的,同時也擁有理論64位的尋址空間,最大支持16EB的內存空間。倘若說這玩意我們還是按4KB來分頁,頁表又要炸掉了。由于要支持64位地址,因此這時的頁表項也被擴充到8字節,低12位的含義不變,只是向上多擴展了32位用于表示頁地址的。也正因如此,現在一個頁表里最多只能配512個頁了,頁表更加得炸。

所以,之前的2層分頁就不滿足要求了,IA-32e模式提供了4層分頁的方式,顧名思義就是從最初的頁目錄到最后的頁表最多有4級。這4層分別被叫做PML4,PDPT,PDT,PT

剛才我用了「最多」這個詞,也就是說,我們不一定真的分4層,也可以在2層或者3層就截止,再哪層截止決定了頁的大小。

這里的邏輯是,假如我們真的配置到了PT層,那么它指向的頁就是4KB的,正常來說PDT層的項應當指向一個PT層表才對,但如果這時我們不讓他再繼續分級,而是直接指向頁的話,這個頁的大小就是2MB(相當于這2MB不再細分成512個4KB的頁了,而是直接作為一個頁)。

同理,如果我們直接在PDT層就直接指向頁的話,那么這一個頁就是1GB,相當于1GB不再細分成512個2MB的頁。

那么如何表示當前頁表項是指向實際頁呢,還是指向下一個層級的頁表呢?這就用到了PS位,當PS為1時,表示它指向下一級頁表,為0時表示直接指向頁。

由于我們當前就利用前1MB的空間就夠了,所以,咱們就選擇按2MB大小分頁,這樣只需要分1個頁就夠用了,所以,我們配置頁表只到PDT層,并且這一層里只需要配一個頁表項,讓它的物理地址是0x0起始的就夠了。代碼如下:

; IA-32e模式下使用4級分頁,PML4-PDPT-PDT-PT
; 這種模式下的頁目錄項、頁表項都是8字節,高52位是物理地址
; 如果在PDPT上就設PS=1的話,實際上只分2層,每頁1GB
; 如果在PDT上就設PS=1的話,實際上只分3層,每頁2MB
; 到了PT上PS必須設為1(因為此模式最多支持4層),每頁4KB
; 這里就將0x00000~0x1FFFFF2MB的空間放到首個頁結構項中; PML4
mov [es:0x20000], dword 0x21003 ; P=1, RW=1, PS=0(表示有下級頁表), Base=0x21_000
mov [es:0x20004], dword 0; PDPT
mov [es:0x21000], dword 0x22003 ; P=1, RW=1, PS=0(表示有下級頁表), Base=0x22_000
mov [es:0x21004], dword 0; PDT
mov [es:0x22000], dword 0x83 ; P=1, RW=1, PS=1(不再有下級頁表,所以直接按2MB分頁), Base=0x0,大小2MB
mov [es:0x22004], dword 0; 將頁目錄首地址寫入CR3寄存器中
mov eax, 00100000_00000000_0_0_00b ; PCD=0, PWT=0, BASE=0x00020_000
mov cr3, eax

與此同時,我們現在的頁表項是按8字節一項來配置的,這件事得讓CPU知道,不然它還是按4字節作為一項來解析的話就麻煩了。這時我們需要將CR4的第5位置1,表示「開啟64位地址擴展的分頁模式」,代碼如下:

; 開啟CR4的第5位,開啟64位地址擴展的分頁模式
mov eax, cr4
or eax, 0x20
mov cr4, eax

進入IA-32e模式

在操作CR0開啟分頁模式之前,我們還需要通知CPU開啟IA-32e模式,畢竟咱們剛才配置的這種4層分頁模式在i386模式下是不支持的,所以開啟分頁模式之前,還要先開啟IA-32e模式的標記位,這樣CPU才能知道,開啟分頁模式的同時,改用IA-32e模式。

在IA-32e架構下,有一些擴展的寄存器,它們并沒有像CR0這樣直接集成到指令集中(這就是它區別于IA-64的地方,雖然擴充到了64位,但仍舊保持對IA-32的高度兼容),而是通過獨立編址的方式,利用特殊的指令來操作,這些寄存器稱為MSR(Modelspecific Register, 模式指定寄存器)。我們現在要操作的寄存器叫做EFER(Extended FeatureEnable Register, 擴展功能開啟寄存器),它的第8位表示開啟長模式(long mode,大家姑且認為這個跟IA-32e模式等價)。代碼如下:

; 讀取EFER(Extended FeatureEnable Register)
mov ecx, 0xc0000080 ; rdmsr指令要求將需要讀取的寄存器地址放在ecx中
rdmsr ; 讀取結果會放在eax中
or eax, 0x100 ; 設置第8位,LME(Long Mode Enable)
wrmsr ; 將eax的值寫入ecx對應地址的寄存器

準備工作做完以后,我們就可以操作CR0來開啟分頁模式了,與此同時,CPU也會進入IA-32e模式:

; 開啟CR0的第31位(最高位)以開啟分頁機制
mov eax, cr0
or eax, 0x80000000
mov cr0, eax ; 此時也會進入IA-32e模式

我們同樣保持后續邏輯不變,來運行一下看看效果:
運行效果

沒有問題!我們成功在IA-32e模式中配置了頁表,并運行了內核程序。

到此的項目源碼會放到附件(13-3)中。

等等……為什么這里能運行成功呢?進入IA-32e模式后難道不應該切換到64位指令集嗎?為什么我們32位的Kernel還能正常運行?不知道讀者到這里會不會有這樣的疑問。

這就是IA-32e模式的偉大之處了,因為它可以完全兼容原本的IA-32指令,所以即使我們已經進入了IA-32e模式,它也能正常執行后續所有的IA-32指令。

那究竟如何真正地運行64位指令呢?這是我們下一章要研究的內容。

小結

本篇我們為進入64位模式做足了前戰準備。首先講解了進入圖形模式的方法并在這個模式下輸出圖像和文字;之后講解了分頁的概念和配置方法,并針對IA-32e模式的4層分頁方式做了講解;最后成功進入了IA-32e模式,同時也體驗了在IA-32e模式下無感知運行IA-32架構的指令。

本篇所有的項目源碼將會通過附件的方式(demo_code_13)上傳,供讀者參考。

下一篇,我們會介紹IA-32e架構的獨有64位指令,和執行指令的方法,還會把之前寫的C語言代碼改用64位方式編譯,并與內核進行適配。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166395.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166395.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166395.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

uniapp打包的ipa上架到appstore的傻瓜式教程

? 轉載&#xff1a;uniapp打包的ipa上架到appstore的傻瓜式教程 uniapp打包 在HBuilder X編輯器中打開需要打包的項目&#xff0c;然后點擊上面菜單欄中 發行 > 原生App-云打包&#xff0c;對以下彈出的彈窗進行內容填寫 ? 填寫完成以后&#xff0c;點擊打包操作 ? ? …

自定義責任鏈Filter實現

核心接口 Filter package com.xxx.arch.mw.nbp.common.extension;import com.xxx.commons.data.domain.Result;/*** date 2023/08/25*/ public interface Filter {Result invoke(final Invoker invoker, final Invocation invocation); } Invoker package com.xxx.arch.mw.…

修改mysql的密碼(每一步都圖文解釋哦)

當你想要連接本機數據庫時&#xff0c;是不是有可能突然忘記了自己的數據庫密碼? 在此文中&#xff0c;我們來詳細解決一下如何去修改自己的數據庫密碼&#xff0c;并使用Navicat來連接測試 1.停止mysql服務 打開終端&#xff0c;鍵入命令,將mysql服務先停止掉&#xff0c;…

設置滾動條樣式

滾動條樣式&#xff1a; 下面是代碼&#xff1a; <!doctype html> <html lang"en"><head><meta charset"UTF-8"><title>CSS3自定義滾動條</title><style>header {font-family: Lobster, cursive;text-align: c…

亞馬遜云科技向量數據庫助力生成式AI成功落地實踐探秘(二)

向量數據庫選擇哪種近似搜索算法&#xff0c;選擇合適的集群規模以及集群設置調優對于知識庫的讀寫性能也十分關鍵&#xff0c;主要需要考慮以下幾個方面&#xff1a; 向量數據庫算法選擇 在 OpenSearch 里&#xff0c;提供了兩種 k-NN 的算法&#xff1a;HNSW (Hierarchical…

基于STM32設計的智能防盜單車鎖(馬蹄鎖)設計_升級版

1. 前言 隨著共享單車和自行車的普及,人們對自行車的安全和便利性提出了更高的要求。智能防盜馬蹄鎖是一種基于 STM32 單片機的智能鎖,可以通過手機藍牙實現開鎖和關鎖控制,同時具備 GPRS 防盜預警功能,提高了自行車的安全性和使用便利性。 通過智能防盜馬蹄鎖,用戶可以…

YOLOv5和改進后模型的result.csv文件對比

import pandas as pd import matplotlib.pyplot as plt# 讀取CSV文件 df1 pd.read_csv(rE:\xianyu\yolo.csv) df2 pd.read_csv(rE:\xianyu\final.csv)# 獲取列名 columns df1.columns# 循環繪制每一列的對比圖 for column in columns:plt.figure(figsize(8, 5))plt.plot(df1…

技術分享 | 在 IDE 插件開發中接入 JCEF 框架

項目背景 當前的開發環境存在多種不同語言的 IDE&#xff0c;如 JetBrains 全家桶、Eclipse、Android Studio 和 VS Code 等等。由于每個 IDE 各有其特定的語言和平臺要求&#xff0c;因此開發 IDE 插件時&#xff0c;需要投入大量資源才能盡可能覆蓋大部分工具。同時&#xf…

數據結構算法-貪心算法

引言 貪心&#xff1a;人只要有 “需求“ &#xff0c;都會有有點“貪“&#xff0c; 這種“貪“是一種選擇&#xff0c;或者“”取舍“ RTS&#xff08;即時戰略&#xff09;游戲&#xff1a; 帝國時代里 首先確保擁有足夠的人口 足夠的糧食&#xff0c;足夠的戰略資源 足夠的…

干貨科普 | 不同類型的機器人及其在工作中的應用

原創 | 文 BFT機器人 制造商在其操作中使用各種類型的機器人&#xff0c;每種機器人都具有特定的能力和功能。我們將討論制造業中使用的一些最常見類型的機器人&#xff0c;以及哪種機器人可能最適合您的應用。 01 關節機器人 關節式機器人是一種工業機器人&#xff0c;具有一…

npm,yarn,pnpm 清理緩存

目錄 1&#xff0c;為什么要清理緩存1&#xff0c;緩存文件太多&#xff0c;影響系統運行2&#xff0c;不同源會有區別 2&#xff0c;命令2.1&#xff0c;npm2.2&#xff0c;yarn2.3&#xff0c;pnpm 1&#xff0c;為什么要清理緩存 1&#xff0c;緩存文件太多&#xff0c;影響…

關于easy-es的聚合問題

es實體類&#xff1a; public class ChemicalES {IndexId(type IdType.CUSTOMIZE)private Long id;HighLightIndexField(fieldType FieldType.TEXT, analyzer "ik_max_word")private String name;IndexField(fieldType FieldType.KEYWORD)private List<Stri…

三數之和 Java版

題目描述&#xff1a;給你一個包含 n 個整數的數組 nums&#xff0c;判斷 nums 中是否存在三個元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 請你找出所有和為 0 且不重復的三元組。 注意&#xff1a;答案中不可以包含重復的三元組。 輸入&#xff1a;nums …

“土味出海”,屢試不爽!短劇出海引來新一輪爆發?

土味和“錢途”并存的短劇不僅在國內迅猛爆發&#xff0c;今年下半年以來海外市場多部爆火短劇出現&#xff0c;“短劇出海”的話題熱度不斷攀升&#xff0c;絲毫不差2021年網文出海的盛況。 “霸總的愛&#xff0c;日入千萬刀”&#xff0c;是真實存在的&#xff01; 據統計…

tp8 使用rabbitMQ(1)簡單隊列

php8.0 使用 rabbitmq 要使用 3.6版本以上的&#xff0c; 并且還要開啟 php.ini中的 socket 擴展 php think make:command SimpleMQProduce //創建一個生產者命令行 php think make:command SimpleMQConsumer //創建一個消費者命令行 代碼中的消息持久化的說明 RabbitMQ 消息持…

#Js篇:var、let和 const

var 聲明的變量具有函數作用域或者全局作用域&#xff1b;存在變量提升&#xff0c;即在執行上下文中&#xff0c;變量會被提升到函數或全局作用域的頂部&#xff0c;但初始化的賦值不會提升&#xff1b;可以重復聲明同一個變量不會報錯&#xff1b;可以被重新賦值&#xff1b…

vue3 + Ant Design Vue國際化,組件默認顯示中文

官網 寫入App.vue <template><ConfigProvider :locale"zhCN"><router-view :key"$route.fullPath"></router-view></ConfigProvider> </template><script setup> import { ConfigProvider } from "ant-de…

某上市證券公司:管控文件交換行為 保護核心數據資產

客戶簡介 某上市證券公司成立于2001年&#xff0c;經營范圍包括&#xff1a;證券經紀、證券投資咨詢、證券承銷與保薦、證券自營等。經過多年發展&#xff0c;在北京、上海、深圳、重慶、杭州、廈門等國內主要中心城市及甘肅省內各地市設立了15家分公司和80余家證券營業部。20…

軟文推廣中如何提煉好產品賣點,媒介盒子分享

內容同質化的時代下&#xff0c;企業應該如何讓用戶留下印象&#xff0c;并且成功將產品賣出去&#xff0c;核心思維就在于提煉產品賣點&#xff0c;產品賣點是銷量提升的關鍵&#xff0c;相信企業在推廣產品時都會有點困惑&#xff0c;感覺自家產品和競品比起來只是logo、外觀…

壓縮與解壓縮

核心接口 Compressor package com.xxx.arch.mw.nbp.common.csp;import com.xxx.arch.mw.nbp.common.constant.CommonConstants; import com.xxx.arch.mw.nbp.common.exception.NbpException;import static com.xxx.arch.mw.nbp.common.csp.CompressorEnum.ZSTD;/*** */ publi…