Jetson基本笔录2 -- 源码编译与KO生成

内核源码编译

对于自己做的Jetson的板子一般需要修改设备树和驱动, 编译, 然后替换内核镜像(Image)和设备树(FDT), 参考下面的Makefile文件:

  • make env_depend, 安装环境依赖
  • make download, 下载BSP源码(Sources包含kernel/u-boot等), 交叉编译工具链
  • make decompress, 解压kernel源码和交叉编译工具链
  • make prepare_build_kernel, 为编译做准备, 主要是把顶级Makefile和相关文件等放到一个build文件夹里
  • make build, 编译内核源码, 里面包含make menuconfig, 编译出来的内核镜像为build/arch/arm64/boot/Image, 设备树blob为build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb
  • make scp, 可以把Image和dtb传到远程TX2的home目录
BOARD = jetson-tx2-devkit

PWD := $(shell pwd)

# TX2 Image and dtb
TX2_REMOTE= tx2@192.168.6.157
TX2_IMAGE = ${PWD}/build/arch/arm64/boot/Image
TX2_DTB   = ${PWD}/build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb

L4T_RELEASE_PACKAGE = jetson_linux_r32.6.1_aarch64.tbz2
SAMPLE_FS_PACKAGE = tegra_linux_sample-root-filesystem_r32.6.1_aarch64.tbz2

CROSS_COMPILE = ${PWD}/l4t-gcc/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
LOCALVERSION = -tegra

TEGRA_KERNEL_OUT = ${PWD}/build
RELEASE_DIR = ${PWD}/Linux_for_Tegra/kernel


.PHONY: download
download:
	# L4T Driver Package (BSP), ~355MB
	wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/t186/jetson_linux_r32.6.1_aarch64.tbz2
	
	# Sample Root Filesystem, ~1430MB
	wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/t186/tegra_linux_sample-root-filesystem_r32.6.1_aarch64.tbz2
	
	# Jetson Linux Developer Guide (downloadable version)
	wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/nvidia_jetson_linux_driver_package.tar
	
	# L4T Driver Package (BSP) Sources, ~174MB
	wget https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/sources/t186/public_sources.tbz2
	
	# toolchain
	wget http://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz

.PHONY: env_depend
env_depend:
	sudo apt update
	sudo apt install -y qemu-user-static
	sudo apt install -y build-essential bc

.PHONY: decompress
decompress:
	@# ===== doc =====
	mkdir -p l4t_docs & tar xvf nvidia_jetson_linux_driver_package.tar -C l4t_docs

	@# ===== bsp =====
	@echo "\n-->\ndecompress l4t release package\n-->\n"
	@echo ${L4T_RELEASE_PACKAGE}
	tar xf ${L4T_RELEASE_PACKAGE}

	@# ===== rootfs =====
	@# must use sudo, or apply_binaries.sh will break and tell you do this 
	@echo "\n--->\ndecompress sample fs package\n--->\n"
	cd Linux_for_Tegra/rootfs/; \
	sudo tar xpf ../../${SAMPLE_FS_PACKAGE}; \
	cd .. ..

	@# ===== sources =====
	@echo "\n--->\nsources\n--->\n"
	tar -xjf public_sources.tbz2
	cd Linux_for_Tegra/source/public; \
	tar xjf kernel_src.tbz2; \
	cd .. .. ..

	@# ===== toolchain =====
	@echo "\n--->\ntoolchain\n--->\n"
	mkdir -p l4t-gcc; \
	cd l4t-gcc; \
	tar xf ../gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz; \
	cd ..


.PHONY: apply_binaries
apply_binaries:
	cd Linux_for_Tegra; \
	sudo ./apply_binaries.sh; \
	cd ..
	@# the last line must be Success!


.PHONY: flashing
flashing:
	cd Linux_for_Tegra; \
	sudo ./flash.sh ${BOARD} mmcblk0p1


.PHONY: prepare_build_kernel
prepare_build_kernel:
	export CROSS_COMPILE=${CROSS_COMPILE}; \
	export LOCALVERSION=${LOCALVERSION}; \
	mkdir -p ${TEGRA_KERNEL_OUT}; \
	cd ${PWD}/Linux_for_Tegra/source/public/kernel/kernel-4.9; \
	make ARCH=arm64 O=${TEGRA_KERNEL_OUT} tegra_defconfig

