自制操作系統day8 (鼠標數據取得、通往32位模式之路、A20GATE、切換到保護模式、控制寄存器cr0-cr4以及cr8、ALIGNB)

day8

鼠標數據取得方法

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);
}
}
}

這段代碼是一個處理鍵盤和鼠標輸入事件的主循環,通過FIFO隊列緩沖數據,并在屏幕上實時顯示輸入信息。以下是逐行解釋:


一、代碼功能概述

  1. 初始化鼠標FIFO隊列

    fifo8_init(&mousefifo, 128, mousebuf);  // 初始化鼠標FIFO,容量128字節,緩沖區為mousebuf
    • 為鼠標數據分配緩沖區,確保中斷接收的數據能暫存。
  2. 主事件循環

    for (;;) {io_cli();  // 關中斷,防止數據競爭if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { // 無數據時io_stihlt();  // 開中斷并休眠(等待中斷喚醒)} else {  // 有數據時處理// 處理鍵盤或鼠標事件}
    }

二、核心代碼邏輯

1. 關中斷與休眠

  • io_cli():關閉CPU中斷,確保檢查FIFO狀態時不會被中斷打斷。
  • io_stihlt():重新開啟中斷并執行HLT指令,讓CPU休眠直到下一個中斷到來。
    • 目的:避免忙等待(Busy Waiting),節省CPU資源。

2. 處理鍵盤事件

if (fifo8_status(&keyfifo) != 0) {  // 鍵盤FIFO有數據i = fifo8_get(&keyfifo);        // 取出鍵盤數據io_sti();                       // 開中斷(允許新中斷)sprintf(s, "%02X", i);          // 將數據轉為十六進制字符串// 清空屏幕區域(0,16)到(15,31)boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);// 顯示十六進制值putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
}

3. 處理鼠標事件

else if (fifo8_status(&mousefifo) != 0) {  // 鼠標FIFO有數據i = fifo8_get(&mousefifo);            // 取出鼠標數據io_sti();                             // 開中斷sprintf(s, "%02X", i);                // 轉為十六進制字符串// 清空屏幕區域(32,16)到(47,31)boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);// 顯示十六進制值putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
}

三、關鍵函數與參數

1. FIFO操作函數

  • fifo8_init(fifo, size, buf):初始化FIFO隊列,指定容量和緩沖區地址。
  • fifo8_status(fifo):返回隊列中未處理的數據量。
  • fifo8_get(fifo):從隊列頭部取出一個字節數據。

2. 屏幕操作函數

  • boxfill8(vram, scrnx, color, x0, y0, x1, y1)

    在指定屏幕區域填充顏色。

    • vram:顯存地址。
    • scrnx:屏幕水平分辨率(像素)。
    • color:顏色值(如COL8_008484表示藍綠色)。
    • x0, y0, x1, y1:填充區域的左上角和右下角坐標。
  • putfonts8_asc(vram, scrnx, x, y, color, str)

    在屏幕指定位置繪制ASCII字符串。

    • x, y:文本起始坐標。
    • str:要顯示的字符串。

四、代碼執行流程

  1. 初始化
    • 鼠標FIFO隊列初始化,準備接收數據。
  2. 進入主循環
    • 關中斷:防止處理數據時被中斷打斷。
    • 檢查隊列:若鍵盤和鼠標隊列均為空,則開中斷并休眠。
    • 處理數據:若隊列非空,按優先級處理鍵盤數據(先)或鼠標數據(后)。
  3. 顯示數據
    • 將數據轉換為十六進制字符串。
    • 清空屏幕局部區域(避免舊數據殘留)。
    • 在指定位置繪制新數據。

五、設計細節分析

1. 中斷控制

  • 關中斷(io_cli

    在檢查FIFO狀態前關閉中斷,確保檢查與取數據的操作是原子的,避免競態條件(Race Condition)。

  • 開中斷(io_sti

    在取出數據后立即開啟中斷,允許新的中斷處理程序填充隊列。

2. 屏幕刷新優化

  • 局部刷新
    僅刷新顯示數據的區域(如0,1615,31),而非全屏刷新,減少顯存操作開銷。

3. 數據格式轉換

  • 十六進制顯示
    將原始掃描碼或鼠標數據以十六進制顯示,便于調試觀察(如0x1C代表按鍵A按下)。

六、性能與擴展性

  • 低功耗休眠

    通過io_stihlt()在無數據時休眠,顯著降低CPU占用率。

  • 緩沖區容量

    鼠標FIFO容量為128字節,足夠容納多個鼠標數據包(每個包3字節),避免溢出。

  • 擴展性

    可擴展支持更多輸入設備(如游戲手柄),只需增加對應的FIFO隊列和處理邏輯。


總結

這段代碼實現了一個高效的輸入事件處理循環,核心特點包括:

  1. 中斷安全:通過io_cli/io_sti確保數據操作的原子性。
  2. 低功耗:無數據時休眠,減少資源消耗。
  3. 實時反饋:即時將輸入數據可視化,便于調試或用戶交互。

該設計是操作系統輸入子系統的典型實現,適用于需要實時響應外設的場景(如嵌入式系統或低級硬件驅動開發)。


鼠標解讀(1)(harib05a

struct MOUSE_DEC {
unsigned char buf[3], phase;
};
void enable_mouse(struct MOUSE_DEC *mdec);
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);
void HariMain(void)
{
(中略)
struct MOUSE_DEC mdec;
(中略)
enable_mouse(&mdec);
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_decode(&mdec, i) != 0) {
/* 3字節都湊齊了,所以把它們顯示出來*/
sprintf(s, "%02X %02X %02X", mdec.buf[0], mdec.buf[1], mdec.buf[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);
}
}
}
}
}
void enable_mouse(struct MOUSE_DEC *mdec)
{
/* 鼠標有效 */
wait_KBC_sendready();
io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
/* 順利的話,ACK(0xfa)會被送過來 */
mdec->phase = 0; /* 等待0xfa的階段 */
return;
}
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) {
/* 等待鼠標第一字節的階段 */
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;
return 1;
}
return -1; /* 應該不可能到這里來 */
}

