[自製操作系統] 第09回 載入內核

来源:https://www.cnblogs.com/Lizhixing/archive/2022/06/20/16393527.html
-Advertisement-
Play Games

目錄 一、前景回顧 二、用C語言編寫內核 三、載入內核 四、運行測試 一、前景回顧 本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。 藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。 二、用C語言編寫內核 為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對 ...


目錄
一、前景回顧
二、用C語言編寫內核
三、載入內核
四、運行測試

 

一、前景回顧

  本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。
  
  藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。

二、用C語言編寫內核

  為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對於我們來講,看C語言代碼肯定要比彙編語言更容易理解,看起來也沒那麼費勁。所以用C語言可以更加省事。

  先來看看我們內核代碼的最初形態,首先在項目路徑下新建一個project/kernel的目錄,以後我們內核相關的文件都存放於此,在該目錄下新建一個名為main.c的文件,在main.c中鍵入如下代碼:

1 int main(void)
2 {
3     while(1);
4     return 0;
5 }

  這就是我們的內核代碼,當然現在什麼都還沒有,就算內核成功載入進去也沒有什麼反應。這裡我們先實現一個自己的列印函數,在main函數中調用這個列印函數來列印出“HELLO KERNEL”的字元,這樣就能測試內核代碼運行是否成功。前面我們一直都是直接操作顯存段的記憶體來往屏幕上來列印字元,現在開始用C語言編程了,自然要封裝一個列印函數來列印字元。

  同樣,在項目路徑下新建另一個project/lib/kernel目錄,該目錄用來存放一些供內核使用的庫文件。在該目錄下新建名為print.S和print.h的文件,在此之前,我們在project/lib目錄下新建一個名為stdint.h的文件用來定義一些數據類型。代碼如下:

 1 #ifndef __LIB_STDINT_H__
 2 #define __LIB_STDINT_H__
 3 typedef signed char int8_t;
 4 typedef signed short int int16_t;
 5 typedef signed int int32_t;
 6 typedef signed long long int int64_t;
 7 typedef unsigned char uint8_t;
 8 typedef unsigned short int uint16_t;
 9 typedef unsigned int uint32_t;
10 typedef unsigned long long int uint64_t;
11 #endif
stdint.h
  1 TI_GDT         equ  0
  2 RPL0           equ  0
  3 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
  4 
  5 section .data
  6 put_int_buffer dq 0
  7 
  8 [bits 32]
  9 section .text
 10 ;-----------------------------------put_str--------------------------------------
 11 ;功能描述:put_str通過put_char來列印以0字元結尾的字元串
 12 ;----------------------------------------------------------------------------------
 13 global put_str
 14 put_str:
 15         push ebx
 16         push ecx
 17         xor ecx, ecx
 18         mov ebx, [esp + 12]
 19 .goon:
 20         mov cl, [ebx]
 21         cmp cl, 0
 22         jz .str_over
 23         push ecx
 24         call put_char
 25         add esp, 4
 26         inc ebx
 27         jmp .goon
 28 .str_over:
 29         pop ecx
 30         pop ebx
 31         ret
 32         
 33 ;--------------------------put_char-------------------------
 34 ;功能描述:把棧中的一個字元寫入到游標所在處
 35 ;---------------------------------------------------------------
 36 global put_char
 37 put_char:
 38         pushad                                         ;備份32位寄存器環境
 39         mov ax, SELECTOR_VIDEO  ;不能直接把立即數送入段寄存器中
 40         mov gs, ax
 41 
 42         ;----------------------獲取當前游標位置---------------------------------
 43         ;先獲取高8位
 44         mov dx, 0x03d4
 45         mov al, 0x0e
 46         out dx, al
 47         mov dx, 0x03d5
 48         in al, dx
 49         mov ah, al
 50 
 51         ;再獲取低8位
 52         mov dx, 0x03d4
 53         mov al, 0x0f
 54         out dx, al
 55         mov dx, 0x03d5
 56         in al, dx
 57 
 58         ;將游標位置存入bx
 59         mov bx, ax
 60 
 61         ;在棧中獲取待列印的字元
 62         mov ecx, [esp + 36]  ;pushad將8個32位寄存器都壓入棧中,再加上主調函數4位元組的返回地址,所以esp+36之後才是主調函數壓入的列印字元
 63         cmp cl, 0xd                 ;判斷該字元是否為CR(回車),CR的ASCII碼為0x0d
 64         jz .is_carriage_return
 65 
 66         cmp cl, 0xa                 ;判斷該字元是否為LF(換行),LF的ASCII碼為0x0a
 67         jz .is_line_feed
 68 
 69         cmp cl, 0x8                 ;判斷該字元是否為BS(空格),BS的ASCII碼為0x08
 70         jz .is_backspace
 71 
 72         jmp .put_other
 73 
 74 ;字元為BS(空格)的處理辦法
 75 .is_backspace:
 76         dec bx
 77         shl bx, 1
 78         mov byte [gs:bx], 0x20
 79         inc bx
 80         mov byte [gs:bx], 0x07
 81         shr bx, 1
 82         jmp set_cursor
 83 
 84 ;字元為CR(回車)以及LF(換行)的處理辦法
 85 .is_line_feed:
 86 .is_carriage_return:
 87         xor dx, dx
 88         mov ax, bx
 89         mov si, 80
 90         div si
 91         sub bx, dx
 92 
 93 ;CR(回車)符的處理結束
 94 .is_carriage_return_end:
 95         add bx, 80
 96         cmp bx, 2000
 97 ;LF(換行)符的處理結束
 98 .is_line_feed_end:
 99         jl set_cursor
