<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Raspberry_Pi_Bare_Bones</id>
	<title>Raspberry Pi Bare Bones - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Raspberry_Pi_Bare_Bones"/>
	<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Raspberry_Pi_Bare_Bones&amp;action=history"/>
	<updated>2026-04-04T14:22:22Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://wiki.foofun.cn//index.php?title=Raspberry_Pi_Bare_Bones&amp;diff=257&amp;oldid=prev</id>
		<title>Zhang3：创建页面，内容为“{{BeginnersWarning}} {{Rating|3}} {{Template:Kernel designs}}  这是关于 Raspberry Pi 上的操作系统开发的教程。 本文章将作为如何创建最小系统的示例，而不是如何正确构建项目的示例。  有一个类似的教程 Raspberry Pi Bare Bones Rust 用Rust代替C。  == 准备 ==  你即将开始开发新的操作系统。 也许有一天，你的新操作系统可以在其自身下开发。 这是一个被称为引导或…”</title>
		<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Raspberry_Pi_Bare_Bones&amp;diff=257&amp;oldid=prev"/>
		<updated>2022-01-20T02:35:32Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“{{BeginnersWarning}} {{Rating|3}} {{Template:Kernel designs}}  这是关于 &lt;a href=&quot;/index.php?title=Raspberry_Pi&quot; title=&quot;Raspberry Pi&quot;&gt;Raspberry Pi&lt;/a&gt; 上的操作系统开发的教程。 本文章将作为如何创建最小系统的示例，而不是如何正确构建项目的示例。  有一个类似的教程 &lt;a href=&quot;/index.php?title=Raspberry_Pi_Bare_Bones_Rust&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Raspberry Pi Bare Bones Rust（页面不存在）&quot;&gt;Raspberry Pi Bare Bones Rust&lt;/a&gt; 用Rust代替C。  == 准备 ==  你即将开始开发新的操作系统。 也许有一天，你的新操作系统可以在其自身下开发。 这是一个被称为引导或…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{BeginnersWarning}}&lt;br /&gt;
{{Rating|3}}&lt;br /&gt;
{{Template:Kernel designs}}&lt;br /&gt;
&lt;br /&gt;
这是关于 [[Raspberry Pi]] 上的操作系统开发的教程。 本文章将作为如何创建最小系统的示例，而不是如何正确构建项目的示例。&lt;br /&gt;
&lt;br /&gt;
有一个类似的教程 [[Raspberry Pi Bare Bones Rust]] 用Rust代替C。&lt;br /&gt;
&lt;br /&gt;
== 准备 ==&lt;br /&gt;
&lt;br /&gt;
你即将开始开发新的操作系统。 也许有一天，你的新操作系统可以在其自身下开发。 这是一个被称为引导或自我托管的过程。 然而，这是未来的道路。 今天，我们只需要设置一个系统，该系统可以从现有的操作系统中编译你的操作系统。 这是一个被称为交叉编译的过程，这是操作系统开发的第一步。&lt;br /&gt;
&lt;br /&gt;
本文假设你正在使用类似Unix的操作系统，例如Linux，它很好地支持操作系统开发。 Windows用户应该能够在MinGW或Cygwin环境中完成它。 &lt;br /&gt;
&lt;br /&gt;
== 构建交叉编译器 ==&lt;br /&gt;
: ''主条目: [[GCC Cross-Compiler]]，[[Why do I need a Cross Compiler?]]&lt;br /&gt;
&lt;br /&gt;
你应该做的第一件事是为 ''' arm-none-eabi''' 设置一个 [[GCC Cross-Compiler]]。 你还没有修改你的编译器来了解你的操作系统的存在，所以我们使用一个名为arm-none-eabi的通用目标，它为你提供了一个针对 [[system V ABI]] 的工具链。&lt;br /&gt;
你将 “无法” 在没有交叉编译器的情况下正确编译你的操作系统。&lt;br /&gt;
&lt;br /&gt;
如果你想要一个64位内核，你应该设置目标为''' aarch64-elf '''。 这提供了相同的系统V ABI接口，但仅适用于64位。 使用elf不是强制性的，但是可以简化一些工作。&lt;br /&gt;
&lt;br /&gt;
== 概述 ==&lt;br /&gt;
&lt;br /&gt;
到现在为止，你应该已经为适当的ABI设置了 [[GCC Cross-Compiler|cross-compiler]] (如上所述)。本教程提供了创建操作系统的最小解决方案。 它不作为项目结构的推荐框架，而是作为一个最小内核的例子。 在这种简单的情况下，我们只需要三个输入文件:&lt;br /&gt;
&lt;br /&gt;
* boot.S - 设置处理器环境的内核入口点&lt;br /&gt;
* kernel.c - 你实际的内核例程&lt;br /&gt;
* linker.ld - 用于链接上述文件&lt;br /&gt;
&lt;br /&gt;
== 启动操作系统 ==&lt;br /&gt;
&lt;br /&gt;
现在，我们将创建一个名为boot.S的文件，并讨论其内容。 在这个例子中，我们使用的是GNU汇编程序，它是你之前构建的交叉编译器工具链的一部分。 这个汇编器与GNU工具链的其余部分集成得非常好。 &lt;br /&gt;
&lt;br /&gt;
每个Pi型号都需要不同的设置。 通常，你必须区分AArch32和AArch64模式，因为它们的启动方式不同。 后者只能从Pi 3及以上访问。 在一种模式下，你可以在运行时 [[Detecting_Raspberry_Pi_Board|detect the board]]，并相应地设置mmio基址。&lt;br /&gt;
&lt;br /&gt;
=== Pi型号 A，B，A+，B和Zero ===&lt;br /&gt;
&lt;br /&gt;
环境的设置和执行从 [https://github.com/raspberrypi/tools/blob/master/armstubs/armstub.S#L35 armstub.s] 转移到 _start。&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
// AArch32 mode&lt;br /&gt;
&lt;br /&gt;
// 将其保留在二进制文件的第一部分中。&lt;br /&gt;
.section &amp;quot;.text.boot&amp;quot;&lt;br /&gt;
&lt;br /&gt;
// Make _start global.&lt;br /&gt;
.globl _start&lt;br /&gt;
&lt;br /&gt;
        .org 0x8000&lt;br /&gt;
// 内核的入口点。&lt;br /&gt;
// r15 -&amp;gt; 应该从0x8000开始执行。&lt;br /&gt;
// r0 -&amp;gt; 0x00000000&lt;br /&gt;
// r1 -&amp;gt; 0x00000C42 - machine id&lt;br /&gt;
// r2 -&amp;gt; 0x00000100 - start of ATAGS&lt;br /&gt;
// 保留这些寄存器作为kernel_main的参数&lt;br /&gt;
_start:&lt;br /&gt;
	// 设置堆栈。&lt;br /&gt;
	mov sp, #0x8000&lt;br /&gt;
&lt;br /&gt;
	// 清除bss。&lt;br /&gt;
	ldr r4, =__bss_start&lt;br /&gt;
	ldr r9, =__bss_end&lt;br /&gt;
	mov r5, #0&lt;br /&gt;
	mov r6, #0&lt;br /&gt;
	mov r7, #0&lt;br /&gt;
	mov r8, #0&lt;br /&gt;
	b       2f&lt;br /&gt;
&lt;br /&gt;
1:&lt;br /&gt;
	// 在r4存储多个&lt;br /&gt;
	stmia r4!, {r5-r8}&lt;br /&gt;
&lt;br /&gt;
	// 如果我们仍然小于bss_end，则循环。&lt;br /&gt;
2:&lt;br /&gt;
	cmp r4, r9&lt;br /&gt;
	blo 1b&lt;br /&gt;
&lt;br /&gt;
	// 调用kernel_main&lt;br /&gt;
	ldr r3, =kernel_main&lt;br /&gt;
	blx r3&lt;br /&gt;
&lt;br /&gt;
	// 暂停&lt;br /&gt;
halt:&lt;br /&gt;
	wfe&lt;br /&gt;
	b halt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
“.text.boot” 部分将在链接器脚本中使用，以将boot.S作为我们内核映像中的第一件事。 代码在调用kernel_main函数之前初始化一个最小C环境，这意味着有一个堆栈并将BSS段归零。 请注意，该代码避免使用r0-r2，因此对于kernel_main调用保持有效。&lt;br /&gt;
&lt;br /&gt;
然后，你可以使用以下方法组装boot.S:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -c boot.S -o boot.o &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Pi 2 ===&lt;br /&gt;
&lt;br /&gt;
有了新版本的Pi，还有更多的事情要做。 Raspberry Pis 2和3 (第一个支持64位的型号) 有4个核心。 在引导时，所有内核都在运行并执行相同的引导代码。 因此，你必须区分核心，并且只允许其中一个运行，将其他核心置于无限循环中。&lt;br /&gt;
&lt;br /&gt;
环境的设置和执行从 [https://github.com/raspberrypi/tools/blob/master/armstubs/ armstub7.S # L167 armstub7.s] 转移到 _start。&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
// AArch32 mode&lt;br /&gt;
&lt;br /&gt;
// 将其保留在二进制文件的第一部分中。&lt;br /&gt;
.section &amp;quot;.text.boot&amp;quot;&lt;br /&gt;
&lt;br /&gt;
// Make _start global.&lt;br /&gt;
.globl _start&lt;br /&gt;
&lt;br /&gt;
        .org 0x8000&lt;br /&gt;
// 内核的入口点。&lt;br /&gt;
// r15 -&amp;gt; 应该从0x8000开始执行。&lt;br /&gt;
// r0 -&amp;gt; 0x00000000&lt;br /&gt;
// r1 -&amp;gt; 0x00000C42 - 机器标识&lt;br /&gt;
// r2 -&amp;gt; 0x00000100-ATAGS的启动&lt;br /&gt;
// 保留这些寄存器作为kernel_main的参数&lt;br /&gt;
_start:&lt;br /&gt;
	// 关闭额外的内核&lt;br /&gt;
	mrc p15, 0, r5, c0, c0, 5&lt;br /&gt;
	and r5, r5, #3&lt;br /&gt;
	cmp r5, #0&lt;br /&gt;
	bne halt&lt;br /&gt;
&lt;br /&gt;
	// 设置堆栈。&lt;br /&gt;
	ldr r5, =_start&lt;br /&gt;
	mov sp, r5&lt;br /&gt;
&lt;br /&gt;
	// Clear out bss.&lt;br /&gt;
	ldr r4, =__bss_start&lt;br /&gt;
	ldr r9, =__bss_end&lt;br /&gt;
	mov r5, #0&lt;br /&gt;
	mov r6, #0&lt;br /&gt;
	mov r7, #0&lt;br /&gt;
	mov r8, #0&lt;br /&gt;
	b       2f&lt;br /&gt;
&lt;br /&gt;
1:&lt;br /&gt;
	// 在r4存储多个&lt;br /&gt;
	stmia r4!, {r5-r8}&lt;br /&gt;
&lt;br /&gt;
	// 如果我们仍然低于bss_end，则循环。&lt;br /&gt;
2:&lt;br /&gt;
	cmp r4, r9&lt;br /&gt;
	blo 1b&lt;br /&gt;
&lt;br /&gt;
	// 调用kernel_main&lt;br /&gt;
	ldr r3, =kernel_main&lt;br /&gt;
	blx r3&lt;br /&gt;
&lt;br /&gt;
	// 暂停&lt;br /&gt;
halt:&lt;br /&gt;
	wfe&lt;br /&gt;
	b halt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
你可以使用以下方法组装boot.S:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -c boot.S -o boot.o &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Pi 3, 4===&lt;br /&gt;
&lt;br /&gt;
值得一提的是，Pi 3和4通常将kernel8.img引导到64位模式，但是你仍然可以将AArch32与kernel7.img一起使用，以实现向后兼容。 请注意，在64位模式下，引导代码以0x80000而不是0x8000加载。 对于Pi 3和4，AArch64中的引导代码完全相同，但是Pi 4具有不同的外围基址 (请参见下面的C示例代码)。&lt;br /&gt;
&lt;br /&gt;
使用最新的固件，只有主核心运行 (核心0)，辅助核心正在循环中等待。 要唤醒它们，请在0xE0 (核心1)，0xE8 (核心2) 或0xF0 (核心3) 上写一个函数的地址，然后它们将开始执行该函数。&lt;br /&gt;
&lt;br /&gt;
环境的设置和执行从 [https://github.com/raspberrypi/tools/blob/master/armstubs/ armstub8.S # L154 armstub8.s] 转移到 _start。&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
// AArch64模式&lt;br /&gt;
&lt;br /&gt;
// 将其保留在二进制文件的第一部分中。&lt;br /&gt;
.section &amp;quot;.text.boot&amp;quot;&lt;br /&gt;
&lt;br /&gt;
// 使 _start全局。&lt;br /&gt;
.globl _start&lt;br /&gt;
&lt;br /&gt;
    .org 0x80000&lt;br /&gt;
// Entry point for the kernel. Registers:&lt;br /&gt;
// x0 -&amp;gt; 内存中DTB的32位指针 (仅主核)/0 (辅助核)&lt;br /&gt;
// x1 -&amp;gt; 0&lt;br /&gt;
// x2 -&amp;gt; 0&lt;br /&gt;
// x3 -&amp;gt; 0&lt;br /&gt;
// x4 -&amp;gt; 32位内核入口点，_start位置&lt;br /&gt;
_start:&lt;br /&gt;
    // 在我们的代码之前设置堆栈&lt;br /&gt;
    ldr     x5, =_start&lt;br /&gt;
    mov     sp, x5&lt;br /&gt;
&lt;br /&gt;
    // 清除bss&lt;br /&gt;
    ldr     x5, =__bss_start&lt;br /&gt;
    ldr     w6, =__bss_size&lt;br /&gt;
3:  cbz     w6, 4f&lt;br /&gt;
    str     xzr, [x5], #8&lt;br /&gt;
    sub     w6, w6, #1&lt;br /&gt;
    cbnz    w6, 3b&lt;br /&gt;
&lt;br /&gt;
    // 跳转到C代码，不应该返回&lt;br /&gt;
4:  bl      kernel_main&lt;br /&gt;
    // 对于故障保护，也请停止此核心&lt;br /&gt;
    b 1b&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
使用以下方法编译代码:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
aarch64-elf-as -c boot.S -o boot.o&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 实现内核 ==&lt;br /&gt;
&lt;br /&gt;
到目前为止，我们已经编写了bootstrap程序集存根，用于设置处理器，以便可以使用高级语言 (例如C)。 也可以使用C等其他语言。&lt;br /&gt;
&lt;br /&gt;
=== 独立和托管环境 ===&lt;br /&gt;
&lt;br /&gt;
如果你在用户空间中进行了C或C编程，则使用了所谓的托管环境。 托管意味着有一个C标准库和其他有用的运行时功能。另外，还有独立式版本，这就是我们在这里使用的版本。 独立式意味着没有C标准库，只有我们自己提供的。 但是，有些头文件实际上不是C标准库的一部分，而是编译器。 即使在独立的C源代码中，这些仍然可用。 在这种情况下，我们使用 &amp;lt;stddef.h&amp;gt; 来获取size_t &amp;amp; NULL，并使用 &amp;lt;stdint.h&amp;gt; 来获取对于操作系统开发非常宝贵的intx_t和uintx_t数据类型，你需要确保变量是一个精确的大小 (如果我们使用一个短而不是uint16_t和短的大小改变，我们的代码将被破坏!)。 此外，你还可以访问 &amp;lt;float.h&amp;gt;，&amp;lt;iso646.h&amp;gt;，&amp;lt;limits.h&amp;gt; 和 &amp;lt;stdarg.h&amp;gt; 标头，因为它们也是独立的。 GCC实际上还提供了一些头，但这些都是特殊用途。&lt;br /&gt;
&lt;br /&gt;
=== 用C编写内核 ===&lt;br /&gt;
&lt;br /&gt;
下面演示如何在C ++ 中创建一个简单的内核。 请花点时间理解代码。 要在运行时设置 “int raspi” 的值，请参见 [[检测 _ raspberry_pi_board | 检测板类型]]。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang = &amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stddef.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
static uint32_t MMIO_BASE;&lt;br /&gt;
&lt;br /&gt;
// MMIO区域基地址，取决于主板类型&lt;br /&gt;
static inline void mmio_init(int raspi)&lt;br /&gt;
{&lt;br /&gt;
    switch (raspi) {&lt;br /&gt;
        case 2:&lt;br /&gt;
        case 3:  MMIO_BASE = 0x3F000000; break; // for raspi2 &amp;amp; 3&lt;br /&gt;
        case 4:  MMIO_BASE = 0xFE000000; break; // for raspi4&lt;br /&gt;
        default: MMIO_BASE = 0x20000000; break; // for raspi1, raspi zero etc.&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 内存映射的I/O输出&lt;br /&gt;
static inline void mmio_write(uint32_t reg, uint32_t data)&lt;br /&gt;
{&lt;br /&gt;
	*(volatile uint32_t*)(MMIO_BASE + reg) = data;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 内存映射I/O输入&lt;br /&gt;
static inline uint32_t mmio_read(uint32_t reg)&lt;br /&gt;
{&lt;br /&gt;
	return *(volatile uint32_t*)(MMIO_BASE + reg);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 以编译器不会优化的方式循环 &amp;lt;延迟&amp;gt; 时间&lt;br /&gt;
static inline void delay(int32_t count)&lt;br /&gt;
{&lt;br /&gt;
	asm volatile(&amp;quot;__delay_%=: subs %[count], %[count], #1; bne __delay_%=\n&amp;quot;&lt;br /&gt;
		 : &amp;quot;=r&amp;quot;(count): [count]&amp;quot;0&amp;quot;(count) : &amp;quot;cc&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
enum&lt;br /&gt;
{&lt;br /&gt;
    // reach寄存器的偏移量。&lt;br /&gt;
    GPIO_BASE = 0x200000,&lt;br /&gt;
&lt;br /&gt;
    // 控制所有GPIO引脚上/下拉的致动。&lt;br /&gt;
    GPPUD = (GPIO_BASE + 0x94),&lt;br /&gt;
&lt;br /&gt;
    // Controls actuation of pull up/down for specific GPIO pin.&lt;br /&gt;
    GPPUDCLK0 = (gpio _ 基数0x98)，&lt;br /&gt;
&lt;br /&gt;
    // UART的基址。&lt;br /&gt;
    UART0_BASE = (GPIO_BASE + 0x1000), // for raspi4 0xFE201000, raspi2 &amp;amp; 3 0x3F201000, and 0x20201000 for raspi1&lt;br /&gt;
&lt;br /&gt;
    // UART的reach register的偏移量。&lt;br /&gt;
    UART0_DR     = (UART0_BASE + 0x00),&lt;br /&gt;
    UART0_RSRECR = (UART0_BASE + 0x04),&lt;br /&gt;
    UART0_FR     = (UART0_BASE + 0x18),&lt;br /&gt;
    UART0_ILPR   = (UART0_BASE + 0x20),&lt;br /&gt;
    UART0_IBRD   = (UART0_BASE + 0x24),&lt;br /&gt;
    UART0_FBRD   = (UART0_BASE + 0x28),&lt;br /&gt;
    UART0_LCRH   = (UART0_BASE + 0x2C),&lt;br /&gt;
    UART0_CR     = (UART0_BASE + 0x30),&lt;br /&gt;
    UART0_IFLS   = (UART0_BASE + 0x34),&lt;br /&gt;
    UART0_IMSC   = (UART0_BASE + 0x38),&lt;br /&gt;
    UART0_RIS    = (UART0_BASE + 0x3C),&lt;br /&gt;
    UART0_MIS    = (UART0_BASE + 0x40),&lt;br /&gt;
    UART0_ICR    = (UART0_BASE + 0x44),&lt;br /&gt;
    UART0_DMACR  = (UART0_BASE + 0x48),&lt;br /&gt;
    UART0_ITCR   = (UART0_BASE + 0x80),&lt;br /&gt;
    UART0_ITIP   = (UART0_BASE + 0x84),&lt;br /&gt;
    UART0_ITOP   = (UART0_BASE + 0x88),&lt;br /&gt;
    UART0_TDR    = (UART0_BASE + 0x8C),&lt;br /&gt;
&lt;br /&gt;
    // Mailbox寄存器的偏移量&lt;br /&gt;
    MBOX_BASE    = 0xB880,&lt;br /&gt;
    MBOX_READ    = (MBOX_BASE + 0x00),&lt;br /&gt;
    MBOX_STATUS  = (MBOX_BASE + 0x18),&lt;br /&gt;
    MBOX_WRITE   = (MBOX_BASE + 0x20)&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
// 设置时钟速率为PL011至3mhz标签的Mailbox message&lt;br /&gt;
volatile unsigned int  __attribute__((aligned(16))) mbox[9] = {&lt;br /&gt;
    9*4, 0, 0x38002, 12, 8, 2, 3000000, 0 ,0&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
void uart_init(int raspi)&lt;br /&gt;
{&lt;br /&gt;
	mmio_init(raspi);&lt;br /&gt;
&lt;br /&gt;
	// 禁用UART0。&lt;br /&gt;
	mmio_write(UART0_CR, 0x00000000);&lt;br /&gt;
	// 设置GPIO引脚14 &amp;amp; &amp;amp; 15。&lt;br /&gt;
&lt;br /&gt;
	// 禁用所有GPIO引脚的上拉/下拉，并延迟150周期。&lt;br /&gt;
	mmio_write(GPPUD, 0x00000000);&lt;br /&gt;
	delay(150);&lt;br /&gt;
&lt;br /&gt;
	// 禁用引脚14、15的上拉/下拉，并延迟150个周期。&lt;br /&gt;
	mmio_write(GPPUDCLK0, (1 &amp;lt;&amp;lt; 14) | (1 &amp;lt;&amp;lt; 15));&lt;br /&gt;
	delay(150);&lt;br /&gt;
&lt;br /&gt;
	// 将0写入GPPUDCLK0以使其生效。&lt;br /&gt;
	mmio_write(GPPUDCLK0, 0x00000000);&lt;br /&gt;
&lt;br /&gt;
	// 清除挂起的中断。&lt;br /&gt;
	mmio_write(UART0_ICR, 0x7FF);&lt;br /&gt;
&lt;br /&gt;
	// Set integer &amp;amp; fractional part of baud rate.&lt;br /&gt;
	// Divider = UART_CLOCK/(16 * Baud)&lt;br /&gt;
	// Fraction part register = (Fractional part * 64) + 0.5&lt;br /&gt;
	// Baud = 115200.&lt;br /&gt;
&lt;br /&gt;
	// 对于Raspi3和4，默认情况下，UART_CLOCK依赖于系统时钟。&lt;br /&gt;
	// 将其设置为3Mhz，以便我们可以一致地设置波特率&lt;br /&gt;
	if (raspi &amp;gt;= 3) {&lt;br /&gt;
		// UART_CLOCK = 30000000;&lt;br /&gt;
		unsigned int r = (((unsigned int)(&amp;amp;mbox) &amp;amp; ~0xF) | 8);&lt;br /&gt;
		// wait until we can talk to the VC&lt;br /&gt;
		while ( mmio_read(MBOX_STATUS) &amp;amp; 0x80000000 ) { }&lt;br /&gt;
		// 将我们的消息发送到属性通道并等待响应&lt;br /&gt;
		mmio_write(MBOX_WRITE, r);&lt;br /&gt;
		while ( (mmio_read(MBOX_STATUS) &amp;amp; 0x40000000) || mmio_read(MBOX_READ) != r ) { }&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	// 分频器 Divider = 3000000/(16*115200) = 1.627 = ~ 1。&lt;br /&gt;
	mmio_write(UART0_IBRD, 1);&lt;br /&gt;
	// 小数部分寄存器 = (.627*64) 0.5 = 40.6 = ~ 40。&lt;br /&gt;
	mmio_write(UART0_FBRD, 40);&lt;br /&gt;
&lt;br /&gt;
	// 启用FIFO &amp;amp; 8位数据传输 (1个停止位，无奇偶校验)。&lt;br /&gt;
	mmio_write(UART0_LCRH, (1 &amp;lt;&amp;lt; 4) | (1 &amp;lt;&amp;lt; 5) | (1 &amp;lt;&amp;lt; 6));&lt;br /&gt;
&lt;br /&gt;
	// 屏蔽所有中断。&lt;br /&gt;
	mmio_write(UART0_IMSC, (1 &amp;lt;&amp;lt; 1) | (1 &amp;lt;&amp;lt; 4) | (1 &amp;lt;&amp;lt; 5) | (1 &amp;lt;&amp;lt; 6) |&lt;br /&gt;
	                       (1 &amp;lt;&amp;lt; 7) | (1 &amp;lt;&amp;lt; 8) | (1 &amp;lt;&amp;lt; 9) | (1 &amp;lt;&amp;lt; 10));&lt;br /&gt;
&lt;br /&gt;
	// 启用UART0，接收和传输UART的一部分。&lt;br /&gt;
	mmio_write(UART0_CR, (1 &amp;lt;&amp;lt; 0) | (1 &amp;lt;&amp;lt; 8) | (1 &amp;lt;&amp;lt; 9));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void uart_putc(unsigned char c)&lt;br /&gt;
{&lt;br /&gt;
	// 等待UART准备好传输。&lt;br /&gt;
	while ( mmio_read(UART0_FR) &amp;amp; (1 &amp;lt;&amp;lt; 5) ) { }&lt;br /&gt;
	mmio_write(UART0_DR, c);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
unsigned char uart_getc()&lt;br /&gt;
{&lt;br /&gt;
    // 等待UART收到东西。&lt;br /&gt;
    while ( mmio_read(UART0_FR) &amp;amp; (1 &amp;lt;&amp;lt; 4) ) { }&lt;br /&gt;
    return mmio_read(UART0_DR);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void uart_puts(const char* str)&lt;br /&gt;
{&lt;br /&gt;
	for (size_t i = 0; str[i] != '\0'; i ++)&lt;br /&gt;
		uart_putc((unsigned char)str[i]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#if defined(__cplusplus)&lt;br /&gt;
extern &amp;quot;C&amp;quot; /* Use C linkage for kernel_main. */&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
# ifdef AARCH64&lt;br /&gt;
// AArch64的参数&lt;br /&gt;
void kernel_main(uint64_t dtb_ptr32, uint64_t x1, uint64_t x2, uint64_t x3)&lt;br /&gt;
#else&lt;br /&gt;
// arguments for AArch32&lt;br /&gt;
void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)&lt;br /&gt;
#endif&lt;br /&gt;
{&lt;br /&gt;
	// 为Raspi2初始化UART&lt;br /&gt;
	uart_init(2);&lt;br /&gt;
	uart_puts(&amp;quot;Hello, kernel World!\r\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	while (1)&lt;br /&gt;
		uart_putc(uart_getc());&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
GPU引导加载程序通过r0-r2将参数传递给AArch32内核，引导确保保留这3个寄存器。 它们是C函数调用中的前3个参数。参数基本传染数包含RPi从其启动的设备的代码。  通常为0，但其实际值取决于主板的固件。  r1包含 “arm Linux机器类型”，其对于RPi是3138 (0xc42) 标识BCM2708 CPU。 可以从 [http://www.arm.linux.org.uk/developer/machines/ 这里] 获得ARM机器类型的完整列表。  r2包含ATAGs的地址。&lt;br /&gt;
&lt;br /&gt;
对于AArch64，寄存器有点不同，但也作为参数传递给C函数。 第一，x0是DTB的32位地址 (即内存中的 [https://elinux.org/Device_Tree_Reference设备树Blob])。 小心，它是32位地址，高位可能无法清除。 其他参数，x1-x3现在被清除为零，但保留供将来使用。 你的boot.S应该保存它们。&lt;br /&gt;
&lt;br /&gt;
请注意，我们希望如何使用通用的C函数strlen，但这个函数是C标准库的一部分，我们没有。 相反，我们依靠独立的标头 &amp;lt;stddef.h&amp;gt; 来提供size_t，我们只是声明自己的strlen实现。 你将必须为你希望使用的每个函数执行此操作 (因为独立标头仅提供宏和数据类型)。 &lt;br /&gt;
&lt;br /&gt;
GPIO和UART的地址是与外围基地址的偏移量，对于Raspberry Pi 1为0x20000000，对于Raspberry Pi 2和Raspberry Pi 3为0x3F000000。 对于 [[Raspberry Pi 4]]，基址是0xFE000000。 你可以在BCM2835手册中找到寄存器的地址以及如何使用它们。 可以通过 [[Detecting_Raspberry_Pi_Board | 读取板id]] 在运行时检测基址。&lt;br /&gt;
&lt;br /&gt;
编译使用:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -std=gnu99 -c kernel.c -o kernel.o -O2 -Wall -Wextra&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
或64位:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
aarch64-elf-gcc -ffreestanding -c kernel.c -o kernel.o -O2 -Wall -Wextra&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
请注意，上面的代码使用了一些扩展，因此我们将其构建为c99的GNU版本。&lt;br /&gt;
&lt;br /&gt;
== 链接内核 ==&lt;br /&gt;
&lt;br /&gt;
要创建完整的和最终的内核，我们必须将这些目标文件链接到最终的内核程序中。 在开发用户空间程序时，你的工具链附带了用于链接此类程序的默认脚本。 但是，这些不适合内核开发，我们需要提供自己的自定义链接器脚本。&lt;br /&gt;
&lt;br /&gt;
64位模式的链接器脚本看起来完全一样，除了起始地址。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
ENTRY(_start)&lt;br /&gt;
&lt;br /&gt;
SECTIONS&lt;br /&gt;
{&lt;br /&gt;
    /* 从LOADER_ADDR开始。*/&lt;br /&gt;
    . = 0x8000;&lt;br /&gt;
    /* 对于AArch64，使用。= 0x80000; */&lt;br /&gt;
    __start = .;&lt;br /&gt;
    __text_start = .;&lt;br /&gt;
    .text :&lt;br /&gt;
    {&lt;br /&gt;
        KEEP(*(.text.boot))&lt;br /&gt;
        *(.text)&lt;br /&gt;
    }&lt;br /&gt;
    . = ALIGN(4096); /* align to page size */&lt;br /&gt;
    __text_end = .;&lt;br /&gt;
&lt;br /&gt;
    __rodata_start = .;&lt;br /&gt;
    .rodata :&lt;br /&gt;
    {&lt;br /&gt;
        *(.rodata)&lt;br /&gt;
    }&lt;br /&gt;
    . = ALIGN(4096); /* align to page size */&lt;br /&gt;
    __rodata_end = .;&lt;br /&gt;
&lt;br /&gt;
    __data_start = .;&lt;br /&gt;
    .data :&lt;br /&gt;
    {&lt;br /&gt;
        *(.data)&lt;br /&gt;
    }&lt;br /&gt;
    . = ALIGN(4096); /* align to page size */&lt;br /&gt;
    __data_end = .;&lt;br /&gt;
&lt;br /&gt;
    __bss_start = .;&lt;br /&gt;
    .bss :&lt;br /&gt;
    {&lt;br /&gt;
        bss = .;&lt;br /&gt;
        *(.bss)&lt;br /&gt;
    }&lt;br /&gt;
    . = ALIGN(4096); /* align to page size */&lt;br /&gt;
    __bss_end = .;&lt;br /&gt;
    __bss_size = __bss_end - __bss_start;&lt;br /&gt;
    __end = .;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这里有很多文字，但不要绝望。 如果你一点一点地看，这个脚本是相当简单的。&lt;br /&gt;
&lt;br /&gt;
ENTRY(_start) 声明内核映像的入口点。 该符号已在boot.S文件中声明。由于我们实际上是在启动二进制映像，因此该条目完全无关紧要，但是它必须存在于我们作为中间文件构建的elf文件中。&lt;br /&gt;
&lt;br /&gt;
SECTIONS声明节。 它决定了我们的代码和数据的点点滴滴，并设置了一些符号来帮助我们跟踪每个部分的大小。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
    . = 0x8000;&lt;br /&gt;
    __start = .;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
“.” 表示当前地址，因此第一行告诉链接器将当前地址设置为0x8000 (或0x80000)，即内核开始的位置。 当链接器添加数据时，当前地址会自动递增。 然后，第二行创建一个符号 “__start”，并将其设置为当前地址。&lt;br /&gt;
&lt;br /&gt;
之后，为文本 (代码)，只读数据，读写数据和BSS (0初始化内存) 定义了部分。 除了名称之外，这些部分是相同的，所以让我们看看其中一个:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
    __text_start = .;&lt;br /&gt;
    .text : {&lt;br /&gt;
        KEEP(*(.text.boot))&lt;br /&gt;
        *(.text)&lt;br /&gt;
    }&lt;br /&gt;
    . = ALIGN(4096); /* align to page size */&lt;br /&gt;
    __text_end = .;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
第一行为该部分创建一个 __text_start符号。 第二行打开一个。输出文件的文本部分，在第五行中关闭。 第3行和第4行声明输入文件中的哪些部分将放置在输出中。文本部分。 在我们的案例中，“.text.boot” 将首先放置在更一般的 “.text” 之后。“.text.boot” 仅在boot.S中使用，并确保它最终位于内核映像的开头。“.text” 然后包含所有剩余的代码。 链接器添加的任何数据都会自动增加当前地址 (“.”)。 在第6行中，我们显式地增加它，使其与4096字节边界对齐 (这是RPi的页面大小)。 最后一行7创建一个 __text_end符号，以便我们知道该部分的结尾。&lt;br /&gt;
&lt;br /&gt;
__ text_start和 __ text_end是什么？为什么使用页面对齐？ 可以在内核源中使用2个符号，然后链接器将正确的地址放入二进制文件中。 例如，在boot.S中使用 __ bss_start和 __ bss_end。 但是你也可以通过先声明它们extern来使用来自C的符号。 虽然不是必需的，但我使所有部分都与页面大小对齐。 稍后，这允许将它们映射到具有可执行、只读和读写权限的页表中，而不必处理重叠 (一页中有2个部分)。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
    __end = .;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
声明所有部分后，将创建 __end符号。 如果你想知道你的内核在运行时有多大，你可以使用 __start和 __end来找出答案。&lt;br /&gt;
&lt;br /&gt;
有了这些组件，你现在可以实际构建最终内核。 我们使用编译器作为链接器，因为它允许它更好地控制链接过程。 请注意，如果你的内核是用C编写的，则应改用C编译器。&lt;br /&gt;
&lt;br /&gt;
然后，你可以使用以下方式链接你的内核:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
arm-none-eabi-gcc -T linker.ld -o myos.elf -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc&lt;br /&gt;
arm-none-eabi-objcopy myos.elf -O binary kernel7.img&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
或64位:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
aarch64-elf-gcc -T linker.ld -o myos.elf -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc &lt;br /&gt;
aarch64-elf-objcopy myos.elf -O binary kernel8.img&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 启动内核 ==&lt;br /&gt;
&lt;br /&gt;
过一会儿，你会看到你的内核在运行。&lt;br /&gt;
&lt;br /&gt;
=== 测试你的操作系统 (真实硬件) ===&lt;br /&gt;
&lt;br /&gt;
当你在上面测试硬件时，你是否仍然拥有带有原始Raspbian映像的sd卡？ 太好了。 所以你已经有一个带有引导分区和所需文件的sd卡。 如果没有，请下载原始Raspberry引导映像之一并将其复制到sd卡。&lt;br /&gt;
&lt;br /&gt;
现在从sd卡挂载第一个分区，看看它:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
bootcode.bin  fixup.dat     kernel.img            start.elf&lt;br /&gt;
cmdline.txt   fixup_cd.dat  kernel_cutdown.img    start_cd.elf&lt;br /&gt;
config.txt    issue.txt     kernel_emergency.img&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果没有raspbian映像，则可以从官方存储库中创建一个FAT32分区，然后 [https://github.com/raspberrypi/firmware/tree/master/boot 下载固件文件]。 你只需要三个文件:&lt;br /&gt;
&lt;br /&gt;
* bootcode.bin: 这是首先加载的，在GPU上执行的 (RPi4不需要，因为该模型在ROM中具有bootcode.bin)&lt;br /&gt;
* fixup.dat: 此数据文件包含与硬件相关的重要信息，必须具有&lt;br /&gt;
* start.elf: 这是RPi固件 (与IBM PC上的BIOS相同)。这也在GPU上运行。&lt;br /&gt;
&lt;br /&gt;
简化当RPi上机时，ARM CPU停止并且GPU运行。 GPU从ROM加载引导加载程序并执行 然后找到sd卡并加载bootcode.bin (RPi4除外，它有一个足够大的ROM来包含bootcode.bin)。 bootcode加载固件start.elf，它处理config.txt和cmdline.txt。 start.elf加载内核 *.img，最后ARM CPU开始运行该内核映像。&lt;br /&gt;
&lt;br /&gt;
要在ARM模式之间切换，你必须重命名内核.img文件。 如果将其重命名为 ''' kernel7.img'''，则将在AArch32模式 (ARMv7) 下执行。 对于AArch64模式 (ARMv8)，你必须将其重命名为 ''' kernel8.img '''。&lt;br /&gt;
&lt;br /&gt;
因此，现在我们用自己的umount，sync替换原始的kernel.img，将sd卡插入RPi并打开电源。&lt;br /&gt;
然后，你的微型计算机应该显示以下内容:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
Hello, kernel World!&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 测试你的操作系统 (QEMU) ===&lt;br /&gt;
&lt;br /&gt;
QEMU支持用机器类型 “raspi2” 模拟Raspberry Pi 2。 在撰写本文时，此功能在大多数软件包管理器中不可用，但可以在此处找到的最新QEMU来源中找到: https://github.com/qemu/qemu&lt;br /&gt;
&lt;br /&gt;
检查你的QEMU安装是否有qemu-system-arm，并且它支持选项 “-M raspi2”。 在QEMU中进行测试时，请务必使用源代码中注明的raspi2基址。&lt;br /&gt;
&lt;br /&gt;
使用QEMU，你无需将内核objcopy到纯二进制文件中; QEMU还支持ELF内核:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=text&amp;gt;&lt;br /&gt;
$YOURINSTALLLOCATION/bin/qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel kernel.elf&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 更新了对AArch64 (raspi2，raspi3) 的支持 ====&lt;br /&gt;
从QEMU 2.12 (2018年4月) 开始，针对64位ARM “qemu-system-aarch64” 的仿真现在支持使用机器类型 “raspi2” 和 “raspi3” 直接仿真Raspberry Pi 2和3。''分别。 这应该允许测试64位系统代码。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
qemu-system-aarch64 -M raspi3 -serial stdio -kernel kernel8.img&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
请注意，在大多数情况下，32位ARM代码和64位ARM代码之间几乎没有差异，但是代码的行为方式可能会有所不同，尤其是在内核内存管理方面。 此外，一些AArch64实现可以支持在它们的32位对应物中的任何一个上找不到的特征 (例如，加密扩展、增强的霓虹灯SIMD支持)。&lt;br /&gt;
&lt;br /&gt;
另一个非常重要的注意事项: 从Raspberry Pi 3开始，SoC更改为 [https://github.com/raspberrypi/documentation/files/ 1888662/ BCM2837]，并且PL011时钟 (UART0) 不再固定，而是从系统时钟派生。 因此，要正确设置波特率，首先必须设置时钟频率。 这可以通过 [https://github.com/raspberrypi/firmware/wiki/Mailboxes邮箱呼叫] 来完成。 或者你可以使用AUX miniUART (UART1) 芯片，这更容易编程。 下面的链接包括有关如何同时进行的教程。&lt;br /&gt;
&lt;br /&gt;
= = 另见 = =&lt;br /&gt;
&lt;br /&gt;
=== 文章 ===&lt;br /&gt;
* [[Raspberry Pi Bare Bones Rust]]&lt;br /&gt;
* [[ARMv7-A Bare Bones]]&lt;br /&gt;
&lt;br /&gt;
=== 外部链接 ===&lt;br /&gt;
* [http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf BCM2835 ARM Peripherals] (original Raspberry Pi)&lt;br /&gt;
* [https://github.com/raspberrypi/documentation/files/1888662/ BCM2837 ARM Peripherals] (latest Raspberry Pi 3)&lt;br /&gt;
* [http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf PrimeCell UART (PL011) Technical Reference Manual]&lt;br /&gt;
* [https://github.com/s-matyukevich/raspberry-pi-os Raspberry-Pi-OS a hobby OS tutorial for the Raspberry Pi] (details Linux drivers too, great source)&lt;br /&gt;
* [https://github.com/bztsrc/raspi3-tutorial Bare metal tutorial for AArch64]&lt;br /&gt;
&lt;br /&gt;
[[Category:ARM]]&lt;br /&gt;
[[Category:ARM_RaspberryPi]]&lt;br /&gt;
[[Category:Bare bones tutorials]]&lt;br /&gt;
[[Category:C]]&lt;br /&gt;
[[Category:C++]]&lt;/div&gt;</summary>
		<author><name>Zhang3</name></author>
	</entry>
</feed>