Linux0.12內核源碼解讀(2)-Bootsect.S

来源:https://www.cnblogs.com/xiaoniuhululu/p/18130983
-Advertisement-
Play Games

大家好,我是呼嚕嚕,在上一篇文章聊聊x86電腦啟動發生的事?我們瞭解了x86電腦啟動過程,MBR、0x7c00是什麼?其中當bios引導結束後,操作系統接過電腦的控制權後,發生了哪些事?本文將揭開迷霧的序章-Bootsect.S 回顧電腦啟動過程 我們先來回顧一下,上古時期電腦按下電源鍵的 ...


大家好,我是呼嚕嚕,在上一篇文章聊聊x86電腦啟動發生的事?我們瞭解了x86電腦啟動過程,MBR、0x7c00是什麼?其中當bios引導結束後,操作系統接過電腦的控制權後,發生了哪些事?本文將揭開迷霧的序章-Bootsect.S

回顧電腦啟動過程

我們先來回顧一下,上古時期電腦按下電源鍵的啟動過程,這裡以8086架構為例:

8086、80x86是什麼意思?

有許多人不知道 經常遇到的8086、80x86是什麼意思?我們簡單科普一下:

  1. 8086是Intel公司推出的最早,也是最流行的面向個人電腦的CPU型號
  2. x86泛指一系列基於Intel 8086且向後相容的中央處理器指令集架構,由於以“86”作為結尾,因此其架構被稱為"x86"
  3. 80x86也就是在8086基礎上的增強版,包括80286,80386,80486,其後面就是我們所熟悉的奔騰、酷睿、i5、i7等等

寄存器初始化CS:IP

相比於上一篇文章聊聊x86電腦啟動發生的事,我們這裡再講細緻點,當電腦一按下電源後,8086CPU就處於實模式的狀態,此時會將CPU的寄存器初始化為CS=0xFFFF;IP=0x0000,也就是實際物理地址0xFFFF0(CS左移4位+IP)

CS : 代碼段寄存器;IP : 指令指針寄存器。CS:IP指向的內容 會被CPU當做電腦指令去執行

那麼從地址0xFFFF0中取出來的指令是什麼?我們知道當電路通電後,記憶體是一片空白的,記憶體斷電後 數據是無法保存的,所以BIOS程式需要事先被刷入只讀存儲器ROM中。物理地址0xFFFF0就是指向這樣一段BIOS ROM

CPU是如何和ROM相連的?

那麼問題又來了,CPU是如何和ROM相連的?CPU 不僅和ROM相連,還和RAM(俗稱記憶體),IO介面等設備相連,他們是通過匯流排相連。還好當時筆者將電腦組成原理好好複習了一遍,不然這部分真挺難理解的。

匯流排是貫穿整個系統的是一組電子管道,是連接各個部件的信息傳輸線,是各個部件共用的傳輸介質,稱作匯流排,它攜帶信息位元組並負責在各個電腦部件間傳遞

匯流排按系統匯流排傳輸信息內容的不同,又可以分為3 種:數據匯流排、地址匯流排和控制匯流排。我們這裡用到的就是地址匯流排,把 0xFFFF0 作為 CPU 的地址匯流排信號傳輸出去,去這個地址匯流排對應的位置處找

由於電腦有多個設備,必然會存在多個設備同時競爭匯流排控制權的問題,這時候就需要匯流排仲裁,讓某個設備優先獲得匯流排控制權,獲得了匯流排控制權的設備,才能開始傳送數據。未獲勝的設備只能等待獲勝的設備處理完成後才能執行。

我們簡單總結一下:當匯流排仲裁器仲裁通過後,CPU可以依靠地址匯流排定址,找到對應設備ROM上地址0xFFFF0處的內容。

拓展可見:什麼是電腦中的高速公路-匯流排?

載入MBR到記憶體中

當BIOS自檢完成,設置啟動順序後,利用 BIOS 的輸入功能將啟動磁碟的啟動扇區MBR(也叫第一扇區,主引導記錄)的內容原封不動地搬到記憶體的0x7C00地址處,並設置CPU寄存器CS=0x07C0,IP=0x0000。到這一步,電腦的控制權將交到操作系統手中!

為什麼是0x7C00這個地址?如何得出?別再問了,本文不再解釋了,具體看筆者的上一篇文章聊聊x86電腦啟動發生的事

對於Linux0.12來說,第一個程式Bootsect.S 編譯成二進位後,需要事先放到主引導記錄MBR中,MBR大小就是一個扇區的大小512位元組,如果這512位元組的最後兩個位元組是0x55AA,表明這個設備可以用於啟動。只有這樣我們BIOS才能識別它,才能把bootsect.S載入到記憶體中。

如果不是0x55和0xAA,表明設備不能用於啟動,控制權於是被轉交給"啟動順序"中的下一個設備。如果到最後還是沒找到符合條件的,直接報出一個無啟動區的error。

下麵我們看下操作系統編譯後,存放在儲存設備(硬碟)的模塊分佈:


先簡單介紹一下,不必深究,後續文章會娓娓道來:

  1. bootsect.s的主要作用就是載入操作系統,把操作系統從硬碟中,載入到記憶體里去
  2. setup.s的主要作用:首先獲得游標,記憶體,顯卡,磁碟等硬體參數存放在記憶體空間中,方便後續程式使用;臨時建立gdt、idt表,並且從實模式進入到了保護模式
  3. 在linux0.12源碼,boot目錄下還有一個head.s,在上圖中被歸於system模塊,屬於操作系統主體文件,主要是進行進入保護模式之後的初始化工作
  4. system模塊:就是操作系統的主體,比如文件系統,IO,進程等模塊。 Linux0.12 內核 system 模塊大約占隨後的 260 個扇區。

更多精彩文章在公眾號「小牛呼嚕嚕

bootsect.S具體幹了什麼?

bootsect的主要作用就是載入操作系統,把操作系統從硬碟中,載入到記憶體里去,我們下麵結合bootsect.s的源碼一起來看看bootsect.S具體幹了什麼?

呼嚕嚕這裡整個過程先匯成了圖,大家配合圖去閱讀下文,對照起來,更容易理解

設置段基址 & 記憶體分段機制

要想bootsect啟動,需要讓BIOS將bootsect.s 從硬碟的MBR中搬到 記憶體位置0x7c00處,大小512個位元組。當bootsect被BIOS載入到記憶體後,電腦的控制權就到操作系統bootsect的手上了。

entry start        ! 告知鏈接程式,程式入口是從start 標號開始執行的
start:
	mov	ax,#BOOTSEG  !BOOTSEG=0x7c0 , 將 ds 段寄存器置為 0x7C0
	mov	ds,ax        !再將 ax 段寄存器里的值複製到 ds 段寄存器里
	mov	ax,#INITSEG  !SETUPSEG=0x9000,將 es 段寄存器置為 0x9000
	mov	es,ax        !再將 ax 段寄存器里的值複製到 es 段寄存器里

	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw     
	jmpi	go,INITSEG

我們可以看到CPU實際執行第一句的代碼 mov ax,#BOOTSEG !BOOTSEG=0x7c0,這是彙編寫的,其實這裡的0x7c0對應的就是我們上文的地址0x7C00

0x7c0是段地址,0x7C00是其實際的物理地址,0x7c0左移四位就是0x7c00,這就是記憶體定址-分段機制

那麼大家一定會有疑問記憶體為什麼分段?

電腦記憶體究竟是什麼?其實它就像數組一樣,咦有人不懂數組是什麼,那麼我們可以再頭腦風暴一下,記憶體其實就像紙帶一樣,我們來看下上古時期的電腦:

穿孔紙帶,圖片來源於網路

紙帶上有一個個孔,這樣大家可能還看不明白,我們再來看一張圖:

這些孔排列組合其實就是二進位數,紙帶其實就是儲存數據的介質,那麼記憶體就是足夠長的“紙帶”

在現代電腦中,記憶體它使用的是DRAM晶元,也叫動態隨機存取存儲器,即只需給出地址,就能直接訪問指定地址的數據,這一點特別像數組,所以許多材料都是用數組來畫記憶體圖

那麼CPU訪問記憶體明明可以直接通過地址訪問記憶體,為什麼還要分段?其實這又是一個歷史因素導致的,讓我們回到"分段"首次出現的時候:"分段"是從Intel 8086晶元開始的,8086又是你......

由於8086那個時代CPU、記憶體都很昂貴, CPU 和寄存器等寬度都是 16 位的,其可定址2的16次方位元組,也就是64kb,然而8086有20根地址線,可定址的最大記憶體空間是1MB。CPU和寄存器的定址能力遠遠不能滿足使用,於是機智的祖師爺們,採用了分段技術

分段,為解決這個問題,8086引入段寄存器,如CS、DS、ES、SS。通過段基址+段內偏移地址的方式生成20位的地址,擴大定址能力,從而實現對1MB記憶體空間的定址。由於這樣程式中指令了只用到16位地址,縮短了指令長度,也變相地提高了程式執行速度。

  • CS:代碼段寄存器,存放代碼段的段基址
  • DS是數據段寄存器,存放數據段的段基址
  • ES是擴展段寄存器,存放當前程式使用附加數據段的段基址,該段是串操作指令中目的串所在的段
  • SS是堆棧段寄存器,存放堆棧段的段基址
  • 80836還新增2個寄存器,FS標誌段寄存器、GS全局段寄存器。

使用段地址還有一個好處是 程式可以重定位,那個時候的電腦可沒有虛擬地址之說,只有物理地址訪問任何存儲單元都直接給出物理地址。這就帶來一個問題: 如果此時電腦多道程式併發運行,程式中的地址都是實際物理地址,這些程式編譯出來的程式運行地址是相同的,電腦只能運行一個程式。

重定向: 將程式中指令的地址改成另一個地址,但該地址處的內容還是原記憶體地址處的內容。這樣程式指令雖然還是物理地址,但程式能夠併發運行了。

1982年處理器80286,首次提出保護模式概念,為了保持相容性,所以同樣支持記憶體分段管理,將8086這種稱為實模式,最大的區別是物理記憶體地址不能直接被程式訪問,這塊非常重要,篇幅也較長,筆者先挖坑,後續系列文章再單獨出一篇。

咳咳,拓展的有點多了,趕緊讓我們回到bootsect源碼處

mov ds,ax 這句話代碼的意思就是:將 ax 段寄存器里的值複製到 ds 段寄存器里。ds在上文我們提到,8086特地為採用記憶體分段機制,引入的段寄存器。ds具體表示 數據段寄存器存放數據段的段基址

換句話說,就是將段基址設為0x07c0,那麼後續數據段程式中只需寫段內偏移地址,就能訪問實際物理地址了。比如後續程式中出現mov ax,0x010x01其實是[ds:0x01],那麼ax的實際物理地址= 0x07c0 <<4 + 0x01。將ds寄存器段基址設置好後,其實就是方便之後程式訪問記憶體,訪問的數據的記憶體地址都先預設加上 0x7c00,然後再去記憶體中定址。

如果實際編程時,代碼段的起始地址一般放到 CS寄存器,雖然CPU沒有強制規定代碼段、數據段等分離。

mov ax,#INITSEGmov es,ax 將 ax 段寄存器里的值0x9000複製到 es 段寄存器里,和ds賦值同理,不再贅述。需要註意的是8086無法直接給段寄存器進行賦值,需要使用通用寄存器來當中介(一般使用ax)

bootsect的"再次搬家"到0x90000

接著bootsect自己把自己從記憶體位置0x7c00處,搬到0x90000處,這次可沒BIOS幫忙了,得自食其力

          
start:
	mov	ax,#BOOTSEG  
	mov	ds,ax        
	mov	ax,#INITSEG  
	mov	es,ax        

	mov	cx,#256       ! 設置移動計數值=256 字(512 位元組);
	sub	si,si         ! si寄存器 清零
	sub	di,di         ! di寄存器 清零
	rep               ! 重覆執行並遞減 cx 的值,直到 cx = 0 為止。
	movw              ! 即 movs 指令。從記憶體[si]處移動 cx 個字到[di]處。//一次移動兩個位元組,256B*2=512B

mov cx,#256 將cx 寄存器的值賦值為 256,單位是字(Word), 1 word=2Byte

sub si,si 是si寄存器 清零操作,sub是彙編語言中的一種運算指令,它用來執行減法運算,並將結果存儲到被減數(前者)上去。比如sub a,b就是a = a-b。再結合前面的ds,es,那麼此時si的段地址ds:si = 0x07C0:0x0000,同理di的段地址es:di = 0x9000:0x0000

rep就是重覆執行後一條指令,movw就是複製的意思。rep movw 就是重覆多次搬運

我們可以知道這段的總體意思就是:迴圈256次,反覆將段地址0x07C0:0x0000的內容一個字一個字的複製到段地址0x9000:0x0000處,直到寄存器cx為0。這樣就實現了bootsect的"自我搬運",把實際物理記憶體地址0x7c00處512個位元組的內容全部複製到實際物理記憶體地址0x90000處

那為啥bootsect還要"多此一舉" 將自己從0x7c00,搬到0x90000處?

  • 操作系統system後續最終是要從物理記憶體起始位置處 地址0開始存放,好處是讓system代碼中的地址對應上實際的物理地址。
  • 一般要留512KB的記憶體空間放操作系統system,會覆蓋0x7c00地址的內容,所以需要把bootsect代碼搬到記憶體更高處。

載入setup.s到記憶體0x90200

當上面bootsect完成自我搬運後,緊接著執行jmpi go,INITSEG,jmpi有段間跳轉的作用。這裡 INITSEG 指出跳轉到的段地址0x9000,標號 go 是段內偏移地址。

其實就是執行完jmpi go,INITSEG後,CPU已經移動到記憶體0x90000+go位置處的代碼中 執行。為啥要加go?其實此時bootsect編譯後的二進位內容,已經搬運到記憶體0x90000處,但是我們不能再從頭執行start: mov ax,#BOOTSEG操作,而是從go: mov ax,cs處代碼繼續執行下去。

	jmpi	go,INITSEG  ! 段間跳轉。這裡 INITSEG 指出跳轉到的段地址,標號 go 是段內偏移地址。

go:	mov	ax,cs		
	mov	dx,#0xfef4	! arbitrary value >>512 - disk parm size

	mov	ds,ax
	mov	es,ax
	push	ax        ! 臨時保存段值(0x9000)

	mov	ss,ax		    ! put stack at 0x9ff00 - 12.
	mov	sp,dx

	push	#0        ! 置段寄存器 fs = 0。
	pop	fs          ! fs:bx 指向存有軟碟機參數表地址處(指針的指針)
	mov	bx,#0x78		! fs:bx is parameter table address
	seg fs
	lgs	si,(bx)			! gs:si is source

	mov	di,dx			! es:di is destination
	mov	cx,#6			! copy 12 bytes
	cld

	rep           ! 複製 12 位元組的軟碟機參數表到 0x9000:0xfef4 處。
	seg gs
	movw

	mov	di,dx
	movb	4(di),*18		! patch sector count

	seg fs         ! 讓中斷向量 0x1E 的值指向新表。
	mov	(bx),di
	seg fs
	mov	2(bx),es

	pop	ax
	mov	fs,ax
	mov	gs,ax
	
	xor	ah,ah			! reset FDC 讓中斷向量 0x1E 的值指向新表。
	xor	dl,dl
	int 	0x13	

上述主要是將 寄存器DS、ES 和SS 重新設置為CPU移動後,代碼所在的段處0×9000 ,設置SP棧寄存器0xfef4
棧指針要遠大於512位元組偏移(即 0x90200 )處都可以,一般setup程式大概占用4個扇區,這樣棧頂段地址ss:sp和現有的代碼足夠遠 ,防止後續棧操作覆蓋掉已有的代碼。

還有BIOS 設置的中斷 0x1e 的中斷向量值等操作。這邊和主幹操作不太相干,簡略過一下,主要就是把這些寄存器重新設置好值,方便後續使用。

更多精彩文章在公眾號「小牛呼嚕嚕

接下來緊接著將setup.s 載入到記憶體0x90200

load_setup:
	xor	dx, dx			         ! 驅動器drive 0, 磁頭head 0
	mov	cx,#0x0002		    	 ! 扇區sector 2, 磁軌號track 0,從第二個扇區開始讀
	mov	bx,#0x0200					 ! 偏移address = 512, in INITSEG ,表示讀到0x90200
	mov	ax,#0x0200+SETUPLEN	 ! service 2, nr of sectors ,SETUPLEN是 4個扇區
	int	0x13								 ! read it


	jnc	ok_load_setup				 ! ok,就跳到ok_load_setup

	push	ax								 ! dump error code
	call	print_nl           ! 屏幕游標回車
	mov	bp, sp
	call	print_hex          ! 顯示十六進位值
	pop	ax	
  	
	xor	dl, dl							 ! reset FDC
	xor	ah, ah
	int	0x13
	j	load_setup             ! j 即 jmp 指令,失敗就再跳轉到load_setup,重覆執行

