前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >探究Linux Kernel内核架构,让你成为真正的内核专家

探究Linux Kernel内核架构,让你成为真正的内核专家

原创
作者头像
嵌入式Linux内核
发布2023-08-08 21:56:20
7300
发布2023-08-08 21:56:20
举报
文章被收录于专栏:用户10025783的专栏

一、前言

本文是“Linux内核分析”系列文章的第一篇,会以内核的核心功能为出发点,描述Linux内核的整体架构,以及架构之下主要的软件子系统。之后,会介绍Linux内核源文件的目录结构,并和各个软件子系统对应。

注:本文和其它的“Linux内核分析”文章都基于如下约定: a) 内核版本为Linux 3.10.29(该版本是一个long term的版本,会被Linux社区持续维护至少2年),可以从下面的链接获取:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.10.29.tar.xz b) 鉴于嵌入式系统大多使用ARM处理器,因此涉及到体系结构部分的内容,都以ARM为分析对象

二、Linux内核的核心功能

如下图所示,Linux内核只是Linux操作系统一部分。对下,它管理系统的所有硬件设备;对上,它通过系统调用,向Library Routine(例如C库)或者其它应用程序提供接口。

因此,其核心功能就是:管理硬件设备,供应用程序使用。而现代计算机(无论是PC还是嵌入式系统)的标准组成,就是CPU、Memory(内存和外存)、输入输出设备、网络设备和其它的外围设备。所以为了管理这些设备,Linux内核提出了如下的架构。

三、内核架构

3.1内核之作用

  • Linux内核为用户进程提供了虚拟机接口。 进程编程无需知道计算机上安装了什么物理硬件,Linux内核将所有硬件抽象为一致的虚拟接口。
  • 此外,Linux以对用户进程透明的方式支持多任务:每个进程都可以像它是计算机上唯一的进程一样工作,并且专用于占用主内存和其他硬件资源。内核实际上同时运行多个进程,并负责中介对硬件资源的访问,以便在维护进程间安全性的同时,每个进程都具有公平的访问权限。

内核之结构

内核主要由以下五大组成部分:

  • 进程调度器(SCHED)负责控制对CPU的进程访问。调度程序执行一项调度策略,以确保进程可以公平地访问CPU,同时确保内核按时执行必要的硬件操作。
  • 内存管理器(MM)允许多个进程安全地共享机器的主内存系统。此外,内存管理器还支持虚拟内存,该虚拟内存允许Linux支持使用的内存量超过系统可用内存的进程。使用文件系统将未使用的内存换出到持久性存储,然后在需要时交换回来。
  • 虚拟文件系统(VFS)通过为所有设备提供通用文件接口来抽象化各种硬件设备的详细信息。此外,VFS支持几种与其他操作系统兼容的文件系统格式。
  • 网络接口(NET)提供对几种网络标准和各种网络硬件的访问。
  • 进程间通信(IPC)子系统实现在单个Linux系统上进行进程间通信的多种机制。

从依赖性的角度分析:

  • 进程调度程序子系统使用内存管理器为恢复特定进程的特定进程调整硬件内存映射。
  • 进程间通信子系统依赖于内存管理器来支持共享内存通信机制。此机制允许两个进程除了访问其通常的私有内存之外,还访问公共内存的区域。
  • 虚拟文件系统使用网络接口来支持网络文件系统(NFS),并且还使用内存管理器来提供ramdisk设备。
  • 内存管理器使用虚拟文件系统来支持交换;这是内存管理器依赖进程调度程序的唯一原因。当某个进程访问当前已换出的内存时,内存管理器会向文件系统发出请求,从持久性存储中获取内存,并挂起该进程。

3.2内核之重要数据结构

  • 任务链表(Task List):流程调度程序为每个活动的流程维护一个数据块。这些数据块存储在称为任务列表的链接列表中。进程调度程序始终维护一个指示当前活动进程的当前指针。
  • 内存映射(memry map):内存管理器基于每个进程存储虚拟地址到物理地址的映射,还存储有关如何获取和替换特定页面的其他信息。此信息存储在内存映射数据结构中,该结构存储在流程调度程序的任务列表中。
  • 索引节点(I-node):虚拟文件系统使用索引节点(索引节点)表示逻辑文件系统上的文件。索引节点数据结构存储文件块号到物理设备地址的映射。如果两个进程打开了相同的文件,则可以在多个进程之间共享I节点数据结构。共享是通过两个任务数据块指向相同的i节点来完成的。
  • 数据连接(Data Connection) 所有数据结构都植根于流程调度程序的任务列表。系统上的每个进程都有一个数据结构,该数据结构包含一个指向其内存映射信息的指针,以及指向代表所有打开文件的i节点的指针。最后,任务数据结构还包含指向数据结构的指针,该数据结构表示与每个任务关联的所有打开的网络连接。

【文章福利】小编推荐自己的Linux内核技术交流群:【865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

四、各子系统架构分析

4.1进程调度器架构

进程调度器是Linux内核中最重要的子系统。其目的是控制对计算机CPU的访问。这不仅包括用户进程的访问,还包括其他内核子系统的访问。

由上图可知,进程调度器可分为四大模块:

  • 调度策略模块负责判断哪个进程可以访问CPU;设计该策略是为了使进程可以公平地访问CPU。
  • 体系结构相关模块特定于体系结构的模块设计有一个公共接口,用于抽象任何特定计算机体系结构的详细信息。这些模块负责与CPU通信以挂起和恢复进程。这些操作涉及知道每个进程需要保留哪些寄存器和状态信息,并执行汇编代码以执行挂起或恢复操作。
  • 体系结构无关模块与体系结构无关的模块与策略模块进行通信,以确定下一步将执行哪个进程,然后调用特定于体系结构的模块以恢复适当的进程。此外,此模块调用内存管理器以确保为恢复的过程正确还原了内存硬件
  • 系统调用接口模块允许用户进程仅访问内核显式导出的那些资源。这将用户进程对内核的依赖性限制为一个定义良好的接口,尽管其他内核模块的实现发生了变化,该接口很少更改

进程调度器维护一个数据结构,即任务列表,每个活动进程具有一个条目。此数据结构包含足够的信息来暂停和恢复过程,但还包含其他记帐和状态信息。该数据结构可在整个内核层公开使用。

如前所述,进程调度程序将调用内存管理器子系统。因此,进程调度程序子系统依赖于内存管理器子系统。此外,所有其他内核子系统都依赖进程调度程序来挂起和恢复进程,同时等待硬件请求完成。这些依赖关系通过函数调用和对共享任务列表数据结构的访问来表示。所有内核子系统都读取和写入代表当前任务的数据结构,从而导致整个系统中的双向数据流。

除了内核层中的数据和控制流之外,O / S服务层还为用户进程提供了一个接口,用于注册计时器通知。这对应于[Garlan 1994]中描述的隐式执行体系结构样式。这导致控制从调度程序流向用户进程。恢复休眠过程的通常情况在正常情况下不视为控制流程,因为用户进程无法检测到此操作。最后,调度程序与CPU通信以挂起和恢复进程。这导致数据流和控制流。CPU负责中断当前正在执行的进程,并允许内核调度另一个进程。

4.2内存管理器架构

内存管理器子系统负责控制对硬件内存资源的进程访问。这是通过硬件内存管理系统完成的,该系统提供了进程内存引用与机器物理内存之间的映射。内存管理器子系统在每个进程的基础上维护此映射,以便两个进程可以访问相同的虚拟内存地址并实际使用不同的物理内存位置。此外,内存管理器子系统还支持交换。它将未使用的内存页面移至持久性存储,以使计算机支持的虚拟内存多于物理内存。

模块结构分析

内存管理器主要由以下三个模块组成:

  • 体系结构相关模块为内存管理硬件提供了虚拟接口
  • 体系结构无关模块执行所有的每个进程映射和虚拟内存交换。此模块负责确定在出现页面错误时将收回哪些内存页面-由于没有预期将需要更改此策略,因此没有单独的策略模块。
  • 系统调用接口模块提供对用户进程的受限访问。该接口允许用户进程分配和释放存储,还可以执行内存映射文件I / O。

从数据表征的角度分析

内存管理器存储物理地址到虚拟地址的每个进程的映射。该映射作为参考存储在流程调度程序的任务列表数据结构中。除了此映射之外,数据块中的其他详细信息还告诉内存管理器如何获取和存储页面。例如,可执行代码可以将可执行映像用作后备存储,但是必须将动态分配的数据备份到系统页面文件中。最后,内存管理器在此数据结构中存储权限和记帐信息,以确保系统安全。

数据流,控制流和依赖关系

内存管理器控制内存硬件,并在发生页面错误时从硬件接收通知-这意味着内存管理器模块和内存管理器硬件之间存在双向数据和控制流。另外,内存管理器使用文件系统来支持交换和内存映射的I / O。此要求意味着内存管理器需要对文件系统进行过程调用以存储和从持久性存储中获取内存页面。由于无法立即完成文件系统请求,因此内存管理器需要暂停一个过程,直到将内存换回为止。此要求导致内存管理器对过程调度程序进行过程调用。同样,由于每个进程的内存映射都存储在进程调度程序的数据结构中,因此在内存管理器和进程调度程序之间存在双向数据流。用户进程可以在进程地址空间内设置新的内存映射,并可以注册自己以在新映射的区域内通知页面错误。这引入了从内存管理器到系统调用接口模块再到用户进程的控制流。从传统意义上讲,没有来自用户进程的数据流,但是用户进程可以使用系统调用接口模块中的选择系统调用从内存管理器中检索某些信息。

4.3虚拟文件系统架构

虚拟文件系统旨在提供存储在硬件设备上的数据的一致视图。计算机中几乎所有的硬件设备都是使用通用设备驱动程序接口表示的。虚拟文件系统进一步发展,并允许系统管理员在任何物理设备上安装一组逻辑文件系统中的任何一个。逻辑文件系统促进与其他操作系统标准的兼容性,并允许开发人员使用不同的策略来实现文件系统。虚拟文件系统抽象了物理设备和逻辑文件系统的详细信息,并允许用户进程使用通用接口访问文件,而不必知道文件驻留在哪个物理或逻辑系统上。

除了传统的文件系统目标之外,虚拟文件系统还负责加载新的可执行程序。该责任由逻辑文件系统模块完成,这使Linux支持多种可执行格式。

故简言之:

  • 抽象物理设备和逻辑文件系统的详细信息,提供公共访问接口。
  • 提供通用的设备驱动程序接口。
  • 加载新的可执行程序。

模块结构分析

可分为四大模块:

  • 公共驱动抽象模块 由于存在大量不兼容的硬件设备,因此存在大量的设备驱动程序。Linux系统最常见的扩展是添加了新的设备驱动程序。设备独立接口模块提供了所有设备的一致视图。
  • 逻辑文件系统模块,每个支持的文件系统都有一个逻辑文件系统模块。
  • 系统独立接口层提供了硬件资源与硬件和逻辑文件系统无关的视图。该模块使用面向块或面向字符的文件接口显示所有资源。
  • 系统调用接口层为用户进程提供对文件系统的受控访问。虚拟文件系统仅将特定功能导出到用户进程。

4.4网络接口层架构

网络子系统允许Linux系统通过网络连接到其他系统。支持许多可能的硬件设备,以及可以使用的许多网络协议。网络子系统抽象了这两个实现细节,因此用户进程和其他内核子系统可以访问网络而不必知道正在使用什么物理设备或协议。

模块结构组成

  • 网络设备驱动程序负责与硬件设备通信。每个可能的硬件设备都有一个设备驱动程序模块。
  • 设备无关的接口模块提供了所有硬件设备的一致视图,因此子系统中的更高级别不需要特定的使用硬件知识。
  • 网络协议模块负责实现每种可能的网络传输协议。
  • 协议无关接口模块提供独立于硬件设备和网络协议的接口。这是接口模块,其他内核子系统使用该接口模块访问网络,而无需依赖特定的协议或硬件。

简言之,

  • 网络设备驱动实现底层硬件的控制
  • 设备独立接口层为上层提供对底层设备驱动层实现统一访问接口
  • 网络协议层实现不同的网络协议
  • 协议独立接口层用以抽象隐藏不同硬件、不同网络协议

五、Linux内核源代码的目录结构

5.1Linux内核源代码包括三个主要部分:

  • 1. 内核核心代码,包括第3章所描述的各个子系统和子模块,以及其它的支撑子系统,例如电源管理、Linux初始化等
  • 2. 其它非核心代码,例如库文件(因为Linux内核是一个自包含的内核,即内核不依赖其它的任何软件,自己就可以编译通过)、固件集合、KVM(虚拟机技术)等
  • 3. 编译脚本、配置文件、帮助文档、版权说明等辅助性文件

下图示使用ls命令看到的内核源代码的顶层目录结构,具体描述如下:

  • include/ ---- 内核头文件,需要提供给外部模块(例如用户空间代码)使用。
  • kernel/ ---- Linux内核的核心代码,包含了3.2小节所描述的进程调度子系统,以及和进程调度相关的模块。
  • mm/ ---- 内存管理子系统(3.3小节)。
  • fs/ ---- VFS子系统(3.4小节)。
  • net/ ---- 不包括网络设备驱动的网络子系统(3.5小节)。
  • ipc/ ---- IPC(进程间通信)子系统。
  • arch// ---- 体系结构相关的代码,例如arm, x86等等。
  • arch//mach- ---- 具体的machine/board相关的代码。
  • arch//include/asm ---- 体系结构相关的头文件。
  • arch//boot/dts ---- 设备树(Device Tree)文件。
  • init/ ---- Linux系统启动初始化相关的代码。
  • block/ ---- 提供块设备的层次。
  • sound/ ---- 音频相关的驱动及子系统,可以看作“音频子系统”。
  • drivers/ ---- 设备驱动(在Linux kernel 3.10中,设备驱动占了49.4的代码量)。
  • lib/ ---- 实现需要在内核中使用的库函数,例如CRC、FIFO、list、MD5等。
  • crypto/ ----- 加密、解密相关的库函数。
  • security/ ---- 提供安全特性(SELinux)。
  • virt/ ---- 提供虚拟机技术(KVM等)的支持。
  • usr/ ---- 用于生成initramfs的代码。
  • firmware/ ---- 保存用于驱动第三方设备的固件。
  • samples/ ---- 一些示例代码。
  • tools/ ---- 一些常用工具,如性能剖析、自测试等。
  • Kconfig, Kbuild, Makefile, scripts/ ---- 用于内核编译的配置文件、脚本等。
  • COPYING ---- 版权声明。
  • MAINTAINERS ----维护者名单。
  • CREDITS ---- Linux主要的贡献者名单。
  • REPORTING-BUGS ---- Bug上报的指南。
  • Documentation, README ---- 帮助、说明文档。

5.2Linux内核的任务

1.从技术层面讲,内核是硬件与软件之间的一个中间层。作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。 2.从应用程序的层面讲,应用程序与硬件没有联系,只与内核有联系,内核是应用程序知道的层次中的最底层。在实际工作中内核抽象了相关细节。 3.内核是一个资源管理程序。负责将可用的共享资源(CPU时间、磁盘空间、网络连接等)分配得到各个系统进程。 4.内核就像一个库,提供了一组面向系统的命令。系统调用对于应用程序来说,就像调用普通函数一样。

5.3内核实现策略

1.微内核。最基本的功能由中央内核(微内核)实现。所有其他的功能都委托给一些独立进程,这些进程通过明确定义的通信接口与中心内核通信。 2.宏内核。内核的所有代码,包括子系统(如内存管理、文件管理、设备驱动程序)都打包到一个文件中。内核中的每一个函数都可以访问到内核中所有其他部分。目前支持模块的动态装卸(裁剪)。Linux内核就是基于这个策略实现的。 哪些地方用到了内核机制?

1.进程(在cpu的虚拟内存中分配地址空间,各个进程的地址空间完全独立;同时执行的进程数最多不超过cpu数目)之间进行通 信,需要使用特定的内核机制。 2.进程间切换(同时执行的进程数最多不超过cpu数目),也需要用到内核机制。 进程切换也需要像FreeRTOS任务切换一样保存状态,并将进程置于闲置状态/恢复状态。 3.进程的调度。确认哪个进程运行多长的时间。

六、Linux内核配置、编译、安装

6.1x86配置

(1)方法一:交互式,在内核顶层目录下运行make config

(2)方法二:菜单式,在内核顶层目录下运行make menuconfig

