Boot Loader启动过程分析

2023-05-16

一、    Boot Loader的概念和功能 

1、嵌入式Linux软件结构与分布在一般情况下嵌入式Linux系统中的软件主要分为以下及部分:

(1)引导加载程序:其中包括内部ROM中的固化启动代码和Boot Loader两部分。而这个内部固化ROM是厂家在芯片生产时候固化的,作用基本上是引导Boot Loader。有的芯片比较复杂,比如Omap3,他在flash中没有代码的时候有许多启动方式:USB、UART或以太网等等。而S3C24x0则很简单,只有Norboot和Nandboot。

(2)Linux kernel 和drivers。

(3)文件系统。包括根文件系统和建立于Flash内存设备之上的文件系统(EXT4、UBI、CRAMFS等等)。它是提供管理系统的各种配置文件以及系统执行用户应用程序的良好运行环境的载体。

(4)应用程序。用户自定义的应用程序,存放于文件系统之中。
在Flash 存储器中,他们的 一般分布如下:

Boot Loader启动过程分析 - singleboy - singleboy的博客 

但是以上只是大部分情况下的分布,也有一些可能根文件系统是initramfs,被一起压缩到了内核映像里,或者没有Bootloader参数区,等等。

2、在嵌入式Linux中为什么要有BootLoader
在linux内核的启动运行除了内核映像必须在主存的适当位置,CPU还必须具备一定的条件:

【1】CPU寄存器设置:

R0=0;

R1=Machine ID(即Machine Type Number,定义在linux/arch/arm/tools/mach-types);

R2=内核启动参数在RAM中起始基地址;

【2】CPU模式:

必须禁止中断(IRQs和FIQs);

CPU 必须工作在是超级保护模式(SVC) 模式;

【3】Cache和MMU的设置:

MMU 必须关闭;

指令Cache可以打开也可以关闭;

数据Cache必须关闭;

但是在CPU刚上电启动的时候,一般连内存控制器都没有配置过,根本无法在内存中运行程序,更不可能处在Linux内核的启动环境中。为了初始化CPU及其他外设,使得Linux内核可以在系统主存中跑起来,并让系统符合Linux内核启动的必备条件,必须要有一个先于内核运行的程序,他就是所谓的引导加载程序(Boot Loader)。

而Boot Loader并不是Linux才需要,是几乎所有的运行操作系统的设备都具备的。我们的PC的BOIS就是Boot Loader的一部分(只是前期引导,后面一般还有外存中的各种Boot Loader),对于Linux PC来说,Boot Loader = BIOS + GRUB/LILO。

正如前面所述,Boot Loader是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境,最后从别处(Flash、以太网、UART)载入内核映像并跳到入口地址。

由于BootLoader直接操作硬件,所以她严重依赖于硬件,而且依据所引导的操作系统的不同。

二、Boot Loader的工作模式

大多数 Boot Loader 都包含两种不同的操作模式:“启动加载”模式和“下载”模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

启动加载(Boot loading)模式:

这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

下载(Downloading)模式:

在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

 象Blob 或U-Boot 等这样功能强大的Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。

三、Boot Loader 与主机之间进行文件传输协议

最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。

此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和TFTP 协议来下载文件时,主机方必须有一个软件用来的提供 TFTP 服务。

四、Bootloader的工作流程

由于Boot Loader的实现依赖与CPU的体系结构,因此大多数的Boot Loader都分为stage1和stage2两个阶段:

1,Bootloader 的第一阶段(Stage1),工作流程

  • 硬件设备初始化
  • 代码重定位,为加载 Boot Loader 的 stage2 准备 RAM 空间
  • 加载t第二阶段代码到RAM空间
  • 设置堆栈跳转到第二阶段代码入口

1.1,硬件设备初始化通常包括如下步骤:(按先后顺序执行):

【1】复位(reset)

【2】设置CPU为超级保护模式(SVC) 即特权模式(Supervisor)

【3】关闭看门狗,不必附加喂狗代码。

【4】屏蔽所有中断,为中断提供服务通常是OS设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写CPU的中断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。

【5】设置系统时钟频率。

【6】初始化内存控制器,包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。

【7】初始化串口等,典型地,初始化UART并向串口打印相关字符信息。

【8】初始化LED。典型地,通过GPIO 来驱动LED,其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的Logo 字符信息来完成这一点。

【9】关闭 CPU 内部指令/数据 cache。