那怎麼簡單高效將磁碟里的內容載入到記憶體中呢?linus這裡用的是bios的中斷程式,因為此時bios還在記憶體中,可以為我們所用,0x13號中斷 在BIOS中是可以訪問軟盤、IDE、ROM、遠程磁碟服務的作用。

這裡0x13 和C語言中的函數調用是很像的,不過需要註意的是它的參數只能通過寄存器去傳參,而C語言函數調用不僅可以寄存器傳參,還可以棧傳參。所以0x13的參數就是其前面的dx,cx,bx,ax寄存器的值,另外磁碟只認磁頭磁軌扇區,如果給個地址,磁碟是不識別的,磁碟一副不太聰明的樣子。

另外xor對兩個操作數進行邏輯(按位)異或操作,並將結果存放在目標操作數,xor dx,dx也是一個置零操作,指定驅動和磁頭

那麼我們連起來,這段主要是讓bios 0x13號中斷處理程式 從磁碟的第2扇區開始讀,接連讀4個扇區的內容到記憶體0x90200處中。成功就跳轉到ok_load_setup,沒成功就回到load_setup,重覆執行上述操作。

載入system到記憶體0x10000

當bootsect成功將setup.s搬到記憶體0x90200處後,CPU從ok_load_setup處繼續執行指令。接下來就是需要將整個操作系統system(head.s+其他文件,大約260個扇區)的內容載入到記憶體0x10000處,下麵我們就具體看下代碼是如何實現的:

ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track  
!提示這面段代碼功能是:利用BIOSINT 0x13 中斷,來來取磁碟的一些參數,比如是取每磁軌扇區數,並保存在
位置 sectors 處

	xor	dl,dl
	mov	ah,#0x08		! AH=8 is get drive parameters
	int	0x13
	xor	ch,ch
	seg cs          !表示下一條語句的操作數在 cs 段寄存器所指的段中。它隻影響其下一條語句
	mov	sectors,cx
	mov	ax,#INITSEG
	mov	es,ax       !取磁碟參數中斷改了es寄存器的值,這裡重置es的值


! Print some inane message 提示下麵這段功能是:列印一些消息

	mov	ah,#0x03		  ! read cursor pos 讀取當前游標的地址
	xor	bh,bh
	int	0x10          ! bios 0x10中斷,其作用:在屏幕上顯示字元和字元串
	
	mov	cx,#9
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1      ! msg1的內容是:  .byte 13,10(換行+回車)  .ascii "Loading"
	mov	ax,#0x1301		! write string, move cursor
	int	0x10


! ok, we've written the message, now
! we want to load the system (at 0x10000)   載入system到記憶體0x10000

	mov	ax,#SYSSEG
	mov	es,ax		     ! segment of 0x010000
	call	read_it    ! 讀磁碟上 system 模塊
	call	kill_motor ! 關閉驅動器馬達
	call	print_nl   ! 游標回車換行

	... 省略非主幹代碼...

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:

	jmpi	0,SETUPSEG   !bootsect程式到這裡就結束了,跳轉到0x9020,同時setup獲得控制權

這裡int 0x10號中斷,其作用是 在屏幕上顯示字元和字元串,由於操作系統比較大,載入需要時間,這時在屏幕上顯示提示信息"Loading"

這裡將操作系統載入到記憶體中,是通過子程式read_it來實現的,read_it就不具體展開了,比較複雜。我們需要知道由於操作系統比較大,一個磁軌是遠遠放不下的,另外磁碟是不認地址的,在搬運過程中,需要進行磁軌、扇區和磁頭的計算,特別是一個段的大小是64k,如果放不下,需要更換段地址。如果不更換段地址,會從該段地址0位元組開始重新寫,這樣會覆蓋之前的內容。

那為什麼一個段的大小是64KB呢?
我們知道在8086CPU中,其記憶體地址是表示為段基址+段內偏移地址,其中偏移地址使用一個16位的二進位數表示,表示範圍0000~FFFF,所以總共有2^16(2的16次方)=64K個不同的地址,一個記憶體最小單元是位元組Byte,所以一個段大小為64KB

jmpi 0,SETUPSEG,bootsect程式到這裡就結束了,跳轉到記憶體地址0x90200,同時setup獲得控制權

為了幫助大家理解,呼嚕嚕這裡又把本篇文章全部串起來,大家可以根據下麵這張圖重新回顧一下bootsect整個工作流程:

額外補充一下:

boot_flag: .word 0xAA55 最後2個位元組是0xAA55,由於bootsect是採用AT&T彙編,小端顯示的,實際上就是0x55AA與前文MBR那邊前後呼應

這也說明瞭操作系統在開始載入到記憶體的程式中,得與記憶體地址一一對應, 不能多一個位元組,也不能少一個位元組!!!

尾語

本文主要講解了bootsect.S的主要工作流程,Linux0.12雖然和如今的Linux6.x內核相比顯得過於簡陋,但麻雀雖小五臟俱全,它是我們打開操作系統大門的鑰匙,後面讓我們看看setup.s獲得電腦的控制權後,會發生什麼?

最近實在太忙了,後面隨緣更新,留言可催更(bushi)~~


參考資料:

《Linux內核完全註釋5.0》
《操作系統真象還原》
https://elixir.bootlin.com/linux/0.12/source/boot/bootsect.S
https://files.embeddedts.com//old/saved-downloads-manuals/EBIOS-UM.PDF


本篇文章到這裡就結束啦,如果我的文章對你有所幫助的話,還請點個免費的,你的支持會激勵我輸出更高質量的文章,感謝!


作者:小牛呼嚕嚕 ,首發於公眾號 小牛呼嚕嚕,系列文章還有:

  1. 聊聊x86電腦啟動發生的事?
  2. Linux0.12內核源碼解讀(2)-Bootsect.S
  3. Linux0.12內核源碼解讀(3)-Setup.S
  4. 圖解CPU的實模式與保護模式
  5. Linux0.12內核源碼解讀(7)-陷阱門初始化
  6. 圖解電腦中斷
  7. 什麼是系統調用機制?結合Linux0.12源碼圖解

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

-Advertisement-
Play Games
更多相關文章
  • 在構建用戶界面時,控制項扮演著至關重要的角色。它們不僅負責展示內容,還處理用戶的交互。然而,有時標準的控制項庫可能無法滿足我們的需求,這時自繪控制項就顯得尤為重要。在Avalonia UI框架中,自繪控制項允許我們完全掌控控制項的渲染邏輯,實現高度自定義的UI元素。本文將深入探討自繪控制項的概念、優勢、應用場景 ...
  • 官網:一個 Vue 3 UI 框架 | Element Plus (element-plus.org) 1.安裝:運行cmd,轉到我的項目的目錄下\vuedemo ,執行命令:npm install element-plus --save 2.整體導入Element-plus,修改mian.js文件 ...
  • 概述:在C#中,尋找調用當前方法的方法可採用`StackTrace`和`CallerMemberName`兩種方式。`StackTrace`通過分析堆棧信息提供詳細信息,而`CallerMemberName`則簡化了獲取調用者方法名的過程,更輕量且效率較高。選擇取決於需求,若需要堆棧信息,可選`St ...
  • 概述:在C#中,選擇使用結構(struct)而非類(class)取決於數據大小、不可變性和性能需求。結構適用於小型、不可變的數據對象,具有輕量級和高性能的優勢。然而,對於複雜對象和需要繼承的情況,應選擇類。以下是一個簡單的結構示例,演示了結構在棧上分配記憶體和不可變性的特性。在程式設計中,根據實際需求 ...
  • 官網:Vue Router | Vue.js 的官方路由 (vuejs.org) 安裝命令:npm install vue-router@4 1.添加兩個頁面\vuedemo\src\views\index.vue、\vuedemo\src\views\content.vue 2.添加\vuedem ...
  • 威聯通NAS VirtualizationStation 安裝ubuntu配置SSH遠程訪問,解決虛擬機記憶體分配和Linux SSH穿透後遠程連接的問題 ...
  • VS studio上查看標準cout輸出 網上的方法 在解決方案管理器中,單擊選中項目後,點擊菜單【視圖】->【屬性頁】 在生成事件->生成後事件->命令行(Build Events->Post-Build Event->Command) Line)中增加$(OutDir)$(ProjectName ...
  • 本文介紹筆記本電腦出現No Bootable Device錯誤提示,且無法開機的多種解決辦法。 1 問題產生 最近,筆記本電腦正在正常使用時,突然藍屏,出現你的設備遇到問題,需要重啟。的提示;最下方的終止代碼具體是CRITICAL_PROCESS_DIED還是SYSTEM_SERVICE_EXCEP ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...