分类: LINUX
2008-04-19 11:49:27
以前我在看关于Linux原理的内容的时候,总遇到什么内核栈、用户栈。今天单独的看了一下Linux中栈的使用,在这里作一下总结。
一、linux中的堆栈
Linux0.11核中总共涉及到了四种栈:系统引导时候的临时栈;内核初始化使用的栈;内核态栈;用户态栈。
1) 系统引导时候的临时栈
在boosetct中,当它把自身移动到0x90000处时候,就会设置临时栈,以0x90000为段地址,0xff00为偏移地址。
Setup中也沿用了这个栈,作为系统引导的临时栈。
2) 内核初始化使用的栈
在进入保护模式执行head的时候,栈的段地址设置在内核的数据段地址处(此时ss中保存的不再是具体的基地址了,而是堆栈段选择符,当然也是数据段的选择符),栈顶指针指向user_stack数组顶端,保留一页内存作为堆栈。
在进入main进行系统初始化的时候,沿用head中设置的栈,但在move_to_user_mode()之后,代码的执行切换到了任务0,在任务0中用创建了任务1,任务1使用的是自己栈(在任务1的task_struct中的tss中指出)。而任务0仍然使用之前使用的栈作为它的用户栈。
3) 用户态栈
当用户进程在运行的时候,会涉及到过程调用等栈操作,此时它用的是用户栈,用户栈的基地址是任务的数据段基地址,栈顶指针在靠近线性地址空间的顶端处(只是靠近顶端,最顶端存放的是命令行参数和环境变量)。它使用的实际物理页是在“写时复制”时分配的得到的。
4) 核心态栈
当用户进程执行内核代码的时候,例如系统调用,就会使用核心态栈,每个任务都有自己的核心态栈,它的物理位置处在此任务的task_struct所在页中,具体的基地址和栈顶指针由tss中的ss0和esp0指定。
二、用户态栈和内核态栈的切换
执行任务切换的时候,毫无疑问会发生栈的切换,它使用的堆栈会发生变化,它的切换是两个任务的用户栈间的切换,是通过TSS完成的;而当CPU的特权级发生变化的时候,它使用的堆栈也会发生变化,而这时的切换是同一任务的用户栈和核心栈的切换。下面我就说一下用户态栈和核心态栈的切换的大致过程。
在0.11核中所有的中断都属于内核代码,所以如果一个中断产生时,任务正运行用户代码,也就是运行在用户态,那么就会引起任务从用户态(特权级3)向内核态(特权级0)的转变,此时就会发生从用户态栈到核心态栈的切换。CPU先从task_struct中的TSS中获得内核态栈的地址,然后把用户态栈的段地址、栈顶指针压入核心态栈,随后再把eflags、es、ip也压入堆栈。接着执行中断处理程序,此时使用的是内核态栈。执行完中断处理程序返回的时候,iret会返回到用户模式,恢复用户栈和eflags。这样就又使用用户栈。
注释:如果处在内核态,中断的时候,cpu并不进行ss、esp的栈操作。
三、任务0中的栈切换
在内核初时话过程中,会将代码的执行交给任务0(move_to_user_mode),任务0的内核栈在它自己的任务数据结构所在页面的末端,而任务0的用户栈是之前CPU进入保护模式时候用的栈,即usr_stack[],把它设置为任务0的用户栈是利用手工设置的:当执行iret的时候,如果发生了特权级的变化,iret还要把栈中的栈基址和栈顶指针也同时恢复,那么在move_to_user_mode()中,LINUX手工地把任务0的用户栈的基址和栈顶指针压入堆栈,同时把任务0的eflag、cs、ip压入堆栈,注意此时压入的SS,CS虽然也指向模式转换之前的段,但是它门包括了特权级的信息,所以当cs出栈的时候,发现它的特权级(3)与现在的(0)不同,而且低于现在的,所以就会还会再把新栈的SS和esp出来。这样代码的执行就交给了处在用户模式下的任务0,在任务0中再创建任务1.......