国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

《30天自制操作系統(tǒng)》學習筆記(七)

這篇具有很好參考價值的文章主要介紹了《30天自制操作系統(tǒng)》學習筆記(七)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

先體驗一下編譯仿真方法:

30天自制操作系統(tǒng)光盤代碼在下面鏈接,但是沒有編譯仿真工具:
https://gitee.com/zhanfei3000/30dayMakeOS

仿真工具在下面鏈接:
https://gitee.com/909854136/nask-code-ide

這是一個集成的編譯仿真工具,只需要把上面仿真工具的文件夾:
\nask-code-ide-master\crtools
《30天自制操作系統(tǒng)》學習筆記(七),學習,筆記
復制到源碼文件加下,并改名為z_tools就可以按照書中的方法編譯仿真了:
\30dayMakeOS-master\z_tools
z_tools如下:
《30天自制操作系統(tǒng)》學習筆記(七),學習,筆記
在代碼30dayMakeOS-master\01_day目錄下執(zhí)行下面編譯仿真指令就可以看到仿真出的操作系統(tǒng)了。
《30天自制操作系統(tǒng)》學習筆記(七),學習,筆記
《30天自制操作系統(tǒng)》學習筆記(七),學習,筆記
后面章節(jié)源碼寫了 makefile就簡單了,只要輸入make就可以編輯了 然后再輸入make run就可以仿真了

標題一、代碼執(zhí)行順序(前內容六天的內容)

ipl10.nas–>asmhead.nas–>boopack.c

標題二、代碼閱讀

1.ipl10.nas(將軟盤內容拷貝到內存中)

; haribote-ipl
; TAB=4
; 讀取軟盤內容到內存中,然后跳轉到0xc200開始執(zhí)行,就是asmhead.nas文件
CYLS	EQU		10				; CYLS=10 讀取是10個柱面

		ORG		0x7c00			; 指明程序裝載地址

; 以下這段是FAT12格式軟盤專用代碼  0x7c00--0x7dff 
		JMP        entry
        DB        0x90
        DB        "HARIBOTE"         ; 啟動區(qū)的名字可以是任意的,但必須是8字節(jié)
        DW        512                ; 每個扇區(qū)(sector)的大小必須為512字節(jié)
        DB        1                  ;(cluster)的大小必須為1個扇區(qū)
        DW        1                  ; FAT的起始位置(一般從第一個扇區(qū)開始)
        DB        2                  ; FAT的個數(shù)(必須為2)
        DW        224                ; 根目錄的大小(一般設為244項)
        DW        2880               ; 該磁盤的的大小(必須為2880扇區(qū))
        DB        0xf0               ; 磁盤的種類(必須為0xfd)
        DW        9                  ; FAT的長度(必須為9扇區(qū))
        DW        18                 ; 一個磁道(track)有幾個扇區(qū)(必須為18)
        DW        2                  ; 磁頭數(shù)(必須為2)
        DD        0                  ; 不使用分區(qū)(必須為0)
        DD        2880               ; 重寫一次磁盤大小
        DB        0,0,0x29           ; 意義不明,固定
        DD        0xffffffff         ; (可能是)卷標號碼
        DB        "HARIBOTEOS "      ; 磁盤名稱(11字節(jié))
        DB        "FAT12   "         ; 磁盤格式名稱(8字節(jié))
        RESB    18                   ; 先騰出18字節(jié)

; 程序核心

entry:
		MOV		AX,0			; AX=0 初始化寄存器
		MOV		SS,AX			; SS=AX=0
		MOV		SP,0x7c00		; SP=0x7c00
		MOV		DS,AX			; DS=AX=0

; 讀磁盤(從軟盤中讀數(shù)據裝到內存中0x8200--0x83ff  

		MOV		AX,0x0820		; AX=0x0820 設置緩存區(qū)的段地址
		MOV		ES,AX			; ES=AX=0x0820 ES:BX就是緩存區(qū)的地址
		MOV		CH,0			; CH=0 CH表示柱面號
		MOV		DH,0			; DH=0 DH表示磁頭號
		MOV		CL,2			; CL=2 CL表示扇區(qū)號
readloop:
		MOV		SI,0			; SI=0, 用于記錄錯誤次數(shù),實現(xiàn)試錯功能(非必須功能)
retry:
		MOV		AH,0x02			; AH=0x02 13號中斷所需參數(shù),表示操作類型,0x02(讀盤),0x03寫盤,0x04校驗,0x0c尋道
		MOV		AL,1			; AL=1 AL處理對象的扇區(qū)數(shù),表示一次只能讀取1個扇區(qū)
		MOV		BX,0			; BX=0 緩沖地址
		MOV		DL,0x00			; DL=0x00 DL表示驅動器號
		INT		0x13			; BIOS提供的服務,用于操作軟盤
		JNC		next			; CF=0,跳轉到next執(zhí)行
		ADD		SI,1			; SI=SI+1,記錄嘗試的次數(shù),實現(xiàn)試錯功能(非必須功能)
		CMP		SI,5			; 
		JAE		error			; SI >= 5 跳轉到error執(zhí)行
		MOV		AH,0x00			; SI<5 AH=0x00 清空INT 0x13的錯誤碼
		MOV		DL,0x00			; DL=0x00 設置驅動器號
		INT		0x13			; 
		JMP		retry			; 跳轉到retry執(zhí)行
next:
		MOV		AX,ES			; AX=ES
		ADD		AX,0x0020		; AX=AX+0x0020
		MOV		ES,AX			; ES=AX ES向后移動了一個扇區(qū)的大小
		ADD		CL,1			; CL=CL+1 扇區(qū)號加1
		CMP		CL,18			; 
		JBE		readloop		; CL <= 18 跳轉到readloop執(zhí)行
		MOV		CL,1			; CL > 18 CL=1 
		ADD		DH,1			; DH=1 準備讀取磁頭0的內容
		CMP		DH,2			; 
		JB		readloop		; DH < 2 跳轉到readloop執(zhí)行
		MOV		DH,0			; DH>=2 說明已讀取完成
		ADD		CH,1			; CH=CH+1 準備讀取下一個柱面
		CMP		CH,CYLS			; 
		JB		readloop		; CH < CYLS 跳轉到readloop執(zhí)行

; 磁盤內容裝載內容的結束地址告訴haribote.sys

		MOV		[0x0ff0],CH		; [0x0ff0]=CH 將CYLS的值寫入到內存地址0x0ff0中,可以參考asmhead.nas中對應的變量賦值
		JMP		0xc200			; 跳轉到0xc200

error:
		MOV		SI,msg			;SI=msg 顯示錯誤信息
putloop:
		MOV		AL,[SI]			; AL=[SI] 讀取[SI]內存中的信息
		ADD		SI,1			; SI=SI+1
		CMP		AL,0			; 
		JE		fin				; AL==0, 錯誤信息顯示完畢,跳轉到fin
		MOV		AH,0x0e			; AH=0x0e 設置顯示屬性
		MOV		BX,15			; BX=15 設置顯示屬性
		INT		0x10			; 調用BIOS顯示服務
		JMP		putloop			; 
fin:
		HLT						; 讓CPU停止等待命令
		JMP		fin				; 
msg:
		DB		0x0a, 0x0a		; 
		DB		"load error"
		DB		0x0a			; 
		DB		0				; 

		RESB	0x7dfe-$		; 

		DB		0x55, 0xaa		; 按規(guī)定設置字節(jié)

2.asmhead.nas(完成一些不能用c語言實現(xiàn)的功能,因為編碼問題,有一些亂碼,大概能看明白)

; haribote-os boot asm
; TAB=4

[INSTRSET "i486p"]

VBEMODE	EQU		0x105			; 1024 x  768 x 8bit 彩色
; 顯示模式
;	0x100 :  640 x  400 x 8bit 彩色
;	0x101 :  640 x  480 x 8bit 彩色
;	0x103 :  800 x  600 x 8bit 彩色
;	0x105 : 1024 x  768 x 8bit 彩色
;	0x107 : 1280 x 1024 x 8bit 彩色

BOTPAK	EQU		0x00280000		; 加載bootpack
DSKCAC	EQU		0x00100000		; 磁盤緩存的位置
DSKCAC0	EQU		0x00008000		; 磁盤緩存的位置(實模式)

; BOOT_INFO 相關
CYLS	EQU		0x0ff0			; 引導扇區(qū)設置
LEDS	EQU		0x0ff1
VMODE	EQU		0x0ff2			; 關于顏色的信息
SCRNX	EQU		0x0ff4			; 分辨率X
SCRNY	EQU		0x0ff6			; 分辨率Y
VRAM	EQU		0x0ff8			; 圖像緩沖區(qū)的起始地址

		ORG		0xc200			;  這個的程序要被裝載的內存地址

; 確認VBE是否存在

		MOV		AX,0x9000
		MOV		ES,AX
		MOV		DI,0
		MOV		AX,0x4f00
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

; 檢查VBE的版本

		MOV		AX,[ES:DI+4]
		CMP		AX,0x0200
		JB		scrn320			; if (AX < 0x0200) goto scrn320

; 取得畫面模式信息

		MOV		CX,VBEMODE
		MOV		AX,0x4f01
		INT		0x10
		CMP		AX,0x004f
		JNE		scrn320

; 畫面模式信息的確認
		CMP		BYTE [ES:DI+0x19],8		;顏色數(shù)必須為8
		JNE		scrn320
		CMP		BYTE [ES:DI+0x1b],4		;顏色的指定方法必須為4(4是調色板模式)
		JNE		scrn320
		MOV		AX,[ES:DI+0x00]				;模式屬性bit7不是1就不能加上0x4000
		AND		AX,0x0080
		JZ		scrn320					; 模式屬性的bit7是0,所以放棄

;	畫面設置

		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] ;VRAM的地址
		MOV		[VRAM],EAX
		JMP		keystatus

