跳转至

BSP FAQ

前言

概述

本文档描述使用BSP遇到问题的解决方案。

本文档中涉及的器件型号,仅表示测试结果,不提供兼容性承诺。

说明:

本文以Hi3519DV500描述为例,未有特殊说明,Hi3519DV500与Hi3516DV500内容一致。

  • Hi3519DV500以musl库工具链aarch64-v01c01-linux-musl-gcc描述为例,使用glibc库工具链时,请使用aarch64-v01c01-linux-gnu-gcc替换aarch64-v01c01-linux-musl-gcc即可。
  • Hi3516CV610/Hi3516CV608/Hi3511CV100仅支持musl库工具链arm-v01c02-linux-musleabi-gcc。

产品版本

与本文档相对应的产品版本如下。

产品名称

产品版本

Hi3519D

V500

Hi3516D

V500

Hi3516C

V608

Hi3516C

V610

Hi3511C

V100

读者对象

本文档(本指南)主要适用于以下工程师:

  • 技术支持工程师
  • 软件开发工程师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修订记录

修订记录累积了每次文档更新的说明。最新版本的文档包含以前所有文档版本的更新内容。

文档版本

发布日期

修改说明

00B01

2026-06-30

第1次临时版本发布。

SDK环境、使用类

为什么执行server_install脚本报错

典型的错误提示如下:

./server_install
\33[32m
you must use 'root' to execute this shell
\33[39m
./cross.install: 25: Syntax error: "do" unexpected (expecting "fi")
./cross.install: 28: Syntax error: "do" unexpected (expecting "fi")
./cross.install: 30: Syntax error: "do" unexpected (expecting "fi")

这是因为SDK发布的脚本都是基于bash的,而您使用的Linux服务器可能安装的是dash或者其他的命令行程序。推荐解决方法:卸载dash或者把默认的sh改成bash。一般删除原来的sh软链接,重新建立一个指向bash的软链接即可:

cd /bin
rm –f sh
ln –s /bin/bash /bin/sh

MMZ和MMB分别指什么,如何配置MMZ的区域和大小

关于MMZ和MMB的概念解释如下。

名词解释

MMZ:Media-Memory-Zone,媒体内存域,也就是分配池。

MMB:Media-Memory-Block,媒体内存块。

MMZ管理的物理内存区域不属于Linux内核控制,是单独给媒体驱动(如解码器、DEMUX)使用的物理内存区域。MMB是指从MMZ中分配的内存块。

MMZ的原理

MMZ驱动管理用户创建的分配池,用户程序分配内存的时候可以指定要在哪个分配池中分配内存,分配器将查找满足要求的分配池并从中分配合适的内存块给程序使用。

MMZ驱动的模块参数

"mmz =" 用来定义media-mem的分配池,格式为:

mmz=<name>,<gfp>,<phys_start_addr>,<size>:<name>,<gfp>,<phys_start_addr>:……

  • <name>:字符串,分配池的名字,例如 ddr。
  • <gfp>:数字,表示分配池的属性,主要用于在有多种内存的单板上指定MMZ位于哪种内存上(比如DDR、SDRAM、DDR2、DDR3),为0表示自动,目前一般都直接将该值置为0。
  • <phys_start_addr>:分配池的物理起始位置,16 进制数,如 0x86000000;注意MMZ的内存区域不能与Linux内核的内存区域重叠,MMZ的物理起始位置就要从“内存起始地址+Linux内核使用的内存大小”开始。在某平台上,内存的起始地址固定为0x80000000;举例说明如下:假设单板的bootargs为 'mem=96M console=ttyAMA0,115200 root=xxxx',这表示Linux内核将使用96M的内存空间,那么MMZ的起始地址应该配置为0x80000000+96M = 0x86000000。
  • <size>:分配池的大小,可以使用如下两种表示方式:0x100000、1M。注意分配池的大小加上Linux内核的内存大小不能超过物理内存的实际大小。比如单板上的物理内存是256M大小,Linux内核使用了96M,MMZ就只能使用最多256-96=160M。

以上每一个参数都是必需的,参数之间用“,”号分隔,可以指定多个分配池,之间用“:”号分隔。例如:insmod ot_osal.ko anony=1 mmz_allocator=ot mmz=anonymous,0,0x88000000,0x40000000:anonymous,0,0x100000000,0x1000000。

为什么会有MMB LEAK之类的打印

常见的打印类似这样:

MMB LEAK(pid=11093): 0x880BA000, 3686400 bytes, ''
mmz_userdev_release: mmb<0x880BA000> mapped to userspace 0x408d1000 will be force unmapped!
MMB LEAK(pid=11093): 0x884FD000, 2764800 bytes, ''
mmz_userdev_release: mmb<0x884FD000> mapped to userspace 0x40d14000 will be force unmapped!
MMB LEAK(pid=11093): 0x8843E000, 520192 bytes, 'decctrl'
mmz_userdev_release: mmb<0x8843E000> mapped to userspace 0x40c55000 will be force unmapped!
MMB LEAK(pid=11093): 0x884BD000, 262144 bytes, 'Hdec'
mmz_userdev_release: mmb<0x884BD000> mapped to userspace 0x40cd4000 will be force unmapped!

这个打印并不代表内存泄漏。这是应用程序在退出时还有资源没有释放干净,SDK检测到这种情况后强制释放资源时给出的提示信息。请检查应用程序的去初始化动作是否完善(比如创建的通道是不是都销毁了,打开的设备是不是都关闭了)。

用fopen打开大于4G的文件失败,如何操作大于4G的文件

这个问题有多种解决方法,推荐使用如下方法。

在makefile编译选项里加上以下选项:

-D_GNU_SOURCE -D_XOPEN_SOURCE=600 -D_LARGEFILE_SOURCE 
-D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64

为什么NFS挂载不上

推荐如下的方法挂载NFS:

mount -t nfs -o nolock,tcp xxx.xxx.xxx.xxx:/xxx/sdk_root /mnt

如果有:

rpcbind: server localhost not responding, timed out
RPC: failed to contact local rpcbind server (errno 5).
rpcbind: server localhost not responding, timed out
RPC: failed to contact local rpcbind server (errno 5).
rpcbind: server localhost not responding, timed out
RPC: failed to contact local rpcbind server (errno 5).

之类的打印,一般是-o nolock选项没有加;如果nfs经常失去响应(特别是读写大文件的时候,伴随nfs server not responding, still trying之类的打印),一般是没有加-o tcp选项。

为什么无法烧写文件系统或者flash出现非常多的bad block

常见错误可能如下:

# nand write.yaffs 0x82000000 0x700000 0x1928ac0
NAND write: device 0 offset 0x700000, size 0x1928ac0
Attempt to write non page aligned data, length 26380992 4096 128
26380992 bytes written: ERROR

这是由于制作yaffs文件系统的时候指定的pagesize和ecc参数与单板上的nand flash的实际物理参数不匹配导致的,请确认单板上的nand flash的pagesize和ecc参数后重新制作一个文件系统。

注意pagesize和ecc参数错误并不一定导致烧写错误,也可能会烧进去,但还是无法启动,并且打印bad block n之类,碰到此类错误,也请重新制作文件系统,并且用“nand scrub NandFlash地址 长度”命令把yaffs文件系统所在的区域清理一遍才能再次烧写yaffs文件系统, 比如 “nand scrub 0x400000 0x1000000”表示从0x400000开始清理16M。如果最后一个参数不传,则表示从此地址开始清理至nand flash结束,比如“nand scrub 0x400000”表示清理从0x400000开始的所有flash空间。

那么怎样才能知道nand flash的pagesize和ecc类型呢?就看内核启动时的打印,内核启动时会有许多打印,找到其中的这几句:

Nand Flash Controller V300 Device Driver, Version 1.00
Nand ID: 0xAD 0xDC 0x10 0x95 0x54 0xAD 0xDC 0x10
Nand(Hardware): Block:128K Page:2KB Ecc:1bit Chip:512M OOB:64Byte
NAND device: Manufacturer ID: 0xad, Chip ID: 0xdc (Hynix NAND 512MiB 3,3V 8-bit)

第三行里面就有page和ecc的大小。如果找不到这个打印,说明发布包不支持这种类型的flash。

为什么无法启动文件系统,提示No init found

常见的错误可能如下:

ata2: failed to resume link (SControl 0)
ata2: SATA link down (SStatus 0 SControl 0)
yaffs: dev is 32505858 name is "mtdblock2" rw
yaffs: passed flags ""
VFS: Mounted root (yaffs2 filesystem) on device 31:2.
Freeing init memory: 100K
Kernel panic - not syncing: No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.

可能的原因有以下两项:

  1. yaffs文件系统制作时输入的pagesize和ecctype错误,这两个参数如果与实际nand flash的属性不一样的话,内核将无法识别yaffs文件系统。SDK make build的时候,默认按照随SDK发布的参考板制作文件系统,采用的pagesize和ecctype参数不一定与您的一致。
  2. bootargs配置错误,比如误把bootargs配置为:setenv bootargs ' bootargs=mem=96M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=yaffs2 mtdparts=nand:4M(boot),60M(rootfs),-(others)'之类或者bootargs中的rootfstype配置错误,比如烧写的是jffs2文件系统,bootargs却配置为yaffs2,也可能导致内核无法识别文件系统。

为什么无法启动文件系统,提示不能打开console

这是因为用来制作文件系统的rootbox里面没有/dev/console文件,或者console文件的属性不对。正常的console文件属性如下:

cd SDK根目录
ls ./pub/rootbox/dev/ -l
总计 0
crw-r--r-- 1 root root   5,  1 2010-10-18 18:52 console
crw-r--r-- 1 root root 204, 64 2010-10-18 18:52 ttyAMA0
crw-r--r-- 1 root root 204, 65 2010-10-18 18:52 ttyAMA1
crw-r--r-- 1 root root 204, 64 2010-10-18 18:52 ttyS000

tftp无法使用时,需要哪些检查

请按照如下步骤进行检查:

  1. 网线是否已经插上单板并连接正常? 插任意一个网口都可以。
  2. 插在单板上的网线在其他地方是否可以正常使用?网线本身有没有问题?
  3. 单板与PC之间是否是通过网线直连? 如果不是,这中间的网络是否是需要认证或者代理?某些交换机会屏蔽非交换机本身动态分配的IP地址,在boot下直接用setenv ipaddr的方式配置IP地址可能会导致网络不通;某些公司的网管会屏蔽掉非特定范围内的其他MAC地址或者IP地址,也可能导致单板无法访问网络。推荐采用单板与PC直连的方式进行tftp操作。
  4. PC上是否启用了IPv6协议?单板在boot下不支持IPv6协议,请关闭PC上IPv6协议的支持。

为什么在服务器上执行SDK的某些编译命令或者脚本,报“找不到这个文件”或者类似的错误

这个很可能是因为服务器上安装的是64位的操作系统,而SDK要求服务器上有32位的C库导致的。解决办法,在服务器上安装32位的运行库。可在互联网上搜索服务器上安装32位运行库的方法。

参考命令(仅在Ubuntu 64bit server版服务器上测试过,注意服务器需要能够连接Internet):

apt-get install libc6-i386
apt-get install gcc-multilib g++-multilib  libc6-dev-i386 libzip-dev
apt-get install ia32-libs lib32asound2 libasound2-plugins
apt-get install -y lib32nss-mdns lib32gcc1 lib32ncurses5 lib32stdc++6 lib32z1 libc6 libcanberra-gtk-module
dpkg -i --force-all getlibs-all.deb

制作cramfs文件系统时,报出文件大小被截为16M的警告,同时制作的文件大小不对

常见的错误如下:

root@Athena:~$ mkcramfs ./tools/ root.img

Directory data: 37924 bytes

Everything: 43936 kilobytes

Super block: 76 bytes

CRC: 40e6dc31

warning: file sizes truncated to 16MB (minus 1 byte)

warning: gids truncated to 8 bits (this may be a security concern)

出现这个错误原因是,本身的cramfs不支持单个文件大小大于16M,如果需要制作单个文件大小大于16M的文件系统,请按照下面的流程检查修改。

  • 请确保对cramfs文件系统的支持和对MTD驱动的支持。
  • 修改mkcramfs的源码,使其支持制作特殊的文件系统,从http://sourceforge.net/projects/cramfs/下载cramfs的源码,解压之后修改cramfs/linux/cramfs_fs.h的#define CRAMFS_SIZE_WIDTH 24,此处的24代表16M,例如你需要支持256M的文件,可将其设置为28。
  • 修改内核的cramfs文件系统。修改内核源码目录的:include/linux/cramfs_fs.h文件,同样的方式修改宏 CRAMFS_SIZE_WIDTH

串口调试终端(如SecureCRT)出现显示卡住,输入无响应,但系统运行正常的情况

出现此情况可能的原因是调试终端接收到ANSI控制码——“\033P”,然后对命令作出的响应。“\033P”控制码会控制终端停止显示接收到的数据(调试打印信息),停止接收输入的数据(如键盘输入)。

一些打印信息为增加显示效果而加入了颜色(如红色显示ANSI控制码:“\033[0;32;31m”)。在系统多任务抢占、中断等情况下,打印信息可能会被打断。某些情况下,调用的printf打印了‘\033’,被其它调用printf打印了‘P’的任务或中断打断,拼接成“\033P”控制码发送给了终端,从而造成终端停止显示并停止接收输入。

应当谨慎使用ANSI控制码,避免发送“\033P”控制码。

工具类

如何在单板上使用GDB

客户可以从gdb的官网(http://www.gnu.org/software/gdb/download/)下载gdb软件包,然后使用SDK版本中对应的交叉编译工具链编译gdb。然后将编译生成的gdb拷贝到板端/usr/bin目录来使用,或者挂载nfs目录后,使用gdb的绝对路径运行gdb。

下面以从gdb官网下载的gdb-10.2版本为例(aarch64-v01c01-linux-gnu工具链推荐使用gdb-10.2版本),编译生成gdb的步骤如下:

  1. 解压压缩包:tar zxf gdb-10.2.tar.gz
  2. 在gdb-10.2的同一级目录创建build目录,并进入build目录:mkdir build; cd build
  3. 配置gdb:../gdb-10.2/configure --host=aarch64-v01c01-linux-gnu --prefix=/home/xxx/gdb_output

    注意:host参数需要根据实际使用的工具链来设置,prefix参数需要根据实际需求指定gdb的安装目录。如果使用arm-v01c02-linux-musleabi工具链,需使用-DOK_TO_USE_1S_CLOCK选项编译gdb,如 ../configure --host=arm-v01c02-linux-musleabi --prefix=/home/xxx/gdb_output CFLAGS="-DOK_TO_USE_1S_CLOCK"。

  4. 如果使用的工具链是aarch64-v01c01-linux-musl,则需要修改../gdb-10.2/gdbserver/Makefile.in,在第391行添加$(INTL),修改后的Makefile脚本第391行如下:-o $(IPA_LIB) ${IPA_OBJS} $(INTL) -ldl -pthread。

  5. 执行编译和安装:make -j;make install

注意:gdb仅供调试时使用,严禁在正式产品中使用。

如何让gdb在调试过程中忽略信号事件

一般碰到的问题是gdb会打印以下信息,然后停住等待用户输入命令“c”才继续运行。

Program received signal SIG32, Real-time event 32.
0x4052d940 in __rt_sigsuspend () from /lib/libc.so.0

这种消息往往我们并不关注,可以在gdb的命令行中使用命令:handle SIG32 pass noprint nostop,使gdb忽略SIG32。其他消息也类似。

如何在单板端使用 telnet或者tftp服务

由于公司网络安全需求对外的 SDK 包里面默认关闭 telnet、tftp 服务,客户如果想在单板端使用 telnet 或者tftp服务需要执行如下操作:

  1. 进入到 busybox

    cd third_party/busybox/busybox-1.34.1
    
  2. 打开 telnet 或者 tftp服务(建议客户按需打开)

    cp config_aarch64_a55_softfp_neon .config
    make menuconfig
    Networking Utilities  --->
    [*] telnet (8.8 kb)
    [*]   Pass TERM type to remote host
    [*]   Pass USER type to remote host (NEW)
    [*]   Enable window size autodetection (NEW)
    [*] telnetd (12 kb)
    [*]   Support standalone telnetd (not inetd only) (NEW)
    [*]   Support -w SEC option (inetd wait mode) (NEW)
    [*] tftp (11 kb)
    
  3. 编译telnet 或者 tftp 工具

    make -j 20
    make install
    
  4. 拷贝工具如下路径工具到单板端对应的目录

    _install/usr/bin/telnet
    _install/usr/sbin/telnetd
    _install/usr/bin/tftp
    

CPU占用率100%,如何优化

  1. 在单板上运行telnetd&,打开telnet服务。
  2. 运行应用程序,在程序正常运行的状态下(不要按CTRL+Z把程序放到后台),telnet登录单板。
  3. 在telnet终端上运行“top -d 1”,查看哪个pid的线程CPU占有率最高,记下这个值。
  4. 按‘q’退出top,或者另外再起一个telnet终端登录单板。
  5. 在telnet终端上运行gdb,不需要带任何参数,进入gdb命令行后,输入“attach pid”,这里的pid指步骤3中记录下的pid的值。Attach完成后,输入bt full,查看堆栈信息,一般情况下,堆栈的最顶端(最先打印出来的那几个函数)就是CPU占用最高的那个线程的函数。检查一下这个线程的循环里面是否有没有释放CPU的操作,加上usleep(20000)或者使用其他方式在这个线程里面出让CPU。
  6. 重新编译程序,验证CPU占用率是否有下降,如果没有,重复步骤2至步骤6。

如何查看和修改寄存器

/tools/board/reg-tools-1.0.0/bin目录中有一系列操作寄存器的工具,将它们传输至单板/usr/sbin目录下(并赋予执行权限),即可在单板命令行中使用。使用方法如下:

工具

参数

功能

备注

bspmm

参数1:address,必选

参数2:value,可选

把address地址对应的内容改写为value

如果没有输入value,bspmm会把address对应的值打印出来,然后提示输入新的值。

bspmd

参数1:address,必选

参数2:length,可选

从address开始,以大端方式打印length字节长度的内容。

如果没有输入length,那么默认打印256字节。

bspmd.l

参数1:address,必选

参数2:length,可选

从address开始,以小端方式打印length字节长度的内容。

如果没有输入length,那么默认打印256字节。

参数address既可以是寄存器地址,也可以是内存地址,所以以上工具也可以用于内存查看、修改。

如何使用DDR带宽统计工具bspddrs

bspddrs用于实时统计DDR的带宽使用情况,参数说明如下。

-d 表示DDR控制器,默认是0。0代表控制器0,1代表控制器1,以此类推。支持多个参数,例如0和1表示同时统计控制器0和控制器1。

-i 表示时间间隔,单位是毫秒。

-h 显示帮助信息。

同时统计控制器0-1示例:bspddrs -d 0 1 -i 1000。

打印详解:

ddrc[0] [0.00%], rcount[00000], wcount[00000]

ddrc[控制器0] [当前所有模块对控制器0的占用率], [当前所有模块对控制器0的读访问次数], [当前所有模块对控制器0的写访问次数]

如何让udhcpc占用更少的内存

这个问题的表面现象是用system调用的方式执行udhcpc会失败。解释:由于system是通过fork实现的,而子进程会复制父进程的VM空间,当父进程占用较多VM空间,很容易导致system调用失败。其本质是子进程分配VM空间失败导致的。

解决方法:执行:echo 1 > /proc/sys/vm/overcommit_memory即可。

更好的解决办法是不使用system调用方法,而是使用posix_spawn调用,简单的示例如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
    pid_t pid;
    int err;
    char *spawnedArgs[] = {"/bin/ls","-l","/home",NULL};/* posix_spawn需要指定子进程的命令的全路径(绝对路径) */
    char *spawnedEnv[] = {NULL};

    printf("Parent process id=%ld\n", getpid());
    if( (err=posix_spawn(&pid, spawnedArgs[0], NULL, NULL,
         spawnedArgs, spawnedEnv)) !=0 )
{
    fprintf(stderr,"posix_spawn() error=%d\n",err), exit(-1);
}
printf("Child process  id=%ld\n", pid);

    /* Wait for the spawned process to exit */
    (void)wait(NULL);
     return 0;
}

posix_spawn的更多用法,请自行上网搜索。

为什么有时候udhcpc无法获得IP地址

单板上的 udhcpc 默认只发起3次 IP 请求,有时候服务器响应比较慢,需要增加请求次数。

增加请求次数的命令参数为:“udhcpc -t 50”。

在U-Boot下如何获取DDR的容量大小

在启动时看启动log,如:

上图表示DDR size为0x80(128)M

如何解决 top 统计 CPU 占用率不准

top工具统计CPU占用的数据来源是/proc/stat信息,/proc/stat信息的更新源于tick时钟采样的中断处理,tick时钟采样点在哪个上下文,就会把这个tick的整体时间统计到对应的上下文中。另外中断处理时间统计的选项CONFIG_IRQ_TIME_ACCOUNTING默认没有打开,中断处理的时间被统计到前一个上下文中,可能是某个线程,也可能是idle。以上这些因素导致top工具统计的CPU占用率不准。

如果希望top统计比较准确的CPU占用,需要执行以下操作:

  • 统计中断处理的时间:打开内核的CONFIG_IRQ_TIME_ACCOUNTING选项统计中断处理的时间,或者开启中断线程化(在bootargs中增加threadirqs);
  • 增加tick中断采样的频率:打开内核的CONFIG_HIGH_RES_TIMERS高精度timer选项;

    注意上面打开的这些选项功能本身也会引起CPU消耗,仅推荐在调试的场景中使用。

mtd-utils交叉编译板端jffs2工具方法

因为版本的SDK包移除了zlib开源组件,所以要交叉编译板端jffs2工具,必须确保third_party/zlib目录下有zlib-1.2.12.tar.gz、zlib-1.2.12.patch、Makefile三个文件,可从旧版本的SDK包中获取,下面以Hi3519DV500项目为例编译jffs2工具。

  1. third_party目录下创建zlib目录,并将zlib-1.2.12.tar.gz、zlib-1.2.12.patch、Makefile三个文件放到zlib目录中,然后执行:

    cd third_party/zlib
    make OSDRV_CROSS=aarch64-v01c01-linux-musl  
    
  2. 进入到mtd-utils目录:

    cd ../mtd-utils
    
  3. 按照下面修改点修改mtd-utils目录下的Makefile:

    diff --git a/Makefile b/Makefile
    index c24ce7d..c9773d1 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -54,10 +54,11 @@ endif
            @echo $(BOARD_DIR)/$(TOOL_NAME)/configure
            test -f $(BOARD_DIR)/$(TOOL_NAME)/configure || ( pushd $(BOARD_DIR)/$(TOOL_NAME)/; ./autogen.sh; popd )
            cd $(BOARD_DIR)/$(TOOL_NAME)/ && \
    -               ./configure --without-ubifs --without-jffs2 --without-xattr --without-lzo --without-zstd --without-crypto \
    +               ./configure --without-ubifs --without-xattr --without-lzo --without-zstd --without-crypto \
                    --host=$(OSDRV_CROSS) $(CLANG_CROSS) CFLAGS="$(OSDRV_CROSS_CFLAGS) $(INCLUDE)" LDFLAGS="$(OSDRV_CROSS_LDFLAGS)" \
    -               --prefix=$(BOARD_DIR)/$(TOOL_NAME)/$(TOOL_BIN) --disable-tests && \
    -               make 1>/dev/null && \
    +               --prefix=$(BOARD_DIR)/$(TOOL_NAME)/$(TOOL_BIN) \
    +               ZLIB_LIBS=$(TOOL_DIR)/../zlib/out/board/lib/libz.a --disable-tests && \
    +               make CFLAGS=--static INCLUDES=-I$(TOOL_DIR)/../zlib/out/board/include 1>/dev/null && \
                    make install 1>/dev/null;
    
            cp -rf $(BOARD_DIR)/$(TOOL_NAME)/$(TOOL_BIN)/sbin/* $(BOARD_DIR)/$(TOOL_BIN)/
    
  4. 交叉编译板端jffs2工具:

    make OSDRV_CROSS=aarch64-v01c01-linux-musl
    

    注意:生成的jffs2工具在out/board/bin/路径下。

外设类

重新配置管脚复用后 I2C 首次传输失败

问题现象:重新配置管脚复用后 I2C 首次传输失败

原因分析:如果管脚复用配置错误,异常的电平信号会导致 I2C 控制器进入异常状态。控制器异常后,管脚复用重新配置正确,控制器的异常状态和对端设备的状态没有被清空,依然会导致 I2C 请求失败。

解决办法:1、把管脚复用配置正确。2、业务重新发起一次 I2C 请求,通过 I2C 驱动清空 I2C 控制器和 I2C 对端设备的状态。

如何使用USB键盘鼠标

Hi3516DV500/Hi3519DV500 内核默认情况下已经支持USB键盘鼠标,插入设备之后,就能直接使用,可参考如下第三方sample。

https://github.com/freedesktop-unofficial-mirror/evtest

Hi3516CV610 默认没有打开 USB 键鼠驱动,需要参考《外围设备驱动 操作指南》打开对应的设备。

为什么UDP接收或发送会丢包

  • 用户态应用程序在接收UDP数据时(单播或组播报文),同时进行其它有延时的操作(如写码流数据到USB存储设备), 应该程序将延迟接收UDP数据包,而socket默认接收缓存只有108544Byte,这样可能会使socket接收缓存满,无法接收新的UDP数据包,出现丢包现象。

    可在内核下通过执行下面命令进行确认:

    cat /proc/net/snmp | grep Udp
    

    如果RcvbufErrors字段增加较多,说明确实是socket接收缓存满导致的丢包。

    以下命令可以增加接收缓冲区,解决以上问题。

    echo 20000000 > /proc/sys/net/core/rmem_max
    echo 20000000 > /proc/sys/net/core/rmem_default
    echo 20000000 > /proc/sys/net/core/netdev_max_backlog
    

    这种改动,需要根据实际码流发送速度和接收程序的延时进行参数调优。

  • UDP发送可能会丢包,一种原因是CPU发送UDP报文的速率超过了网卡MAC的发包速率,导致网卡MAC的发送缓冲队列满,引起了丢包。

    可以在内核下通过执行下面命令进行确认:

    ifconfig eth0
    

    如果打印的信息中TX dropped和overruns值基本相等,都增加较多,说明是网络MAC的发送缓冲队列满导致的丢包。

    以下命令可以减小发送缓冲区,让CPU发包速率慢一点,解决以上问题。

    echo 20000 > /proc/sys/net/core/wmem_max
    echo 20000 > /proc/sys/net/core/wmem_default
    

    这种改动,需要根据码流发送速率和丢包率的要求进行参数调优。

GMAC网口ping不通

网口ping不通通常会有如下可能:

  • IP地址与其他设备的IP地址重复:

    现象:PC端ping单板延迟很大,时断时通;

    判断办法:PC端向单板发送ping包,同时拔掉单板对应网口的网线,看是否依然可以ping通。

  • MAC地址与其他设备的MAC地址重复:

    现象:PC端ping单板ping不通;

    判断办法:从PC端ping单板,同时在PC端使用arp –a命令查看PC端的arp列表。查看IP地址对应的MAC地址是否与单板上的MAC地址是对应的;

网络GRO介绍与使用

基本概念

GRO(generic-receive-offload)网络接口协议栈的特性在于将多个较小的接收数据包合并为一个较大的数据包,然后传递给内核IP协议栈。

开启GRO的方法

GRO功能可以通过ethtool工具查看或者控制。

查看当前GRO是否打开,执行ethtool -k eth0:

图 1 执行结果

须知:

图1中对应功能显示为 off [fixed], 则说明当前版本不支持此功能。

使能GRO:

ethtool -K eth0 gro on

关闭GRO:

ethtool -K eth0 gro off

应用程序使用socket接口时,如何正确工作在非阻塞模式下

基本概念

在网络编程中对于一个网络句柄会遇到阻塞IO 和非阻塞IO 的概念,这里对于这两种socket 先做一下说明。

  • 阻塞IO:socket 的阻塞模式意味着必须要做完IO 操作(包括错误)才会返回。
  • 非阻塞IO:非阻塞模式下无论操作是否完成都会立刻返回,需要通过其他方式来判断具体操作是否成功。

IO模式设置

对于一个socket 是阻塞模式还是非阻塞模式有两种方式来处理:

  • 方法1:fcntl 设置;用F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK;

    fcntl 函数可以将一个sockfd句柄设置成非阻塞模式:

    flags = fcntl(sockfd, F_GETFL, 0); //获取文件的flags值。

    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //设置成非阻塞模式;

    设置之后每次对于sockfd的操作都是非阻塞的。

    flags = fcntl(sockfd,F_GETFL,0);

    fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //设置成阻塞模式;

    设置之后每次对于sockfd的操作都是阻塞的。

  • 方法2:recv、send 系列的参数。(读取,发送时,临时将sockfd或filefd设置为非阻塞)

    recv、send 函数的最后有一个flag 参数可以设置成MSG_DONTWAIT

    临时将sockfd设置为非阻塞模式,而无论原有是阻塞还是非阻塞。

    recv(sockfd, buff, buff_size, MSG_DONTWAIT); //非阻塞模式的消息接受

    send(sockfd, buff, buff_size, MSG_DONTWAIT); //非阻塞模式的消息发送

demo板第一次执行网络操作的时候,会出现如下的打印:

No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
No such device: 0:2
PHY not link!

明明网线已经连接到单板上了,为什么还会报PHY not link呢?这是因为demo板上的PHY需要一些时间与对端设备的PHY进行工作模式以及速度的协商,完成后还要一定时间的复位。可能会导致插上网线后立即执行网络操作(ping、tftp或者其他操作)时出现如上所述的失败情况。只需等到单板PHY的电源指示灯(一般为绿色)变亮后,再执行网络操作即可。

为什么USB3.0口过流保护芯片去掉,会导致某些 USB3.0 U盘上电启动不识别

USB3.0口去掉过流保护芯片,发现一款台电科技的USB3.0的U盘插着上电后,不能被识别。造成这个问题的原因是控制器处于USB3.0的时间太短,USB3.0的U盘还未初始化完全导致识别失败。如图1所示。

图 1 USB3.0 U盘启动识别流程

去掉过流保护芯片的USB3.0控制器,会先进入USB2.0状态,再reset后进入USB3.0状态。这样,控制器处于USB3.0状态的时间T2远远小于在有过流保护芯片时直接进入USB3.0状态的时间T1,导致U盘的状态还没有准备好,最终host端发送命令失败。解决方法是在drivers/usb/core/hub.c文件中将HUB_ROOT_RESET_TIME的值延长为100即可:

#define HUB_ROOT_RESET_TIME     100;

I2C内核态接口原子操作注意事项

在I2C设备驱动中用到的内核态接口函数有i2c_master_send,i2c_master_recv和i2c_transfer,这几个接口函数内部都会根据原子或非原子操作申请不同的锁,代码位于drivers/i2c/i2c-core-base.c文件中,如图1所示。

图 1 内核态接口中申请锁的操作

调用I2C接口函数之前如果使用了原子锁或在中断中调用都会使当前操作处于原子操作中,如果处于原子操作会运行图1中上半部的if分支,否则会运行下半部的else分支。

须知:

在原子操作中会通过i2c_trylock_adapter(adap)来尝试请求锁,如果出错返回-EAGAIN,则表示没有得到锁,而不是I2C通信出现问题。这种情况下读写操作是没有执行的,对于要写的值没有写进去; 对于读操作值没有意义。因此需要进一步判断错误返回值是否等于-EAGAIN,如果是就要根据情况决定是否重复调用,以图2 I2C写为例进行说明。

图 2 写操作示例图

接USB摄像头拉流时内核输出错误日志的解决办法

当单板的USB口以Host模式,外接UVC摄像头拉流时,串口会报usb控制器(xHCI)的异常log

xhci-hcd ****: ERROR unknown event type 37

此外,可能还存在拉流过程中,内核空间CPU占用率较高的情况(通过top命令)。

针对上述两种情况,通过内核源码的修改,初步验证可解决相关问题。

【修改文件】

drivers/media/usb/uvc/uvc_video.c

【修改内容】

在文件的开头,定义CONFIG_DMA_NONCOHERENT宏。

 #define CONFIG_DMA_NONCOHERENT

对接部分有问题的U盘,可能会出现挂死

对接部分U盘,在利用mkfs命令格式化时,控制台挂死。进一步分析,发现系统并没有挂死,仅仅是mkfs进程进入D态。假若执行命令前,通过telnet等其他方式打开其他控制台,可发现:当执行mkfs的控制台无响应时,其他控制台正常。

再进一步分析,发现为U盘自身原因。mkfs命令格式化U盘时,会调用USB总线,对盘进行写操作,但U盘无响应,导致进程一直等待IO完成。该现象,在所有Linux内核的OS上均能复现。

USB口不供电,无法识别外设

Host模式,不识别USB外设,发现是端口没有供电,通过原理图检视,发现端口电源是通过SOC的USB_PWREN管脚控制。但是该管脚没有被正确配置管脚复用。

如何实现PCIe RC侧复位/解复位EP?

以Hi3511CV100 demo板为例说明,PCIe有专门的复位引脚PCIE_RST_N,可参考如下操作方式实现:

  • 准备点1:Hi3511CV100作为从片时,还需要另外修改跳帽和拨码,让主片的PCIe复位输出作为从片的系统复位输入。

    • 跳帽J1101 连接A2 A3,使得配置为PCIe reset input,让PCIe连接器上的复位脚作为PCIe复位输入:

    • 跳帽J401 连接A5 A6,使得配置为external pcie reset,将SOC系统复位源选择为PCIe复位:

    • 支持PCIe外部时钟和内部时钟两种方式启动,将拨码SW951[4] 拨到1即为外部时钟,将拨码SW951[4] 拨到0即为内部时钟。

  • 准备点2:Hi3511CV100作为主片时,还需要另外修改跳帽,让SOC的PCIe复位输出连接到PCIe连接器的复位脚上。

    跳帽J1101 连接A1 A2,使得配置为PCIe reset output,让SOC的PCIe复位输出连接到PCIe连接器的复位脚上:

  • 准备点3:Hi3511CV100对接Hi3511CV100时,RC侧需要复位/解复位EP,可以通过GPIO生成复位/解复位信号:

    mw 0x11014768 0x101; # 打开GPIO8_3 时钟(PCIE_RST_N)

    mw 0x11098400 0x8; # GPIO8_3 配置为输出方向

    mw 0x11098020 0x0; # GPIO8_3 拉低(输出低电压复位EP)

    mw 0x11098020 0x8; # GPIO8_3 拉高(输出高电压解复位EP)

    可以在RC侧将上述命令设置成 U-Boot 环境变量

    setenv reset_pcie1_dev 'mw 0x11014768 0x101;mw 0x11098400 0x8;mw 0x11098020 0x0;mw 0x11098020 0x8';sa

    启动时,调用环境变量复位 EP即可

    setenv bootcmd 'run reset_pcie1_dev ; nand read 0x41000000 0x100000 0x400000; bootm 0x41000000'; sa

为何RC需要复位EP

根据PCIe协议,在PCIe级联业务中,首次PCIe链路训练状态机进行扫描时,由于上电时间的差异,会出现建链失败或建链速率比预期低的情况。建议参考“如何实现PCIe RC侧复位/解复位EP?”章节,实现PCIe RC侧复位/解复位EP,配置复位EP,重新触发探测,以确保PCIe建链稳定。

说明:

在所有情况下均应该配置RC复位EP,否则可能出现建链状态不正确的情况。

如何查看PCIe自适应参数是否生效和修改自适应线程超时时间

PCIe自适应:在gen3模式下自动触发Link Equalization的初始自适应。

  • 在Linux下读取0x1034404C[19:8]的值,若为"e00", "b01", "701", "b33", "733", "312", "332", "333", "354", "357", "365"其中的值,则说明PCIe自适应已生效。示例如下:

  • PCIe自适应线程超时时间默认是5s,若需修改此时间,可调整drivers/pci/bsp_pcie/pcie_bsp_init.c文件中的PCIE_THREAD_OUT_TIME参数以更改超时时间。

说明:

PCIe自适应功能仅在Hi3511CV100的Gen3模式下支持。

SPI或UART使用DMA传输需增加DMA连续内存

Hi3516CV610或Hi3511CV100项目的SPI或UART配置工作模式为DMA时,需要在配置bootargs参数中加入"coherent_pool=1M"参数。

Flash

如何标记flash上的坏块

默认情况下,SDK的NAND flash读写函数已经内置了flash坏块处理策略,用户不需要关注。以下方法,仅仅用于用户想要强行标记flash某些块为坏块进行某种测试的场景。正常情况下,不需要使用这些方法。

  • U-boot 2022.07下标记坏块方法

    标记NAND坏块的命令如下:

    nand markbad offset
    

    该命令会标记offset位置所在的NAND块为坏块,如希望标记1M位置的块为坏块,命令如下:

    nand markbad 0x100000
    

    offset最好为NAND的块大小的整数倍。标记坏块后可以用如下命令查看NAND坏块:

    nand bad
    
  • 内核下标记坏块方法

    相关代码如下:

    #define MEMSETBADBLOCK _IOW('M', 12, __kernel_loff_t)
    int fd;
    unsigned long long offset;
    fd = open("/dev/mtd1", O_RDWR);
    offset = 0x100000;
    if (ioctl(fd, MEMSETBADBLOCK, &offset))
    {
        printf("Mark bad block 0x%llX failed!\n", offset);
    }
    

    该段程序会标记open的mtd分区的offset偏移位置所在的NAND块为坏块;

    如希望标记mtd1分区偏移1M位置的块为坏块,需要在open函数时指定mtd1(为对应分区的字符设备节点),

    设置offset为0x100000,如上段代码所示。

注意:U-boot 2022.07下的offset是相对于整个NAND的偏移位置,内核下的offset是相对open的对应分区的偏移位置。

如何正确使用mtd-utils的nandwrite裸写工具

使用mtd-utils的nandwrite裸写工具时,如果写的是u-boot.bin镜像,且镜像大小大于Nand Flash的一个块的大小,一定要保证u-boot.bin镜像的数据按块对齐填充。否则,写进去的u-boot.bin镜像无法正常启动。

具体原因如下:

由于个别Nand Flash出厂时坏块标记位(BB,Bad Block)被标记为非全0的数,例如0xFE,在判断坏块的时候,容易被FMC控制器利用ECC纠错为0xFF(因为全0xFF在控制器的ECC算法上是合法可纠错的),故在每一个page的OOB信息的最后两个byte设置空块标记(EB,Empty Block)位。如图1所示,在u-boot启动时,逻辑上把block视为好块的前提条件是:block的第一个page 1和最后一个page N的BB=0xFF, EB=0x00。

图 1 Nand Flash 块结构图

nandwrite是根据镜像文件大小逐页写数据的,并且写page时,会自动将当页的EB位配置为0x00。当写的最后一个page不是所在block的最后一个page时,由于所在block最后一个page的EB=0xFF,逻辑就是视这个block为空块不会去读数据,导致U-Boot启动失败。

值得注意的是,由于Nand Flash器件出厂时保证第一个块为好块,故逻辑不会去判断第一个块的情况,所以当u-boot.bin镜像大小小于一个块大小时,U-Boot是可以正常启动的。

此外,一旦U-Boot正常启动,软件上我们不会再去判断EB位,这也是为什么使用nandwrite写内核镜像和文件系统镜像时可以不考虑镜像大小是否块对齐。

有些Flash器件ID不变工艺更新导致参数变化对兼容性的影响

伴随着Flash(SPI Nor/SPI Nand/Parallel Nand)工艺的不断更新,Flash器件的接口、OOB和性能等参数在不断的改变和优化。但是,某些厂家为了图方便,虽然Flash制作工艺升级了,但是Flash ID没有变化。而我们的驱动是通过ID去识别器件的,不会通过厂家建议的SFDP寄存器去识别器件的批次、工艺等信息,因为SFDP不是标准的,各个厂家存在差异,即使同一个厂家,新老器件也会有差异。

因此,底下列出的Flash器件都是ID一致,参数不一致导致影响兼容性的几种情况。假如客户遇到以下相同情况,可以参考下面的例子进行修改驱动,从而保证器件有正常的功能和优越的性能。

  • SPI Nor Flash 的ID不变,接口类型变化

    工艺迭代之后,ID一致,但是新工艺器件接口类型增加了2x I/O Read Mode/4x I/O Read Mode/4x I/O Page Program的支持,如下所示:

    工艺迭代前,fmc_spi_nor_ids.c文件中的器件信息结构体fmc_spi_nor_info_table应该定义为:

    工艺迭代后,fmc_spi_nor_ids.c文件中的器件信息结构体fmc_spi_nor_info_table应该定义为:

  • SPI Nor Flash 的ID不变,命令字变化

    相同ID的不同器件,写命令出现差异,命令字有38h和12h的差别

    对于命令字是38h的器件,可以直接通过配置WRITE_QUAD_ADDR使用,因为现在驱动默认是匹配38h的命令。

    如果用的是命令字为12h的器件,可以修改fmc_spi_ids.h头文件中的SPI_CMD_WRITE_QUAD_ADDR命令定义来使用这种接口类型。

  • Parallel Nand的 ID不变,OOB变化

    ID一样,但是OOB Size不一样。器件工艺迭代之前为64Byte,而升级之后OOB为128Byte,使用匹配64Byte的驱动会导致128Byte器件启动失败。

    如果想使用OOB大小为64Byte的器件一定要确保fmc_nand_spl_ids.c中nand_flash_special_dev结构体的器件信息定义参数.oobsize=64。

    如果是用OOB大小为128Byte的器件,要确保.oobsize=128,从而保证器件稳定性。

如何支持Flash双片选功能

  • 在U-Boot下支持双片选功能:

    以Hi3519DV500为例,想要再增加一个Flash,需要在include/configs/hi3519dv500.h文件下增加flash片选支持数量,如下修改:

    --- a/include/configs/hi3519dv500.h
    +++ b/include/configs/hi3519dv500.h
    @@ -73,12 +73,12 @@
     #ifdef CONFIG_FMC
     #define CONFIG_FMC_REG_BASE            FMC_REG_BASE
     #define CONFIG_FMC_BUFFER_BASE FMC_MEM_BASE
    -#define CONFIG_FMC_MAX_CS_NUM          1
    +#define CONFIG_FMC_MAX_CS_NUM          2
     #endif
    
     #ifdef CONFIG_FMC_SPI_NOR
     #define CONFIG_CMD_SF
    -#define CONFIG_SPI_NOR_MAX_CHIP_NUM    1
    +#define CONFIG_SPI_NOR_MAX_CHIP_NUM    2
     #define CONFIG_SPI_NOR_QUIET_TEST
     #endif
    
  • 在内核下支持双片选功能:

从Linux 3.18内核开始,SPI Nor Flash驱动适配SPI Nor标准驱动框架,且Soc和board级的拓扑结构统一在DTS(设备树)文件上进行描述。默认都只支持单片SPI Nor Flash。

以Hi3519DV500为例,若想要支持两颗SPI Nor Flash,就要在board级DTS文件中增加一个SPI Nor器件结点。在arch/arm64/boot/dts/vendor/hi3519dv500-demb.dts文件中,做如下修改:

--- a/arch/arm64/boot/dts/vendor/hi3519dv500-demb.dts
+++ b/arch/arm64/boot/dts/vendor/hi3519dv500-demb.dts
@@ -267,7 +267,7 @@ &gpio_chip14 {
 };

 &sfc {
-       sfc@0 {
+       sfc_0@0 {
                compatible = "jedec,spi-nor";
                reg = <0>;
                spi-max-frequency = <200000000>;
        };

+       sfc_1@1 {
+               compatible = "jedec,spi-nor";
+               reg = <1>;
+               spi-max-frequency = <200000000>;
+       };
};
 &snfc {
        nand@0 {

需要注意以下几点:

  • Device Node命名必须要区分,比如使用sfc_0和sfc_1;
  • Chipselect片选号必须指定;
  • 设备结点增加好之后,分区信息要依据Device Node命名指定:

    mtdparts=sfc_0:1M(mtd0),4M(mtd1);sfc_1:4M(mtd2),11M(mtd3)

如果是想要支持一颗SPI Nor+一颗SPI Nand,则不需要修改DTS文件下的节点,但需要注意,分区信息要依据Device Node命名指定:

mtdparts=sfc:1M(mtd0),4M(mtd1);nand:4M(mtd2),11M(mtd3)

须知:

Hi3516CV610的双片选支持版型有两种。其中QFN版型的IPTESTB2单板,由于SPI和SDIO0管脚复用冲突了。所以想支持双片选模式,需要在boards/dmeb/dts/hi3516cv610-demb-flash.dts文件中关闭sdio0的节点,修改如下:

&sdio0 {

-       status = "okay";
+       status = "disabled";
};

为何当Fastboot镜像不满足block对齐时按页写入flash系统无法启动

Fastboot镜像必须要按block对齐写入Flash;当Fastboot镜像不满足block对齐时,未对齐block的尾部page数据为擦除数据0xFF。而启动时FMC控制器处于boot模式,逻辑内部会将该块判断为坏块,从而导致读取数据错误启动失败,相关FMC逻辑对坏块判定的条件可见芯片手册 "Flash Memory 控制器" 章节。

文件系统

NAND挂载cramfs/squashfs文件系统注意事项

在 nand上使用cramfs/squashfs时,因cramfs/squashfs不是专门为nand器件设计,所以文件系统本身不能跳坏块,可以使用ubifs+cramfs/squashfs方式识别坏块,以下为ubifs+squashfs步骤示例:

  1. 制作squashfs文件系统:rootfs.squashfs

    ./mksquashfs ./rootfs ./rootfs.squashfs -b 128K -comp gzip;
    

    表 1 参数说明

    参数

    描述

    ./rootfs

    输入文件,已经制作好的根文件系统

    ./rootfs.squashfs

    输出文件,生成的squashfs文件系统映像文件

    -b 128K

    squashfs文件系统的块大小为128K(决定于实际spi flash块大小)

    -comp gzip

    指定文件系统压缩方式为gzip

  2. 配置 ubinize.cfg文件参数,将以下内容放入新建的ubinize.cfg文件中即可。

    [squashfs]

    mode=ubi
    image=./rootfs.squashfs
    vol_id=0
    vol_type=dynamic
    vol_name=squashfs
    vol_flags=autoresize
    

    表 2 参数说明

    参数

    描述

    mode=ubi

    是强制参数, 当前不能输入别的值, 保留为以后扩展功能;

    image=./rootfs.squashfs

    表示卷对应的 UBIFS 文件系统镜像文件名称

    vol_id=0

    表示卷的ID号, UBI镜像可能包含多个卷, 这个用来区别不同的卷。

    vol_type=dynamic

    表示当前卷类型是可读写的。如果此UBI卷类型为只读,对应的参数应该为“vol_type=static”;

    vol_name=squashfs

    表示卷的名称, squashfs做根文件系统时, 将用到卷名称。

    vol_flags=autoresize

    表示卷大小是可以动态扩展。

  3. 将rootfs.squashfs打包为uifs格式。

    ubinize -o ubi-rootfs.squashfs -m 2048 -p 128KiB ubinize.cfg

    表 3 参数说明

    参数

    描述

    -o ubi-rootfs.squashfs

    输出的UBI镜像转换后的名称为“ubi-rootfs.squashfs”,输入的UBI镜像文件名由ubinize.cfg配置文件输入。

    -m 2048

    最小读写单元是“2KiB” 。

    -p 128KiB

    flash的擦除块大小。注意这是物理擦除大小, 不是逻辑擦除块大小。

    ubinize.cfg

    配置文件

  4. 烧写ubi-rootfs.squashfs至介质文件系统分区,分区信息可参考步骤6中bootargs 参数。

  5. 内核编译时开启CONFIG_MTD_UBI_BLOCK,menuconfig编译时参照如下选项开启:

    Device Drivers  --->
        <*> Memory Technology Device (MTD) support  --->
             <*> Enable UBI - Unsorted block images  --->
                  [*]   Read-only block devices on top of UBI volumes
    
  6. u-boot配置ubi-rootfs.squashfs启动环境变量。

    setenv bootargs 'mem=132928K console=ttyAMA0,115200 clk_ignore_unused root=/dev/ubiblock0_0 ubi.block=0,0 ubi.mtd=5 rootfstype=squashfs ro mtdparts=nand:512K(boot),512K(env),512K(bl31),13M(kernel),32M(rootfs),32M(ubi_squashfs)';save
    setenv bootcmd 'nand read 0x40010000 0x100000 0x80000; ipcboot bl31 0x40010000; nand read 0x44000000 0x180000 0xd00000; ipcboot uimgfdt 0x44000000';save
    

    表 4 参数说明

    参数

    描述

    ubi.mtd=5

    表示UBI绑定到“/dev/mtd5”分区,通过内核打印信息可确认加载的根文件系统是否为mtd5分区的ubifs;

    ubi.block=0,0

    表示使用ubi设备/dev/ubi0_0来创建ubiblock设备/dev/ubiblock0_0;

    root=/dev/ubiblock0_0

    使用ubiblock设备/dev/ubiblock0_0用于根文件系统挂载;

    rootfstype=squashfs

    表示文件系统类型为squashfs 文件。

    须知:

    以上所用工具可以使用SDK版本版本包中自带工具,具体位置为:boards/dmeb/pub/bin/board_xxx_arm64(xxx代表为musl或glibc,取决于选择的工具链类型)

eMMC加载动态库注意事项

部分项目使用的uclibc,加载动态库或rootfs的时候,对分区有限制,需要在主分区才能正常加载,默认eMMC驱动中CONFIG_MMC_BLOCK_MINORS = 8即最高支持7个主分区(额外1个表示整个磁盘),建议将动态库或者rootfs放在前面7个分区,如果需要放在第八分区及以上(如mmcblk0p8)需要将CONFIG_MMC_BLOCK_MINORS根据需要适当改大。

Device Drivers  ---> 
        <*> MMC/SD/SDIO card support  --->
               --- MMC/SD/SDIO card support
             (8)     Number of minors per block device

如何修改rootfs分区默认size

下面以Hi3511CV100 DEMO板Nand器件使用的默认分区为例,对如何将UBI类型的rootfs分区默认size从32M调整到500M进行指导说明:

  1. 将bsp/tools/pc/uboot_env/env_text/hi3511cv100/nand_env.txt文件中,

    原来:

    bootargs=mem=128m console=ttyAMA0,115200 clk_ignore_unused root=ubi0:ubifs rootfstype=ubifs rw ubi.mtd=3 mtdparts=nand:512K(boot),512K(env),4M(kernel),32M(rootfs)
    

    改成:

    bootargs=mem=128m console=ttyAMA0,115200 clk_ignore_unused root=ubi0:ubifs rootfstype=ubifs rw ubi.mtd=3 mtdparts=nand:512K(boot),512K(env),4M(kernel),500M(rootfs)
    
  2. 将bsp/tools/pc/uboot_env/scripts/env2burn.py文件中,

    原来:

    if size == 0x2000000: #32M
    return 'rootfs_%s_2k_128k_32M.ubifs' % plat
    

    改成:

    if size == 0x1F400000: #500M
    return 'rootfs_%s_2k_128k_500M.ubifs' % plat
    
  3. 将bsp/Makefile文件中,

    原来:

    # build the pagesize = 2k, blocksize = 128k, part_size = 32M #
    pushd $(OSDRV_DIR)/pub/$(PUB_IMAGE);fakeroot ./mkubiimg.sh $(CHIP) 2k 128k $(OSDRV_DIR)/pub/$(PUB_ROOTFS) 32M $(OSDRV_DIR)/pub/bin/pc;popd
    

    改成:

    # build the pagesize = 2k, blocksize = 128k, part_size = 500M #
    pushd $(OSDRV_DIR)/pub/$(PUB_IMAGE);fakeroot ./mkubiimg.sh $(CHIP) 2k 128k $(OSDRV_DIR)/pub/$(PUB_ROOTFS) 500M $(OSDRV_DIR)/pub/bin/pc;popd
    

    修改后编译小系统镜像,得到的nand_burn_table.xml中便会选择rootfs_hi3511cv100_2k_128k_500M.ubifs作为默认rootfs分区。

快速启动优化

  • 配置boot下的环境变量bootdelay为0

    • 方法:在boot下的命令行中输入:setenv bootdelay 0;saveenv
    • 说明:为了方便进入boot命令行,boot下默认设置bootdelay为1,配置bootdelay为0可以加快fastboot启动时间约1s(boot中已修改代码配置默认值为0)
  • 配置boot阶段不做内核校验

    • 方法:在boot下的命令行中输入:setenv verify n;saveenv
    • 说明:如果内核出错,在boot阶段做不做校验,系统基本都会挂死,因此设置不做校验理论上不会产生影响,该操作可加快启动时间约1s(boot中已修改代码配置默认值为不做校验)
  • 设置bootcmd命令

    • 方法:配置 setenv bootcmd 'nand read 0x807fffc0 0x100000 0x400000;bootm 0x807fffc0'
    • 说明:设置成上述bootcmd后,boot直接将内核镜像从flash读到0x807fffc0,然后从0x807fffc0启动。相反如果按照默认bootcmd配置: nand read 0x82000000 0x100000 0x400000;bootm 0x82000000,则boot阶段先将kernel从flash读到0x82000000地址,再将镜像从0x82000000拷贝至更低地址0x807fffc0,然后从0x807fffc0启动。

Kernel

pid和tgid区别

task_struct结构体包含两个字段,pid和tgid。

简单理解: pid为该task_struct唯一的id号。tgid是该task所在线程组的组长id号,thread group id,若它没组长,它自己就是组长,则它的tgid = pid。tgid的存在为了兼容posix标准。

getpid() return tgid。

In normal processes, the TGID is equal to the PID.

With threads, the TGID is the same for all threads in a thread group. This enables the threads to call getpid() and get the same PID。

In fact, the POSIX 1003.1c standard states that all threads of a multithreaded application must have the same PID。

普通进程和实时进程优先级

进程优先级范围

0---139

实时进程优先级

0---99

普通进程优先级

100—139

nice对应普通进程

-20——19 <--> 100——139

内核默认进程优先级为

120—对应nice=0

如何设置dmesg buf的大小

在 menuconfig 菜单下, 选择以下选项:

General setup  --->  Kernel log buffer size
CONFIG_LOG_BUF_SHIFT(例如18 =256K)

遇到“段错误”(segmentation fault)怎样生成core dump文件来进行问题分析

可以在shell下通过设置如下命令来生成core dump文件:

ulimit -S -c unlimited > /dev/null 2>&1

但是要能在core dump文件中正常显示出错误信息,则编译可执行文件时,还必须用 -g调试选项来编译生成文件。

客户跑一个很简单的程序,top信息的loadaverage值比较大,达到2.95,而CPU的占用率比较低是否正常

loadaverage的值是衡量CPU等待完成的任务排队的情况。请解释该值过高的原因,是否影响业务?

top命令中load average显示的是最近1分钟、5分钟和15分钟的系统平均负载。系统平均负载表示:

系统平均负载被定义为在特定时间间隔内运行队列中(在CPU上运行或者等待运行多少进程)的平均进程数。如果一个进程满足以下条件则其就会位于运行队列中:

  • 它没有在等待I/O操作的结果
  • 它没有主动进入等待状态(也就是没有调用’wait’)
  • 没有被停止(例如:等待终止)

Update:在Linux中,进程分为三种状态,一种是阻塞的进程blocked process,一种是可运行的进程runnable process,另外就是正在运行的进程running process。当进程阻塞时,进程会等待I/O设备的数据或者系统调用。

进程可运行状态时,它处在一个运行队列run queue中,与其他可运行进程争夺CPU时间。系统的load是指正在运行running one和准备好运行runnable one的进程的总数。比如现在系统有2个正在运行的进程,3个可运行进程,那么系统的load就是5。load average就是一定时间内的load数量。

例如:

?[Copy to clipboard]View Code BASH

1

2

3

# uptime

7:51pm up 2 days, 5:43, 2 users, load average: 8.13, 5.90, 4.94

命令输出的最后内容表示在过去的1、5、15分钟内运行队列中的平均进程数量。

一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,若每个CPU的任务数在5以内,也是可以接受的。当每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。对于上面的例子来说,假设系统有两个CPU,那么其每个CPU的当前任务数为:8.13/2=4.065。这表示该系统的性能是可以接受的。

从以上的解释可以看出我们每个CPU的当前任务数为2.95/2=1.475,因此我认为是很正常的!

GDB不能支持多线程调试的问题

现象:(通过GDB调试一个多线程的测试程序test):

./gdb test
GNU gdb (GDB) 7.9.1
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "aarch64-mix100-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...done.
(gdb) 
(gdb) r
Starting program: /var/test 
warning: File "/lib64/libthread_db.so.1" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path /lib64/libthread_db.so.1
line to your configuration file "//.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "//.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
thread1
thread11
thread2
thread3

Program received signal SIGINT, Interrupt.
0x0000007fb7efb3a4 in nanosleep () from /lib64/libc.so.6
(gdb) info b
(gdb) info thread
Id   Target Id         Frame 
* 1    LWP 1387 "test"   0x0000007fb7efb3a4 in nanosleep ()
from /lib64/libc.so.6
(gdb)

如最后三行的提示,线程调试不可用。

问题的原因:为了减少默认发布版本文件系统的大小,将板端的动态库全部进行strip操作,其中当然也包括线程调试库libthread_db.so.1。使用被strip后的线程库将影响板端GDB的线程调试。

解决方法:从对应工具链的安装目录(服务器上)找出该线程库(未strip过的),替换原来strip过的线程库。具体可参考如下操作:

$ cd /opt/linux/x86-arm/aarch64-xxxx-linux/target/lib
$ find . -name "libpthread*.so"
./libpthread-x.xx.so

用户可根据实际环境的情况,用该文件替换原来已经strip过的动态库,以支持多线程调试。

关闭CONFIG_NO_HZ选项之后,CPU占用率升高了

使能CONFIG_NO_HZ对于那些有可能长时间待机的设备来说,可以显著的降低设备功耗,延长电池使用时间。但对于那些有恒定的业务负载或者实时响应要求很高的应用场景,CONFIG_NO_HZ模式反而会因为在正常任务和idle循环之间的切换过程复杂,造成较大的系统时延。考虑到监测后端业务对于电源功耗不太敏感,新版本的Linux内核默认都不打开CONFIG_NO_HZ。

在一些场景中,关闭了CONFIG_NO_HZ,会影响到top 命令下CPU占用率的统计结果。比如在同一个环境下,使用 iperf 工具进行网络性能测试时,关闭CONFIG_NO_HZ的内核版本会比打开CONFIG_NO_HZ的内核版本消耗更多的CPU占用率。但这不是真的CPU占用率上升了,它是CONFIG_NO_HZ 选项带来的CPU占用率统计上的误差。网络传输中有大量的中断和软中断处理,CONFIG_NO_HZ会大幅减少调度中断的触发,CPU时间更新很不及时,系统对各个任务的CPU占用率采样次数也大幅减少,从而影响了统计结果的准确性。用户可以通过打开内核选项CONFIG_IRQ_TIME_ACCOUNTING(默认关闭)来获得更准确的统计结果。

为何将用户态线程的栈大小设置为16K时,线程会出现Segmentation Fault

动态连接的用户程序在开始执行之前,需要通过动态解析器对相关的外部符号地址进行重新定位,而动态解析器运行时也是使用所在进程(线程)的栈。在glibc-2.24版本中,动态解析器ld-2.24.so(实际是函数__dl_fixup)执行时会默认将SP指针向下移动16KB,以预留内存给符号解析使用。而当用户把进程(线程)的栈设置为16KB(或者略大于16KB)时,这个动作将导致线程出现Segmentation Fault。当前版本的动态解析器对栈的使用和Linux 32位系统中用户线程栈的最小值为16KB有些不一致。当前问题没有其它方面的影响,而C库中相关的改动涉及多个文件,容易引入其它问题,因此暂不做修改。

建议:

  • 用户态进程(线程)栈大小最小值不小于32KB;
  • 如果必须使用最小16KB的用户态进程(线程)栈,用户可以通过线程库提供的接口自己分配线程栈内存。

Kswapd线程的 CPU占用率陡然升高的原因

在某些业务场景下,Linux系统中kswapd线程CPU占用率突然升高,且长时间保持很高的CPU占用率,如图1所示。

图 1 CPU占用率升高

内核的启动信息,如图2所示。

图 2 内核启动打印

系统内存为125MB,远远没有到达系统low memory 和 vmalloc空间的阈值,但启动信息出现了1MB的Highmem。

Kswapd的异常升高和系统保持Highmem这个zone的内存平衡有直接关系。当Highmem这个区域内存使用超过一定阈值后,kswapd就被激活,以回收Highmem区域的内存。

  • 出现Highmem区域的条件:

    • Linux内核中使能了CONFIG_HIGHMEM选项;
    • 系统内存大小(单位:MB)配置为奇数的时候,如:mem=125M。
  • 解决方案:

    • 方案1:Bootargs中系统内存大小配置为2MB对齐(推荐);
    • 方案2:关闭CONFIG_HIGHMEM选项。

Linux-5.10.y内核中,使用“cat /proc/vmallocinfo”命令查看vmalloc信息,地址信息显示为“0x(____ptrval____)”的原因

Linux-5.10.y内核中,为提高安全性,对于内核中以“%p”格式打印的地址信息,默认以“____ptrval____”字符串显示,因此,执行“cat /proc/vmallocinfo”命令,地址信息显示为“0x(____ptrval____)”。

如果要使用“cat /proc/vmallocinfo”命令查看vmalloc地址信息,需修改mm/vmalloc.c代码,修改方法如图1所示。

图 1 修改方法

注意:此修改方法仅适用于调试版本,正式发布版本不可这样修改。

ARM64 flush data cache说明

Linux-4.4内核之前,提供了flush_cache_all接口,该接口通过遍历way/set,完成cache的clean和invalid操作。但之后Linux社区认为通过set/way操作cache,只能作用于本地核,和其他核的cache操作存在竞态风险,所以社区在2015年删除了flush_cache_all和其他相关接口。推荐使用__flush_dcache_area接口,该接口通过虚拟地址flush cache,可以确保cache的PoC一致性。

__flush_dcache_area接口操作的内存大小超过一定范围时,耗时较长,如用户想继续使用社区flush_cache_all接口,则需自行恢复社区代码,并自行根据业务场景验证其正确性。

社区删除flush_cache_all的提交,请参考链接:https://github.com/torvalds/linux/commit/68234df4ea7939f98431aa81113fbdce10c4a84b#diff-1e4a7952678f25f16e932574cb1703c7ccd3b0e6d327983275de07c96a7c8b7e

社区删除flush_cache_all邮件讨论,请参考链接:https://patchwork.kernel.org/project/linux-arm-kernel/patch/1429521875-16893-1-git-send-email-mark.rutland@arm.com/

工具链

工具链glibc库升级到2.29版本后,应用程序产生“内存空洞”问题的解决方法

现象描述

某些应用程序频繁调用malloc函数申请内存空间,且申请空间的大小差别比较大,使用完成后通过free函数释放内存空间,但内存空间依然缓存在glibc中,没有归还操作系统,导致系统内存不足。

原因分析

Glibc中进程的内存分配由两个系统调用完成:brk和mmap。

  • brk是将数据段(.data)的最高地址指针_edata往高地址推;brk分配的内存需要等到高地址内存释放以后才能释放;

    如果先后通过brk申请了A和B两块内存,在B释放之前,A是不可能释放的,仍然被进程占用,通过TOP查看疑似“内存泄漏”。

  • mmap是在进程的虚拟地址空间中申请一块空闲的空间。mmap分配的内存由munmap释放,内存释放时将立即归还操作系统。

默认情况下,大于等于128KB的内存分配会调用mmap/munmap,小于128KB的内存请求调用brk,但可以通过修改M_MMAP_THRESHOLD值来调整。

另外,Glibc2.29有一个新特性:M_MMAP_THRESHOLD可以动态调整。M_MMAP_THRESHOLD的值在128KB到32MB(32位机)或者64MB(64位机)之间动态调整,如:当申请并释放一个大小为2MB的内存后,M_MMAP_THRESHOLD的值被调整为2MB到2MB + 4KB之间的一个值。

因此,当应用程序中申请的内存空间数量多,且先后申请的内存空间的大小变化比较大时,在申请一段大的内存后,M_MMAP_THRESHOLD的值被调大,后续的内存申请空间大小 < M_MMAP_THRESHOLD时将使用brk申请,而brk需要等到高地址内存释放以后,低地址内存才能释放。当应用程序没有释放高地址内存时,就导致大量低地址内存空间不能及时释放,从而产生“内存空洞”,导致系统内存不足。

解决方法

  • 在进程启动时,使用int mallopt(int param,int value)函数显式地设置M_MMAP_THRESHOLD的值为128K,关闭M_MMAP_THRESHOLD动态调整特性。
  • 优化应用程序中内存管理方式,不要频繁申请、释放内存空间,减少内存碎片。