C中如何获取“mm信号量”?

问题描述 投票:0回答:4

如果您想在实现 mmap 的自定义内核驱动程序中使用

remap_pfn_range
函数,您知道您必须获取“mm 信号量”。但从我可以找到的公开示例来看,尚不清楚如何做到这一点。我认为编辑可用的示例将使社区受益,我愿意这样做,但我不知道从哪里开始。

根据文档只有在调用时保持 mm 信号量时才是安全的。

c linux-kernel
4个回答
7
投票

查看实际来源会有所帮助 [for

remap_pfn_range
]。它位于
mm
子目录中,特别是在
mm/memory.c

在那里你会看到

struct mm_struct *mm = vma->vm_mm;
,这就是你想要的
mm
。请注意,这也是[可能]
current->mm

如果您查看更多文件[特别是

mm/mmap.c
],您将看到
down_write(&mm->mmap_sem)
up_write(&mm->mmap_sem)
[它们是内核的信号量原语]。请注意,如果您只需要从该区域读取,则有
down_read
up_read

所以,把它们放在一起:

void
myfnc(...)
{
    struct vm_area_struct *vma = ...;
    struct mm_struct *mm = vma->vm_mm;

    ...

    down_write(&mm->mmap_sem);
    remap_pfn_range(vma,...);
    up_write(&mm->mmap_sem);

    ...
}

除了文档之外,找到这些东西的最好方法之一就是查看源代码本身。我编写 Linux 内核/驱动程序代码已经有 20 多年了,当我需要找到一些我不知道的东西时,我就是这么做的。


3
投票

首先,我对 Linux 不太了解:)而且我也不知道为什么要找你:)

这是我发现的:

1- mm 指此处 15.1.7 下的内存映射。进程内存映射标题:

内存管理难题的最后一部分是进程内存映射结构,它将所有其他数据结构保存在一起。系统中的每个进程(除了一些内核空间辅助线程之外)都有一个 struct mm_struct (在 中定义),其中包含进程的虚拟内存区域列表、页表和各种其他内存管理内务信息,以及带有信号量(mmap_sem)和自旋锁(page_table_lock)。在任务结构中可以找到指向该结构的指针;在驱动程序需要访问它的极少数情况下,通常的方法是使用 current->mm。注意,内存管理结构可以在进程之间共享;例如,Linux 线程的实现就是以这种方式工作的。

2-

mm_struct
定义为:

struct mm_struct {
    int count;
    pgd_t * pgd;
    unsigned long context;
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack, start_mmap;
    unsigned long arg_start, arg_end, env_start, env_end;
    unsigned long rss, total_vm, locked_vm;
    unsigned long def_flags;
    struct vm_area_struct * mmap;
    struct vm_area_struct * mmap_avl;
    struct semaphore mmap_sem; /**this what you are looking for**/
};

3-最后,这里有锁定和解锁(down_write 和 up_write)mmap_sem 属性的示例。

我必须再次承认,我真的不明白发生了什么事:)不知何故我想找到解决方案。

希望对您有帮助, 戈汗。


0
投票

补充 Craig 的答案,从 Linux 5.8 开始,VMA 锁更改为

mmap_lock
。您必须执行以下操作:

down_write(&mm->mmap_lock);
remap_pfn_range...
up_write(&mm->mmap_lock);

此外,一套新的API正在开发中: https://elixir.bootlin.com/linux/v5.8.2/source/include/linux/mmap_lock.h

VMA 锁是 Linux 社区中一个有争议的问题: https://lwn.net/Articles/787629/


0
投票

我在修改cmem_gdb_access/module/cmem.c时发现了这个问题,这是一个实现mmap的自定义内核驱动程序

在从

cmem_mmap
mmap
字段引用的
struct file_operations
函数中,尝试在对 remap_pfn_range 的调用周围添加调用以获取
mm 信号量
,如 answer 中所建议:

    vma->vm_ops = &custom_vm_ops;
    down_write(&vma->vm_mm->mmap_sem);
    ret = remap_pfn_range(vma, vma->vm_start,
            vma->vm_pgoff,
            sz, vma->vm_page_prot);
    up_write(&vma->vm_mm->mmap_sem);

但是,添加

down_write
up_write
调用后,尝试对驱动程序执行
mmap
调用会导致用户进程调用者挂起,并出现以下回溯:

$ sudo cat /proc/2932/stack 
[<0>] rwsem_down_write_slowpath+0x38d/0x560
[<0>] cmem_mmap+0x73/0xa6 [cmem_dev]
[<0>] mmap_region+0x3e3/0x640
[<0>] do_mmap+0x34b/0x540
[<0>] vm_mmap_pgoff+0xd4/0x120
[<0>] ksys_mmap_pgoff+0x1bc/0x270
[<0>] do_syscall_64+0x5b/0x1b0
[<0>] entry_SYSCALL_64_after_hwframe+0x61/0xc6

上面的挂起是在 AmlaLinux 8.9 的

4.18.0-513.18.1.el8_9.x86_64
内核中。

浏览 v4.18 源码并查看回溯中的函数:

  1. do_mmap有评论:
    /*
     * The caller must hold down_write(&current->mm->mmap_sem).
     */
    
  2. vm_mmap_pgoff 获取对
    do_mmap_pgoff
    调用周围的 mm 信号量:
         if (down_write_killable(&mm->mmap_sem))
             return -EINTR;
         ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff,
                     &populate, &uf);
         up_write(&mm->mmap_sem);
    
    do_mmap_pgoff是一个静态内联函数,它调用
    do_mmap
    ,这解释了为什么
    do_mmap_pgoff
    不出现在回溯中。

上面的意思是,在 v18.0 内核中,当调用自定义内核驱动程序

mmap
函数时,mm 信号量已经被持有。这样一来,就不需要在驱动
mmap
函数内部获取mm信号量了。

© www.soinside.com 2019 - 2024. All rights reserved.