1.2,代码重定位主要检查自己是否在内存中。如果是跳到堆栈段(stack_setup代码段)设置堆栈,不是就加载自己到RAM空间。

为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。

由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。

为了后面的叙述方便,这里把所安排的RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:

stage2_end = stage2_start + stage2_size

另外,还必须确保所安排的地址范围的的确确是可读写的RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:

 【1】先保存 memory page 一开始两个字的内容。

 【2】向这两个字中写入任意的数字。比如:向第一个字写入 0x55,第 2 个字写入 0xaa。

 【3】 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

 【4】再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0x55。

 【5】然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0x55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

 【6】恢复这两个字的原始内容。测试完毕。

为了得到一段干净的RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。

1.3,加载Bootloader第二阶段代码到RAM空间,拷贝时要确定两点:(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM 空间的起始地址。Boot Loader启动过程分析 - singleboy - singleboy的博客

1.4,设置好堆栈,强调下堆和栈的区别:栈区(stack) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;堆区(heap) 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。程序的局部变量存在于(栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中全局变量实际上是存在一个(一般来说正常的编译器)可读可写的内存空间,这个空间是在你写程序编译好的空间地址(由编译器决定),是固定的。

堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),因为栈是向下生长的,所以通常把栈指针设在1MB空间的最顶端。此外,在设置堆栈之前,也可以把指示用的LED灯关闭,以提示用户跳转到Stage2。经过以上步骤设置以后,系统的物理内存布局应该如图所示。

1.5,跳转到第二阶段(Stage2)代码入口,在上述一切就绪后,就可以跳转到Boot Loader的Stage2执行了,在ARM系统中是通过修改PC寄存器为合适的地址来实现的。如U-Boot中是这样实现的:

ldr pc, _start_armboot
start_armboot是第二阶段(Stage2)的C程序的入口点。start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

2,Bootloader的第二阶段(Stage2)工作流程

  • 初始化本阶段要使用到的硬件设备
  • 检测系统内存映射
  • 加载内核映像和根文件系统映像
  • 设置内核的启动参数
  • 启动内核

2.1,初始化本阶段要使用到的硬件设备,这通常包括:

(1)设置时钟、初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main_loop() 函数执行。

board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/samsung/smdk2440/smdk2440.c中实现。值得注意的是board_init函数还保存了机器类型ID,这将在调用内核的时候传递给内核。代码如下:

gd->bd->bi_arch_number = MACH_TYPE_S3C2440;   //值为362

串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数。

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 地址单元,哪些是处于 "unused" 状态的。

对于smdk2440的开发板,其内存分布是明确的,一般内存起始地址为0x3000 0000,大小为64M = 0x0400 0000。代码如下:

int dram_init(void)

{

     gd->bd->bi_dram[0] . start = PHYS_SDRAM_1;          //即0x3000 0000

     gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;  //即0x0400 0000

//这两个值都定义在include/configs/smdk2440.h中

}

2.3,将内核映像和根文件系统映像从Flash上读到RAM空间中。

【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 */

};

2.4,为内核设置启动参数。

应该说,在将内核映像和根文件系统映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。

U-Boot 是通过标记列表向内核传递参数。

setup_memory_tags

setup_commandline_tag

这两个标记列表定义在arch/arm/lib/bootm.c中,需要在定义命令的文件include/configs/smdk2440.h中定义两个命令

#define CONFIG_SETUP_MEMORY_TAGS  1

#define CONFIG_CMDLINE_TAG                  1

Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的/arch/arm/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 是一个 struct 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 中的什么地方可以找到 initrd 映象(压缩格式)以及它的大小: 

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 有多大(单位是KB):

 

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;

}

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()函数调用应该永远不返回的。如果这个调用返回,则说明出错。

对于ARM构架的CPU来说,都是通过../lib_arm/bootm.c中的do_bootm_linux函数来启动内核的。这个函数中,设置标记列表,最后通过

theKernel = (void (*)(int, int, uint))images->ep;

调用内核。其中,theKernel 指向内核存放的地址(对于ARM构架的CPU,通常这个地址是0x3000 8000)。传递的3个参数如下:

void (*theKernel)(int zero, int arch, uint params);

R0: 0

R1: 机器类型ID -- gd->bd->bi_arch_number = MACH_TYPE_S3C2440;   //值为362

R2: 启动参数标记列表在RAM中的起始地址 0x3000 0100

五、 关于串口终端

在 boot loader 程序的设计与实现中,没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外,向串口终端打印信息也是一个非常重要而又有效的调试手段。但是,我们经常会碰到串口终端显示乱码或根本没有显示的问题。造成这个问题主要有两种原因:(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确,这包括:波特率、奇偶校验、数据位和停止位等方面的设置。

此外,有时也会碰到这样的问题,那就是:在 boot loader 的运行过程中我们可以正确地向串口终端输出信息,但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑:

(1) 首先请确认你的内核在编译时配置了对串口终端的支持,并配置了正确的串口驱动程序。

(2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外,对于诸如 s3c44b0x 这样的 CPU,CPU 时钟频率的设置也会影响串口,因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致,也会使串口终端无法正确显示信息。

(3) 最后,还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致,尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000,但你的 boot loader 却将它加载到 0xc0010000 处去执行,那么内核映像当然不能正确地执行了。

六、 结束语

Boot Loader 的设计与实现是一个非常复杂的过程。如果能从串口收到那激动人心的

"uncompressing linux

.................. done,

booting the kernel……"

内核启动信息,说明boot loader 已经成功地转起来了!。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Boot Loader启动过程分析 的相关文章

  • ROS入门保姆级教程:7-ROS话题通信实现2:自定义消息类型(msg)

    ROS入门往期 xff1a ROS入门保姆级教程 xff1a 1 hello world初体验 ROS入门保姆级教程 xff1a 2 VScode中使用ROS ROS入门保姆级教程 xff1a 3 ROS文件系统 ROS入门保姆级教程 xf
  • Proteus仿真时报错:[SPICE] Too many iterations without convergence

    一 问题 xff1a 在利用Proteus仿真时 xff0c 经常会遇到 SPICE Too many iterations without convergence xff08 太多没有收敛的迭代 xff09 这个提示 xff0c 致使仿真
  • Android.bp编译提示ninja: error: unknown target ‘MODULES-IN-xxx‘终极指南

    Android bp编译提示ninja error unknown target 39 MODULES IN xxx 终极指南 Android bp系列博客 Android bp你真的了解吗 Android bp入门指南之Android m
  • 转折点----------我的IT梦

    干IT这一行快6年了 从来没有留下任何属于自己的一些所谓的阅历 时常去逛逛同行前辈们的空间 总能看到他们留下的心得体会 实则令人羡慕 xff0c 或者自己的卑微 xff0c 或者实在没什么体会 xff0c 写出来会丢人现眼 只是默默的去感受
  • 有了这份程序员面试指南,你离大厂Offer还远吗?| 附推荐书籍

    点击上方蓝色字体 xff0c 关注我 一个在阿里云打工的清华学渣 图by 石头 64 长白山 关于作者 xff1a 程序猿石头 ID tangleithu xff0c 现任阿里巴巴技术专家 xff0c 清华学渣 xff0c 前大疆后端 Le
  • 记录一次harbor的镜像扫描和更新

    前提 已经部署好harbor 192 168 14 16 项目名称 harbor 1 在harbor服务器拉取centos源镜像 root localhost docker pull centos Using default tag lat
  • 关于pixhawk硬件IMU和compass那点事儿

    文章目录 前言一 IMU和compass是什么 xff1f 二 导航坐标系与机体坐标系三 安装IMU xff0c compasss四 hwdef中设置IMU xff0c compass朝向总结 前言 继上一篇讲解了pixhawk的硬件组成
  • ubuntu下ardupilot编译环境搭建与仿真

    文章目录 前言一 ardupilot 源码下载二 编译环境建立仿真经验教训坑1坑2 参考 前言 虽然怒飞老师给出了详细的windows下的开发环境的搭建教程 但是对于开发者而言 xff0c 最好的系统环境还是在Linux系统下 xff0c
  • Ubuntu下MissionPlanner的安装

    文章目录 前言一 安装mono二 下载并使用MissionPlanner三 创建快捷脚本 前言 众所周知 xff0c QGC地面站外观更加好看 xff0c 开发上也是采用跨平台的Qt 在ubuntu上安装十分方便 但是我还是更喜欢用Miss
  • Nmap详解

    Nmap简介 Nmap也就是Network Mapper xff0c 网络发现 xff08 Network Discovery xff09 和安全审计 xff0c 是一款网络连接端扫描软件 xff0c 用来扫描网上电脑开放的网络连接端 确定
  • PX4 自定义bootloader生成

    本文主要是记录一下自己在这方面的学习 xff0c 方便以后回顾 xff0c 也希望对其他朋友有用 本着不重复造轮子的精神 xff0c 这里引文不在复制粘贴 xff0c 直接给出链接 生成bootloader的两种方式 以STM32H7作为主
  • PX4开发中遇到的一些问题和解决方法

    文章目录 前言正文1 仿真出现 FCU Preflight Fail Accel 0 uncalibrated等错误2 添加mavlink数据发送or提高数据发送频率3 PX4在不同硬件下的RC输入4 PX4 参数自定义5 电机输出顺序6
  • PX4自定义混控器

    文章目录 前言混控器简介混控器的启动自定义混控器参考 xff1a 前言 上一篇我对PX4的控制和输出的全流程都进行了较为详尽的分析 xff0c 本来想着之后的研究主要在四旋翼控制算法上 xff0c 不会定义啥新机型 xff0c 混控器的部分
  • 运行VINS,相机模型与参数的准备

    相机模型与参数 对于VINS来说 xff0c 相机的内参的准确是万分重要的 如果参数不对 xff0c 那么100 跑飞 xff0c 没商量 要想VINS可以很好的工作 xff0c 给出良好的相机内参是必须的 对于realsense系列的相机
  • MTF模块 PX4 光流模块详细配置

    对于研究无人机的新手 xff0c 直接飞手动模式 xff0c 可能过于困难 在室内测试时 xff0c 又没有GPS可用 为了安全和方便起见 xff0c 可以考虑选用光流模块 xff0c 降低入手难度 这里我们选择微空科技出品的MTF 01模
  • GNSS系列(3)------GNSS定位漂移讨论

    由于工作需要 xff0c 最近开启了GNSS系列文章的撰写工作 xff0c 发布于公司官网 xff0c 现将其同步至CSDN 原文链接 xff1a http onemo10086 com school article 196 小伙伴们 xf
  • ML302 OpenCPU系列(5)---Log工具的使用

    ML302 OpenCPU系列 xff08 5 xff09 Log工具的使用 一 使用串口助手抓取Log二 使用Coolwatcher抓取AP Log三 查看死机现场 工欲善其事 xff0c 必先利其器 Log是嵌入式开发中最重要的调试手段
  • 2017--就业分享之IT校招现状和面试经历

    在介绍自己整个春季和夏季实习求职经历之前 xff0c 先给大家公布一则新闻 2017届互联网校招薪酬报告 xff1a 先说下对 16 年 17 届校招的总体看法 xff1a 本该是个不大不小的年 xff0c 结果被华为一己之力搞成了个大年
  • Shell判断字符串是否相等,=两边需要有空格

    custom span class token operator 61 span span class token string 34 34 span span class token keyword if span span class
  • 学嵌入式系统设计的人应不应该看模拟电路基础

    我学嵌入式系统设计是首先从数字电路看起的 由于数字电路这门课程在大一的时候就学过 xff0c 所以觉得略看就能看懂 xff0c 也算是把这门课复习了一遍 但是当我看到存储器和可编程控件这一章时 xff0c 里面讲到存储单元主要是由半导体组成

随机推荐

  • 使用Python爬取淘宝两千款套套

    各位同学们 xff0c 好久没写原创技术文章了 xff0c 最近有些忙 xff0c 所以进度很慢 xff0c 给大家道个歉 gt 警告 xff1a 本教程仅用作学习交流 xff0c 请勿用作商业盈利 xff0c 违者后果自负 xff01 如
  • ​揭秘国内首个进入Apache的高校顶级项目——Apache IoTDB

    本文约4200字 xff0c 建议阅读10 43 分钟 本文与你分享有关开源数据库项目成长 开源社区治理 加速赋能企业等方面的观点与见解 近年来 xff0c 随着人工智能 物联网的兴起 xff0c 大数据成为重要的生产资料 xff0c 而时
  • 收藏 | 一张地图带你玩转机器学习(附资源)

    本文来自AI学习与实践平台SigAI 本文共16965字 xff0c 建议阅读20 43 分钟 本文对常用的机器学习和深度学习算法进行了总结 xff0c 整理出它们之间的关系 xff0c 以及每种算法的核心点 xff0c 各种算法之间的比较
  • python代码获取远程电脑IP

    实时的使用邮箱发送IP地址到指定邮箱 使用python代码封装好的软件 xff1a 链接 xff1a https pan baidu com s 1Flz7HHtZM0w3HGDeF 4BhQ pwd 61 yxy2 提取码 xff1a y
  • win11旗舰版安装WSL子系统和环境-3Ubuntu换源

    Wsl2开启 Ubuntu换源 https blog csdn net WPwalter article details 101508601 这个blog是进行系统安装WSL2 的 Linux 第一步 xff1a 启用虚拟机平台和 Linu
  • win11旗舰版安装WSL子系统和环境-12配置SSH(Win远程连接)

    配置SSH Win远程连接 https blog csdn net weixin 43897590 article details 109446339 utm medium 61 distribute pc relevant downloa
  • win11旗舰版安装WSL子系统和环境-17Xshell连接wsl子系统的ssh

    https blog csdn net w4187402 article details 85992013 那么问题找到了 出现这样的问题只能是ip冲突 xff0c 其他同事的服务器占用了我当前服务器的ip地址 xff0c 我现在连接的服务
  • 超好用的word插件-工作和科研

    Word精灵7 0版 可以很好地处理图片 xff0c 转换 xff0c 以及各种批量处理操作 GrammarlyAddInSetup 可以很好地校对英文错误 xff0c 单词以及语法错误 xff0c 但被动语态不太行 链接 xff1a ht
  • Adobe Premiere Pro 2023 SP(未完成)

    需要添加
  • 一个程序员所应该具备的精神

    所谓障碍都是主观上的 如果你想研发什么新的技术 xff0c 只需要在冰箱里放满食物和饮料 xff0c 再有一台便宜的计算机 xff0c 和以之献身的决心 xff0c 你即可拥有任何你想拥有的编程深度 xff01 John Carmack
  • ROS中的多线程

    ROS中的多线程 ROS多线程消息回调处理函数多线程MultiThreadedSpinnerAsyncSpinner callback传参timertimer加参数 C 43 43 中的多线程Python中的多线程 ROS使用master管
  • Linux系统下磁盘分区

    计算机的磁盘分区信息是计算机引导操作系统必须的信息 xff0c 根据引导方式的不同 xff0c 一般分别保存在MBR或者GPT中 其中 xff0c BIOS引导会读取MBR xff08 Main Boot Record xff09 中的磁盘
  • ROS中的tf发布读取转换

    ROS tf 基础使用查看tf信息1 创建link关系图2 在rqt中查看link关系图3 终端中输出tf变换关系4 rviz中查看 程序中使用TransformerTransformBroadcasterTransformListener
  • linux下shell脚本启动其他可执行程序

    linux下shell脚本启动其他可执行程序 零 前言一 C 43 43 代码二 shell脚本三 shell运行效果 零 前言 linux下的项目中经常需要使用shell脚本去启动其他程序的操作 xff0c 下面是自己编写的测试程序 xf
  • 嵌入式面试常见问题

    1 什么是嵌入式 以应用为中心 xff0c 以计算机技术为基础 xff0c 软硬件可裁剪 xff0c 适用于应用系统对功能 可靠性 成本 体积 功耗有严格要求的专用计算机系统 2 字符设备和块设备的区别 xff1f Linux里设备类型分
  • 步进电机和伺服电机的区别你知道吗?

    在许多领域都需要各种电机 xff0c 包括知名的步进电机和伺服电机 但是 xff0c 对于许多用户而言 xff0c 他们不了解这两种电机的主要区别 xff0c 因此他们始终不知道如何选择 那么 xff0c 步进电机和伺服电机之间的主要区别是
  • 独轮车成功站立

    真是废了不少力 卡了这么久首要原因就是过于青睐串级PID 串级PID可以自主寻找机械中位的特性实在是太优雅了 但动量轮这种对即使性要求极高的系统似乎不能用串级PID实现 昨天沉下心把串级PID推掉换成并联 xff0c 波形一下就朝着正常的方
  • 字节序:大端字节序(Big Endian) & 小端字节序(Little Endian)

    一 什么是字节序 xff1f 多字节数据存储在存储器中的顺序就叫做字节序 字节序又分为俩种 xff0c 一种叫做小端字节序 xff1b 另外一种叫做大端字节序 二 大端字节序 xff08 Big Endian xff09 amp 小端字节序
  • Google doc

    https docs google com spreadsheets d 1lOtc072A0QaJAXormoUeiaqZu5 20BR1ikh0YZe65PI edit gid 61 0
  • Boot Loader启动过程分析

    一 Boot Loader的概念和功能 1 嵌入式Linux软件结构与分布在一般情况下嵌入式Linux系统中的软件主要分为以下及部分 xff1a xff08 1 xff09 引导加载程序 xff1a 其中包括内部ROM中的固化启动代码和Bo