LINUX内核故障问题之SKB DROP

2024-01-08 11:05:59

???关键词

  • Red Hat Enterprise Linux (RHEL) 7.6
  • SKB linearization failed
  • vm.min_free_kbytes

一、问题现象?

一台业务主机dmesg 日志中频繁有以下报错:

[qede_ start_ xmit :1289(p1p2)]SKB linearization failed - silently dropping this SKB。

二、问题分析

1、基础概念

Packet:通过网卡收发的报文,包括链路层、网络层、传输层的协议头和携带的数据

Data Buffer:用于存储 packet 的内存空间

SKB:struct sk_buffer 的简写

Struct sk_buffer (SKB)是 linux TCP/IP stack 中,用于管理Data Buffer的结构。Sk_buffer 在数据包的发送和接收中起着重要的作用。为了提高网络处理的性能,应尽量避免数据包的拷贝。Linux 内核开发者们在设计 sk_buffer 结构的时候,充分考虑到这一点。目前 Linux 协议栈在接收数据的时候,需要拷贝两次:数据包进入网卡驱动后拷贝一次,从内核空间递交给用户空间的应用时再拷贝一次。

2、源码查错

The?qede_start_xmit()?源码的 1287-1290行指向了本次报错内容

1256 /* Main transmit function */

1257 netdev_tx_t qede_start_xmit(struct sk_buff *skb, struct net_device *ndev)

