diff --git "a/articles/\345\260\206Linux\347\247\273\346\244\215\345\210\260\346\226\260\347\232\204\345\244\204\347\220\206\345\231\250\346\236\266\346\236\204\342\200\224\342\200\224\357\274\210\344\270\200\357\274\211\345\237\272\347\241\200.md" "b/articles/\345\260\206Linux\347\247\273\346\244\215\345\210\260\346\226\260\347\232\204\345\244\204\347\220\206\345\231\250\346\236\266\346\236\204\342\200\224\342\200\224\357\274\210\344\270\200\357\274\211\345\237\272\347\241\200.md" new file mode 100644 index 0000000000000000000000000000000000000000..6725afc5c5faf034310a4c0adf68a9837f6c562a --- /dev/null +++ "b/articles/\345\260\206Linux\347\247\273\346\244\215\345\210\260\346\226\260\347\232\204\345\244\204\347\220\206\345\231\250\346\236\266\346\236\204\342\200\224\342\200\224\357\274\210\344\270\200\357\274\211\345\237\272\347\241\200.md" @@ -0,0 +1,107 @@ +@[TOC](将Linux移植到新的处理器架构——(一)基础) + +# 将Linux移植到新的处理器体系结构,第1部分:基础 +虽然对于最近在Linux 4.2-rc1中移植的不带mmu的Hitachi 8/300可能只需要3775行代码,但让Linux内核在新的处理器架构上运行仍然是一个困难的过程。并且网络上描述移植过程的文档并不多。本系列共三篇文章的目的是概述将Linux内核移植到新处理器架构时可以遵循的过程。 + +在研究了许多受支持的架构之后,我发现Linux内核有一个定义非常好的框架,他提供了很多接口供移植使用。这样一个框架在逻辑上可以分为两个部分,这两部分贯穿整个系统。第一部分是引导代码,也就是说从内核接管bootloader->init最终执行的那一刻开始,特定于架构的代码就被执行了。第二部关于架构的代码在引导阶段完成后,内核正常运行时定期执行。包括启动新线程,处理硬件中断、软件中断,用户态和内核态的数据传输,处理系统调用等。 + +## 1、如何确定到底是不是一个新的架构移植? + +“移植”一词有三种含义 +1、移植到一个新的开发板(已经支持的处理器) +2、移植到当前受支持的处理器系列中的新处理器 +3、移植到一个全新架构 + +如果一个新处理器增加了新的指令集,那肯定是一次新的架构移植,但有时就不太清楚到底是不是一次架构移植。 + +有一次花了我几周的时间才搞清楚到底是不是一次新的架构移植。 + +当时是2013年5月,我刚刚被法国学术计算机实验室LIP6聘请,将Linux内核移植到TSAR,这是一种正在被研究设计的架构,TSAR是一种遵循当前趋势的架构图:更小、single-issue,低功耗多核通信网络。他还增加了一些创新:一个完整的硬件缓存一致性协议,用于icache和dcache和TLB以及物理分布但逻辑共享的内存(physically distributed but logically shared memory)。 + +我当时的问题是,处理器核心与MIPS32 ISA兼容,这意味着本次移植可能属于第二类:“现有处理器系列中的新处理器”。但是,由于TSAR的虚拟内存模型与任何MIPS处理器的虚拟内存模型完全不同,我不得不大幅修改整个MIPS分支,以引入这种新处理器,有时几乎别无选择,只能用#ifndef TSAR#将整个文件包围起来#endif。 +所以最终结果是,我们就是一个新架构的移植,为其创建一个新的文件夹:mkdir linux/arch/tsar + +## 2、了解你要移植硬件 +真正了解底层硬件无疑是将Linux移植到它的基本前提,这是最明显的前提。 + +处理器的规格通常在逻辑上或物理上分为至少两部分(例如,最近发布的新RISC-V处理器规格)。第一部分通常详细介绍用户级ISA,这是处理器能够理解和执行的用户级指令列表。第二部分描述了特权体系结构,其中包括仅内核级指令列表和控制处理器状态的各种系统寄存器。 + +第二部分包含了需要移植的大部分信息,这部分的差别导致无法重用其他体系结构中的代码。 + +需要重视的信息主要有: + + **处理器体系结构的虚拟内存模型、页表的格式和转换机制是什么?** + + 许多处理器架构(如x86、ARM或TSAR)定义了灵活的虚拟内存布局。理论上,它们的虚拟地址空间可以在用户空间和内核空间之间以任何方式分割,尽管Linux中32位处理器的默认布局通常将较低的3GiB分配给用户空间,并将较高的1Gb保留给内核空间。在其他一些体系结构中,这种布局受到硬件设计的强烈限制。例如,在MIPS32上,虚拟地址空间被静态分割为两个大小相同的区域:较低的2GiB专用于用户空间,较高的2GiB专用于内核空间;后者甚至在物理地址空间中包含预定义的窗口。 + 页面表的格式与处理器使用的转机制密切相关。在硬件管理机制的情况下,当包含虚拟地址和物理地址之间最近使用的转换的有限大小的TLB-a硬件缓存不包含给定虚拟地址的转换(称为TLB未命中)时,硬件状态机将直接从内存中的页表结构中获取正确的转换,并用它填充TLB。这意味着页表的格式必须是固定的,并且必须由处理器的规范定义。在基于软件的机制中,TLB未命中异常由一段代码处理,从理论上讲,只需指定TLB条目的格式,就可以完全自由地组织页表。 + +**如何启用/禁用中断,从特权模式切换到用户模式,反之亦然,获取异常的原因,等等。** +尽管所有这些操作通常只涉及读取或修改可用系统寄存器集中的某些位字段,但是不同架构的寄存器有不同的用法。正是因为这个原因,在大多数情况下,它们实际上是由小块专用汇编代码执行的。 +**什么ABI?** + 尽管人们可能会认为应用程序二进制接口(ABI)只与编译工具有关,因为它定义了堆栈格式化为堆栈帧的方式、函数给出或返回参数和返回值的方式等。;在移植Linux时,实际上完全有必要熟悉它。例如,作为系统调用(通常由ABI定义)的接收者,内核必须知道从哪里获取参数以及如何返回值;或者在上下文开关上,内核必须知道要保存和恢复的内容,以及构成线程上下文的内容,等等。 +## 3、了解内核 +学习一些内核概念,尤其是关于Linux使用的内存布局,肯定会有所帮助。我承认,我花了一段时间才弄清楚低记忆和高记忆之间的区别,以及直接映射和vmalloc区域之间的区别。 + +对于一个典型且简单的移植(到32位处理器),内核占据了虚拟地址空间的上1GB,它通常相当简单。在这个1GiB中,Linux定义它的较低部分将直接映射到系统内存的较低部分(因此称为低内存):这意味着如果内核访问地址0xC0000000,它将被重定向到物理地址0x00000000。 + +相比之下,在物理内存多于直接映射区域中可映射内存的系统中,内核通常无法访问系统内存的上部(称为高内存)。必须使用其他机制,例如kmap()和kmap_atomic(),以便临时访问这些高内存页。 + +基于直接映射区域的vmalloc区域,是由vmalloc()控制的。这种分配机制提供了以几乎连续的方式分配内存页的能力,尽管这些页不一定是物理上连续的。它对于以几乎连续的方式分配大量内存页特别有用,否则可能无法找到等量的连续空闲物理页。 +在[Linux设备驱动程序\[PDF\]](https://lwn.net/images/pdf/LDD3/ch15.pdf)和这篇[LWN文章](https://lwn.net/Articles/356378/)中可以找到关于Linux中内存管理的进一步阅读。 +## 4、如何开始 +在你满脑子都是处理器规范和内核原理的情况下,终于到了向这个新创建的arch目录添加一些文件的时候了。但是等等。。。我们应该从哪里开始,如何开始?我们在移植过程中应该必须遵守哪些API接口? +首先,内核甚至需要一组最小的文件来定义一组最小的符号(函数、变量、定义)。这组文件和符号通常可以从编译失败中推断出来:如果由于缺少文件/符号而导致编译失败,则很好地表明可能应该实现它(或者有时应该修改某些配置选项)。在移植Linux的情况下,当实现架构代码和内核其他部分时的头文件以及API时,这种方法尤其重要。 +在内核最终编译并能够在目标硬件上执行之后,了解引导代码的执行流程非常有用。这允许许多函数在开始时保持为空,直到系统最终变得稳定并达到init进程,才逐渐实现。这种方法通常适用于在早期汇编引导代码之后执行的几乎所有C函数。不过,建议您启动早期的_printk()基础设施,否则可能很难调试。 +## 5、终于开始了:非代码文件的最小集合 +将编译工具移植到新的处理器体系结构是移植Linux内核的先决条件,但在这里,我们假设它已经被执行了。就编译工具而言,剩下要做的就是构建一个交叉编译器。由于此时很可能尚未完成(甚至尚未启动)标准C库的移植,因此只能创建一个阶段1交叉编译器。 +这种交叉编译器只能编译裸机执行的源代码,这非常适合内核,因为它不依赖任何外部库。相比之下,stage-2交叉编译器内置了对标准C库的支持。 +将Linux移植到新处理器的第一步是在arch/中创建一个新目录,该目录位于内核树的根(例如,在我的例子中是Linux/arch/tsar/)。在这个新目录中,布局相当标准化: + +- configs/: 支持系统的默认配置 (i.e. *_defconfig files) +- include/asm/ ,Linux源码内部使用的头文件 +- include/uapi/asm 对于要导出到用户空间(例如libc)的头文件 +- kernel/: 通用内核管理 +- lib/: 常用的那套函数 (e.g. memcpy(), memset(), etc.) +- mm/: 内存管理 +一旦新的arch目录存在,Linux就会自动知道它。但是会报找不到Makefile,而不是找不到找不到这种新的架构。 +```bash + ~/linux $ make ARCH=tsar + Makefile: ~/linux/arch/tsar/Makefile: No such file or directory +``` +如以下示例所示,最小arch Makefile只有几个变量需要指定: +``` + KBUILD_DEFCONFIG := tsar_defconfig + + KBUILD_CFLAGS += -pipe -D__linux__ -G 0 -msoft-float + KBUILD_AFLAGS += $(KBUILD_CFLAGS) + + head-y := arch/tsar/kernel/head.o + + core-y += arch/tsar/kernel/ + core-y += arch/tsar/mm/ + + LIBGCC := $(shell $(CC) $(KBUILD_CFLAGS) -print-libgcc-file-name) + libs-y += $(LIBGCC) + libs-y += arch/tsar/lib/ + + drivers-y += arch/tsar/drivers/ +``` +- KBUILD_DEFCONFIG 必须包含有效默认配置的名称,该配置是configs目录中的defconfig文件之一 (e.g. configs/tsar_defconfig). +- KBUILD_CFLAGS 和 KBUILD_AFLAGS分别为“compiler ”和“assembler”定义编译标志。 +- {head,core,libs,}-y列出要在内核映像中编译的对象(或包含对象的子目录)(有关详细信息,请参阅[Documentation/kbuild/makefiles.txt](https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt)) +另一个位于arch目录根目录下的文件是Kconfig。该文件主要用于两个目的: +1、定义描述架构的arch的配置选项。 +2、选择适用于该架构的独立于arch的配置选项(即Linux源代码中其他地方已经定义的选项)。 + +由于这将是新创建的 arch 的主要配置文件,它的内容也决定了 menuconfig 命令的布局(例如 make ARCH=tsar menuconfig)。 很难给出一个例子,因为它在很大程度上取决于目标架构,但是可以参考一下其他简单架构的配置文件。 + +defconfig 文件(例如 configs/tsar_defconfig)是完成与 Linux 内核构建系统(kbuild)相关的文件所必需的。 它的作用是定义架构的默认配置,这基本上意味着指定一组配置选项,这些选项将用作种子,以生成用于 Linux 内核编译的完整配置。 再一次,从其他架构的 defconfig 文件开始应该会有所帮助,但仍然建议对其进行改进,因为它们往往会能激活更多功能——支持 USB、IOMMU 甚至文件系统,不过在这个移植阶段搞这些还太早。 + +最后,要创建的最后一个“不是真正的代码但仍然很重要”的文件是一个脚本(通常位于 kernel/vmlinux.lds.S),它将指示链接器如何将各个代码和数据部分放置在最终的内核映像中 . 例如,通常需要在二进制文件的最开头设置早期汇编引导代码,正是这个脚本干的。 + +## 6、结论 +至此,构建系统就可以使用了:现在可以生成初始内核配置,对其进行自定义,甚至开始编译。 但是,编译很快就会停止,因为该移植仍然不包含任何代码。 + +在下一篇文章中,我们将深入探讨移植的第二部分的一些代码:头文件、早期的汇编引导代码,以及在创建第一个内核线程之前执行的所有最重要的 arch 函数。 + +参考文献:[Porting Linux to a new processor architecture, part 1: The basics](https://lwn.net/Articles/654783/) \ No newline at end of file