硬件平台MCU:imx6qdl,4核
Linux kernel:fsl-yocto-L4.1.15
编译器版本:fsl-imx-fb
功能需求:
以上问题的现象总结起来就是,在每路视频帧率达到每秒30帧的情况下,视频推流出现延迟以及卡顿,
经过验证,存储的文件没有卡顿以及延迟,说明代码的整体逻辑并没有问题
网络丢包现象是查看 ifconfig eth0
RX packets 3553389376 bytes 2599862532475 (2.3 TiB)
RX errors 8745827 dropped 0 overruns 8745827 frame 0
TX packets 3479495131 bytes 3205366800850 (2.9 TiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
出现overrun,网上搜索结果说是网卡丢包是因为设置的缓存区(ring buffer)太小,或者中断过于频繁,导数据来不及进入MAC的ringbuffer,出现overrun
所有的代码都运行起来后通过top命令查看CPU的运行状况,发现CPU的总计占有率为130%左右, 4核CPU嘛,也不算高,怎么视频就延迟和卡顿了呢
灵光一闪,总计CPU占有率是不高,但是都有哪些CPU占着呢,在top界面下按“1”,出现4个CPU占有率的情况,CPU0为125%,其它CPU都为0,感情活都让CPU0一个人干了,其它几个都闲着呢
那好咱就接着查看中断都是谁响应的吧,cat /proc/interrupts命令查看,我操发现所有的中断都是CPU0响应的,
而且发现跟eth0相关的290号中断特真的不少,是不是CPU0太忙,来不及响应网卡中断,导致overrun呢
进一步猜想,是不是CPU0太忙导致视频推送不出去,延迟累积,并且多路推送不平衡,出现视频卡顿呢
问题解决要一步一来,首先解决网络丢包的问题吧
在没具体理解overrun之前,曾经这样尝试:
linux 系统在接收报文之后,会把报文保存到缓存区中。因为缓存区的大小是有限的,如果出现 UDP 报文过大(超过缓存区大小或者 MTU 大小)、接收到报文的速率太快,都可能导致 linux 因为缓存满而直接丢包的情况。在系统层面,linux 设置了 receive buffer 可以配置的最大值,可以在下面的文件中查看,一般是 linux 在启动的时候会根据内存大小设置一个初始值。
/proc/sys/net/core/rmem_max:允许设置的 receive buffer 最大值
/proc/sys/net/core/rmem_default:默认使用的 receive buffer 值
/proc/sys/net/core/wmem_max:允许设置的 send buffer 最大值
/proc/sys/net/core/wmem_dafault:默认使用的 send buffer 最大值
但是这些初始值并不是为了应对大流量的 UDP 报文,如果应用程序接收和发送 UDP 报文非常多,需要讲这个值调大。可以使用 sysctl 命令让它立即生效:
sysctl -w net.core.rmem_max=26214400 # 设置为 25M
另外一个可以配置的参数是 netdev_max_backlog,它表示 linux 内核从网卡驱动中读取报文后可以缓存的报文数量,默认是 1000,可以调大这个值,比如设置成 2000:
sudo sysctl -w net.core.netdev_max_backlog=2000
经过这些调整,发现情况并没改善,而且进一步验证,只有在发送大于5000字节以上的包时,才会出现丢包现象,IMX6的以太网配置可是千兆的,外部是RTL8365,4个端口的PHY。
进一步验证发现是在IMX6与TX2通信时,大于5000字节以上的包会出现丢包现象,而当与外部的百兆网通信时,却没有问题。
那么只能说明imx6配置的RTL8365的千兆网配置有问题,这样解决这个问题的办法有两种:
这两条路我都试过了,将IMX6与RTL8365的自适应配置成百兆网以后,使用gstreamer的RTP元件进行视频推送,没有出现丢包现象。
但是我最终选择的是第二条路,自己写套接字,控制收发过程,还好我这样选择了,因为后来又需要将imx6的一些数据回传给TX2,使用RTP库是做不到的。
另外再将中断负载均衡到各个CPU,尤其是290好的eth0中断,echo 2 > /proc/irq/290/smp_affinity
,imx6为4核CPU,因此前面的数字只能是1,2, 4, 8,f,分别表示CPU0, CPU1, CPU2,CPU3,或者全部CPU
既然在问题分析步骤已经发现,是CPU0占用率过高,而其它CPU几乎不干活情况下,出现这两问题,那么我就自然想到两个办法,
既然要做CPU负荷均衡,那就要好好规划一下都有哪些任务,经过具体分析,任务可分为一下集中
经过以上分析后,遂决定CPU的分配如下:
cpu_set_t mask;
//CPU_ZERO(&mask);
//CPU_SET(cpu_id, &mask);
__CPU_ZERO_S (sizeof (cpu_set_t), &mask);
__CPU_SET_S (0, sizeof (cpu_set_t), &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) < 0)
{
printf("set affinity error!\n");
return -1;
}
这一步只是将主进程绑定到CPU0,进程绑定CPU与线程绑定CPU可以分开,线程绑定CPU需进行如下操作
cpu_set_t mask;
__CPU_ZERO_S (sizeof (cpu_set_t), &mask);
__CPU_SET_S (2, sizeof (cpu_set_t), &mask);
pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask);
// 更改第3行代码中的数字2,即可绑定到其它CPU,对于imx6来说可选择的数字为:0,1,2,3
注意:以上头文件的包含可能会造成以下警告编译信息
warning: implicit declaration of function 'pthread_setaffinity_np'
[-Wimplicit-function-declaration]
pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask)
那么正确的头文件包含顺序为:
#define _GNU_SOURCE
#include
#include
#include
这样就不会产生编译告警信息,为什么不需要定义#define __USE_GNU
这个宏呢?因为定义#define _GNU_SOURCE
宏之后,会在stdio.h文件包含的features.h中,再定义#define __USE_GNU
这个宏。
经过这样的CPU绑定后,每个CPU各司其职,通过top命令查看,除去360环视需要消耗较多的CPU资源占用率在70%左右,其它3个CPU的占用率均在15%左右,没有明显的差异,不会出现无活可干的CPU。
经过以上整改,再次运行所有代码进行测试,视频延迟与卡顿的现象消失,而且不会出现视频的累积延迟,测试时间达到18小时,延迟时间始终稳定在3秒以内。
甚感欣慰,觉得自己又进步了一点。