1258 {

1259 ????????struct qede_dev *edev = netdev_priv(ndev);

1260 ????????struct netdev_queue *netdev_txq;

1261 ????????struct qede_tx_queue *txq;

1262 ????????struct eth_tx_1st_bd *first_bd;

1263 ????????struct eth_tx_2nd_bd *second_bd = NULL;

1264 ????????struct eth_tx_3rd_bd *third_bd = NULL;

1265 ????????struct eth_tx_bd *tx_data_bd = NULL;

1266 ????????u16 txq_index, val = 0;

1267 ????????u8 nbd = 0;

1268 ????????dma_addr_t mapping;

1269 ????????int rc, frag_idx = 0, ipv6_ext = 0;

1270 ????????u8 xmit_type;

1271 ????????u16 idx;

1272 ????????u16 hlen;

1273 ????????bool data_split = false;

1274 ????????????????????????????????????

1275 ????????/* Get tx-queue context and netdev index */

1276 ????????txq_index = skb_get_queue_mapping(skb);

1277 ????????WARN_ON(txq_index >= QEDE_TSS_COUNT(edev));

1278 ????????txq = edev->fp_array[edev->fp_num_rx + txq_index].txq;

1279 ????????netdev_txq = netdev_get_tx_queue(ndev, txq_index);

1280 ???????????????????????

1281 ????????WARN_ON(qed_chain_get_elem_left(&txq->tx_pbl) < (MAX_SKB_FRAGS + 1));

1282 ????????

1283 ????????xmit_type = qede_xmit_type(skb, &ipv6_ext);

1284

1285 #if ((MAX_SKB_FRAGS + 2) > ETH_TX_MAX_BDS_PER_NON_LSO_PACKET)

1286 ????????if (qede_pkt_req_lin(skb, xmit_type)) {

>1287 ????????????????if (skb_linearize(skb)) {

>1288 ????????????????????????DP_NOTICE(edev,

>1289 ??????????????????????????????????"SKB linearization failed - silently dropping this SKB\n");

>1290 ????????????????????????dev_kfree_skb_any(skb);

1291 ????????????????????????return NETDEV_TX_OK;

1292 ????????????????}

1293 ????????}

1294 #endif

?从qede_start_xmit() 源码可以看到drop发生在skb_linearize() 函数
继续看skb_linearize()函数说明了和内存有关

2925 /**

2926 ?* ?????skb_linearize - convert paged skb to linear one

2927 ?* ?????@skb: buffer to linarize

2928 ?* ?????

2929 ?* ?????If there is no free memory -ENOMEM is returned, otherwise zero

2930 ?* ?????is returned and the old skb data released.

2931 ?*/

2932 static inline int skb_linearize(struct sk_buff *skb)

2933 {

2934 ????????return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;

2935 }

2920 static inline int __skb_linearize(struct sk_buff *skb)

2921 { ??????????????????????????????????

2922 ????????return __pskb_pull_tail(skb, skb->data_len) ? 0 : -ENOMEM;

2923 } ???

__pskb_pull_tail?说明了skb与buffer reallocate有关,怀疑最小KB数(vm.min_free_kbytes)较低导致用于存储 packet 的内存空间不足,出现丢失SKB情况。

1654 /** ????????????????????????????????

1655 ?* ?????__pskb_pull_tail - advance tail of skb header

1656 ?* ?????@skb: buffer to reallocate

1657 ?* ?????@delta: number of bytes to advance tail

1658 ?* ?????

1659 ?* ?????The function makes a sense only on a fragmented &sk_buff,

1660 ?* ?????it expands header moving its tail forward and copying necessary

1661 ?* ?????data from fragmented part.

1662 ?* ?????

1663 ?* ?????&sk_buff MUST have reference count of 1.

1664 ?*

1665 ?* ?????Returns %NULL (and &sk_buff does not change) if pull failed

1666 ?* ?????or value of new tail of skb in the case of success.

1667 ?*

1668 ?* ?????All the pointers pointing into skb header may change and must be

1669 ?* ?????reloaded after call to this function.

1670 ?*/

1671

1672 /* Moves tail of skb head forward, copying data from fragmented part,

1673 ?* when it is necessary.

1674 ?* 1. It may fail due to malloc failure.

1675 ?* 2. It may change skb pointers.

1676 ?*

1677 ?* It is pretty complicated. Luckily, it is called only in exceptional cases.

1678 ?*/

1679 void *__pskb_pull_tail(struct sk_buff *skb, int delta)

三、处理过程

vm.min_free_kbytes参数调优

什么是vm.min_free_kbytes?

官方释义是:这用于强制 Linux VM 保持最小数量的可用千字节。VM 使用这个数字来计算系统中每个 lowmem 区域的 watermark[WMARK_MIN] 值。每个 lowmem 区域都会根据其大小按比例获得一些保留的空闲页面。满足 PF_MEMALLOC 分配需要一些最小的内存量;如果您将其设置为低于 1024KB,您的系统将被巧妙地破坏,并且在高负载下容易死锁。设置得太高会立即 OOM 你的机器。

?所以设定这个参数时请小心,因为该值过低和过高都有问题。min_free_kbytes太低可防止系统重新利用内存。这可导致系统挂起并让 OOM 杀死多个进程。但将这个参数值设定太高(占系统总内存的 5-10%)会让您的系统很快会内存不足。Linux 的设计是使用所有可用 RAM 缓存文件系统数据。设定高min_free_kbytes值的结果是在该系统中花费太多时间重新利用内存。

min_free_kbytes这个参数不仅仅是影响Linux内存回收中的water_mark(尤其是direct reclaim回收方式,Linux有两种内存回收方式:一种是kswapd后台回收,在早期的内核版本kswapd是周期性唤醒,因此又叫周期回收,但是没有必要,所以在现代Linux内核版本中已经不再是周期唤醒,而是在分配内存的时候会基于zone的water_mark来唤醒做后台回收内存;另一种就是direct reclaim,这种又叫同步回收,因为此时系统可用内存到了water_mark_min,意味着系统内存非常紧张,所以allocate page申请内存的进程会被阻塞直到回收可用内存。当然,如果经历了内存回收流程仍旧没有回收到足够的内存,那么在allocate page函数中会走out_of_memory函数的oom流程),还会影响系统的可用内存available memory。

设置了/proc/sys/vm/min_free_kbytes之后,通过water_mark_min计算water_mark_low和water_mark_high的默认公式:

watermark[min] = per_zone_min_free_pages (min_free_kbytes换算为page单位)

watermark[low] = watermark[min] * 5 / 4

watermark[high] = watermark[min] * 3 / 2

min 和 low的区别:

1、min下的内存是保留给内核使用的;当到达min,会触发内存的direct reclaim
2、low水位比min高一些,当内存可用量小于low的时候,会触发 kswapd回收内存,当kswapd慢慢的将内存 回收到high水位,就开始继续睡眠

内存回收方式有两种,主要对应low ,min

1、direct reclaim : 触发min水位线时执行
2、kswapd reclaim : 触发low水位线时执行

四、经验总结

??如果发现系统因为direct reclaim而导致卡顿、延迟(此时IO相关的指标会比较异常,并且系统负载会增加),那么就需要调高min_free_kbytes,可以通过sar -B命令观测pgscand和%vmeff来慢慢调整。这是为什么呢?因为min=min_free_kbytes,low=1.25min,high=1.5min,到low会唤醒kswapd做内存的后台回收,这时候即便是刷脏页、swap out等虽然会消耗磁盘io性能,但是绝大多数情况下不会影响进程;但是到了min,所有此时申请物理内存的进程都会被阻塞做direct reclaim,直到回收满足申请的内存。如果min_free_kbytes太小,那么就意味着kswapd后台回收启动没多久就进入direct reclaim,如果能把两者的时间间隔拉长,让后台回收有充分的时间来回收内存,那么就会降低direct reclaim的影响。

对于线上128G的内存的机器,可以考虑将min设置为512M左右。因为太大了,可能会导致内存的浪费;如果只有40G的物理机,更不要考虑把min设置超过1G了,这样会导致频繁的触发内存回收;具体优化也要根据业务来看。

关键是在于调整内存的内核参数的时候,?调大的风险远大于调小的风险! 如果有人想将vm.min_free_kbytes 调大,千万要注意当前的水位,如果一旦调大vm.min_free_kbytes 立刻触发direct reclaim,可能会导致机器hang住,ping的通,ssh不上,影响业务。hang住的原因是当vm.min_free_kbytes 是512M的时候, free只有1G,此时正常运行,如果调大vm.min_free_kbytes 到5G,将会direct reclaim失败。??

文章来源:https://blog.csdn.net/vincent0920/article/details/135449217
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。