冬天OS(一):BOOT

--------------------------------------------------------

boot 加载loader 到内存

跳转到 loader

--------------------------------------------------------

 

 

一,一个符合 FAT12 文件系统格式的简单 boot 扇区

; ----------------------------
; <boot.asm> 
; Jack Zheng 11.26
; ----------------------------

org 07c00h                   

; ----------------------------
BS_OEMName    	DB 'ForrestY'       ; OEM String, 必须 8 个字节         
BPB_BytsPerSec  DW 512              ; 每扇区字节数
BPB_SecPerClus  DB 1                ; 每簇多少扇区
BPB_RsvdSecCnt  DW 1                ; Boot 记录占用多少扇区
BPB_NumFATs     DB 2                ; 共有多少 FAT 表
BPB_RootEntCnt  DW 224              ; 根目录文件数最大值
BPB_TotSec16    DW 2880             ; 逻辑扇区总数
BPB_Media       DB 0xF0             ; 媒体描述符
BPB_FATSz16     DW 9                ; 每FAT扇区数
BPB_SecPerTrk   DW 18               ; 每磁道扇区数
BPB_NumHeads    DW 2                ; 磁头数(面数)
BPB_HiddSec     DD 0                ; 隐藏扇区数
BPB_TotSec32    DD 0                ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
BS_DrvNum       DB 0                ; 中断 13 的驱动器号
BS_Reserved1    DB 0                ; 未使用
BS_BootSig      DB 29h              ; 扩展引导标记 (29h)
BS_VolID        DD 0                ; 卷序列号
BS_VolLab       DB 'Tinix0.01  '    ; 卷标, 必须 11 个字节
BS_FileSysType  DB 'FAT12   '       ; 文件系统类型, 必须 8个字节 
; ----------------------------


LABEL_START:
    ; ----
    ; 测试引导扇区格式是否赋值正确
    ; ----
    mov ax, 0b800h
    mov gs, ax
    mov ah, 0fh
    mov al, 'B'
    mov [gs:(80 * 2 )], ax
    jmp $ 

times   510-($-$$)  db  0       ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55                      ; 结束标志

编译并写入虚拟软盘,执行:

 

二,让 boot 加载 Loader 入内存

首先我们要写一个简单的 loader.asm,之后我们要通过对 FAT12 的了解,通过汇编将我们写入 FAT12 文件系统的 loader.bin 读入内存!

; ----------------------------
; <boot.asm> 
; Jack Zheng 11.26
; ----------------------------

org 07c00h                       

; ----------------------------
jmp short   LABEL_START
nop                             ; nop 是必须的
BS_OEMName      DB 'ForrestY'   ; OEM String, 必须 8 个字节
BPB_BytsPerSec  DW 512          ; 每扇区字节数
BPB_SecPerClus  DB 1            ; 每簇多少扇区
BPB_RsvdSecCnt  DW 1            ; Boot 记录占用多少扇区
BPB_NumFATs     DB 2            ; 共有多少 FAT 表
BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
BPB_TotSec16    DW 2880         ; 逻辑扇区总数
BPB_Media       DB 0xF0         ; 媒体描述符
BPB_FATSz16     DW 9            ; 每FAT扇区数
BPB_SecPerTrk   DW 18           ; 每磁道扇区数
BPB_NumHeads    DW 2            ; 磁头数(面数)
BPB_HiddSec     DD 0            ; 隐藏扇区数
BPB_TotSec32    DD 0            ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
BS_DrvNum       DB 0            ; 中断 13 的驱动器号
BS_Reserved1    DB 0            ; 未使用
BS_BootSig      DB 29h          ; 扩展引导标记 (29h)
BS_VolID        DD 0            ; 卷序列号
BS_VolLab       DB 'Tinix0.01  '; 卷标, 必须 11 个字节
BS_FileSysType  DB 'FAT12   '   ; 文件系统类型, 必须 8个字节 
; ----------------------------

