主頁 > 作業系統 > MIT6.828——Lab1 partA(麻省理工作業系統課程實驗)

MIT6.828——Lab1 partA(麻省理工作業系統課程實驗)

2021-10-03 06:06:08 作業系統

Lab1

基本部分

在實驗給出的檔案中,已經詳說明了早期PC的記憶體布局,并且運行了bootloader,詳細地解釋了,上電后BIOS所做的作業,因此這部分不再贅述,需要注意的是bootloader的職能:

  • 進入保護模式
  • 從磁盤加載kernel到記憶體

boot/boot.S

實驗檔案中要求好好理解boot/boot.Sboot/main.c ,先看匯編檔案

# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.set PROT_MODE_CSEG, 0x8         # kernel code segment selector
.set PROT_MODE_DSEG, 0x10        # kernel data segment selector
.set CR0_PE_ON,      0x1         # protected mode enable flag

.globl start
start:
  .code16                     # Assemble for 16-bit mode
  cli                         # Disable interrupts
  cld                         # String operations increment

可以看到第一條被加載到0x7c00處的代碼應該是cli關中斷指令,之后進行了部分暫存器的初始化

# Set up the important data segment registers (DS, ES, SS).
  xorw    %ax,%ax             # Segment number zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

然后是開啟A20地址線,早期8086處理器,只有20根地址線,因此編碼時地址最高到0xfffff之后便會歸零,然而在80286時期,地址線已經增長到24根,為了早期程式的兼容性,A20(第21根地址線)是默認關閉的,現在為了進入保護模式我們需要手動開啟這根線,開啟的方法是利用8042芯片的IO埠,

 # Enable A20:
  #   For backwards compatibility with the earliest PCs, physical
  #   address line 20 is tied low, so that addresses higher than
  #   1MB wrap around to zero by default.  This code undoes this.
seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

首先了解一下8042的狀態暫存器

Bit7: PARITY-EVEN(P_E): 從鍵盤獲得的資料奇偶校驗錯誤
Bit6: RCV-TMOUT(R_T): 接收超時,置1
Bit5: TRANS_TMOUT(T_T): 發送超時,置1
Bit4: KYBD_INH(K_I): 為1,鍵盤沒有被禁止,為0,鍵盤被禁止,
Bit3: CMD_DATA(C_D): 為1,輸入緩沖器中的內容為命令,為0,輸入緩沖器中的內容為資料,
Bit2: SYS_FLAG(S_F): 系統標志,加電啟動置0,自檢通過后置1
Bit1: INPUT_BUF_FULL(I_B_F): 輸入緩沖器滿置1,i8042 取走后置0
BitO: OUT_BUF_FULL(O_B_F): 輸出緩沖器滿置1,CPU讀取后置0

然后便是埠地址,8042只使用兩個埠

0x64 :命令埠

0x60 :資料埠

然后便是一些相關的命令,這里只選取和代碼相關的:

驅動對鍵盤控制器發送命令是通過寫埠64h實作:

D1h
準備寫Output埠,隨后通過60h埠寫入的位元組,會被放置在Output Port中,

關于向8042發送命令前的準備作業:

向i8042發命令的方法,首先,讀取狀態暫存器,判斷bit1,狀態暫存器bit1為0,說明輸入緩沖器為空,可以寫入,保證狀態暫存器bit1為0,然后對64h埠進行寫操作,寫入命令,

那么這段代碼就很明顯了,至于0xdf解釋如下

0xdd :disenable A20

0xdf :enable A20

至此A20便開啟了,


下面是關于加載GDT的代碼

# Switch from real to protected mode, using a bootstrap GDT
  # and segment translation that makes virtual addresses 
  # identical to their physical addresses, so that the 
  # effective memory map does not change during the switch.
  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0
  #......
  # Bootstrap GDT
.p2align 2                                # force 4 byte alignment
gdt:
  SEG_NULL				# null seg
  SEG(STA_X|STA_R, 0x0, 0xffffffff)	    # code seg
  SEG(STA_W, 0x0, 0xffffffff)	        # data seg
gdtdesc:
  .word   0x17                            # sizeof(gdt) - 1
  .long   gdt                             # address gdt

指令lgdt裝載了gdt descriptor,其中的0x17是全域描述符表的大小,一共裝載了3個段,每個段的大小是8位元組,因此24位元組,關于gdt可以看下圖:

而對于這三個段的定義,需要結合#include <inc/mmu.h>

/*
 * Macros to build GDT entries in assembly.
 */
#define SEG_NULL						\
	.word 0, 0;						\
	.byte 0, 0, 0, 0
#define SEG(type,base,lim)					\
	.word (((lim) >> 12) & 0xffff), ((base) & 0xffff);	\
	.byte (((base) >> 16) & 0xff), (0x90 | (type)),		\
		(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
//......
#define STA_X		0x8	    // Executable segment
#define STA_E		0x4	    // Expand down (non-executable segments)
#define STA_C		0x4	    // Conforming code segment (executable only)
#define STA_W		0x2	    // Writeable (non-executable segments)
#define STA_R		0x2	    // Readable (executable segments)
#define STA_A		0x1	    // Accessed

為了更好地理解,先看一下Intel手冊中,關于保護模式下記憶體管理地描述(chapter3),幾個值得注意的點,一個是關于GDTR

這就是ldgt裝載地暫存器,前文的代碼含義便很清楚了,

下圖是關于段的type

因此:

#define STA_X		0x8	    // Executable segment
#define STA_W		0x2	    // Writeable (non-executable segments)
#define STA_R		0x2	    // Readable (executable segments)

所以(STA_X|STA_R)便是type(1010),即可執行可讀,

下圖是關于segment descriptor

有幾個域是我們感興趣的:

Segment limit field
Specifies the size of the segment. The processor puts together the two segment limit fields to form a 20-bit value.

Base address fields
Defines the location of byte 0 of the segment within the 4-GByte linear address space. The processor puts together the three base address fields to form a single 32-bit value. Segment base addresses should be aligned to 16-byte boundaries. Although 16-byte alignment is not required,this alignment allows programs to maximize performance by aligning code and data on 16-byte boundaries.

Type field

Indicates the segment or gate type and specifies the kinds of access that can be made to the segment and the direction of growth.

S (descriptor type) flag
Specifies whether the segment descriptor is for a system segment (S flag is clear) or a code or data segment (S flag is set).

DPL (descriptor privilege level) field
Specifies the privilege level of the segment. The privilege level can range from 0 to 3, with 0 being the most privileged level.

P (segment-present) flag
Indicates whether the segment is present in memory (set) or not present (clear).

D/B (default operation size/default stack pointer size and/or upper bound) flag
Performs different functions depending on whether the segment descriptor is an executable code segment, an expand-down data segment, or a stack segment. (This flag should always be set to 1 for 32-bit code and data segments and to 0 for 16-bit code and data segments.)

L (64-bit code segment) flag
In IA-32e mode, bit 21 of the second doubleword of the segment descriptor indicates whether a code segment contains native 64-bit code. A value of 1 indicates instructions in this code segment are executed in 64-bit mode. A value of 0 indicates the instructions in this code segment are executed in compatibility mode.

G (granularity) flag
Determines the scaling of the segment limit field. When the granularity flag is clear, the segment limit is interpreted in byte units; when flag is set, the segment limit is interpreted in 4-KByte units. (This flag does not affect the granularity of the base address; it is always byte granular.) When the granularity flag is set, the twelve least significant bits of an offset are not tested when checking the offset against the segment limit. For example, when the granularity flag is set, a limit of 0 results in valid offsets from 0 to 4095.

這之后我們再去看代碼,便很清晰了,以

SEG(STA_X|STA_R, 0x0, 0xffffffff)	    # code seg

為例子,先解讀C語言的這段宏

#define SEG(type,base,lim)					\
	.word (((lim) >> 12) & 0xffff), ((base) & 0xffff);	\
	.byte (((base) >> 16) & 0xff), (0x90 | (type)),		\
		(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)

.word (((lim) >> 12) & 0xffff), ((base) & 0xffff)

獲得了界限的bit12-27,共計16位,在(((lim) >> 28) & 0xf)中獲得了最高4位,共計20位,因為要映射到完整的4G空間,也就是說,段限最大0xffffffff,因為后面置位了段描述符的G位,因此以4KB為單元就擴展到了最大段限制4G,

(0x90 | (type)),(0xC0 | (((lim) >> 28) & 0xf))

這里只需解釋:

0x90 P=1(在記憶體中) DPL=00(特權級0) S=1(代碼段或者資料段)

0xC0 G=1(4KB單位解釋段限) D/B=1(32位代碼應總是1) L=0 AVL=0

至此關于GDT的這段故事完結,

之后

movl    %cr0, %eax
orl     $CR0_PE_ON, %eax
movl    %eax, %cr0

先看關于CR0

CR0.PE
Protection Enable (bit 0 of CR0) — Enables protected mode when set; enables real-address mode when clear. This flag does not enable paging directly. It only enables segment-level protection. To enable paging, both the PE and PG flags must be set.

所以PE允許了保護模式的開啟,并且開啟了分段保護機制(沒開啟分頁PG),這也就是為什么要在此之前建立GDT,至此,已經進入了32位的保護模式,因此尋址方式也已經發生變化,

先了解段選擇子

保護模式是通過“段選擇符+段內偏移”尋址最終的線性地址或物理地址的,

TI位,選擇GDT或者是LDT

因此

# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp    $PROT_MODE_CSEG, $protcseg

段選擇子就是$PROT_MODE_CSEG也就是0x8,即0000000000001 0 00

所以是GDT的第二項,基址為0x00000000,因此與之前的CS一致,

這里針對保護模式下的段選擇子和段暫存器再多寫一點[來自Intel手冊Volume 3],

為了減少地址轉換用時和編碼的復雜度,處理器提供6個段暫存器來保存段選擇子,

每個段暫存器都支持特定的記憶體尋址,對于任何程式的執行,至少要將CS,DS,SS賦予有效的段選擇符,當一個行程需要訪問某個段時,這個段的段選擇子必須被賦予到某一個段暫存器中,因此,盡管可以定義很多段,但是只有6個能被直接使用,每個段暫存器都由兩個部分組成,可見部分和不可見部分,當一個段選擇子被加載到段暫存器的可見部分時,處理器也通過段選擇符指向的段描述符獲得了這個段的不可見資訊,

有兩種裝入段暫存器的指令:

  • 直接載入

    mov pop lds les lss lgslfs

  • 隱含地載入

    far pointer 版本的call jmp ret指令,還有iret intn int0 int3 指令,伴隨著這些指令的進行,他們改變了CS暫存器,

# Set up the stack pointer and call into C.
  movl    $start, %esp
  call bootmain

把堆疊頂設在了start處,也就是0x7c00,之后呼叫了C語言函式,

參考材料

[關于8042詳細的解讀]https://blog.csdn.net/wyyy2088511/article/details/108847079

[關于A20地址線]https://docs.huihoo.com/gnu_linux/own_os/booting-a20_4.htm

[關于0xdf]https://stackoverflow.com/questions/15768683/the-a20-line-with-jos

[關于A20與編程]https://www.win.tue.nl/~aeb/linux/kbd/A20.html

[GDT wiki]https://en.wikipedia.org/wiki/Global_Descriptor_Table

[lgdt]https://www.jianshu.com/p/2cb94c4c0cd0

[ucore]https://zhuanlan.zhihu.com/p/67259776

[CHAPTER 3 protected-mode]https://pdos.csail.mit.edu/6.828/2018/readings/ia32/IA32-3A.pdf

[ucore boot]https://www.cnblogs.com/maruixin/p/3175894.html

boot/main.c

先看一個比較簡單的函式

void waitdisk(void)
{
	// wait for disk reaady
	while ((inb(0x1F7) & 0xC0) != 0x40)
		/* do nothing */;
}

1F7在被讀的時候是作為狀態暫存器使用,其中bit_7=0表示控制器空閑,bit_6=1表示驅動器就緒,因此,waitdisk在控制器空閑和驅動器就緒同時成立時才會結束等待,即在0100 0000時退出等待,

下面是一個讀取扇區的函式

void readsect(void *dst, uint32_t offset)
{
	// wait for disk to be ready
	waitdisk();

	outb(0x1F2, 1);		// count = 1
	outb(0x1F3, offset);
	outb(0x1F4, offset >> 8);
	outb(0x1F5, offset >> 16);
	outb(0x1F6, (offset >> 24) | 0xE0);
	outb(0x1F7, 0x20);	// cmd 0x20 - read sectors

	// wait for disk to be ready
	waitdisk();

	// read a sector
	insl(0x1F0, dst, SECTSIZE/4);
}

首先

outb(port,data): 向port寫入1位元組資料data

insl(port,addr,cnt) : 從port讀cnt個dword到addr中去

關于埠的含義,可以參考UCORE的實驗教材

因此,這個函式從磁盤上讀了SECTSIZE/4dword的資料,也就是512位元組,剛好一個扇區,

下面一個函式,進行kernel的加載,一個輔助的函式是從磁盤讀到記憶體中特定的地址上,

// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
// Might copy more than asked
void
readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
	uint32_t end_pa;
	end_pa = pa + count;
	// round down to sector boundary
	pa &= ~(SECTSIZE - 1);

	// translate from bytes to sectors, and kernel starts at sector 1
	offset = (offset / SECTSIZE) + 1;

	// If this is too slow, we could read lots of sectors at a time.
	// We'd write more to memory than asked, but it doesn't matter --
	// we load in increasing order.
	while (pa < end_pa) {
		// Since we haven't enabled paging yet and we're using
		// an identity segment mapping (see boot.S), we can
		// use physical addresses directly.  This won't be the
		// case once JOS enables the MMU.
		readsect((uint8_t*) pa, offset);
		pa += SECTSIZE;
		offset++;
	}
}

