处理地址空间内的错误地址

如果addr地址属于进程的地址空间,则do_page_fault()转到good_area标记处的语句执行:
/*
 * Ok, we have a good vm_area for this memory access, so
 * we can handle it..
 */
good_area:
 si_code = SEGV_ACCERR;
 write = 0;
 switch (error_code & 3) {
  default: /* 3: write, present */
#ifdef TEST_VERIFY_AREA
   if (regs->cs == KERNEL_CS)
    printk("WP fault at %08lx/n", regs->eip);
#endif
   /* fall through */
  case 2:  /* write, not present */
   if (!(vma->vm_flags & VM_WRITE))
    goto bad_area;
   write++;
   break;
  case 1:  /* read, present */
   goto bad_area;
  case 0:  /* read, not present */
   if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
    goto bad_area;
 }

 

首先,缺页异常中可能在用户态,即error_code & 3 = 2,如果异常由写访问引起,函数检查这个线性区是否可写(!(vma->vm_flags & VM_WRITE))。如果不可写,跳到bad_area代码处;如果可写,把write局部变量置为1。

 

如果异常由读或执行访问引起,函数检查这一页是否已经存在于RAM中。在存在的情况下,异常发生是由于进程试图访问用户态下的一个有特权的页框(页框的User/Supervisor标志被清除),因此函数跳到bad_area代码处(然而,这种情况从不会发生,因为内核不会把具有特权的页框贼给进程。)。在不存在的情况下(error_code & 3 = 0),函数还将检查这个线性区是否可读或可执行。

 

如果这个线性区的访问权限与引起异常的访问类型相匹配,则调用handle_mm_fault()函数分配一个新的页框:
survive:
 /*
  * If for any reason at all we couldn't handle the fault,
  * make sure we exit gracefully rather than endlessly redo
  * the fault.
  */
 switch (handle_mm_fault(mm, vma, address, write)) {
  case VM_FAULT_MINOR:
   tsk->min_flt++;
   break;
  case VM_FAULT_MAJOR:
   tsk->maj_flt++;
   break;
  case VM_FAULT_SIGBUS:
   goto do_sigbus;
  case VM_FAULT_OOM:
   goto out_of_memory;
  default:
   BUG();
 }

 /*
  * Did it hit the DOS screen memory VA from vm86 mode?
  */
 if (regs->eflags & VM_MASK) {
  unsigned long bit = (address - 0xA0000) >> PAGE_SHIFT;
  if (bit < 32)
   tsk->thread.screen_bitmap |= 1 << bit;
 }
 up_read(&mm->mmap_sem);
 return;

 

如果handle_mm_fault()函数成功地给进程分配一个页框,则返回VM_FAULT_MINOR或VM_FAULT_MAJOR。值VM_FAULT_MINOR表示在没有阻塞当前进程的情况下处理了缺页;这种缺页叫做次缺页(minor fault)。值VM_FAULT_MAJOR表示缺页迫使当前进程睡眠(很可能是由于当用磁盘上的数据填充所分配的页框时花费时间);阻塞当前进程的缺页就叫做主缺页(major fault)。函数也返回VM_FAULT_OOM(没有足够的内存)或VM_FAULT_STGBOS(其他任何错误)。

 

如果handle_mm_fault()返回值VM_FAULT_SIGBUS,则向进程发送SIGBUS信号:
do_sigbus:
 up_read(&mm->mmap_sem);

 /* Kernel mode? Handle exceptions or die */
 if (!(error_code & 4))
  goto no_context;

 /* User space => ok to do another page fault */
 if (is_prefetch(regs, address, error_code))
  return;

 tsk->thread.cr2 = address;
 tsk->thread.error_code = error_code;
 tsk->thread.trap_no = 14;
 force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
}

 

如果handle_mm_fault()不分配新的页框,就返回值VM_FAULT_OOM,此时内核通常杀死当前进程,不过,如果当前进程是init进程(tsk->pid == 1),则只是把它放在运行队列的末尾并调用调度程序;一旦init恢复执行,则又去执行handle_mm_fault():
out_of_memory:
 up_read(&mm->mmap_sem);
 if (tsk->pid == 1) {
  yield();
  down_read(&mm->mmap_sem);
  goto survive;
 }
 printk("VM: killing process %s/n", tsk->comm);
 if (error_code & 4)
  do_exit(SIGKILL);
 goto no_context;


下面我们就来详细分析handle_mm_fault()函数,这个函数是重中之重,其作用于4个参数:
mm:指向异常发生时正在CPU上运行的进程的内存描述符。
vma:指向引起异常的线性地址所在线性区的描述符。
address:引起异常的线性地址。
write_access:如果tsk试图向address写,则置为1(我们这里的情况);如果tsk试图在address读或执行,则置为0。

static inline int handle_mm_fault(struct mm_struct *mm,
   struct vm_area_struct *vma, unsigned long address,
   int write_access)
{
 return __handle_mm_fault(mm, vma, address, write_access) &
    (~VM_FAULT_WRITE);
}
/*
 * By the time we get here, we already hold the mm semaphore
 */
int __handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
  unsigned long address, int write_access)
{
 pgd_t *pgd;
 pud_t *pud;
 pmd_t *pmd;
 pte_t *pte;

 __set_current_state(TASK_RUNNING);

 count_vm_event(PGFAULT);

 if (unlikely(is_vm_hugetlb_page(vma)))
  return hugetlb_fault(mm, vma, address, write_access);

 pgd = pgd_offset(mm, address);
 pud = pud_alloc(mm, pgd, address);
 if (!pud)
  return VM_FAULT_OOM;
 pmd = pmd_alloc(mm, pud, address);
 if (!pmd)
  return VM_FAULT_OOM;
 pte = pte_alloc_map(mm, pmd, address);
 if (!pte)
  return VM_FAULT_OOM;

 return handle_pte_fault(mm, vma, address, pte, pmd, write_access);
}

 