100 
101 .put_other:
102         shl bx, 1
103         mov [gs:bx], cl
104         inc bx
105         mov byte [gs:bx], 0x07
106         shr bx, 1
107         inc bx
108         cmp bx, 2000
109         jl set_cursor
110 
111 .roll_screen:
112         cld
113         mov ecx, 960
114         mov esi, 0xc00b80a0
115         mov edi, 0xc00b8000
116         rep movsd
117         
118         mov ebx, 3840
119         mov ecx, 80
120 
121 .cls:
122         mov word [gs:ebx], 0x0720
123         add ebx, 2
124         loop .cls
125         mov bx, 1920
126 global set_cursor
127 set_cursor:
128         mov dx, 0x03d4
129         mov al, 0x0e
130         out dx, al
131         mov dx, 0x03d5
132         mov al, bh
133         out dx, al
134 
135         mov dx, 0x03d4
136         mov al, 0x0f
137         out dx, al
138         mov dx, 0x03d5
139         mov al, bl
140         out dx, al
141 .put_char_done:
142         popad
143         ret
144 ;-----------------------------------put_int--------------------------------------
145 ;功能描述:將小端位元組序的數字變成對應的ASCII後,倒置
146 ;輸入:棧中參數為待列印的數字
147 ;輸出:在屏幕中列印十六進位數字,並不會列印首碼0x
148 ;如列印十進位15時,只會列印f,而不是0xf
149 ;----------------------------------------------------------------------------------
150 global put_int
151 put_int:
152         pushad
153         mov ebp, esp
154         mov eax, [ebp + 36]
155         mov edx, eax
156         mov edi, 7
157         mov ecx, 8
158         mov ebx, put_int_buffer
159 
160 ;將32位數字按照16進位的形式從低位到高位逐個處理,共處理8個16進位數字
161 .16based_4bits:                   ; 每4位二進位是16進位數字的1位,遍歷每一位16進位數字
162         and edx, 0x0000000F               ; 解析16進位數字的每一位。and與操作後,edx只有低4位有效
163         cmp edx, 9                   ; 數字0~9和a~f需要分別處理成對應的字元
164         jg .is_A2F 
165         add edx, '0'                   ; ascii碼是8位大小。add求和操作後,edx低8位有效。
166         jmp .store
167 .is_A2F:
168         sub edx, 10                   ; A~F 減去10 所得到的差,再加上字元A的ascii碼,便是A~F對應的ascii碼
169         add edx, 'A'
170 
171 ;將每一位數字轉換成對應的字元後,按照類似“大端”的順序存儲到緩衝區put_int_buffer
172 ;高位字元放在低地址,低位字元要放在高地址,這樣和大端位元組序類似,只不過咱們這裡是字元序.
173 .store:
174 ; 此時dl中應該是數字對應的字元的ascii碼
175         mov [ebx+edi], dl               
176         dec edi
177         shr eax, 4
178         mov edx, eax 
179         loop .16based_4bits
180 
181 ;現在put_int_buffer中已全是字元,列印之前,
182 ;把高位連續的字元去掉,比如把字元000123變成123
183 .ready_to_print:
184         inc edi                   ; 此時edi退減為-1(0xffffffff),加1使其為0
185 .skip_prefix_0:  
186         cmp edi,8                   ; 若已經比較第9個字元了,表示待列印的字元串為全0 
187         je .full0 
188 ;找出連續的0字元, edi做為非0的最高位字元的偏移
189 .go_on_skip:   
190         mov cl, [put_int_buffer+edi]
191         inc edi
192         cmp cl, '0' 
193         je .skip_prefix_0               ; 繼續判斷下一位字元是否為字元0(不是數字0)
194         dec edi                   ;edi在上面的inc操作中指向了下一個字元,若當前字元不為'0',要恢復edi指向當前字元               
195         jmp .put_each_num
196 
197 .full0:
198         mov cl,'0'                   ; 輸入的數字為全0時,則只列印0
199 .put_each_num:
200         push ecx                   ; 此時cl中為可列印的字元
201         call put_char
202         add esp, 4
203         inc edi                   ; 使edi指向下一個字元
204         mov cl, [put_int_buffer+edi]           ; 獲取下一個字元到cl寄存器
205         cmp edi,8
206         jl .put_each_num
207         popad
208         ret
print.S
1 #ifndef  __LIB_KERNEL_PRINT_H
2 #define  __LIB_KERNEL_PRINT_H
3 #include "stdint.h"
4 void put_char(uint8_t char_asci);
5 void put_str(char *message);
6 void put_int(uint32_t num);
7 #endif
print.h

  最後輸入如下命令來編譯print.S:

nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S

  完善了列印函數後,我們現在可以在main函數中實現列印功能了,修改main.c文件:

1 #include "print.h"
2 int main(void)
3 {
4     put_str("HELLO KERNEL\n");
5     while(1);
6     return 0;
7 }

三、載入內核

  前面我們已經將內核代碼實現完成了,接下來按道理應該和前面一樣,將main.c文件編譯載入到硬碟中,隨後通過loader來讀取載入該文件,最終跳轉運行。的確也是如此,不過略有不同。請聽我慢慢講來。

  現在我們是main.c文件,不同於彙編代碼,我們接下來要使用gcc工具將main.c文件編譯成main.o文件:

gcc -m32 -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o

  它只是一個目標文件,也稱為重定位文件,重定位文件指的是文件裡面所用的符號還沒有安排地址,這些符號的地址將來是要與其他目標文件“組成”一個可執行文件時再重定位(編排地址),這裡的符號就是指的所調用的函數或使用的變數,看我們的main.c文件中,在main函數中調用了print.h中聲明的put_str函數,所以將來main.o文件需要和print.o文件一起組成可執行文件。

  如何“組成”呢?這裡的“組成”其實就是指的C語言程式變成可執行文件下的四步驟(預處理、編譯、彙編和鏈接)中的鏈接,Linux下使用的是ld命令來鏈接,我們是在Linux平臺下的,所以自然使用ld命令:

ld -m elf_i386 -Ttext 0xc0001500 -e main -o project/kernel/kernel.bin project/kernel/main.o project/lib/kernel/print.o

  最終生成可執行文件kernel.bin。它就是我們需要載入到硬碟里的那個文件。

  到這裡都和前面步驟一致,只是後面loader並不是單純的將kernel.bin文件拷貝到記憶體某處再跳轉執行。這是因為我們生成的kernel.bin文件的格式為elf,elf格式的文件,在文件最開始有一個名為elf格式頭的部分,該部分詳細包含了整個文件的信息,具體內容過多我這裡不再展開講,感興趣的朋友可以參考原書《操作系統真象還原》p213~222,或者百度。所以說如果我們只是單純地跳轉到該文件的載入處,那麼必定會出現問題,因為該文件的開始部分並不是可供CPU執行的程式,我們跳轉的地址應該是該文件的程式部分。這個地址在我們前面鏈接時已經指定為0xc0001500,因為我們前面已經開啟了分頁機制,所以實際上這個地址對應的是物理地址的0x1500處。

  接下來再修改loader.S文件,增加拷貝內核部分代碼以及拷貝函數代碼,為了便於閱讀,我將新代碼附在了之前的loader.S文件下,除此之外,boot.inc也有新增的內容。

  1 %include "boot.inc"
  2 section loader vstart=LOADER_BASE_ADDR
  3 LOADER_STACK_TOP equ LOADER_BASE_ADDR
  4 jmp loader_start
  5 
  6 ;構建gdt及其內部描述符
  7 GDT_BASE:        dd 0x00000000
  8                 dd 0x00000000
  9 CODE_DESC:       dd 0x0000FFFF
 10                 dd DESC_CODE_HIGH4
 11 DATA_STACK_DESC: dd 0x0000FFFF
 12                 dd DESC_DATA_HIGH4
 13 VIDEO_DESC:      dd 0x80000007
 14                 dd DESC_VIDEO_HIGH4
 15 
 16 GDT_SIZE  equ $-GDT_BASE
 17 GDT_LIMIT equ GDT_SIZE-1
 18 times 60 dq 0  ;此處預留60個描述符的空位
 19 
 20 SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0
 21 SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0
 22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
 23 
 24 ;以下是gdt指針,前2個位元組是gdt界限,後4個位元組是gdt的起始地址
 25 gdt_ptr   dw GDT_LIMIT 
 26         dd GDT_BASE
 27 
 28 ;---------------------進入保護模式------------
 29 loader_start:
 30     ;一、打開A20地址線
 31     in al, 0x92
 32     or al, 0000_0010B
 33     out 0x92, al
 34     
 35     ;二、載入GDT
 36     lgdt [gdt_ptr]
 37 
 38     ;三、cr0第0位(pe)置1
 39     mov eax, cr0
 40     or eax, 0x00000001
 41     mov cr0, eax
 42     
 43     jmp dword SELECTOR_CODE:p_mode_start ;刷新流水線
 44 
 45     [bits 32]
 46     p_mode_start:
 47             mov ax, SELECTOR_DATA
 48             mov ds, ax
 49             mov es, ax
 50             mov ss, ax
 51             mov esp, LOADER_STACK_TOP
 52             mov ax, SELECTOR_VIDEO
 53             mov gs, ax
 54             
 55             mov byte [gs:160], 'p'
 56 
 57 ;------------------開啟分頁機制-----------------
 58     ;一、創建頁目錄表並初始化頁記憶體點陣圖
 59     call setup_page
 60 
 61     ;將描述符表地址及偏移量寫入記憶體gdt_ptr,一會兒用新地址重新載入
 62     sgdt [gdt_ptr]
 63     ;將gdt描述符中視頻段描述符中的段基址+0xc0000000
 64     mov ebx, [gdt_ptr + 2]
 65     or dword [ebx + 0x18 + 4], 0xc0000000
 66             
 67     ;將gdt的基址加上0xc0000000使其成為內核所在的高地址
 68     add dword [gdt_ptr + 2], 0xc0000000
 69 
 70     add esp, 0xc0000000  ;將棧指針同樣映射到內核地址
 71             
 72     ;二、將頁目錄表地址賦值給cr3
 73     mov eax, PAGE_DIR_TABLE_POS
 74     mov cr3, eax
 75             
 76     ;三、打開cr0的pg位
 77     mov eax, cr0
 78     or eax, 0x80000000
 79     mov cr0, eax
 80             
 81     ;在開啟分頁後,用gdt新的地址重新載入
 82     lgdt [gdt_ptr]
 83     mov byte [gs:160], 'H'
 84     mov byte [gs:162], 'E'
 85     mov byte [gs:164], 'L'
 86     mov byte [gs:166], 'L'
 87     mov byte [gs:168], 'O'
 88     mov byte [gs:170], ' '
 89     mov byte [gs:172], 'P'
 90     mov byte [gs:174], 'A'
 91     mov byte [gs:176], 'G'
 92     mov byte [gs:178], 'E'
 93 
 94 ;---------------------------------------------
 95 
 96 ;--------------------拷貝內核文件併進入kernel--------------------------
 97     mov eax, KERNEL_START_SECTOR              ;kernel.bin所在的扇區號 0x09
 98     mov ebx, KERNEL_BIN_BASE_ADDR             ;從磁碟讀出後,寫入到ebx指定的地址0x70000
 99     mov ecx, 200                              ;讀入的扇區數
100 
101     call rd_disk_m_32
102 
103     ;由於一直處在32位下,原則上不需要強制刷新,但是以防萬一還是加上
104     ;跳轉到kernel處
105     jmp SELECTOR_CODE:enter_kernel
106     
107     enter_kernel:
108         call kernel_init
109         mov esp, 0xc009f000               ;更新棧底指針
110         jmp KERNEL_ENTRY_POINT            ;內核地址0xc0001500
111         ;jmp $
112         ;---------------------將kernel.bin中的segment拷貝到指定的地址
113         kernel_init:
114             xor eax, eax
115             xor ebx, ebx   ;ebx記錄程式頭表地址
116             xor ecx, ecx    ;cx記錄程式頭表中的program header數量
117             xor edx, edx    ;dx記錄program header 尺寸,即e_phentsize
118 
119             ;偏移文件42位元組處的屬性是e_phentsize, 表示program header大小
120             mov dx, [KERNEL_BIN_BASE_ADDR + 42]
121             
122             ;偏移文件28位元組處的屬性是e_phoff
123             mov ebx, [KERNEL_BIN_BASE_ADDR + 28]
124 
125             add ebx, KERNEL_BIN_BASE_ADDR
126             mov cx, [KERNEL_BIN_BASE_ADDR + 44]
127     
128             .each_segment: 
129                     cmp byte [ebx + 0], PT_NULL
130                     je .PTNULL
131 
132             ;為函數memcpy壓入參數,參數是從右往左壓入
133             push dword [ebx + 16]
134             mov eax, [ebx + 4]
135             add eax, KERNEL_BIN_BASE_ADDR
136             push eax
137             push dword [ebx + 8]
138             call mem_cpy
139             add esp, 12
140 
141             .PTNULL:
142                     add ebx, edx
143                     loop .each_segment
144             ret
145 
146             ;-----------逐位元組拷貝mem_cpy(dst, src, size)
147             mem_cpy:
148                     cld
149                     push ebp
150                     mov ebp, esp
151                     push ecx
152                     mov edi, [ebp + 8]
153                     mov esi, [ebp + 12]
154                     mov ecx, [ebp + 16]
155                     rep movsb
156 
157                     pop ecx
158                     pop ebp
159                     ret 
160 ;---------------------------------------------------    
161 
162 ;--------------函數聲明------------------------
163     ;setup_page:(功能)設置分頁------------
164     setup_page:
165         ;先把頁目錄

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大佬理解->Java集合之ArrayList 1、ArrayList的特點 存放的元素有序 元素不唯一(可以重覆) 隨機訪問快 插入刪除元素慢 非線程安全 2、底層實現 底層初始化,使用一個Object類型的空對象數組,初始長度為0; 源碼 //Object類型對象數組引用 transient Ob ...
  • 大佬的理解->《Java IO(五) -- 字元流進階及BufferedWriter,BufferedReader》 1、BufferedReader BufferedReader高效字元流讀取文件基本用法,自帶緩衝區,讀取文件效率高,支持逐行讀取; 1.1 初始化 BufferedReader(R ...
  • 游戲的世界精彩紛呈,有動作類、策略類、角色扮演類等諸多類型,還有很多難以分類的小游戲,讓人玩起來往往愛不釋手 ...
  • 大佬的理解->《Java IO(四) -- 字元流》 FileReader字元流讀取文件,更適合用於讀取文件,可以讀取中文 1、FileReader 1.1 初始化 FileReader(File file) FileReader(String fileName) 1.2 讀取文件內容 read() ...
  • 項目全部代碼地址:https://github.com/Tom-shushu/work-study.git (mqtt-emqt 項目) 先看我們最後實現的一個效果 1.手機端向主題 topic111 發送消息,並接收。(手機測試工具名稱:MQTT調試器) 2.控制台列印 MQTT基本簡介 MQTT ...
  • 大佬的理解->《Java IO(三) -- 位元組流》 1、FileInputStream 1.1 初始化 FileInputStream(String name) FileInputStream(File file) //直接通過文件地址初始化 FileInputStream fis = new i ...
  • 第二回 巧習得元素分類 子不知懷璧其罪 雲溪父親見狀看了看雲溪,臉上滲出意思冷汗,但遲疑一下就立即退了出去,匆匆忙忙的往右邊廚房趕,只留下了雲溪和這位神秘的老爺子。 雲溪瞠目結舌的看著悠然自得的喝著老爹泡的茶的老爺子,下意識說了一句:“老先生你怎麼這麼快,還知道我要來這裡”。 “方向,你一直在繞巷子 ...
  • 一、Iproute2簡介 Iproute2是一個在Linux下的高級網路管理工具軟體。實際上,它是通過rtnetlink sockets方式動態配置內核的一些小工具組成的,從Linux2.2內核開始,Alexey Kuznetsov 實現了通過rtnetlink sockets用來配置網路協議棧,它 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...