scrn320:
		MOV		AL,0x13						; VGA圖、320x200x8bit彩色
		MOV		AH,0x00
		INT		0x10
		MOV		BYTE [VMODE],8		; 記下畫面模式(參考C語言)
		MOV		WORD [SCRNX],320
		MOV		WORD [SCRNY],200
		MOV		DWORD [VRAM],0x000a0000

;	通過 BIOS 獲取指示燈狀態(tài)

keystatus:
		MOV		AH,0x02
		INT		0x16 			; keyboard BIOS
		MOV		[LEDS],AL

;	PIC關閉一切中斷
;	根據AT兼容機的規(guī)格,如果要初始化PIC,
;	必須在CLI之前進行,否則有時會掛起。
;	隨后進行PIC的初始化。

		MOV		AL,0xff
		OUT		0x21,AL
		NOP						; 如果連續(xù)執(zhí)行OUT指令,有些機種會無法正常運行
		OUT		0xa1,AL

		CLI						; 禁止CPU級別的中斷

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

		CALL	waitkbdout
		MOV		AL,0xd1
		OUT		0x64,AL
		CALL	waitkbdout
		MOV		AL,0xdf			; enable A20
		OUT		0x60,AL
		CALL	waitkbdout

;	切換到保護模式

[INSTRSET "i486p"]				; 說明使用486指令

		LGDT	[GDTR0]			; 設置臨時GDT
		MOV		EAX,CR0
		AND		EAX,0x7fffffff	; 設bit31為0(禁用分頁)
		OR		EAX,0x00000001	; bit0到1轉換(保護模式過渡)
		MOV		CR0,EAX
		JMP		pipelineflush
pipelineflush:
		MOV		AX,1*8			;  可讀寫的段 32bit
		MOV		DS,AX
		MOV		ES,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		SS,AX

; bootpack傳遞

		MOV		ESI,bootpack	; 轉送源
		MOV		EDI,BOTPAK		; 轉送目標
		MOV		ECX,512*1024/4
		CALL	memcpy

; 磁盤數(shù)據最終轉送到它本來的位置去
; 首先從啟動扇區(qū)開始

		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	; 從柱面數(shù)變換為字節(jié)數(shù)/4
		SUB		ECX,512/4		; 減去 IPL 偏移量
		CALL	memcpy

; 必須由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

waitkbdout:
		IN		 AL,0x64
		AND		 AL,0x02
		JNZ		waitkbdout	; AND的結果如果不是0,就跳到waitkbdout
		RET

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy			; 減法運算的結果如果不是0,就跳轉到memcpy
		RET
; memcpy地址前綴大小

		ALIGNB	16
GDT0:
		RESB	8				; 初始值
		DW		0xffff,0x0000,0x9200,0x00cf	; 可以讀寫的段(segment)32bit
		DW		0xffff,0x0000,0x9a28,0x0047	; 可執(zhí)行的文件的32bit寄存器(bootpack用)

		DW		0
GDTR0:
		DW		8*3-1
		DD		GDT0

		ALIGNB	16
bootpack:

3.bookpack.c(主函數(shù)文件,完成初始化等操作)

#include "bootpack.h"
#include <stdio.h>
 
//該結構體用于控制鼠標
struct MOUSE_DEC {
	unsigned char buf[3], phase;	
	int x, y, btn;		
};
 
extern struct FIFO8 keyfifo, mousefifo;	//外部變量,定義在fifo.c文件中
void enable_mouse(struct MOUSE_DEC *mdec);	//啟動鼠標的函數(shù)
void init_keyboard(void);	//初始化鍵盤
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat);	//處理鼠標信息
 
void HariMain(void)	//主函數(shù)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;	//啟動信息數(shù)據結構
	char s[40], mcursor[256], keybuf[32], mousebuf[128];	
	int mx, my, i;
	struct MOUSE_DEC mdec;
 
	init_gdtidt();	//初始化gdt.idt
	init_pic();		//初始化pic
	io_sti(); 	//執(zhí)行STI指令
	fifo8_init(&keyfifo, 32, keybuf);	//初始化鍵盤緩存區(qū)	
	fifo8_init(&mousefifo, 128, mousebuf);	//初始化鼠標緩存區(qū)
	io_out8(PIC0_IMR, 0xf9); //設置中斷
	io_out8(PIC1_IMR, 0xef); //因為鍵盤中斷是IRQ1,鼠標中斷時IRQ12,所以需要打開主從電路上的對應管腳
 
	init_keyboard();	//初始化鍵盤
 
	init_palette();	//初始化調色板
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);	//初始化屏幕,形成最初的窗口界面
	//獲取畫面中央的坐標
	mx = (binfo->scrnx - 16) / 2; 
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);	//鼠標光標的顯示
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);	
	sprintf(s, "(%3d, %3d)", mx, my);	//將鼠標位置轉換成字符串
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);	//顯示字符串,這個函數(shù)的位置在哪里?
 
	enable_mouse(&mdec);	//啟動鼠標
	for (;;) {	
		io_cli();	//關閉中斷	
		//如果鍵盤緩沖區(qū)和鼠標緩沖區(qū)中都沒有數(shù)據
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
			io_stihlt();	//打開中斷并執(zhí)行hlt命令
		} else {
			//如果鍵盤緩存區(qū)中有數(shù)據
			if (fifo8_status(&keyfifo) != 0) {
				i = fifo8_get(&keyfifo);	//從緩存區(qū)中讀取數(shù)據(FIFO)
				sprintf(s, "%02X", i);	//將數(shù)據已字符串形式輸出
				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) {	//如果鼠標緩存區(qū)中有函數(shù)(鼠標和鍵盤的數(shù)據是怎么存入到對應緩存區(qū)的?)
				i = fifo8_get(&mousefifo);		//從緩存區(qū)中讀取數(shù)據
				io_sti();	//打開中斷
				if (mouse_decode(&mdec, i) != 0) {	//對鼠標信息進行處理
					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); 
				}
			}
		}
	}
}
 
