嵌入式BootLoader技術內幕(二)

三、Boot Loader 的主要任務與典型結構框架

在繼續本節的討論之前,首先我們做一個假定,那就是:假定內核映像與根文件系統映像
都被加載到 RAM 中運行。之所以提出這樣一個假設前提是因為,在嵌入式系統中內核映像
與根文件系統映像也可以直接在 ROM 或 Flash 這樣的固態存儲設備中直接運行。但這種
做法無疑是以運行速度的犧牲為代價的。從操作系統的角度看,Boot Loader 的總目標就
是正確地調用內核來執行。

另外,由于 Boot Loader 的實現依賴于 CPU 的體系結構,因此大多數 Boot Loader 都分
為 stage1 和 stage2 兩大部分。依賴于 CPU 體系結構的代碼,比如設備初始化代碼等,
通常都放在 stage1 中,而且通常都用匯編語言來實現,以達到短小精悍的目的。而 sta
ge2 則通常用C語言來實現,這樣可以實現給復雜的功能,而且代碼會具有更好的可讀性和
可移植性。

Boot Loader 的 stage1 通常包括以下步驟(以執行的先后順序):
·硬件設備初始化。
·為加載 Boot Loader 的 stage2 準備 RAM 空間。
·拷貝 Boot Loader 的 stage2 到 RAM 空間中。
·設置好堆棧。
·跳轉到 stage2 的 C 入口點。
Boot Loader 的 stage2 通常包括以下步驟(以執行的先后順序):
·初始化本階段要使用到的硬件設備。
·檢測系統內存映射(memory map)。
·將 kernel 映像和根文件系統映像從 flash 上讀到 RAM 空間中。
·為內核設置啟動參數。
·調用內核。

3.1 Boot Loader 的 stage1

3.1.1 基本的硬件初始化

這是 Boot Loader 一開始就執行的操作,其目的是為 stage2 的執行以及隨后的 kernel
的執行準備好一些基本的硬件環境。它通常包括以下步驟(以執行的先后順序):

1.屏蔽所有的中斷。為中斷提供服務通常是 OS 設備驅動程序的責任,因此在 Boot Loa
der 的執行全過程中可以不必響應任何中斷。中斷屏蔽可以通過寫 CPU 的中斷屏蔽寄存器
或狀態寄存器(比如 ARM 的 CPSR 寄存器)來完成。

2.設置 CPU 的速度和時鐘頻率。

3.RAM 初始化。包括正確地設置系統的內存控制器的功能寄存器以及各內存庫控制寄存器
等。

4.初始化 LED。典型地,通過 GPIO 來驅動 LED,其目的是表明系統的狀態是 OK 還是
Error。如果板子上沒有 LED,那么也可以通過初始化 UART 向串口打印 Boot Loader 的
Logo 字符信息來完成這一點。

5. 關閉 CPU 內部指令/數據 cache。

3.1.2 為加載 stage2 準備 RAM 空間

為了獲得更快的執行速度,通常把 stage2 加載到 RAM 空間中來執行,因此必須為加載
Boot Loader 的 stage2 準備好一段可用的 RAM 空間范圍。

由于 stage2 通常是 C 語言執行代碼,因此在考慮空間大小時,除了 stage2 可執行映象
的大小外,還必須把堆棧空間也考慮進來。此外,空間大小最好是 memory page 大小(通
常是 4KB)的倍數。一般而言,1M 的 RAM 空間已經足夠了。具體的地址范圍可以任意安排
,比如 blob 就將它的 stage2 可執行映像安排到從系統 RAM 起始地址 0xc0200000 開始
的 1M 空間內執行。但是,將 stage2 安排到整個 RAM 空間的最頂 1MB(也即(RamEnd-1M
B) - RamEnd)是一種值得推薦的方法。

