前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >FPGA Xilinx Zynq 系列(三十六)Linux 内核

FPGA Xilinx Zynq 系列(三十六)Linux 内核

作者头像
FPGA技术江湖
发布2020-12-30 12:14:56
1.5K0
发布2020-12-30 12:14:56
举报
文章被收录于专栏:FPGA技术江湖FPGA技术江湖
大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来FPGA Xilinx Zynq 系列第三十六篇,开启第二十三章,带来Linux 内核相关内容,本篇内容目录简介如下:

本系列分享来源于《The Zynq Book》,Louise H. Crockett, Ross A. Elliot,Martin A. Enderwitz, Robert W. Stewart. L. H. Crockett, R. A. Elliot, M. A. Enderwitz and R. W. Stewart, The Zynq Book: Embedded Processing with the ARM Cortex-A9 on the Xilinx Zynq-7000 All Programmable SoC, First Edition, Strathclyde Academic Media, 2016。

Linux 内核

上一章介绍了 Linux 内核的概念,这一章试图详细说明 Linux 操作系统的关键部分。要查看内核本身的层次结构,讨论主要的一些特征:内存管理、进程管理和文件系统。

23.1 Linux 内核层级

到目前为止,Linux 内核还是一个谜团,只知道是基于 Linux 的系统的一个决定性的部分。现在我们要来进一步探究这个内核,看看它所负责做的那些核心操作。

内核实际上是一个操作系统的核心。这是操作系统启动时首先被载入的部分, 因此会驻留在主内存中。

图 23.1 和上一章的图 22.1 很像,不同的是内核被展开了,显示了其中最重要的一些部件。说真的,如果你搜索 Linux 内核的 “ 地图 ”,会看到一个复杂的网络,其中有许多不同的部件和路径,不过这已经超出本书的范畴了。

图 23.1: Linux 内核的重要成分

23.2 系统调用接口

系统调用 (SCI)是用户空间里的应用和它所需的内核所提供的服务之间的交互。SCI 在用户空间和内核空间的边界上建立起这个联系,直接的跨越这个边界的函数调用是不可能的。Linux 系统调用的实现和具体的处理器架构是密切相关的。图 23.2 表示了基于处理器中断实现的相当简化的方法 [1]。

  • 所有的系统调用都是通过单一的内核入口点,根据一个寄存器的值再分派的,这个寄存器在 C 库中指定并赋值;
  • 要触发一个软件中断,然后这个中断的处理程序来执行 system_call 函数;
  • 这个识别寄存器将system_call_table编入索引中通过 SCI触发正确的系统调用;
  • 从系统调用中返回的时候,要切换回用户空间,然后继续执行 C 库中的代码,然后回到起先的用户应用中。

因此系统调用实现了用户空间程序和内核自身之间在某种程度上的抽象。这还能使得所有的用户应用和内核代码之间的交互是高度一致的。

图 23.2: 简化版的基于中断的系统调用

23.3 内存管理

尽管计算系统中所使用的存储器技术在飞速的前进,软件对所用内存的需求始终紧随其后。事实上软件一直有比有限的内存更大的存储要求。虚拟内存有助于解决这个问题,让系统中看起来有比实际更多的内存。

23.3.1 虚拟内存

图 23.3 是虚拟内存的简化图示。两个进程,进程 A 和进程 B,每个都有自己的虚拟地址空间和页表。一个页简单说就是一段内存,具有一个唯一的页帧编号(Page Frame Number,PFN)。而页表中存放的是映射数据,使得处理器可以把虚拟 内存地址映射到物理地址上。比如这里进程 A 的虚拟 PFN1 映射到了物理的 PFN3。为了能把这个虚拟 PFN1 映射到物理的 PFN3,处理器要用虚拟页中的某个偏移量来做页表的索引。如果页表中有一项是对应那个偏移量的,就能获得那个物理 PFN。如果没有,处理器就变成要试图访问不存在的内存区域了,就是说那个地址就不能被解析为物理地址,于是操作系统就会得到一个缺页通知,要着手解决这个问题。

内存管理子系统提供了一些功能,再来看图 23.3,可以发现虚拟 PFN 和物理 PFN 之间并非一一对应,实际上不同的进程中的几个虚拟 PFN 可以被映射到相同的物理 PFN 上,比如可以看到进程 A 和进程 B 都有页映射到物理内存的 PFN3。这样就使得系统可以使用很大的地址空间,看上去比物理存在的空间还要大。每个进程的虚拟地址空间是独立的,通过这种隔离就实现了一定程度的保护,防止设计之外的代码或数据的不正常覆盖。这种方法还有一个好处是一个系统中所有运行的进程能公平地共享系统所提供的物理内存 [2]。

图 23.3: 简化版本的虚拟内存 [2]

23.3.2 内存的高端和低端

历史上,由于 32 位提供给内核的虚拟地址空间是 0xC0000000 到 0xFFFFFFFF, 因此一个 32 位的 Linux 系统只能支持 4GB 的内存。这 4GB 空间默认的分配是在地址低端的 3GB 给用户空间,而剩下的从 0xC0000000 开始的 1GB 留给内核代码自己用(实际上整个空间比 4GB 要略小一点,因为 I/O 也要占用地址空间)。

