Chinaunix首页 | 论坛 | 博客
  • 博客访问: 519350
  • 博文数量: 184
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1172
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-21 13:40
个人简介

技术改变命运

文章分类

全部博文(184)

文章存档

2020年(16)

2017年(12)

2016年(156)

我的朋友

分类: LINUX

2016-08-24 10:21:28

linux内核分析之缺页中断

地址为内核空间:

1,当地址为内核地址空间并且在内核中访问时,如果是非连续内存地址,将init_mm中对应的项复制到本进程对应的页表项做修正;

2,地址为内核空间时,检查页表的访问权限;

3,如果1,2没搞定,跳到非法访问处理(在后面详细分析这个);

地址为用户空间:

4,如果使用了保留位,打印信息,杀死当前进程;

5,如果在中断上下文中火临界区中时,直接跳到非法访问;

6,如果出错在内核空间中,查看异常表,进行相应的处理;

7,查找地址对应的vma,如果找不到,直接跳到非法访问处,如果找到正常,跳到good_area

8,如果vma->start_address>address,可能是栈太小,对齐进行扩展;

9good_area处,再次检查权限;

10,权限正确后分配新页框,页表等;

简单来说,讨论linux页表就是讨论linux进程的的页表:linux页表的创建与更新都包含于进程的创建与更新中。当前的linux内核采用的是写时复制方法,在创建一个linux进程时,完全复制父进程的页表,并且将父子进程的页表均置为写保护(即写地址的时候会产生缺页异常等)。那么父子进程谁向地址空间写数据时,产生缺页异常,分配新的页,并将两个页均置为可写,按照这种方式父子进程的地址空间渐渐变得不同。
    按照上面的分析, 只需要讨论第一个进程页表初始化,进程创建时页表的拷贝,以及缺页异常时页表的更新即可。
1.init_task进程页表的初始化
    init_task的地址空间是init_mm, init_mm在内核初始化的时候就赋值给了current->active_mm. init_mm的初始化页表是swapper_pg_dir,在mips架构中swapper_pg_dir初始化在函数pagetable_init中,初始化关系是
swapper_pg_dir -> invalide_pmd_table -> invalide_pte_table 或
swapper_pg_dir -> invalide_pte_table.
即在init_mm中,页表指向的全部是invalide_pte_table。
2.创建进程时页表的拷贝
    进程创建一般调用的是do_fork函数,按照如下调用关系:
    do_fork->copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range
    找到copy_page_range函数,这个函数便是负责页表的拷贝,函数核心代码如下:
 874     do {
 875         next = pgd_addr_end(addr, end);
 876         if (pgd_none_or_clear_bad(src_pgd))
 877             continue;
 878         if (unlikely(copy_pud_range(dst_mm, src_mm, dst_pgd, src_pgd,
 879                         vma, addr, next))) {
 880             ret = -ENOMEM;
 881             break;
 882         }
 883     } while (dst_pgd++, src_pgd++, addr = next, addr != end);
     copy_pud_range便是拷贝pud表,copy_pud_range调用copy_pmd_range, copy_pmd_range调用copy_pte_range,以此完成对三级页表的复制。需要注意的是在copy_pte_range调用的copy_one_pte中有如下代码:
 694     if (is_cow_mapping(vm_flags)) {
 695         ptep_set_wrprotect(src_mm, addr, src_pte);
 696         pte = pte_wrprotect(pte);
 697     }
这里便是判断如果采用的是写时复制,便将父子页均置为写保护,即会产生如下所示的缺页异常。
3.缺页异常时页表的更新
  由页表的初始化可以看到,init_mm的页表全指向无效页表,然而普通的进程中不可能页表均指向无效项,因此肯定拥有一个不断扩充页表的机制,这个机制是通过缺页异常实现的。
  以mips为例,mips的缺页异常最终会调用do_page_fault,do_page_fault调用handle_mm_fault,handle_mm_fault是公共代码,一般所有的缺页异常均会调用handle_mm_fault的核心代码如下:
3217     pud = pud_alloc(mm, pgd, address);
3218     if (!pud)
3219         return VM_FAULT_OOM;
3220     pmd = pmd_alloc(mm, pud, address);
3221     if (!pmd)
3222         return VM_FAULT_OOM;
3223     pte = pte_alloc_map(mm, pmd, address);
3224     if (!pte)
3225         return VM_FAULT_OOM;
其中pud_alloc代码如下:
1056 static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
1057 {   
1058     return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?
1059         NULL: pud_offset(pgd, address);
1060 }   
其中pgd_none用于判断pgd是否为invalide,如果是可调用__pud_alloc,如果不是获得其地址继续查。
pmd_alloc函数和pte_alloc_map函数类似。
因此可以看出,在缺页异常中,会按照地址一次查三张页表,如果页表为invalide,比如invalide_pmd_table或invalide_pte_table,则会分配一个新的页表项取代invalide的页表项。这便是页表扩充的机制。
需要注意的是handle_mm_fault最终会调用handle_pte_fault,在handle_pte_fault函数中有如下代码:
3171     if (flags & FAULT_FLAG_WRITE) {
3172         if (!pte_write(entry))
3173             return do_wp_page(mm, vma, address,
3174                     pte, pmd, ptl, entry);
3175         entry = pte_mkdirty(entry);
3176     }
即在缺页异常中如果遇到写保护会调用do_wp_page,这里面会处理上面所说的写时复制中父子进程区分的问题。
如上三个部分便是linux页表的大体处理框架

 

三级页表结构示意图[zz]


图3.3 Linux的三级页表结构

