【前言】在工业产品中使用操作系统,一般都需要使用实时性较强的操作系统,而众所周知,linux系统是基于时间片划分的非实时系统,其实时性难以满足工业化对时效性的要求,因此很多应用场景中无法使用linux操作系统。当然这一局限性已经有所改善,目前linux社区已经增加了众多版本的实时补丁,只要给linux内核打上实时补丁,其实时性会得到大幅度提升。“实时补丁”的主要工作就是针对Linux系统的优先级倒置、自旋锁等问题进行改进,以达到实时操作系统的要求。
1、环境说明
1.1、硬件平台:M3568-4GF16GLI-T(RK3568)
1.2、Linux内核版本:Linux4.19.193
2、下载RT Preempt补丁
Index of /pub/linux/kernel/projects/rt/https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/
根据实际的Linux内核版本下载对应的RT Preempt补丁。博主使用的Linux内核版本为4.19.193,故在这里我们下载的补丁文件为:patch-4.19.193-rt81.patch.gz。(当然也可以下载其它压缩格式的文件)
3、打补丁操作
3.1、解压补丁包
输入以下命令解压你刚下载的补丁包:
gunzip patch-4.19.193-rt81.patch.gz
3.2、将RT补丁打进内核文件
将解压出来的补丁文件patch-4.19.193-rt81.patch.gz复制到SDK包中的kernel目录中。
随后执行如下指令:
sudo patch -p1 < patch-4.19.193-rt81.patch
至此完成打补丁工作。
注意:直接从官网下载的补丁包,打入RK3568内核后,会出现很多错误,其原因是有些修改并未patch进内核源码中,详情请参见第6节。
4、配置并编译Linux内核
在kernel目录下,执行make ARCH=arm64 menuconfig 进入内核menuconfig菜单(执行前需保证内核根目录下的.config是arm64架构)。
进入 > General setup > Preemption Model
选择 (X) Fully Preemptible Kernel (RT)
保存配置文件。
执行make ARCH=arm64 savedefconfig,导出配置文件,其命名为defconfig,用defconfig文件复制至arch/arm64/configs/rockchip_linux_defconfig文件(此处要根据你的实际情况去替换,博主的内核配置文件为arch/arm64/configs/rockchip_linux_defconfig)。
使用 ./build.sh kernel指令去编译内核。(瑞芯微官方提供的内核编译脚本)
5、测试linux的实时性
前面我们已经生成了实时的linux内核镜像,接下来我们把实时内核烧写到开发板上,进行实时性性能测试。这里需要引入一个测试linux性能的工具cyclictest。
5.1、获取cyclictest工具
获取cyclictest工具有2种方式,一种是通过编译buildroot的方式获取,另外一种是下载cyclictest源码自行编译获取。
5.1.1、通过buildroot获取cyclictest工具
配置rt-tests选项方式一:进入buildroot根目录下,通过打开menuconfig菜单来配置rt-tests选项。操作方法与kernel的配置类似。
配置rt-tests选项方式二:
直接在配置文件中添加rt-tests配置,如下图所示:
博主的配置文件为:configs/rockchip_rk3568_defconfig。(此处要根据自己的实际环境去找到对应的根文件系统的配置文件去修改)
通过上述任一种方式配置好rt-tests选项后,使用 ./build.sh buildroot指令去编译buildroot。(瑞芯微官方提供的根文件系统编译脚本)编译结束后根文件系统就自带了cyclictest工具。
5.1.2、通过cyclictest源码获取cyclictest工具
5.1.2.1、下载cyclictest源码
在ubuntu上下载rt_tests源码:
git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git
至此从Git 仓库中拉取了cyclictest工具的源码包。如下所示:
5.1.2.2、切换分支
切换git分支进到源码的v1.0版本,具体操作如下:
cd rt-tests/
git checkout stable/v1.0
5.1.2.3、修改Makefile
打开Makefile,指定交叉编译工具链和平台架构,其修改内容如下所示:
5.1.2.4、编译cyclictest源码
执行如下命令编译cyclictest源码:
make clean
make all NUMA=0 或 make
编译结束后,会在当前目录下生成cyclictest工具,如下图所示:
5.2、利用cyclictest进行性能测试
5.2.1、cyclictest常用参数
-p | --prio=PRIO | 最高优先级线程的优先级 ,使用方法为: -p 90 或 --prio=90 |
-m | --mlockall | 锁定当前和将来的内存分配。 |
-c | --clock=CLOCK | 选择时钟,cyclictest -c 0 0 = CLOCK_MONOTONIC (默认) 1 = CLOCK_REALTIME |
-i | --interval=INTV | 基本线程间隔,默认为1000(单位为us) |
-l | --loops=LOOPS | 循环的次数,默认为0(无穷个),与 -i 间隔数结合可大致算出整个测试的时间,比如 -i 1000 -l 1000000 ,总的循环时间为1000*1000000=1000000000 us =1000s ,所以大致为16分钟多点。 |
-n | --nanosleep | 使用 clock_nanosleep |
-h | --histogram=US | 在执行完后在标准输出设备上画出延迟的直方图(很多线程有相同的权限)US为最大的跟踪时间限制。 |
-q | --quiet | 使用-q 参数运行时不打印信息,只在退出时打印概要内容,结合-h HISTNUM参数会在退出时打印HISTNUM 行统计信息以及一个总的概要信息。 |
-t | --threads | 运行的线程数量,cyclictest t 15 |
-f | --ftrace | ftrace函数跟踪(通常与-b 配套使用,其实通常使用 -b 即可,不使用 -f ) |
-b | --breaktrace=USEC | 当延时大于USEC指定的值时,发送停止跟踪。USEC,单位为微秒(us) |
5.2.2、cyclictest测试
如果是通过cyclictest源码编译生成的成cyclictest工具,则需要将其下载到开发板上,并给予其可执行权限;如果通过配置buildroot来生成的cyclictest工具,则无需其它操作,直接使用即可。接下来就使用cyclictest命令来进行linux内核实时性能测试,其测试命令为:
cyclictest -t 20 -p 80 -n -i 10000 -l 500000 (cyclictest路径在环境变量中)
或
./cyclictest -t 20 -p 80 -n -i 10000 -l 500000 (cyclictest路径在被执行的当前目录下)
5.2.3、运行结果
5.2.3.1、非实时系统测试结果
执行完上述指令后,其测试结果如下图: (运行时间大约83.3分钟)
5.2.3.2、实时系统测试结果
执行完上述指令后,其测试结果如下图: (运行时间大约83.3分钟)
5.2.3.3、运行结果含义
T: 0 | 序号为0的线程 |
P:80 | 线程优先级为80 |
C:9397 | 计数器。线程的时间间隔每达到一次,计数器加1 |
I:10000 | 时间间隔为10000微秒(us) |
Min: | 最小延时(us) |
Act: | 最近一次的延时(us) |
Avg: | 平均延时(us) |
Max: | 最大延时(us) |
5.2.3.4、测试结果分析
在线程数和运行时间等条件均相同的情况下:
(1)对于非实时系统:最小时延在20us左右,平均时延在65us左右,最大时延在3500us左右。
(2)对于打了RT补丁的实时系统:最小时延在10us左右,平均时延在15us左右,最大时延在300us左右。
通过测试数据分析,打了RT补丁的Linux系统其时延性得到了大幅度改善。所以对于实时性要求较高的系统,还是需要给linux内核打上实时的补丁。
6、遇到的问题
6.1、报类似Hunk #6 FAILED at 107. 错误
6.1.1、错误原因:
这个错误信息表明在尝试修改(patching)arch/arm64/crypto/Kconfig文件时,第6个补丁(Hunk)失败了。具体来说,补丁在第107行处失败。由于一个补丁中有多个失败,所以总共有6个补丁失败。错误信息还指出,这些失败的补丁将被保存到arch/arm64/crypto/Kconfig.rej文件中。 这通常意味着在尝试应用一个补丁或进行某种代码更改时,有一些更改与原始文件不匹配,因此没有被成功应用。这可能是由于文件已经更改、有冲突或其他原因。
注意:所有未patch进kernel源码的内容会保存在xxx.rej文件中(xxx指代具体的kernel文件名)。例如下文的例子,未patch进内核文件的内容保存在了arch/arm64/crypto/Kconfig.rej文件中,而源文件名为arch/arm64/crypto/Kconfig。
6.1.2、解决办法:(目前只通过如下办法解决该问题,如有更好的方法欢迎评论区讨论!)
打开arch/arm64/crypto/Kconfig.rej文件,查看具体是哪一行和哪些更改被拒绝。博主的情况如下:
根据上述没有正常patch进源文件的内容手动修改。从arch/arm64/crypto/Kconfig文件中找到对应的位置手动修改成arch/arm64/crypto/Kconfig.rej文件中对应指定的内容。具体如下所示:
其它类似该问题的解决方法参考上文。
注意:可以使用命令:find . -name "*.rej" -print 查看当前kernel下有多少文件没有被patch进内核源码,然后可以逐一手动去修改需要patch的内容。
6.2、error: too few arguments to function 'try_to_wake_up'
具体报错情况如下图所示:
细究patch后的kernel/sched/core.c文件第2805行代码,调用try_to_wake_up();函数时确实是少了一个参数。通过对try_to_wake_up();函数的参数研究并分析该函数的调用前后文情况,博主目前将该函数缺少的第四个参数传一个1进去(并未深入研究其内部逻辑,如有大佬发现有不妥,恳请指正)。修改后的代码如下图所示:
6.3、error:'struct zram_table_entry' has no member named 'value'
具体报错情况如下图所示:
上述错误显示 'struct zram_table_entry'结构体中没有‘value’这个变量。分析发现patch-4.19.193-rt81.patch文件中,生成补丁的源码文件中'struct zram_table_entry'结构体中使用的是value变量,如下图所示:
在博主的内核源码中,该结构体中使用的是flags变量,如下图所示:
故此处针对上述错误的解决办法为: 将patch进博主内核源码的相关value变量改成flags变量。修改后如下图所示: (文件:drivers/block/zram/zram_drv.c)
6.4、error: 'struct task_struct' has no member named 'cpus_allowed';
具体报错情况如下图所示:
cpus_allowed变量在patch补丁中是被屏蔽的,但新增了同数据类型的cpus_ptr变量。通过对for_each_cpu_and();和cpumask_test_cpu();各自参数的研究,分析函数被调用前后文情况和patch文件的如下部分内容:
针对上述问题的解决办法:用patch后新增加的cpus_ptr变量去替换cpus_allowed变量。(目前不知道这样修改是否有什么风险,恳请知道的大佬留言指正!)
具体修改如下图所示:
7、参考文献
7.1、RT Preempt相关
realtime:documentation:howto:applications:start [Wiki] (linuxfoundation.org)
实时Linux内核(PREEMPT_RT)的编译安装以及测试_实时内核-CSDN博客
stm32mp157 rt-preempt测试-CSDN博客
linux打实时补丁以及实时性能测试_linux cyclictest测试实时补丁-CSDN博客
7.2、cyclictest测试工具相关
实时性测试:cyclictest详解 - 知乎 (zhihu.com)
【结束语】因技术能力有限,文章如有不妥之处,恳请各位技术大佬留言指正!