3

如何使能512个virtio_blk设备 - _备忘录

 2 years ago
source link: https://www.cnblogs.com/10087622blog/p/16611697.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一例virtio_blk设备中断占用分析

背景:这个是在客户的centos8.4的环境上复现的,dpu是目前很多

云服务器上的网卡标配了,在云豹的dpu产品测试中,dpu实现的virtio_blk

设备在申请中断时报错,在排查这个错误的过程中,觉得某些部分还比较有

趣,故记录之。本身涉及的背景知识有:irq,msi,irq_domain,

affinity,virtio_blk,irqbalance

下面列一下我们是怎么排查并解决这个问题的。

一、故障现象

内核团队接到测试组测试客户前端内核抛栈:

[25338.485128] virtio-pci 0000:b3:00.0: virtio_pci: leaving for legacy driver
[25338.496174] genirq: Flags mismatch irq 0. 00000080 (virtio418) vs. 00015a00 (timer)
[25338.503822] CPU: 20 PID: 5431 Comm: kworker/20:0 Kdump: loaded Tainted: G           OE    --------- -  - 4.18.0-305.30.1.jmnd2.el8.x86_64 #1
[25338.516403] Hardware name: Inspur NF5280M5/YZMB-00882-10E, BIOS 4.1.21 08/25/2021
[25338.523881] Workqueue: events work_for_cpu_fn
[25338.528235] Call Trace:
[25338.530687]  dump_stack+0x5c/0x80
[25338.534000]  __setup_irq.cold.53+0x7c/0xd3
[25338.538098]  request_threaded_irq+0xf5/0x160
[25338.542371]  vp_find_vqs+0xc7/0x190
[25338.545866]  init_vq+0x17c/0x2e0 [virtio_blk]
[25338.550223]  ? ncpus_cmp_func+0x10/0x10
[25338.554061]  virtblk_probe+0xe6/0x8a0 [virtio_blk]
[25338.558846]  virtio_dev_probe+0x158/0x1f0
[25338.562861]  really_probe+0x255/0x4a0
[25338.566524]  ? __driver_attach_async_helper+0x90/0x90
[25338.571567]  driver_probe_device+0x49/0xc0
[25338.575660]  bus_for_each_drv+0x79/0xc0
[25338.579499]  __device_attach+0xdc/0x160
[25338.583337]  bus_probe_device+0x9d/0xb0
[25338.587167]  device_add+0x418/0x780
[25338.590654]  register_virtio_device+0x9e/0xe0
[25338.595011]  virtio_pci_probe+0xb3/0x140
[25338.598941]  local_pci_probe+0x41/0x90
[25338.602689]  work_for_cpu_fn+0x16/0x20
[25338.606443]  process_one_work+0x1a7/0x360
[25338.610456]  ? create_worker+0x1a0/0x1a0
[25338.614381]  worker_thread+0x1cf/0x390
[25338.618132]  ? create_worker+0x1a0/0x1a0
[25338.622051]  kthread+0x116/0x130
[25338.625283]  ? kthread_flush_work_fn+0x10/0x10
[25338.629731]  ret_from_fork+0x1f/0x40
[25338.633395] virtio_blk: probe of virtio418 failed with error -16
       

从堆栈看,是某个virtio_blk设备在probe的时候报错,错误码为-16。

二、故障现象分析

从堆栈信息看:

1、virtio418是一个virtio_blk设备,在probe过程中调用 __setup_irq 返回了-16。

2、[25338.496174] genirq: Flags mismatch irq 0. 00000080 (virtio418) vs. 00015a00 (timer),说明我们的virtio_blk

设备去申请了0号中断,由于0号中断被timer占用,irq子系统在比较flags时发现不符合,则打印这行。

具体代码为:


static int

__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{

......

mismatch:
        if (!(new->flags & IRQF_PROBE_SHARED)) {
                pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
                       irq, new->flags, new->name, old->flags, old->name);

......
       ret = -EBUSY;

......

      return ret;

}

至于为什么virtio_blk会去申请0号中断,因为我们实现virtio_blk后端设备的时候,并没有支持intx,即virtio_pci类虚拟的pci_dev设备的irq值为0,本文先不管这个。