為了后面的敘述方便,這里把所安排的 RAM 空間范圍的大小記為:stage2_size(字節),
把起始地址和終止地址分別記為:stage2_start 和 stage2_end(這兩個地址均以 4 字節
邊界對齊)。因此:

stage2_end=stage2_start+stage2_size



另外,還必須確保所安排的地址范圍的的確確是可讀寫的 RAM 空間,因此,必須對你所安
排的地址范圍進行測試。具體的測試方法可以采用類似于 blob 的方法,也即:以 memor
y page 為被測試單位,測試每個 memory page 開始的兩個字是否是可讀寫的。為了后面
敘述的方便,我們記這個檢測算法為:test_mempage,其具體步驟如下:

1.先保存 memory page 一開始兩個字的內容。

2.向這兩個字中寫入任意的數字。比如:向第一個字寫入 0x55,第 2 個字寫入 0xaa。


3.然后,立即將這兩個字的內容讀回。顯然,我們讀到的內容應該分別是 0x55 和 0xaa
。如果不是,則說明這個 memory page 所占據的地址范圍不是一段有效的 RAM 空間。


4.再向這兩個字中寫入任意的數字。比如:向第一個字寫入 0xaa,第 2 個字中寫入 0x
55。

5.然后,立即將這兩個字的內容立即讀回。顯然,我們讀到的內容應該分別是 0xaa 和
0x55。如果不是,則說明這個 memory page 所占據的地址范圍不是一段有效的 RAM 空間


6.恢復這兩個字的原始內容。測試完畢。

為了得到一段干凈的 RAM 空間范圍,我們也可以將所安排的 RAM 空間范圍進行清零操作


3.1.3 拷貝 stage2 到 RAM 中

拷貝時要確定兩點:(1) stage2 的可執行映象在固態存儲設備的存放起始地址和終止地址
;(2) RAM 空間的起始地址。

3.1.4 設置堆棧指針 sp

堆棧指針的設置是為了執行 C 語言代碼作好準備。通常我們可以把 sp 的值設置為(stag
e2_end-4),也即在 3.1.2 節所安排的那個 1MB 的 RAM 空間的最頂端(堆棧向下生長)。
此外,在設置堆棧指針 sp 之前,也可以關閉 led 燈,以提示用戶我們準備跳轉到 stag
e2。經過上述這些執行步驟后,系統的物理內存布局應該如下圖2所示。

3.1.5 跳轉到 stage2 的 C 入口點

在上述一切都就緒后,就可以跳轉到 Boot Loader 的 stage2 去執行了。比如,在 ARM
系統中,這可以通過修改 PC 寄存器為合適的地址來實現。


http://tech.ccidnet.com/pub/attachme.../12/268047.gif

圖2 bootloader 的 stage2 可執行映象剛被拷貝到 RAM 空間時的系統內存布局


3.2 Boot Loader 的 stage2

正如前面所說,stage2 的代碼通常用 C 語言來實現,以便于實現更復雜的功能和取得更
好的代碼可讀性和可移植性。但是與普通 C 語言應用程序不同的是,在編譯和鏈接 boot
loader 這樣的程序時,我們不能使用 glibc 庫中的任何支持函數。其原因是顯而易見的
。這就給我們帶來一個問題,那就是從那里跳轉進 main() 函數呢?直接把 main() 函數
的起始地址作為整個 stage2 執行映像的入口點或許是最直接的想法。但是這樣做有兩個
缺點:1)無法通過main() 函數傳遞函數參數;2)無法處理 main() 函數返回的情況。一種
更為巧妙的方法是利用 trampoline(*簧床)的概念。也即,用匯編語言寫一段trampolin
e 小程序,并將這段 trampoline 小程序來作為 stage2 可執行映象的執行入口點。然后
我們可以在 trampoline 匯編小程序中用 CPU 跳轉指令跳入 main() 函數中去執行;而當
main() 函數返回時,CPU 執行路徑顯然再次回到我們的 trampoline 程序。簡而言之,
這種方法的思想就是:用這段 trampoline 小程序來作為 main() 函數的外部包裹(exter
nal wrapper)。