#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
 
//功能:等待鍵盤控制電路準備完畢
//如果鍵盤控制電路可以接受CPU指令,CPU從設備號碼0x0064處所讀取的數(shù)據倒數(shù)第二位應該是0,否則就是一直循環(huán)等待
void wait_KBC_sendready(void)
{
	for (;;) {
		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {	//判斷第二位的情況
			break;
		}
	}
	return;
}
 
//功能:初始化鍵盤
void init_keyboard(void)
{
	wait_KBC_sendready();	
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	wait_KBC_sendready();	
	io_out8(PORT_KEYDAT, KBC_MODE);	
	return;
}
 
#define KEYCMD_SENDTO_MOUSE		0xd4
#define MOUSECMD_ENABLE			0xf4
 
//功能:啟用鼠標
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);
	mdec->phase = 0;
	return;
}
 
//處理鼠標信息
int mouse_decode(struct MOUSE_DEC *mdec, unsigned char dat)
{
	if (mdec->phase == 0) {
		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;
		return 1;
	}
	return -1; 
}

4.dsctbl.c(gdt和idt設置)

#include "bootpack.h"
//初始化gdt和idt
void init_gdtidt(void)
{
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;	//提前設置好的GDT在內存中的地址
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) ADR_IDT;	//提前設置好的IDT在內存中的地址
	int i;
 
	for (i = 0; i <= LIMIT_GDT / 8; i++) {	//對所有的全局描述符進行初始化
		set_segmdesc(gdt + i, 0, 0, 0);	//先將所有的全局描述符設置為0
	}
	set_segmdesc(gdt + 1, 0xffffffff,   0x00000000, AR_DATA32_RW);	//設置第1號描述符,段基址為0,大小4gb,可讀寫32位段
	set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);	//設置第2號描述符,段基址0x00280000,大小0x0007ffff,屬性0x4092
	load_gdtr(LIMIT_GDT, ADR_GDT);	//載入gdtr到cpu中
 
	for (i = 0; i <= LIMIT_IDT / 8; i++) {	//對所有的idt描述符進行初始化
		set_gatedesc(idt + i, 0, 0, 0);	//先將所有的idt描述符設為0
	}
	load_idtr(LIMIT_IDT, ADR_IDT);	//載入idtr到cpu中
 
	set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);	//對idt描述符賦值,注意第二個變量,是偏移量
	set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
 
	return;
}
 
//功能:對段描述符賦值
//參數(shù):段描述符地址,長度、基址、屬性值
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
	//判斷段描述大小的計數(shù)單位
	if (limit > 0xfffff) {	//如果段界限超過了限制1MB
		ar |= 0x8000; /* G_bit = 1 */	//將G位設置為1,即大小以Kb為單位
		limit /= 0x1000;	//換算成KB
	}
	sd->limit_low    = limit & 0xffff;	//從低16位開始設置,即段界限的低16位
	sd->base_low     = base & 0xffff;	//接著設置16-31的16位數(shù)據,即基地址的低16位
	sd->base_mid     = (base >> 16) & 0xff;	//設置32-39的8位數(shù)據,即基地址的中間8位,將base右移16位,然后做與運算,取出中間8位
	sd->access_right = ar & 0xff;	//設置40-47位,即ar的第八位
	sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);	//設置49-56位,limit_high比較特殊,其中四位是段界限,4位是段屬性值
	sd->base_high    = (base >> 24) & 0xff;	//設置57-63位,即段基址的高8位
	return;
}
 
//功能:設置門描述符
//參數(shù):門描述符地址,偏移、段選擇符,屬性值
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
	gd->offset_low   = offset & 0xffff;	//設置0-15位,偏移地址的低16位
	gd->selector     = selector;	//設置16-31位,制度段選擇子
	gd->dw_count     = (ar >> 8) & 0xff;	//設置32-39位,基本上全是0
	gd->access_right = ar & 0xff;	//設置40-47位,門描述符屬性
	gd->offset_high  = (offset >> 16) & 0xffff;	//設置48-63Wie,偏移地址的高16位
	return;
}

5.graphic.c

//用于處理屏幕顯示
 
#include "bootpack.h"
 
