Linux内核移植与调试指南
上电 → MiniLoader → U-Boot → Linux Kernel → 用户空间↑操作系统核心阶段名称主要功能1U-Boot加载内核镜像和设备树2初始化硬件、启动系统服务3Init进程启动用户空间程序4应用程序执法记录仪应用内核移植: 从配置、编译到烧录的完整流程调试方法: printk、ftrace、KGDB等工具的使用项目定制: 摄像头、5G、WiFi等驱动的集成驱动开发: 字符设
Linux内核移植与调试指南
文档信息
- 平台: RV1126B (ARM Cortex-A53, 64位)
- 内核版本: Linux 6.1
- 内核源码路径:
/home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
目录
一、Linux内核的作用
1.1 什么是Linux内核
Linux内核是操作系统的核心,在系统启动流程中的位置:
上电 → MiniLoader → U-Boot → Linux Kernel → 用户空间
↑
操作系统核心
启动流程详解:
| 阶段 | 名称 | 主要功能 |
|---|---|---|
| 1 | U-Boot | 加载内核镜像和设备树 |
| 2 | Linux Kernel | 初始化硬件、启动系统服务 |
| 3 | Init进程 | 启动用户空间程序 |
| 4 | 应用程序 | 执法记录仪应用 |
1.2 内核的核心职责
1. 硬件抽象与管理
内核提供统一的硬件访问接口:
// 内核管理的硬件资源
├── 处理器管理
│ ├── 进程调度
│ ├── CPU频率调节(CPUFreq)
│ └── 电源管理(PM)
│
├── 内存管理
│ ├── 虚拟内存
│ ├── 物理内存分配
│ └── DMA缓冲区
│
├── 设备驱动
│ ├── 摄像头(V4L2)
│ ├── 音频(ALSA)
│ ├── 网络(5G/WiFi)
│ ├── 存储(eMMC)
│ └── 传感器(I2C/SPI)
│
└── 文件系统
├── VFS虚拟文件系统
├── ext4/squashfs
└── /proc、/sys接口
2. 系统调用接口
内核为用户空间提供系统调用:
// 常用系统调用
open() // 打开文件/设备
read() // 读取数据
write() // 写入数据
ioctl() // 设备控制
mmap() // 内存映射
3. 进程管理
内核调度器
├── CFS调度器(普通进程)
├── 实时调度器(RT进程)
└── 进程间通信(IPC)
1.3 RV1126B平台内核特性
Rockchip RV1126B专用特性:
- ISP图像处理: 支持IMX415等MIPI摄像头
- MPP硬件编解码: H.264/H.265硬件编码
- RGA图像加速: 2D图形加速
- NPU神经网络: AI加速(可选)
- RKVENC: Rockchip视频编码器
二、内核移植步骤
2.1 获取内核源码
方式一: 使用瑞芯微厂家提供的 SDK(推荐)
# SDK中已包含内核源码
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
ls -la
# 输出: Linux 6.1内核源码
为什么使用BSP内核?
| 对比项 | 主线内核 | Rockchip BSP内核 |
|---|---|---|
| ISP驱动 | ❌ 不支持 | ✅ 完整支持 |
| MPP编解码 | ❌ 不支持 | ✅ 硬件加速 |
| RGA加速 | ❌ 不支持 | ✅ 2D加速 |
| 稳定性 | ⚠️ 需要大量调试 | ✅ 厂商验证 |
方式二: 从主线内核移植(不推荐)
# 仅作学习参考
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v6.1
2.2 配置内核
步骤1: 选择defconfig
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
# 查看可用的defconfig
ls arch/arm/configs/ | grep rv1126b
# 使用正点原子提供的配置
make ARCH=arm rv1126b_defconfig
defconfig文件位置:
arch/arm/configs/rv1126b_defconfig
步骤2: 定制配置(menuconfig)
# 打开图形化配置界面
make ARCH=arm menuconfig
执法记录仪项目必须启用的配置:
设备驱动包括音频驱动、 USB 驱动、存储设备驱动、网络设备驱动。
[*] Networking support
[*] Wireless
<M> cfg80211 - wireless configuration API
<M> Generic IEEE 802.11 Networking Stack (mac80211)
[*] Device Drivers
[*] Multimedia support
[*] Media USB Adapters
<*> V4L2 platform devices
<*> Rockchip ISP2 driver
[*] Sound card support
<*> Advanced Linux Sound Architecture (ALSA)
<*> ALSA for SoC audio support
[*] USB support
<*> USB Mass Storage support
<M> USB Serial Converter support
<M> USB driver for GSM and CDMA modems
[*] MMC/SD/SDIO card support
<*> Synopsys DesignWare Memory Card Interface
<*> Rockchip SD/MMC controller support
[*] Network device support
<M> USB Network Adapters
<M> CDC Ethernet support
<M> CDC NCM support
步骤3: 保存配置
# 保存为新的defconfig
make ARCH=arm savedefconfig
# 将defconfig复制到configs目录
cp defconfig arch/arm/configs/alientek_rv1126_defconfig
2.3 修改设备树
设备树描述硬件连接关系,是内核移植的关键。
设备树文件位置
arch/arm/boot/dts/
├── rv1126.dtsi # 芯片级公共配置
├── rv1126-pinctrl.dtsi # 引脚复用配置
└── rv1126-alientek.dts # 板级配置(需要创建)
创建板级设备树
cd arch/arm/boot/dts/
cp rv1126-evb.dts rv1126-alientek.dts
编辑 rv1126-alientek.dts:
/dts-v1/;
#include "rv1126.dtsi"
#include "rv1126-pinctrl.dtsi"
/ {
model = "Alientek RV1126B Law Enforcement Recorder";
compatible = "alientek,rv1126b-recorder", "rockchip,rv1126";
chosen {
bootargs = "earlycon=uart8250,mmio32,0xff570000 console=ttyFIQ0,1500000n8 root=/dev/mmcblk0p7 rootwait rw";
};
// 5G模组电源控制
vcc_5g: vcc-5g-regulator {
compatible = "regulator-fixed";
regulator-name = "vcc_5g";
gpio = <&gpio1 RK_PA2 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-boot-on;
};
// WiFi模组电源控制
vcc_wifi: vcc-wifi-regulator {
compatible = "regulator-fixed";
regulator-name = "vcc_wifi";
gpio = <&gpio1 RK_PA3 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
};
// 摄像头配置(IMX415)
&i2c1 {
status = "okay";
clock-frequency = <400000>;
imx415: imx415@1a {
compatible = "sony,imx415";
reg = <0x1a>;
clocks = <&cru CLK_MIPICSI_OUT>;
clock-names = "xvclk";
power-domains = <&power RV1126_PD_VI>;
pinctrl-names = "default";
pinctrl-0 = <&mipicsi_clk>;
reset-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
pwdn-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;
port {
imx415_out: endpoint {
remote-endpoint = <&mipi_in_ucam0>;
data-lanes = <1 2 3 4>;
};
};
};
};
// 陀螺仪配置(ICM20948)
&i2c2 {
status = "okay";
clock-frequency = <400000>;
icm20948: icm20948@68 {
compatible = "invensense,icm20948";
reg = <0x68>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;
mount-matrix = "1", "0", "0",
"0", "1", "0",
"0", "0", "1";
};
};
// eMMC配置
&sdmmc {
status = "okay";
bus-width = <8>;
cap-mmc-highspeed;
mmc-hs200-1_8v;
supports-emmc;
non-removable;
vmmc-supply = <&vcc_3v3>;
vqmmc-supply = <&vcc_1v8>;
};
// USB配置(5G模组)
&usb_host0_ehci {
status = "okay";
};
&usb_host0_ohci {
status = "okay";
};
// SDIO配置(WiFi模组RTL8733)
&sdio {
status = "okay";
bus-width = <4>;
cap-sd-highspeed;
cap-sdio-irq;
keep-power-in-suspend;
non-removable;
vmmc-supply = <&vcc_wifi>;
};
// 音频配置
&i2s0_8ch {
status = "okay";
rockchip,clk-trcm = <1>;
#sound-dai-cells = <0>;
};
// ISP配置
&isp_vir0 {
status = "okay";
};
&rkisp_vir0 {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
isp0_in: endpoint@0 {
reg = <0>;
remote-endpoint = <&csidphy0_out>;
};
};
};
// MIPI CSI配置
&csi_dphy0 {
status = "okay";
ports {
port@1 {
reg = <1>;
csidphy0_out: endpoint {
remote-endpoint = <&isp0_in>;
};
};
};
};
编译设备树
# 编译单个设备树
make ARCH=arm rv1126-alientek.dtb
# 或者在编译内核时自动编译
# 需要修改 arch/arm/boot/dts/Makefile
修改 arch/arm/boot/dts/Makefile:
dtb-$(CONFIG_ARCH_ROCKCHIP) += \
rv1126-evb.dtb \
rv1126-alientek.dtb
2.4 编译内核
步骤1: 设置交叉编译环境
# 设置交叉编译工具链
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 或者使用SDK提供的工具链
export PATH=/home/alientek/atk_dlrv1126b_linux6.1_sdk/prebuilts/gcc/linux-x86/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH
export CROSS_COMPILE=arm-none-linux-gnueabihf-
步骤2: 编译内核
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
# 清理之前的编译
make distclean
# 加载配置
make rv1126_defconfig
# 开始编译(-j8表示使用8个CPU核心)
make -j8
# 编译完成后生成的文件:
# arch/arm/boot/zImage - 压缩的内核镜像
# arch/arm/boot/dts/*.dtb - 设备树二进制文件
编译输出:
LD vmlinux
SORTTAB vmlinux
SYSMAP System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gzip
CC arch/arm/boot/compressed/misc.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
DTC arch/arm/boot/dts/rv1126-alientek.dtb
步骤3: 编译内核模块
# 编译所有模块
make modules -j8
# 安装模块到指定目录
make INSTALL_MOD_PATH=./modules_install modules_install
# 模块安装在:
# ./modules_install/lib/modules/6.1.0/
2.5 打包内核镜像
Rockchip平台使用FIT格式打包内核、设备树和资源文件。
创建boot.img
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk
# 使用SDK提供的打包脚本
./build.sh kernel
# 或者手动打包
./mkfirmware.sh
boot.img结构:
boot.img (FIT格式)
├── kernel (zImage)
├── fdt (设备树DTB)
└── resource (启动Logo等)
2.6 烧录内核
方式一: 使用upgrade_tool烧录
# 进入烧录模式
# 按住Recovery键,然后上电
# 烧录boot.img
sudo upgrade_tool di -b ./out/boot.img
# 重启设备
sudo upgrade_tool rd
方式二: 通过U-Boot更新
# 在U-Boot命令行中
# 1. 通过TFTP下载新内核
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.200
tftp 0x40000000 boot.img
# 2. 写入eMMC
mmc dev 0
mmc write 0x40000000 0x4000 0x8000
# 3. 重启
reset
三、内核调试方法
3.1 串口调试
串口是内核调试最基本也是最重要的手段。
配置串口输出
内核配置:
Kernel hacking
[*] Kernel debugging
[*] Early printk
[*] Early printk via UART
[*] Kernel low-level debugging functions
(0xff570000) Physical base address of debug UART
bootargs配置:
# 在U-Boot中设置
setenv bootargs "console=ttyFIQ0,1500000n8 earlyprintk=uart8250,mmio32,0xff570000"
查看内核日志
# 实时查看内核日志
dmesg -w
# 查看启动日志
dmesg | less
# 过滤特定驱动的日志
dmesg | grep imx415
dmesg | grep usb
3.2 内核启动日志分析
正常启动日志示例:
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 6.1.0 (alientek@ubuntu) (arm-linux-gnueabihf-gcc 10.3.0)
[ 0.000000] CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=10c5387d
[ 0.000000] Machine model: Alientek RV1126B Law Enforcement Recorder
[ 0.000000] Memory policy: Data cache writealloc
[ 0.000000] Reserved memory: created DMA memory pool at 0x10000000
[ 0.000000] OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool
[ 0.000000] Zone ranges:
[ 0.000000] Normal [mem 0x0000000000000000-0x000000001fffffff]
[ 0.000000] HighMem empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000000000000-0x000000001fffffff]
... (省略中间日志)
[ 2.156789] mmc0: new HS200 MMC card at address 0001
[ 2.157234] mmcblk0: mmc0:0001 8GTF4R 7.28 GiB
[ 2.234567] mmcblk0: p1 p2 p3 p4 p5 p6 p7 p8
[ 3.456789] EXT4-fs (mmcblk0p7): mounted filesystem with ordered data mode
[ 3.456890] VFS: Mounted root (ext4 filesystem) on device 179:7
[ 3.567890] Freeing unused kernel memory: 1024K
[ 3.678901] Run /sbin/init as init process
关键启动阶段:
| 时间戳 | 阶段 | 说明 |
|---|---|---|
| 0.000000 | 内核启动 | CPU初始化 |
| 0.000000 | 内存初始化 | DDR配置 |
| 1.000000 | 驱动加载 | 各设备驱动probe |
| 2.156789 | eMMC识别 | 存储设备就绪 |
| 3.456789 | 挂载根文件系统 | 文件系统挂载 |
| 3.678901 | 启动init | 进入用户空间 |
3.3 使用printk调试
printk日志级别
#define KERN_EMERG "<0>" // 系统不可用
#define KERN_ALERT "<1>" // 必须立即采取行动
#define KERN_CRIT "<2>" // 严重错误
#define KERN_ERR "<3>" // 错误
#define KERN_WARNING "<4>" // 警告
#define KERN_NOTICE "<5>" // 正常但重要的信息
#define KERN_INFO "<6>" // 信息
#define KERN_DEBUG "<7>" // 调试信息
在驱动中使用printk
// 推荐使用dev_xxx系列函数
dev_info(&pdev->dev, "IMX415 probe success\n");
dev_err(&pdev->dev, "Failed to request GPIO: %d\n", ret);
dev_dbg(&pdev->dev, "Register value: 0x%x\n", val);
// 或使用pr_xxx系列
pr_info("5G module detected\n");
pr_err("I2C transfer failed: %d\n", ret);
3.4 动态调试(Dynamic Debug)
动态调试允许运行时开关调试信息。
启用动态调试
内核配置:
Kernel hacking
[*] Dynamic printk() support
使用动态调试
# 查看所有可调试的位置
cat /sys/kernel/debug/dynamic_debug/control
# 启用特定文件的调试
echo 'file imx415.c +p' > /sys/kernel/debug/dynamic_debug/control
# 启用特定函数的调试
echo 'func imx415_probe +p' > /sys/kernel/debug/dynamic_debug/control
# 启用特定模块的调试
echo 'module 8733bu +p' > /sys/kernel/debug/dynamic_debug/control
# 禁用调试(-p)
echo 'file imx415.c -p' > /sys/kernel/debug/dynamic_debug/control
3.5 使用ftrace跟踪
ftrace是内核内置的强大跟踪工具。
启用ftrace
内核配置:
Kernel hacking
[*] Tracers
[*] Kernel Function Tracer
[*] Kernel Function Graph Tracer
[*] Interrupts-off Latency Tracer
使用ftrace
# 挂载debugfs
mount -t debugfs none /sys/kernel/debug
cd /sys/kernel/debug/tracing
# 查看可用的跟踪器
cat available_tracers
# 输出: function function_graph nop
# 启用函数跟踪
echo function > current_tracer
# 设置过滤器(只跟踪特定函数)
echo imx415_* > set_ftrace_filter
# 启动跟踪
echo 1 > tracing_on
# 查看跟踪结果
cat trace
# 停止跟踪
echo 0 > tracing_on
# 清空跟踪缓冲区
echo > trace
ftrace输出示例:
# tracer: function
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
v4l2_open-1234 [000] 1234.567890: imx415_s_power <-v4l2_subdev_call
v4l2_open-1234 [000] 1234.567891: imx415_write_reg <-imx415_s_power
3.6 使用KGDB调试
KGDB允许使用GDB远程调试内核。
配置KGDB
内核配置:
Kernel hacking
[*] KGDB: kernel debugger
[*] KGDB: use kgdb over the serial console
bootargs配置:
setenv bootargs "console=ttyFIQ0,1500000 kgdboc=ttyFIQ0,1500000 kgdbwait"
使用KGDB
# 在开发主机上
arm-linux-gnueabihf-gdb vmlinux
(gdb) target remote /dev/ttyUSB0
(gdb) continue
# 设置断点
(gdb) break imx415_probe
(gdb) continue
# 查看变量
(gdb) print ret
(gdb) print *pdev
# 查看调用栈
(gdb) backtrace
3.7 内核崩溃分析
Oops/Panic信息分析
典型的Oops信息:
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c0004000
[00000000] *pgd=00000000
Internal error: Oops: 5 [#1] PREEMPT SMP ARM
Modules linked in: 8733bu(O) icm20948
CPU: 0 PID: 1234 Comm: v4l2_app Tainted: G O 6.1.0 #1
Hardware name: Alientek RV1126B
PC is at imx415_read_reg+0x24/0x80
LR is at imx415_probe+0x120/0x300
pc : [<c0456789>] lr : [<c0456abc>] psr: 60000013
sp : c1234e00 ip : 00000000 fp : c1234e24
r10: 00000000 r9 : 00000000 r8 : 00000000
r7 : c0789abc r6 : c0789def r5 : 00000000 r4 : c1234567
r3 : 00000000 r2 : 00000001 r1 : 00003008 r0 : 00000000
Call trace:
[<c0456789>] (imx415_read_reg) from [<c0456abc>] (imx415_probe+0x120/0x300)
[<c0456abc>] (imx415_probe) from [<c0345678>] (i2c_device_probe+0x100/0x200)
[<c0345678>] (i2c_device_probe) from [<c0234567>] (driver_probe_device+0x80/0x100)
使用addr2line定位代码
# 将地址转换为源代码行号
arm-linux-gnueabihf-addr2line -e vmlinux c0456789
# 输出:
# drivers/media/i2c/imx415.c:234
常见崩溃原因
| 错误类型 | 原因 | 解决方法 |
|---|---|---|
| NULL pointer dereference | 空指针访问 | 检查指针是否为NULL |
| Unable to handle kernel paging request | 非法内存访问 | 检查内存地址是否有效 |
| BUG: scheduling while atomic | 原子上下文中睡眠 | 使用GFP_ATOMIC或改用工作队列 |
| BUG: spinlock lockup | 死锁 | 检查锁的使用顺序 |
四、针对执法记录仪项目的定制
4.1 摄像头驱动集成
IMX415驱动配置
内核配置:
Device Drivers
<*> Multimedia support
[*] Media controller API
[*] V4L2 sub-device userspace API
<*> Rockchip ISP2 driver
<M> Sony IMX415 sensor support
验证摄像头:
# 查看V4L2设备
v4l2-ctl --list-devices
# 输出:
# rkisp-vir0 (platform:rkisp-vir0):
# /dev/video0
# /dev/video1
# 查看支持的格式
v4l2-ctl -d /dev/video0 --list-formats-ext
# 采集一帧图像测试
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap --stream-count=1 --stream-to=test.yuv
4.2 5G模组驱动集成
USB网络驱动配置
内核配置:
Device Drivers
[*] Network device support
<M> USB Network Adapters
<M> Multi-purpose USB Networking Framework
<M> CDC Ethernet support
<M> CDC NCM support
[*] USB support
<M> USB Serial Converter support
<M> USB driver for GSM and CDMA modems
添加RM500Q设备ID
编辑 drivers/net/usb/cdc_ether.c:
static const struct usb_device_id products[] = {
// ... 现有设备ID ...
// 添加RM500Q
{
USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x0800,
USB_CLASS_COMM,
USB_CDC_SUBCLASS_ETHERNET,
USB_CDC_PROTO_NONE),
.driver_info = (unsigned long)&wwan_info,
},
{ },
};
验证5G模组:
# 查看USB设备
lsusb | grep 2c7c
# 查看网络接口
ifconfig -a | grep wwan
# 查看串口设备
ls /dev/ttyUSB*
# 测试AT命令
echo -e "AT\r\n" > /dev/ttyUSB2
cat /dev/ttyUSB2
4.3 WiFi驱动集成
RTL8733驱动配置
内核配置:
Device Drivers
[*] Network device support
[*] Wireless LAN
<M> Realtek 8733BU USB WiFi
[*] MMC/SD/SDIO card support
<*> Secure Digital Host Controller Interface support
编译WiFi驱动模块:
# 驱动源码通常在SDK的external目录
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/external/rkwifibt/drivers/rtl8733bu
# 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=/path/to/kernel
# 安装模块
cp 8733bu.ko /path/to/rootfs/lib/modules/6.1.0/
验证WiFi:
# 加载驱动
modprobe 8733bu
# 查看无线接口
ifconfig wlan0
# 扫描WiFi
iw dev wlan0 scan | grep SSID
4.4 陀螺仪驱动集成
ICM20948驱动配置
内核配置:
Device Drivers
<*> Industrial I/O support
<*> Enable buffer support within IIO
<*> Industrial I/O buffering based on kfifo
Inertial measurement units
<M> InvenSense ICM20948 IMU driver
验证陀螺仪:
# 查看IIO设备
ls /sys/bus/iio/devices/
# 读取陀螺仪数据
cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw
cat /sys/bus/iio/devices/iio:device0/in_anglvel_y_raw
cat /sys/bus/iio/devices/iio:device0/in_anglvel_z_raw
# 读取加速度计数据
cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw
cat /sys/bus/iio/devices/iio:device0/in_accel_y_raw
cat /sys/bus/iio/devices/iio:device0/in_accel_z_raw
4.5 音频驱动配置
内核配置:
Device Drivers
<*> Sound card support
<*> Advanced Linux Sound Architecture (ALSA)
<*> ALSA for SoC audio support
<*> Rockchip I2S Device Driver
<*> ASoC Simple sound card support
验证音频:
# 查看声卡
aplay -l
arecord -l
# 录音测试
arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 test.wav
# 播放测试
aplay test.wav
五、驱动开发指南
5.1 字符设备驱动示例
创建一个简单的字符设备驱动。
示例代码 (simple_char_dev.c):
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "simple_dev"
#define CLASS_NAME "simple_class"
static int major_number;
static struct class *dev_class = NULL;
static struct device *dev_device = NULL;
static char message[256] = {0};
static short message_size;
// 设备打开
static int dev_open(struct inode *inodep, struct file *filep) {
pr_info("simple_dev: Device opened\n");
return 0;
}
// 设备读取
static ssize_t dev_read(struct file *filep, char *buffer,
size_t len, loff_t *offset) {
int error_count = 0;
error_count = copy_to_user(buffer, message, message_size);
if (error_count == 0) {
pr_info("simple_dev: Sent %d characters to user\n", message_size);
return (message_size = 0);
} else {
pr_err("simple_dev: Failed to send %d characters\n", error_count);
return -EFAULT;
}
}
// 设备写入
static ssize_t dev_write(struct file *filep, const char *buffer,
size_t len, loff_t *offset) {
sprintf(message, "%s(%zu letters)", buffer, len);
message_size = strlen(message);
pr_info("simple_dev: Received %zu characters from user\n", len);
return len;
}
// 设备关闭
static int dev_release(struct inode *inodep, struct file *filep) {
pr_info("simple_dev: Device closed\n");
return 0;
}
static struct file_operations fops = {
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
};
// 模块初始化
static int __init simple_dev_init(void) {
pr_info("simple_dev: Initializing\n");
// 注册字符设备
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
pr_err("simple_dev: Failed to register major number\n");
return major_number;
}
pr_info("simple_dev: Registered with major number %d\n", major_number);
// 创建设备类
dev_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(dev_class)) {
unregister_chrdev(major_number, DEVICE_NAME);
pr_err("simple_dev: Failed to create device class\n");
return PTR_ERR(dev_class);
}
// 创建设备节点
dev_device = device_create(dev_class, NULL, MKDEV(major_number, 0),
NULL, DEVICE_NAME);
if (IS_ERR(dev_device)) {
class_destroy(dev_class);
unregister_chrdev(major_number, DEVICE_NAME);
pr_err("simple_dev: Failed to create device\n");
return PTR_ERR(dev_device);
}
pr_info("simple_dev: Device created successfully\n");
return 0;
}
// 模块退出
static void __exit simple_dev_exit(void) {
device_destroy(dev_class, MKDEV(major_number, 0));
class_unregister(dev_class);
class_destroy(dev_class);
unregister_chrdev(major_number, DEVICE_NAME);
pr_info("simple_dev: Module unloaded\n");
}
module_init(simple_dev_init);
module_exit(simple_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("Simple character device driver");
MODULE_VERSION("1.0");
Makefile:
obj-m += simple_char_dev.o
KDIR := /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
编译和测试:
# 编译驱动
make
# 加载驱动
insmod simple_char_dev.ko
# 查看设备节点
ls -l /dev/simple_dev
# 测试写入
echo "Hello Driver" > /dev/simple_dev
# 测试读取
cat /dev/simple_dev
# 卸载驱动
rmmod simple_char_dev
5.2 平台设备驱动示例
平台设备驱动用于SoC内部外设。
示例代码 (platform_driver.c):
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
struct my_platform_data {
struct gpio_desc *gpio_led;
int value;
};
static int my_platform_probe(struct platform_device *pdev)
{
struct my_platform_data *pdata;
struct device *dev = &pdev->dev;
dev_info(dev, "Probing platform device\n");
// 分配私有数据
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
// 获取GPIO
pdata->gpio_led = devm_gpiod_get(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(pdata->gpio_led)) {
dev_err(dev, "Failed to get LED GPIO\n");
return PTR_ERR(pdata->gpio_led);
}
// 保存私有数据
platform_set_drvdata(pdev, pdata);
// 点亮LED
gpiod_set_value(pdata->gpio_led, 1);
dev_info(dev, "Platform device probed successfully\n");
return 0;
}
static int my_platform_remove(struct platform_device *pdev)
{
struct my_platform_data *pdata = platform_get_drvdata(pdev);
// 熄灭LED
gpiod_set_value(pdata->gpio_led, 0);
dev_info(&pdev->dev, "Platform device removed\n");
return 0;
}
static const struct of_device_id my_platform_of_match[] = {
{ .compatible = "alientek,my-platform-device", },
{ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);
static struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my-platform-driver",
.of_match_table = my_platform_of_match,
},
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("Platform device driver example");
设备树配置:
/ {
my_device: my-platform-device {
compatible = "alientek,my-platform-device";
led-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
5.3 I2C设备驱动示例
I2C设备驱动框架。
示例代码 (i2c_driver.c):
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#define DEVICE_REG_ID 0x00
#define DEVICE_REG_CTRL 0x01
struct my_i2c_data {
struct i2c_client *client;
u8 device_id;
};
// I2C读取寄存器
static int my_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
int ret;
struct i2c_msg msgs[2];
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = ®
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = val;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret != 2) {
dev_err(&client->dev, "I2C read failed: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
// I2C写入寄存器
static int my_i2c_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
u8 buf[2] = {reg, val};
int ret;
ret = i2c_master_send(client, buf, 2);
if (ret != 2) {
dev_err(&client->dev, "I2C write failed: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
static int my_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct my_i2c_data *data;
int ret;
u8 device_id;
dev_info(&client->dev, "Probing I2C device at 0x%02x\n", client->addr);
// 分配私有数据
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
i2c_set_clientdata(client, data);
// 读取设备ID验证
ret = my_i2c_read_reg(client, DEVICE_REG_ID, &device_id);
if (ret) {
dev_err(&client->dev, "Failed to read device ID\n");
return ret;
}
data->device_id = device_id;
dev_info(&client->dev, "Device ID: 0x%02x\n", device_id);
// 初始化设备
ret = my_i2c_write_reg(client, DEVICE_REG_CTRL, 0x01);
if (ret) {
dev_err(&client->dev, "Failed to initialize device\n");
return ret;
}
dev_info(&client->dev, "I2C device probed successfully\n");
return 0;
}
static int my_i2c_remove(struct i2c_client *client)
{
dev_info(&client->dev, "I2C device removed\n");
return 0;
}
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "alientek,my-i2c-device", },
{ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
static const struct i2c_device_id my_i2c_id[] = {
{ "my-i2c-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id);
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "my-i2c-driver",
.of_match_table = my_i2c_of_match,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_i2c_id,
};
module_i2c_driver(my_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("I2C device driver example");
六、常见问题与解决方案
6.1 内核无法启动
问题现象:
- 串口无输出
- 或输出到某处停止
可能原因及解决方案:
| 原因 | 解决方法 |
|---|---|
| bootargs配置错误 | 检查console参数,确认串口地址和波特率 |
| 设备树错误 | 检查DTS语法,使用dtc编译验证 |
| 根文件系统无法挂载 | 检查root参数,添加rootwait |
| 内核配置缺少驱动 | 确认eMMC驱动已编译进内核 |
调试步骤:
# 1. 在U-Boot中检查bootargs
printenv bootargs
# 2. 添加earlyprintk
setenv bootargs "console=ttyFIQ0,1500000 earlyprintk=uart8250,mmio32,0xff570000 root=/dev/mmcblk0p7 rootwait rw"
# 3. 使用init=/bin/sh进入shell
setenv bootargs "console=ttyFIQ0,1500000 root=/dev/mmcblk0p7 rootwait rw init=/bin/sh"
6.2 设备树配置错误
问题现象:
- 外设无法识别
- 驱动probe失败
检查设备树:
# 查看编译后的设备树
dtc -I dtb -O dts -o output.dts arch/arm/boot/dts/rv1126-alientek.dtb
# 在运行系统中查看设备树
ls /proc/device-tree/
cat /proc/device-tree/model
常见错误:
// 错误: GPIO编号错误
reset-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>; // 应该检查原理图
// 错误: I2C地址错误
reg = <0x1a>; // 应该与硬件实际地址一致
// 错误: 时钟配置错误
clocks = <&cru CLK_WRONG>; // 应该使用正确的时钟ID
// 错误: 中断配置错误
interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>; // 检查中断类型
6.3 驱动加载失败
问题现象:
- insmod报错
- dmesg显示probe失败
调试方法:
# 查看详细错误信息
dmesg | tail -50
# 检查模块依赖
modinfo xxx.ko
# 强制加载模块
insmod xxx.ko
# 查看模块参数
cat /sys/module/xxx/parameters/*
常见错误:
# 错误1: 符号未定义
insmod: ERROR: could not insert module xxx.ko: Unknown symbol in module
# 解决: 检查内核配置,确保依赖的功能已启用
# 错误2: 版本不匹配
insmod: ERROR: could not insert module xxx.ko: Invalid module format
# 解决: 使用相同内核版本重新编译模块
# 错误3: 设备已存在
insmod: ERROR: could not insert module xxx.ko: File exists
# 解决: 先卸载旧模块 rmmod xxx
6.4 性能问题
问题现象:
- 系统响应慢
- CPU占用率高
分析工具:
# 查看CPU占用
top
# 查看进程状态
ps aux
# 使用perf分析
perf top
perf record -a -g sleep 10
perf report
# 查看中断统计
cat /proc/interrupts
# 查看内存使用
free -h
cat /proc/meminfo
优化建议:
- 启用内核抢占: CONFIG_PREEMPT
- 调整调度器: 使用实时调度策略
- 优化DMA: 减少CPU拷贝
- 使用硬件加速: MPP编码,RGA处理
6.5 内存泄漏
检测工具:
# 启用KMEMLEAK
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
# 查看slab分配
cat /proc/slabinfo
# 使用valgrind(用户空间)
valgrind --leak-check=full ./app
预防措施:
// 使用devm_系列函数自动管理资源
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
// 确保错误路径释放资源
if (error) {
kfree(data);
return error;
}
总结
本指南涵盖了Linux内核移植与调试的核心内容:
- 内核移植: 从配置、编译到烧录的完整流程
- 调试方法: printk、ftrace、KGDB等工具的使用
- 项目定制: 摄像头、5G、WiFi等驱动的集成
- 驱动开发: 字符设备、平台设备、I2C驱动示例
- 问题解决: 常见问题的诊断和解决方案
关键要点:
- 使用Rockchip BSP内核,充分利用硬件特性
- 设备树配置是移植的核心,必须与硬件一致
- 善用内核调试工具,快速定位问题
- 遵循内核编码规范,使用devm_系列函数
- 重视错误处理,确保资源正确释放
下一步:
- 深入学习特定子系统(V4L2、ALSA、网络等)
- 阅读内核文档: Documentation/目录
- 参与内核社区,学习最佳实践
- 针对项目需求优化性能和功耗
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)