下面給出一個簡單的 trampoline 程序示例(來自blob):

.text

.globl _trampoline
_trampoline:
bl main
/* if main ever returns we just call it again */
b _trampoline



可以看出,當 main() 函數返回后,我們又用一條跳轉指令重新執行 trampoline 程序―
―當然也就重新執行 main() 函數,這也就是 trampoline(*簧床)一詞的意思所在。


3.2.1初始化本階段要使用到的硬件設備

這通常包括:(1)初始化至少一個串口,以便和終端用戶進行 I/O 輸出信息;(2)初始
化計時器等。在初始化這些設備之前,也可以重新把 LED 燈點亮,以表明我們已經進入
main() 函數執行。

設備初始化完成后,可以輸出一些打印信息,程序名字字符串、版本號等。

3.2.2 檢測系統的內存映射(memory map)

所謂內存映射就是指在整個 4GB 物理地址空間中有哪些地址范圍被分配用來尋址系統的
RAM 單元。比如,在 SA-1100 CPU 中,從 0xC000,0000 開始的 512M 地址空間被用作系
統的 RAM 地址空間,而在 Samsung S3C44B0X CPU 中,從 0x0c00,0000 到 0x1000,0000
之間的 64M 地址空間被用作系統的 RAM 地址空間。雖然 CPU 通常預留出一大段足夠的
地址空間給系統 RAM,但是在搭建具體的嵌入式系統時卻不一定會實現 CPU 預留的全部
RAM 地址空間。也就是說,具體的嵌入式系統往往只把 CPU 預留的全部 RAM 地址空間中
的一部分映射到 RAM 單元上,而讓剩下的那部分預留 RAM 地址空間處于未使用狀態。由
于上述這個事實,因此 Boot Loader 的 stage2 必須在它想干點什么 (比如,將存儲在
flash 上的內核映像讀到 RAM 空間中) 之前檢測整個系統的內存映射情況,也即它必須知
道 CPU 預留的全部 RAM 地址空間中的哪些被真正映射到 RAM 地址單元,哪些是處于 "u
nused" 狀態的。

(1) 內存映射的描述

可以用如下數據結構來描述 RAM 地址空間中的一段連續(continuous)的地址范圍:

typedef struct memory_area_struct {
u32 start; /* the base address of the memory region */
u32 size; /* the byte number of the memory region */
int used;
} memory_area_t;



這段 RAM 地址空間中的連續地址范圍可以處于兩種狀態之一:(1)used=1,則說明這段連
續的地址范圍已被實現,也即真正地被映射到 RAM 單元上。(2)used=0,則說明這段連續
的地址范圍并未被系統所實現,而是處于未使用狀態。

基于上述 memory_area_t 數據結構,整個 CPU 預留的 RAM 地址空間可以用一個 memory
_area_t 類型的數組來表示,如下所示:

memory_area_t memory_map[NUM_MEM_AREAS] = {
[0 ... (NUM_MEM_AREAS - 1)] = {
.start = 0,
.size = 0,
.used = 0
},
};



(2) 內存映射的檢測

下面我們給出一個可用來檢測整個 RAM 地址空間內存映射情況的簡單而有效的算法:


/* 數組初始化 */
for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0;

/* first write a 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
* (u32 *)addr = 0;

for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
/*
* 檢測從基地址 MEM_START+i*PAGE_SIZE 開始,大小為
* PAGE_SIZE 的地址空間是否是有效的RAM地址空間。
*/
調用3.1.2節中的算法test_mempage();
if ( current memory page isnot a valid ram page) {
/* no RAM here */
if(memory_map[i].used )
i++;
continue;
}

