# 安装交叉编译工具链
sudo apt update
sudo apt install gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf
sudo apt install binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf
sudo apt install qemu-system-arm qemu-user-static
# 验证安装
aarch64-linux-gnu-gcc --version
arm-linux-gnueabihf-gcc --version
# 创建工作目录
mkdir -p ~/embedded_learning/{kernel,rootfs,bootloader,projects}
cd ~/embedded_learning
创建 hello.c:
#include<stdio.h>
intmain() {
printf("Hello from ARM64!\n");
return0;
}
编译和测试:
# 编译ARM64版本
aarch64-linux-gnu-gcc -static -o hello_arm64 hello.c
# 编译ARM32版本
arm-linux-gnueabihf-gcc -static -o hello_arm32 hello.c
# 查看文件类型
file hello_arm64 hello_arm32
# 使用QEMU用户态模拟运行
qemu-aarch64-static ./hello_arm64
qemu-arm-static ./hello_arm32
创建 cross_module.c:
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
staticint __init cross_init(void)
{
pr_info("Cross-compiled module loaded on %s\n",
IS_ENABLED(CONFIG_ARM64) ? "ARM64" : "ARM32");
return0;
}
staticvoid __exit cross_exit(void)
{
pr_info("Cross-compiled module unloaded\n");
}
module_init(cross_init);
module_exit(cross_exit);
创建 Makefile:
obj-m += cross_module.o
# 设置交叉编译
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
# 内核源码路径(需要ARM64内核源码)
KDIR ?= ~/embedded_learning/kernel/linux-6.6
all:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
cd ~/embedded_learning/kernel
# 下载内核
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz
tar xf linux-6.6.tar.xz
cd linux-6.6
# 设置环境变量
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
# 使用默认配置
make defconfig
# 或使用menuconfig定制
make menuconfig
# 关键配置项
make menuconfig
# General setup
# [*] Support for paging of anonymous memory (swap)
# [*] System V IPC
# [*] POSIX Message Queues
# Platform selection (ARM64)
# [*] ARMv8 based platforms
# [*] QEMU ARM Virtual Machine
# Device Drivers
# [*] Virtio drivers
# [*] PCI driver for virtio devices
# [*] Virtio block driver
# [*] Virtio network driver
# [*] Virtio console
# [*] Block devices
# [*] Virtio block driver
# File systems
# [*] Ext4 filesystem
# [*] Tmpfs support
# Pseudo filesystems
# [*] /proc file system support
# [*] sysfs file system support
# 启用调试选项
# Kernel hacking
# [*] Kernel debugging
# [*] Debug filesystem
# 编译内核和模块
make -j$(nproc)
# 编译结果
lsarch/arm64/boot/
# Image - 内核镜像
# Image.gz - 压缩镜像
# 编译设备树
make dtbs
lsarch/arm64/boot/dts/
# 设备树文件(.dtb)
cd ~/embedded_learning/bootloader
# 下载U-Boot
git clone https://github.com/u-boot/u-boot.git --depth=1
cd u-boot
# 设置环境
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
# 配置(QEMU ARM64虚拟机)
make qemu_arm64_defconfig
# 编译
make -j$(nproc)
# 产物
ls u-boot.bin
# 使用QEMU启动U-Boot
qemu-system-aarch64 -M virt -cpu cortex-a72 -m 1024 \
-bios u-boot.bin -nographic
# U-Boot常用命令
help# 帮助
printenv# 打印环境变量
setenv var value # 设置变量
saveenv # 保存环境变量
# 内存操作
md 0x40000000 0x10 # 显示内存
mw 0x40000000 0x12345678 # 写内存
# 加载文件
fatload mmc 0:1 0x40000000 Image
# 或
tftp 0x40000000 Image
# 启动内核
booti 0x40000000 - 0x45000000
# 参数: kernel_addr - initrd_addr fdt_addr
# 退出QEMU: Ctrl+A, X
# 设置启动命令
setenv bootcmd 'fatload mmc 0:1 0x40000000 Image; fatload mmc 0:1 0x45000000 virt.dtb; booti 0x40000000 - 0x45000000'
# 设置内核参数
setenv bootargs 'console=ttyAMA0 root=/dev/vda rw rootwait'
# 保存
saveenv
cd ~/embedded_learning/rootfs
# 下载BusyBox
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
# 配置
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 关键配置:
# Settings -> Build static binary (no shared libs) = y
# Settings -> Cross compiler prefix = aarch64-linux-gnu-
# 编译安装
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- install CONFIG_PREFIX=../rootfs_arm64
cd ~/embedded_learning/rootfs/rootfs_arm64
# 创建目录结构
mkdir -p {proc,sys,dev,etc,tmp,var,root,home}
mkdir -p etc/init.d
mkdir -p dev/pts
mkdir -p var/{log,run,lock}
# 创建设备节点
sudo mknod -m 666 dev/null c 1 3
sudo mknod -m 666 dev/zero c 1 5
sudo mknod -m 666 dev/random c 1 8
sudo mknod -m 666 dev/urandom c 1 9
sudo mknod -m 666 dev/tty c 5 0
sudo mknod -m 600 dev/console c 5 1
# 创建/etc/passwd
cat > etc/passwd << 'EOF'
root:x:0:0:root:/root:/bin/sh
EOF
# 创建/etc/group
cat > etc/group << 'EOF'
root:x:0:
EOF
# 创建/etc/inittab
cat > etc/inittab << 'EOF'
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
EOF
# 创建启动脚本
cat > etc/init.d/rcS << 'EOF'
#!/bin/sh
echo"=== Embedded Linux Booting ==="
# 挂载文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /tmp
mount -t devpts devpts /dev/pts
# 设置主机名
hostname embedded
echo"System ready!"
EOF
chmod +x etc/init.d/rcS
# 创建/etc/fstab
cat > etc/fstab << 'EOF'
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
EOF
cd ~/embedded_learning/rootfs
# 创建ext4镜像
ddif=/dev/zero of=rootfs.ext4 bs=1M count=64
mkfs.ext4 rootfs.ext4
# 挂载并复制文件
mkdir -p mnt
sudo mount rootfs.ext4 mnt
sudo cp -a rootfs_arm64/* mnt/
sudo umount mnt
# 或创建initramfs
cd rootfs_arm64
find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz
cd ~/embedded_learning
# 使用rootfs镜像启动
qemu-system-aarch64 \
-M virt \
-cpu cortex-a72 \
-m 1024 \
-kernel kernel/linux-6.6/arch/arm64/boot/Image \
-append "console=ttyAMA0 root=/dev/vda rw rootwait" \
-drive file=rootfs/rootfs.ext4,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-nographic
# 使用initramfs启动
qemu-system-aarch64 \
-M virt \
-cpu cortex-a72 \
-m 1024 \
-kernel kernel/linux-6.6/arch/arm64/boot/Image \
-initrd rootfs/initramfs.cpio.gz \
-append "console=ttyAMA0 rdinit=/linuxrc" \
-nographic
# 退出: Ctrl+A, X
# 用户态网络(简单,可访问外网)
qemu-system-aarch64 \
-M virt -cpu cortex-a72 -m 1024 \
-kernel kernel/linux-6.6/arch/arm64/boot/Image \
-append "console=ttyAMA0 root=/dev/vda rw rootwait" \
-drive file=rootfs/rootfs.ext4,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-device,netdev=net0 \
-nographic
# Guest内配置网络
ip linkset eth0 up
ip addr add 10.0.2.15/24 dev eth0
ip route add default via 10.0.2.2
# 从主机SSH到Guest
ssh -p 2222 root@localhost
# 启动QEMU并等待GDB连接
qemu-system-aarch64 \
-M virt -cpu cortex-a72 -m 1024 \
-kernel kernel/linux-6.6/arch/arm64/boot/Image \
-append "console=ttyAMA0 nokaslr" \
-nographic -s -S
# 另一个终端启动GDB
aarch64-linux-gnu-gdb kernel/linux-6.6/vmlinux
(gdb) target remote :1234
(gdb) b start_kernel
(gdb) c
创建 simple.dts:
/dts-v1/;
/{
compatible="myvendor,myboard";
#address-cells = <2>;
#size-cells = <2>;
chosen{
bootargs="console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait";
stdout-path="serial0:115200n8";
};
memory@40000000{
device_type="memory";
reg=<0x00x400000000x00x40000000>;// 1GB @ 0x40000000
};
cpus{
#address-cells = <1>;
#size-cells = <0>;
cpu@0{
device_type="cpu";
compatible="arm,cortex-a72";
reg=<0>;
};
};
soc{
compatible="simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
// 串口
uart0:serial@9000000{
compatible="ns16550a";
reg=<0x00x90000000x00x1000>;
interrupts=<014>;
clock-frequency=<1843200>;
};
// GPIO控制器
gpio0:gpio@9010000{
compatible="myvendor,my-gpio";
reg=<0x00x90100000x00x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
// LED设备
leds{
compatible="gpio-leds";
led0{
label="status";
gpios=<&gpio000>;// GPIO0, active high
default-state="off";
};
led1{
label="heartbeat";
gpios=<&gpio010>;
linux,default-trigger="heartbeat";
};
};
// I2C控制器
i2c0:i2c@9020000{
compatible="myvendor,my-i2c";
reg=<0x00x90200000x00x1000>;
#address-cells = <1>;
#size-cells = <0>;
clock-frequency=<100000>;
// I2C设备
eeprom@50{
compatible="atmel,24c02";
reg=<0x50>;
};
rtc@68{
compatible="dallas,ds1307";
reg=<0x68>;
};
};
// SPI控制器
spi0:spi@9030000{
compatible="myvendor,my-spi";
reg=<0x00x90300000x00x1000>;
#address-cells = <1>;
#size-cells = <0>;
flash@0{
compatible="jedec,spi-nor";
reg=<0>;
spi-max-frequency=<50000000>;
};
};
};
};
# 编译dts为dtb
dtc -I dts -O dtb -o simple.dtb simple.dts
# 反编译dtb为dts
dtc -I dtb -O dts -o output.dts simple.dtb
# 查看dtb内容
fdtdump simple.dtb
创建 dt_driver.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/of.h>
#include<linux/of_device.h>
MODULE_LICENSE("GPL");
structmy_device_data {
void __iomem *base;
int irq;
u32 clock_freq;
};
staticintmy_probe(struct platform_device *pdev)
{
structmy_device_data *data;
structresource *res;
structdevice_node *np = pdev->dev.of_node;
pr_info("my_driver: Probing %s\n", pdev->name);
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
// 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res) {
data->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->base))
return PTR_ERR(data->base);
pr_info(" Registers: 0x%llx - 0x%llx\n", res->start, res->end);
}
// 获取中断
data->irq = platform_get_irq(pdev, 0);
if (data->irq > 0)
pr_info(" IRQ: %d\n", data->irq);
// 读取设备树属性
if (of_property_read_u32(np, "clock-frequency", &data->clock_freq) == 0)
pr_info(" Clock: %u Hz\n", data->clock_freq);
platform_set_drvdata(pdev, data);
pr_info("my_driver: Probe successful\n");
return0;
}
staticintmy_remove(struct platform_device *pdev)
{
pr_info("my_driver: Remove\n");
return0;
}
staticconststructof_device_idmy_of_match[] = {
{ .compatible = "myvendor,my-gpio" },
{ .compatible = "myvendor,my-i2c" },
{ .compatible = "myvendor,my-spi" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
staticstructplatform_drivermy_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
cd ~/embedded_learning
# 下载Buildroot
git clone https://github.com/buildroot/buildroot.git --depth=1
cd buildroot
# 配置
make menuconfig
Target options
Target Architecture: AArch64 (little endian)
Target Architecture Variant: cortex-A72
Toolchain
Toolchain type: External toolchain
Toolchain: Linaro AArch64
System configuration
System hostname: embedded
System banner: Welcome to Embedded Linux
Root password: root
/bin/sh: busybox
Kernel
[*] Linux Kernel
Kernel version: Latest LTS
Kernel configuration: Use defconfig
Defconfig name: defconfig
Target packages
[*] BusyBox
Networking applications
[*] dropbear (SSH)
[*] iperf3
System tools
[*] htop
Filesystem images
[*] ext4 root filesystem
[*] cpio the root filesystem
# 编译(首次可能需要1-2小时)
make -j$(nproc)
# 产物位置
ls output/images/
# Image - 内核
# rootfs.ext4 - 根文件系统
# rootfs.cpio - initramfs
# 使用QEMU测试
qemu-system-aarch64 \
-M virt -cpu cortex-a72 -m 1024 \
-kernel output/images/Image \
-append "console=ttyAMA0 root=/dev/vda rw" \
-drive file=output/images/rootfs.ext4,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-nographic
创建 virt_gpio.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/gpio/driver.h>
#include<linux/platform_device.h>
MODULE_LICENSE("GPL");
#define NUM_GPIOS 8
structvirt_gpio {
structgpio_chipgc;
u8 data;
u8 direction; // 0=output, 1=input
};
staticintvirt_gpio_get(struct gpio_chip *gc, unsigned offset)
{
structvirt_gpio *vg = gpiochip_get_data(gc);
return (vg->data >> offset) & 1;
}
staticvoidvirt_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
{
structvirt_gpio *vg = gpiochip_get_data(gc);
if (value)
vg->data |= (1 << offset);
else
vg->data &= ~(1 << offset);
pr_info("virt_gpio: Set GPIO%d = %d (data=0x%02x)\n",
offset, value, vg->data);
}
staticintvirt_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
structvirt_gpio *vg = gpiochip_get_data(gc);
vg->direction |= (1 << offset);
return0;
}
staticintvirt_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int value)
{
structvirt_gpio *vg = gpiochip_get_data(gc);
vg->direction &= ~(1 << offset);
virt_gpio_set(gc, offset, value);
return0;
}
staticintvirt_gpio_probe(struct platform_device *pdev)
{
structvirt_gpio *vg;
int ret;
vg = devm_kzalloc(&pdev->dev, sizeof(*vg), GFP_KERNEL);
if (!vg) return -ENOMEM;
vg->gc.label = "virt-gpio";
vg->gc.ngpio = NUM_GPIOS;
vg->gc.base = -1; // 动态分配
vg->gc.parent = &pdev->dev;
vg->gc.owner = THIS_MODULE;
vg->gc.get = virt_gpio_get;
vg->gc.set = virt_gpio_set;
vg->gc.direction_input = virt_gpio_direction_input;
vg->gc.direction_output = virt_gpio_direction_output;
ret = devm_gpiochip_add_data(&pdev->dev, &vg->gc, vg);
if (ret) return ret;
platform_set_drvdata(pdev, vg);
pr_info("virt_gpio: Registered %d GPIOs\n", NUM_GPIOS);
return0;
}
staticstructplatform_drivervirt_gpio_driver = {
.probe = virt_gpio_probe,
.driver = { .name = "virt-gpio" },
};
staticstructplatform_device *pdev;
staticint __init virt_gpio_init(void)
{
int ret;
ret = platform_driver_register(&virt_gpio_driver);
if (ret) return ret;
pdev = platform_device_register_simple("virt-gpio", -1, NULL, 0);
if (IS_ERR(pdev)) {
platform_driver_unregister(&virt_gpio_driver);
return PTR_ERR(pdev);
}
return0;
}
staticvoid __exit virt_gpio_exit(void)
{
platform_device_unregister(pdev);
platform_driver_unregister(&virt_gpio_driver);
}
module_init(virt_gpio_init);
module_exit(virt_gpio_exit);
# 加载模块
sudo insmod virt_gpio.ko
# 查看GPIO
cat /sys/kernel/debug/gpio
# 导出GPIO
echo 0 > /sys/class/gpio/export
# 设置方向
echo out > /sys/class/gpio/gpio0/direction
# 设置值
echo 1 > /sys/class/gpio/gpio0/value
echo 0 > /sys/class/gpio/gpio0/value
# 取消导出
echo 0 > /sys/class/gpio/unexport
obj-m += cross_module.o
obj-m += dt_driver.o
obj-m += virt_gpio.o
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
KDIR ?= ~/embedded_learning/kernel/linux-6.6
all:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
# 本地测试(x86)
local:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
# 下载树莓派OS镜像
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-03-15/2024-03-15-raspios-bookworm-arm64-lite.img.xz
# 解压
xz -d 2024-03-15-raspios-bookworm-arm64-lite.img.xz
# 烧录到SD卡(替换/dev/sdX为实际设备)
sudo ddif=2024-03-15-raspios-bookworm-arm64-lite.img of=/dev/sdX bs=4M status=progress
sync
# 启用SSH(挂载boot分区后)
sudo mount /dev/sdX1 /mnt
sudo touch /mnt/ssh
sudo umount /mnt
# 克隆树莓派内核
git clone --depth=1 https://github.com/raspberrypi/linux.git rpi-linux
cd rpi-linux
# 安装交叉编译工具链
sudo apt install crossbuild-essential-arm64
# 配置(树莓派4/5 64位)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
# 自定义配置
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 编译
make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
# 安装模块到临时目录
mkdir -p ../rpi-modules
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=../rpi-modules modules_install
# 挂载SD卡分区
sudo mount /dev/sdX1 /mnt/boot
sudo mount /dev/sdX2 /mnt/rootfs
# 备份原内核
sudo cp /mnt/boot/kernel8.img /mnt/boot/kernel8.img.bak
# 复制新内核
sudo cparch/arm64/boot/Image /mnt/boot/kernel8.img
# 复制设备树
sudo cparch/arm64/boot/dts/broadcom/*.dtb /mnt/boot/
sudo cparch/arm64/boot/dts/overlays/*.dtb* /mnt/boot/overlays/
# 复制模块
sudo cp -r ../rpi-modules/lib/modules/* /mnt/rootfs/lib/modules/
# 卸载
sudo umount /mnt/boot /mnt/rootfs
创建 rpi_gpio_led.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/gpio.h>
#include<linux/timer.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("RPi GPIO LED Blink");
#define LED_GPIO 17 // BCM GPIO17 (Pin 11)
staticstructtimer_listblink_timer;
staticint led_state = 0;
staticvoidblink_timer_callback(struct timer_list *t)
{
led_state = !led_state;
gpio_set_value(LED_GPIO, led_state);
mod_timer(&blink_timer, jiffies + HZ); // 1秒后再触发
}
staticint __init rpi_led_init(void)
{
int ret;
ret = gpio_request(LED_GPIO, "led_gpio");
if (ret) {
pr_err("Failed to request GPIO %d\n", LED_GPIO);
return ret;
}
gpio_direction_output(LED_GPIO, 0);
timer_setup(&blink_timer, blink_timer_callback, 0);
mod_timer(&blink_timer, jiffies + HZ);
pr_info("RPi LED blink started on GPIO %d\n", LED_GPIO);
return0;
}
staticvoid __exit rpi_led_exit(void)
{
del_timer_sync(&blink_timer);
gpio_set_value(LED_GPIO, 0);
gpio_free(LED_GPIO);
pr_info("RPi LED blink stopped\n");
}
module_init(rpi_led_init);
module_exit(rpi_led_exit);
创建 rpi_i2c_sensor.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/i2c.h>
#include<linux/device.h>
MODULE_LICENSE("GPL");
#define SENSOR_REG_ID 0x00
#define SENSOR_REG_DATA 0x01
structsensor_data {
structi2c_client *client;
structdevice *dev;
};
staticintsensor_read_reg(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
staticssize_tsensor_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
structsensor_data *data = dev_get_drvdata(dev);
int id = sensor_read_reg(data->client, SENSOR_REG_ID);
returnsprintf(buf, "0x%02x\n", id);
}
staticssize_tsensor_value_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
structsensor_data *data = dev_get_drvdata(dev);
int value = sensor_read_reg(data->client, SENSOR_REG_DATA);
returnsprintf(buf, "%d\n", value);
}
staticDEVICE_ATTR(sensor_id, 0444, sensor_id_show, NULL);
staticDEVICE_ATTR(sensor_value, 0444, sensor_value_show, NULL);
staticstructattribute *sensor_attrs[] = {
&dev_attr_sensor_id.attr,
&dev_attr_sensor_value.attr,
NULL,
};
staticstructattribute_groupsensor_attr_group = {
.attrs = sensor_attrs,
};
staticintsensor_probe(struct i2c_client *client)
{
structsensor_data *data;
int ret;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
data->client = client;
data->dev = &client->dev;
i2c_set_clientdata(client, data);
ret = sysfs_create_group(&client->dev.kobj, &sensor_attr_group);
if (ret) return ret;
dev_info(&client->dev, "I2C sensor probed at 0x%02x\n", client->addr);
return0;
}
staticvoidsensor_remove(struct i2c_client *client)
{
sysfs_remove_group(&client->dev.kobj, &sensor_attr_group);
dev_info(&client->dev, "I2C sensor removed\n");
}
staticconststructi2c_device_idsensor_id[] = {
{ "mysensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);
staticconststructof_device_idsensor_of_match[] = {
{ .compatible = "myvendor,mysensor" },
{ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
staticstructi2c_driversensor_driver = {
.driver = {
.name = "mysensor",
.of_match_table = sensor_of_match,
},
.probe = sensor_probe,
.remove = sensor_remove,
.id_table = sensor_id,
};
module_i2c_driver(sensor_driver);
┌─────────────────────────────────────────────────┐
│ Yocto Project │
├─────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Poky │ │ Layers │ │ BSP │ │
│ │ (参考发行) │ │ (元数据) │ │ (板级包) │ │
│ └───────────┘ └───────────┘ └───────────┘ │
├─────────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────┐ │
│ │ BitBake │ │
│ │ (构建引擎) │ │
│ └───────────────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Recipes │ │ Classes │ │ Configs │ │
│ │ (.bb) │ │ (.bbclass)│ │ (.conf) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────┘
# 安装依赖
sudo apt install gawk wget git diffstat unzip texinfo gcc \
build-essential chrpath socat cpio python3 python3-pip \
python3-pexpect xz-utils debianutils iputils-ping \
python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev \
xterm python3-subunit mesa-common-dev zstd liblz4-tool
# 克隆Poky(Yocto参考发行版)
git clone git://git.yoctoproject.org/poky -b scarthgap
cd poky
# 初始化构建环境
source oe-init-build-env build-rpi
编辑 conf/local.conf:
# 目标机器
MACHINE = "raspberrypi4-64"
# 并行构建
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8"
# 下载目录(避免重复下载)
DL_DIR = "${TOPDIR}/../downloads"
# 共享状态缓存
SSTATE_DIR = "${TOPDIR}/../sstate-cache"
# 添加功能
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
# 添加额外软件包
IMAGE_INSTALL:append = " openssh htop vim"
# 启用调试信息
EXTRA_IMAGE_FEATURES = "debug-tweaks tools-debug"
编辑 conf/bblayers.conf:
BBLAYERS ?= " \
/path/to/poky/meta \
/path/to/poky/meta-poky \
/path/to/poky/meta-yocto-bsp \
/path/to/meta-raspberrypi \
/path/to/meta-openembedded/meta-oe \
"
cd ..
git clone git://git.yoctoproject.org/meta-raspberrypi -b scarthgap
git clone https://github.com/openembedded/meta-openembedded.git -b scarthgap
cd build-rpi
bitbake-layers add-layer ../meta-raspberrypi
bitbake-layers add-layer ../meta-openembedded/meta-oe
创建 meta-mylayer/recipes-apps/myapp/myapp_1.0.bb:
SUMMARY = "My Custom Application"
DESCRIPTION = "A simple application for embedded Linux"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
SRC_URI = "file://myapp.c \
file://Makefile"
S = "${WORKDIR}"
do_compile() {
oe_runmake
}
do_install() {
install -d ${D}${bindir}
install -m 0755 myapp ${D}${bindir}
}
FILES:${PN} = "${bindir}/myapp"
创建源文件目录结构:
meta-mylayer/
├── conf/
│ └── layer.conf
├── recipes-apps/
│ └── myapp/
│ ├── myapp_1.0.bb
│ └── files/
│ ├── myapp.c
│ └── Makefile
创建 meta-mylayer/recipes-kernel/mymodule/mymodule_1.0.bb:
SUMMARY = "My Kernel Module"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-only;md5=801f80980d171dd6425610833a22dbe6"
inherit module
SRC_URI = "file://mymodule.c \
file://Makefile"
S = "${WORKDIR}"
RPROVIDES:${PN} += "kernel-module-mymodule"
# 构建最小镜像
bitbake core-image-minimal
# 构建带开发工具的镜像
bitbake core-image-full-cmdline
# 构建SDK
bitbake -c populate_sdk core-image-minimal
# 输出位置
ls tmp/deploy/images/raspberrypi4-64/
创建 spi_display.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/spi/spi.h>
#include<linux/of.h>
#include<linux/delay.h>
#include<linux/fb.h>
MODULE_LICENSE("GPL");
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
structdisplay_data {
structspi_device *spi;
structfb_info *fb;
u8 *buffer;
};
staticintdisplay_send_cmd(struct spi_device *spi, u8 cmd)
{
// 假设DC引脚在传输前设置为低
return spi_write(spi, &cmd, 1);
}
staticintdisplay_send_data(struct spi_device *spi, u8 *data, size_t len)
{
// 假设DC引脚在传输前设置为高
return spi_write(spi, data, len);
}
staticvoiddisplay_init_hw(struct spi_device *spi)
{
// OLED初始化序列示例
display_send_cmd(spi, 0xAE); // Display off
display_send_cmd(spi, 0xD5); // Set display clock
display_send_cmd(spi, 0x80);
display_send_cmd(spi, 0xA8); // Set multiplex ratio
display_send_cmd(spi, 0x3F);
display_send_cmd(spi, 0xD3); // Set display offset
display_send_cmd(spi, 0x00);
display_send_cmd(spi, 0x40); // Set start line
display_send_cmd(spi, 0xAF); // Display on
}
staticssize_tdisplay_write(struct file *file, constchar __user *buf,
size_t count, loff_t *ppos)
{
// Framebuffer写入处理
return count;
}
staticstructfb_opsdisplay_ops = {
.owner = THIS_MODULE,
.fb_write = display_write,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
staticintdisplay_probe(struct spi_device *spi)
{
structdisplay_data *data;
structfb_info *fb;
int ret;
data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
data->spi = spi;
spi_set_drvdata(spi, data);
// 配置SPI
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi->max_speed_hz = 10000000; // 10MHz
ret = spi_setup(spi);
if (ret) return ret;
display_init_hw(spi);
// 分配framebuffer
fb = framebuffer_alloc(sizeof(struct display_data), &spi->dev);
if (!fb) return -ENOMEM;
fb->fbops = &display_ops;
fb->var.xres = DISPLAY_WIDTH;
fb->var.yres = DISPLAY_HEIGHT;
fb->var.bits_per_pixel = 1;
fb->fix.line_length = DISPLAY_WIDTH / 8;
data->buffer = devm_kzalloc(&spi->dev,
DISPLAY_WIDTH * DISPLAY_HEIGHT / 8,
GFP_KERNEL);
fb->screen_base = data->buffer;
fb->screen_size = DISPLAY_WIDTH * DISPLAY_HEIGHT / 8;
ret = register_framebuffer(fb);
if (ret) {
framebuffer_release(fb);
return ret;
}
data->fb = fb;
dev_info(&spi->dev, "SPI display probed\n");
return0;
}
staticvoiddisplay_remove(struct spi_device *spi)
{
structdisplay_data *data = spi_get_drvdata(spi);
unregister_framebuffer(data->fb);
framebuffer_release(data->fb);
}
staticconststructof_device_iddisplay_of_match[] = {
{ .compatible = "myvendor,spi-display" },
{ }
};
MODULE_DEVICE_TABLE(of, display_of_match);
staticstructspi_driverdisplay_driver = {
.driver = {
.name = "spi-display",
.of_match_table = display_of_match,
},
.probe = display_probe,
.remove = display_remove,
};
module_spi_driver(display_driver);
创建 pwm_motor.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/pwm.h>
#include<linux/of.h>
MODULE_LICENSE("GPL");
structmotor_data {
structpwm_device *pwm;
unsignedint duty_cycle; // 0-100%
unsignedint period_ns;
};
staticssize_tspeed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
structmotor_data *data = dev_get_drvdata(dev);
returnsprintf(buf, "%u\n", data->duty_cycle);
}
staticssize_tspeed_store(struct device *dev,
struct device_attribute *attr,
constchar *buf, size_t count)
{
structmotor_data *data = dev_get_drvdata(dev);
unsignedint speed;
int ret;
ret = kstrtouint(buf, 10, &speed);
if (ret) return ret;
if (speed > 100) speed = 100;
data->duty_cycle = speed;
ret = pwm_config(data->pwm,
data->period_ns * speed / 100,
data->period_ns);
if (ret) return ret;
if (speed > 0)
pwm_enable(data->pwm);
else
pwm_disable(data->pwm);
return count;
}
staticDEVICE_ATTR_RW(speed);
staticintmotor_probe(struct platform_device *pdev)
{
structmotor_data *data;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
data->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(data->pwm)) {
dev_err(&pdev->dev, "Failed to get PWM\n");
return PTR_ERR(data->pwm);
}
// 默认20kHz PWM
data->period_ns = 50000; // 50us = 20kHz
data->duty_cycle = 0;
platform_set_drvdata(pdev, data);
ret = device_create_file(&pdev->dev, &dev_attr_speed);
if (ret) return ret;
dev_info(&pdev->dev, "PWM motor driver probed\n");
return0;
}
staticintmotor_remove(struct platform_device *pdev)
{
structmotor_data *data = platform_get_drvdata(pdev);
device_remove_file(&pdev->dev, &dev_attr_speed);
pwm_disable(data->pwm);
return0;
}
staticconststructof_device_idmotor_of_match[] = {
{ .compatible = "myvendor,pwm-motor" },
{ }
};
MODULE_DEVICE_TABLE(of, motor_of_match);
staticstructplatform_drivermotor_driver = {
.driver = {
.name = "pwm-motor",
.of_match_table = motor_of_match,
},
.probe = motor_probe,
.remove = motor_remove,
};
module_platform_driver(motor_driver);
设备树节点:
motor:motor@0{
compatible="myvendor,pwm-motor";
pwms=<&pwm050000>;// PWM0, 50us period
status="okay";
};
创建 gpio_irq_button.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/gpio/consumer.h>
#include<linux/interrupt.h>
#include<linux/input.h>
MODULE_LICENSE("GPL");
structbutton_data {
structgpio_desc *gpio;
structinput_dev *input;
int irq;
int key_code;
};
staticirqreturn_tbutton_irq_handler(int irq, void *dev_id)
{
structbutton_data *data = dev_id;
int state = gpiod_get_value(data->gpio);
input_report_key(data->input, data->key_code, !state);
input_sync(data->input);
return IRQ_HANDLED;
}
staticintbutton_probe(struct platform_device *pdev)
{
structbutton_data *data;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
// 获取GPIO
data->gpio = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
if (IS_ERR(data->gpio)) {
dev_err(&pdev->dev, "Failed to get GPIO\n");
return PTR_ERR(data->gpio);
}
// 获取中断号
data->irq = gpiod_to_irq(data->gpio);
if (data->irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ\n");
return data->irq;
}
// 创建input设备
data->input = devm_input_allocate_device(&pdev->dev);
if (!data->input) return -ENOMEM;
data->input->name = "gpio-button";
data->key_code = KEY_ENTER;
input_set_capability(data->input, EV_KEY, data->key_code);
ret = input_register_device(data->input);
if (ret) return ret;
// 请求中断
ret = devm_request_irq(&pdev->dev, data->irq, button_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpio-button", data);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ\n");
return ret;
}
platform_set_drvdata(pdev, data);
dev_info(&pdev->dev, "Button driver probed, IRQ=%d\n", data->irq);
return0;
}
staticconststructof_device_idbutton_of_match[] = {
{ .compatible = "myvendor,gpio-button" },
{ }
};
MODULE_DEVICE_TABLE(of, button_of_match);
staticstructplatform_driverbutton_driver = {
.driver = {
.name = "gpio-button",
.of_match_table = button_of_match,
},
.probe = button_probe,
};
module_platform_driver(button_driver);
设备树节点:
button:button@0{
compatible="myvendor,gpio-button";
button-gpios=<&gpio17 GPIO_ACTIVE_LOW>;
status="okay";
};
# 在内核命令行添加
initcall_debug printk.time=1
# 启动后分析
dmesg | grep "initcall" | sort -k2 -n -t'=' | tail -20
# 使用bootchart
sudo apt install bootchart pybootchartgui
# 重启后查看 /var/log/bootchart.png
# 使用systemd-analyze
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain
# 最小化内核配置
make tinyconfig # 极简配置起点
# 或从当前配置裁剪
make localmodconfig # 只保留当前使用的模块
# 禁用不需要的功能
scripts/config --disable DEBUG_INFO
scripts/config --disable PRINTK
scripts/config --disable BUG
scripts/config --disable KALLSYMS
scripts/config --disable FTRACE
scripts/config --disable KPROBES
# 启用快速启动选项
scripts/config --enable CC_OPTIMIZE_FOR_SIZE
scripts/config --enable EMBEDDED
scripts/config --enable EXPERT
创建 fast_init.c:
#include<linux/init.h>
#include<linux/module.h>
MODULE_LICENSE("GPL");
// 使用不同的初始化级别
// pure_initcall - 0
// core_initcall - 1
// postcore_initcall - 2
// arch_initcall - 3
// subsys_initcall - 4
// fs_initcall - 5
// device_initcall - 6
// late_initcall - 7
staticint __init early_init(void)
{
pr_info("Early init (core level)\n");
return0;
}
core_initcall(early_init);
staticint __init normal_init(void)
{
pr_info("Normal init (device level)\n");
return0;
}
device_initcall(normal_init);
staticint __init late_init(void)
{
pr_info("Late init\n");
return0;
}
late_initcall(late_init);
// 异步初始化
staticint __init async_init(void)
{
pr_info("Async init starting\n");
return0;
}
// 标记为可异步执行
module_init(async_init);
创建 deferred_probe.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/of.h>
MODULE_LICENSE("GPL");
staticintmydev_probe(struct platform_device *pdev)
{
structdevice *dependency;
// 检查依赖设备是否就绪
dependency = bus_find_device_by_name(&platform_bus_type,
NULL, "i2c-controller");
if (!dependency) {
dev_info(&pdev->dev, "Dependency not ready, deferring\n");
return -EPROBE_DEFER; // 请求稍后重试
}
put_device(dependency);
dev_info(&pdev->dev, "Device probed successfully\n");
return0;
}
staticconststructof_device_idmydev_of_match[] = {
{ .compatible = "myvendor,mydev" },
{ }
};
staticstructplatform_drivermydev_driver = {
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
.probe = mydev_probe,
};
module_platform_driver(mydev_driver);
# 内核压缩选项比较
# CONFIG_KERNEL_GZIP - 兼容性好,解压慢
# CONFIG_KERNEL_LZMA - 压缩率高,解压慢
# CONFIG_KERNEL_XZ - 压缩率高,解压较慢
# CONFIG_KERNEL_LZO - 压缩率低,解压快
# CONFIG_KERNEL_LZ4 - 压缩率低,解压最快
# 快速启动推荐LZ4
scripts/config --disable KERNEL_GZIP
scripts/config --enable KERNEL_LZ4
创建 boot_time_measure.c:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/proc_fs.h>
#include<linux/seq_file.h>
#include<linux/sched/clock.h>
MODULE_LICENSE("GPL");
static u64 module_load_time;
staticintboottime_show(struct seq_file *m, void *v)
{
u64 now = local_clock();
u64 uptime_ns = now;
u64 uptime_ms = uptime_ns / 1000000;
u64 module_time_ms = module_load_time / 1000000;
seq_printf(m, "=== Boot Time Analysis ===\n\n");
seq_printf(m, "Current uptime: %llu.%03llu seconds\n",
uptime_ms / 1000, uptime_ms % 1000);
seq_printf(m, "Module loaded at: %llu.%03llu seconds\n",
module_time_ms / 1000, module_time_ms % 1000);
// 从/proc/stat读取启动时间
seq_printf(m, "\n--- Optimization Tips ---\n");
seq_printf(m, "1. Use initcall_debug to find slow initcalls\n");
seq_printf(m, "2. Use CONFIG_KERNEL_LZ4 for faster decompression\n");
seq_printf(m, "3. Defer non-critical driver loading\n");
seq_printf(m, "4. Use async probe where possible\n");
seq_printf(m, "5. Minimize kernel size with localmodconfig\n");
return0;
}
staticintboottime_open(struct inode *inode, struct file *file)
{
return single_open(file, boottime_show, NULL);
}
staticconststructproc_opsboottime_ops = {
.proc_open = boottime_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
staticstructproc_dir_entry *proc_entry;
staticint __init boottime_init(void)
{
module_load_time = local_clock();
proc_entry = proc_create("boot_time_analysis", 0444, NULL, &boottime_ops);
pr_info("Boot time module loaded at %llu ns\n", module_load_time);
return proc_entry ? 0 : -ENOMEM;
}
staticvoid __exit boottime_exit(void)
{
proc_remove(proc_entry);
}
module_init(boottime_init);
module_exit(boottime_exit);
ARCH ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
KDIR ?= /path/to/linux-kernel
obj-m += rpi_gpio_led.o
obj-m += rpi_i2c_sensor.o
obj-m += spi_display.o
obj-m += pwm_motor.o
obj-m += gpio_irq_button.o
obj-m += boot_time_measure.o
all:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
-C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
# 本地编译(x86测试)
local:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
# 部署到树莓派
deploy:
scp *.ko pi@raspberrypi.local:~/modules/
# 安装到rootfs
install:
make ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
-C $(KDIR) M=$(PWD) \
INSTALL_MOD_PATH=$(DESTDIR) modules_install