//初始化調色板
void init_palette(void)
{
	static unsigned char table_rgb[16 * 3] = {	//設置調色板變量,這里3個字符一組,組成了一個顏色,顏色應該是計算機中已經設定好的
		0x00, 0x00, 0x00,	
		0xff, 0x00, 0x00,	
		0x00, 0xff, 0x00,	
		0xff, 0xff, 0x00,	
		0x00, 0x00, 0xff,	
		0xff, 0x00, 0xff,	
		0x00, 0xff, 0xff,	
		0xff, 0xff, 0xff,	
		0xc6, 0xc6, 0xc6,	
		0x84, 0x00, 0x00,	
		0x00, 0x84, 0x00,	
		0x84, 0x84, 0x00,	
		0x00, 0x00, 0x84,	
		0x84, 0x00, 0x84,	
		0x00, 0x84, 0x84,	
		0x84, 0x84, 0x84	
	};
	set_palette(0, 15, table_rgb);	//設置調色板
	return;
 
}
 
//功能:設置調色板,將顏色和編號對上
void set_palette(int start, int end, unsigned char *rgb)
{
	int i, eflags;
	eflags = io_load_eflags();	//匯編語言函數(shù)
	io_cli(); //關閉中斷					
	io_out8(0x03c8, start);	//寫入端口
	for (i = start; i <= end; i++) {	//每三個一組合成一個rgb顏色
		io_out8(0x03c9, rgb[0] / 4);
		io_out8(0x03c9, rgb[1] / 4);
		io_out8(0x03c9, rgb[2] / 4);
		rgb += 3;
	}
	io_store_eflags(eflags);	//匯編語言函數(shù)
	return;
}
 
//功能:畫一個窗口
//其中xsize表示窗口寬度,理論上應該等于x1-x0
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
	int x, y;
	for (y = y0; y <= y1; y++) {
		for (x = x0; x <= x1; x++)
			vram[y * xsize + x] = c;	//顯示出字符
	}
	return;
}
 
//初始化屏幕
void init_screen8(char *vram, int x, int y)
{
	boxfill8(vram, x, COL8_008484,  0,     0,      x -  1, y - 29);
	boxfill8(vram, x, COL8_C6C6C6,  0,     y - 28, x -  1, y - 28);
	boxfill8(vram, x, COL8_FFFFFF,  0,     y - 27, x -  1, y - 27);
	boxfill8(vram, x, COL8_C6C6C6,  0,     y - 26, x -  1, y -  1);
 
	boxfill8(vram, x, COL8_FFFFFF,  3,     y - 24, 59,     y - 24);
	boxfill8(vram, x, COL8_FFFFFF,  2,     y - 24,  2,     y -  4);
	boxfill8(vram, x, COL8_848484,  3,     y -  4, 59,     y -  4);
	boxfill8(vram, x, COL8_848484, 59,     y - 23, 59,     y -  5);
	boxfill8(vram, x, COL8_000000,  2,     y -  3, 59,     y -  3);
	boxfill8(vram, x, COL8_000000, 60,     y - 24, 60,     y -  3);
 
	boxfill8(vram, x, COL8_848484, x - 47, y - 24, x -  4, y - 24);
	boxfill8(vram, x, COL8_848484, x - 47, y - 23, x - 47, y -  4);
	boxfill8(vram, x, COL8_FFFFFF, x - 47, y -  3, x -  4, y -  3);
	boxfill8(vram, x, COL8_FFFFFF, x -  3, y - 24, x -  3, y -  3);
	return;
}
 
//顯示字體
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;
}
 
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;
}
 
void init_mouse_cursor8(char *mouse, char bc)
{
	static char cursor[16][16] = {
		"**************..",
		"*OOOOOOOOOOO*...",
		"*OOOOOOOOOO*....",
		"*OOOOOOOOO*.....",
		"*OOOOOOOO*......",
		"*OOOOOOO*.......",
		"*OOOOOOO*.......",
		"*OOOOOOOO*......",
		"*OOOO**OOO*.....",
		"*OOO*..*OOO*....",
		"*OO*....*OOO*...",
		"*O*......*OOO*..",
		"**........*OOO*.",
		"*..........*OOO*",
		"............*OO*",
		".............***"
	};
	int x, y;
 
	for (y = 0; y < 16; y++) {
		for (x = 0; x < 16; x++) {
			if (cursor[y][x] == '*') {
				mouse[y * 16 + x] = COL8_000000;	//顯示鼠標
			}
			if (cursor[y][x] == 'O') {
				mouse[y * 16 + x] = COL8_FFFFFF;
			}
			if (cursor[y][x] == '.') {
				mouse[y * 16 + x] = bc;
			}
		}
	}
	return;
}
 
//功能:顯示背景
//vram和vxsize是關于vram的信息
//pxsize,pysize是想要顯示的圖形大小
//px0、py0制定圖像在畫面上的顯示位置
//buf指定圖形存放的地址
//bxsize指定每一行含有的像素數(shù)
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;
}

6.fifo.c

#include "bootpack.h"
 
#define FLAGS_OVERRUN		0x0001
 
//功能:初始化FIFO緩存區(qū)
//參數(shù):緩存區(qū)結構體,大小,緩存區(qū)地址
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; 
	fifo->flags = 0;
	fifo->p = 0; 
	fifo->q = 0;
	return;
}
 
//功能:向緩存區(qū)寫入數(shù)據
int fifo8_put(struct FIFO8 *fifo, unsigned char data)
{
	if (fifo->free == 0) {	//如果緩存區(qū)大小等于零,代表緩存區(qū)已經被寫滿
		fifo->flags |= FLAGS_OVERRUN;	//將覆蓋標志置1
		return -1;	//返回一個錯誤值
	}
	fifo->buf[fifo->p] = data;	//讀取當前緩存區(qū)的第一個數(shù)據
	fifo->p++;	//將讀取指針后移
	if (fifo->p == fifo->size) {	//如果已經讀完緩存區(qū)
		fifo->p = 0;	//將讀取指針指向第一個位置
	}
	fifo->free--;	//緩存區(qū)可用位置減1
	return 0;
}
 
//功能:從緩存區(qū)讀取數(shù)據
int fifo8_get(struct FIFO8 *fifo)
{
	int data;
	if (fifo->free == fifo->size) {	//如果緩存區(qū)是空的,返回錯誤
		return -1;
	}
	data = fifo->buf[fifo->q];	//讀取數(shù)據
	fifo->q++;	//讀取指針后移
	if (fifo->q == fifo->size) {	//讀到最后一個將讀取指針指向第一個位置
		fifo->q = 0;
	}
	fifo->free++;	//可用區(qū)域加1 緩存區(qū)是循環(huán)寫入的
	return data;
}
 
int fifo8_status(struct FIFO8 *fifo)	//判斷緩存區(qū)的狀態(tài)
{
	return fifo->size - fifo->free;
}

7. int.c

#include "bootpack.h"
#include <stdio.h>
 
//功能:中斷初始化函數(shù),初始化pic
void init_pic(void)
{
	io_out8(PIC0_IMR,  0xff  );	//主片禁止所有中斷
	io_out8(PIC1_IMR,  0xff  ); //從片禁止所有中斷
	//設置pic0,主片
	io_out8(PIC0_ICW1, 0x11  ); //邊沿觸發(fā)模式
	io_out8(PIC0_ICW2, 0x20  ); //IRQ0-7由INT20-27接收
	io_out8(PIC0_ICW3, 1 << 2); //PIC1由IRQ2接收
	io_out8(PIC0_ICW4, 0x01  );	//無緩沖區(qū)模式
	//設置pic1,從片
	io_out8(PIC1_ICW1, 0x11  );	//邊沿觸發(fā)模式
	io_out8(PIC1_ICW2, 0x28  );	//IRQ8-15由INT28-2f接收
	io_out8(PIC1_ICW3, 2     ); //PIC1由IRQ2連接
	io_out8(PIC1_ICW4, 0x01  );	//無緩沖區(qū)模式
 
	io_out8(PIC0_IMR,  0xfb  ); //11111011,PIC1以外的全部禁止
	io_out8(PIC1_IMR,  0xff  ); //11111111,禁止PIC1的所有中斷
 
	return;
}
 
#define PORT_KEYDAT		0x0060
 
struct FIFO8 keyfifo;
//鍵盤中斷處理,鍵盤是IRQ1,所以編寫INT 0x21
//這里已經有了C語言編寫的函數(shù),為什么還要添加匯編語言的函數(shù)?
//是在匯編語言中調用該函數(shù)
void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	
	data = io_in8(PORT_KEYDAT);	//讀取數(shù)據
	fifo8_put(&keyfifo, data);	//將數(shù)據寫入緩存區(qū)
	return;
}
 
struct FIFO8 mousefifo;
//功能:鼠標中斷處理,編寫INT 0x2c
void inthandler2c(int *esp)
{
	unsigned char data;
	io_out8(PIC1_OCW2, 0x64);	
	io_out8(PIC0_OCW2, 0x62);	
	data = io_in8(PORT_KEYDAT);	//讀取數(shù)據
	fifo8_put(&mousefifo, data);	//將數(shù)據寫入緩存區(qū)
	return;
}
 
void inthandler27(int *esp)								*/
{
	io_out8(PIC0_OCW2, 0x67); 
	return;
}

8. naskfunc.nas(匯編和c語言文件之間的橋梁)

; naskfunc
; TAB=4

[FORMAT "WCOFF"]				;
[INSTRSET "i486p"]				;
[BITS 32]						;
[FILE "naskfunc.nas"]			;
;定義外部符號,可以從文件外進行調用
		GLOBAL	_io_hlt, _io_cli, _io_sti, _io_stihlt
		GLOBAL	_io_in8,  _io_in16,  _io_in32
		GLOBAL	_io_out8, _io_out16, _io_out32
		GLOBAL	_io_load_eflags, _io_store_eflags
		GLOBAL	_load_gdtr, _load_idtr
		GLOBAL	_asm_inthandler21, _asm_inthandler27, _asm_inthandler2c
		EXTERN	_inthandler21, _inthandler27, _inthandler2c

[SECTION .text]

_io_hlt:	; void io_hlt(void);
		HLT
		RET

_io_cli:	; void io_cli(void);
		CLI
		RET

_io_sti:	; void io_sti(void);
		STI
		RET

_io_stihlt:	; void io_stihlt(void);
		STI
		HLT
		RET

_io_in8:	; int io_in8(int port); 從指定端口中讀取數(shù)據
		MOV		EDX,[ESP+4]		; port,獲取端口號
		MOV		EAX,0	;清空ax寄存器
		IN		AL,DX	;從DX指定的端口中讀取數(shù)據到al
		RET

_io_in16:	; int io_in16(int port);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,0
		IN		AX,DX
		RET

_io_in32:	; int io_in32(int port);
		MOV		EDX,[ESP+4]		; port
		IN		EAX,DX
		RET

_io_out8:	; void io_out8(int port, int data);	向指定端口寫入數(shù)據
		MOV		EDX,[ESP+4]		; port	獲取端口號
		MOV		AL,[ESP+8]		; data	獲取要寫入的數(shù)據
		OUT		DX,AL	;將al中的數(shù)據寫入到dx指定的端口中
		RET

_io_out16:	; void io_out16(int port, int data);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,[ESP+8]		; data
		OUT		DX,AX
		RET

_io_out32:	; void io_out32(int port, int data);
		MOV		EDX,[ESP+4]		; port
		MOV		EAX,[ESP+8]		; data
		OUT		DX,EAX
		RET

_io_load_eflags:	; int io_load_eflags(void);
		PUSHFD		; 將eflags寄存器壓入棧中,入棧順序是EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
		POP		EAX	; 將edi的值彈出到eax中
		RET

_io_store_eflags:	; void io_store_eflags(int eflags);
		MOV		EAX,[ESP+4]
		PUSH	EAX
		POPFD		; 將棧中的寄存器值彈出到eflags寄存器中
		RET

_load_gdtr:		; void load_gdtr(int limit, int addr);加載GDTR	
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LGDT	[ESP+6]
		RET

_load_idtr:		; void load_idtr(int limit, int addr); 記載IDTR,原理與加載GDTR相同
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LIDT	[ESP+6]
		RET

;這個函數(shù)只是將寄存器的值保存在棧里,然后將DS和ES調整到與SS相等,再調用_inthandler21,返回后將所有寄存器的值再返回到原來的值,然后執(zhí)行IRETD
;之所以如此小心翼翼地保護寄存器,原因在于,中斷處理發(fā)生在函數(shù)處理途中,通過IREDT從中斷處理后,寄存器就亂了
_asm_inthandler21:
		PUSH	ES
		PUSH	DS
		PUSHAD	;PUSHAD指令壓入32位寄存器,其入棧順序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI .
		MOV		EAX,ESP	;eax=esp
		PUSH	EAX	壓入eax的值,即esp
		MOV		AX,SS	
		MOV		DS,AX	;ds=ss
		MOV		ES,AX	;es=ss
		CALL	_inthandler21	;調用inthandler21函數(shù),c語言編寫
		POP		EAX	;彈出esp的值到eax中
		POPAD
		POP		DS	
		POP		ES
		IRETD

_asm_inthandler27:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler27
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD

_asm_inthandler2c:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler2c
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD

標題三、其它

根據書中的描述,整個項目的編譯過程如圖
《30天自制操作系統(tǒng)》學習筆記(七),學習,筆記

最后,asmhead文件和bookpack文件會編譯到一起,bookpack的內容就是從bootpack標號開始?

關于中斷處理程序的調用

如果要讓一個中斷處理程序發(fā)揮作用,首先要將其注冊到idt中,書中使用了函數(shù)set_gatedesc(idt+0x21,(int)asm_inthandler21,2*8,AR_INTGATE32)2

即將_asm_inthandler21注冊為idt的第21號,如果發(fā)生中斷了,cpu就會自動調用asm_inthandler21

2表示asm_inthandler屬于那一個段,因低3位必須是0,所以寫成2*8
(ps:一定要讀源碼)
————————————————
版權聲明:本文為CSDN博主「qq_35041101」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_35041101/article/details/51866877文章來源地址http://www.zghlxwxcb.cn/news/detail-806138.html

到了這里,關于《30天自制操作系統(tǒng)》學習筆記(七)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【操作系統(tǒng)學習筆記】文件管理1.5

    參考書籍: 王道考研 視頻地址: Bilibili 邏輯結構: 從用戶角度看,由創(chuàng)建文件的用戶自己設計的 無結構文件 有結構文件 順序文件 順序存儲 鏈式存儲 索引文件 索引順序文件 物理結構: 從操作系統(tǒng)看,由操作系統(tǒng)決定 連續(xù)分配 鏈接分配 索引分配

    2024年03月09日
    瀏覽(105)
  • 軟考學習筆記--操作系統(tǒng)-進程管理

    軟考學習筆記--操作系統(tǒng)-進程管理

    進程管理是一個具有獨立功能的程序關于數(shù)據集合的一次可以并發(fā)執(zhí)行的運行活動,是系統(tǒng)進行資源分配和調度的基本單位。相對于程序,進程是動態(tài)的概念,而程序是靜態(tài)的概念,是指令的集合。進程具有動態(tài)性和并發(fā)性,需要一定的資源來完成任務。在大多數(shù)操作系統(tǒng)中

    2024年01月18日
    瀏覽(92)
  • 銀河麒麟操作系統(tǒng)基礎學習筆記十三

    tar可以將很多文件打包成一個文件,目錄也可以。bzip2和gzip只能壓縮單個文件。 bzip2程序能提供比gzip更高的壓縮比,是gzip的升級版,推薦使用bzip2進行壓縮。

    2024年02月12日
    瀏覽(28)
  • 《操作系統(tǒng)真象還原》學習筆記:第七章 中斷

    《操作系統(tǒng)真象還原》學習筆記:第七章 中斷

    由于 CPU 獲知了計算機中發(fā)生的某些事,CPU 暫停正在執(zhí)行的程序,轉而去執(zhí)行處理該事件的程序,當這段程序執(zhí)行完畢后,CPU 繼續(xù)執(zhí)行剛才的程序。整個過程稱為中斷處理,也稱為中斷。 把中斷按事件來源分類,來自CPU外部的中斷就稱為外部中斷,來自CPU內部的中斷就稱為

    2024年02月11日
    瀏覽(31)
  • 2.1萬字,30張圖詳解操作系統(tǒng)常見面試題(收藏版)

    2.1萬字,30張圖詳解操作系統(tǒng)常見面試題(收藏版)

    耗時兩周,新版的操作系統(tǒng)常見知識點/問題總結總算搞完了,手繪了30多張圖。大家可以用來復習操作系統(tǒng)或者準備操作系統(tǒng)面試。對于大部分公司的面試來說基本夠用了,不過,像騰訊、字節(jié)這種大廠的面試還是要適當深入一些。 這篇文章總結了一些我覺得比較重要的操作

    2023年04月13日
    瀏覽(25)
  • 【操作系統(tǒng)OS】學習筆記:第二章 進程與線程 (上)【哈工大李治軍老師】

    【操作系統(tǒng)OS】學習筆記:第二章 進程與線程 (上)【哈工大李治軍老師】

    基于本人觀看學習 哈工大李治軍老師主講的操作系統(tǒng)課程 所做的筆記,僅進行交流分享 特此鳴謝李治軍老師,操作系統(tǒng)的神作! 如果本篇筆記幫助到了你,還請點贊 關注 支持一下 ???)!! 主頁專欄有更多,如有疑問歡迎大家指正討論,共同進步! 給大家跳段街舞感謝支持

    2024年02月02日
    瀏覽(92)
  • Python入門教程30:(Win系統(tǒng))下PyCharm常用的快捷鍵操作

    pycharm快捷鍵及一些常用設置 1、編輯(Editing) Ctrl + Space 基本的代碼完成(類、方法、屬性) Ctrl + Alt + Space 快速導入任意類 Ctrl + Shift + Enter 語句完成 Ctrl + P 參數(shù)信息(在方法中調用參數(shù)) Ctrl + Q 快速查看文檔 Shift + F1 外部文檔 Ctrl + 鼠標 簡介 Ctrl + F1 顯示錯誤描述或警告信

    2024年02月10日
    瀏覽(25)
  • 操作系統(tǒng)-筆記-第一章-操作系統(tǒng)的概念

    操作系統(tǒng)-筆記-第一章-操作系統(tǒng)的概念

    一、第一章——操作系統(tǒng)的概念 二、第二章——【進程】 二、第二章——【線程】?編輯 二、第二章——【進程調度】 二、第二章——【進程同步與互斥】 二、第二章——【鎖】 三、第三章——內存管理 四、第四章——文件管理 五、第五章——輸入輸出管理 ???學習心

    2024年02月12日
    瀏覽(32)
  • 操作系統(tǒng)復習筆記2

    目錄 1、不可中斷的原子操作? 2、進程切換、系統(tǒng)調用關于用戶態(tài)、內核態(tài)的知識 3、調度算法三兩事 4、臨界區(qū)和臨界資源 5、互斥準則 6、互斥、同步、異步 網上查了一下,Linux和C++的舉例有很多,大體是加鎖、解鎖、中斷現(xiàn)場保護、恢復等,總的來說,好像中斷用的比較

    2024年02月09日
    瀏覽(22)
  • 操作系統(tǒng)-筆記-匯總

    操作系統(tǒng)-筆記-匯總

    目錄 ?? 前言 ??章節(jié)匯總? ???學習心得 ?2023年8月24日?星期四 在學習過了《計算機組成原理》之后,對計算機硬件有了一定清晰的認識 從一個架構,到一個個硬件的誕生,一個個線路的規(guī)劃,一步步結構修改,來提升性能、簡化操作 隨后,開始學習操作系統(tǒng),也就是在

    2024年02月11日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包