rt-linux下__slab_alloc里的另外一处可能睡眠的逻辑
一、背景
我们在第二章里说明一下这处调用链,在第三章里,拓展说一下,rt-linux内核里的这种内存分配路径下的睡眠和唤醒逻辑所造成的一些调度有关的一些问题。
二、rt-linux下__slab_alloc里的另外一处可能睡眠的调用链
这个另外一处可能睡眠的调用链是一处使用锁的调用,在rt-linux下普通spinlock也是会引起睡眠的。
2.1 rt-linux下__slab_alloc里加锁的地方
rt-linux下__slab_alloc里的这另外一处可能睡眠的调用链所导致的一次warning的calltrace的打印里的部分调用链如下:
现在的内核版本基本都是使用的CONFIG_SLUB来代替CONFIG_SLAB,slub是基于slab核心实现的,slub改写自slob,是slob的优化后版本,比slab性能更好。如下图是内核文档里的相关说明:
所以,对应代码里是slub.c下的__slab_alloc函数,
在__slab_alloc函数里,还是明显地看到有使用锁的地方:
2.2 local_lock_irqsave在rt-linux里的实现
上图里的local_lock_irqsave虽然在普通内核里是会关抢占和禁用irq的,但是在rt-linux版本里,它是不会关抢占和禁用irq的,我们来看一下rt-linux里local_lock_irqsave的实现:
如下图local_lock_irqsave直接使用了__local_lock_irqsave:
rt-linux打开了CONFIG_PREEMPT_RT编译选项,所以,__local_lock_irqsave是调用的下图的逻辑:
即调用的__local_lock:
可以看到__local_lock会禁用migrate之外,就是调用的spin_lock,只是这个spinlock是一个per_cpu变量而已,实际调用流程和spin_lock是一样的。
要注意,这里只是禁用了迁移,抢占没有禁用,所以,在持锁期间,它是可能被抢占的。但是,更重要的是,在rt-linux的普通spinlock锁逻辑里,它会进行睡眠。
2.3 rt-linux里的spinlock里的rtlock_might_resched检查
在开启了CONFIG_PREEMPT_RT后,在rt-linux的spinlock_rt.c里的普通spinlock的核心逻辑__rt_spin_lock函数里,有如下的rtlock_might_resched的检查:
这个检查是为了在rt-linux里,避免在一些关中断/关抢占场景下,使用普通spinlock,检查的逻辑如下图在core.c里的__might_resched里:
这个检查是一个warning,但是实际上,如果看到这样的warning,就是说明有巨大的风险的,为什么这么说,因为,打印warning表示是在非预期的上下文下(关中断或关抢占)使用了rt-linux的普通spinlock。
假设,在关中断下,进入了rt-linux的普通spinlock锁里的睡眠的逻辑,也就是在关中断下调用了schedule函数,这是很明显的会导致系统错乱的情况,至于后面造成panic还是系统死锁都是有可能的。
三、rt-linux下内存分配逻辑引起的一些问题
在rt-linux下,除了要注意上面 2.3 里描述的在一些中断关闭或者抢占关闭的场景下使用可能睡眠的接口以外,还有不少因为rt-linux的普通spinlock锁有睡眠逻辑(有睡眠逻辑,自然就有相应的唤醒逻辑),从而导致一些性能有关的问题。
在之前的 博客里,我们讲到了rt-linux下的一个cgroup cpu的死锁问题 rt-linux下的cgroup cpu的死锁bug,还有rt-linux下的 rt-linux下的底层锁依赖因cgroup cpu功能导致不相干进程的高时延问题 。这里,对于rt-linux下的底层锁依赖,导致的不相干的进程之间的高时延问题,再做一定的说明。
3.1 内存分配路径下常见的两种锁导致的进程间的干扰
内存分配路径主要有两种锁,会产生这样的不相干进程之间的干扰,一种是在博客 里讲到的下图的唤醒关系:
还有一种是pre-cpu的锁导致的唤醒关系:
其实从上图里可以看到per-cpu的唤醒动作所发生的cpu和被唤醒任务锁在的cpu是同一个cpu,这其实在上面 2.2 里已经可以解释了,因为slab的分配所用的锁都是per-cpu的,自然相干扰的是同一个cpu上的任务。
但是,上面说的第一种,也就是一些全局的锁,从图里也可以看到,唤醒动作发生的cpu和被唤醒任务所在的cpu不是一个cpu:
3.2 除了slab分配时local_lock之间的干扰,还有lru_rotate锁及lru_lock锁
在上一节贴出的调用链里有lru_add_drain_cpu函数,lru_add_drain_cpu函数里有在执行pagevec_lru_move_fn前进入了lru_rotate.lock这个锁:
如上图看到lru_rotate.lock是一个local_lock,在rt-linux下也是会睡眠的,不过local_lock只涉及到同一个cpu上的影响。
对于不同cpu上的影响的锁是在上图里的pagevec_lru_move_fn函数里的逻辑:
也就是调用的folio_lruvec_relock_irqsave函数,folio_lruvec_relock_irqsave函数继而调用了folio_lruvec_lock_irqsave函数:
而folio_lruvec_lock_irqsave函数在打开memory cgroup时是如下实现:
使用的是lru_lock锁。