.PHONY: build
build:
	export CROSS_COMPILE=${CROSS_COMPILE}; \
	export LOCALVERSION=${LOCALVERSION}; \
	export TEGRA_KERNEL_OUT=${TEGRA_KERNEL_OUT}; \
	cd ${TEGRA_KERNEL_OUT}; \
	make menuconfig; \
	make ARCH=arm64 O=${TEGRA_KERNEL_OUT} tegra_defconfig; \
	make ARCH=arm64 O=${TEGRA_KERNEL_OUT} -j $$(nproc); \
	cp ${TEGRA_KERNEL_OUT}/arch/arm64/boot/Image ${RELEASE_DIR}/; \
	cp -r ${TEGRA_KERNEL_OUT}/arch/arm64/boot/dts/* ${RELEASE_DIR}/dtb/


.PHONY: scp
scp:
	sudo scp ${TX2_IMAGE} ${TX2_DTB} ${TX2_REMOTE}:~


.PHONY: ssh
ssh:
	ssh ${TX2_REMOTE}

内核和设备树更新

方法比较多:

  • 插上USB, 用flash.sh, -K更新内核, -d更新设备树文件
  • extlinux.conf, NFS挂载?
  • extlinux.conf, 直接替换, 这里用这种方式

DTB文件所在的位置参考:

需要先进行设置

虽然默认情况下L4T不再在/boot/extlinux/extlinux.conf中设置FDT条目, 用户仍然可以手动设置此选项,以使更改生效

# TX2
$ sudo vi /boot/extlinux/extlinux.conf
# 添加
FDT /boot/dtb/kernel_tegra186-quill-p3310-1000-c03-00-base.dtb

在这里插入图片描述

使用如下

# 更新内核和设备树
# PC
sudo scp build/arch/arm64/boot/Image build/arch/arm64/boot/dts/tegra186-quill-p3310-1000-c03-00-base.dtb tx2@192.168.6.157:~

# TX2, 1.sh
#!/bin/bash
sudo mv ~/Image /boot
sudo mv ~/tegra186-quill-p3310-1000-c03-00-base.dtb /boot/dtb/kernel_tegra186-quill-p3310-1000-c03-00-base.dtb
# sync
sudo reboot

# 注意dtb的名字前面多了 kernel_

检查更新情况

# 时间应该比Ubuntu系统时间快12小时

# 内核
$ uname -a
Linux tx2-pc 4.9.253 #1 SMP PREEMPT Wed Jan 19 22:13:36 PST 2022 aarch64 aarch64 aarch64 GNU/Linux

# 设备树
$ dmesg | grep DTB
[    0.164259] DTB Build time: Jan 19 2022 22:03:52
[    0.433289] DTB Build time: Jan 19 2022 22:03:52

发行版部分组件

之前的文章里写过, 解决新电脑装旧发行版驱动问题时(如2021/20222年出的电脑装Ubuntu 16/18, 图形显示/WiFi6/蓝牙等肯定会不正常), 可以通过更新内核的方式进行支持, 如更新到5.11内核, 需要下载的有:

# https://blog.csdn.net/weifengdq/article/details/118915007
$ tree
.
├── linux-firmware_1.197.2_all.deb
├── linux-headers-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb
├── linux-headers-5.11.0-051100_5.11.0-051100.202102142330_all.deb
├── linux-image-unsigned-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb
└── linux-modules-5.11.0-051100-generic_5.11.0-051100.202102142330_amd64.deb

其中:

  • image 就是内核镜像, 毕竟最后运行的是编译好的内核镜像, 而不是源码
  • modules是一些可以动态加载到内核的 .ko 组件, 这些往往是通过源码编译出来的
  • firmware是包含某些硬件设备的部分或全部功能所需的固件二进制 blob, 通常是专有的, 因为某些硬件制造商不会发布源码(如NVIDIA显卡, Intel的Wi-Fi芯片组, 想方设法避开中GPL的毒, 地铁老人看手机.jpg)
  • headers: 一个提供Linux内核头文件的软件包, 充当内部内核组件之间 以及 用户空间和内核之间 的接口

如果你正在构建一个完整的内核,那么,显然,你需要完整的源文件,而不仅仅是头文件。

但是,如果您正在编译链接到内核的设备驱动程序或其他可加载模块,那么您只需要头文件,因此可以通过不安装完整源代码来节省空间。

我们编写内核模块时, 经常会看见

#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>

那这些文件在哪里, PC的其实就在/lib/modules/$(uname -r)/build 文件夹里面, 如 linux/module.h 应该就对应X86主机的 /lib/modules/5.13.0-27-generic/build/include/linux/module.h, 那 TX2 的源码包里肯定也有 Linux_for_Tegra/source/public/kernel/kernel-4.9/include/linux/module.h

甚至可以直接在文件夹里面打开 make menuconfig, 如电脑X86平台的

# ubuntu20, now is 5.13.0-27-generic
cd /lib/modules/$(uname -r)/build
sudo make menuconfig
# you will find Kconfig Makefile here

X86 First KO

下面先来写一个 x86 平台的 hello module:

mkdir hello
cd hello
vi hello.c

hello.c的源码和注释

//linux headers
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

//加载时执行
static int hello_init(void)
{
    //打印消息到内核日志缓冲区, 通常用dmesg查看
    //KERN_ALERT为日志级别. 也可以用 pr_alert()
    printk(KERN_ALERT "Hello Module Test\n");
    pr_alert("Hello Module Test2\n");
    return 0;
}

//卸载时执行
static void hello_exit(void)
{
    printk(KERN_INFO "Goodbye Hello\n");
}

//传递给API
//module_init, 驱动初始化入口点
//module_exit, 驱动退出的入口点
module_init(hello_init);
module_exit(hello_exit);

//作者声明, 用 "Name <email>" 或 "Name", 多个作者可以多个 MODULE_AUTHOR() 行
MODULE_AUTHOR("yiming");
//LICENSE声明, 有法律效应, GPL有传染性
MODULE_LICENSE("GPL");
//描述, 通过 modinfo 查看
MODULE_DESCRIPTION("First Driver");

然后是vi Makefile

obj-m:=hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

其中:

  • -C, 为change directory, 即切换目录到 /lib/modules/$(shell uname -r)/build, 这里是 当前Linux版本 的 内核构建目录, 这里有内核的顶级Makefile
  • M=, 顶级Makefile的一个变量参考下面源码, 让Makefile在尝试生成模块目标之前移回指定目录, pwd, 就指明了hello.ko生成在当前目录
# 来自 /lib/modules/$(shell uname -r)/build/Makefile

# Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the
# directory of external module to build. Setting M= takes precedence.
ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

现在开始走一把

# hello目录
$ tree
.
├── hello.c
└── Makefile

$ make

$ tree
.
├── hello.c
├── hello.ko
├── hello.mod
├── hello.mod.c
├── hello.mod.o
├── hello.o
├── Makefile
├── modules.order
└── Module.symvers

# 查看文件类型, elf 64bit LSB 浮动程序, x86-64 平台
$ file hello.ko
hello.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=ea5c2968e3fe351b700b5e6f8978904e58254b8f, with debug_info, not stripped

# 加载hello.ko
$ sudo insmod hello.ko 

# 查看最后两行
$ dmesg | tail -2
[23749.761288] Hello Module Test
[23749.761293] Hello Module Test2

# 卸载hello.ko, 可以不加后缀.ko
$ sudo rmmod hello
$ dmesg | tail -1
[23902.056184] Goodbye Hello

# 如果让dmesg直接输出到console
# sudo dmesg -n 8  或 sudo dmesg -n debug
# 恢复默认
# sudo dmesg -n 4  或 
# 让dmesg一直显示不退出
$ dmesg -wH &

最后的命令还可以查看级别, 红色的alert:

在这里插入图片描述

Jetson First KO

参考文档中的Kernel Cusomization -> Preparing to Building External Kernel Modules 和 Building External Kernel Modules两小节, 编译一个外部的Jetson内核模块的步骤:

  • 预处理: 准备module源文件里面header, 顶级Makefile, kernel_source_tree等, 只准备一次就可以
  • 设置变量CROSS_COMPILE来指定交叉编译器
  • 设置 -C <top_makefile_dir> 来指定顶级Makefile所在目录, 一般在内核目录, 但这里我直接丢到了top文件夹里面
  • 设置 M= 指定module的位置

新建一个jetson_hello文件夹(/home/z/jetson/test/jetson_hello), 还是上面的hello.c不变拷贝进去, 接着撸Makefile

obj-m:=hello.o

kernel_dir = /home/z/jetson/Linux_for_Tegra/source/public/kernel/kernel-4.9
cross_compile_dir = /home/z/jetson/l4t-gcc/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-

makefile_dir := $(shell pwd)
top_makefile_dir := $(shell pwd)/top
build_dir := $(shell pwd)/build

all:
	@if [ ! -d ${top_makefile_dir} ]; then \
		export CROSS_COMPILE=${cross_compile_dir}; \
		export LOCALVERSION=-tegra; \
		mkdir -p ${top_makefile_dir}; \
		cd ${kernel_dir}; \
		make ARCH=arm64 O=${top_makefile_dir} tegra_defconfig; \
		cd ${top_makefile_dir}; \
		make ARCH=arm64 O=${top_makefile_dir} -j $$(nproc) modules_prepare; \
		echo "\n=================================\n"; \
	fi; \
	export CROSS_COMPILE=${cross_compile_dir}; \
	cd ${makefile_dir};\
	mkdir -p ${build_dir}; \
	make ARCH=arm64 -C ${top_makefile_dir} M=${makefile_dir} -j $$(nproc); \
	mv *.mod.c *.o *.order *.symvers ${build_dir}
	
clean:
	@# rm -rf *.mod.* *.o *.order *.symvers *.ko
	rm -rf build *.ko top

其中:

  • obj-m, 这里的m指module, 一般是配合Kconfig文件在make menuconfig里面选编译进内核(y), 还是编译成KO(m), 或者不编译(n), 这里直接指定-m
  • make的时候先判断top文件夹是否存在, 不存在就做上面的预处理那一步
  • 每次编译都要设置变量CROSS_COMPILE来指定交叉编译器
  • -j $(nproc), 全部CPU进场, 不过这里只有一个.c文件, 用不上

测试一把

$ make
make[1]: Entering directory '/home/z/jetson/test/jetson_hello/top'

  WARNING: Symbol version dump ./Module.symvers
           is missing; modules will have no dependencies and modversions.

  LD      /home/z/jetson/test/jetson_hello/built-in.o
  CC [M]  /home/z/jetson/test/jetson_hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/z/jetson/test/jetson_hello/hello.mod.o
  LD [M]  /home/z/jetson/test/jetson_hello/hello.ko
make[1]: Leaving directory '/home/z/jetson/test/jetson_hello/top'

$ tree -L 2
.
├── build
│   ├── built-in.o
│   ├── hello.mod.c
│   ├── hello.mod.o
│   ├── hello.o
│   ├── modules.order
│   └── Module.symvers
├── hello.c
├── hello.ko
├── Makefile
└── top
    ├── arch
    ├── include
    ├── kernel
    ├── Makefile
    ├── scripts
    └── source -> /home/z/jetson/Linux_for_Tegra/source/public/kernel/kernel-4.9

# ARM aarch64
$ file hello.ko
hello.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=8f24215154dff2b07306d655040f7c29de7a5efb, with debug_info, not stripped

$ scp hello.ko tx2@192.168.6.157:~

$ ssh tx2@192.168.6.157

# TX2
$ sudo insmod hello.ko
$ sudo rmmod hello
$ dmesg
...
[26332.410359] hello: no symbol version for module_layout
[26332.416120] hello: loading out-of-tree module taints kernel.
[26332.425103] Hello Module Test
[26332.428150] Hello Module Test2
[26347.063612] Goodbye Hello

如图所示

在这里插入图片描述

Longterm Kernel

The Linux Kernel Archives - Releases, Jetson目前的4.9版本在16年底就release了, 支持到23年初.

在这里插入图片描述

4.9内核的在线文档参考: Linux Kernel Documentation — The Linux Kernel documentation, 有点老了… 看着没有新的5.1x的组织的舒服…

命令备忘

# 显示已加载模块
lsmod
# 查看内核模块信息
modinfo
# 载入内核模块
indmod
# 卸载内核模块
rmmod
# 查看模块依赖
depmod
# 载入或移除模块
# 比较智能一点, 可以递归解析模块依赖
modprobe

# 确定文件类型, 32/64bit
file

参考

欢迎扫描二维码关注微信公众号, 及时获取最新文章:
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值