/*
* 當前頁已經是一個被映射到 RAM 的有效地址范圍
* 但是還要看看當前頁是否只是 4GB 地址空間中某個地址頁的別名?
*/
if(* (u32 *)addr != 0) { /* alias? */
/* 這個內存頁是 4GB 地址空間中某個地址頁的別名 */
if ( memory_map[i].used )
i++;
continue;
}

/*
* 當前頁已經是一個被映射到 RAM 的有效地址范圍
* 而且它也不是 4GB 地址空間中某個地址頁的別名。
*/
if (memory_map[i].used == 0) {
memory_map[i].start = addr;
memory_map[i].size = PAGE_SIZE;
memory_map[i].used = 1;
} else {
memory_map[i].size += PAGE_SIZE;
}
} /* end of for (…) */



在用上述算法檢測完系統的內存映射情況后,Boot Loader 也可以將內存映射的詳細信息
打印到串口。

3.2.3 加載內核映像和根文件系統映像

(1) 規劃內存占用的布局

這里包括兩個方面:(1)內核映像所占用的內存范圍;(2)根文件系統所占用的內存范圍
。在規劃內存占用的布局時,主要考慮基地址和映像的大小兩個方面。

對于內核映像,一般將其拷貝到從(MEM_START+0x8000) 這個基地址開始的大約1MB大小的
內存范圍內(嵌入式 Linux 的內核一般都不操過 1MB)。為什么要把從 MEM_START 到 MEM
_START+0x8000 這段 32KB 大小的內存空出來呢?這是因為 Linux 內核要在這段內存中
放置一些全局數據結構,如:啟動參數和內核頁表等信息。

而對于根文件系統映像,則一般將其拷貝到 MEM_START+0x0010,0000 開始的地方。如果用
Ramdisk 作為根文件系統映像,則其解壓后的大小一般是1MB。

(2)從 Flash 上拷貝

由于像 ARM 這樣的嵌入式 CPU 通常都是在統一的內存地址空間中尋址 Flash 等固態存儲
設備的,因此從 Flash 上讀取數據與從 RAM 單元中讀取數據并沒有什么不同。用一個簡
單的循環就可以完成從 Flash 設備上拷貝映像的工作:

while(count) {
*dest++ = *src++; /* they are all aligned with word boundary */
count -= 4; /* byte number */
};



3.2.4 設置內核的啟動參數

應該說,在將內核映像和根文件系統映像拷貝到 RAM 空間中后,就可以準備啟動 Linux
內核了。但是在調用內核之前,應該作一步準備工作,即:設置 Linux 內核的啟動參數。


Linux 2.4.x 以后的內核都期望以標記列表(tagged list)的形式來傳遞啟動參數。啟動參
數標記列表以標記 ATAG_CORE 開始,以標記 ATAG_NONE 結束。每個標記由標識被傳遞參
數的 tag_header 結構以及隨后的參數值數據結構來組成。數據結構 tag 和 tag_header
定義在 Linux 內核源碼的include/asm/setup.h 頭文件中:

/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000

struct tag_header {
u32 size; /* 注意,這里size是字數為單位的 */
u32 tag;
};
……
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;

/*
* Acorn specific
*/
struct tag_acorn acorn;

/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};



在嵌入式 Linux 系統中,通常需要由 Boot Loader 設置的常見啟動參數有:ATAG_CORE、
ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。比如,設置 ATAG_CORE 的代
碼如下:

params = (struct tag *)BOOT_PARAMS;

params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);

params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;

params = tag_next(params);



其中,BOOT_PARAMS 表示內核啟動參數在內存中的起始基地址,指針 params 是一個 str
uct tag 類型的指針。宏 tag_next() 將以指向當前標記的指針為參數,計算緊臨當前標
記的下一個標記的起始地址。注意,內核的根文件系統所在的設備ID就是在這里設置的。


下面是設置內存映射情況的示例代碼:

for(i = 0; i < NUM_MEM_AREAS; i++) {
if(memory_map[i].used) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);