以下是代碼中關于鼠標處理的詳細解釋:


一、數據結構與初始化

1. 鼠標解碼結構體 MOUSE_DEC

struct MOUSE_DEC {unsigned char buf[3], phase; // 存儲3字節數據包和解碼階段
};
  • buf[3]:存儲PS/2鼠標的3字節數據包。
  • phase:標識當前解碼階段(0-3),用于追蹤數據包接收進度。

2. 啟用鼠標 enable_mouse()

void enable_mouse(struct MOUSE_DEC *mdec) {wait_KBC_sendready();            // 等待鍵盤控制器就緒io_out8(PORT_KEYCMD, 0xD4);      // 發送命令:下一字節發送到鼠標wait_KBC_sendready();io_out8(PORT_KEYDAT, 0xF4);      // 發送命令:啟用鼠標數據報告mdec->phase = 0;                 // 初始化階段0(等待ACK)
}
  • 關鍵步驟
    1. 通過鍵盤控制器(端口0x64)發送命令0xD4,通知后續數據發送到鼠標。
    2. 發送0xF4到數據端口(0x60),激活鼠標數據報告模式。
    3. 初始化phase=0,等待鼠標返回ACK(0xFA)。

二、鼠標數據解碼 mouse_decode()

int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat) {if (mdec->phase == 0) {          // 階段0:等待ACK(0xFA)if (dat == 0xFA) {           // 收到ACKmdec->phase = 1;         // 進入階段1(接收字節1)}return 0;} else if (mdec->phase == 1) {   // 階段1:接收數據包第1字節mdec->buf[0] = dat;mdec->phase = 2;             // 進入階段2(接收字節2)return 0;} else if (mdec->phase == 2) {   // 階段2:接收數據包第2字節mdec->buf[1] = dat;mdec->phase = 3;             // 進入階段3(接收字節3)return 0;} else if (mdec->phase == 3) {   // 階段3:接收數據包第3字節mdec->buf[2] = dat;mdec->phase = 1;             // 重置階段1(等待下一數據包)return 1;                    // 返回1表示完整數據包}return -1;                       // 錯誤狀態
}
  • 解碼流程
    1. 階段0:等待鼠標返回ACK(0xFA),確認啟用成功。
    2. 階段1-3:依次接收數據包的3個字節,存儲到buf數組。
    3. 完成解碼:當3字節收齊后,返回1,并重置階段為1以接收下一數據包。

三、主循環中的鼠標處理

for (;;) {io_cli(); // 關中斷if (fifo8_status(&mousefifo) != 0) {i = fifo8_get(&mousefifo);   // 從隊列取數據io_sti(); // CLI(Clear Interrupt)和STI(Set Interrupt)// 開中斷if (mouse_decode(&mdec, i) != 0) { // 解碼成功// 顯示3字節數據(十六進制)sprintf(s, "%02X %02X %02X", mdec.buf[0], mdec.buf[1], mdec.buf[2]);boxfill8(...);           // 清空屏幕區域putfonts8_asc(...);      // 顯示數據}}
}
  • 處理流程
    1. 從鼠標FIFO隊列中讀取數據字節。
    2. 調用mouse_decode解碼,若返回1(完整數據包),則顯示三個字節的十六進制值。

四、PS/2鼠標數據包格式

標準3字節數據包

字節位7-0說明
Byte 0Y溢出X溢出
Byte 1X軸移動量(8位補碼,-128~127)水平移動量(左/右)
Byte 2Y軸移動量(8位補碼,-128~127)垂直移動量(下/上)
  • 溢出處理:若X/Y溢出位為1,表示移動量超過8位范圍(需特殊處理)。
  • 符號位:X/Y符號位為1表示負向移動(左/下)。

五、關鍵硬件交互

1. 鍵盤控制器(KBC)端口

  • PORT_KEYCMD (0x64):發送命令到鍵盤控制器。
  • PORT_KEYDAT (0x60):讀寫數據(鍵盤/鼠標)。

2. 中斷與FIFO

  • IRQ12:鼠標中斷,觸發時將數據存入mousefifo
  • FIFO隊列:緩沖中斷接收的數據,主循環異步處理。

六、注意事項

  1. ACK處理:啟用鼠標后需等待0xFA確認,否則后續數據可能錯位。
  2. 數據包順序:需嚴格按順序接收3字節,否則解析錯誤。
  3. 符號與溢出:需正確處理補碼和溢出標志,以準確計算鼠標移動。

總結

這段代碼通過以下步驟實現鼠標功能:

  1. 初始化:激活鼠標并等待ACK。
  2. 數據接收:通過中斷和FIFO緩沖原始字節。
  3. 數據解碼:按階段拼裝3字節數據包。
  4. 數據顯示:將數據包內容輸出到屏幕。

理解PS/2協議和狀態機管理是處理輸入設備的核心,此代碼為操作系統輸入子系統的基礎實現。


鼠標解讀(2)(harib05c


struct MOUSE_DEC {
unsigned char buf[3], phase;
int x, y, btn;
};
/*
結構體里增加的幾個變量用于存放解讀結果。這幾個變量是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) {
/* 如果第一字節正確 用于判斷第一字節
對移動有反應的部分是否在0~3的范圍內;同時還要判斷第一字節對點擊有反應的
部分是否在8~F的范圍內。如果這個字節的數據不在以上范圍內,它就會被舍去。
雖說基本上不這么做也行,但鼠標連線偶爾也會有接觸不良、即將斷線的可能,這
時就會產生不該有的數據丟失,這樣一來數據會錯開一個字節。數據一旦錯位,就
不能順利解讀,那問題可就大了。而如果添加上對第一字節的檢查,就算出了問
題,鼠標也只是動作上略有失誤,很快就能糾正過來,*/
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];
/***標準3字節數據包**| **字節** | **位7-0** | **說明** |
| --- | --- | --- |
| Byte 0 | Y溢出 | X溢出 |
| Byte 1 | X軸移動量(8位補碼,-128~127) | **水平移動量**(左/右) |
| Byte 2 | Y軸移動量(8位補碼,-128~127) | **垂直移動量**(下/上) |
- **溢出處理**:若X/Y溢出位為1,表示移動量超過8位范圍(需特殊處理)。
- **符號位**:X/Y符號位為1表示負向移動(左/下)。*/
if ((mdec->buf[0] & 0x10) != 0) {
mdec->x |= 0xffffff00;
/*|= 是按位或賦值操作符:
等價于 mdec->x = mdec->x | 0xffffff00
作用是將x變量的高24位全部置為1,保持低8位不變
將原始的8位有符號位移量(-128~127)
轉換為32位有符號整數(-2147483648~2147483647)
保持數值不變的同時擴展存儲空間
這個操作在底層設備驅動中很常見,
用于將硬件返回的補碼(two's complement)有符號數擴展為CPU架構的標準整數格式。*/
}
if ((mdec->buf[0] & 0x20) != 0) {
mdec->y |= 0xffffff00;
}
mdec->y = - mdec->y; /* 鼠標的y方向與畫面符號相反 */
return 1;
}
return -1; /* 應該不會到這兒來 */
}
} 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';
/*如果mdec.btn的最低位是1,就把s的第2個字符(注:第1個字
符是s[0] )換成‘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);
}
}

一、3字節數據包結構

PS/2鼠標通過3字節數據包上報移動和按鍵信息,每個字節的位定義如下:

1. 字節0(狀態字節)

名稱說明
7Y溢出(YV)1表示Y軸移動量超出8位補碼范圍(-127~127),需要特殊處理。
6X溢出(XV)1表示X軸移動量超出8位補碼范圍。
5Y符號位(YS)1表示Y軸負方向移動(向上),0表示正方向(向下)。
4X符號位(XS)1表示X軸負方向移動(向左),0表示正方向(向右)。
3保留固定為0。
2中鍵(MB)1表示中鍵按下。
1右鍵(RB)1表示右鍵按下。
0左鍵(LB)1表示左鍵按下。

2. 字節1(X軸移動量)

  • 8位補碼:表示X軸偏移量,范圍-128~127。
    • 正數:向右移動。
    • 負數:向左移動(通過符號位擴展為32位)。

3. 字節2(Y軸移動量)

  • 8位補碼:表示Y軸偏移量,范圍-128~127。
    • 正數:向下移動。
    • 負數:向上移動(需取反適配屏幕坐標系)。

移動鼠標指針(harib05d

這一步就是將鼠標顯示在顯示器上

先隱藏之前的鼠標,然后在鼠標指針的坐標上,加上解讀得到的位移量

但是隱藏鼠標時填充的背景色需要考慮一下

} 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);
/* 鼠標指針的移動 */
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, mx, my, mx + 15, my + 15); /* 隱藏鼠
標 */
mx += mdec.x;
my += mdec.y;
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;
}
sprintf(s, "(%3d, %3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 隱藏坐標 */
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 顯示坐標 */
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); /* 描畫鼠標 */
}

通往32位模式之路(asmhead.nas代碼解釋)

關閉中斷

; PIC關閉一切中斷
; 根據AT兼容機的規格,如果要初始化PIC,
; 必須在CLI之前進行,否則有時會掛起。
; 隨后進行PIC的初始化。
MOV AL,0xff
OUT 0x21,AL
NOP ; 如果連續執行OUT指令,有些機種會無法正常運行
OUT 0xa1,AL
CLI ; 禁止CPU級別的中斷

等同于

io_out(PIC0_IMR, 0xff); /* 禁止主PIC的全部中斷 */
io_out(PIC1_IMR, 0xff); /* 禁止從PIC的全部中斷 */
io_cli(); /* 禁止CPU級別的中斷*/

防止cpu進行模式轉換的時候有中斷發生,同樣PIC初始化時也不允許有中斷發生,所以要屏蔽全部的中斷

NOP指令什么都不做,只是讓CPU休息一個時鐘長的時間

為了讓CPU能夠訪問1MB以上的內存空間,設定A20GATE

; 為了讓CPU能夠訪問1MB以上的內存空間,設定A20GATE
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
;waitkbdout,等同于wait_KBC_sendready

上面這段程序等同于c語言

// 鍵盤控制器命令定義
#define KEYCMD_WRITE_OUTPORT 0xd1   // 寫輸出端口的命令
#define KBC_OUTPORT_A20G_ENABLE 0xdf // 啟用A20 Gate的位掩碼/* A20GATE的設定流程 */
wait_KBC_sendready();               // 等待KBC準備就緒
io_out8(PORT_KEYCMD, KEYCMD_WRITE_OUTPORT); // 發送寫輸出端口命令
wait_KBC_sendready();
io_out8(PORT_KEYDAT, KBC_OUTPORT_A20G_ENABLE); // 設置A20啟用標志
wait_KBC_sendready();               // 確保命令執行完成 /* 這句話是為了等待完成執行指令 */

程序的基本結構與init_keyboard完全相同,功能僅僅是往鍵盤控制電路發送指令。

關鍵數值解析:

  1. 0xd1:鍵盤控制器命令,表示要寫輸出端口(Output Port)

D1h,準備寫Output端口。隨后通過60h端口寫入的字節,會被放置在Output Port中。

輸出0xdf所要完成的功能,是讓A20GATE信號線變成ON的狀態。

  1. 0xdf:輸出端口的數據配置,二進制形式為11011111,其中:
    • 第1位(bit0):系統復位控制(保持0)
    • 第2位(bit1):A20 Gate使能位(1=啟用)
    • 其他位:保留原有配置(如鍵盤/鼠標中斷使能)

執行過程解析:

  1. 等待KBC準備好接收命令(wait_KBC_sendready
  2. 發送0xd1命令告訴KBC要設置輸出端口
  3. 發送0xdf數據實際配置輸出端口,啟用A20地址線
  4. 最后等待確保配置完成

完整位掩碼示意圖:

0xdf = 1101 1111│││││└─ 保持復位信號不變(0)│└── A20 Gate使能(1)└─── 保留原有中斷設置(鍵盤/鼠標中斷)

這個操作允許CPU訪問超過1MB的內存地址空間,是進入32位保護模式的必要步驟。

常見的鍵盤控制器命令(8042兼容指令):

0xD1 寫輸出端口       - 用于設置系統標志(如A20 Gate)
0x60 寫命令字節      - 修改控制器的配置參數
0xAE 啟用鍵盤接口     - 允許鍵盤輸入
0xAF 禁用鍵盤接口     - 禁止鍵盤輸入
0x20 讀命令字節      - 讀取當前配置
0xDD 禁用A20線       - 關閉高位地址線
0xDF 啟用A20線       - 打開高位地址線(與0xD1配合使用)
0xD0 讀輸出端口      - 獲取當前輸出端口狀態
0xE0 讀測試輸入      - 讀取測試端口(P1/P2)狀態
0xF0-0xFF 自檢指令   - 執行控制器自檢
; 設置A20 Gate示例
CALL    waitkbdout      ; 等待控制器就緒
MOV     AL,0xD1         ; 寫輸出端口命令
OUT     0x64,AL         ; 發送到命令端口
CALL    waitkbdout  
MOV     AL,0xDF         ; 輸出端口數據(A20使能)
OUT     0x60,AL         ; 發送到數據端口

這些命令通過兩個I/O端口操作:

  • 0x64: 命令端口(寫命令)
  • 0x60: 數據端口(讀/寫數據)

關于A20GATE信號線:

這條信號線的作用是什么呢?它能使內存的1MB以上的部分變成可使用狀態。最初出現電腦的
時候,CPU只有16位模式,所以內存最大也只有1MB。后來CPU變聰明了,可以使用很大的內存了。但為了兼容舊版的操作系統,在執行激活指令之前,電路被限制為只能使用1MB內存。和鼠標的情況很類似喲。A20GATE信號線正是用來使這個電路停止從而讓所有內存都可以使用的東西。

切換到保護模式

; 切換到保護模式
[INSTRSET "i486p"] ; “想要使用486指令”的敘述LGDT [GDTR0] ; 設定臨時GDTMOV EAX,CR0AND EAX,0x7fffffff ; 設bit31為0(為了禁止分頁)OR EAX,0x00000001 ; 設bit0為1(為了切換到保護模式)MOV CR0,EAXJMP pipelineflush
pipelineflush:MOV AX,1*8 ; 可讀寫的段 32bitMOV DS,AXMOV ES,AXMOV FS,AXMOV GS,AXMOV SS,AX

INSTRSET指令,是為了能夠使用386以后的LGDT,EAX,CR0等關鍵字。

LGDT指令,不管三七二十一,把隨意準備的GDT給讀進來。對于這個暫定的GDT,我們以后還要重新設置。

然后將CR0(control register 0)這一特殊的32位寄存器的值代入EAX,并將最高位置為0,最低位置為1,再將這個值返回給CR0寄存器。這樣就完成了模式轉換,進入到不用頒的保護模式。

通過代入CR0而切換到保護模式時,要馬上執行JMP指令。所以我們也執行這一指令。為什么要執行JMP指令呢?因為變成保護模式后,機器語言的解釋要發生變化。CPU為了加快指令的執行速度而使用了管道(pipeline)這一機制,就是說,前一條指令還在執行的時候,就開始解釋下一條甚至是再下一條指令。因為模式變了,就要重新解釋一遍,所以加入了JMP指令。

進入保護模式以后,段寄存器的意思也變了(不再是乘以16后再加算的意思了),除了CS以外所有段寄存器的值都從0x0000變成了0x0008。

控制寄存器cr0-cr4以及cr8:

一、CR0(Control Register 0)

核心功能:控制 CPU 的基本運行模式與內存管理。

名稱功能
0PE (Protection Enable)保護模式開關:1=啟用保護模式(支持分段內存管理)。
1MP (Monitor Coprocessor)浮點協處理器監控:與 TS 位配合,控制浮點指令是否觸發異常。
2EM (Emulation)浮點模擬:1=強制浮點指令觸發異常(由軟件模擬 FPU)。
3TS (Task Switched)任務切換標記:1=任務切換后未保存 FPU 狀態,觸發 #NM 異常。
4ET (Extension Type)協處理器類型:已棄用(現代 CPU 固定為 1)。
5NE (Numeric Error)浮點錯誤處理:1=浮點錯誤觸發 #MF 異常,0=通過中斷控制器處理。
16WP (Write Protect)寫保護:1=禁止內核寫用戶只讀頁(防止篡改代碼段)。
18AM (Alignment Mask)對齊檢查:與 EFLAGS.AC 位配合,啟用內存對齊檢查。
31PG (Paging Enable)分頁開關:1=啟用分頁機制(需同時設置 PE=1)。

典型操作示例

; 啟用保護模式和分頁
mov eax, cr0
or eax, 0x80000001; 設置 PE(位0)和 PG(位31)
mov cr0, eax

二、CR1(Control Register 1)

保留寄存器:在 x86/x64 架構中未定義具體功能,通常不使用。


三、CR2(Control Register 2)

核心功能:存儲觸發頁面錯誤(#PF)的線性地址。

  • 用途:當發生缺頁異常時,CR2 保存導致異常的訪問地址。

  • 示例:在缺頁處理程序(Page Fault Handler)中,可通過讀取 CR2 定位錯誤地址:復制下載

    c

    void page_fault_handler(void) {uintptr_t fault_addr;asm("mov %%cr2, %0" : "=r"(fault_addr));
    // 處理缺頁...}
    

四、CR3(Control Register 3)

核心功能:存儲當前頁表結構的基地址(物理地址)。

  • 分頁模式
    • 32 位分頁:CR3 指向頁目錄基地址(Page Directory Base)。
    • PAE 分頁:CR3 指向頁目錄指針表(PDPT)。
    • 64 位分頁:CR3 指向 PML4 表(4 級頁表)。
功能
31:12頁表基地址(對齊到 4KB 邊界)
3PCD (Page Cache Disable)
4PWT (Page Write Through)
63:5保留(64 位模式下使用高 32 位)

示例

; 設置頁表基地址(假設頁目錄物理地址為 0x1000)
mov eax, 0x1000
mov cr3, eax

五、CR4(Control Register 4)

核心功能:控制擴展功能(如虛擬化、安全特性)。

名稱功能
5PAE (Physical Address Extension)物理地址擴展:1=啟用 36 位物理地址(支持 64GB 內存)。
7PGE (Page Global Enable)全局頁表項:1=允許 TLB 緩存全局頁(標記為 Global 的頁表項)。
9OSFXSRSSE/浮點支持:1=啟用 SSE 指令和 FXSAVE/FXRSTOR 指令。
10OSXMMEXCPTSSE 異常處理:1=允許 SSE 指令觸發 #XM 異常。
13VMXEIntel VT-x 虛擬化:1=啟用 CPU 虛擬化擴展。
14SMXESafer Mode Extensions:與 SMEP/SMAP 配合的安全擴展。
17PCIDE進程上下文 ID:1=啟用 PCID(減少 TLB 刷新)。
20SMEP (Supervisor Mode Execution Prevention)內核執行保護:1=禁止內核執行用戶空間代碼。
21SMAP (Supervisor Mode Access Prevention)內核訪問保護:1=禁止內核訪問用戶空間內存(需配合 EFLAGS.AC)。

典型操作示例

; 啟用 PAE 和 SSE 支持
mov eax, cr4
or eax, (1 << 5) | (1 << 9); 設置 PAE(位5)和 OSFXSR(位9)
mov cr4, eax

六、CR8(Control Register 8,僅 x64)

核心功能:控制任務優先級(Task Priority Level, TPL),用于管理中斷屏蔽。

  • 用途:在 x64 中替代傳統 PIC/APIC 的中斷優先級控制。
  • 位定義:低 4 位表示 TPL(0-15),數值越小優先級越高。

七、實際應用場景

1.?操作系統啟動

  • 啟用保護模式:設置 CR0.PE=1。
  • 啟用分頁:設置 CR0.PG=1,并配置 CR3 指向頁表。
  • 啟用 SSE:設置 CR4.OSFXSR=1。

2.?虛擬化

  • 啟用 VT-x:設置 CR4.VMXE=1,并配置 VMCS 結構。

3.?安全防護

  • 防止內核漏洞:設置 CR4.SMEP=1 和 CR4.SMAP=1,阻止內核執行或訪問用戶空間數據。

總結

  • CR0:控制基礎模式(保護模式、分頁、寫保護)。
  • CR2:定位缺頁異常地址。
  • CR3:管理分頁結構的基地址。
  • CR4:啟用高級功能(虛擬化、安全擴展、SSE)。
  • CR8(x64):中斷優先級管理。

bootpack的轉送

; 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
191
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4 ; 從柱面數變換為字節數/4
SUB ECX,512/4 ; 減去 IPL
CALL memcpy

簡單來說,這部分程序只是在調用memcpy函數。

大致相當于c語言

memcpy(bootpack, BOTPAK, 512*1024/4);
memcpy(0x7c00, DSKCAC, 512/4 );
memcpy(DSKCAC0+512, DSKCAC+512, cyls * 512*18*2/4 - 512/4);
//memcpy(轉送源地址, 轉送目的地址, 轉送數據的大小);
//轉送數據大小是以雙字為單位的,所以數據大小用字節數除以4來指定。

memcpy(0x7c00, DSKCAC, 512/4);

DSKCAC是0x00100000,所以上面這句話的意思就是從0x7c00復制512字節到0x00100000。這正好是將啟動扇區復制到1MB以后的內存去的意思。

memcpy(DSKCAC0+512, DSKCAC+512, cyls * 512182/4-512/4);

將始于0x00008200的磁盤內容,復制到0x00100200那里。

轉送數據大小”的計算有點復雜,因為它是以柱面數來計算的,所以需要減去啟動區的那一部分長度。這樣始于0x00100000的內存部分,就與磁盤的內容相吻合了

IMUL”integer multipule”(整數乘法)是乘法運算,

SUB“substract”(減法)“是減法運算。

bootpack是asmhead.nas的最后一個標簽。haribote.sys是通過asmhead.bin和bootpack.hrb連接起來而生成的(可以通過Makefile確認),所以asmhead結束的地方,緊接著串連著bootpack.hrb最前面的部分。

memcpy(bootpack, BOTPAK, 512*1024/4);

→ 從bootpack的地址開始的512KB內容復制到0x00280000號地址去。

AI詳細解釋內存分配和連接機制:

以下是haribOS 05b版本的內存分配與連接機制詳解:

asmhead.nas
; 內存布局關鍵定義
BOTPAK  EQU     0x00280000      ; 內核加載地址
DSKCAC  EQU     0x00100000      ; 磁盤緩存區
DSKCAC0 EQU     0x00008000      ; 臨時緩存區; 啟動流程內存分配
1. 引導扇區(ipl10.bin)- 加載地址:0x7c00 (BIOS標準位置)- 大小:512字節- 職責:加載asmhead.bin和內核2. 啟動頭(asmhead.bin)- 加載地址:0xc200- 職責:* 設置VGA模式(顯存地址0x000a0000)* 收集硬件信息到0x0ff0-0x0fff* 初始化GDT/IDT* 啟用保護模式3. 內核(bootpack.hrb)- 加載地址:0x00280000 (BOTPAK)- 職責:包含操作系統的所有核心功能內存連接實現:
; 內核復制過程
MOV     ESI,bootpack    ; 源代碼位置(編譯時確定)
MOV     EDI,BOTPAK      ; 目標地址0x00280000
MOV     ECX,512*1024/4 ; 復制512KB
CALL    memcpy          ; 執行內存塊復制; 磁盤數據緩存
MOV     ESI,0x7c00      ; 引導扇區原始位置
MOV     EDI,DSKCAC      ; 復制到0x00100000
MOV     ECX,512/4       ; 復制512字節
CALL    memcpy內存管理關鍵技術:
1. 分段機制:通過GDT將物理地址映射為線性地址
```nasm
LGDT    [GDTR0]         ; 加載全局描述符表
MOV     EAX,CR0
OR      EAX,0x00000001  ; 啟用保護模式
MOV     CR0,EAX
  1. 物理內存映射:
0x00000000-0x000fffff : 實模式可用內存
0x00100000-0x00267fff : 磁盤緩存區
0x00280000-0x003fffff : 內核區域
0x000a0000-0x000bffff : VGA顯存區域

該設計通過分階段加載和精確的內存地址控制,實現了從實模式到保護模式的無縫銜接。

bootpack的啟動

; 必須由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
; 內核加載最終階段
MOV EBX,BOTPAK         ; EBX = 0x00280000(內核基地址)
MOV ECX,[EBX+16]       ; 讀取需要復制的數據長度(字節數)
ADD ECX,3              ; 對齊處理:字節數+3
SHR ECX,2              ; 轉換為雙字數(除以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 ; 跳轉到內核入口點
  1. 內核結構定義
    • [BOTPAK+12]:加載地址(物理內存地址)
    • [BOTPAK+16]:需要復制的數據長度
    • [BOTPAK+20]:數據在文件內的偏移量
  2. 地址計算
    • 源地址 = BOTPAK基地址(0x00280000) + 文件內偏移量
    • 目標地址 = 直接指定的物理地址
  3. 復制優化
    • 通過ADD ECX,3SHR ECX,2將字節數轉換為4字節對齊的雙字數,提高復制效率
  4. 內存復制準備
    • 通過內核頭部的元數據(偏移量+12/+16/+20)確定需要復制的數據位置和大小
    • 對非4字節對齊的數據進行向上取整處理
  5. 控制權移交
    • 2*8?表示GDT中代碼段選擇子(第二個描述符,特權級0)
    • 0x0000001b?是內核入口點的偏移地址
    • 該跳轉同時完成從引導程序到內核、從匯編到C代碼的轉換

內存布局示意圖:

0x00280000 (BOTPAK)
+-------------------+
| 內核頭部信息       | → 包含加載地址、數據長度等元數據
+-------------------+
| 實際內核代碼       | → 復制到指定內存位置
+-------------------+↓
0x0000001b (入口點)

逐行分析:

1.?MOV?EBX,BOTPAK:BOTPAK定義為0x00280000,這是內核加載的基地址。EBX現在指向內核的起始位置。

2.?MOV?ECX,[EBX+16]:從EBX+16處讀取值到ECX。這里EBX是內核基址,+16可能對應內核頭部中的某個字段,比如代碼段長度。

3.?ADD?ECX,3?和?SHR?ECX,2:將ECX加3后右移兩位,相當于將字節數轉換為4字節對齊的雙字數。例如,若原長度是5字節,加3得8,右移兩位得2,即復制2個雙字(8字節)。

4.?JZ?skip:如果ECX為0,跳轉到skip,避免執行無意義的復制操作。

5.?MOV?ESI,[EBX+20]:從EBX+20讀取源地址偏移量。加上EBX得到實際的源地址(文件內的位置)。

6.?ADD?ESI,EBX:計算源地址的絕對位置,即BOTPAK?+?偏移量。

7.?MOV?EDI,[EBX+12]:從EBX+12讀取目標地址,這是代碼段應加載的內存地址。

8.?CALL?memcpy:調用內存復制函數,將數據從源復制到目標地址,復制ECX次,每次4字節。

9.?skip::跳過復制的標簽。

10.?MOV?ESP,[EBX+12]:設置棧指針為內核中指定的初始值,通常指向代碼段加載地址,可能內核在此處預設了棧空間。

11.?JMP?DWORD?2*8:0x0000001b:長跳轉到內核入口點。2*8是代碼段選擇子(GDT中第二個描述符,特權級0),0x1b是偏移量。這可能對應內核的入口函數。

關鍵點解釋:

  • 內核頭部結構:EBX+12、+16、+20的偏移量對應內核的不同元數據,如加載地址、長度、文件偏移,這些在編譯時由鏈接器生成。
    -?段選擇子28:在GDT中,每個描述符占8字節。28表示選擇第二個描述符,對應代碼段,其基址可能為0,因此入口點0x1b是實際的物理地址。
    -?控制權轉移:通過JMP指令切換到內核的代碼段,開始執行內核代碼。

總結:這段代碼完成了從引導程序到內核的交接,包括復制內核代碼到正確位置,設置棧指針,并跳轉到內核入口點,從而啟動操作系統。

內存分布圖

0x00000000 - 0x000fffff : 雖然在啟動中會多次使用,但之后就變空。(1MB

0x00100000 - 0x00267fff : 用于保存軟盤的內容。(1440KB

0x00268000 - 0x0026f7ff : 空(30KB

0x0026f800 - 0x0026ffff : IDT2KB

0x00270000 - 0x0027ffff : GDT64KB

0x00280000 - 0x002fffff : bootpack.hrb512KB

0x00300000 - 0x003fffff : 棧及其他(1MB

0x00400000 - :

waitkbdout

waitkbdout:
IN AL,0x64
AND AL,0x02
IN AL,0x60 ; 空讀(為了清空數據接收緩沖區中的垃圾數據)
JNZ waitkbdout ; AND的結果如果不是0,就跳到waitkbdout
RET

它與wait_KBC_sendready相同,但也添加了部分處理,就是從OX60號設備進行IN的處理。也就是說,如果控制器里有鍵盤代碼,或者是已經累積了鼠標數據,就順便把它們讀取出來。

ALIGNB

ALIGNB指令的意思是,一直添加DBO,直到時機合適的時候為止ALIGNB 16的情況下,地址能被16整除的時候,就稱為“時機合適”。如果最初的地址能被16整除,則ALIGNB指令不作任何處理。

ALIGNB 16
GDT0:
RESB 8 ; NULL selector
DW 0xffff,0x0000,0x9200,0x00cf ; 可以讀寫的段(segment)32bit
DW 0xffff,0x0000,0x9a28,0x0047 ; 可以執行的段(segment)32bit(bootpack用)
DW 0
GDTR0:
DW 8*3-1
DD GDT0
ALIGNB 16
bootpack:

這是操作系統中全局描述符表(GDT)的初始化代碼,用于進入保護模式時的內存分段管理:


ALIGNB 16          ; 16字節對齊
GDT0:RESB 8         ; 空描述符(必須存在但不可用); 數據段描述符(32位可讀寫)DW 0xffff      ; 段界限低16位DW 0x0000      ; 段基址低16位DB 0x00        ; 段基址中間8位DB 0x92        ; 訪問權限(P=1, DPL=0, S=1, Type=讀寫數據段)DB 0x00        ; 段界限高4位 + 標志(G=0, D/B=0)DB 0xcf        ; 段基址高8位 + 標志(G=1, D/B=1,表示4KB粒度); 代碼段描述符(32位可執行,bootpack專用)DW 0xffff      ; 段界限低16位DW 0x0000      ; 段基址低16位DB 0x28        ; 段基址中間8位(0x00280000)DB 0x9a        ; 訪問權限(P=1, DPL=0, S=1, Type=執行代碼段)DB 0x47        ; 段界限高4位 + 標志(G=0, D/B=0)DB 0x00        ; 段基址高8位GDTR0:DW 8*3-1       ; GDT界限(3個描述符*8字節 -1)DD GDT0        ; GDT物理地址ALIGNB 16
bootpack:          ; 內核代碼開始位置

關鍵參數解析:

  1. 數據段描述符
    • 基地址:0x00000000
    • 段界限:0xfffff(4GB空間)
    • 權限:0x92(存在、特權級0、可讀寫)
  2. 代碼段描述符
    • 基地址:0x00280000(對應BOTPAK地址)
    • 段界限:0xfffff(4GB空間)
    • 權限:0x9a(存在、特權級0、可執行)

這個GDT配置使內核代碼運行在0x00280000開始的線性地址空間,數據段覆蓋整個4GB內存空間,為后續內存管理提供基礎。

GDT0也是一種特定的GDT。0號是空區域(null sector),不能夠在那里定義段。1
195
號和2號分別由下式設定。
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);

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

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

相關文章

IP大科普:住宅IP、機房IP、原生IP、雙ISP

不同類型的IP在跨境電商、廣告營銷、網絡技術、數據收集等領域都有廣泛應用&#xff0c;比如常見的住宅IP、機房IP、原生IP、雙ISP等&#xff0c;這些IP分別都有什么特點&#xff0c;發揮什么作用&#xff0c;適合哪些業務場景&#xff1f; 一、IP類型及其作用 1.住宅IP 住宅…

Elasticsearch面試題帶答案

Elasticsearch面試題帶答案 Elasticsearch面試題及答案【最新版】Elasticsearch高級面試題大全(2025版),發現網上很多Elasticsearch面試題及答案整理都沒有答案,所以花了很長時間搜集,本套Elasticsearch面試題大全,Elasticsearch面試題大匯總,有大量經典的Elasticsearch面…

Eigen與OpenCV矩陣操作全面對比:最大值、最小值、平均值

功能對比總表 功能Eigen 方法OpenCV 方法主要區別最大值mat.maxCoeff(&row, &col)cv::minMaxLoc(mat, NULL, &maxVal, NULL, &maxLoc)Eigen需要分開調用&#xff0c;OpenCV一次獲取最小值mat.minCoeff(&row, &col)cv::minMaxLoc(mat, &minVal, NU…

echarts之雙折線漸變圖

vue3echarts實現雙折線漸變圖 echarts中文官網&#xff1a;https://echarts.apache.org/examples/zh/index.html 效果圖展示&#xff1a; 整體代碼如下&#xff1a; <template><div id"lineChart" style"width:100%;height:400px;"></di…

MD編輯器推薦【Obsidian】含下載安裝和實用教程

為什么推薦 Obsidian &#xff1f; 免費 &#xff08;Typora 開始收費了&#xff09;Typora 實現的功能&#xff0c;它都有&#xff01;代碼塊可一鍵復制 文件目錄支持文件夾 大綱支持折疊、搜索 特色功能 – 白板 特色功能 – 關系圖譜 下載 https://pan.baidu.com/s/1I1fSly…

vue 鼠標經過時顯示/隱藏其他元素

方式一&#xff1a; 使用純css方式 , :hover是可以控制其他元素 1、 當兩個元素是父子關系 <div class"all_" ><div> <i class"iconfont icon-sun sun"></i></div> </div> .all_{} .sun {display: none; /* 默認…

靜態網站部署:如何通過GitHub免費部署一個靜態網站

GitHub提供的免費靜態網站托管服務可以無需擔心昂貴的服務器費用和復雜的設置步驟&#xff0c;本篇文章中將一步步解如何通過GitHub免費部署一個靜態網站&#xff0c;幫助大家將創意和作品快速展現給世界。 目錄 了解基礎情況 創建基礎站點 在線調試站點 前端項目部署 部署…

Pytorch里面多任務Loss是加起來還是分別backward? | Pytorch | 深度學習

當你在深度學習中進入“多任務學習(Multi-task Learning)”的領域,第一道關卡可能不是設計網絡結構,也不是準備數據集,而是:多個Loss到底是加起來一起backward,還是分別backward? 這個問題看似簡單,卻涉及PyTorch計算圖的構建邏輯、自動求導機制、內存管理、任務耦合…

基于DPABI提取nii文件模板的中心點坐標

基于DPABI提取nii文件模板的中心點坐標 在使用DPABI&#xff08;Data Processing Assistant for Resting-State fMRI&#xff09;處理NIfTI&#xff08;.nii&#xff09;文件時&#xff0c;可以通過以下步驟提取模板中每個坐標點的中心點坐標&#xff1a;https://wenku.csdn.n…

redis 基本命令-17 (KEYS、EXISTS、TYPE、TTL)

Redis 基本命令&#xff1a;KEYS、EXISTS、TYPE、TTL Redis 提供了一套基本命令&#xff0c;這些命令對于管理密鑰和了解數據庫中存儲的數據至關重要。這些命令雖然簡單&#xff0c;但提供了對 Redis 實例的結構和狀態的重要見解。具體來說&#xff0c;KEYS、EXISTS、TYPE 和 …

加速leveldb查詢性能之Cache技術

加速leveldb查詢性能之Cache技術 目錄 1.兩種Cache2.Table Cache3.Block Cache 注&#xff1a;本節所有內容更新至星球。 學習本節之前最好提前需要學習前面兩篇文章&#xff0c;這樣便好理解本節內容。 多圖文講解leveldb之SST/LDB文件格式 【深入淺出leveldb】LRU與哈希表 1.…

5.2.3 使用配置文件方式整合MyBatis

本實戰通過使用Spring Boot和MyBatis技術棧&#xff0c;實現了文章列表顯示功能。首先&#xff0c;通過創建ArticleMapper接口和對應的ArticleMapper.xml配置文件&#xff0c;實現了對文章數據的增刪改查操作&#xff0c;并通過單元測試驗證了功能的正確性。接著&#xff0c;通…

Node.js 源碼架構詳解

Node.js 的源碼是一個龐大且復雜的項目&#xff0c;它主要由 C 和 JavaScript 構成。要完全理解每一部分需要大量的時間和精力。我會給你一個高層次的概述&#xff0c;并指出一些關鍵的目錄和組件&#xff0c;幫助你開始探索。 Node.js 的核心架構 Node.js 的核心可以概括為以…

【NLP 76、Faiss 向量數據庫】

壓抑與痛苦&#xff0c;那些輾轉反側的夜&#xff0c;終會讓我們更加強大 —— 25.5.20 Faiss&#xff08;Facebook AI Similarity Search&#xff09;是由 Facebook AI 團隊開發的一個開源庫&#xff0c;用于高效相似性搜索的庫&#xff0c;特別適用于大規模向…

Go 語言簡介

1. Go 語言簡介 1.1 什么是 Go 語言 Go語言&#xff0c;通常被稱為Golang&#xff0c;是由Google在2007年開始開發&#xff0c;并在2009年正式發布的一種開源編程語言。Go語言的設計初衷是解決大型軟件開發中的效率和可維護性問題&#xff0c;特別是在多核處理器和網絡化系統…

VMware虛擬機突然無法ssh連接

遇到的情況&#xff1a; 功能全部正常的情況下&#xff0c;沒有修改任何配置&#xff0c;重啟電腦之后無法ssh連接 其實不太可能的可能原因&#xff1a; 1、虛擬機內部sshd服務未運行 systemctl status sshd systemctl start sshd 2、檢查SSH端口監聽 netstat -an | grep :…

[ 計算機網絡 ] | 宏觀談談計算機網絡

&#xff08;目錄占位&#xff09; 網絡間通信&#xff0c;本質是不同的兩個用戶通信&#xff1b;本質是兩個不同主機上的兩個進程間通信。 因為物理距離的提升&#xff0c;就衍生出了很多問題。TCP/IP協議棧 / OSI七層模型&#xff0c;將協議分層&#xff0c;每一層都是為了…

Oracle 11g導出數據庫結構和數據

第一種方法&#xff1a;Plsql 利用plsql可視化工具導出&#xff0c;首先根據步驟導出表結構&#xff1a; 工具(Tools)->導出用戶對象(export user objects)。 其次導出數據表結構&#xff1a; 工具(Tools)->導出表(export Tables)->選中表->sql inserts(where語…

跟Gemini學做PPT:匯報背景圖尋找指南

PPT 匯報背景圖尋找指南 既然前端功能已經完善&#xff0c;現在可以專注于匯報了。對于 PPT 背景圖&#xff0c;你有幾個選擇&#xff1a; 1. 內置模板和主題&#xff1a; 優點&#xff1a; 最簡單、快速&#xff0c;PowerPoint、Keynote、Google Slides 等演示軟件都內置了…

【Hadoop】大數據技術之 HDFS

目錄 一、HDFS 概述 1.1 HDFS 產出背景及定義 1.2 HDFS 優缺點 1.3 HDFS 組成架構 1.4 HDFS 文件塊大小 二、HDFS 的Shell 操作 三、HDFS 的讀寫流程&#xff08;面試重點&#xff09; 3.1 HDFS 寫數據流程 3.2 HDFS 讀數據流程 四、DataNode 4.1 DataNode 的工作機制…