linux內核之情景分析mmap操作


進程可以通過mmap把一個已打開文件映射到用戶空間.
   
   
   
           
  1. mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset)
start表示用戶空間映射的起始地址,offset文件的起始length長度.
   
   
   
           
  1. asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
  2. unsigned long prot, unsigned long flags,
  3. unsigned long fd, unsigned long pgoff)
  4. {
  5. return do_mmap2(addr, len, prot, flags, fd, pgoff);
  6. }
其主體是do_mmap2,注意其標志MAP_ANONYMOUS表示匿名映射
   
   
   
           
  1. /* common code for old and new mmaps */
  2. static inline long do_mmap2(
  3. unsigned long addr, unsigned long len,
  4. unsigned long prot, unsigned long flags,
  5. unsigned long fd, unsigned long pgoff)
  6. {
  7. int error = -EBADF;
  8. struct file * file = NULL;
  9. flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
  10. if (!(flags & MAP_ANONYMOUS)) {//map_anonymous表示沒有文件,只是在指定位置分配內存
  11. file = fget(fd);//上一條表示,沒有文件,就跳過if以下,有文件則打開文件
  12. if (!file)//如果文件不存在,直接返回錯誤
  13. goto out;
  14. }
  15. down(&current->mm->mmap_sem);//信號量down操作
  16. error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);//mmap主體操作還是這個
  17. up(&current->mm->mmap_sem);//信號量up操作
  18. if (file)
  19. fput(file);
  20. out:
  21. return error;
  22. }
其主體為do_mmap_pgoff
   
   
   
           
  1. do_mmap_pgoff(file, addr, len, prot, flags, pgoff);
第一個參數為打開文件,第二個地址,第三長度,第四個參數為訪問權限,第五個參數為其他控制目的,第6個為偏移量
   
   
   
           
  1. unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, unsigned long len,
  2. unsigned long prot, unsigned long flags, unsigned long pgoff)
  3. {
  4. struct mm_struct * mm = current->mm;//獲取當前進程的內存描述符
  5. struct vm_area_struct * vma;
  6. int correct_wcount = 0;
  7. int error;
  8. //file非0表示是文件,其對應一定有相關操作函數.
  9. if (file && (!file->f_op || !file->f_op->mmap))
  10. return -ENODEV;
  11. //長度對齊,如果為0,直接返回
  12. if ((len = PAGE_ALIGN(len)) == 0)
  13. return addr;
  14. //長度大於3g或者addr+len映射區域超過用戶空間,返回錯誤
  15. if (len > TASK_SIZE || addr > TASK_SIZE-len)
  16. return -EINVAL;
  17. // 偏移量是否超過了長度
  18. /* offset overflow? */
  19. if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
  20. return -EINVAL;
  21. //映射次數是否超過了限定
  22. /* Too many mappings? */
  23. if (mm->map_count > MAX_MAP_COUNT)
  24. return -ENOMEM;
  25. //是否加鎖?這里不知道了
  26. /* mlock MCL_FUTURE? */
  27. if (mm->def_flags & VM_LOCKED) {
  28. unsigned long locked = mm->locked_vm << PAGE_SHIFT;
  29. locked += len;
  30. if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)
  31. return -EAGAIN;
  32. }
  33. /* Do simple checking here so the lower-level routines won't have
  34. * to. we assume access permissions have been handled by the open
  35. * of the memory object, so we don't do any here.
  36. */
  37. if (file != NULL) { //如果文件存在
  38. switch (flags & MAP_TYPE) {//映射類型:讀寫
  39. case MAP_SHARED://共享映射
  40. if ((prot & PROT_WRITE) && !(file->f_mode & FMODE_WRITE))
  41. return -EACCES;
  42. //確保我們不被允許寫在一個只可追加的文件
  43. /* Make sure we don't allow writing to an append-only file.. */
  44. if (IS_APPEND(file->f_dentry->d_inode) && (file->f_mode & FMODE_WRITE))
  45. return -EACCES;
  46. //確保我們的文件沒有鎖
  47. /* make sure there are no mandatory locks on the file. */
  48. if (locks_verify_locked(file->f_dentry->d_inode))
  49. return -EAGAIN;
  50. /* fall through */
  51. case MAP_PRIVATE://私有映射
  52. if (!(file->f_mode & FMODE_READ))
  53. return -EACCES;
  54. break;
  55. default:
  56. return -EINVAL;
  57. }
  58. }
  59. /* Obtain the address to map to. we verify (or select) it and ensure
  60. * that it represents a valid section of the address space.
  61. */
  62. if (flags & MAP_FIXED) {//如果參數flag的標志位map_fixed為0表示,指定映射位置只是一個參考值
  63. if (addr & ~PAGE_MASK)
  64. return -EINVAL;
  65. } else {//不滿足由內核從空洞執行分配一個區域
  66. addr = get_unmapped_area(addr, len);
  67. if (!addr)
  68. return -ENOMEM;
  69. }
  70. /* Determine the object being mapped and call the appropriate
  71. * specific mapper. the address has already been validated, but
  72. * not unmapped, but the maps are removed from the list.
  73. */
  74. vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//從slab獲取一個vma結構
  75. if (!vma)
  76. return -ENOMEM;
  77. vma->vm_mm = mm;//指向內存描述符
  78. vma->vm_start = addr;//vma的起始地址指向映射的起始地址
  79. vma->vm_end = addr + len;//同上
  80. vma->vm_flags = vm_flags(prot,flags) | mm->def_flags;//設置vma屬性
  81. if (file) {//如果file為0,表示匿名映射,僅僅是為了創建虛擬區間,或者僅在於建立從物理空間到虛存空間映射,而非文件映射
  82. VM_ClearReadHint(vma);//以下代碼設置一堆屬性
  83. vma->vm_raend = 0;
  84. if (file->f_mode & FMODE_READ)
  85. vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
  86. if (flags & MAP_SHARED) {
  87. vma->vm_flags |= VM_SHARED | VM_MAYSHARE;
  88. /* This looks strange, but when we don't have the file open
  89. * for writing, we can demote the shared mapping to a simpler
  90. * private mapping. That also takes care of a security hole
  91. * with ptrace() writing to a shared mapping without write
  92. * permissions.
  93. *
  94. * We leave the VM_MAYSHARE bit on, just to get correct output
  95. * from /proc/xxx/maps..
  96. */
  97. if (!(file->f_mode & FMODE_WRITE))
  98. vma->vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
  99. }
  100. } else {
  101. vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
  102. if (flags & MAP_SHARED)
  103. vma->vm_flags |= VM_SHARED | VM_MAYSHARE;
  104. }
  105. vma->vm_page_prot = protection_map[vma->vm_flags & 0x0f];
  106. vma->vm_ops = NULL;
  107. vma->vm_pgoff = pgoff;//表示所映射內容在文件的起點,此值用於發生缺頁異常根據虛存地址計算出相應頁面的文件位置
  108. vma->vm_file = NULL;
  109. vma->vm_private_data = NULL;
  110. /* Clear old maps */
  111. error = -ENOMEM;
  112. if (do_munmap(mm, addr, len))//檢查目的地址的vma空間是否已經使用(如果map_fixed設置為1的話)
  113. goto free_vma;//已經使用則釋放free_vma
  114. //檢查是否超過了限制
  115. /* Check against address space limit. */
  116. if ((mm->total_vm << PAGE_SHIFT) + len
  117. > current->rlim[RLIMIT_AS].rlim_cur)
  118. goto free_vma;
  119. //檢查當前進程專用的可寫區間而物理頁面不足的情況
  120. /* Private writable mapping? Check memory availability.. */
  121. if ((vma->vm_flags & (VM_SHARED | VM_WRITE)) == VM_WRITE &&
  122. !(flags & MAP_NORESERVE) &&
  123. !vm_enough_memory(len >> PAGE_SHIFT))
  124. goto free_vma;
  125. if (file) {//vm_deanwrite職位表示從文件到vma映射,表示不允許同過常規方式讀寫文件
  126. if (vma->vm_flags & VM_DENYWRITE) {
  127. error = deny_write_access(file);
  128. if (error)
  129. goto free_vma;
  130. correct_wcount = 1;
  131. }
  132. vma->vm_file = file;
  133. get_file(file);//遞增file結構的共享計數
  134. error = file->f_op->mmap(file, vma);//一個文件操作必須存在mmap,否則釋放vma
  135. if (error)
  136. goto unmap_and_free_vma;
  137. } else if (flags & MAP_SHARED) {//共享映射
  138. error = shmem_zero_setup(vma);
  139. if (error)
  140. goto free_vma;
  141. }
  142. /* Can addr have changed??
  143. *為了防止flags與addr有變化,再重新設置一遍,
  144. * Answer: Yes, several device drivers can do it in their
  145. * f_op->mmap method. -DaveM
  146. */
  147. flags = vma->vm_flags;
  148. addr = vma->vm_start;
  149. insert_vm_struct(mm, vma);//插入當前進程的內存描述符
  150. if (correct_wcount)
  151. atomic_inc(&file->f_dentry->d_inode->i_writecount);
  152. mm->total_vm += len >> PAGE_SHIFT;//映射區域+len>>page_shit
  153. if (flags & VM_LOCKED) {//需要加鎖
  154. mm->locked_vm += len >> PAGE_SHIFT;
  155. make_pages_present(addr, addr + len);//建立初始映射
  156. }
  157. return addr;
  158. unmap_and_free_vma:
  159. if (correct_wcount)
  160. atomic_inc(&file->f_dentry->d_inode->i_writecount);
  161. vma->vm_file = NULL;
  162. fput(file);
  163. /* Undo any partial mapping done by a device driver. */
  164. flush_cache_range(mm, vma->vm_start, vma->vm_end);
  165. zap_page_range(mm, vma->vm_start, vma->vm_end - vma->vm_start);
  166. flush_tlb_range(mm, vma->vm_start, vma->vm_end);
  167. free_vma:
  168. kmem_cache_free(vm_area_cachep, vma);
  169. return error;
  170. }
以上是文件與虛擬區間之間建立的映射,但具體的映射(從虛擬地址映射到物理地址)還沒開始,而是把具體頁面的映射推遲到真正需要的時候才進行,具體映射的簡歷,物理頁面的換入和換出分別准備了一些函數,filemap_nopage(),ext2_readpage(),ext2_writepage()
什么時候調用呢
(1)該區間中的一個頁面首次收到訪問時,會由於頁面沒映射發生缺頁異常,相應的異常處理程序do_no_page(),對於ext2系統,do_no_page()會通過ext2_readpage()分配一個空閑內存頁面並從文件讀入相應頁面,並建立映射.
(2)建立映射后,往頁面寫使得頁面變臟,但頁面的內容並不會立即寫回文件.而是由內核線程bdflush()周期性的運行時通過page_launder()間接調用ext2_writepage(),將頁面的內容寫入文件.如果頁面很長時間沒有收到訪問,那就會被try_to_swap_out()解除映射而轉入不活躍狀態,如果頁面是臟的那就也調用ext2_writepage()寫入然后再解除映射
(3)解除了映射的頁面再次收到訪問時又會發生缺頁異常,因為頁面無映射進入do_no_page()
mmap映射,如果文件映射的一個頁面長期得不到訪問,將直接把頁表項設置為0,如果訪問到將重新alloc_page分配一個新頁面,然后把文件讀取到新頁面,再建立映射,對於普通的換入/換出則是發生缺頁異常從swap分區查找到換出的頁面,然后建立映射
















免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM