将操作系统移植到新的芯片架构的经验
移植 芯片 架构

> 
> 在将嵌入式系统操作系统移植到不同的芯片架构时，RT-Thread 的维护者们从中学到了什么。
> 
> 
> 


![](/data/attachment/album/202105/28/221925tuv6j9lsg6xovog2.jpg "diagram of planning a cloud")


曾经有人问我，为什么计算机被称为“计算机”，它们做的事情可远不止计算数字。一台现代的个人电脑可以浏览互联网、播放音频和视频、为视频游戏和电影生成漂亮的图形、模拟和预测复杂的天气模式和流行病风险、将建筑和工程蓝图变为现实等等。


计算机之所以能做到这些，是因为所有这些问题都可以归结为数字方程，而计算机的 CPU —— 其中央处理单元 —— 实际上不过是一个简单的计算器。


为了让 CPU 向硬盘驱动器发送信号以写入数据，或向显示器发送信号以显示图像，它必须接收指令。这些指令是以 “代码” 的形式出现的，这是一种简明的说法，即必须有人写一个 *程序* ，与CPU “说” 同样的语言。CPU 理解的是 *机器语言*，这是一个大多数人都无法理解的比特阵列，大多数人都不可能手动写出来。相反，我们使用像 C、C++、Java、Python 等编程语言。这些语言被解析并编译成机器语言，然后交付给 CPU。


如果你试图用一种它不理解的语言来指示 CPU，它不知道该怎么做。你可以通过尝试用 [x86\_64 RHEL](https://www.redhat.com/en/store/red-hat-enterprise-linux-developer-suite) 镜像启动 [树莓派](https://opensource.com/resources/raspberry-pi) 来体验这种误传尝试的尴尬结果。如果它能工作就好了，但是不能。


### 将一个操作系统移植到一个新的架构上


[RT-Thread 项目](https://opensource.com/article/20/6/open-source-rtos) 为嵌入式系统程序员提供了一个开源的操作系统（OS）。嵌入式领域是非常多样化的，有很多物联网（IoT）、定制工业和业余设备。RT-Thread 的目标是使嵌入式编程对每个人来说都很容易，无论你使用什么设备。有时，这意味着要将一个操作系统移植到一个新的架构上，不管是用于相同架构但指令集略有不同的的芯片，还是用于全新的架构。


一开始处理这个问题可能会有点吓人 —— 你可能不知道从哪里开始或如何开始。这篇文章收集了 RT-Thread 维护者在将 [RTOS](https://www.rt-thread.io/) 移植到新的芯片架构时学到的经验。


### 你在开始之前需要知道什么


这里是一个看似难以逾越的过程的高屋建瓴的观点。这对你的项目来说可能有所不同，但从概念上来说，这是相对普遍的，即使一些具体的细节是不同的：


1. 准备好一个 C 语言的执行环境
2. 确认可以通过串行端口发送和接收字符
3. 确认上下文切换代码可以工作
4. 获取支持的硬件定时器
5. 确认中断程序可以通过串口接收和解析数据


### 执行模式


对于大多数先进的体系结构，操作系统和用户应用程序运行在不同的权限级别上。这可以防止有功能故障的代码影响操作系统的集成和安全。例如，在 ARMv7-A 架构中，操作系统通常在系统模式下运行，而在 ARMv8-A 中，操作系统可以在 EL2 或 EL3 权限级别上运行。


通常情况下，芯片在通电时以最高权限级别执行启动代码。但在此之后，操作系统会将特权级别切换到其目标模式。


#### 1、执行 C 代码


这一步的关键动作是将 <ruby> <a href="https://en.wikipedia.org/wiki/.bss">  块起始符号 </a> <rt>  block starting symbol </rt></ruby>（.bss）部分设置为零，并设置堆栈指针。


在 C 语言的实现中，未初始化的全局变量和静态变量通常存储在 .bss 部分，它不占用存储设备的任何空间。当程序被加载时，相应的空间被分配到内存中，并被初始化为零。当操作系统启动时，它必须自己做这项工作。


另一方面，操作系统必须初始化堆栈空间并设置堆栈指针。由于 C 语言程序在进入和退出函数时在堆栈上保存和恢复局部变量，所以在调用任何 C 函数之前必须设置堆栈指针。RT-Thread 必须为每个新创建的线程做这个步骤。


#### 2、至少使用一个串行驱动器


RT-Thread 通过串口输出信息和日志，这也有助于在移植过程中对代码进行调试。在这个阶段，通过串口 *接收* 数据是不必要的。当我们第一次在串口上看到我们友好的、熟悉的 RT-Thread 的标志时，我们就知道我们走对了路！


#### 3、确认上下文切换逻辑


一个任务的上下文是它的整个执行环境，它包含通用寄存器、程序计数器、堆栈帧的位置等等。当一个新的线程被创建时，RT-Thread 必须手动分配和设置它的上下文，这样调度器就可以切换到新的线程，就像它对其他线程一样。


有三件事需要注意：


* 首先，当 RT-Thread 启动时，默认情况下中断是禁用的。当任务调度器第一次被启用时，它们就会被启用；这个过程是在上下文切换期间用汇编语言实现的。
* 第二，当一个线程退出时，下一个调度将开始，这时拥有的资源会被空闲的线程回收。
* 第三，数据被推入堆栈的顺序必须与从堆栈中弹出数据的顺序一致。


一般来说，你希望正常进入主函数和 msh 控制台。然而，在这个阶段无法实现输入控制，因为串行输入中断还没有实现。当串行中断实现后，就可以进行 msh 输入了。


#### 4、设置定时器


RT-Thread 需要一个定时器来定期产生中断；它被用来计算自系统启动以来所经过的“滴答”。计数器的编号用于提供软件中断功能，并指示内核何时开始调度一个任务。


设置时间片的值可能是一件棘手的事情。它通常是 10ms 到 1ms。如果你在一个慢速的 CPU 上选择一个小的时间片，大部分时间就会花在任务切换上 —— 不利于完成其他事情。


#### 5、确认串口工作正常


在这一步，我们通过串口与 RT-Thread msh 进行交互。我们发送命令，按回车键，然后看着 msh 执行命令并显示结果。


这个过程通常不难实现。不过，有一点要提醒大家。在某些平台上，在处理完串口中断后，别忘了清除中断标志。


一旦串口工作正常，移植过程基本上就完成了。


### 实践


为了将你的项目移植到不同的芯片架构上，你需要非常清楚地了解你所针对的芯片的架构。熟悉你的项目中最关键的部分的底层代码。通过对照芯片的手册结合大量的实际工作经验，你会了解芯片的特权模式、寄存器和编译方法。


如果你没有需要移植到新芯片的项目，请加入我们；RT-Thread 项目总是需要帮助将 RTOS 移植到新的芯片上！作为一个开源项目，RT-Thread 正在改变开源嵌入式编程的面貌。请在 [RT-Thread 俱乐部](https://club.rt-thread.io/)介绍你自己并寻求帮助!




---


本文基于 DEV 社区上的 [如何将操作系统移植到不同的芯片架构上？](https://dev.to/abby06/how-to-port-operating-system-to-different-chip-architecture-3od9)，并经许可转载。




---


via: <https://opensource.com/article/21/5/port-chip-architectures>


作者：[Alan Smithee](https://opensource.com/users/alansmithee) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
I was once asked why computers are called "computers" when they do so much more than compute numbers. A modern PC browses the internet, plays audio and video, generates beautiful graphics for video games and movies, simulates and predicts complex weather patterns and epidemiological risks, brings architectural and engineering blueprints to life, and much more.

The reason computers can do all of this because all these problems can be expressed as numerical equations, and the computer's CPU—its central processing unit—is actually little more than a simple calculator.


To get a CPU to send signals to a hard drive to write data or to a monitor to show an image, it must receive instructions. These instructions come in the form of "code," which is a terse way of saying someone must write a *program* that "speaks" the same language as the CPU. A CPU understands *machine language*, a mostly incomprehensible array of bits that most humans don't bother writing out manually. Instead, we use programming languages like C, C++, Java, Python, and so on. These languages are parsed and compiled into machine language, which is delivered to the CPU.

If you try to instruct a CPU in a language it doesn't understand, the CPU won't know what to do. You can experience the rather unspectacular results of such an attempt at miscommunication by trying to boot a [Raspberry Pi](https://opensource.com/resources/raspberry-pi) from an [x86_64 RHEL](https://www.redhat.com/en/store/red-hat-enterprise-linux-developer-suite) image. It would be nice if it could work, but it doesn't.

## Porting an OS to a new architecture

The [RT-Thread project](https://opensource.com/article/20/6/open-source-rtos) offers an open source operating system (OS) for embedded-systems programmers. The embedded space is extremely diverse, with lots of Internet of Things (IoT), custom industrial, and hobbyist devices. RT-Thread's goal is to make embedded programming easy for *everyone*, regardless of what device you're using. Sometimes, that means porting an OS to a new architecture, whether for a chip of the same architecture but with slightly different instruction sets or new architectures altogether.

Approaching this problem can be a little intimidating at first—you may not know where or how to start. This article collects the lessons RT-Thread maintainers learned as we ported [RTOS](https://www.rt-thread.io/) to new chip architectures.

## What you need to know before beginning

Here's a high-level view of a seemingly insurmountable process. This could differ for your project, but conceptually this is relatively universal, even if some of the specifics are different:

- Prepare a C-language execution environment
- Confirm that characters can be sent and received over a serial port
- Confirm that the context switch code works
- Get the hardware timers supported
- Confirm that the interrupt routine can receive and parse data over the serial port

## The execution model

For most advanced architectures, the OS and user applications run at different privilege levels. This prevents malfunctioning code from affecting the OS's integration and safety. For example, in the ARMv7-A architecture, the OS usually runs in the System mode, while in ARMv8-A, an OS can run at the EL2 or EL3 privilege level.

Usually, a chip executes bootup code at the highest privilege level when it's powered on. After that, though, the OS switches the privilege level to its target mode.

### 1. Execute C code

The key action in this step is to set the [block starting symbol](https://en.wikipedia.org/wiki/.bss) (.bss) section to zero and set up the stack pointers.

In C-language implementations, the uninitialized global variables and static variables are usually stored in the .bss section, which doesn't occupy any space in the storage device. When the program is loaded, the corresponding space is allocated in memory and initialized to zero. When the OS boots up, it has to do this work by itself.

On the other hand, the OS has to initialize the stack space and set up the stack pointer. Since C-language programs save and restore local variables on the stack when entering and exiting a function, the stack pointer must be set before invoking any C functions. RT-Thread has to do this step for each newly created thread.

### 2. Use at least one serial drive

RT-Thread outputs information and logs through the serial port, which also helps debug the code during the transplantation process. At this stage, *receiving* data over serial ports is not required. We knew we were on the right track when we first saw our friendly, familiar RT-Thread logo over the serial port!

### 3. Confirm context switching logic

The context of a task is its whole execution environment, which contains generic registers, the program counter, the location of the stack frame, and so on. When a new thread is created, RT-Thread has to allocate and set up its context manually so that the scheduler can switch to the new thread, as it does with others.

There are three things to pay attention to:

- First, when RT-Thread starts up, interrupts are disabled by default. They are enabled when the task scheduler is enabled for the first time; this process is implemented in assembly language during the context-switch period.
- Second, the next scheduling will start when a thread exits, which is when the resources owned are reclaimed by the idle thread.
- Third, the order that data is pushed into the stack must be consistent with the order of popping data out of the stack.

Generally, you want to enter the main function and the msh console normally. However, input control can't be achieved at this stage because serial input interrupts are not implemented. When serial interrupts are implemented, msh inputs can be made.

### 4. Set the timer

RT-Thread requires a timer to generate interrupts periodically; this is used to count the ticks that elapse since the system startup. The tick number is used to provide software interrupt functions and instruct the kernel when to start scheduling a task.

Setting the value of a time slice can be a tricky business. It's usually 10ms to 1ms. If you choose a small time slice on a slow CPU, most of the time is spent on task switching—to the detriment of getting anything else done.

### 5. Confirm serial port works correctly

In this step, we interacted with RT-Thread msh over the serial port. We sent commands, pressed Enter, and watched as msh executed the command and displayed the results.

This process is usually not difficult to implement. A word of warning, though: Don't forget to clear the interrupt flag on some platforms after the serial port interrupt is handled.

Once the serial port works correctly, the porting process is essentially done!

## Get busy

To port your project to different chip architectures, you need to be very clear about the architecture of the chip you're targeting. Get familiar with the underlying code in the most critical points of your project. By cross-referencing the chip's manual combined with a lot of practical working experience, you'll learn the chip privilege mode, register, and compilation method.

If you don't have a project you need to port to a new chip, please join us; the RT-Thread project can always use help porting RTOS to new chips! As an open source project, RT-Thread is changing the landscape of open source embedded programming. Please introduce yourself and ask for help at [RT-Thread Club](https://club.rt-thread.io/)!

*This article is based on How to Port Operating System to Different Chip Architecture?*

*on the DEV Community and is republished with permission.*

## Comments are closed.