2.5 linux存储管理-用户堆栈扩展

越界访问并不都是坏事,不过只有一种情况。
当用户堆栈过小时,可以通过越界访问使其得到伸展。

进程地址空间如下图所示(从下到上地址增加),每个进程在逻辑上都有这样一个内存描述图。这种内存描述图是mm_struct结构的图形化描述。它描述了进程的内存需求。

堆栈的扩展引发的缺页异常
正常的堆栈扩展操作:正常的堆栈操作可能会引发一次缺页异常,(%esp - 4)可能属于堆栈区和数据\代码区之间的空洞,这必然会引发一次缺页异常。
如何判断是否为正常的对扩展操作:
x86 汇编指令有:push和pusha,push是扩展4个字节(%esp-4),pusha是扩展32个字节(%esp-32),所以扩展的数量超过32Byte,就一定是错的了。
   
   
  1. if (!(vma->vm_flags & VM_GROWSDOWN))
  2. goto bad_area;
  3. if (error_code & 4) {
  4. /*
  5. * accessing the stack below %esp is always a bug.
  6. * The "+ 32" is there due to some instructions (like
  7. * pusha) doing post-decrement on the stack and that
  8. * doesn't show up until later..
  9. */
  10. if (address + 32 < regs->esp)
  11. goto bad_area;
  12. }
  13. if (expand_stack(vma, address))
  14. goto bad_area;
  15. //后面有good_area的处理

堆栈的扩展操作:
进程的 task_struct 结构中有一个rlim结构数组,里面规定了每种资源的限制。我们利用一些和该数组访问资源的限制值。
rlim[RLIMIT_STACK] 里面存放了进程堆栈的相关限制,expend_stack需要检查这一点。一般情况下,进程的堆栈空间是够用的,但是当动态分配过多时,就不能扩展了,会返回-ENOMEM。
但是,expend_stack只是改变了堆栈区的vma结构,并没有建新的映射。(修改了进程的内存描述)
   
   
  1. static inline int expand_stack(struct vm_area_struct * vma, unsigned long address)
  2. {
  3. unsigned long grow;
  4. address &= PAGE_MASK;
  5. grow = (vma->vm_start - address) >> PAGE_SHIFT;
  6. if (vma->vm_end - address > current->rlim[RLIMIT_STACK].rlim_cur ||
  7. ((vma->vm_mm->total_vm + grow) << PAGE_SHIFT) > current->rlim[RLIMIT_AS].rlim_cur)
  8. return -ENOMEM;
  9. vma->vm_start = address;
  10. vma->vm_pgoff -= grow;
  11. vma->vm_mm->total_vm += grow;
  12. if (vma->vm_flags & VM_LOCKED)
  13. vma->vm_mm->locked_vm += grow;
  14. return 0;
  15. }
(参见 include/linux/mm.h)

good_area处理:
expend_stack成功后会进入good_area的处理
首先,根据error进行一些可知错误的判断,如果错误就进入bad_area
然后,处理错误
    
    
  1. /*
  2. * Ok, we have a good vm_area for this memory access, so
  3. * we can handle it..
  4. */
  5. good_area:
  6. info.si_code = SEGV_ACCERR;
  7. write = 0;
  8. switch (error_code & 3) {
  9. default: /* 3: write, present */
  10. #ifdef TEST_VERIFY_AREA
  11. if (regs->cs == KERNEL_CS)
  12. printk("WP fault at %08lx\n", regs->eip);
  13. #endif
  14. /* fall through */
  15. case 2: /* write, not present */
  16. if (!(vma->vm_flags & VM_WRITE))
  17. goto bad_area;
  18. write++;
  19. break;
  20. case 1: /* read, present */
  21. goto bad_area;
  22. case 0: /* read, not present */
  23. if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
  24. goto bad_area;
  25. }
  26. /*
  27. * If for any reason at all we couldn't handle the fault,
  28. * make sure we exit gracefully rather than endlessly redo
  29. * the fault.
  30. */
  31. switch (handle_mm_fault(mm, vma, address, write)) {
  32. case 1:
  33. tsk->min_flt++;
  34. break;
  35. case 2:
  36. tsk->maj_flt++;
  37. break;
  38. case 0:
  39. goto do_sigbus;
  40. default:
  41. goto out_of_memory;
  42. }

非错误处理:
这里的非错误处理是指排除已知错误后进行的处理,其实主要就是物理页面未映射。处理包括申请物理页面、交换页面的准备工作
    
    
  1. 1189 /*
  2. 1190 * By the time we get here, we already hold the mm semaphore
  3. 1191 */
  4. 1192 int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct * vma,
  5. 1193 unsigned long address, int write_access)
  6. 1194 {
  7. 1195 int ret = -1;
  8. 1196 pgd_t *pgd;
  9. 1197 pmd_t *pmd;
  10. 1198
  11. 1199 pgd = pgd_offset(mm, address); //计算pgd表项的指针
  12. 1200 pmd = pmd_alloc(pgd, address); //由于是32bit,所以pmd的分配一定会成功
  13. 1201
  14. 1202 if (pmd) {
  15. 1203 pte_t * pte = pte_alloc(pmd, address); //分配pte表项
  16. 1204 if (pte)
  17. 1205 ret = handle_pte_fault(mm, vma, address, write_access, pte);
  18. 1206 }
  19. 1207 return ret;
  20. 1208 }

