Linux进程调度切换和虚拟空间管理深入分析

一、Linux进程切换深入分析

#define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)

创建内核线程时使用的CLONE标志。

1#define unlikely(x)    __builtin_expect(!!(x), 0)

编译器优化,实际返回值x是整型表达式,0表示并不预期该事件发生,也就是说x0的可能性很小,这是为了让编译器对下面得语句进行优化。

2.进程内核态堆栈结构:

进程是动态实体,进程描述符是存放在动态内存中的。在一块进程内存区上,Linux存放了两个数据结构:指向task_structthread_info和内核态的进程栈。大小一般28K,这要求页面帧对齐213次幂,在X86上编译时可以配置大小为4Kthread_info在内存区开始处,内核栈从内存尾向下增长。在C语言中可以用union结构表示:

1. 8K内核栈和进程描述符task_structthread_info的相互关系

 

union thread_union {

        struct thread_info thread_info;

        unsigned long stack[2048]; /* 1024 for 4KB stacks */

    };

 

CPUesp寄存器用于执行堆栈的顶部指针,当从用户态转向内核态时,进程内核栈总是空的,所以esp就会执行堆栈底部。

使用alloc_thread_info free_thread_info用于分配和释放一个存放thread_info结构和内核堆栈的内存区。

内核通过当前esp指针可以很方便的得到thread_info结构的地址。current_thread_info(void)的原理即如下:

movl $0xffff2000,%ecx /* or 0xfffff000 for 4KB stacks */

   andl %esp,%ecx

movl %ecx,p

thread_infotask指针是第一个,所以current宏相当于current_thread_info( )->task,从而也就得到task指针。

 

每个进程有自己独立得进程空间,所有进程共享CPU寄存器。进程继续执行时必须装入寄存器恢复得数据集称为硬件上下文环境。在Linux中部分硬件上下文存放在进程描述符中,部分存放到内核态堆栈里。

 

3. 进程切换堆栈原理:

每个进程有自己独立得进程空间,所有进程共享CPU寄存器。进程继续执行时必须装入寄存器恢复得数据集称为硬件上下文环境。在Linux中部分硬件上下文存放在进程描述符中,部分存放到内核态堆栈里。

80x86体系支持在进程TSS段跳转时自动执行进程硬件上下文切换。Linux使用软件方法实现。软件方式效率差不多,当更灵活,可以控制流程,留下优化空间。

80x86TSS段保存硬件上下文内容,每个CPU有一个TSS段。从用户态到内核态切换时,从TSS中取出内核栈地址。用户态进程访问I/O端口时,TSS中的I/O访问位图可以验证权限。tss_struct描述了TSS格式,init_tss存放初始TSS内容,每次进程切换,内核更新TSS中的某些字段,以反映当前运行进程的权限等级。每个进程有个反映任务CPU状态的thread_struct结构变量thread,除eaxecx等通用寄存器内容保存在内核态堆栈中,其他大部分寄存器都保存在次结构中。该结构一部分对应于tss_struct中的内容,进程切换时把thread中某些内容更新到tss_struct中就可以反映当前任务的运行CPU环境。

struct tss_struct {

    unsigned short  back_link,__blh;

    unsigned long   esp0;

    unsigned short  ss0,__ss0h;

    unsigned long   esp1;

    unsigned short  ss1,__ss1h; /* ss1 is used to cache MSR_IA32_SYSENTER_CS */

    unsigned long   esp2;

    unsigned short  ss2,__ss2h;

    unsigned long   __cr3;

    unsigned long   eip;

    unsigned long   eflags;

    unsigned long   eax,ecx,edx,ebx;

    unsigned long   esp;

    unsigned long   ebp;

    unsigned long   esi;

    unsigned long   edi;

    unsigned short  es, __esh;

    unsigned short  cs, __csh;

    unsigned short  ss, __ssh;

    unsigned short  ds, __dsh;

    unsigned short  fs, __fsh;

    unsigned short  gs, __gsh;

    unsigned short  ldt, __ldth;

    unsigned short  trace, io_bitmap_base;

    /*

     * The extra 1 is there because the CPU will access an

     * additional byte beyond the end of the IO permission

     * bitmap. The extra byte must be all 1 bits, and must

     * be within the limit.

     */

    unsigned long   io_bitmap[IO_BITMAP_LONGS + 1];

    /*

     * Cache the current maximum and the last task that used the bitmap:

     */

    unsigned long io_bitmap_max;

    struct thread_struct *io_bitmap_owner;

    /*

     * pads the TSS to be cacheline-aligned (size is 0x100)

     */

    unsigned long __cacheline_filler[35];

    /*

     * .. and then another 0x100 bytes for emergency kernel stack

     */

    unsigned long stack[64];

} __attribute__((packed));

 

struct thread_struct {

/* cached TLS descriptors. */

struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES];

unsigned long   esp0;

unsigned long   sysenter_cs;

unsigned long   eip;

unsigned long   esp;

unsigned long   fs;

unsigned long   gs;

/* Hardware debugging registers */

unsigned long   debugreg[8];  /* %%db0-7 debug registers */

/* fault info */

unsigned long   cr2, trap_no, error_code;

/* floating point info */

union i387_union    i387;

/* virtual 86 mode info */

struct vm86_struct __user * vm86_info;

unsigned long       screen_bitmap;

unsigned long       v86flags, v86mask, saved_esp0;

unsigned int        saved_fs, saved_gs;

/* IO permissions */

unsigned long   *io_bitmap_ptr;

     unsigned long   iopl;

/* max allowed port in the bitmap, in bytes: */

unsigned long   io_bitmap_max;

};

 

4.进程切换流程解析switch_to

进程切换本质上两步:

1)       进程页表PGD切换;

2)       内核态堆栈和硬件上下文切换(包括CPU寄存器);

   上面两步通过context_switch()实现,它通过调用switch_mm()切换进程空间,switch_to切换内核上下文环境。

 

首先看看context_switch()做了些什么:

1)        进程描述符中active_mm执行进程使用的地址空间,mm执行进程拥有的地址空间,对于普通进程它们相同。对于内核线程,它的mm总为NULL。所以context_switch()首先判断if (!next->mm)next为内核线程,则使用prev的进程地址空间:

if (!next->mm) {
    next->active_mm = prev->active_mm;
    atomic_inc(&prev->active_mm->mm_count);
    enter_lazy_tlb(prev->active_mm, next);
}

2)        否则,如果next是普通进程,则用next进程空间替换prev的地址空间:

    switch_mm(oldmm, mm, next);

3)        如果prev是内核线程或者正在退出,则设置prev->active_mm runqueueprev_mmNULL

if (!prev->mm) {

      prev->active_mm = NULL;

      WARN_ON(rq->prev_mm);

      rq->prev_mm = oldmm;

  }

 

下面看看switch_mm()如何切换进程空间:

1)        获取cpu逻辑号。

2)        cpu_clear(cpu, prev->cpu_vm_mask)清除cpu_vm_mask位标志。

3)        per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_OK设置cpu_tlbstate状态。

4)        per_cpu(cpu_tlbstate, cpu).active_mm = next设置cpu_tlbstateactive_mmnext

5)        cpu_set(cpu, next->cpu_vm_mask)设置nextcpu_vm_mask标志。

6)        load_cr3(next->pgd)装载nextpgd页表到cr3寄存器。

7)       如果nextLDT描述符改变,则加载nextLDT描述符。

if (unlikely(prev->context.ldt != next->context.ldt))

            load_LDT_nolock(&next->context);

 

最后,switch_to进行内核堆栈和CPU环境切换操作:

#define switch_to(prev,next,last) do {                  /

    unsigned long esi,edi;                      /

    asm volatile("pushfl/n/t"       /* Save flags */    /

             "pushl %%ebp/n/t"                  /

             "movl %%esp,%0/n/t"    /* save ESP */      /

             "movl %5,%%esp/n/t"    /* restore ESP */   /

             "movl $1f,%1/n/t"      /* save EIP */      /

             "pushl %6/n/t"     /* restore EIP */   /

             "jmp __switch_to/n"                /

             "1:/t"                     /

             "popl %%ebp/n/t"                   /

             "popfl"                        /

             :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  /

              "=a" (last),"=S" (esi),"=D" (edi)         /

             :"m" (next->thread.esp),"m" (next->thread.eip),    /

              "2" (prev), "d" (next));              /

} while (0)

 

流程描述,prev是进程Atask结构,next是进程Btask结构,last是进程C的结构:

1)       保存prevnext指针的值到eaxedx

movl prev, %eax

movl next, %edx

2)       保存eflags ebp 寄存器内容到prev内核态堆栈中:

pushfl

pushl %ebp

 

3)       esp内容保存到prev->thread.esp中,该字段执行prev内核堆栈的top地址。

movl %esp,484(%eax)

 

4)       next->thread.esp加载到esp中,现在开始,esp执行next的内核堆栈,进程切换完成。

movl 484(%edx), %esp

 

5)       保存下面Label 1prev->thread.eip指针中,当prev进程恢复运行时,从该位置开始运行。

movl $1f, 480(%eax)

 

6)       next->thread.eip的指针内容压到next的内核态堆栈中,通常它的内容也是Label 1

pushl 480(%edx)

 

7)       跳转到__switch_to()C函数执行。

jmp __switch_to

 

8)       被替换的进程A继续执行,它在Label 1处,首先是恢复eflagsebp寄存器内容。注意这里是发生在调度器选择prevCPU上运行后,次数esp已经执行了prev的内核堆栈。

1:

      popl %ebp

    popfl

 

9)       eax内容保存到last任务结构中。这里eax是被进程A切换下来的进程Ctask结构指针。

movl %eax, last

 

5__switch_to深入分析

__switch_to参数是存放在eaxedx中的内容,这通过

#define fastcall       __attribute__((regparm(3)))告诉gcc编译器。

1)       获取tss_struct tssprev_pnext_pthread_struct结构prevnext、当前CPU逻辑ID

2)       调用__unlazy_fpu(prev_p)根据条件标志选择是否保存prev_pFPU, MMX, XMM寄存器内容

3)       load_esp0(tss, next)next的堆栈地址存放到tss中:tss->esp0 = thread->esp0

4)       savesegment(gs, prev->gs)保存gs寄存器到prev->gsfs已经在栈入口保存,esds在内核态下不需要保存。

5)       load_TLS(next, cpu)nexttls_array 缓存中加载线程的Thread-Local Storage描述符。TLSGDT表中位置678

cpu_gdt_table[cpu][6] = next_p->thread.tls_array[0];

cpu_gdt_table[cpu][7] = next_p->thread.tls_array[1];

    cpu_gdt_table[cpu][8] = next_p->thread.tls_array[2];

6)       如果当前特权级别是0并且prev->iopl != next->iopl则恢复IOPL设置set_iopl_mask(next->iopl)

7)       根据thread_infoTIF标志_TIF_WORK_CTXSWTIF_IO_BITMAP判断是否需要处理debug寄存器和IO位图:__switch_to_xtra(next_p, tss);

l         只有当next_p挂起时即if (test_tsk_thread_flag(next_p, TIF_DEBUG))使用了debug寄存器才需要恢复set_debugreg(next->debugreg[i], i)。只有调试器需要监控prev的状态时,prev_p->thread.debugreg数组的内容才会被修改。Debug寄存器dr0dr7dr4dr5不用。

l         prev_p或者next_p定义了自己的I/O访问位图时,必须更新TSSI/O bitmap

if (prev_p->thread.io_bitmap_ptr || next_p->thread.io_bitmap_ptr)

          handle_io_bitmap(&next_p->thread, &init_tss[cpu]);

进程的I/O访问位图存放在io_bitmap_ptr指针里,通常进程很少修改IO位图,只有当前时间片中访问IO端口才会把实际的IO位图加载到TSS中。

ü         next_p没有自定义位图时:

tss->io_bitmap_base = INVALID_IO_BITMAP_OFFSET; 返回

ü         如果next == tss->io_bitmap_owner则设置有效的偏移量:tss->io_bitmap_base = IO_BITMAP_OFFSET; 返回

ü         否则tss->io_bitmap_base = INVALID_IO_BITMAP_OFFSET_LAZY;

  只有第二种情况tss->io_bitmap_base设置的是有效的io_bitmap偏移量,对于其他两种情况,当用户进程访问I/O端口时将会触发"General protection "的异常,do_general_protection( )异常处理函数根据io_bitmap的值处理异常:如果是0x8000(INVALID_IO_BITMAP_OFFSET)则发送SIGSEGV信号给用户进程;如果是0x9000(INVALID_IO_BITMAP_OFFSET_LAZY)则拷贝进程的thread中的io_bitmap_ptr内容到io_bitmap中,并设置io_bitmap_base为正确的偏移量(104)

 

8)       disable_tsc(prev_p, next_p)设置cr4中的TSC Disable位。

9)       arch_leave_lazy_cpu_mode()设置CPUlazy模式。

10)    如果next_p->fpu_counter > 5则恢复next_pFPU寄存器内容:

math_state_restore()FPU寄存器存放在next_p->thread->i387中,i387i387_unionunion结构:

union i387_union {

struct i387_fsave_struct    fsave;

struct i387_fxsave_struct   fxsave;

struct i387_soft_struct soft;

};

struct i387_fxsave_struct {

unsigned short  cwd;

unsigned short  swd;

unsigned short  twd;

unsigned short  fop;

long    fip;

long    fcs;

long    foo;

long    fos;

long    mxcsr;

long    mxcsr_mask;

long    st_space[32];   /* 8*16 bytes for each FP-reg = 128 bytes */

long    xmm_space[32];  /* 8*16 bytes for each XMM-reg = 128 bytes */

long    padding[56];

} __attribute__ ((aligned (16)));

 

11)    如果需要,则从next->gs中恢复gs寄存器内容。

if (prev->gs | next->gs)

         loadsegment(gs, next->gs);

二、Linux实时调度schedule

1.概述

三种调度策略:SCHED_FIFOSCHED_RRSCHED_NORMAL

FIFO实时调度算法当调度器将CPU指定给某个进程时,它把该进程放到运行队列首;除非有更高优先级的进程,否则该进程将一直占用CPU

Round Robin实时进程调度把CPU指定给某进程,把它放到运行队列尾。时间片运行完再选择其他进程调度。这样保证了同优先级的公平竞争CPU

SCHED_NORMAL是普通的基于运行时间和等待时间等,动态调整进程优先级的一种调度策略。

实时进程优先级1100,普通101139

2.实时进程调度的时机

1)       该进程被更高优先级的进程抢占;

2)       进程执行一个阻塞操作,被放到睡眠队列,状态为TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE

3)       进程被终止(状态为TASK_STOPPED TASK_TRACED),或者进程被杀死(状态为EXIT_ZOMBIE EXIT_DEAD)

4)       进程调用sched_yield()主动放弃CPU

5)       RR实时进程用完了CPU分配的时间片;

 

3.调度器相关函数

1)       scheduler_tick( )

更新当前进程的运行时间片tick值,在update_process_times( )中调用,判断进程的时间片是否用完。

 

2)       try_to_wake_up( )

唤醒一个睡眠的进程并把它的状态设为TASK_RUNNING,插入到运行队列中。

 

3)       recalc_task_prio( )

更新进程的睡眠时间和动态优先级,SCHED_NORMAL调度。

 

4)       schedule( )

进程调度

 

5)       load_balance()

SMP系统的负载均衡。

 

4schedule( )函数

进程调度有两种方式:直接调用和延迟调用。

直接调用schedule,当前进程资源不可用时会直接调用调度器,这种情况下,内核线程进行如下处理:

1)       current插入到合适的等待队列中;

2)       current状态变为TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE

3)       调用schedule();

4)       检查资源是否可用,如果不可用,转到第2)步;