params->u.mem.start = memory_map[i].start;
params->u.mem.size = memory_map[i].size;

params = tag_next(params);
}
}



可以看出,在 memory_map[]數組中,每一個有效的內存段都對應一個 ATAG_MEM 參數標
記。

Linux 內核在啟動時可以以命令行參數的形式來接收信息,利用這一點我們可以向內核提
供那些內核不能自己檢測的硬件參數信息,或者重載(override)內核自己檢測到的信息。
比如,我們用這樣一個命令行參數字符串"console=ttyS0,115200n8"來通知內核以 ttyS0
作為控制臺,且串口采用 "115200bps、無奇偶校驗、8位數據位"這樣的設置。下面是一
段設置調用內核命令行參數字符串的示例代碼:

char *p;

/* eat leading white space */
for(p = commandline; *p == ' '; p++)
;

/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if(*p == '\0')
return;

params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;

strcpy(params->u.cmdline.cmdline, p);

params = tag_next(params);



請注意在上述代碼中,設置 tag_header 的大小時,必須包括字符串的終止符'\0',此外
還要將字節數向上圓整4個字節,因為 tag_header 結構中的size 成員表示的是字數。


下面是設置 ATAG_INITRD 的示例代碼,它告訴內核在 RAM 中的什么地方可以找到 initr
d 映象(壓縮格式)以及它的大小:

params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);

params->u.initrd.start = RAMDISK_RAM_BASE;
params->u.initrd.size = INITRD_LEN;

params = tag_next(params);



下面是設置 ATAG_RAMDISK 的示例代碼,它告訴內核解壓后的 Ramdisk 有多大(單位是K
B):

params->hdr.tag = ATAG_RAMDISK;
params->hdr.size = tag_size(tag_ramdisk);

params->u.ramdisk.start = 0;
params->u.ramdisk.size = RAMDISK_SIZE; /* 請注意,單位是KB */
params->u.ramdisk.flags = 1; /* automatically load ramdisk */

params = tag_next(params);



最后,設置 ATAG_NONE 標記,結束整個啟動參數列表:

static void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}



3.2.5 調用內核

Boot Loader 調用 Linux 內核的方法是直接跳轉到內核的第一條指令處,也即直接跳轉到
MEM_START+0x8000 地址處。在跳轉時,下列條件要滿足:

1. CPU 寄存器的設置:
·R0=0;
@R1=機器類型 ID;關于 Machine Type Number,可以參見 linux/arch/arm/tools/mach
-types。
@R2=啟動參數標記列表在 RAM 中起始基地址;

2. CPU 模式:
·必須禁止中斷(IRQs和FIQs);
·CPU 必須 SVC 模式;

3. Cache 和 MMU 的設置:
·MMU 必須關閉;
·指令 Cache 可以打開也可以關閉;
·數據 Cache 必須關閉;

如果用 C 語言,可以像下列示例代碼這樣來調用內核:

void (*theKernel)(int zero, int arch, u32 params_addr)
= (void (*)(int, int, u32))KERNEL_RAM_BASE;
……
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);



注意,theKernel()函數調用應該永遠不返回的。如果這個調用返回,則說明出錯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537197.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537197.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537197.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MongoDB數據庫的遷移

最近公司開始要換服務器啦&#xff0c;MongoDB上面的數據又得遷移&#xff0c;還是記錄一下比較好。 1&#xff09;、將MongoDB的壓縮包解壓至相對應的路徑(壓縮文件在本地服務器的地址192.168.0.22的/opt/zip文件下面) 2&#xff09;、配置好mongodb.conf文件&#xff0c;配…

excel vba 如何將日期周幾轉換成文字_這5個超實用的Excel技巧,讓你的辦公效率更高...

