(1)进程调度的时机:
(2)挂起正在CPU上执行的进程,与中断时保存现场不同。中断前后是在同一个进程上下文中,只是由用户态转向内核态执行。进程上下文包含了进程执行需要的所有信息:
(3)schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,context_switch中的一个关键宏switch_to来进行关键上下文切换。
(4)0到3G用户可以访问,3G以上只有内核态可以访问。所有进程3G以上都是完全共享的,比如进程X切换到进程Y,但是地址空间仍然是3G以上的部分,只是把进程描述符和其他的进程上下文切换了,只有在返回的时候才不同。哪一个进程都可以“招手”进入内核态,走了一段以后便可以返回到用户态,空车的时候就进入idle进程空转。
(1)schedule
asmlinkage__visible void __sched schedule(void)
{
struct task_struct *tsk = current;
sched_submit_work(tsk);
__schedule();
}
schedule()的尾部调用了__schedule(),__schedule()的关键代码next = pick_next_task(rq, prev);
封装了进程调度算法,使用某种进程调度策略选择下一个进程。得到调度策略后用context_switch(rq, prev, next);
实现进程上下文的切换。其中最关键的switch_to(prev,next, prev);
切换堆栈和寄存器的状态。
(2)switch_to
#define switch_to(prev, next, last) //prev指向当前进程,next指向被调度的进程
do {
unsigned long ebx, ecx, edx, esi, edi;
asm volatile("pushfl\n\t" //把prev进程的flag保存到prev进程的内核堆栈中
"pushl %%ebp\n\t" //把prev进程的基址ebp保存到prev进程的内核堆栈中
"movl %%esp,%[prev_sp]\n\t"//把prev进程的内核栈esp保存到prev->thread.sp中
"movl %[next_sp],%%esp\n\t"//esp指向next进程的内核堆栈栈顶(next->thread.sp)
"movl $1f,%[prev_ip]\n\t"//把"1:\t"地址赋给prev->thread.ip,当prev进程下次被switch_to切回来时,从"1:\t"处执行,即往后执行"popl %%ebp\n\t"和"popfl\n"
"pushl %[next_ip]\n\t"//把next->thread.ip压入next进程的内核堆栈栈顶
__switch_canary
"jmp __switch_to\n"//执行__switch_to()函数,完成硬件上下文切换
"1:\t"
"popl %%ebp\n\t"
"popfl\n"
/* output parameters */
: [prev_sp] "=m"(prev->thread.sp),
[prev_ip] "=m"(prev->thread.ip),
"=a" (last),
/* clobbered output registers: */
"=b" (ebx), "=c"(ecx), "=d" (edx),
"=S" (esi), "=D"(edi)
__switch_canary_oparam
/* input parameters: */
: [next_sp] "m" (next->thread.sp),
[next_ip] "m" (next->thread.ip),
/* regparm parameters for __switch_to():*/
//jmp通过eax寄存器和edx寄存器传递参数
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* reloaded segment registers */
"memory");
} while (0)
[prev_sp] "=m"(prev->thread.sp)
,之前分析汇编的时候,看到的是使用标号(%0、%1、%2等)标记参数,为了更好的可读性,这里用字符串([prev_sp])来标记参数(prev->thread.sp)。
首先保存prev进程的flags,ebp,用"movl %%esp,%[prev_sp]"
和"movl %[next_sp],%%esp"
完成内核堆栈的切换,使esp指向next进程的内核堆栈栈顶,然后把prev进程的thread.ip设置为"1:\t"地址(等到prev进程下次被switch_to切回来执行时,从"1:\t"处执行)。将next->thread.ip保存到next进程的内核堆栈栈顶,接下来执行jmp __switch_to
(注意这里用的是jmp而不是call)完成硬件上下文切换,执行结束返回时弹出next进程内核堆栈的栈顶保存的next->thread.ip,eip指向此位置。分两种情况讨论一下:
movl $1f,%[prev_ip]
,所以next->thread.ip是"1:\t"地址,即__switch_to函数执行结束返回时弹出的是"1:\t",eip指向"1:\t",执行"popl %%ebp"
和"popfl"
恢复next进程的ebp和flag,next进程就可以执行了。所以,如果使用call,会把call __switch_to
的下一条1:\t
压栈,执行结束后eip指向"1:\t",这只对第一种情况适用,无法满足第二种情况的需要去执行ret_from_fork。
当空闲内存低于一个阀值时:空闲内存不足时,需要释放一部分缓存,由于只有不脏的页面才能被释放,所以要把脏页面都回写到磁盘,使其变成干净的页面。
当脏页在内存中驻留时间超过一个阀值时:确保脏页面不会无限期的驻留在内存中,从而减少了数据丢失的风险。
当用户进程调用 sync() 和 fsync() 系统调用时:给用户提供一种强制回写的方法,应对回写要求严格的场景。
2017-2018-1 20179202《Linux内核原理与分析》第九周作业
原文:http://www.cnblogs.com/Jspo/p/7896370.html