5)       一旦资源可用,从等待队列中移除current进程;

 

在设备驱动程序中也经常会检查TIF_NEED_RESCHED并调用schedule()

 

延迟调用方式是通过设置current进程的TIF_NEED_RESCHED标志为1。当恢复用户态进程的执行前,会检查该标志并决定是否调用schedule()。延迟调度的情形有:

1)       scheduler_tick()中如果current用完了时间片则设置该标志;

2)       try_to_wake_up( )中唤醒一个进程并且该进程比当前运行进程优先级高。

3)       调用sched_setscheduler()时。

 

schedule()函数工作流程:

进程切换前的工作:

1)       禁止内核抢占,初始化局部变量prev,释放prev占有的大内核锁;

need_resched:

    preempt_disable();

    prev = current;

    release_kernel_lock(prev);

2)       读取调度TSC时间,计算调整run_time时间, 更新调度状态rq->sched_cnt参数,获取rqspin锁:spin_lock_irq(&rq->lock)

3)       检查prev状态:如果状态不是TASK_RUNNING且没有在内核态被抢占,则从运行队列中移除;但是如果prev状态是TASK_INTERRUPTIBLE并且拥有非阻塞挂起的信号,则把进程状态设为TASK_RUNNING不移出运行队列。

     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

        switch_count = &prev->nvcsw;

        if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&

                unlikely(signal_pending(prev))))

            prev->state = TASK_RUNNING;

        else {

            if (prev->state == TASK_UNINTERRUPTIBLE)

                rq->nr_uninterruptible++;

            deactivate_task(prev, rq);

        }

    }

4)       获取当前CPU逻辑号,如果当前运行队列为空,则调用idle_balance(cpu, rq)从其他CPU运行队列上拉进程到本地CPU的运行队列上。如果调整后,当前运行队列仍为空则next赋为idle进程,跳转到任务切换代码行去。

    if (unlikely(!rq->nr_running)) {

        idle_balance(cpu, rq);

        if (!rq->nr_running) {

            next = rq->idle;

            rq->expired_timestamp = 0;

            goto switch_tasks;

        }

    }

5)       如果runqueue中有进程,并且当前活得进程数为0,则交换active expired队列指针。

    array = rq->active;

    if (unlikely(!array->nr_active)) {

        schedstat_inc(rq, sched_switch);

        rq->active = rq->expired;

        rq->expired = array;

        array = rq->active;

        rq->expired_timestamp = 0;

        rq->best_expired_prio = MAX_PRIO;

    }

 

6)       从运行队列的活动prio_array数据的位图中查找第一个位设置为1的索引,根据索引找到该优先级队列的第一个task

idx = sched_find_first_bit(array->bitmap);

    queue = array->queue + idx;

    next = list_entry(queue->next, struct task_struct, run_list);

 

7)       如果next是普通进程,并且next->sleep_typeSLEEP_INTERACTIVE SLEEP_INTERRUPTED,则重新计算进程睡眠时间和进程优先级。

 

进程切换工作

8)       更新sched_goidle,预期next结构数据,清除TIF_NEED_RESCHED标志,设置quiescent状态计数为1rcu_data ->passed_quiesc = 1;

switch_tasks:

if (next == rq->idle)

     schedstat_inc(rq, sched_goidle);

prefetch(next);

prefetch_stack(next);

clear_tsk_need_resched(prev);

rcu_qsctr_inc(task_cpu(prev));

 

9)       更新prev进程运行时间戳prev->sleep_avgprev->timestamp;

10)    调度信息切换到next,更新next;时间戳和运行队列信息:

sched_info_switch(prev, next);

if (likely(prev != next)) {

     next->timestamp = next->last_ran = now;

     rq->nr_switches++;

     rq->curr = next;

     ++*switch_count;

     ……

}

11)    进行进程切换,context_switch参见前面的分析,它进行进程空间和内核堆栈切换prepare_lock_switch 功能是在定义了__ARCH_WANT_INTERRUPTS_ON_CTXSW情况下,在切换前开中断spin_unlock_irq(&rq->lock); barrier()是保证代码执行顺序不变。

     prepare_task_switch(rq, next);

     prev = context_switch(rq, prev, next);

     barrier();     

     finish_task_switch(this_rq(), prev);

 

 

进程切换后的工作:

进程切换context_switch语句之后的代码并不是由next进程立即执行的,而是由调度器选择prev进程继续执行的。次时prev变量指向的已经是被prev进程替换的其他进程的指针。

 

12)    finish_task_switch()必须与prepare_task_switch配对使用,并主要锁的顺序。它所做的工作,finish_lock_switch调用local_irq_enable(),获取prev的状态和rq->prev_mm,如果mm非空,则调用mmdrop(mm)减少mm的引用计数,如果为0则释放进程页表和虚拟空间。如果prev_stateTASK_DEAD则释放进程的task结构。

 

struct mm_struct *mm = rq->prev_mm;

long prev_state;

 

rq->prev_mm = NULL;

prev_state = prev->state;

finish_arch_switch(prev);

finish_lock_switch(rq, prev);

if (mm)

     mmdrop(mm);

if (unlikely(prev_state == TASK_DEAD)) {

     kprobe_flush_task(prev);

     put_task_struct(prev);

}

 

13)    最后,if (unlikely(task->lock_depth >= 0))则重新获取大内核锁__reacquire_kernel_lock,否则goto need_resched_nonpreemptible; 允许抢占,如果TIF_NEED_RESCHED被设置,则跳转到need_resched重新进行调度。

prev = current;

if (unlikely(reacquire_kernel_lock(prev) < 0))

     goto need_resched_nonpreemptible;

preempt_enable_no_resched();

if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))

     goto need_resched;

 

 


三、Linux缺页中断处理

1.请求调页中断:

进程线性地址空间里的页面不必常驻内存,例如进程的分配请求被理解满足,空间仅仅保留vm_area_struct的空间,页面可能被交换到后援存储器,或者写一个只读页面(COW)Linux采用请求调页技术来解决硬件的缺页中断异常,并且通过预约式换页策略。

主缺页中断和次缺页中断,费时的需要从磁盘读取数据时就会产生主缺页中断。

每种CPU结构提供一个do_page_fault (struct pt_regs *regs, error_code)处理缺页中断,该函数提供了大量信息,如发生异常地址,是页面没找到还是页面保护错误,是读异常还是写异常,来自用户空间还是内核空间。它负责确定异常类型及异常如何被体系结构无关的代码处理。下图是Linux缺页中断处理流程:

Linux缺页中断处理

一旦异常处理程序确定异常是有效内存区域中的有效缺页中断,将调用体系结构无关的函数handle_mm_fault()。如果请求页表项不存在,就分配请求的页表项,并调用handle_pte_fault()

第一步调用pte_present检查PTE标志位,确定其是否在内存中,然后调用pte_none()检查PTE是否分配。如果PTE还没有分配的话,将调用do_no_page()处理请求页面的分配,否则说明该页已经交换到磁盘,也是调用do_swap_page()处理请求换页。如果换出的页面属于虚拟文件则由do_no_page()处理。

第二步确定是否写页面。如果PTE写保护,就调用do_swap_page(),因为这个页面是写时复制页面。COW页面识别方法:页面所在VMA标志位可写,但相应的PTE确不是可写的。如果不是COW页面,通常将之标志为脏,因为它已经被写过了。

第三步确定页面是否已经读取及是否在内存中,但还会发生异常。这是因为在某些体系结构中没有3级页表,在这种情况下建立PTE并标志为新即可。

 

2.请求页面分配:

第一次访问页面,首先分配页面,一般由do_no_page()填充数据。如果父VMAvm_area_struct>vm_ops提供了nopage()函数,则用它填充数据;否则调用do_anonymous_page()匿名函数来填充数据。如果被文件或设备映射,如果时文件映射,filemap_nopage()将替代nopage()函数,如果由虚拟文件映射而来,则shmem_nopage()。每种设备驱动将提供不同的nopage()函数,该函数返回struct page结构。

 

3.请求换页:

将页面交换至后援存储器后,函数do_swap_page()负责将页面读入内存,将在后面讲述。通过PTE的信息就足够查找到交换的页面。页面交换出去时,一般先放到交换高速缓存中。

缺页中断时如果页面在高速缓存中,则只要简单增加页面计数,然后把它放到进程页表中并计数次缺页中断发生的次数。

如果页面仅存在磁盘中,Linux将调用swapin_readahead()读取它及后续的若干页面。

 

4.页面帧回收

除了slab分配器,系统中所有正在使用的页面都存放在页面高速缓存中,并由page->lru链接在一起。Slab页面不存放到高速缓存中因为基于被slab使用的对象对页面计数很困难。除了查找每个进程页表外没有其他方法能把struct page映射为PTE,查找页表代价很大。如果页面高速缓存中存在大量的进程映射页面,系统将会遍历进程页表,通过swap_out()函数交换出页面直到有足够的页面空闲,而共享页会给swap_out()带来问题。如果一个页面是共享的,同时一个交换项已经被分配,PTE就会填写所需信息以便在交换分区里重新找到该页并将引用计数减1。只有引用计数为0时该页才能被替换出去。

内存和磁盘缓存申请越来越多的页面但确无法判断如何释放进程页面,请求分页机制在进程页面缺失时申请新页面,但它却不能强制释放进程不再使用的页面。The Page Frame Reclaiming Algorithm(PFRA)页面回收算法用于从用户进程和内核cache中回收页面放到伙伴系统的空闲块列表中。PFRA必须在系统空闲内存达到某个最低限度时进行页面回收,回收的对象必须是非空闲页面。

 

可将系统页面划分为四种:

1)       Unreclaimable不可回收的,包括空闲页面、保留页面设置了PG_reserved标志、内核动态分配的页面、进程内核栈的页面、设置了PG_locked标志的临时锁住的页面、设置了VM_LOCKED标志的内存页面。

2)       Swappable可交换的页面,用户进程空间的匿名页面(用户堆栈)tmpfs文件系统的映射页面(入IPC共享内存页面),页面存放到交换空间。

3)       Syncable可同步的页面,入用户态地址空间的映射页面、保护磁盘数据的页面缓存的页面、块设备缓冲页、磁盘缓存的页面(入inode cache),如果有必要的话,需同步磁盘映像上的数据。

4)       Discardable可丢弃的页面,入内存缓存中的无用页面(slab分配器中的页面)、dentry cache的页面。

 

PFRA算法是基于经验而非理论的算法,它的设计原则如下:

1)       首先释放无损坏的页面。进程不再引用的磁盘和内存缓存应该先于用户态地址空间的页面释放。

2)       标志所有进程态进程的页面为可回收的。

3)       多进程共享页面的回收,要先清除引用该页面的进程页表项,然后再回收。

4)       回收“不在使用的”页面。PFRALRU链表把进程划分为in-useunused两种,PFRA仅回收unused状态的页面。Linux使用PTE中的Accessed比特位实现非严格的LRU算法。

 

页面回收通常在三种情况下执行:

1)       系统可用内存比较低时进行回收(通常发生在申请内存失败)

2)       内核进入suspend-to-disk状态时进行回收。

3)       周期性回收,内核线程周期性激活并在必要时进行页面回收。

 

Low on memory回收有以下几种情形:

1)       _ _getblk( )调用的grow_buffers( )函数分配新缓存页失败;

2)       create_empty_buffers( )调用的alloc_page_buffers( )函数为页面分配临时的buffer head失败;

3)       _ _alloc_pages( )函数在给定内存区里分配一组连续的页面帧失败。

 

周期性回收涉及的两种内核线程:

1)       Kswapd内核线程在内存区中检测空闲页面是否低于pages_high的门槛值;

2)       预定义工作队列中的事件内核线程,PFRA周期性调度该工作队列中的task回收slab分配器中所有空闲的slab

 

所有用户空间进程和页面缓存的页面被分为活动链表和非活动链表,统称LRU链表。每个区描述符中包括active_listinactive_list两个链表分别将这些页面链接起来。nr_activenr_inactive分别表示这两种页面数量,lru_lock用于同步。页描述符中的PG_lru用于标志一个页面是否属于LRU链表,PG_active用于标志页面是否属于活动链表,lru字段用于把LRU中的链表串起来。活动链表和非活动链表的页面根据最近的访问情况进行动态调整。PG_referenced标志就是此用途。

处理LRU链表的函数有:

add_page_to_active_list()add_page_to_inactive_list()activate_page()lru_cache_add()lru_cache_add_active()等,这些函数比较简单。

shrink_active_list ( )用于将页表从活动链表移到非活动链表。该函数在shrink_zone()函数执行用户地址空间的页面回收时执行。

 

5.交换分区:

系统可以有MAX_SWAPFILES的交换分区,每个分区可放在磁盘分区上或者普通文件里。每个交换区由一系列页槽组成。每个交换区有个swap_header结构描述交换区版本等信息。每个交换区有若干个swap_extent组成,每个swap_extent是连续的物理区域。对于磁盘交换区只有一个swap_extent,对于文件交换区则由多个swap_extent组成,因为文件并不是放在连续的磁盘块上的。mkswap命令可以创建交换分区。

图 交换分区结构

 

图 交换页结构

swp_type() swp_offset()函数根据页槽索引和交换区号得到typeoffset值,函数 swp_entry(type,offset)得到交换槽。最后一位总是清0表示页不在RAM上。槽最大224(64G)。第一个可用槽索引为1。槽索引不能全为0

一个页面可能被多个进程共用,它可能被从一个进程地址空间换出但仍然在物理内存上,因此一个页面可能被多次换出。但物理上仅第一次被换出并存储在交换区上,接下来的换出操作只是增加swap_map的引用计数。swap_duplicate(swp_entry_t entry)的功能正是用户尝试换出一个已经换出的页面。

 

6.交换缓存:

多个进程同时换进一个共享匿名页时或者一个进程换入一个正被PFRA换出的页时存在竞争条件,引入交换缓存解决这种同步问题。通过PG_locked标志可以保证对一个页的并发的交换操作只作用在一个页面上,从而避免竞争条件。

 

7. 页面回收算法描述:

下图是各种情况下进行页面回收时的函数调用关系图。可以看出最终调用函数为cache_reap()shrink_slab()shrink_list()cache_reap()用于周期性回收slab分配器中的无用slabshrink_slab()用于回收磁盘缓存的页面。shrink_list()是页面回收的核心函数,在最新代码中该函数名改为shrink_page_list()。下面会重点讲解。

   图中shrink_caches()最新函数名为shrink_zones()shrink_cache()最新函数名为shrink_inactive_list()。其他函数不变。

PFRA函数结构调用关系

 

低内存回收页面:

如上图所示,当内存分配失败时,内核调用free_more_memory(),该函数首先调用wakeup_bdflush( )唤醒pdflush内核线程触发写操作,从磁盘页面缓冲中写1024dirty页面到磁盘上以释放包含缓冲、缓冲头和VFS的数据结构所占用的页表;然后进行系统调用sched_yield( ),以使pdflush线程能够有机会运行;最后该函数循环遍历系统节点,对每个节点上的低内存区(ZONE_DMA ZONE_NORMAL)调用try_to_free_pages( )函数。

try_to_free_pages(struct zone **zones, gfp_t gfp_mask)函数的目标是通过循环调用shrink_slab()shrink_zones()释放至少32个页帧,每次调用增加优先级参数,初始优先级是12,最高为0。如果循环13次,仍然没有释放掉32个页面,则PFRA进行内存异出保护:调用out_of_memory()函数选择一个进程回收它所有的页面。

shrink_zones(int priority, struct zone **zones, struct scan_control *sc)函数对zones列表中每个区调用shrink_zone()函数。调用shrink_zone()前,先用sc->priority的值更新zone描述符中的prev_priority,如果zone->all_unreclaimable字段不为0且优先级不是12,则不对该区进行页面回收。