Linux总是假定处理器有三级页表。每个页表通过所包含的下级页表的页面框号来访问。图3.3给出了虚拟地址是如何分割成多个域的,每个域提供了 某个指定页表的偏移。为了将虚拟地址转换成物理地址,处理器必须得到每个域的值。这个过程将持续三次直到对应于虚拟地址的物理页面框号被找到。最后再使用 虚拟地址中的最后一个域,得到了页面中数据的地址。

为了实现跨平台运行,Linux提供了一系列转换宏使得核心可以访问特定进程的页表。这样核心无需知道 页表入口的结构以及它们的排列方式。

这种策略相当成功,无论在具有三级页表结构的Alpha AXP还是两级页表的Intel X86处理器中,Linux总是使 用相同的页表操纵代码。

Linux进程调度和切换过程分析

内容:

1):从schedule()开始,几种不同类型的进程之间的调度选择;在相同类型的进程之间的调度选择算法

2):从CPUIP值的变化上,说明在switch_to宏执行后,执行分析

3):堆栈发生切换位置,在切换堆栈前后,current_thread_info变化

4):地址空间发生切换,解释地址空间的切换不会影响后续切换代码的执行

5):current宏所代表的进程发生变化的源码位置

6):任务状态段中关于内核堆栈的信息发生变化源码位置

1,从schedule()开始,说明几种不同类型的进程之间的调度选择;在相同类型的进程之间的调度选择算法。

schedule()函数中,

 

首先禁止抢占,获取当前CPU,该CPU的执行队列,队列上正在执行的进程,以及该进程的交换计数信息并释放该进程占用的锁。之后,对禁止中断,更新运行队列时钟,该队列的自旋时钟加锁,后清除当前进程的thread_flagTIF_NEED_RESCHED

 

如果进程不在可运行状态,并且可被抢占,若进程处于非阻塞挂起,则将其改为可运行,否则调用deactivate_task()函数,并修改上下文交换次数。其中在deactive_task()函数中调用了denqueue_task()函数:

 

P进程调用属于自己调度类的dequeue_task()方法,将p从当前rp运行队列上移出。例如对于公平调度队列中的进程调用以下函数:

p的所有实体除含有子实体的父进程外,从公平队列中移除。

如果运行队列上进程数是0,则先通过idle_balance函数从其他CPU上调度,进行负载均衡。

对当前运行的进程prev,通过调用它所属的类的put_prev_task方法,将当前进程放入运行队列的合适位置。下图展示过程,图为公平调度类的调度方法,之后对实时调度方法的说明(idle类方法为空):

与方法put_prev_entity()方法,将当前进程加入公平调度队列。因为如果该类是公平调度类,则调度一定会在公平调度队列中有一位置,更新当前实例的状态,并入队:

其中入队位置有该值决定entity_key

对于采用实时调度的类,调用update_curr_rt函数,并置当前进程执行开始时间是0

下图为update_curr_tr函数,

计算delta_exec值为运行队列现在的时钟值与当前进程开始值。更改当前进程的状态,修改当前实时进程的总运行时间与开始时间,对实时调度队列中的实例更新时间。

之后,在运行队列上选择下一个进程中pick_next_task函数。

对于运行队列,如果队列中进程数与公平调度队列中的进程数相同,即没有实时进程时则在公平调度队列中选择进程:

对调度类中具有最高优先级的类赋值给class,调用该类的pick_next_task方法,根据不同调度类又分为:

对于实时进程则:

对于一个实例,如果它不在实时队列组中,则返回拥有这个实例的task_struct结构为next进程并修改执行开始时间为运行队列当时钟前值。

对于公平调度进程则:

返回公平调度队列上选择不在公平调度组中的task_struct

对于idle,返回队列中的idle task_struct结构,在调度过程中,永远不会返回NULL,因为至少有idle进程的存在。

在队列中从不同类中,选择出了将要被调度的类后,如果选择的进程nextprev不同则,进行进程的上下文切换:

修改交换次数,将next至为当前进程,进行切换。

2,执行完switch_to后,又执行了battier函数,之后又执行finish_task_switch函数

 

另:

struct task_struct *__switch_to(struct task_struct *prev,struct task_struct *next);

next->thread.esp中的数据存入esp寄存器中

switch_to宏执行后,执行ret_from_fork()函数。

执行完这个函数之后,执行include/asm-x86/system.h 下的__switch_to函数()

再执行__unlazy_fpu()函数。

3,堆栈发生切换位置,在切换堆栈前后,current_thread_info变化

对于切换堆栈,在switch_to中查找修改堆栈指针代码即可即:

图中movel %[next_sp],%%esp 即为修改堆栈指针,指向next进程的堆栈。因为在内核态中,栈顶指针减去8K偏移(两页)便可得到thread_info位置,从而,在切换后current_thread_info内容为切换后的新进程的thread_info内容。

4,地址空间发生切换,解释地址空间的切换不会影响后续切换代码的执行

切换地址空间在context_switch函数的switch_mm方法,在switch_mm中,重新加载页表即修改cr3寄存器的值:

切换地址空间发生在切换堆栈之前,不会影响后续代码执行,因为进程的切换发生在内核态,内核态地址空间是共用的。没有修改堆栈指针及其他寄存器的值,即堆栈没有变,栈内值未发生改变。

5current宏所代表的进程发生变化的源码位置

修改该CPUcurrent_tasknext_p,即current宏发生了改变。

6,任务状态段中关于内核堆栈的信息发生变化源码位置

Tss段在_switch_to中被声明,并被赋值:

其中,esp0即为内核堆栈栈底指针

阅读(1350) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~