mmap_sem鎖簡介
mmap_sem鎖是進程為了保護自身虛擬地址空間不受多線程并發(fā)訪問影響而設(shè)計的。
多線程環(huán)境下,如果想訪問進程的虛擬地址空間(比如find_vma等),是要先持有該mmap_sem鎖才能訪問的,這樣可以避免多線程并發(fā)修改進程vma區(qū)域造成的沖突。
mmap_sem鎖的一些問題總結(jié)
內(nèi)核mmap_sem鎖設(shè)計目前存在一些問題,簡單總結(jié)如下:
1:保護的東西太多,范圍太廣了。
mmap_sem目前保護:
1)Rbtree of VMA,比如做find_vma()時
arm64系統(tǒng)上,由于虛擬地址空間增大,進程的vma數(shù)量會特別多,每個vma操作幾乎都要首先獲取一把這樣的mmap_sem大鎖。
這樣會造成鎖的粒度太大,鎖整個進程的vma地址空間的。
2)VMA list,會Lock the whole address space for even touching one byte
3) VMA flags, 會Need hold write lock to update vm_flags
4)Most of the fields of the mm_struct are protected using the mm.mmap_sem
fields can be arg_start, arg_end, env_start, env_end等。
2: 內(nèi)核會頻繁做page fault, 這樣會頻繁獲取mmap_sem鎖。
soft page fault的描述:
Page faults can be quite expensive, especially those which must be resolved by reading data from disk. On a typical system, though, there are a lot of page faults which do not require I/O. A page fault happens because a specific process does not have a valid page table entry for the needed page, but that page might already be in the page cache, in which case handling the fault is just a matter of fixing the page table entry and increasing the page's reference count; this happens often with shared pages or those brought in via the readahead mechanism. Faults for new anonymous pages (application data and stack space, mostly), instead, can be handled through the allocation of a zero-filled page. In either case, the fault is quickly taken care of with no recourse to backing store required.
In many workloads, this kind of "soft" fault happens much more often than hard faults requiring actual I/O. So it's important that they be executed quickly. Various developers had concluded that the kernel was, in fact, not handling this kind of fault quickly enough, and they identified the use of the mmap_sem reader/writer semaphore as the core of the problem.
Contention wasn't the issue in this case - only a reader lock is required for page fault handling - but the cache line bouncing caused by continual acquisition of the lock was killing performance. As the number of cores in systems increases, this kind of problem can get worse.
內(nèi)核里面這樣的soft page fault會發(fā)生很多,勢必造成mmap_sem獲取很頻繁,引起多核cache顛簸,對多核程序性能也不好。
所以Linux kernel很多地方架構(gòu)設(shè)計不怎么匹配如今的多核cpu架構(gòu)。
3: 一旦有個寫請求在排隊了,該mmap_sem就會變成互斥意義上的鎖了。
mmap_sem這種讀寫鎖是有好處,可以實現(xiàn)一些并發(fā)的多線程讀訪問。
但是它的這種并發(fā)讀訪問是有條件的:
如果一個讀寫信號量當(dāng)前沒有被寫者擁有并且也沒有寫者等待讀者釋放信號量,那么任何讀者都可以成功獲得該讀寫信號量,否則,讀者必須被掛起直到寫者釋放該信號量。
如果一個讀寫信號量當(dāng)前沒有被讀者或?qū)懻邠碛胁⑶乙矝]有寫者等待該信號量,那么一個寫者可以成功獲得該讀寫信號量,否則寫者將被掛起,直到?jīng)]有任何訪問者。
簡單來看個問題場景:
線程1以讀者身份持mmap_sem,然后該線程由于某種原因sleep了。
下來線程2以寫者身份請求持該mmap_sem鎖,因為該鎖已經(jīng)被線程1持有,所以失敗就開始排隊。
再下來該鎖就變成互斥鎖了,再來的read請求(對應(yīng)線程2,3...)就都得排隊了,不能發(fā)揮讀寫鎖并發(fā)讀的優(yōu)勢了。
結(jié)論:
所以綜合上面3個問題特點,在多核,多線程并發(fā)環(huán)境下(比如安卓系統(tǒng)),勢必造成mmap_sem鎖競爭激烈,程序性能不好。
mmap_sem鎖在產(chǎn)品開發(fā)中的優(yōu)化總結(jié)
優(yōu)化方向1:方便快捷地找到持鎖線程。
目前很多方法是通過在出現(xiàn)問題時,人為地讓內(nèi)核崩潰,然后再用crash工具分析內(nèi)核內(nèi)存dump鏡像,從而在一大堆等鎖和持鎖線程中,找到
導(dǎo)致問題出現(xiàn)的持鎖線程信息。一旦找到持鎖線程,就明白問題出現(xiàn)的root casue, 就會有優(yōu)化方案了。
但這種crash工具分析方法還是有點笨重,當(dāng)然由于mmap_sem鎖競爭導(dǎo)致的內(nèi)核崩潰用這種方法是最好最對口的。
但是很多時候出問題是內(nèi)核并未崩潰,只是android上層發(fā)生watchdog超時重啟,或者只是某進程工作timeout。
如果是mmap_sem競爭導(dǎo)致的這些問題發(fā)生可以嘗試用些簡單快捷的方法找到持鎖線程。
1) 可以在安卓fwk層發(fā)生watchdog超時時,打印下system server進程中每個線程的內(nèi)核?;厮菪畔?。
這樣如果對內(nèi)核代碼熟悉的話,會知道哪些地方會長時間持有mmap_sem鎖,這樣看下棧回溯信息,大概能猜出來哪些線程在持鎖或者等鎖。
如果信息還不夠,還可以通過sysrq,打印出系統(tǒng)此時所有處于D狀態(tài)和sleep狀態(tài)的線程內(nèi)核?;厮菪畔?。
因為有持鎖等鎖導(dǎo)致的內(nèi)核性能問題,基本上都出現(xiàn)在D和sleep狀態(tài)的線程里面,通過對這些信息分析,也可以大概猜出來可能的持鎖者。
2) 如果問題可以復(fù)現(xiàn),可以用一些bcc工具找到mmap_sem的持鎖owner信息。
1>輸出持鎖的owner信息
sudo ./trace 'rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) "count=0x%lx owner=%s", sem->count.counter, ((struct task_struct *)((sem->owner.counter)&~0x7))->comm' /virtual/main.c:44:66: warning: comparison of array '((struct task_struct *)((sem->owner.counter) & ~7))->comm' not equal to a null pointer is always true [-Wtautological-pointer-compare] if (((struct task_struct *)((sem->owner.counter)&~0x7))->comm != 0) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~ ~ 1 warning generated. PID TID COMM FUNC - 10127 10127 sync rwsem_down_read_slowpath count=0x103 owner=modprobe //是modprobe進程持有該rwsem鎖。
2> 輸出持鎖owner的其他信息
sudo ./trace 'rwsem_down_read_slowpath(struct rw_semaphore *sem, int state) "count=0x%lx fs name=%s", sem->count.counter, (((struct super_block*)((void *)sem-(void*)(&(((struct super_block*)0)->s_umount))))->s_id)' PID TID COMM FUNC - 10144 10144 sync rwsem_down_read_slowpath count=0x103 fs name=nfsd
從上面可以找到持鎖owner:modprobe進程此時正在掛載 nfsd 模塊。
優(yōu)化方向2:核心路徑中避免對/proc目錄下每進程子目錄做遍歷訪問,規(guī)避mmap_sem導(dǎo)致的問題。
之前碰到一個問題是由于system_server進程發(fā)生watchdog超時導(dǎo)致安卓fwk層重啟。
watchdog超時原因是:
system_server進程一個核心路徑上代碼做了遍歷/proc/每進程/cmdline工作,正常情況下做該工作不會出現(xiàn)問題。
但是由于做該工作,需要down_read獲取每進程的mmap_sem鎖,只要有一個進程的該鎖已經(jīng)被寫者身份持有了,那么再獲取該鎖時,就得等待了。
所以異常就發(fā)生在這個地方,所以該異常會導(dǎo)致該核心路徑中遍歷每進程cmdline工作耗時了,核心路徑一旦性能受影響,就會導(dǎo)致問題出現(xiàn)。
優(yōu)化方法:
避免在核心路徑上做這種潛在的耗時工作:遍歷每進程狀態(tài)。改用其他方法去達到目的。
優(yōu)化方向3:同步社區(qū)一些patch,避免出現(xiàn)因mmap_sem競爭導(dǎo)致的cgroup優(yōu)先級反轉(zhuǎn)問題。
cgroup v2中容易出現(xiàn)這種優(yōu)先級反轉(zhuǎn)問題:
一個高優(yōu)先級group里面某個進程A正在做遍歷訪問/proc/每進程下面狀態(tài)信息的工作,所以需要獲取系統(tǒng)中每進程的mmap_sem鎖。
另外一個低優(yōu)先級group里面,某進程B中有些線程在做耗時長的io操作(進入內(nèi)核filemap_fault函數(shù)里面做的),操作前提前以寫身份獲取了mmap_sem鎖。
所以寫身份先獲取了這把鎖,那么進程A如果想通過訪問獲取進程B的狀態(tài)信息時,就會阻塞在等待進程B的mmap_sem這個地方。
這樣因為進程B是在低優(yōu)先級group里面,io訪問也比高優(yōu)先級group慢,但是此時卻阻塞住了高優(yōu)先級group里面的進程。
優(yōu)化方法:
同步社區(qū)該patch
[RFC][PATCH 0/9][V2] drop the mmap_sem when doing IO in the fault path
優(yōu)化方向4: 其他的一些優(yōu)化持鎖線程的工作負(fù)載方法。
前面提了,解決因mmap_sem競爭導(dǎo)致的問題,關(guān)鍵是找到持鎖線程信息。找到后,還需要優(yōu)化該持鎖線程在持鎖后的工作負(fù)載。
只有保證持鎖過程中,工作時間越短,就越能降低性能問題出現(xiàn)的概率。
1: 比如之前還碰到過一個問題:
某業(yè)務(wù)進程包含若干個工作線程和一個數(shù)據(jù)加載線程。數(shù)據(jù)加載線程需要將工作線程不再使用的上一份數(shù)據(jù)釋放掉,具體需要做munmap 大塊內(nèi)存 (20G+)工作 ,
結(jié)果在釋放數(shù)據(jù)過程中,工作線程的性能受到了影響。
2: 這類問題原因也是:
釋放數(shù)據(jù)時,需要做munmap工作,進一步需要以寫著身份拿工作線程的mmap_sem鎖。這樣會導(dǎo)致工作線程獲取自身mmap_sem的等待時間變長。
通過問題進一步分析,發(fā)現(xiàn)munmap中最耗時的是free_pgtables,做這個也需要寫者拿mmap_sem鎖。
網(wǎng)絡(luò)上一些好的建議是:實現(xiàn)分段munmap,或者Drop mmap_sem during unmapping large map。
3: 其實還有個好的優(yōu)化方法:
不考慮mmap_sem的影響,munmap工作本身就會比較耗時,所以后來有了madvise MADV_FREE和MADV_DONTNEED的優(yōu)化。
所以這個地方可以嘗試用madvise來代替munmap,會縮短釋放數(shù)據(jù)的時間的。
優(yōu)化方向5: Speculative page faults
前面那些優(yōu)化方向都是正面回避該mmap_sem自身的一些特性問題(詳見上面第二大節(jié)的總結(jié)),從側(cè)面,從程序自身業(yè)務(wù)著手去優(yōu)化解決問題。
這個投機性缺頁異常則是嘗試從正面優(yōu)化該mmap_sem問題。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1382瀏覽量
40430 -
Linux
+關(guān)注
關(guān)注
87文章
11345瀏覽量
210409 -
優(yōu)化
+關(guān)注
關(guān)注
0文章
220瀏覽量
23961 -
線程
+關(guān)注
關(guān)注
0文章
505瀏覽量
19758
原文標(biāo)題:內(nèi)核mmap_sem鎖的危害和相關(guān)優(yōu)化
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論