为了克服这个问题,现在 Linux 内核把虚拟地址空间分成两个区域,叫做低内存和高内存。低内存指的是逻辑地址位于内核空间的内存;而高内存是指逻辑地址并不存在,它位于定义给内核虚拟地址范围之外的地方。需要特殊的页表才能把高内存映射到内核的地址空间,由于任何时刻可以映射过来的高地址页的数量是有限的,这样的操作的代价是昂贵的。因此内核的核心就会保持在低内存,而把高内存只用作某些内核任务和进程页 [3]。

23.4 进程管理

和任何其他操作系统一样,要做的任务是由进程执行的。要知道实际上操作系统中的任何程序实际上只不过是一些保存为可执行的映像的机器码格式的指令,所以一个进程就是这样一个在运行的计算机程序。

  • 因为机器码指令是由系统的处理器执行的,进程的变换就这点而言是动态的;
  • Linux 是一个多进程操作系统,每个进程具有自己的权利和义务,意思是说一个进程的崩溃不会影响其他进程;
  • 进程同时要用 CPU等各种系统资源来运行指令,也要用物理内存来存放进程和相应的数据。

23.4.1 进程的表达

在 Linux 中,用一个叫做 task_struct 的结构来表示一个进程。在这个结构中有用来表示那个进程的全部数据,还有大量其他用来把这个进程和任何父 / 子进程联系起来的数据。task_struct 内包含的一些和进程相关的数据如下 [4]:

  • 进程执行的状态 —— 运行、睡眠但不可打断、停止等;
  • 标志 —— 进程创建、退出、内存分配;
  • 进程优先级 —— 优先级的数值越低表示优先级越高,这个优先级是动态的,由多个因素决定;
  • 进程地址空间。

23.4.2 进程创建、调度和析构

进程是创建在用户空间的,可能是通过执行一个程序创建(这样就创建了整个新的进程),也可能是由程序本身通过 fork 这个系统调用(创建一个子进程)来创建的。每个进程有它自己的进程标识符(PID),这就是一个数字,在系统中用来唯一地识别每个进程的。这些数字在进程的生命周期中不会变化,但是一旦进程终止,它的 PID 会被释放,可以用于新的进程。进程会在几个不同的函数中来回传递,这些函数包括 [4]:

  • copy_process —— 按照当前进程创建它的一个拷贝,它会查看 Linux 安全模型来确认当前进程有权创建新任务,然后初始化这个 task_struct
  • dup_task_struct —— 给进程分配一个新的 task_struct,然后把进程的句柄拷贝给这个结构;
  • wake_up_new_task —— 初始化调度器数据,把创建的进程放到运行队列中。

图 23.4: 通过 fork 调用做的进程创建流程

Linux 进程调度器负责根据进程的优先级来管理系统中已有的各种进程。它握有所有进程的列表,以每个优先级上各个进程对应的 task_struct 来标识进程。调度函数根据进程的执行历史和负载,选择最合适的进程来运行。

有几种不同的事件会启动进程机构:运行结束的时候自然进程就终止了;也可能是由于一个特殊的信号发出来终止了进程,或是 exit 这个系统调用导致了终止。前面提到的所有的方法都会导致调用内核函数 do_exit,它把操作系统中与当前进程的所有的联系都彻底清除了。要退出的进程的 PF_EXITING 标识被置上,由此开始的一系列调用把这个进程和它在 运 行 期内所用过的所有的资源都断开。exit_notify 调用在进程状态变为 PF_DEAD 之前发出一个通知,让调度器选择下一个要执行的进程 [4]。

图 23.5: 进程析构的操作

23.5 文件系统

23.5.1 Linux 文件系统

Linux 的功能极为强大,它能支持很多不同的文件系统,这包括但不限于:

表 23.1: Linux 支持的某些文件系统

众多的文件系统排成了一个层次分明的树状结构,从系统看来就是单个文件系统入口。新的文件系统 (就是说一个文件系统关联着 Linux 里的一个存储设备)加到系统中的时候,可以挂载到这棵树上 [5]。

23.5.2 虚拟文件系统

回看图 23.1,能把许多文件系统容纳进来,虚拟文件系统 (Virtual File System,VFS)起到了关键的作用。它实现了一个抽象层,在各个文件系统和内核其他部分之间建立了统一的接口。它是文件系统接口的根的那层,负责追踪所有当前支持并已经挂载了的文件系统。图 23.6 表示了 VFS 和实际文件系统之间的交互(在这个例子中,我们只画出了 vfat 和 ext2 文件系统)。

缓冲区 cache 用来做硬件设备的通用数据缓冲区,能加速对持有文件系统的物理设备上的文件系统的访问。它和文件系统无关,因此也就使得这些文件系统与支持它的设备驱动及介质无关。

