一、背景
我們在第二章里說明一下這處調用鏈,在第三章里,拓展說一下,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鎖。