在解釋bootmain之前需要了解一下ELF檔案格式

ELF檔案(Executable Linkable Format)是一種檔案存盤格式,Linux下的目標檔案和可執行檔案都按照該格式進行存盤,代碼編譯后的指令放在代碼段,全域變數和區域靜態變數放到資料段,檔案以一個“檔案頭”開始,記錄了整個檔案的屬性資訊,

以下是inc/elf.h的部分

#define ELF_MAGIC 0x464C457FU	/* "\x7FELF" in little endian */

struct Elf {
	uint32_t e_magic;	// must equal ELF_MAGIC
	uint8_t e_elf[12];
	uint16_t e_type;
	uint16_t e_machine;
	uint32_t e_version;
	uint32_t e_entry;  //程式入口的虛地址
	uint32_t e_phoff;  //program header表的位置偏移
	uint32_t e_shoff;
	uint32_t e_flags;
	uint16_t e_ehsize;
	uint16_t e_phentsize;
	uint16_t e_phnum;  //program header表中的入口數目
	uint16_t e_shentsize;
	uint16_t e_shnum;
	uint16_t e_shstrndx;
};
//一個ELF檔案中分為好幾個段,程式段、資料段等
struct Proghdr {
	uint32_t p_type;	// 段型別
	uint32_t p_offset;  // 段相對檔案頭的偏移值
	uint32_t p_va;		// 段的第一個位元組將被放到記憶體中的虛擬地址
	uint32_t p_pa;
	uint32_t p_filesz;
	uint32_t p_memsz;	// 段在記憶體映像中占用的位元組數
	uint32_t p_flags;
	uint32_t p_align;
};

下面是bootmain的實作,關鍵部分給與注釋

void
bootmain(void)
{
    //兩個program header指標
	struct Proghdr *ph, *eph;

    //磁盤第一個頁讀到記憶體位置0x10000的位置
    //頁大小512*8=4kB 偏移量為0
	// read 1st page off disk
	readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
	
	// is this a valid ELF?
	if (ELFHDR->e_magic != ELF_MAGIC)
		goto bad;

    // 程式頭表的頭指標,為ELF檔案的起始地址加上程式頭表的偏移量
	// load each program segment (ignores ph flags)
	ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
    
    // 程式頭表的尾指標,為ELF檔案的頭指標加上程式頭表的段數
	eph = ph + ELFHDR->e_phnum;
    
	for (; ph < eph; ph++)
        //回圈讀取ELF程式頭表中的每個段(代碼段,資料段)到記憶體中
		// p_pa is the load address of this segment (as well
		// as the physical address)
        // pa物理地址 memsz占用的位元組 offset偏移
		readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
	//至此內核就加載完成了,馬上移交控制權
	// call the entry point from the ELF header
	// note: does not return!
    // 跳轉到內核程式的入口,CPU 控制權交給kernel,不再回傳
    // 內核代碼會被加載到指定位置,都會以一個ELF格式開頭,以此可以得到入口地址
	((void (*)(void)) (ELFHDR->e_entry))();
bad:
	outw(0x8A00, 0x8A00);
	outw(0x8A00, 0x8E00);
	while (1)
		/* do nothing */;
}

至此,bootloader的程式代碼已經沒有秘密了,是一個很底層的程序,但對于了解計算機啟動的初始階段很有幫助,

下一篇文章,包括kernel部分與lab1的習題,

參考文章

[ports]https://bochs.sourceforge.io/techspec/PORTS.LST

[ports]http://web.archive.org/web/20040304063834/http://members.iweb.net.au/~pstorr/pcbook/book2/ioassign.htm

[ucore ports]https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_3_dist_accessing.html

[insl]https://stackoverflow.com/questions/38410829/why-cant-find-the-insl-instruction-in-x86-document

[ucore elf]https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_4_elf.html

[elf]https://blog.csdn.net/qqNCer/article/details/105984272

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/304793.html

標籤:其他

上一篇:openEuler 20.03/21.03 - 華為歐拉開源版(CentOS 8 華為版開源版)下載

下一篇:VMware ESXi 7.0 U3 SLIC 2.6 & Unlocker

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more