pte_alloc函数处理:
get_pte_fast 返回的物理页面 是从物理页面缓冲池(内核释放的页面表先不释放物理页面,而是构建一个缓冲池)中获取的
get_pte_slow 是从 get_pte_kernel_slow()分配的
set_pmd处理了一些标志位(在pmd表项中)
但是,此时尚未处理pte表项
    
    
  1. 120 extern inline pte_t * pte_alloc(pmd_t * pmd, unsigned long address)
  2. 121 {
  3. 122 address = (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1);
  4. 123
  5. 124 if (pmd_none(*pmd))
  6. 125 goto getnew;
  7. 126 if (pmd_bad(*pmd))
  8. 127 goto fix;
  9. 128 return (pte_t *)pmd_page(*pmd) + address;
  10. 129 getnew:
  11. 130 {
  12. 131 unsigned long page = (unsigned long) get_pte_fast();
  13. 132
  14. 133 if (!page)
  15. 134 return get_pte_slow(pmd, address);
  16. 135 set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(page)));
  17. 136 return (pte_t *)page + address;
  18. 137 }
  19. 138 fix:
  20. 139 __handle_bad_pmd(pmd);
  21. 140 return NULL;
  22. 141 }

handle_pte_fault: 
物理页面映射,重点在于do_no_page。
当我们发现page不在内存当中时(pte_present),我们需要执行do_no_page
如果vma->vm_ops->nopage()被指定了,那么我们就执行该函数。但是,有可能为未被指定,那么内核会调用do_anonymous_page()完成物理页面的分配。
    
    
  1. 1153 static inline int handle_pte_fault(struct mm_struct *mm,
  2. 1154 struct vm_area_struct * vma, unsigned long address,
  3. 1155 int write_access, pte_t * pte)
  4. 1156 {
  5. 1157 pte_t entry;
  6. 1158
  7. 1159 /*
  8. 1160 * We need the page table lock to synchronize with kswapd
  9. 1161 * and the SMP-safe atomic PTE updates.
  10. 1162 */
  11. 1163 spin_lock(&mm->page_table_lock);
  12. 1164 entry = *pte;
  13. 1165 if (!pte_present(entry)) {
  14. 1166 /*
  15. 1167 * If it truly wasn't present, we know that kswapd
  16. 1168 * and the PTE updates will not touch it later. So
  17. 1169 * drop the lock.
  18. 1170 */
  19. 1171 spin_unlock(&mm->page_table_lock);
  20. 1172 if (pte_none(entry))
  21. 1173 return do_no_page(mm, vma, address, write_access, pte);
  22. 1174 return do_swap_page(mm, vma, address, pte, pte_to_swp_entry(entry), write_access);
  23. 1175 }
  24. 1176
  25. 1177 if (write_access) {
  26. 1178 if (!pte_write(entry))
  27. 1179 return do_wp_page(mm, vma, address, pte, entry);
  28. 1180
  29. 1181 entry = pte_mkdirty(entry);
  30. 1182 }
  31. 1183 entry = pte_mkyoung(entry);
  32. 1184 establish_pte(vma, address, pte, entry);
  33. 1185 spin_unlock(&mm->page_table_lock);
  34. 1186 return 1;
  35. 1187 }

do_anonymous_page函数:
该函数为映射的最低一层
只要是只读页面,一开始都是映射到同一个物理页面empty_zero_page,不管其虚拟地址是什么。
只有可写的页面才会申请物理页面,alloc_page分配了一个物理页面
set_pte为止,虚拟页面到物理页面的映射就建立了。
    
    
  1. /*
  2. * This only needs the MM semaphore
  3. */
  4. static int do_anonymous_page(struct mm_struct * mm, struct vm_area_struct * vma, pte_t *page_table, write_access, unsigned long addr)
  5. {
  6. struct page *page = NULL;
  7. pte_t entry = pte_wrprotect(mk_pte(ZERO_PAGE(addr), vma->vm_page_prot));
  8. if (write_access) {
  9. page = alloc_page(GFP_HIGHUSER);
  10. if (!page)
  11. return -1;
  12. clear_user_highpage(page, addr);
  13. entry = pte_mkwrite(pte_mkdirty(mk_pte(page, vma->vm_page_prot)));
  14. mm->rss++;
  15. flush_page_to_ram(page);
  16. }
  17. set_pte(page_table, entry);
  18. /* No need to invalidate - it was non-present before */
  19. update_mmu_cache(vma, addr, entry);
  20. return 1; /* Minor fault */
  21. }










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值