如果您想在实现 mmap 的自定义内核驱动程序中使用
remap_pfn_range
函数,您知道您必须获取“mm 信号量”。但从我可以找到的公开示例来看,尚不清楚如何做到这一点。我认为编辑可用的示例将使社区受益,我愿意这样做,但我不知道从哪里开始。
根据文档:只有在调用时保持 mm 信号量时才是安全的。
查看实际来源会有所帮助 [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 多年了,当我需要找到一些我不知道的东西时,我就是这么做的。
首先,我对 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 属性的示例。
我必须再次承认,我真的不明白发生了什么事:)不知何故我想找到解决方案。
希望对您有帮助, 戈汗。
补充 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/
我在修改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 源码并查看回溯中的函数:
/*
* The caller must hold down_write(¤t->mm->mmap_sem).
*/
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信号量了。