0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

内核mmap_sem锁的危害和相关优化

Linux阅码场 来源:Linux阅码场 2023-02-07 16:01 次阅读

mmap_sem锁简介

mmap_sem锁是进程为了保护自身虚拟地址空间不受多线程并发访问影响而设计的。

多线程环境下,如果想访问进程的虚拟地址空间(比如find_vma等),是要先持有该mmap_sem锁才能访问的,这样可以避免多线程并发修改进程vma区域造成的冲突。

mmap_sem锁的一些问题总结

内核mmap_sem锁设计目前存在一些问题,简单总结如下:

1:保护的东西太多,范围太广了。

mmap_sem目前保护:

1)Rbtree of VMA,比如做find_vma()时

arm64系统上,由于虚拟地址空间增大,进程的vma数量会特别多,每个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: 内核会频繁做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.

内核里面这样的soft page fault会发生很多,势必造成mmap_sem获取很频繁,引起多核cache颠簸,对多核程序性能也不好。

所以Linux kernel很多地方架构设计不怎么匹配如今的多核cpu架构。

3: 一旦有个写请求在排队了,该mmap_sem就会变成互斥意义上的锁了。

mmap_sem这种读写锁是有好处,可以实现一些并发的多线程读访问。

但是它的这种并发读访问是有条件的:

如果一个读写信号量当前没有被写者拥有并且也没有写者等待读者释放信号量,那么任何读者都可以成功获得该读写信号量,否则,读者必须被挂起直到写者释放该信号量。

如果一个读写信号量当前没有被读者或写者拥有并且也没有写者等待该信号量,那么一个写者可以成功获得该读写信号量,否则写者将被挂起,直到没有任何访问者。

简单来看个问题场景:

线程1以读者身份持mmap_sem,然后该线程由于某种原因sleep了。

下来线程2以写者身份请求持该mmap_sem锁,因为该锁已经被线程1持有,所以失败就开始排队。

再下来该锁就变成互斥锁了,再来的read请求(对应线程2,3...)就都得排队了,不能发挥读写锁并发读的优势了。

结论:

所以综合上面3个问题特点,在多核,多线程并发环境下(比如安卓系统),势必造成mmap_sem锁竞争激烈,程序性能不好。

mmap_sem锁在产品开发中的优化总结

优化方向1:方便快捷地找到持锁线程。

目前很多方法是通过在出现问题时,人为地让内核崩溃,然后再用crash工具分析内核内存dump镜像,从而在一大堆等锁和持锁线程中,找到

导致问题出现的持锁线程信息。一旦找到持锁线程,就明白问题出现的root casue, 就会有优化方案了。

但这种crash工具分析方法还是有点笨重,当然由于mmap_sem锁竞争导致的内核崩溃用这种方法是最好最对口的。

但是很多时候出问题是内核并未崩溃,只是android上层发生watchdog超时重启,或者只是某进程工作timeout。

如果是mmap_sem竞争导致的这些问题发生可以尝试用些简单快捷的方法找到持锁线程。

1) 可以在安卓fwk层发生watchdog超时时,打印下system server进程中每个线程的内核栈回溯信息。

这样如果对内核代码熟悉的话,会知道哪些地方会长时间持有mmap_sem锁,这样看下栈回溯信息,大概能猜出来哪些线程在持锁或者等锁。

如果信息还不够,还可以通过sysrq,打印出系统此时所有处于D状态和sleep状态的线程内核栈回溯信息。

因为有持锁等锁导致的内核性能问题,基本上都出现在D和sleep状态的线程里面,通过对这些信息分析,也可以大概猜出来可能的持锁者。

2) 如果问题可以复现,可以用一些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 模块。

优化方向2:核心路径中避免对/proc目录下每进程子目录做遍历访问,规避mmap_sem导致的问题。

之前碰到一个问题是由于system_server进程发生watchdog超时导致安卓fwk层重启。

watchdog超时原因是:

system_server进程一个核心路径上代码做了遍历/proc/每进程/cmdline工作,正常情况下做该工作不会出现问题。

但是由于做该工作,需要down_read获取每进程的mmap_sem锁,只要有一个进程的该锁已经被写者身份持有了,那么再获取该锁时,就得等待了。

所以异常就发生在这个地方,所以该异常会导致该核心路径中遍历每进程cmdline工作耗时了,核心路径一旦性能受影响,就会导致问题出现。

优化方法:

避免在核心路径上做这种潜在的耗时工作:遍历每进程状态。改用其他方法去达到目的。

优化方向3:同步社区一些patch,避免出现因mmap_sem竞争导致的cgroup优先级反转问题。

cgroup v2中容易出现这种优先级反转问题:

一个高优先级group里面某个进程A正在做遍历访问/proc/每进程下面状态信息的工作,所以需要获取系统中每进程的mmap_sem锁。

另外一个低优先级group里面,某进程B中有些线程在做耗时长的io操作(进入内核filemap_fault函数里面做的),操作前提前以写身份获取了mmap_sem锁。

所以写身份先获取了这把锁,那么进程A如果想通过访问获取进程B的状态信息时,就会阻塞在等待进程B的mmap_sem这个地方。

这样因为进程B是在低优先级group里面,io访问也比高优先级group慢,但是此时却阻塞住了高优先级group里面的进程。

优化方法:

同步社区该patch

[RFC][PATCH 0/9][V2] drop the mmap_sem when doing IO in the fault path

优化方向4: 其他的一些优化持锁线程的工作负载方法。

前面提了,解决因mmap_sem竞争导致的问题,关键是找到持锁线程信息。找到后,还需要优化该持锁线程在持锁后的工作负载。

只有保证持锁过程中,工作时间越短,就越能降低性能问题出现的概率。

1: 比如之前还碰到过一个问题:

某业务进程包含若干个工作线程和一个数据加载线程。数据加载线程需要将工作线程不再使用的上一份数据释放掉,具体需要做munmap 大块内存 (20G+)工作 ,

结果在释放数据过程中,工作线程的性能受到了影响。

2: 这类问题原因也是:

释放数据时,需要做munmap工作,进一步需要以写着身份拿工作线程的mmap_sem锁。这样会导致工作线程获取自身mmap_sem的等待时间变长。

通过问题进一步分析,发现munmap中最耗时的是free_pgtables,做这个也需要写者拿mmap_sem锁。

网络上一些好的建议是:实现分段munmap,或者Drop mmap_sem during unmapping large map。

3: 其实还有个好的优化方法:

不考虑mmap_sem的影响,munmap工作本身就会比较耗时,所以后来有了madvise MADV_FREE和MADV_DONTNEED的优化。

所以这个地方可以尝试用madvise来代替munmap,会缩短释放数据的时间的。

优化方向5: Speculative page faults

前面那些优化方向都是正面回避该mmap_sem自身的一些特性问题(详见上面第二大节的总结),从侧面,从程序自身业务着手去优化解决问题。

这个投机性缺页异常则是尝试从正面优化该mmap_sem问题。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1309

    浏览量

    39848
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206736
  • 优化
    +关注

    关注

    0

    文章

    220

    浏览量

    23715
  • 线程
    +关注

    关注

    0

    文章

    489

    浏览量

    19495

原文标题:内核mmap_sem锁的危害和相关优化

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    拆解mmap内存映射的本质!

    mmap 内存映射里所谓的内存其实指的是虚拟内存,在调用 mmap 进行匿名映射的时候(比如进行堆内存的分配),是将进程虚拟内存空间中的某一段虚拟内存区域与物理内存中的匿名内存页进行映射,当调用
    的头像 发表于 01-24 14:30 348次阅读
    拆解<b class='flag-5'>mmap</b>内存映射的本质!

    参加搜索引擎营销SEM培训的好处?

    1. 可以快速学习搜索引擎营销(SEM)投放策略、方法和技术,避免在自己摸索中浪费时间;2. 可以快速学习到搜索引擎营销(SEM)最新优化技术,在顶尖SEM
    发表于 04-11 14:21

    【安富莱】【RTX操作系统教程】第11章 临界段,任务和中断

    第11章 临界段,任务和中断 本章教程为大家讲解几个重要的概念,临界段,任务和中断。 本章教程配套的例子含Cortex-M3内核的S
    发表于 01-25 16:52

    第11章 临界段,任务和中断

    转rtx操作系统 本章教程为大家讲解几个重要的概念,临界段,任务和中断。本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
    发表于 10-04 19:58

    Linux的mmap文件内存映射机制

    Linux的mmap文件内存映射机制在讲述文件映射的概念时, 不可避免的要牵涉到虚存(SVR 4的VM). 实际上, 文件映射是虚存的中心概念, 文件映射一方面给用户提供了一组措施, 好似用户将文件
    发表于 03-08 09:54

    字符设备驱动另一种写法—mmap方法操作LED

    。经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作LED。 mmap方法是把设备物理地址直接映射到用户空间的一种系统调用方法,他使得
    发表于 01-02 17:38

    芯灵思SinlinxA33开发板的Linux内核信号量学习

    用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要
    发表于 02-20 15:50

    芯灵思SinlinxA64开发板 Linux内核信号量学习

    等待此信号量,则唤醒此进程。     维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护
    发表于 03-15 16:10

    Linux内核同步机制的自旋原理是什么?

    自旋是专为防止多处理器并发而引入的一种,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋
    发表于 03-31 08:06

    鸿蒙内核实现用户态快速互斥Futex设计资料合集

    linux内核开发的2.5.7版;其语义在2.5.40固定下来,然后在 2.6.x 系列稳定版内核中出现,是内核提供的一种系统调用能力。本篇为快下篇,说清楚快锁在
    发表于 03-23 14:12

    linux_mmap_access_performance

    linux 内存访问提升性能的一片论文,需要理解kernel的mmap方式,比较适合优化驱动
    发表于 02-23 15:48 14次下载

    mmap系统调用和vmalloc获取地址空间

    mmap()系统调用是在用户进程与内核之间共享内存区域的常用方法。我们最近有个程序,需要应用进程能够读取内核驱动获取的数据,经过简单的调研,决定采用mmap方式。
    的头像 发表于 02-02 16:13 3951次阅读

    linux drivers中的mmap实现

    将设备驱动内核空间的内存映射到用户空间里,可以通过用户空间中的mmap系统调用代替系统调用write和read。目的是提高读写效率。
    发表于 05-15 10:31 1436次阅读

    通过mmap实现零拷贝技术

    ,但是显存被映射到内核空间,应用程序是没有访问权限的,如果显存也能同时映射到用户空间那就不需要拷贝操作了,于是字符设备中提供了mmap接口,可以将内核空间映射的那块物理内存再次映射到用户空间,这样用户空间就可以直接访问不需要任何
    的头像 发表于 06-28 17:33 1163次阅读

    mmap原理详解

    一句话概括mmap mmap的作用,在应用这一层,是让你把文件的某一段,当作内存一样来访问。将文件映射到物理内存,将进程虚拟空间映射到那块内存。 这样,进程不仅能像访问内存一样读写文件,多个进程映射
    的头像 发表于 11-09 14:59 317次阅读
    <b class='flag-5'>mmap</b>原理详解