導讀&#xff1a;對于辦公職員來說&#xff0c;Excel是幾乎每天都會接觸的辦公軟件。在Excel中&#xff0c;有非常多的小技巧&#xff0c;學習這些小技巧需要不斷的積累和應用&#xff0c;今天指北針就來給大家分享5個超實用的Excel技巧&#xff0c;讓辦公變得更加有效率。文/芒…

VMware創建Linux及局域網內獨立訪問IP和訪問外網IP的配置

好早之前有一篇是配置遠程連接Linux和部署Tomcat的文章&#xff0c;但是并沒有講解如何配置IP的相關知識。最近公司在搞集群配置&#xff0c;我就先拿電腦上的VMware上的Linux做個測試&#xff0c;分享和總結一下經驗吧&#xff0c;也算是為了補齊之前的那個空白&#xff01; …

每位設計師都應該擁有的50個CSS代碼片段

每位設計師都應該擁有的50個CSS代碼片段

C#淺拷貝與深拷貝區別

也許會有人這樣解釋C# 中淺拷貝與深拷貝區別&#xff1a; 淺拷貝是對引用類型拷貝地址&#xff0c;對值類型直接進行拷貝。 不能說它完全錯誤&#xff0c;但至少還不夠嚴謹。比如&#xff1a;string 類型咋說&#xff1f; 其實&#xff0c;我們可以通過實踐來尋找答案。 首先&a…

內網安裝nginx+keepalived環境配置及簡單使用

分享一下這次艱難的配置過程&#xff0c;銜接上一篇的配置內網獨立IP虛擬機。 先吐槽一波&#xff0c;由于公司網絡屬于內網&#xff0c;與外網互不相通&#xff0c;所以在安裝nginx的時候可能會去外網找相對應rpm文件&#xff0c;而且也有許多的版本不兼容問題&#xff0c;好…

cad連續標注數字123怎么弄_實例講解CAD模型與布局中的各種比例

好課推薦&#xff1a;零基礎CAD&#xff1a;點我CAD室內&#xff1a;點我 周站長CAD&#xff1a;點我CAD機械&#xff1a;點我 Bim教程&#xff1a;點我CAD建筑&#xff1a;點我CAD三維&#xff1a;點我全屋定制&#xff1a;點我 ps教程&#xff1a;點我蘋果版CAD:點我 3dmax教…

SpringMvc異步請求的使用及部分原理

最近隔壁項目組的項目又出問題了&#xff0c;一直被用戶投訴太卡了&#xff0c;頁面白屏的那種&#xff0c;打開源代碼一看&#xff0c;全是非異步請求&#xff0c;類似于以下寫法&#xff1a; ResponseBodyRequestMapping(value "/getTest")public String getTest(…

Microsoft BizTalk ESB Toolkit 2.0

[>>> 更多<BizTalk開發系列>文章 ] 微軟于6月8號發布了BizTalk Server 2009企業集成平臺的最后一個功能組件:ESB Toolkit 2.0 (原名:ESB Guidance 2.0)&#xff0c;ESB ToolKit 2.0一個是工具和代碼集擴展了BizTalk Server 2009對于松耦合和動態消息架構的支持…

python解釋器環境中用于表示上一次運算結果的特殊變量_判斷正誤 PUSH CL_學小易找答案...

【單選題】將數學關系式2 【填空題】請用4位十六進制寫出每條指令結束后AX的值。 MOV AX, 0 DEC AX ADD AX, 7FFFH ADC AX, 1 NEG AX OR AX, 3FDFH AND AX, 0EBEDH XCHG AH, AL SAL AX, 1 RCL AX, 1 【判斷題】判斷正誤 MOV DX, 09H 【判斷題】判斷正誤 MOV [1200H], [SI] 【單…

Java線程的使用及共享協作

創建線程的三種方式 1、繼承Thread&#xff1b; static class MyThread extends Thread{Overridepublic void run() {//do something...} } public static void main(String[] args) throws InterruptedException {MyThread thread new MyThread ();thread.start(); } 2、實…