6.2x86编译

(1)内核编译

  • make zImage:编译出的内核 < 512k
  • make bzImage:通用编译命令

(2)内核模块编译:make modules命令编译内核模块

(3)内核模块安装:make modules_install命令将编译号的内核模块copy到当前系统的/lib/modules下的**目录下;

(4)内核模块打包:mkinitrd initrd-$version $version命令对内核模块进行打包,其中initrd-$version为打包后的文件名字,$version为要打包的目录;

6.3x86安装

(1)拷贝内核:copy内核编译出来的内核映像(步骤1)到启动目录,即cp arch/$cpu/boot/bzImage /boot/vmlinuxz-$version

(2)拷贝内核模块:copy内核模块打包文件(步骤4)到/boot目录下,即cp initrd-$version /boot/

(3)修改启动配置文件:修改/etc/grub.conf文件

七、驱动认知

为什么要学习写驱动

原来树莓派开发使用厂家提供的wiringPi库,开发简单。但未来做开发时,不一定都是用树莓派,没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。

文件名与设备号

linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?

代码语言:javascript
复制
依靠文件名与设备号

依靠文件名与设备号。在/devls -l可以看到

设备号又分为:主设备号用于区别不同种类的设备;次设备号区别同种类型的多个设备。驱动插入到链表的位置(顺序)由设备号检索

内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:

  • 编写完驱动程序,加载到内核
  • 用户空间open后,调用驱动程序(驱动程序就是操作寄存器来驱动IO口,单片机51,32就是这种操作)

open函数打通上层到底层硬件的详细过程:

用户空间调用open(比如open(“/dev/pin4”,O_RDWR))产生一个软中断(中断号是0x80),进入内核空间调用sys_call,这个sys_call在内核里面是汇编的,用Source Insight搜索不到。

sys_calll真正调用的是sys_open(属于VFS层虚拟文件系统,因为磁盘的分区和引脚分区不一样,为了实现上层统一化),根据你的设备名比如pin4去到内核的驱动链表,根据其主设备号与次设备号找到相关驱动函数。

调用驱动函数里面的open,这个open就是对寄存器的操作,从而设置IO口引脚电平。这件事对于单片机来说特变容易,就两句话搞定:

代码语言:javascript
复制
sbit pin4 = P1^4;
pin4=1;

八、shell

shell(壳)是一个特殊的应用,也经常被称为命令行 。可以理解为是一个命令解释器

例如:当我们输入“ls -l”的时候,它将此字符串解释为

1.在默认路径找到该文件(/bin/ls), 2.执行该文件,并附带参数"-l"。

一个shell对应一个终端 (terminal)。曾经来说,终端是一个硬件设备,用来输入并显示输出。如今,由于图形化界面的普及,终端往往就像下图一样,是一个图形化的窗口。

你可以通过这个窗口输入或者输出文本,这个文本直接传递给shell进行分析解释,然后执行,本质就是提供和内核交互的程序。

shell脚本

在没有图形界面之前,shell充当了用户的界面,当用户要运行某些应用时,通过shell输入命令,来运行程序。shell是可编程的,它可以执行符合shell语法的文本,这样的文本叫做shell脚本(script)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
    • 二、Linux内核的核心功能
      • 三、内核架构
        • 3.1内核之作用
        • 3.2内核之重要数据结构
      • 四、各子系统架构分析
        • 4.1进程调度器架构
        • 4.2内存管理器架构
        • 4.3虚拟文件系统架构
        • 4.4网络接口层架构
      • 五、Linux内核源代码的目录结构
        • 5.1Linux内核源代码包括三个主要部分:
        • 5.2Linux内核的任务
        • 5.3内核实现策略
      • 六、Linux内核配置、编译、安装
        • 6.1x86配置
        • 6.2x86编译
        • 6.3x86安装
      • 七、驱动认知
        • 八、shell
        相关产品与服务
        云硬盘
        云硬盘(Cloud Block Storage,CBS)为您提供用于 CVM 的持久性数据块级存储服务。云硬盘中的数据自动地在可用区内以多副本冗余方式存储,避免数据的单点故障风险,提供高达99.9999999%的数据可靠性。同时提供多种类型及规格,满足稳定低延迟的存储性能要求。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档