在已挂载的文件系统中遍历时,inode 要持续地读或写,因此 VFS 维持了一个 inode cache 来加速对已挂载的文件系统的访问。(说明 ——inode 表示索引结点 (index node),是 Unix 文件系统里的一种数据结构,包含了文件系统对象的所有 信息。)

VFS 里还维持了目录项的 cache,以加速对最常用的目录的访问。当一个目录被系统文件访问时,它的详细数据被加入到这个 cache 中,因此下次访问相同目录时会快很多。VFS 使用一种最近使用 (LRU)算法来维护这个 cache,当一个目录项被添加到这个 cache 时,之前用过的目录会下降一个层级。当 cache 满了的时候,层级最低的目录会被丢弃,那么新的目录就可以加进来了 [5]。

图 23.6: Linux 虚拟文件系统的系统交互 [5]

23.6 架构的相关代码

Linux 内核层次中最低的那层是和架构相关的代码。Linux 内核支持非常多种不同的硬件平台,因此每种平台就需要有平台特殊的代码才能和 “ 通用 ” 的架构无关的代码结合起来。这就是一般叫做 BSP 的代码,包括支持架构系列和处理器的源文件、通用启动支持文件、DMA 硬件接口、中断处理和其他和特定的处理器系列相关的内容 [6]。做完配套的基于 ZedBoard 的教程,你会发现设计中重要的一步是给Zynq 构建正确的 BSP,让处理器能和开发板通信。

23.7 Linux 设备驱动

和任何操作系统里的一样,Linux 的设备驱动程序,可以被看做是个 “ 黑盒子 ”,在系统里的硬件设备和操作系统中运行的程序之间实现了一个抽象层。利用设备驱动程序,就可以实现一组标准化的调用,使用这组调用的程序就和特定的设备无关了,这些调用再负责使用特定的驱动程序来执行所需的操作。这样就得到了一个模块化的方法,可以在内核之外构建驱动程序,需要的时候再加进来 [7]。

23.7.1 关于机制与策略的说明

关于设备驱动程序,一个重要的特征是他们实现的是机制而非策略 [7]。

  • 机制决定了这部分代码能做什么。因此设备驱动程序通过它内部的代码决定了哪部分硬件的能力。
  • 策略是在系统更高的层次上定义的,关注的是机制应该如何被使用。

确保驱动程序是和策略无关的带来了灵活性,这样无论用户对于某个硬件的特殊需求是什么,都能使用上相应的硬件。

23.7.2 模块 / 设备分类

在 Linux 中,模块是一段在运行时刻加入到内核去的代码,下一章要厘清这个概念的。设备驱动程序也是一种模块,文件系统类型也是以模块的方式存在于内核中的。

设备驱动程序可以是三种基础类型中的一种,这三种类型是 [7]:

  • 字符设备——以字符流的方式访问的设备,如文件。负责这种设备的驱动程序会做 open/close 和 read/write 这些系统调用;
  • 块设备 —— 能够实现一个文件系统的设备,比如硬盘。Linux 让块设备像字符设备一样工作,可以读写任意数量的字节,而不是像传统的 Unix 系统那样,只能读写 512或更大的一个块。块设备和字符设备的不同是内核本身管理数据的方式,因此具有不同的接口;
  • 网络接口 —— 用于经由网络接口传输数据包的设备。网络设备并不知道传输的连接底层,只是处理所收发的包。

23.8 本章回顾

本章给出了 Linux 内核的一些基础部分的高层概述。系统调用接口是从内核抽象出来一个用户层的方式。对于内核本身,本章讨论了内存和进程管理、文件系统和设备驱动程序。

23.9 参考文献

注意:所有的 URL 最后是在 2014 年 6 月访问过。

[1] IBM Developer Works, “Anatomy of the Linux File System”, 30 October 2007位于 : http://www.ibm.com/developerworks/linux/library/l-linux-filesystem/

[2] David A. Rusling, “Memory Management” in The Linux Kernel, v1.0位于 : http://www.tldp.org/LDP/tlk/mm/memory.html

[3] J. Corbet, G. Kroah-Hartman and A. Rubini, “Memory Mapping and DMA” in Linux Device Drivers, 3rd Edition, O’Reilly, 2005, pp 412-463

[4] IBM Developer Works, “Anatomy of Linux Process Management”, 20 December 2008位于 : http://www.ibm.com/developerworks/library/l-linux-process-management/

[5] David A. Rusling “The File System” in The Linux Kernel, v1.0位于 : http://tldp.org/LDP/tlk/fs/filesystem.html

[6] M. Tim Jones, “2. GNU/Linux Architecture” in GNU/Linux Applications Programming, Cengage Learning, 2005, p17

[7] J. Corbet, G. Kroah-Hartman and A. Rubini, “An Introduction to Device Drivers” in Linux Device Drivers, 3rd Edition, O’Reilly, 2005, pp 1-14

第三十六篇到此结束,下一篇将带来第三十七篇,开启第二十四章,本系列最后一篇,Linux 启动等相关内容。欢迎各位大侠一起交流学习,共同进步。

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FPGA技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档