操作系统移植
- 操作系统移植指的是将一个发行版的 Linux 操作系统或纯内核(Kernel)移植到指定的硬件平台上,使其能够正常运行。
- 移植过程中,开发者需要根据目标硬件平台(如 CPU 架构、开发板等)的特定要求,进行配置、编译和调试。
- 操作系统与内核的区别:
- 操作系统:通常指的是一个完整的发行版(如 Ubuntu、Debian),包括内核、用户空间程序、系统服务和应用程序。操作系统的镜像文件一般较大,如 .iso 文件大约 4G 左右。
- 内核(Kernel):纯正的Linux系统,内核是操作系统的核心部分,负责管理系统资源和硬件交互。纯内核镜像通常较小,例如编译后的 uImage 镜像大约为 3MB。
编译、移植 Linux 内核
- 移植 Linux 内核的过程一般包括获取内核源码–》配置硬件相关的参数–》编译生成内核镜像–》以及在目标开发板上启动内核。
- 获取内核源码,在纯Linux环境下解压
tar -xvf linux-3.14.tar.xz
解压后,将得到一个顶层目录 linux-3.14,进入该目录,后续的编译和移植工作都将在这个目录下进行。
- 内核源码目录结构
Linux 内核源码的目录结构分为 平台相关 和 平台无关 两部分。理解这些目录结构有助于定位你需要修改或查看的文件。类似于U-boot
- 平台相关
- arch/arm/mach-exynos/:包含与三星 Exynos SoC(如 fs4412)相关的代码,如 CPU 初始化、时钟设置等。
- arch/arm/boot/dts/exynos4412-origen.dts:设备树文件,描述硬件信息。你可以基于 exynos4412-origen.dts 模板修改,适配 fs4412 的硬件
- arch/arm/configs/exynos_defconfig:默认配置文件,定义内核与硬件相关的选项。在编译内核时需要加载该配置,启用开发板相关驱动。
- 平台无关
- Documentation/:内核相关的文档,包含开发和使用内核的说明。
- init/:内核初始化代码,负责内核启动时的初始化操作。
- drivers/:内核中的驱动程序代码,用于支持不同的硬件设备。
- fs/:与文件系统相关的代码,支持多种文件系统(如 FAT、EXT4 等)。
- kernel/:内核的核心功能代码,包括调度、信号处理、定时器等。
- mm/:内存管理相关代码,负责虚拟内存、物理内存的管理。
- 配置内核
将目标板子的配置文件复制为 .config,这是 Linux 内核编译时使用的主配置文件。在这里,我们使用 Exynos 平台的配置文件
cp arch/arm/configs/exynos_defconfig .config
//这一步将 exynos_defconfig 的默认配置复制到 .config 中,成为编译时的实际配置文件。
加载完配置文件后,可以通过 menuconfig 进一步修改和调整配置
sudo apt-get install libncurses5-dev
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- menuconfig
//在这个配置界面中,你可以启用或禁用一些功能,例如文件系统支持、设备驱动等。
- 编译内核
加载并配置好内核之后,接下来就是编译内核。编译过程会根据配置生成内核镜像(例如 uImage 或 zImage),可以通过 make 命令进行编译。
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- -j4
//-j4 表示使用 4 个 CPU 核心进行并行编译,加快编译速度。
编译完成后,内核镜像会生成在 arch/arm/boot/ 目录下,常见的文件包括:
- zImage:压缩后的内核镜像,常用于某些启动引导程序。
- uImage:通过 U-Boot 使用的内核镜像,带有启动头。
如果需要生成 uImage,可以运行以下命令
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- uImage
- 将内核移植到开发板
编译完成后,需要将生成的内核镜像(uImage 或 zImage)移植到开发板上。常见的方法包括通过 TFTP、SD 卡 或 JTAG 进行内核镜像的传输和启动。
-
通过 TFTP 启动内核:
- 设置 TFTP 服务器,并将生成的 uImage 放置在 TFTP 根目录中。
- 配置开发板网络,在 U-Boot 中配置 IP 地址和 TFTP 服务器地址:
setenv ipaddr 192.168.1.100 # 开发板的 IP 地址 setenv serverip 192.168.1.1 # TFTP 服务器的 IP 地址
- 通过 TFTP 下载内核镜像
tftp 0x42000000 uImage # 将 uImage 下载到开发板内存地址 0x42000000
- 启动内核
bootm 0x42000000 # 启动内核镜像
网卡移植实验
- 编译内核的操作需要在 顶层目录(例如 linux-3.14)下进行,整个编译和移植过程涉及配置、编译和生成适用于 U-Boot 启动的 uImage 文件。
- 实验环境
- 主机:ubuntu
- 开发板:FS4412平台
- 交叉编译工具:arm-none-linux-gnueabi-gcc
1. 修改内核文件:内核在启动时会忽略未使用的时钟,保持它们开启
- 在嵌入式系统中,网卡或其他外设通常依赖时钟信号进行正常工作。如果内核在启动过程中检测到某些时钟未被使用,它会默认关闭这些时钟以节省功耗。
- 在设备驱动加载之前,网卡的时钟可能暂时处于未使用状态。如果时钟在内核启动时被关闭,网卡设备可能无法正常初始化或工作,因为驱动程序依赖这个时钟信号。
- 在网卡移植实验中,将 clk_ignore_unused 设置为 true 是为了确保网卡的时钟信号不会被关闭,从而使网卡能够正常工作。
- 具体步骤
- vim driver/clk/clk.c
- 修改
static bool clk_ignore_unused;
为static bool clk_ignore_unused = true;
2. 复制默认配置文件
将 Exynos 平台的默认内核配置文件复制到当前目录,并命名为 .config,为后续的内核配置和编译做好准备。
cp arch/arm/configs/exynos_defconfig .config
3. 进入内核配置界面
图形化工具
sudo apt-get install libncurses5-dev//安装依赖图形化配置工具
make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
1.启用网络支持和相关协议
[*] Networking support --->
Networking options --->
<*> Packet socket
<*> Unix domain sockets
[*] TCP/IP networking
[*] IP: kernel level autoconfiguration
2. 启用以太网驱动:
Device Drivers → Network device support → 启用 DM9000 Ethernet support
3. 启用 NFS(Network File System) 客户端支持
这些配置允许设备通过网络访问远程文件系统并将其用作根文件系统。
File systems —> Network File Systems (NEW) —>选择如下
4. 然后点击save即可
4. 创建fs4412的设备树文件
设备树(Device Tree)文件用于描述硬件信息。对于 fs4412 开发板,可以基于 exynos4412-origen.dts
1. 创建设备树文件。
cp arch/arm/boot/dts/exynos4412-origen.dts arch/arm/boot/dts/exynos4412-fs4412.dts
编辑 arch/arm/boot/dts/Makefile,在设备树编译目标下添加 exynos4412-fs4412.dtb
exynos4412-origen.dtb \
exynos4412-fs4412.dtb \
2. 修改设备树文件
根据你的硬件需求修改 arch/arm/boot/dts/exynos4412-fs4412.dts 文件。以 DM9000 网卡为例,设备树中描述了网卡的寄存器地址、相关中断等。
在设备树第34行添加代码
srom-cs1@5000000 { // 定义 SROM CS1 引脚的设备,基地址为 0x5000000
compatible = "simple-bus"; // 该设备与 "simple-bus" 兼容,允许子设备挂载
#address-cells = <1>; // 地址单元数为 1,子设备的地址用 1 个单元表示
#size-cells = <1>; // 大小单元数为 1,子设备的大小用 1 个单元表示
reg = <0x5000000 0x1000000>; // 设备的地址范围为 0x5000000,大小为 0x1000000 (16 MB)
ranges; // 表示地址空间映射,省略时表示地址直接映射
ethernet@5000000 { // 定义连接在 SROM CS1 的以太网设备,基地址为 0x5000000
compatible = "davicom,dm9000"; // 该设备与 "davicom,dm9000" 以太网控制器驱动兼容
reg = <0x5000000 0x2 0x5000004 0x2>; // 定义两个寄存器范围,0x5000000 大小 2 字节 (数据寄存器),0x5000004 大小 2 字节 (控制寄存器)
interrupt-parent = <&gpx0>; // 中断父节点,表示使用 gpx0 控制器管理中断
interrupts = <6 4>; // 中断号为 6,触发类型为 4(边沿触发,具体类型根据系统定义)
davicom,no-eeprom; // 指定设备没有 EEPROM(用于存储 MAC 地址)
mac-address = [00 0a 2d a6 55 a2]; // 设备的 MAC 地址,指定为 00:0a:2d:a6:55:a2
};
};
3. 编译设备树,生成 .dtb 文件
编译完成后,exynos4412-fs4412.dtb 文件会生成在 arch/arm/boot/dts/ 目录下。
5. 编译内核和设备树
make uImage ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
make dtbs ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
6. 测试与验证
编译完成后,将生成的 uImage 和设备树文件通过 TFTP 传输到开发板,并在 U-Boot 中启动。
-
通过 TFTP 启动内核和设备树
- 将生成的 uImage 和 exynos4412-fs4412.dtb 文件复制到 TFTP 服务器的目录 /tftpboot/
sudo cp arch/arm/boot/uImage /tftpboot/ sudo cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
- 在开发板上,通过 U-Boot 加载并启动内核
setenv ipaddr 192.168.1.100 # 开发板 IP setenv serverip 192.168.1.1 # TFTP 服务器 IP tftp 0x42000000 uImage # 下载内核到内存 tftp 0x43000000 exynos4412-fs4412.dtb # 下载设备树文件 bootm 0x42000000 - 0x43000000 # 启动内核并加载设备树
私有LED驱动的移植
- 实验目的
- 理解如何编译和移植私有 LED 驱动程序到内核中
- 掌握如何在用户空间通过应用程序控制 LED 设备。
- 实验环境
- 主机:Ubuntu 12.04 发行版
- 目标机:FS4412 平台(嵌入式开发板)
- 交叉编译工具:arm-none-linux-gnueabi-gcc
具体步骤
1. 添加驱动文件
将实验中的 LED 驱动代码 fs4412_led_drv.c 拷贝到内核源码的 drivers/char/ 目录下。此驱动程序控制 FS4412 板上的 LED。
cp Led_test/fs4412_led_drv.c linux/drivers/char/
2. 修改 drivers/char/Kconfig 文件
为了让新驱动在内核的配置菜单中可选,需要在 drivers/char/Kconfig 文件中添加相应的配置项。
vim drivers/char/Kconfig
在 menu “Character devices” 下,添加如下配置项
menu "Character devices"
config FS4412_LED #定义一个配置项,名字为 FS4412_LED
tristate "FS4412 LED Device Support" # 配置类型为 tristate(可选 y、m、n),用于启用 FS4412 开发板上的 LED 设备支持
depends on ARCH_EXYNOS4 # 限制驱动只能在 Exynos4 平台上使用
help #帮助文本,显示在配置界面中,帮助用户了解此配置项的功能
Support for LED device on FS4412 development board.
3. 修改 drivers/char/Makefile
在 Makefile 中添加编译驱动的规则。打开 drivers/char/Makefile,在文件末尾添加以下内容:
obj-$(CONFIG_FS4412_LED) += fs4412_led_drv.o
//CONFIG_FS4412_LED:这个宏由 menuconfig 中的选项生成。如果在配置中选择了该驱动,内核会根据 Kconfig 中的配置决定是否编译该驱动。
//fs4412_led_drv.o:该文件是编译生成的目标文件。
4. 编译应用程序
-
驱动编写完成后,还需要编写并编译一个应用程序,用来测试驱动程序对 LED 的控制。应用程序通常使用设备文件(如 /dev/led)与内核驱动通信。
- 将应用程序 fs4412_led_app.c 拷贝到任意目录。
- 使用交叉编译工具编译该应用程序
arm-none-linux-gnueabi-gcc fs4412_led_app.c -o fs4412_led_app
- 将生成的应用程序 fs4412_led_app 拷贝到目标板的文件系统中(例如通过 NFS 传输到 /source/rootfs)
cp fs4412_led_app /source/rootfs
5.编译LED驱动到内核
5.1 使用 menuconfig 配置内核
为了将 LED 驱动编译到内核中,需要使用 menuconfig 选择驱动支持。
make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
进入配置界面后,设置保存后退出
Device Drivers --->
Character devices --->
<*> FS4412 LED Device Support
5.2 编译内核并生成 uImage
make uImage ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- -j4
将生成的 uImage 文件拷贝到 TFTP 服务器的目录(以便开发板可以通过 TFTP 加载)
cp arch/arm/boot/uImage /tftpboot
5.3 启动开发板并加载内核
在开发板上,通过 U-Boot 加载新编译的内核镜像。启动开发板并进入 U-Boot,运行以下命令加载内核
tftp 0x41000000 uImage
bootm 0x41000000
5.4 创建设备节点
在开发板上,需要为 LED 驱动创建设备节点
mknod /dev/led c 500 0
5.5 运行测试程序
现在可以运行交叉编译的应用程序来测试 LED 驱动
./fs4412_led_app
观察 LED 的响应情况,验证驱动是否工作正常。
6. 编译LED驱动为模块
除了将驱动编译到内核中,还可以选择将 LED 驱动编译为模块,便于动态加载和卸载。这个过程类似于编译内核驱动,但最终生成的是 .ko 模块文件。
6.1 修改配置为模块
在 menuconfig 中,将 LED 驱动配置为模块( 表示模块)
make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
导航路径
Device Drivers --->
Character devices --->
<M> FS4412 LED Device Support
编译内核和模块
make uImage modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- -j4
编译完成后,拷贝生成的 uImage 和驱动模块到目标设备:
cp arch/arm/boot/uImage /tftpboot/
cp drivers/char/fs4412_led_drv.ko /source/rootfs/
6.3 启动设备并加载模块
通过 TFTP 启动设备后,可以使用 insmod 命令加载 LED 驱动模块:
insmod /source/rootfs/fs4412_led_drv.ko
在开发板上创建节点,如果有冲突的删除掉文件重新创建
mknod /dev/led c 500 0
6.4运行测试程序
./fs4412_led_app
实验总结
- 驱动文件的集成:通过修改 Kconfig 和 Makefile 文件,将驱动集成到内核中,并在 menuconfig 中显示配置项。
- 驱动的编译方式:可以选择将驱动编译到内核中(内建)或编译为模块(动态加载)。模块的灵活性更高,便于在需要时加载。
- 测试程序的编译与执行:通过交叉编译工具编译应用程序,并将其放置到目标文件系统中,用于与驱动通信。
- 设备节点:通过 mknod 创建字符设备节点,作为应用程序与驱动的通信接口。
- 模块加载与验证:如果驱动编译为模块,使用 insmod 加载驱动模块,使用 dmesg 查看系统日志,检查驱动加载是否正常。
可能存在的移植错误情况
- 由于硬件差异,启动在中途失败。通常启动失败的日志会指出设备树或驱动无法正确初始化的设备。
- 分析原因:硬件差异与代码移植
- 分析硬件差异
- 移植代码
- 修改设备树
- 继续调试启动
- 私有驱动
- 驱动代码的获取
- 获取第三方驱动代码,通常是设备厂商提供的源代码。
- 将驱动代码放置到内核源代码中的合适位置,例如 drivers/char/。
- 添加驱动到内核
- 修改内核的 Kconfig 和 Makefile,将第三方驱动集成到内核编译系统中:
- 在 Kconfig 中添加新驱动的选项。
- 在 Makefile 中添加驱动文件的编译规则。
- 使用 menuconfig 选择驱动:
- 通过 make menuconfig,在内核配置菜单中启用该驱动。
- 编译内核:
- 重新编译内核,生成包含第三方驱动的内核映像(uImage)。
- 也可以选择将驱动编译为模块(.ko 文件)。
- 驱动加载与测试:
- 通过加载模块(insmod)或编译到内核(内建)的方式,使驱动能够工作。
- 测试驱动是否能够正确控制设备。
- 驱动代码的获取