这个函数会检查用来映射address的页中间目录和页表是否存在:
 if (!pud)
 if (!pmd)
 if (!pte)

但是pgd是肯定存在的,通过:
pgd = pgd_offset(mm, address)
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))
#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))


嗯,很简单,就是找到address对应的那个页全局目录项,赋给pgd局部变量。

 

即使address属于进程的地址空间,相应的页表也可能还没有被分配,因此,在做别的事情之前首先执行分配页目录和页表的任务:
pud = pud_alloc(mm, pgd, address);
pmd = pmd_alloc(mm, pud, address);
pte = pte_alloc_map(mm, pmd, address);

 

如果是i386就用不到pud,所以pud_alloc分配一个空的pud,handle_pte_fault也用不到,我们挑pte_alloc_map来看一下吧,pmd_alloc跟他差不多:
#define pte_alloc_map(mm, pmd, address)   /
 ((unlikely(!pmd_present(*(pmd))) && __pte_alloc(mm, pmd, address))? /
  NULL: pte_offset_map(pmd, address))
int __pte_alloc(struct mm_struct *mm, pmd_t *pmd, unsigned long address)
{
 struct page *new = pte_alloc_one(mm, address);
 if (!new)
  return -ENOMEM;

 pte_lock_init(new);
 spin_lock(&mm->page_table_lock);
 if (pmd_present(*pmd)) { /* Another has populated it */
  pte_lock_deinit(new);
  pte_free(new);
 } else {
  mm->nr_ptes++;
  inc_zone_page_state(new, NR_PAGETABLE);
  pmd_populate(mm, pmd, new);
 }
 spin_unlock(&mm->page_table_lock);
 return 0;
}
struct page *pte_alloc_one(struct mm_struct *mm, unsigned long address)
{
 struct page *pte;

#ifdef CONFIG_HIGHPTE
 pte = alloc_pages(GFP_KERNEL|__GFP_HIGHMEM|__GFP_REPEAT|__GFP_ZERO, 0);
#else
 pte = alloc_pages(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO, 0);
#endif
 return pte;
}


看见没有,如果不是高端映射,分配一个页表,其大小就是一个页面。

 

回到__handle_mm_fault中,pgd局部变量包含引用address的页全局目录项。如果需要的话,调用pud_alloc()和pmd_alloc()函数分别分配一个新的页上级目录和页中间目录(在80x86微处理器中,这种分配永远不会发生,因为页上级目录总是包含在页全局目录中,并且页中间目录或者包含在页上级目录中(PAE未激活),或者与页上级目录一块被分配(PAE被激活))。但是!pud_alloc()和pmd_alloc()函数还是会执行成功的,其返回的pud和pmd临时变量的值就是address对应的那个页全局目录项的值。然后,如果需要的话调用的pte_alloc_map()函数会分配一个新的页表。我们在__pte_alloc函数中看到,(pmd_present(*pmd))说明页全局目录指向的那个页表已经存在了,就pte_free(new),即不用分配一个新的页表了。

 

如果以上两步都成功,pte局部变量所指向的页表项就是引用address的表项。然后调用handle_pte_fault()函数检查address地址所对应的页表项,并决定如何为进程分配一个新页框:

1、如果被访问的页不存在,也就是说,这个页还没有被存放在任何一个页框中,那么,内核分配一个新的页框并适当地初始化。这种技术称为请求调页(demand paging)。

2、如果被访问的页存在但是标记为只读,也就是说,它已经被存放在一个页框中,那么,内核分配一个新的页框,并把旧页框的数据拷贝到新页框来初始化它的内容。这种技术称为写时复制(Copy On Write,COW)。

 

这两个技术都是非常非常非常重要的知识要点,后面两篇博文将会重点讨论这两个技术,我们在希望大家重视!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些常见的 Zabbix 错误及其处理方式: 1. 无法连接到 Zabbix 服务器:如果 Zabbix 代理无法连接到 Zabbix 服务器,首先检查代理配置文件中的服务器地址和端口号是否正确。还可以检查网络连接是否正常,防火墙是否允许代理连接到服务器。 2. Zabbix 服务器未接收到数据:如果 Zabbix 服务器未接收到代理发送的数据,可以检查代理配置文件中的主机名和 IP 地址是否正确。还可以检查代理是否正在运行,以及防火墙是否允许代理发送数据。 3. Zabbix 服务器缓存空间不足:如果 Zabbix 服务器缓存空间不足,可以尝试增加缓存大小,或者减少监控项数量。可以通过在配置文件中修改参数 “CacheSize” 来增加缓存大小。 4. Zabbix 数据库连接失败:如果 Zabbix 服务器无法连接到数据库,可以检查数据库配置是否正确。还可以检查数据库是否正在运行,以及防火墙是否允许 Zabbix 服务器连接到数据库。 5. Zabbix Web 界面无法访问:如果 Zabbix Web 界面无法访问,可以检查 Web 服务器是否正在运行。还可以检查防火墙是否允许访问 Web 界面。 6. Zabbix 告警未发送:如果 Zabbix 告警未发送,可以检查告警配置是否正确。还可以检查邮件服务器是否正常运行,以及防火墙是否允许 Zabbix 服务器连接到邮件服务器。 以上是一些常见的 Zabbix 错误及其处理方式。在实际使用 Zabbix 过程中,可能会遇到其他问题,需要根据具体情况进行排查和解决。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值