本文分析基于Linux 0.11内核,转载请标明出处,http://blog.csdn.net/yming0221/archive/2011/06/01/6459119.aspx
在main.c的程序中,当设备初始化完毕后,程序将从内核模式切换到用户模式,也就是所说的任务0,执行fork()函数(该函数使用内嵌的汇
编,防止函数调用弄乱堆栈,造成写时复制COPY_ON_WRITE)切换到用户堆栈并执行任务0使用iret指令,首先将有关的值压入堆栈,然后执
行iret指令。将刚刚压入对堆栈的数据根据标志位弹入相应的寄存器。
//// 切换到用户模式运行。 // 该函数利用iret 指令实现从内核模式切换到用户模式(初始任务0)。 #define move_to_user_mode() / __asm__ ( "movl %%esp,%%eax/n/t" / // 保存堆栈指针esp 到eax 寄存器中。 "pushl $0x17/n/t" / // 首先将堆栈段选择符(SS)入栈。 "pushl %%eax/n/t" / // 然后将保存的堆栈指针值(esp)入栈。 "pushfl/n/t" / // 将标志寄存器(eflags)内容入栈。 "pushl $0x0f/n/t" / // 将内核代码段选择符(cs)入栈。 "pushl $1f/n/t" / // 将下面标号1 的偏移地址(eip)入栈。 "iret/n" / // 执行中断返回指令,则会跳转到下面标号1 处。 "1:/tmovl $0x17,%%eax/n/t" / // 此时开始执行任务0, "movw %%ax,%%ds/n/t" / // 初始化段寄存器指向本局部表的数据段。 "movw %%ax,%%es/n/t" "movw %%ax,%%fs/n/t" "movw %%ax,%%gs":::"ax")
执行iret前的堆栈如下:
一下这段话引用自http://faydoc.tripod.com/cpu/iret.htm
In Protected Mode, the action of the IRET instruction depends on the settings of the NT (nested task) and VM flags in the EFLAGS register and the VM flag in the EFLAGS image stored on the current stack. Depending on the setting of these flags, the processor performs the following types of interrupt returns:
If the NT flag (EFLAGS register) is cleared, the IRET instruction performs a far return from the interrupt procedure, without a task switch. The code segment being returned to must be equally or less privileged than the interrupt handler routine (as indicated by the RPL field of the code segment selector popped from the stack). As with a real-address mode interrupt return, the IRET instruction pops the return instruction pointer, return code segment selector, and EFLAGS image from the stack to the EIP, CS, and EFLAGS registers, respectively, and then resumes execution of the interrupted program or procedure. If the return is to another privilege level, the IRET instruction also pops the stack pointer and SS from the stack, before resuming program execution. If the return is to virtual-8086 mode, the processor also pops the data segment registers from the stack.
-----------------------------------
由于在sched_init()中已经设置了标志寄存器中的vm标志为0,所以iret掉用后不会发生任务切换,而是继续执行EIP指向的指令故继续执行
1标号的代码,开始执行任务0,任务0的堆栈段选择符为0x17,在sched_init()中已设置了任务0 的任务描述符和局部描述符为INIT_TASK set_tss_desc (gdt + FIRST_TSS_ENTRY, &(init_task.task.tss)); set_ldt_desc (gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt));
/* * INIT_TASK is used to set up the first task table, touch at * your own risk!. Base=0, limit=0x9ffff (=640kB) */ /* * INIT_TASK 用于设置第1 个任务表,若想修改,责任自负?! * 基址Base = 0,段长limit = 0x9ffff(=640kB)。 */ // 对应上面任务结构的第1 个任务的信息。 #define INIT_TASK / /* state etc */ { 0,15,15, / // state, counter, priority /* signals */ 0, { { } ,} , 0, / // signal, sigaction[32], blocked /* ec,brk... */ 0, 0, 0, 0, 0, 0, / // exit_code,start_code,end_code,end_data,brk,start_stack /* pid etc.. */ 0, -1, 0, 0, 0, / // pid, father, pgrp, session, leader /* uid etc */ 0, 0, 0, 0, 0, 0, / // uid, euid, suid, gid, egid, sgid /* alarm */ 0, 0, 0, 0, 0, 0, / // alarm, utime, stime, cutime, cstime, start_time /* math */ 0, / // used_math /* fs info */ -1, 0022, NULL, NULL, NULL, 0, / // tty,umask,pwd,root,executable,close_on_exec /* filp */ { NULL,} , / // filp[20] { / // ldt[3] { 0, 0} , /* ldt */ { 0x9f, 0xc0fa00} , / // 代码长640K,基址0x0,G=1,D=1,DPL=3,P=1 TYPE=0x0a { 0x9f, 0xc0f200} , / // 数据长640K,基址0x0,G=1,D=1,DPL=3,P=1 TYPE=0x02 } , /*tss*/ { 0, PAGE_SIZE + (long) &init_task, 0x10, 0, 0, 0, 0, (long) &pg_dir, / // tss 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, _LDT (0), 0x80000000, { } } ,}