路由器开启无线,使用5g一段时间后,free 内存会慢慢的往下降,使用过程就是刷抖音,玩王者荣耀,流量都比较低。
前面写过一篇博客,使用page owner来分析内存泄露,使用该方法定位出了,内存泄露的原因是大量的skb没有释放,对应的分配栈为:
times 304->878 order 3 mask 0xd2a20
__alloc_pages+0x134/0x1064
page_frag_alloc_align+0x84/0x180
__netdev_alloc_skb+0x158/0x190
ath11k_ce_rx_post_pipe+0x1c0/0x364 [ath11k]
ath11k_ce_per_engine_service+0x2d8/0x38c [ath11k]
ath11k_pci_ce_tasklet+0x18/0x34 [ath11k_ahb]
tasklet_action_common.constprop.0+0xc8/0xf0
__do_softirq+0x13c/0x444
times 0->302 order 3 mask 0xd2a20
__alloc_pages+0x134/0x1064
page_frag_alloc_align+0x84/0x180
__netdev_alloc_skb+0x158/0x190
ath11k_dp_rxbufs_replenish+0x150/0x3e8 [ath11k]
ath11k_dp_process_rx+0x370/0x48c [ath11k]
ath11k_dp_service_srng+0x80/0x30c [ath11k]
ath11k_pci_ext_grp_napi_poll+0x20/0x88 [ath11k_ahb]
__napi_poll+0x28/0x208
net_rx_action+0xe4/0x270
__do_softirq+0x13c/0x444
times 0->739 order 3 mask 0xd2a20
__alloc_pages+0x134/0x1064
page_frag_alloc_align+0x84/0x180
__netdev_alloc_skb+0x158/0x190
syn_dp_rx_refill+0x120/0x1bc [nss_dp]
syn_dp_napi_poll_rx+0x2c/0x84 [nss_dp]
__napi_poll+0x28/0x208
net_rx_action+0xe4/0x270
__do_softirq+0x13c/0x444
ath11k debug fs可以查看内存统计:
/ # cat /sys/kernel/debug/ath11k/qcn6122_1/memory_stats
MEMORY STATS IN BYTES:
malloc size : 786432
ce_ring_alloc size: 32076
dma_alloc size:: 6719115
htc_skb_alloc size: 3648
wmi tx skb alloc size: 4240818176
per peer object: 1428
rx_post_buf size: 1623040
Total size: 4249983915
drivers/net/wireless/ath/ath11k/hw.h
#define TARGET_NUM_VDEVS 8
#define TARGET_NUM_PEERS_PDEV (128 + TARGET_NUM_VDEVS)
#define TARGET_NUM_STATIONS 128
#define ATH11K_QMI_TARGET_MEM_MODE ATH11K_QMI_TARGET_MEM_MODE_256M
#define ATH11K_DP_TX_COMP_RING_SIZE 8192
#define ATH11K_DP_RXDMA_MON_STATUS_RING_SIZE 512
#define ATH11K_DP_RXDMA_MONITOR_BUF_RING_SIZE 128
#define ATH11K_DP_RXDMA_MONITOR_DST_RING_SIZE 128
#define ATH11K_DP_RXDMA_REFILL_RING_SIZE 2048
#define ATH11K_DP_RXDMA_NSS_REFILL_RING_SIZE 1816
咋也不知道这是啥,但是看代码,这一块分配很多内存
drivers/net/wireless/ath/ath11k/ce.c
分配调用栈:
int ath9k_ahb_probe(struct platform_device *pdev)
ret = ath11k_ce_alloc_pipes(ab);
for (i = 0; i < ab->hw_params.ce_count; i++)
ret = ath11k_ce_alloc_pipe(ab, i);
int ath11k_ce_alloc_pipe(struct ath11k_base *ab, int ce_id)
调用ath11k_ce_alloc_ring()为每个ce分配src和dst ring.
ring = ath11k_ce_alloc_ring(ab, nentries, desc_sz);
ath11k_ce_alloc_ring(struct ath11k_base *ab, int nentries, int desc_sz)
分配nentries个skb指针。
分配nentries*desc_sz内存的dma.
drivers/net/wireless/ath/ath11k/core.c
的 ath11k_hw_params[] 数组中,定义了各个芯片的硬件参数。
qcn6122的.host_ce_config用的是qcn9074的ce配置
const struct ce_attr ath11k_host_ce_config_qcn9074[] = {
/* CE0: host->target HTC control and raw streams */
{
.flags = CE_ATTR_FLAGS,
.src_nentries = 16,
.src_sz_max = 2048,
.dest_nentries = 0,
},
/* CE1: target->host HTT + HTC control */
{
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
.dest_nentries = 256,
.recv_cb = ath11k_htc_rx_completion_handler,
},
/* CE2: target->host WMI */
{
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
.dest_nentries = 128,
.recv_cb = ath11k_htc_rx_completion_handler,
},
/* CE3: host->target WMI (mac0) */
{
.flags = CE_ATTR_FLAGS,
.src_nentries = 32,
.src_sz_max = 2048,
.dest_nentries = 0,
.send_cb = ath11k_htc_tx_completion_handler,
},
/* CE4: host->target HTT */
{
.flags = CE_ATTR_FLAGS | CE_ATTR_DIS_INTR,
.src_nentries = 512,
.src_sz_max = 256,
.dest_nentries = 0,
},
/* CE5: target->host pktlog */
{
.flags = CE_ATTR_FLAGS,
.src_nentries = 0,
.src_sz_max = 2048,
.dest_nentries = 256,
.recv_cb = ath11k_dp_htt_htc_t2h_msg_handler,
},
};
就ring这个,占用的内存其实不多,就几十k:
[ 12.480524] =======ce 0 src entries 16
[ 12.491351] ring size: 100 256
[ 12.501859] =======ce 1 dst entries 256
[ 12.512082] ring size: 1060 2048
[ 12.522251] ring size: 1060 4096
[ 12.532328] =======ce 2 dst entries 128
[ 12.535416] ring size: 548 1024
[ 12.540287] ring size: 548 2048
[ 12.542113] =======ce 3 src entries 32
[ 12.545224] ring size: 164 512
[ 12.549085] =======ce 4 src entries 512
[ 12.552082] ring size: 2084 8192
[ 12.555837] =======ce 5 dst entries 256
[ 12.559310] ring size: 1060 2048
[ 12.562861] ring size: 1060 4096
drivers/net/wireless/ath/ath11k/dp_rx.c
rx分配调用过程:
在firmware加载完毕
ret = ath11k_core_qmi_firmware_ready(ab);
ret = ath11k_core_pdev_create(ab);
ret = ath11k_dp_pdev_alloc(ab);
ath11k_dp_rx_pdev_alloc(ab, i)
测试发现,在出现内存紧张的时候,卸载掉ath11k_ahb模式,内存就只消耗了20M。那就说明skb没有泄露,是被ath11k追踪起来了,在卸载的模块的时候释放后,内存就正常了。
于是在卸载过程中,使用下面的接口获取可用内存:
global_zone_page_state(NR_FREE_PAGES)
添加打印,发现下面的函数释放了大量的内存,释放了13K page,50多M内存。
[ 128.878227] a111==2 free 7019
[ 128.881166] a1112==1 free 7019
[ 128.896926] a1112==2 free 20225
[ 128.897332] a1112==3 free 20273
[ 128.900290] a1112==4 free 20273
static int ath11k_dp_rxdma_buf_ring_free(struct ath11k *ar,
struct dp_rxdma_ring *rx_ring)
{
struct sk_buff *skb;
int buf_id;
spin_lock_bh(&rx_ring->idr_lock);
idr_for_each_entry(&rx_ring->bufs_idr, skb, buf_id) {
idr_remove(&rx_ring->bufs_idr, buf_id);
/* TODO: Understand where internal driver does this dma_unmap
* of rxdma_buffer.
*/
dma_unmap_single(ar->ab->dev, ATH11K_SKB_RXCB(skb)->paddr,
skb->len + skb_tailroom(skb), DMA_FROM_DEVICE);
dev_kfree_skb_any(skb);
}
idr_destroy(&rx_ring->bufs_idr);
spin_unlock_bh(&rx_ring->idr_lock);
return 0;
}
skb存放在idr数据结构体中,rx dma ring,通过id来映射skb。所以使用idr结构体来存储skb。一开始以为skb的个数很多,
添加个数统计,发现内存很紧张的时候,也只有4095个skb。其实idr中skb的个数,就是rx dma ring的ring的个数对应的。
而ring的大小是4096,因此idr中的skb不会海量。
分配skb的时候,DP_RX_BUFFER_SIZE是2048, DP_RX_BUFFER_ALIGN_SIZE 是 128. 一个skb也就2K多,4095个skb也就8M多
怎么就消耗了50多M内存呢。于是怀疑不是释放skb导致的内存突降,将dev_kfree_skb_any(skb)注释,发现内存没有下降,那就实锤了,是skb占用的内存。分配skb的代码:
while (num_remain > 0) {
skb = dev_alloc_skb(DP_RX_BUFFER_SIZE +
DP_RX_BUFFER_ALIGN_SIZE);
if (!skb)
break;
if (!IS_ALIGNED((unsigned long)skb->data,
DP_RX_BUFFER_ALIGN_SIZE)) {
skb_pull(skb,
PTR_ALIGN(skb->data, DP_RX_BUFFER_ALIGN_SIZE) -
skb->data);
}
paddr = dma_map_single(ab->dev, skb->data,
skb->len + skb_tailroom(skb),
DMA_FROM_DEVICE);
这个skb有什么奥秘吗?于是来看内核的 dev_alloc_skb() 函数的实现 (net/core/skbuff.c):
struct sk_buff *__netdev_alloc_skb(struct net_device *dev, unsigned int len,
gfp_t gfp_mask)
{
struct page_frag_cache *nc;
struct sk_buff *skb;
bool pfmemalloc;
void *data;
len += NET_SKB_PAD;
/* If requested length is either too small or too big,
* we use kmalloc() for skb->head allocation.
*/
if (len <= SKB_WITH_OVERHEAD(1024) ||
len > SKB_WITH_OVERHEAD(PAGE_SIZE) ||
(gfp_mask & (__GFP_DIRECT_RECLAIM | GFP_DMA))) {
skb = __alloc_skb(len, gfp_mask, SKB_ALLOC_RX, NUMA_NO_NODE);
if (!skb)
goto skb_fail;
goto skb_success;
}
len += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
len = SKB_DATA_ALIGN(len);
if (sk_memalloc_socks())
gfp_mask |= __GFP_MEMALLOC;
if (in_hardirq() || irqs_disabled()) {
nc = this_cpu_ptr(&netdev_alloc_cache);
data = page_frag_alloc(nc, len, gfp_mask);
pfmemalloc = nc->pfmemalloc;
} else {
local_bh_disable();
nc = this_cpu_ptr(&napi_alloc_cache.page);
data = page_frag_alloc(nc, len, gfp_mask);
pfmemalloc = nc->pfmemalloc;
local_bh_enable();
}
if (unlikely(!data))
return NULL;
skb = __build_skb(data, len);
if (unlikely(!skb)) {
skb_free_frag(data);
return NULL;
}
可以看到如果len较小,或者len较大,就调用__alloc_skb() 来分配skb, 其是用kmalloc来分配内存。
如果len不大不小,如 2048 + 128就属于,这种情况用kmalloc,直接就是分配一个page, 此时会非常浪费内存。
因此,内核开发者搞了一种方法:page_frag_alloc(nc, len, gfp_mask)。它就是分配多个页,来容纳多个片段,就可以避免
内存浪费。
比如分配3K内存,那我就使用3个页,就是12K, 那就可以分配4个对象,不浪费一点内存。
所以在我们开头看到的栈:
times 304->878 order 3 mask 0xd2a20
__alloc_pages+0x134/0x1064
page_frag_alloc_align+0x84/0x180
__netdev_alloc_skb+0x158/0x190
分配的是order 3 也就是8个连续页,32K内存。此时,我就想到了一个点,如果这32K内存,其它skb都释放了,只剩一个skb.
那这一个skb就占用了 32 K 内存,那就非常浪费内存了,这只是我的一个猜想。
page_frag_alloc()的第一个参数是struct page_frag_cache *,它用来保存缓存记录,它是一个全局变量:
nc = this_cpu_ptr(&napi_alloc_cache.page);
是否, 会有碎片化的问题呢,为了验证我的猜想,修改 __netdev_alloc_skb(),强制使用__alloc_skb(),测试发现,内存稳定起来了。
不会在用着用着内存不断下降。这就证明了,是内存碎片化导致的问题。因此解决办法是,在ath11k里面,将dev_alloc_skb()修改为
alloc_skb(), 如下:
while (num_remain > 0) {
/*
* use alloc_skb instead of dev_alloc_skb
* solve the memory exhaused problem on 256M device.
*
* the dev_alloc_skb() alloc order 3 page to store
* multiple skb data, this can lead to fragment
* problem, which a skb hold 8 pages memory.
*/
skb = alloc_skb(DP_RX_BUFFER_SIZE +
DP_RX_BUFFER_ALIGN_SIZE, GFP_ATOMIC);
此时,skb和skb data都用kmalloc分配,可以通过slab info来查看个数:
/ # cat /proc/slabinfo
skbuff_head_cache 12156 12411 192 21 1 : tunables 0 0 0 : slabdata 591 591 0
kmalloc-4k 10922 10992 4096 8 8 : tunables 0 0 0 : slabdata 1374 1374 0
之前的ath11k代码分析,没有太大的用,在发现卸载ath11k模块,内存恢复了之后,就找到了定位的方向,看看是卸载过程释放哪个资源导致了内存显著下降,顺藤摸瓜即可。