Linux内核
Linux内核具有如下重要特性。
- 可移植性:支持硬件平台广泛,在大多数体系结构上都可以运行。
- 可裁剪性:及可以运行在超级计算机上,也可以运行在很小的设备上。
- 标准化和互用性:遵守标准化和互用性规范。
- 完善的网络支持。
- 安全性:源码开放,缺陷暴露。
- 稳定性和可靠性。
- 模块化:运行时根据系统需要加载程序
内核配置
内核自行配制:
- 尺寸小。自己定制内核可以使代码尺寸减小,运行将会更快。
- 节省内存。由于内核部分代码永远占用物理内存,定制内核可以是系统拥有更多的可用物理内存。
- 减少漏洞。不需要的功能编译进入内核可能会增加系统被攻击者利用的忌讳。
- 动态加载模块。根据需要动态的加载或者卸载模块,可以节省系统内存。但是,将来某种功能编译为模块方式会比编译到内核内的方式速度慢一些。
内核源码获取
要获得Linux内核源码,可以遵循以下步骤:
-
访问Linux内核官方网站 https://www.kernel.org/,在下载页面中选择需要的版本进行下载。
-
通过Git将内核源码克隆到本地:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
也可以使用其他镜像网站来加速下载速度,例如GitHub、阿里云等。
- 解压下载的源码:
tar -xvf linux-x.y.z.tar.gz
其中,x.y.z是内核版本号。
Linux内核源码顶层目录
以下是Linux内核源码顶层目录的表格及其说明:
目录/文件名 | 说明 |
---|---|
arch/ | 存放不同硬件架构的代码 |
block/ | 存放块设备驱动程序 |
certs/ | 存放用于数字证书验证的X.509证书 |
crypto/ | 存放密码学相关代码 |
Documentation/ | 存放内核文档 |
drivers/ | 存放硬件驱动程序 |
firmware/ | 存放外部设备的固件文件 |
fs/ | 存放文件系统相关代码 |
include/ | 存放内核头文件 |
init/ | 存放初始化代码 |
ipc/ | 存放进程间通信(IPC)相关代码 |
kernel/ | 存放内核核心代码 |
lib/ | 存放内核库 |
LICENSES/ | 存放内核使用的许可证 |
Makefile | 内核的主Makefile,负责调用其他Makefile |
mm/ | 存放与内存管理相关的代码 |
net/ | 存放网络设备和协议相关的代码 |
samples/ | 存放一些示例代码 |
scripts/ | 存放各种脚本文件,例如编译脚本 |
security/ | 存放安全子系统相关的代码 |
sound/ | 存放声卡驱动程序 |
tools/ | 存放与内核开发相关的工具 |
usr/ | 存放initramfs根文件系统中的/usr目录 |
virt/ | 存放虚拟化相关的代码 |
.clang-format | 定义C语言源代码的格式 |
.cocciconfig | Coccinelle脚本的配置文件 |
.get_maintainer.ignore | 不被维护者指定的文件列表 |
.gitattributes | Git版本控制系统的属性文件 |
.gitignore | Git版本控制系统的忽略文件 |
.mailmap | 映射不同邮箱地址的贡献者名字 |
COPYING | GNU GPL协议的全文 |
CREDITS | 内核开发者和其他贡献者的名单 |
Kbuild | 包含了内核编译系统Makefile的目标及规则 |
Kconfig | 内核配置系统的主配置文件 |
MAINTAINERS | 维护者及其管理的子系统、硬件或文件的列表 |
README | 对内核的简要说明 |
REPORTING-BUGS | 提交Bug报告的说明文件 |
Linux内核配置方式
以下是Linux内核的配置方式及其说明:
配置方式 | 说明 |
---|---|
makeconfig或makeallconfig | 这两种配置方式将所有选项设置为默认值,并提示用户为新硬件和功能添加支持 |
makeallyesconfig或makeallmodconfig | 将所有配置选项设置为“是”或者“模块”,前者会将所有代码编译进内核,后者会编译成模块 |
make menuconfig | 提供一个基于ncurses库的图形化菜单,允许用户浏览和修改所有配置选项 |
make xconfig | 提供一个基于Qt库的图形化界面,与make menuconfig相似,但提供更丰富的视觉效果和交互性 |
make gconfig | 提供一个基于GTK库的图形化界面,与make menuconfig相似,但使用GTK库而不是ncurses库 |
/usr/src/linux/.config文件 | 内核配置选项的默认值存储在.config文件中,可以通过编辑该文件手动配置内核 |
需要注意的是,内核的配置选项非常多,有些选项可能需要根据特定的硬件或应用程序进行调整。
内核移植
对于嵌入式Linux系统来说,有各种体系结构的处理器和硬件平台,并且用户需要根据需求自己定制硬件单板。只要是硬件平台有变化,即使非常小。可能也需要做一些移植工作。
- ARM公司提供ARM的核
- 芯片生产厂商在核的外围加入一些必要的硬件单元(时钟、电源管理单元、中断控制器、DDR控制器等)形成SoC
- 设备生产厂商根据产品需求在SoC外围增加设备(网卡、存储器芯片、液晶显示屏等)
所以Linux内核的移植工作也就分为了三个层次:
- 内核代码要添加ARM核的支持。
- 内核代码中要添加Soc的支持。
- 内核代码中要添加相应硬件单板的支持。
制作内核版本补丁
假设基于linux-3.14.25内核移植,没有修改的内容源代码目录是linux-3.14.25,修改过的内核源代码目录是linux-3.14.25-fs4412.
cd linux-3.14.25-fs4412
cp .config arch/arm/configs/fs4412_defconfig
make ARCH=arm distclean
cd ../
diff -urN linux-3.14.25/ linux-3.14.25-fs4412/ > patch-linux-3.14.25-fs4412
这样就得到了一个补丁文件patch-linux-3.14.25-fs4412。
Linux 设备树
在Linux内核源码的ARM体系结构引入设备树之前,相关的BSP代码中充斥着大量的平台设备代码。这些代码大多是重复的、杂乱的。之前内核移植的工作很大一部分就是在修改这一部分代码中特定硬件相关的平台设备信息。为了避免重复劳动,Linux设备树诞生了。
在Linux中,BSP代表"板级支持包"(Board Support Package),这是一些特定于硬件平台的软件代码和配置文件。BSP通常由芯片厂商、设备制造商或第三方开发人员提供,用于为不同类型的嵌入式设备提供适当的软件支持。
BSP代码通常包括以下内容:
- 引导加载程序(Bootloader):负责将操作系统加载到系统内存中,并启动它。
- 内核补丁(Kernel patches):为支持特定硬件平台而对Linux内核进行的修改。
- 设备驱动程序(Device drivers):用于与硬件交互的软件组件,例如网络接口卡、磁盘控制器等。
- 系统初始化脚本(System initialization scripts):在启动过程中运行,以设置系统环境和配置参数。
- 文件系统镜像(File system images):包含操作系统所需的所有文件和资源,例如库、二进制文件、配置文件等。
BSP代码是嵌入式系统的关键组成部分,它们提供了对硬件平台的完整支持,使Linux能够在各种嵌入式设备上运行。
设备树的目的
设备树是一个描述硬件的数据结构。它只是提供了一种语言,将硬件配置从Linux内核源码中提取出来。设备树使得目标板和设备变成数据驱动的,他们必须基于传递给内核的数据进行初始化,而不是像以前一样采用硬编码的方式。
- 平台识别
内核使用设备树中的数据去识别特定的设备。内核必须在早期初始化阶段识别机器,这样内核才有机会运行特定机器相关的初始化序列。
大多数情况下,机器识别是与设备树无关的。内核通过机器的CPU或者SoC来选择初始化代码。
以ARM平台为例。setup_arch会调用setup_machine_fdt,后者遍历machine_desc链表,选择最匹配设备树数据的machine_desc结构体。
- 实时配置
大多数情况下,设备树是固件与内核之间进行数据通信的唯一方式,所以也用于传递实时的或者配置数据给内核,比如内核参数、initrd镜像的地址等。
- 设备植入
经过目标板的识别和早期配置数据解析之后,内核进一步进行初始化。期间,unflatten_device_tree函数被调用,将设备树的数据转换成一种更有效地实时的形式。
Linux设备树的使用
设备树文件的类型有:.dts、.dtsi和.dtb。
其中.dtsi是被包含的设备树源文件,类似于C语言中的头文件。
.dts是设备树源文件,可以包含其他.dtsi文件。
.dtb文件由.dts编译生成。
假设我们要描述一个带有LED灯的开发板。我们可以编写一个名为"board.dts"的设备树源文件,内容如下:
/dts-v1/;
/ {
model = "Example Board";
compatible = "example,board";
leds {
compatible = "gpio-leds";
green {
gpios = <&gpio1 2 0>; /* GPIO1_2 */
label = "Green LED";
};
};
};
上述代码定义了一个名为"Example Board"的模型,并指定了该设备树与"example,board"兼容。接着定义了一个leds节点,表示这个开发板上有一颗LED灯。该节点也指定了它的兼容性为"gpio-leds",并定义了具体的LED灯属性——在GPIO1的第2个引脚上,标签为"Green LED"。
接下来,我们需要将这个设备树编译成二进制格式。可以使用如下命令:
dtc -I dts -O dtb -o board.dtb board.dts
该命令会将名称为"board.dts"的设备树源文件编译成二进制格式,并保存为"board.dtb"文件。
最后,我们需要在内核启动时指定要加载的设备树。可以在bootloader中指定,也可以使用内核命令行参数"-dtb"来加载设备树。例如,在U-Boot中可以使用如下命令:
u-boot> loadb
(enter device tree source file from your TFTP server)
u-boot> fdt addr ${loadaddr}
u-boot> bootm
这些命令会从TFTP服务器下载设备树源文件,并将其编译成二进制格式,并将其加载到内存地址${loadaddr}处。然后使用"bootm"命令启动内核,并将设备树的地址传递给内核。在内核中,可以使用"of_find_node_by_name()"等函数来查找和解析设备树节点,以获取硬件信息并进行配置。
以上是一个简单的设备树示例及使用方法。实际应用中,设备树可能更加复杂,需要考虑多个设备节点、设备驱动程序等因素。