从堆栈看,virtio 申请中断是走了vp_find_vqs_intx流程,


crash> dis -l vp_find_vqs+0xc7
/usr/src/debug/linux/drivers/virtio/virtio_pci_common.c: 369---行号为369


    356 static int vp_find_vqs_intx(struct virtio_device *vdev, unsigned nvqs,
    357                 struct virtqueue *vqs[], vq_callback_t *callbacks[],
    358                 const char * const names[], const bool *ctx)
    359 {
 ......
    366
    367         err = request_irq(vp_dev->pci_dev->irq, vp_interrupt, IRQF_SHARED,
    368                         dev_name(&vdev->dev), vp_dev);

    369         if (err)----压栈的返回地址

......

我们dpu卡实现的virtio设备,都是使能msix的,按照代码流程,应该是先尝试msix,既然能走到 vp_find_vqs_intx 流程,说明 vp_find_vqs_msix失败了,而且按照如下代码:


    395 int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
    396                 struct virtqueue *vqs[], vq_callback_t *callbacks[],
    397                 const char * const names[], const bool *ctx,
    398                 struct irq_affinity *desc)
    399 {
    400         int err;
    401
    402         /* Try MSI-X with one vector per queue. */
    403         err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);//caq:先尝试单vqueue单中断号
    404         if (!err)
    405                 return 0;
    406         /* Fallback: MSI-X with one vector for config, one shared for queues. */
    407         err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);//caq:尝试多个vq共享一个中断号
    408         if (!err)
    409                 return 0;
    410         /* Finally fall back to regular interrupts. */
    411         return vp_find_vqs_intx(vdev, nvqs, vqs, callbacks, names, ctx);//caq:最后退化成intx模式
    412 }

说明vp_find_vqs_msix 失败了两次。第一次是 单vqueue单中断号的非共享方式,第二次是多个vq共用一个中断的方式。

通过打点发现,失败两次的原因是 __irq_domain_activate_irq 返回了-28


__irq_domain_activate_irq return=-28

0xffffffff8fb52f70 : __irq_domain_activate_irq+0x0/0x80 [kernel]
 0xffffffff8fb54bc5 : irq_domain_activate_irq+0x25/0x40 [kernel]
 0xffffffff8fb56bfe : msi_domain_alloc_irqs+0x15e/0x2f0 [kernel]
 0xffffffff8fa5e5e4 : native_setup_msi_irqs+0x54/0x90 [kernel]
 0xffffffff8feef69f : __pci_enable_msix_range+0x3df/0x5e0 [kernel]
 0xffffffff8feef96b : pci_alloc_irq_vectors_affinity+0xbb/0x130 [kernel]
 0xffffffff8ff7472b : vp_find_vqs_msix+0x1fb/0x510 [kernel]
 0xffffffff8ff74aad : vp_find_vqs+0x6d/0x190 [kernel]


查看具体的代码:


static int __irq_domain_activate_irq(struct irq_data *irqd, bool reserve)
{
 int ret = 0;

 if (irqd && irqd->domain) {//caq:均不为NULL
 struct irq_domain *domain = irqd->domain;

 if (irqd->parent_data)
 ret = __irq_domain_activate_irq(irqd->parent_data,
 reserve);//caq:递归,将父domain的对应irq都active一下
 if (!ret && domain->ops->activate) {
 ret = domain->ops->activate(domain, irqd, reserve);//caq:parent 执行完activate 然后再儿子辈执行。
 /* Rollback in case of error */
 if (ret && irqd->parent_data)//caq:异常则回退
 __irq_domain_deactivate_irq(irqd->parent_data);
 }
 }
 return ret;
}

由于客户 host kernel开启了 CONFIG_IRQ_DOMAIN_HIERARCHY,根据irq_domain 级别 ,该系统的irq_domain 级联如下:

不过上图是arm常见的,盗用arm图,本x86系统类似,irq_domain级别具体跟踪如下:


crash> irq_domain.name,parent 0xffff9bff87d4dec0
  name = 0xffff9bff87c1fd60 "INTEL-IR-MSI-1-2"
  parent = 0xffff9bff87400000
crash> irq_domain.name,parent 0xffff9bff87400000
  name = 0xffff9bff87c24300 "INTEL-IR-1"
  parent = 0xffff9bff87c6c900
crash> irq_domain.name,parent 0xffff9bff87c6c900
  name = 0xffff9bff87c3ecd0 "VECTOR"-----------最高级的
  parent = 0x0---所以parent为空

根据返回-28,根据最高级的irq_domain定位到 调用链为:


//caq:类比于 dma_domain_ops,在x86内是最高级的irq_domain了,因为他的domain parent为NULL
static const struct irq_domain_ops x86_vector_domain_ops = {//caq:x86针对acpi实现的irq_domain_ops
 .alloc = x86_vector_alloc_irqs,//caq:分配中断
 .free = x86_vector_free_irqs,
 .activate = x86_vector_activate,//caq:activate实现
 .deactivate = x86_vector_deactivate,
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
 .debug_show = x86_vector_debug_show,
#endif
};
调用链:

x86_vector_activate-->activate_managed-->assign_managed_vector-->irq_matrix_alloc_managed

查看 代码如下:


int irq_matrix_alloc_managed(struct irq_matrix *m, const struct cpumask *msk,
      unsigned int *mapped_cpu)
{//caq:managed irq 分配
 unsigned int bit, cpu, end = m->alloc_end;
 struct cpumap *cm;

 if (cpumask_empty(msk))
 return -EINVAL;

 cpu = matrix_find_best_cpu_managed(m, msk);//caq:找最合适的cpu
 if (cpu == UINT_MAX)
 return -ENOSPC;//caq:说明没找到
......
}

由于没有开启 CONFIG_GENERIC_IRQ_DEBUGFS,所以没办法直接看到 vector_matrix 具体的值,

借助crash工具查看:


crash> p *vector_matrix
$82 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 0,//caq:只剩下了这么多个irq
  global_reserved = 151,
  systembits_inalloc = 3,
  total_allocated = 1922,//caq:只分配了这么多个irq
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {18446744069952503807, 18446744073709551615, 18446744073709551615, 18446735277616529407, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

一个疑问涌上心头,为什么总共才分配了1922 个中断,全局的global_available就为0了呢?

然后继续打点,发现virtio_blk设备的vq申请中断时,走的是 apicd->is_managed 流程,而同属于virtio协议的virtio_net设备却不是。

而走managed流程,也就是申请中断时,带有了RQD_AFFINITY_MANAGED:


static int
assign_irq_vector_policy(struct irq_data *irqd, struct irq_alloc_info *info)
{
 if (irqd_affinity_is_managed(irqd))//caq:如果是 managed 的irq,也就是irq_data中有 IRQD_AFFINITY_MANAGED 标记
 return reserve_managed_vector(irqd);

我们回过来查看vector alloc时的调用链:

x86_vector_alloc_irqs-->assign_irq_vector_policy-->reserve_managed_vector-->irq_matrix_reserve_managed

对一个两个队列的virtio_blk申请中断时,打点发现如下:


m->global_available=15296 

0xffffffff87158300 : irq_matrix_reserve_managed+0x0/0x130 [kernel]---从15296减少到15256
m->global_available=15256

call vdev=0xffff8b781ce17000,index=0,callback=0xffffffffc0448000,ctx=0,msix_vec=1----------容量减少了40

由于已经缩小到是因为virtio_blk设备的中断申请流程,使用热插拔确认一下:


118:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0         53          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0  IR-PCI-MSI 94371841-edge      virtio3-req.0
 119:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0         49          0          0          0  IR-PCI-MSI 94371842-edge      virtio3-req.1

热拔前:
crash> p *vector_matrix
$2 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 15215,
  global_reserved = 150,
  systembits_inalloc = 3,
  total_allocated = 553,
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {1179746449752063, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

热拔后:
crash> p *vector_matrix
$3 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 15296,---增长了81个,一个config中断+两个分别为40的req中断,此时req.0和req.1是非共享模式
  global_reserved = 150,
  systembits_inalloc = 3,
  total_allocated = 550,
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {481036337152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

说明 对于mask内的cpu号,都需要一个中断容量,两个40就是因为80核的服务器,两个vq,平分中断,感兴趣的同学可以去查看irq_build_affinity_masks这个函数的实现。这样一个vector,因为开启了 IRQD_AFFINITY_MANAGED 属性,导致需要占用80个中断容量。而我们系统由于有512个virtio_blk设备,所以申请到部分设备的时候,就把总的

vector容量耗光了,但其实分配的irq总数才不到2000.

那么,virtio_blk设备什么时候开启的 IRQD_AFFINITY_MANAGED 属性的呢?查看git记录:


6abd6e5a44040 (Amit Shah                 2011-12-22 16:58:29 +0530  507) static int init_vq(struct virtio_blk *vblk)
6abd6e5a44040 (Amit Shah                 2011-12-22 16:58:29 +0530  508) {
......
6a27b656fc021 (Ming Lei                  2014-06-26 17:41:48 +0800  515)        struct virtio_device *vdev = vblk->vdev;
ad71473d9c437 (Christoph Hellwig         2017-02-05 18:15:25 +0100  516)        struct irq_affinity desc = { 0, };----会导致blk申请中断时,使用内核managed方式来申请,一个dev会占用cpu核数这么多的容量。

看起来是 ad71473d9c437 这个 commit引入了这个问题。

但是根据virtio_blk驱动,第一遍中断申请的时候才有affinity_managed 设置,第二遍应该并没有设置,具体 vp_find_vqs_msix 如下:


//caq:vq申请中断,msix 模式,per_vq_vectors 决定是个vq共享中断还是独占中断
static int vp_find_vqs_msix(struct virtio_device *vdev, unsigned nvqs,
 struct virtqueue *vqs[], vq_callback_t *callbacks[],
 const char * const names[], bool per_vq_vectors,
 const bool *ctx,
 struct irq_affinity *desc)
{
 struct virtio_pci_device *vp_dev = to_vp_device(vdev);
 u16 msix_vec;
 int i, err, nvectors, allocated_vectors;

 vp_dev->vqs = kcalloc(nvqs, sizeof(*vp_dev->vqs), GFP_KERNEL);
 if (!vp_dev->vqs)
 return -ENOMEM;

 if (per_vq_vectors) {//caq:单个vq 单个vector 中断号
 /* Best option: one for change interrupt, one per vq. */
 nvectors = 1;//caq:这个是config的中断,注意要和virtio_net的 ctrl vq区分
 for (i = 0; i < nvqs; ++i)
 if (callbacks[i])//caq:由于ctrl vq是不设置callbacks的,所以它没有中断
 ++nvectors;
 } else {
 /* Second best: one for change, shared for all vqs. */
 nvectors = 2;
 }
    //caq:中断总数最少为2,最多为vq个数+1,1为config的中断,另外单个vq单个vector才具备带亲核属性
 err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
       per_vq_vectors ? desc : NULL);//caq:nvectors 为总中断数,注意desc的配置取决于 per_vq_vectors

//caq:virtio_pci申请msix的vector
static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
    bool per_vq_vectors, struct irq_affinity *desc)
{
......
 vp_dev->msix_affinity_masks
 = kcalloc(nvectors, sizeof(*vp_dev->msix_affinity_masks),
   GFP_KERNEL);//caq:中断掩码内存的分配
......
 if (desc) {//caq:要求带亲核属性
 flags |= PCI_IRQ_AFFINITY;//caq:带上亲核属性
 desc->pre_vectors++; /* virtio config vector *///caq:细节,相当于指定了config中断不要设置亲核,走系统默认
 }
......

原因,因为前面很多virtio_blk设备因为一个vector占用了80个中断容量,导致整体中断数不够了,

而后面的virtio_blk设备,第一遍使用vp_find_vqs_msix带 managed_affinity属性申请中断时失败,第二遍尽管使用vq共享中断模式,由于os连一个中断都分配不出来, 也会失败,导致走入了第三个流程,也就是 vp_find_vqs_intx 流程。

在另外一个virtio_blk单vq的环境上,具体查看如下:


[root@localhost config_json]#  cat /proc/interrupts |grep req |tail -1
 986:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0        114          0          0  IR-PCI-MSI 93687809-edge      virtio180-req.0
[root@localhost config_json]# cat /proc/irq/986/smp_affinity
ffff,ffffffff,ffffffff

[root@localhost config_json]#  cat /proc/interrupts |grep queues |tail -1
1650:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0        120          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0  IR-PCI-MSI 94369793-edge      virtio512-virtqueues


[root@localhost config_json]# cat /proc/irq/1650/smp_affinity
ffff,ffffffff,ffffffff
[root@localhost config_json]# ls /sys/bus/virtio/devices/virtio180/block/
vdsh
[root@localhost config_json]# ls /sys/bus/virtio/devices/virtio512/block/
vdafj

以上可以看出,virtio180,也就是 vdsh 的block设备,走了 vp_find_vqs_msix 第一遍流程,分配了带 managed_affinity 的vector,所以他的中断名字是req结尾的,

而另外一个 virtio512 ,也就是 vdafj 的block设备,走了 vp_find_vqs_msix 第二遍流程,没有分配 带 managed_affinity 的vector,所以它的中断名字是 virtqueues 结尾的,

而后面的设备,只能走第三个流程,报错了,所以打点发现,除了 activate的时候会报容量不足,在alloc阶段,也会在 irq_matrix_alloc 报容量不足。


int irq_matrix_alloc(struct irq_matrix *m, const struct cpumask *msk,
      bool reserved, unsigned int *mapped_cpu)
{

......
 cpu = matrix_find_best_cpu(m, msk);
 if (cpu == UINT_MAX)//caq:在msk内,没找到合适的cpu来记账
 return -ENOSPC;

 cm = per_cpu_ptr(m->maps, cpu);
 bit = matrix_alloc_area(m, cm, 1, false);//caq:获取一个
 if (bit >= m->alloc_end)
 return -ENOSPC;//caq:没有资源了
......

打点记录如下:

Returning from:  0xffffffffb7158300 : irq_matrix_reserve_managed+0x0/0x130 [kernel]
Returning to  :  0xffffffffb705adb7 : x86_vector_alloc_irqs+0x2d7/0x3c0 [kernel]
 0xffffffffb75dc42c : intel_irq_remapping_alloc+0x7c/0x6d0 [kernel]
 0xffffffffb7156438 : msi_domain_alloc+0x68/0x120 [kernel]
 0xffffffffb715457d : __irq_domain_alloc_irqs+0x12d/0x300 [kernel]
 0xffffffffb7156b38 : msi_domain_alloc_irqs+0x98/0x2f0 [kernel]
 0xffffffffb705e5e4 : native_setup_msi_irqs+0x54/0x90 [kernel]
 0xffffffffb74ef69f : __pci_enable_msix_range+0x3df/0x5e0 [kernel]
 0xffffffffb74ef96b : pci_alloc_irq_vectors_affinity+0xbb/0x130 [kernel]
 0xffffffffb70635e0 : kretprobe_trampoline+0x0/0x50 [kernel]
 0x1b7574a40
 0x1b7574a40 (inexact)
irq_matrix_reserve_managed return -28

三、故障复现

1、只要是一开始各个核中断的aviable 容量相当,然后热插拔足够多virtio_blk设备,则必现。

2、如果各个核的中断的available容量相差很多,比如常见的numa节点的第一个cpu的中断占用过多,

使得走第一分支时因为某个核容量不够而reserve_managed 失败,然后

则会使得后面大量的virtio_blk走第二个分支,此时不带managed_affinity,反而能分配成功。

四、故障规避或解决

可能的解决方案之一:


static int init_vq(struct virtio_blk *vblk)//caq:初始化关于vq相关的内容
{
 int err;
 int i;
 vq_callback_t **callbacks;
 const char **names;
 struct virtqueue **vqs;
 unsigned short num_vqs;
 struct virtio_device *vdev = vblk->vdev;
 struct irq_affinity desc = { 0, };//caq:去掉这行代码

解决方案之二:

开启irqbalance,并让服务器进入 Power-save mode 时,irqbalance 会将中断集中分配给numa节点的第一个 CPU,这样慢慢地,各个核

的available的irq 容量就相差比较大了,当然这种不太靠谱。

解决方案之三:

手工调整中断亲核,使得某些核的容量接近于0,然后再加载virtio_blk设备。

五、作者简介

陈安庆,目前在dpu厂商 云豹智能 负责linux内核及虚拟化方面的工作,

联系方式:微信与手机同号:18752035557。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK