吕然 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一. 实验过程:
这次实验,首先删掉之前的menu, 重新下载;再将上周的代码加到test.c中,作为单独的函数,并在main函数中写下调用命令。编译运行成功。修改代码的部分如下所示:
int OpenTXT(int argc, char *argv[]){
int fd=-1;
fd = open("LICENSE",O_RDONLY);
if (fd != -1){
printf("Open file, successful.\n");
}
else{
printf("Open file, failed\n");
}
return 0;
}
int OpenTXTAsm(int argc, char *argv[]){
int fd=-1;
int res;
asm volatile(
"mov $0x5,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax,%0\n\t"
: "=m" (fd)
: "b"("LICENSE"), "c"(O_RDONLY)
);
if (fd != -1){
printf("Open file, successful.\n");
} else{
printf("Open file, failed\n");
}
return 0;
}
int main()
{
PrintMenuOS();
SetPrompt("MenuOS>>");
MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
MenuConfig("quit","Quit from MenuOS",Quit);
MenuConfig("time","Show System Time",Time);
MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
MenuConfig("openTXT","Open LICENSE",OpenTXT);
MenuConfig("openTXT-asm","Open LICENSE(asm)",OpenTXTAsm);
ExecuteMenu();
}
然后用kernel调试,设置断点在start_kernel,进入程序后,设置断点在调用的sys_open这里。
然后就可以依次观察运行的每一步了,这里只截图出刚进入断点时候的样子。
二. 实验分析:
在Linux系统中应用程序发起系统调用后,使用int $0x80或sysenter汇编指令将CPU切换到内核态,然后开始从地址system_call处执行命令。一直到Linux系统中系统调用处理过程的最后一条汇编指令iret为止。
老师的伪代码过程如下:
.macro INTERRUPT_RETURN ; 中断返回
iret
.endm
.macro SAVE_ALL ; 保护现场
...
.macro RESTORE_INT_REGS
...
.endm
ENTRY(system_call)
SAVE_ALL
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax, PT_EAX(%esp) ; store the return value
syscall exit:
testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work
restore_all:
RESTORE_INT_REGS
irq_return:
INTERRUPT_RETURN ; 执行结束
ENDPROC(system_call)
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx
jz work_pending
END(syscall_exit_work)
work_pending:
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig
work_resched:
call schedule
jz restore_all
work_notifysig:
... ; deal with pending signals
END(work_pending)
源代码如下:
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
ASM_CLAC
pushl_cfi %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
# system call tracing in operation / emulation
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
jnz syscall_trace_entry
cmpl $(NR_syscalls), %eax
jae syscall_badsys
syscall_call:
call *sys_call_table(,%eax,4)
syscall_after_call:
movl %eax,PT_EAX(%esp) # store the return value
syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work
restore_all:
TRACE_IRQS_IRET
restore_all_notrace:
#ifdef CONFIG_X86_ESPFIX32
movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb PT_OLDSS(%esp), %ah
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS
#endif
restore_nocheck:
RESTORE_REGS 4 # skip orig_eax/error_code
irq_return:
INTERRUPT_RETURN
这里通过图示来简单表示过程。
三.总结
1、用户态进程调用int 0x80(或system_call),中断进程,保护现场,让CPU停止当前工作转为执行系统内核中预设的一些任务,然后进入才能进入内核态;调用结束后又会恢复现场回到内核态。
2、中断后会对调用的任务进行各种检查,并进行调度,完成调用后,再进行检查,才能执行iret返回。