通常,Linux总是将文件中某个连续的部分也连续的映射到virtual memory。所以如果想进行非连续的映射,就得使用更多的资源,即分配更多的vm_area_struct。为此kernel引入了一个独立的system call,sys_remap_file_pages去简化。
mm/fremap.c
------
SYSCALL_DEFINE5(remap_file_pages, unsigned long, start, unsigned long, size,
unsigned long, prot, unsigned long, pgoff, unsigned long, flags)
它将现存的映射(pgoff,size)移到start,显然这个start必须属于已有的现存映射。所有建立的nonlinear mapping的vm_area_struct在一个链表中,属于address_apce的i_mmap_nonlinear。
最重要的是pte必须被标识为非线性映射的,这样在访问时被正确处理得到正确的页。这个标志位就是_PAGE_FILE,是pte的bit1。pte的bit0是_PAGE_PRESENT,这时!_PAGE_PRESENT。
handle_mm_fault() -> handle_pte_fault():
-------
static inline int handle_pte_fault(struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long address,
pte_t *pte, pmd_t *pmd, unsigned int flags)
{
......
if (!pte_present(entry)) {
if (pte_none(entry)) {
可见非线性映射的页看似不存在,但实际上区别与此。通常pte_none利用_PTE_NONE_MASK去判断pte是否有效,所以必须注意一些特殊的页。比如pte_val无效的页,正常情况下都是pte_none。但有可能用于非线性映射,所以必须确保_PTE_NONE_MASK不会mask掉非线性映射的页,即_PAGE_FILE。
我在fsl P4080的powerpc geust OS遇到这个问题。因为e500mc是32bit的,默认pte也是32bit。但是paravirt后的virtual MMU (hypervisor接管真实的MMU) 采用64bit pte,所以必须update _PTE_NONE_MASK和pte_to_pgoff和pgoff_to_pte按照64bit pte的format。
if (vma->vm_ops) {
if (likely(vma->vm_ops->fault))
return do_linear_fault(mm, vma, address,
pte, pmd, flags, entry);
}
return do_anonymous_page(mm, vma, address,
pte, pmd, flags);
}
if (pte_file(entry))
return do_nonlinear_fault(mm, vma, address,
pte, pmd, flags, entry);
......
而sys_remap_file_pages()主要在检查一些标志后,如果认定vma_area_struct还不是非线性的就需要设置VM_NONLINEAR。同时调用vma_prio_tree_remove()去从优先树移出,再调用vma_nonlinear_insert()插入非线性映射列表(list_add_tail(&vma->shared.vm_set.list, list))中。
更重要的是更新pte,这个由populate_range()安页大小去调用install_file_pte()
-------
static int populate_range(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, unsigned long size, pgoff_t pgoff)
{
int err;
do {
err = install_file_pte(mm, vma, addr, pgoff, vma->vm_page_prot);
if (err)
return err;
size -= PAGE_SIZE;
addr += PAGE_SIZE;
pgoff++;
} while (size);
.......
接着:
static int install_file_pte(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, unsigned long pgoff, pgprot_t prot)
{
if (!pte_none(*pte))
zap_pte(mm, vma, addr, pte);
install_file_pte()首先调用zap_pte清除那些存在的pte.主要对在内存的页flush cache和TLB,这是容易理解的.
set_pte_at(mm, addr, pte, pgoff_to_pte(pgoff)
构建新的pte。
如果一切顺利,我们可以利用make_pages_present()换入映射好的页了。
make_pages_present() -> get_user_pages() -> __get_user_pages() -> handle_mm_fault() -> handle_pte_fault() ->
这里就回到了正常的page fault的路径,
entry = *pte;
if (!pte_present(entry)) {
if (pte_none(entry)) {
if (vma->vm_ops) {
if (likely(vma->vm_ops->fault))
return do_linear_fault(mm, vma, address,
pte, pmd, flags, entry);
}
return do_anonymous_page(mm, vma, address,
pte, pmd, flags);
}
if (pte_file(entry))
return do_nonlinear_fault(mm, vma, address,
pte, pmd, flags, entry);
最终到__do_fault()。
ltp里面有对次系统调用的测试用例:
#> /opt/ltp-full/testcases/bin/remap_file_pages01
remap_file_pages01 1 PASS : Non-Linear shm file OK
remap_file_pages01 2 PASS : Non-Linear /tmp/ file OK
主用把事先mmap的再进行非线性映射,然后把映射后的值读出跟没映射前写入的值来比较是否正确。
阅读(3301) | 评论(0) | 转发(1) |