WCF學習筆記(三):開啟net.tcp端口

正在做一個使用tcp協議的WCF示例&#xff0c;遇到很多問題。首當其沖的問題就是——如何為WCF打開tcp端口。。。 具體步驟如下&#xff1a; 1、在IIS中為WCF安裝支持TCP協議的組件&#xff1a; 2、在防火墻的入棧規則中開啟808端口&#xff1b; 3、在servies.msc中打開兩個服務…

孿生神經網絡_軒轅實驗室:數字孿生:基于機器學習的汽車數字孿生模型

本文來源&#xff1a;A. Rassolkin, T. Vaimann, A. Kallaste, and V. Kuts, “Digital twin for propulsion drive of autonomous electric vehicle,” in 2019 IEEE 60th International Scientific Conference on Power and Electrical Engineering of Riga Technical Univer…

Java線程Fork/Join思想及實現

最近在看線程這一塊的東西&#xff0c;所以之前的那篇文章就是用來記錄的&#xff0c;但看起來好簡單的樣子&#xff0c;哈哈哈&#xff01; 這兩天看的是Fork/Join 分而治之的思想&#xff0c;Doug Lea大師的JUC還是挺強的&#xff0c;學并發編程應該沒有人不知道這個大佬吧&…

Sgen.exe: Speed up XmlSerializer's Startup Performance [.NET 2.0, XML Serialization]

Sgen.exe: Speed up XmlSerializers Startup Performance [.NET 2.0, XML Serialization] Written by Allen Lee 1. Why Sgen.exe? 在《Serialize Your Deck with Positron [XML Serialization, XSD, C#]》一文中&#xff0c;我們領略到 XML Serialization 是如何簡化我們的 X…

Java線程并發常用工具類使用

這次整理了一些比較常用的線程工具類啦。 CountDownLatch&#xff1a;在一組線程執行完后&#xff0c;才能開始執行調用等待的線程。上片文章提到過junit的測試盡量不要測試線程&#xff0c;如果硬是要可以使用CountDownLatch進行測試 CyclicBarrier&#xff1a;在一組線程中…

三維圖形幾何變換算法實驗_計算機視覺方向簡介 | 深度學習視覺三維重建

點擊上方“計算機視覺life”&#xff0c;選擇“星標”快速獲得最新干貨作者&#xff1a; Moonsmilehttps://zhuanlan.zhihu.com/p/79628068本文已由作者授權&#xff0c;未經允許&#xff0c;不得二次轉載三維重建意義三維重建作為環境感知的關鍵技術之一&#xff0c;可用于自動…

讀《高效程序員的45個習慣——敏捷開發修煉之道》

本書主要用平易的語言講述了45個有助于提高程序員自身敏捷的習慣&#xff0c;個人感覺這種老外寫的書翻譯成中文就少了很多意思。 主要的45個習慣是&#xff1a; 做事欲速則不達對事不對人排除萬難跟蹤變化對團隊投資懂得丟棄打破沙鍋問到底把握開發節奏讓客戶做決定讓設計指導…

Java線程CAS原子操作

這次分享一些關于原子操作(CAS)的東西. 定義 CAS(Compare And Swap)是CPU的一個指令級別的操作&#xff0c;叫原子操作&#xff0c;原子操作是不可分割的&#xff0c;跟事務差不多&#xff0c;要么全部執行完成&#xff0c;要么不執行&#xff1b; 像這種操作有點類似阻塞鎖…

python 導航欄_解析導航欄的url--selnium,beautifulsoup實戰

前段時間做ui自動化測試的時候&#xff0c;導航欄菜單始終有點問題&#xff0c;最后只好直接獲取到url&#xff0c;然后直接使用driver.get(url)進入頁面&#xff1b;包括做壓測的時候&#xff0c;比如我要找出所有報表菜單的url&#xff0c;這樣不可能手動去一個一個找出來&am…