LABEL_START:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, BaseOfStack

    ; ----
    ; 清屏
    ; ----
    mov ax, 0600h       ; AH = 6,  AL = 0h
    mov bx, 0700h       ; 黑底白字(BL = 07h)
    mov cx, 0           ; 左上角: (0, 0)
    mov dx, 0184fh      ; 右下角: (80, 50)
    int 10h             ; int 10h

    mov dh, 0           ; "Booting  "
    call    DispStr     ; 显示字符串
    
    xor ah, ah  ; ┓
    xor dl, dl  ; ┣ 软驱复位
    int 13h     ; ┛

    ; ---- 
    ; 通过 FAT12 文件系统找到文件 loader.bin
    ; ----
    mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp word [wRootDirSizeForLoop], 0       ; ┓
    jz  LABEL_NO_LOADERBIN                  ; ┣ 判断根目录区是不是已经读完
    dec word [wRootDirSizeForLoop]          ; ┛ 如果读完表示没有找到 LOADER.BIN
    mov ax, BaseOfLoader
    mov es, ax                              ; es <- BaseOfLoader
    mov bx, OffsetOfLoader                  ; bx <- OffsetOfLoader  于是, es:bx = BaseOfLoader:OffsetOfLoader
    mov ax, [wSectorNo]                     ; ax <- Root Directory 中的某 Sector 号
    mov cl, 1
    call    ReadSector

    mov si, LoaderFileName                  ; ds:si -> "LOADER  BIN"
    mov di, OffsetOfLoader                  ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
    cld
    mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
    cmp dx, 0                               ; ┓循环次数控制,
    jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR  ; ┣如果已经读完了一个 Sector,
    dec dx                                  ; ┛就跳到下一个 Sector
    mov cx, 11
LABEL_CMP_FILENAME:
    cmp cx, 0
    jz  LABEL_FILENAME_FOUND                ; 如果比较了 11 个字符都相等, 表示找到
dec cx
    lodsb                                   ; ds:si -> al
    cmp al, byte [es:di]
    jz  LABEL_GO_ON
    jmp LABEL_DIFFERENT                     ; 只要发现不一样的字符就表明本 DirectoryEntry 不是

LABEL_GO_ON:
    inc di
    jmp LABEL_CMP_FILENAME                  ;   继续循环

LABEL_DIFFERENT:
    and di, 0FFE0h                          ; else ┓ di &= E0 为了让它指向本条目开头
    add di, 20h                             ;      ┃
    mov si, LoaderFileName                  ;      ┣ di += 20h  下一个目录条目
    jmp LABEL_SEARCH_FOR_LOADERBIN          ;      ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add word [wSectorNo], 1
    jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
    mov dh, 2                               ; "No LOADER."
    call    DispStr         
    jmp $                   

LABEL_FILENAME_FOUND:       
    mov dh, 3                               ; "FIND LOAD"
    call    DispStr         
    jmp $                   


; ----------------------------
; 函数名: ReadSector
; ax : 逻辑扇区号
; cl : 要读多少扇区
; 目的地 : es:bx
; ----------------------------
ReadSector:
    push bp
    mov bp, sp
    sub esp, 2

    ; ----
    ; 通过逻辑扇区号算出 磁头、柱面(磁道)、起始扇区(相对于磁道)
    ; ----
    mov byte [bp-2], cl
    push    bx              ; 保存 bx
    mov bl, [BPB_SecPerTrk] ; bl: 除数
    div bl                  ; y 在 al 中, z 在 ah 中
    inc ah                  ; z ++
    mov cl, ah              ; cl <- 起始扇区号
    mov dh, al              ; dh <- y
    shr al, 1               ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov ch, al              ; ch <- 柱面号
    and dh, 1               ; dh & 1 = 磁头号
    pop bx                  ; 恢复 bx

    mov dl, [BS_DrvNum]     ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov ah, 2               ; 读
    mov al, byte [bp-2]     ; 读 al 个扇区
    int 13h
    jc  .GoOnReading        ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    add esp, 2
    pop bp
    ret
; ----------------------------

; ----------------------------
; 函数名: DispStr
; int 10h
; ----------------------------
DispStr:
    mov ax, MessageLength
    mul dh
    add ax, BootMessage
    mov bp, ax              ; ┓
    mov ax, ds              ; ┣ ES:BP = 串地址
    mov es, ax              ; ┛
    mov cx, MessageLength   ; CX = 串长度
    mov ax, 01301h          ; AH = 13,  AL = 01h
    mov bx, 0007h           ; 页号为0(BH = 0) 黑底白字(BL = 07h)
    mov dl, 0
    int 10h                 ; int 10h
    ret
; ----------------------------

; ----
; 加载 loader 过程中需要用到的一些变量和宏定义
; ----
BaseOfStack                 equ 07c00h  
BaseOfLoader                equ 09000h  
OffsetOfLoader              equ 0100h
RootDirSectors              equ 14  
SectorNoOfRootDirectory     equ 19  