shrink_zone(int priority, struct zone *zone, struct scan_control *sc)函数尝试回收32个页面。该函数循环进行shrink_active_list()shrink_inactive_list的操作以达到目标。该函数流程如下:

1)       atomic_inc(&zone->reclaim_in_progress)增加区的回收计数;

2)       增加zone->nr_scan_active,根据优先级,增加范围是zone->nr_active/212 to zone->nr_active/20 。如果zone->nr_scan_active >= 32则赋给nr_active变量,同时zone->nr_scan_active设为0,否则nr_active0

3)       zone->nr_scan_inactivenr_inactive做同样处理;

4)       如果nr_activenr_inactive不同时为空,则进行while循环进行56步操作:

5)       如果nr_active0,则从active链表移动某些页面到inactive链表:

        nr_to_scan = min(nr_active,(unsigned long)sc->swap_cluster_max);

        nr_active -= nr_to_scan;

        shrink_active_list(nr_to_scan, zone, sc, priority);

6)       如果nr_inactive0,则回收inactive链表中的页面:

        nr_to_scan = min(nr_inactive,(unsigned long)sc->swap_cluster_max);

        nr_inactive -= nr_to_scan;

        nr_reclaimed += shrink_inactive_list(nr_to_scan, zone, sc);

7)       atomic_dec(&zone->reclaim_in_progress)减小回收计数,并返回回收页面数nr_reclaimed

 

shrink_inactive_list(unsigned long max_scan, struct zone *zone, struct scan_control *sc)函数从区的inactive链表中抽取一组页面,放到临时链表中,调用shrink_page_list()对链表中的每个页面进行回收。下面是shrink_inactive_list()主要步骤:

1)       调用lru_add_drain()将当前CPUpagevec结构的lru_add_pvecslru_add_active_pvecs中的页面分别移到活动链表和非活动链表中;

2)       获取LRUspin_lock_irq(&zone->lru_lock);

3)       最多扫描max_scan个页面,对每个页面增加使用计数,检查该页面是否正被释放到伙伴系统中,将该页面移动一个临时链表中;

4)       zone->nr_inactive中减去移到临时链表中的页面数;

5)       增加zone->pages_scanned计数;

6)       释放LRU锁:spin_unlock_irq(&zone->lru_lock)

7)       对临时链表调用shrink_page_list(&page_list, sc)回收页面;

8)       增加nr_reclaimed计数;

9)       获取LRUspin_lock(&zone->lru_lock)

10)    shrink_page_list(&page_list, sc)没有回收掉的页面重新添加到active链表和inactive链表中。该函数在回收过程中可能会设置PG_active标志,所以也要考虑往active链表中添加。

11)    如果扫描页面数nr_scanned小于max_scan则循环进行310的操作;

12)    返回回收的页面数;

 

shrink_page_list(struct list_head *page_list, struct scan_control *sc)做真正的页面回收工作,该函数流程如下:

 

shrink_page_list()页面回收逻辑处理流程

 

1)       调用cond_resched()进行条件调度;

2)       循环遍历page_list中每个页面,从列表中移出该页面描述符并回收该页面,如果回收失败,则把该页面插入一个局部链表中;该步流程参见流程图。

l         调用cond_resched() 进行条件调度;

l         LRU链表中取出第一个页面并从LRU链表中删除;

l         如果页面被锁定,这调过该页面,该页加到临时链表中;

l         如果页面不能部分回收并且页面是进程页表的映射,这跳过该页;

l         如果进程是回写的dirty页面,则跳过;

l         如果页面被引用并且页面映射在使用,这跳过并激活该页面,以便后面放入active列表;

l         如果是匿名页面且不在交换区中,这调用add_to_swap()为该页面分配交换区空间并把该页加到交换缓存中;

l         如果页面是进程空间映射并且页面映射地址非空,则调用try_to_unmap()移除该页面的页表映射;

l         如果页面为dirty页面并且无引用、交换可写、且是fs文件系统映射,调用pageout()写出该页面。

3)       循环结束,把局部链表中的页面移回到page_list链表中;

4)       返回回收页面数。

 

每个页面帧处理后只有三种结果:

1)       通过调用free_code_page()页面被释放到伙伴系统中,页面被有效回收;

2)       页面没有回收,被重新插入到page_list链表中,并且认为该页面在将来可能会被再次回收,因而清除PG _active标志,以便在后面加入到inactive链表中;

3)       页面没有回收,被重新插入到page_list链表中,并且认为该页面在可预见的将来不会被再次回收,因而设置PG _active标志,以便在后面加入到active链表中

 

    回收一个匿名页面时,该页面必须添加到交换缓存中,交换区中必须为其预留一个新页槽。如果页面在某些进程的用户态地址空间中,shrink_page_list()调用try_to_unmap定位所有执向该页面帧的进程PTE项,只有返回成功时才继续回收;如果页面是dirty状态,必须要写到磁盘上才能回收,这需要调用pageout()函数,回收只有在pageout()很快完成写操作或者不必进行写操作时才继续进行;如果页面保护VFS buffers,则调用try_to_release_page()释放buffer heads

    最后如果上面都进展顺利的话, shrink_page_list()函数检查页的引用计数:如果值正好为2,则一个为页面缓存或交换缓存,另一个是PFRA自身(shrink_inactive_page()函数中增加该值)。这种情况下,该页面可以回收,并且它不为dirty。根据页面PG _swapcache标志,页面从页面缓存或交换缓存中移除;然后调用free_code_page()

          

换出页面

       add_to_swap(struct page * page, gfp_t gfp_mask)换出操作首先是为页面分配交换页槽,并分配交换缓存;步骤如下:

1)       get_swap_page()为换出页面预留交换槽位;

2)       调用__add_to_swap_cache()传入槽索引、页描述符和gfp标志将页面加到交换缓存中,并标记为dirty

3)       设置页面PG _uptodatePG_dirty标志,以便shrink_inactive_page()能够强制将页面写到磁盘上;

4)       返回;

 

    try_to_unmap(struct page *page, int migration),换出操作第二步,在add_to_swap后面调用,该函数查找所有用户页表中指向该匿名页帧的页表项,并在PTE中设置换出标志。

 

     Page_out()换出操作第三步将dirty页面写到磁盘:

1)       检查页面缓存或交换缓存中的页,并查看该页面是否近被页面缓存或交换缓存占有;如果失败,返回PAGE_KEEP

2)       检查address_space对象的writepage方法是否定义,如没有返还PAGE_ACTIVATE

3)       检查当前进程是否可以发送写请求到当前映射地址空间对象对应的块设备上请求队列上。

4)       SetPageReclaim(page)设置页面回收标志;

5)       调用mapping->a_ops->writepage(page, &wbc)进行写操作,如果失败则清除回收标志;

6)       如果PageWriteback(page)失败,页面没有写回,则清除回收标志ClearPageReclaim(page)

7)       返回成功;

 

    对于交换分区,writepage的实现函数是swap_writepage(),该函数流程如下:

1)       检查是否有其他进程引用该页面,如果没有,从交换缓存中移除该页面返回0

2)       get_swap_bio()分配初始化bio描述符,该函数从交换页标志中找到交换区,然后遍历交换扩展链表找到页槽的起始磁盘分区。bio描述符包含对一个页面的请求,并设置完成方法为end_swap_bio_write()

3)       set_page_writeback(page)设置页面writeback标志,unlock_page()该页面解锁;

4)       submit_bio(rw, bio)向块设备提交bio描述符进行写操作;

5)       返回;

 

一旦写操作完成,end_swap_bio_write()被执行。该函数唤醒等待页面PG_writeback标志清除的进程,清除PG_writeback标志,是否bio描述符。

 

换入页面

    换入页面操作发生在一个进程访问被换出到磁盘上的页面时。当下列条件发生时页面出错处理程序会进行换入操作:

1)       包含引发异常的地址的页面是一个当前进程内存区域的有效页面;

2)       该页面不在内存中,PTE的页面present表示被清0

3)       与页面相关的pte不为nullDirty位被清0,这意味着该pte包含换出页的标志;

 

当上述条件同时满足时,hand_pte_fault()调用do_swap_page()函数换入请求页面。 

do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,

              unsigned long address, pte_t *page_table, pmd_t *pmd,

              int write_access, pte_t orig_pte)

该函数处理流程如下:

1)       entry = pte_to_swp_entry(orig_pte)得到交换槽位信息;

2)       page = lookup_swap_cache(entry)查看交换槽对应的页面是否存在于交换缓存中,如果是则跳到第6步;

3)       调用swapin_readahead(entry, address, vma)从交换区中读取一组页面,对每个页面调用read_swap_cache_async()读取该页面;

4)       对于进程访问异常的页面再次调用read_swap_cache_async()。因为swapin_readahead调用可能失败,在它成功的情况下read_swap_cache_async()发现该页面在交换缓存里,很快返回;

5)       如果页面还是没有在交换缓存中,可能存在其他内核控制路径已经把该页面换入。比较page_table对应的pte内容与orig_pte是否相同,如果不同,说明页面已经换入。函数跳出返回。

6)       如果页面在交换缓存中,调用mark_page_accessed并锁住该页面;

7)       pte_offset_map_lock(mm, pmd, address, &ptl)获取page_table对应的pte内容,与orig_pte比较,判断是否有其他内核控制路径进行换入操作;

8)       测试PG_uptodate标志,如果未设置,则出错返回;

9)       增加mm->anon_rss的计数;

10)  mk_pte(page, vma->vm_page_prot)创建PTE并设置标志,插入到进程页表中;

11)  page_add_anon_rmap()为该匿名页插入反向映射数据结构的内容;

12)  swap_free(entry)释放页槽;

13)  检查交换缓存负载是否达到50%,如果是,并且该页面仅被触发页面访问异常的进程占有,则从交换缓存中释放该页。

14)  如果write_access标志为1,说明是COW写时复制,调用do_wp_page()拷贝一份该页面;

15)  释放页锁和页面缓存等,并返回结果。


附录1:进程空间数据结构

struct mm_struct {

       struct vm_area_struct * mmap;            /* list of VMAs */

       struct rb_root mm_rb;

       struct vm_area_struct * mmap_cache;   /* last find_vma result */

       unsigned long (*get_unmapped_area) (struct file *filp,

                            unsigned long addr, unsigned long len,

                            unsigned long pgoff, unsigned long flags);

       void (*unmap_area) (struct mm_struct *mm, unsigned long addr);

       unsigned long mmap_base;           /* base of mmap area */

       unsigned long task_size;        /* size of task vm space */

       unsigned long cached_hole_size;         /* if non-zero, the largest hole below free_area_cache */

       unsigned long free_area_cache;            /* first hole of size cached_hole_size or larger */

       pgd_t * pgd;

       atomic_t mm_users;                     /* How many users with user space? */

       atomic_t mm_count;                    /* How many references to "struct mm_struct" (users count as 1) */

       int map_count;                            /* number of VMAs */

       struct rw_semaphore mmap_sem;

       spinlock_t page_table_lock;          /* Protects page tables and some counters */

 

       struct list_head mmlist;         /* List of maybe swapped mm's.  These are globally strung

                                           * together off init_mm.mmlist, and are protected

                                           * by mmlist_lock

                                           */

 

       /* Special counters, in some configurations protected by the

        * page_table_lock, in other configurations by being atomic.

        */

       mm_counter_t _file_rss;

       mm_counter_t _anon_rss;

 

       unsigned long hiwater_rss;    /* High-watermark of RSS usage */

       unsigned long hiwater_vm;    /* High-water virtual memory usage */

 

       unsigned long total_vm, locked_vm, shared_vm, exec_vm;

       unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;

       unsigned long start_code, end_code, start_data, end_data;

       unsigned long start_brk, brk, start_stack;

       unsigned long arg_start, arg_end, env_start, env_end;

 

       unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

 

       cpumask_t cpu_vm_mask;

 

       /* Architecture-specific MM context */

       mm_context_t context;

 

       /* Swap token stuff */

       /*

        * Last value of global fault stamp as seen by this process.

        * In other words, this value gives an indication of how long

        * it has been since this task got the token.

        * Look at mm/thrash.c

        */

       unsigned int faultstamp;

       unsigned int token_priority;

       unsigned int last_interval;

 

       unsigned char dumpable:2;

 

       /* coredumping support */

       int core_waiters;

       struct completion *core_startup_done, core_done;

 

       /* aio bits */

       rwlock_t         ioctx_list_lock;

       struct kioctx           *ioctx_list;

};

 

 

/*

 * Linux kernel virtual memory manager primitives.

 * The idea being to have a "virtual" mm in the same way

 * we have a virtual fs - giving a cleaner interface to the

 * mm details, and allowing different kinds of memory mappings

 * (from shared memory to executable loading to arbitrary

 * mmap() functions).

 */

 

/*

 * This struct defines a memory VMM memory area. There is one of these

 * per VM-area/task.  A VM area is any part of the process virtual memory

 * space that has a special rule for the page-fault handlers (ie a shared

 * library, the executable area etc).

 */

struct vm_area_struct {

       struct mm_struct * vm_mm;  /* The address space we belong to. */

       unsigned long vm_start;        /* Our start address within vm_mm. */

       unsigned long vm_end;         /* The first byte after our end address

                                      within vm_mm. */

 

       /* linked list of VM areas per task, sorted by address */

       struct vm_area_struct *vm_next;

 

pgprot_t vm_page_prot;                 /* Access permissions of this VMA. VMA中每个PTE的保护标志位和属性标志位*/

       unsigned long vm_flags;              /* Flags, listed below. */

 

       struct rb_node vm_rb;

 

       /*

        * For areas with an address space and backing store,

        * linkage into the address_space->i_mmap prio tree, or

        * linkage to the list of like vmas hanging off its node, or

        * linkage of vma in the address_space->i_mmap_nonlinear list.

        */

       union {

              struct {

                     struct list_head list;

                     void *parent;  /* aligns with prio_tree_node parent */

                     struct vm_area_struct *head;

              } vm_set;

 

              struct raw_prio_tree_node prio_tree_node;

       } shared;

 

       /*

        * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma

        * list, after a COW of one of the file pages.  A MAP_SHARED vma

        * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack

        * or brk vma (with NULL file) can only be in an anon_vma list.

        */

       struct list_head anon_vma_node;   /* Serialized by anon_vma->lock */

       struct anon_vma *anon_vma; /* Serialized by page_table_lock */

 

       /* Function pointers to deal with this struct. */

       struct vm_operations_struct * vm_ops;

 

       /* Information about our backing store: */

       unsigned long vm_pgoff;             /* Offset (within vm_file) in PAGE_SIZE

                                      units, *not* PAGE_CACHE_SIZE */

       struct file * vm_file;             /* File we map to (can be NULL). */

       void * vm_private_data;        /* was vm_pte (shared mem) */

       unsigned long vm_truncate_count;/* truncate_count or restart_addr */

 

#ifndef CONFIG_MMU

       atomic_t vm_usage;              /* refcount (VMAs shared if !MMU) */

#endif

#ifdef CONFIG_NUMA

       struct mempolicy *vm_policy;      /* NUMA policy for the VMA */

#endif

};

 

/*

 * The anon_vma heads a list of private "related" vmas, to scan if

 * an anonymous page pointing to this anon_vma needs to be unmapped:

 * the vmas on the list will be related by forking, or by splitting.

 *

 * Since vmas come and go as they are split and merged (particularly

 * in mprotect), the mapping field of an anonymous page cannot point

 * directly to a vma: instead it points to an anon_vma, on whose list

 * the related vmas can be easily linked or unlinked.

 *

 * After unlinking the last vma on the list, we must garbage collect

 * the anon_vma object itself: we're guaranteed no page can be

 * pointing to this anon_vma once its vma list is empty.

 */

struct anon_vma {

       spinlock_t lock;      /* Serialize access to vma list */

       struct list_head head;     /* List of private "related" vmas */

};

 

struct address_space {

       struct inode            *host;             /* owner: inode, block_device */

       struct radix_tree_root     page_tree;       /* radix tree of all pages */

       rwlock_t         tree_lock;       /* and rwlock protecting it */

       unsigned int           i_mmap_writable;/* count VM_SHARED mappings */

       struct prio_tree_root      i_mmap;         /* tree of private and shared mappings */

       struct list_head       i_mmap_nonlinear;/*list VM_NONLINEAR mappings */

       spinlock_t              i_mmap_lock; /* protect tree, count, list */

       unsigned int           truncate_count;      /* Cover race condition with truncate */

       unsigned long        nrpages;  /* number of total pages */

       pgoff_t                  writeback_index;/* writeback starts here */

       const struct address_space_operations *a_ops;      /* methods */

       unsigned long        flags;             /* error bits/gfp mask */

       struct backing_dev_info *backing_dev_info; /* 记录预读相关信息 */

       spinlock_t              private_lock;   /* for use by the address_space */

struct list_head       private_list;    /* 可用的针对address_space的私有链表,如果已经使用了辅助函数mark_buffer_dirty_inode()sync_mapping_buffers(),则该函数通过buffer_head>b_assoc_buffers字段链接到缓冲区的首部 */

struct address_space  *assoc_mapping;       /* 映射private_list保护的address_space的后援缓冲区 */

} __attribute__((aligned(sizeof(long))));

 

 

struct swap_info_struct {

       unsigned int flags;

       int prio;                 /* swap priority */

       struct file *swap_file;

       struct block_device *bdev;

       struct list_head extent_list;  /*交换扩展区的头部*/

       struct swap_extent *curr_swap_extent; /*当前最近使用的交换扩展区*/

       unsigned old_block_size;

unsigned short * swap_map;  /*交换区每个页槽使用情况的数组指针,0表示空闲,整数表示换出的次数*/

       unsigned int lowest_bit;

       unsigned int highest_bit;

       unsigned int cluster_next;

       unsigned int cluster_nr;

       unsigned int pages; /*交换区可用页面大小,不包括头和有缺陷的区*/

       unsigned int max;  /*交换区总大小*/

       unsigned int inuse_pages;

       int next;                /* 按优先级的swap链表,表示下一个swap的数组下标 */

};

 

struct swap_extent {

       struct list_head list;

       pgoff_t start_page;

       pgoff_t nr_pages;

       sector_t start_block;

};

 

union swap_header {

       struct {

              char reserved[PAGE_SIZE - 10];

              char magic[10];                    /* SWAP-SPACE or SWAPSPACE2 */

       } magic;

       struct {

              char        bootbits[1024];      /* Space for disklabel etc. */

              __u32            version;

              __u32            last_page;

              __u32            nr_badpages;

              unsigned char  sws_uuid[16];

              unsigned char  sws_volume[16];

              __u32            padding[117];

              __u32            badpages[1];

       } info;

};

 

typedef struct {

       unsigned long val;

} swp_entry_t;

 

struct scan_control {

       gfp_t gfp_mask;

       int may_writepage; /*是否允许将dirty页面写到磁盘上*/

       /* Can pages be swapped as part of reclaim? */

       int may_swap;

       /* This context's SWAP_CLUSTER_MAX. If freeing memory for

        * suspend, we effectively ignore SWAP_CLUSTER_MAX.

        * In this context, it doesn't matter that we scan the

        * whole list at once. */

       int swap_cluster_max;

       int swappiness;

       int all_unreclaimable;

};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


附录2:调度数据结构

#define pte_present(x)   ((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))

_PAGE_PRESENT0_PAGE_PROTNONE1时该页面常驻内存但不可被用户进程访问。_PAGE_PRESENT0时访问该页面会发生页面错误。

 

struct thread_info {

       struct task_struct    *task;             /* main task structure */

       struct exec_domain *exec_domain;       /* execution domain */

       unsigned long        flags;             /* low level flags */

       unsigned long        status;            /* thread-synchronous flags */

       __u32                   cpu;        /* current CPU */

       int                 preempt_count;      /* 0 => preemptable, <0 => BUG */

 

 

       mm_segment_t              addr_limit;     /* thread address space:

                                            0-0xBFFFFFFF for user-thead

                                             0-0xFFFFFFFF for kernel-thread

                                          */

       void               *sysenter_return;

       struct restart_block    restart_block;

 

       unsigned long           previous_esp;   /* ESP of the previous stack in case

                                             of nested (IRQ) stacks

                                          */

       __u8                     supervisor_stack[0];

};

 

struct sched_param {

       int sched_priority;

};

 

struct prio_array {

       unsigned int nr_active;

       DECLARE_BITMAP(bitmap, MAX_PRIO+1); /* include 1 bit for delimiter */

       struct list_head queue[MAX_PRIO];

};

 

/*

 * This is the main, per-CPU runqueue data structure.

 *

 * Locking rule: those places that want to lock multiple runqueues

 * (such as the load balancing or the thread migration code), lock

 * acquire operations must be ordered by ascending &runqueue.

 */

struct rq {

       spinlock_t lock;

 

       /*

        * nr_running and cpu_load should be in the same cacheline because

        * remote CPUs use both these fields when doing load calculation.

        */

       unsigned long nr_running;

       unsigned long raw_weighted_load;

#ifdef CONFIG_SMP

       unsigned long cpu_load[3];

#endif

       unsigned long long nr_switches;

 

       /*

        * This is part of a global counter where only the total sum

        * over all CPUs matters. A task can increase this counter on

        * one CPU and if it got migrated afterwards it may decrease

        * it on another CPU. Always updated under the runqueue lock:

        */

       unsigned long nr_uninterruptible;

 

       unsigned long expired_timestamp;

       /* Cached timestamp set by update_cpu_clock() */

       unsigned long long most_recent_timestamp;

       struct task_struct *curr, *idle;

       unsigned long next_balance;

       struct mm_struct *prev_mm;

       struct prio_array *active, *expired, arrays[2];

       int best_expired_prio;

       atomic_t nr_iowait;

 

#ifdef CONFIG_SMP

       struct sched_domain *sd;

 

       /* For active balancing */

       int active_balance;

       int push_cpu;

       int cpu;          /* cpu of this runqueue */

 

       struct task_struct *migration_thread;

       struct list_head migration_queue;

#endif

 

#ifdef CONFIG_SCHEDSTATS

       /* latency stats */

       struct sched_info rq_sched_info;

 

       /* sys_sched_yield() stats */

       unsigned long yld_exp_empty;

       unsigned long yld_act_empty;

       unsigned long yld_both_empty;

       unsigned long yld_cnt;

 

       /* schedule() stats */

       unsigned long sched_switch;

       unsigned long sched_cnt;

       unsigned long sched_goidle;

 

       /* try_to_wake_up() stats */

       unsigned long ttwu_cnt;

       unsigned long ttwu_local;

#endif

       struct lock_class_key rq_lock_key;

};

 

struct task_struct {

       volatile long state;  /* -1 unrunnable, 0 runnable, >0 stopped */

       struct thread_info *thread_info;

       atomic_t usage;

       unsigned long flags;       /* per process flags, defined below */

       unsigned long ptrace;

 

       int lock_depth;              /* BKL lock depth */

 

#ifdef CONFIG_SMP

#ifdef __ARCH_WANT_UNLOCKED_CTXSW

       int oncpu;

#endif

#endif

       int load_weight;     /* for niceness load balancing purposes */

       int prio, static_prio, normal_prio;

       struct list_head run_list;

       struct prio_array *array;

 

       unsigned short ioprio;

#ifdef CONFIG_BLK_DEV_IO_TRACE

       unsigned int btrace_seq;

#endif

       unsigned long sleep_avg;

       unsigned long long timestamp, last_ran;

       unsigned long long sched_time; /* sched_clock time spent running */

       enum sleep_type sleep_type;

 

       unsigned long policy;

       cpumask_t cpus_allowed;

       unsigned int time_slice, first_time_slice;

 

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)

       struct sched_info sched_info;

#endif

 

       struct list_head tasks;

       /*

        * ptrace_list/ptrace_children forms the list of my children

        * that were stolen by a ptracer.

        */

       struct list_head ptrace_children;

       struct list_head ptrace_list;

 

       struct mm_struct *mm, *active_mm;

 

/* task state */

       struct linux_binfmt *binfmt;

       long exit_state;

       int exit_code, exit_signal;

       int pdeath_signal;  /*  The signal sent when the parent dies  */

       /* ??? */

       unsigned long personality;

       unsigned did_exec:1;

       pid_t pid;

       pid_t tgid;

 

#ifdef CONFIG_CC_STACKPROTECTOR

       /* Canary value for the -fstack-protector gcc feature */

       unsigned long stack_canary;

#endif

       /*

        * pointers to (original) parent process, youngest child, younger sibling,

        * older sibling, respectively.  (p->father can be replaced with

        * p->parent->pid)

        */

       struct task_struct *real_parent; /* real parent process (when being debugged) */

       struct task_struct *parent;      /* parent process */

       /*

        * children/sibling forms the list of my children plus the

        * tasks I'm ptracing.

        */

       struct list_head children;       /* list of my children */

       struct list_head sibling;  /* linkage in my parent's children list */

       struct task_struct *group_leader;   /* threadgroup leader */

 

       /* PID/PID hash table linkage. */

       struct pid_link pids[PIDTYPE_MAX];

       struct list_head thread_group;

 

       struct completion *vfork_done;            /* for vfork() */

       int __user *set_child_tid;             /* CLONE_CHILD_SETTID */

       int __user *clear_child_tid;          /* CLONE_CHILD_CLEARTID */

 

       unsigned long rt_priority;

       cputime_t utime, stime;

       unsigned long nvcsw, nivcsw; /* context switch counts */

       struct timespec start_time;

/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */

       unsigned long min_flt, maj_flt;

 

      cputime_t it_prof_expires, it_virt_expires;

       unsigned long long it_sched_expires;

       struct list_head cpu_timers[3];

 

/* process credentials */

       uid_t uid,euid,suid,fsuid;

       gid_t gid,egid,sgid,fsgid;

       struct group_info *group_info;

       kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;

       unsigned keep_capabilities:1;

       struct user_struct *user;

#ifdef CONFIG_KEYS

       struct key *request_key_auth;       /* assumed request_key authority */

       struct key *thread_keyring;   /* keyring private to this thread */

       unsigned char jit_keyring;     /* default keyring to attach requested keys to */

#endif

       /*

        * fpu_counter contains the number of consecutive context switches

        * that the FPU is used. If this is over a threshold, the lazy fpu

        * saving becomes unlazy to save the trap. This is an unsigned char

        * so that after 256 times the counter wraps and the behavior turns

        * lazy again; this to deal with bursty apps that only use FPU for

        * a short time

        */

       unsigned char fpu_counter;

       int oomkilladj; /* OOM kill score adjustment (bit shift). */

       char comm[TASK_COMM_LEN]; /* executable name excluding path

                                 - access with [gs]et_task_comm (which lock

                                   it with task_lock())

                                 - initialized normally by flush_old_exec */

/* file system info */

       int link_count, total_link_count;

#ifdef CONFIG_SYSVIPC

/* ipc stuff */

       struct sysv_sem sysvsem;

#endif

/* CPU-specific state of this task */

       struct thread_struct thread;

/* filesystem information */

       struct fs_struct *fs;

/* open file information */

       struct files_struct *files;

/* namespaces */

       struct nsproxy *nsproxy;

/* signal handlers */

       struct signal_struct *signal;

       struct sighand_struct *sighand;

 

       sigset_t blocked, real_blocked;

       sigset_t saved_sigmask;         /* To be restored with TIF_RESTORE_SIGMASK */

       struct sigpending pending;

 

       unsigned long sas_ss_sp;

       size_t sas_ss_size;

       int (*notifier)(void *priv);

       void *notifier_data;

       sigset_t *notifier_mask;

      

       void *security;

       struct audit_context *audit_context;

       seccomp_t seccomp;

 

/* Thread group tracking */

     u32 parent_exec_id;

     u32 self_exec_id;

/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */

       spinlock_t alloc_lock;

 

       /* Protection of the PI data structures: */

       spinlock_t pi_lock;

 

#ifdef CONFIG_RT_MUTEXES

       /* PI waiters blocked on a rt_mutex held by this task */

       struct plist_head pi_waiters;

       /* Deadlock detection and priority inheritance handling */

       struct rt_mutex_waiter *pi_blocked_on;

#endif

 

#ifdef CONFIG_DEBUG_MUTEXES

       /* mutex deadlock detection */

       struct mutex_waiter *blocked_on;

#endif

#ifdef CONFIG_TRACE_IRQFLAGS

       unsigned int irq_events;

       int hardirqs_enabled;

       unsigned long hardirq_enable_ip;

       unsigned int hardirq_enable_event;

       unsigned long hardirq_disable_ip;

       unsigned int hardirq_disable_event;

       int softirqs_enabled;

       unsigned long softirq_disable_ip;

       unsigned int softirq_disable_event;

       unsigned long softirq_enable_ip;

       unsigned int softirq_enable_event;

       int hardirq_context;

       int softirq_context;

#endif

#ifdef CONFIG_LOCKDEP

# define MAX_LOCK_DEPTH 30UL

       u64 curr_chain_key;

       int lockdep_depth;

       struct held_lock held_locks[MAX_LOCK_DEPTH];

       unsigned int lockdep_recursion;

#endif

 

/* journalling filesystem info */

       void *journal_info;

 

/* VM state */

       struct reclaim_state *reclaim_state;

 

       struct backing_dev_info *backing_dev_info;

 

       struct io_context *io_context;

 

       unsigned long ptrace_message;

       siginfo_t *last_siginfo; /* For ptrace use.  */

/*

 * current io wait handle: wait queue entry to use for io waits

 * If this thread is processing aio, this points at the waitqueue

 * inside the currently handled kiocb. It may be NULL (i.e. default

 * to a stack based synchronous wait) if its doing sync IO.

 */

       wait_queue_t *io_wait;

#ifdef CONFIG_TASK_XACCT

/* i/o counters(bytes read/written, #syscalls */

       u64 rchar, wchar, syscr, syscw;

#endif

       struct task_io_accounting ioac;

#if defined(CONFIG_TASK_XACCT)

       u64 acct_rss_mem1;      /* accumulated rss usage */

       u64 acct_vm_mem1;      /* accumulated virtual memory usage */

       cputime_t acct_stimexpd;/* stime since last update */

#endif

#ifdef CONFIG_NUMA

      struct mempolicy *mempolicy;

       short il_next;

#endif

#ifdef CONFIG_CPUSETS

       struct cpuset *cpuset;

       nodemask_t mems_allowed;

       int cpuset_mems_generation;

       int cpuset_mem_spread_rotor;

#endif

       struct robust_list_head __user *robust_list;

#ifdef CONFIG_COMPAT

       struct compat_robust_list_head __user *compat_robust_list;

#endif

       struct list_head pi_state_list;

       struct futex_pi_state *pi_state_cache;

 

       atomic_t fs_excl;    /* holding fs exclusive resources */

       struct rcu_head rcu;

 

       /*

        * cache last used pipe for splice

        */

       struct pipe_inode_info *splice_pipe;

#ifdef      CONFIG_TASK_DELAY_ACCT

       struct task_delay_info *delays;

#endif

#ifdef CONFIG_FAULT_INJECTION

       int make_it_fail;

#endif

};

 

 

Logo

更多推荐