wRootDirSizeForLoop         dw  RootDirSectors      ; Root Directory 占用的扇区数, 在循环中会递减至零.
wSectorNo                   dw  0                   ; 要读取的扇区号
bOdd                        db  0                   ; 奇数还是偶数

LoaderFileName              db  "LOADER  BIN", 0    ; LOADER.BIN 之文件名
MessageLength               equ 9
BootMessage:                db  "Booting  "         ; 9字节, 不够则用空格补齐. 序号 0
Message1                    db  "Ready.   "         ; 9字节, 不够则用空格补齐. 序号 1
Message2                    db  "No LOADER"         ; 9字节, 不够则用空格补齐. 序号 2
Message3                    db  "FIND LOAD"

times   510-($-$$)  db  0       ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55                      ; 结束标志

首先我们的软盘中没有 loader.bin :

当我们将 loader.bin 文件写入软盘之后:

 

三,加载 loader.bin 到内存并跳转到 loader.bin :

我们既然已经找到 loader.bin 所对应的目录项了,可以从中找到文件的起始簇号,再结合 FAT1 表,就可以将此文件 loader.bin 所有文件数据都读入内存(09000h:0100h),我们将引导扇区信息放入了 fat12hdr.inc 文件,然后让 boot.asm 包含它,因为后面还有其他文件需要使用引导扇区数据!

; ----------------------------
; <boot.asm>
; Jack Zheng 11.26
; ----------------------------
org 07c00h                          

jmp short   LABEL_START
nop                             ; nop 是必须的
    %include "fat12hdr.inc"

LABEL_START:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, BaseOfStack

    ; ----
    ; 清屏
    ; ----
    mov ax, 0600h       ; AH = 6,  AL = 0h
    mov bx, 0700h       ; 黑底白字(BL = 07h)
    mov cx, 0           ; 左上角: (0, 0)
    mov dx, 0184fh      ; 右下角: (80, 50)
    int 10h             ; int 10h

    mov dh, 0           ; "Booting  "
    call    DispStr     ; 显示字符串
    
    xor ah, ah  ; ┓
    xor dl, dl  ; ┣ 软驱复位
    int 13h     ; ┛

    ; ---- 
    ; 通过 FAT12 文件系统找到文件 loader.bin
    ; ----
    mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp word [wRootDirSizeForLoop], 0       ; ┓
    jz  LABEL_NO_LOADERBIN                  ; ┣ 判断根目录区是不是已经读完
    dec word [wRootDirSizeForLoop]          ; ┛ 如果读完表示没有找到 LOADER.BIN
    mov ax, BaseOfLoader
    mov es, ax                              ; es <- BaseOfLoader
    mov bx, OffsetOfLoader                  ; bx <- OffsetOfLoader  于是, es:bx = BaseOfLoader:OffsetOfLoader
    mov ax, [wSectorNo]                     ; ax <- Root Directory 中的某 Sector 号
    mov cl, 1
    call    ReadSector

    mov si, LoaderFileName                  ; ds:si -> "LOADER  BIN"
    mov di, OffsetOfLoader                  ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
    cld
    mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
    cmp dx, 0                               ; ┓循环次数控制,
    jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR  ; ┣如果已经读完了一个 Sector,
    dec dx                                  ; ┛就跳到下一个 Sector
    mov cx, 11
LABEL_CMP_FILENAME:
    cmp cx, 0
    jz  LABEL_FILENAME_FOUND                ; 如果比较了 11 个字符都相等, 表示找到
dec cx
    lodsb                                   ; ds:si -> al
    cmp al, byte [es:di]
    jz  LABEL_GO_ON
    jmp LABEL_DIFFERENT                     ; 只要发现不一样的字符就表明本 DirectoryEntry 不是

LABEL_GO_ON:
    inc di
    jmp LABEL_CMP_FILENAME                  ;   继续循环

LABEL_DIFFERENT:
    and di, 0FFE0h                          ; else ┓ di &= E0 为了让它指向本条目开头
    add di, 20h                             ;      ┃
    mov si, LoaderFileName                  ;      ┣ di += 20h  下一个目录条目
    jmp LABEL_SEARCH_FOR_LOADERBIN          ;      ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add word [wSectorNo], 1
    jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
    mov dh, 2                               ; "No LOADER."
    call    DispStr         
    jmp $                   
    ; ----
    ; 找到 loader.bin 之后就开始加载 loader.bin
    ; ----
LABEL_FILENAME_FOUND:                       ; 找到 LOADER.BIN 后便来到这里继续
    mov ax, RootDirSectors
    and di, 0FFE0h                          ; di -> 当前条目的开始
    add di, 01Ah                            ; di -> 首 Sector
    mov cx, word [es:di]
    push    cx                              ; 保存此 Sector 在 FAT 中的序号
    add cx, ax
    add cx, DeltaSectorNo                   ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
    mov ax, BaseOfLoader
    mov es, ax                              ; es <- BaseOfLoader
    mov bx, OffsetOfLoader                  ; bx <- OffsetOfLoader  于是, es:bx = BaseOfLoader:OffsetOfLoader = BaseOfLoader * 10h + OffsetOfLoader
    mov ax, cx                              ; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
    push    ax                              ; ┓
    push    bx                              ; ┃
    mov ah, 0Eh                             ; ┃ 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果:
    mov al, '.'                             ; ┃
    mov bl, 0Fh                             ; ┃ Booting ......
    int 10h                                 ; ┃
    pop bx                                  ; ┃
    pop ax                                  ; ┛

    mov cl, 1
    call    ReadSector
    pop ax                                  ; 取出此 Sector 在 FAT 中的序号
    call    GetFATEntry
    cmp ax, 0FFFh
    jz  LABEL_FILE_LOADED
    push    ax                              ; 保存 Sector 在 FAT 中的序号
    mov dx, RootDirSectors
    add ax, dx
    add ax, DeltaSectorNo
    add bx, [BPB_BytsPerSec]
    jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:

    mov dh, 1                               ; "Ready."
    call    DispStr                         ; 显示字符串

    ; ----
    ; 跳到 loader.bin
    ; ----
    jmp BaseOfLoader:OffsetOfLoader        

; ----------------------------
; 函数名: ReadSector
; ax : 逻辑扇区号
; cl : 要读多少扇区
; 目的地 : es:bx
; ----------------------------
ReadSector:
    push bp
    mov bp, sp
    sub esp, 2

    ; ----
    ; 通过逻辑扇区号算出 磁头、柱面(磁道)、起始扇区(相对于磁道)
    ; ----
    mov byte [bp-2], cl
    push    bx              ; 保存 bx
    mov bl, [BPB_SecPerTrk] ; bl: 除数
    div bl                  ; y 在 al 中, z 在 ah 中
    inc ah                  ; z ++
    mov cl, ah              ; cl <- 起始扇区号
    mov dh, al              ; dh <- y
    shr al, 1               ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov ch, al              ; ch <- 柱面号
    and dh, 1               ; dh & 1 = 磁头号
    pop bx                  ; 恢复 bx

    mov dl, [BS_DrvNum]     ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov ah, 2               ; 读
    mov al, byte [bp-2]     ; 读 al 个扇区
    int 13h
    jc  .GoOnReading        ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    add esp, 2
    pop bp
    ret
; ----------------------------

; ----------------------------
; 函数名: GetFATEntry
; 作用:
; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
; ----------------------------
GetFATEntry:
    push    es
    push    bx
    push    ax
    mov ax, BaseOfLoader    ; ┓
    sub ax, 0100h           ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
    mov es, ax              ; ┛
    pop ax
    mov byte [bOdd], 0
    mov bx, 3
    mul bx                  ; dx:ax = ax * 3
    mov bx, 2
    div bx                  ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
    cmp dx, 0
    jz  LABEL_EVEN
    mov byte [bOdd], 1
LABEL_EVEN:                 ;偶数
    xor dx, dx              ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
    mov bx, [BPB_BytsPerSec]
    div bx                  ; dx:ax / BPB_BytsPerSec  ==>   ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
                            ;               dx <- 余数 (FATEntry 在扇区内的偏移)。
    push    dx
    mov bx, 0               ; bx <- 0   于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h
    add ax, SectorNoOfFAT1  ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
    mov cl, 2
    call    ReadSector      ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
    pop dx
    add bx, dx
    mov ax, [es:bx]
    cmp byte [bOdd], 1
    jnz LABEL_EVEN_2
    shr ax, 4
LABEL_EVEN_2:
    and ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

    pop bx
    pop es
    ret
; ----------------------------

; ----------------------------
; 函数名: DispStr
; int 10h
; ----------------------------
DispStr:
    mov ax, MessageLength
    mul dh
    add ax, BootMessage
    mov bp, ax              ; ┓
    mov ax, ds              ; ┣ ES:BP = 串地址
    mov es, ax              ; ┛
    mov cx, MessageLength   ; CX = 串长度
    mov ax, 01301h          ; AH = 13,  AL = 01h
    mov bx, 0007h           ; 页号为0(BH = 0) 黑底白字(BL = 07h)
    mov dl, 0
    int 10h                 ; int 10h
    ret
; ----------------------------

; ----
; 加载 loader 过程中需要用到的一些变量和宏定义
; ----
BaseOfStack                 equ 07c00h  
BaseOfLoader                equ 09000h  
OffsetOfLoader              equ 0100h

wRootDirSizeForLoop         dw  RootDirSectors      ; Root Directory 占用的扇区数, 在循环中会递减至零.
wSectorNo                   dw  0                   ; 要读取的扇区号
bOdd                        db  0                   ; 奇数还是偶数

LoaderFileName              db  "LOADER  BIN", 0    ; LOADER.BIN 之文件名
MessageLength               equ 9
BootMessage:                db  "Booting  "         ; 9字节, 不够则用空格补齐. 序号 0
Message1                    db  "Ready    "         ; 9字节, 不够则用空格补齐. 序号 1
Message2                    db  "No LOADER"         ; 9字节, 不够则用空格补齐. 序号 2
Message3                    db  "FIND LOAD"

times   510-($-$$)  db  0       ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55                      ; 结束标志
; ----------------------------
; <loader.asm>
; Jack Zheng 11.26
; ----------------------------

org 08000h

    ; ----
    ; 测试 'Loader' 被 load 成功!
    ; ----
    mov ax, 0b800h
    mov gs, ax
    mov ah, 0fh
    mov al, 'L'
    mov [gs:(80 * 4 )], ax
    jmp $ 
; ----------------------------
; fat12hdr.inc
; Jack Zheng 11.26
; ----------------------------
BS_OEMName      DB 'ForrestY'   ; OEM String, 必须 8 个字节

BPB_BytsPerSec  DW 512          ; 每扇区字节数
BPB_SecPerClus  DB 1            ; 每簇多少扇区
BPB_RsvdSecCnt  DW 1            ; Boot 记录占用多少扇区
BPB_NumFATs     DB 2            ; 共有多少 FAT 表
BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
BPB_TotSec16    DW 2880         ; 逻辑扇区总数
BPB_Media       DB 0xF0         ; 媒体描述符
BPB_FATSz16     DW 9            ; 每FAT扇区数
BPB_SecPerTrk   DW 18           ; 每磁道扇区数
BPB_NumHeads    DW 2            ; 磁头数(面数)
BPB_HiddSec     DD 0            ; 隐藏扇区数
BPB_TotSec32    DD 0            ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数

BS_DrvNum       DB 0            ; 中断 13 的驱动器号
BS_Reserved1    DB 0            ; 未使用
BS_BootSig      DB 29h          ; 扩展引导标记 (29h)
BS_VolID        DD 0            ; 卷序列号
BS_VolLab       DB 'Tinix0.01  '; 卷标, 必须 11 个字节
BS_FileSysType  DB 'FAT12   '   ; 文件系统类型, 必须 8个字节 

; ----------------------------
; 基于 FAT12 头的一些常量定义,如果头信息改变,下面的常量可能也要做相应改变
; ----------------------------
FATSz                   equ 9   ; BPB_FATSz16
RootDirSectors          equ 14  ; 根目录占用空间: RootDirSectors = ((BPB_RootEntCnt * 32) + (BPB_BytsPerSec – 1)) / BPB_BytsPerSec; 但如果按照此公式代码过长
SectorNoOfRootDirectory equ 19  ; Root Directory 的第一个扇区号    = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz)
SectorNoOfFAT1          equ 1   ; FAT1 的第一个扇区号  = BPB_RsvdSecCnt
DeltaSectorNo           equ 17

执行:

可以看到,我们已经跳转到 loader 开始执行了!跳转到 loader 的目的是因为 boot 扇区只能 512 字节,我们无法在 boot 里面大展拳脚,到 loader 之后,我们会有更大的可操作空间...

---------------------------- 冬天里的 OS 起航 ----------------------------

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值