1.前情提要
在上一周的实验中,我们以库函数API和汇编形式完成了一个sys_getpid的系统调用。这里我们需要将着两个系统调用添加到MenuOS系统中;其中MenuOS系统在第三课的时候也建立起来了,这里要做的是修改MenuOS的根文件系统。
2. 构建新的MenuOS系统
将两种getpid添加到MenuOS系统的跟文件中共需要四步:
1)更新menu代码到最新版
2)在test.c的main函数中增加MenuConfig
3)在test.c增加对应的getpid函数和getpid-asm函数
4)make rootfs
这里想起介绍一下第二步和第三步:
第二步:在test.c中找到main函数,在main函数中添加如下代码:
MenuConfig("mygetpid","mygetpid",mygetpid);
MenuConfig("mygetpid_asm,"mygetpid_asm",mygetpid_asm);
修改之后的main函数为:
1 intmain()2 {3 PrintMenuOS();4 SetPrompt("MenuOS>>");5 MenuConfig("version","XXX V1.0(Menu program v1.0 inside)",NULL);6 MenuConfig("quit","Quit from XXX",Quit);7 MenuConfig("mygetpid","mygetpid",mygetpid);8 MenuConfig("mygetpid_asm,"mygetpid_asm",mygetpid_asm);
9
10 ExecuteMenu();11 }
第三步:在test.c中添加 mygetpid()和mygetpid_asm()两个函数,函数的具体位置可以不固定,两个函数的具体内容参见上一篇文章。
修改完成之后,make rootfs就可以生成有mygetpid和mygetpid_asm命令的MenuOS系统了。
实现结果为:
3. 系统调用过程的分析
3.1 系统调用的初始化
系统调用的初始化是通过linux-3.18.6/init/main.c文件中的start_kernel函数里的trap_init()完成的。
在…/arch/x86/kernel下的trap.c函数中,可以找到trap_init()函数;
函数的内容是:
1 void __init trap_init(void)2 {3 inti;4
5 #ifdef CONFIG_EISA6 void __iomem *p = early_ioremap(0x0FFFD9, 4);7
8 if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))9 EISA_bus = 1;10 early_iounmap(p, 4);11 #endif
12
13 set_intr_gate(X86_TRAP_DE, divide_error);14 set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);15 /*int4 can be called from all*/
16 set_system_intr_gate(X86_TRAP_OF, &overflow);17 set_intr_gate(X86_TRAP_BR, bounds);18 set_intr_gate(X86_TRAP_UD, invalid_op);19 set_intr_gate(X86_TRAP_NM, device_not_available);20 #ifdef CONFIG_X86_3221 set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);22 #else
23 set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);24 #endif
25 set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);26 set_intr_gate(X86_TRAP_TS, invalid_TSS);27 set_intr_gate(X86_TRAP_NP, segment_not_present);28 set_intr_gate(X86_TRAP_SS, stack_segment);29 set_intr_gate(X86_TRAP_GP, general_protection);30 set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);31 set_intr_gate(X86_TRAP_MF, coprocessor_error);32 set_intr_gate(X86_TRAP_AC, alignment_check);33 #ifdef CONFIG_X86_MCE34 set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);35 #endif
36 set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);37
38 /*Reserve all the builtin and the syscall vector:*/
39 for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)40 set_bit(i, used_vectors);41
42 #ifdef CONFIG_IA32_EMULATION43 set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);44 set_bit(IA32_SYSCALL_VECTOR, used_vectors);45 #endif
46
47 #ifdef CONFIG_X86_3248 set_system_trap_gate(SYSCALL_VECTOR, &system_call);49 set_bit(SYSCALL_VECTOR, used_vectors);50 #endif
51
52 /*
53 * Set the IDT descriptor to a fixed read-only location, so that the54 * "sidt" instruction will not leak the location of the kernel, and55 * to defend the IDT against arbitrary memory write vulnerabilities.56 * It will be reloaded in cpu_init()*/
57 __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);58 idt_descr.address =fix_to_virt(FIX_RO_IDT);59
60 /*
61 * Should be a barrier for any external CPU state:62 */
63 cpu_init();64
65 x86_init.irqs.trap_init();66
67 #ifdef CONFIG_X86_6468 memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);69 set_nmi_gate(X86_TRAP_DB, &debug);70 set_nmi_gate(X86_TRAP_BP, &int3);71 #endif
72 }
其中,最重要的两句是:
#ifdef CONFIG_X86_32
set_system_trap_gate(SYSCALL_VECTOR,&system_call);
set_bit(SYSCALL_VECTOR, used_vectors);#endif
两句将0x80与system_call进行绑定,之后调用int 0x80 就会立即跳转到system_call执行。
3.2 system_call函数分析
system_call位于arch/x86/kernel/entry_32.S 的汇编文件中,其中有一个ENTRY(system_call) ,这就是system_call。system_call是一个特殊的中断。所以也会有SAVE_ALL保存现场,和RESTORE_ALL恢复现场。
其中 call *sys_call_table(,%eax,4), 会调用系统调用号(eax的值)对应的服务程序。
在entry_32.S 汇编文件中,实现系统调用全过程的代码基本如下所示:
1 #system call2 #asm pseudo code3
4 .macro INTERRUPT_RETURN5 iret6 .endm7 .marco SAVE_ALL8 ...9 .endm10 .marco RESTORE_INT_REGS11 ...12 .endm13 ENTRY(system_call)14 SAVE_ALL15 syscall_call:16 call *sys_call_table(,%eax,4)17 mov %eax,PT_EAX(%esp) #store the returnvalue18 syscall_exit:19 testl $_TIFALLWORK_MASK, %ecx #current->work20 jne syscall_exit_work21 restore_all:22 RESTORE_INT_REGS23 irq_return:24 INTERRUPT_RETURN25 ENDPROC(system_call)26 syscall_exit_work:27 testl $_TIF_WORK_SYSCALL_EXIT, %ecx28 jz work_pending29 END(syscall_exit_work)30 work_pending:31 testb $_TIF_NEED_RESCHED, %cl32 jz work_notifysig33 work_resched:34 call schedule35 jz restore all36 work_notifysig: #deal with pending signals37 ...38 END(work_pending)
因此,可以简单推测出系统调用的基本流程是:
以下不问为别人对entry_32.S汇编代码的部分解析,这里拿来主义,参考一下,源博客为http://blog.csdn.net/sunshinefor2008/article/details/66474890;
1.SAVE ALL // 保存调用前寄存器相关的信息
2.call *sys_call_table(,%eax,4) // 执行系统调用对应的处理函数,eax存放系统调用号
// 通过linux-3.18.6/arch/x86/syscalls/syscall_32.tbl找到系统调用号对应处理函数
3.movl %eax,PT_EAX(%esp) // 保存系统调用处理函数返回值到exa
4. testl $_TIF_ALLWORK_MASK, %ecx # current->work
jne syscall_exit_work
// 这两句检查调用退出前是否有其他工作要处理,如有则跳到syscall_exit_work处继
//续处理,以下是syscall_exit_work相关代码:
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx //测试是否退出前还有工作要处理,如有则跳到work_pending
jz work_pending
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
# schedule() instead
movl %esp, %eax
call syscall_trace_leave
jmp resume_userspace
END(syscall_exit_work)
5.下面是work_pending的相关代码,在注释中解释相关内容
work_pending:
testb $_TIF_NEED_RESCHED, %cl // 是否有要继续调度的相关信号
jz work_notifysig #跳转到处理信号相关的代码处
work_resched:
call schedule // 时间调度,进程调度的时机在这里处理
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
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other // 是否有其他工作要处理
# than syscall tracing?
jz restore_all // 如果没有则恢复中断上下文,即恢复进入之前保存的寄存器内容
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched
work_notifysig: # deal with pending signals and // 处理相关信号代码
# notify-resume requests
#ifdef CONFIG_VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
1:
#else
movl %esp, %eax
#endif
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_NONE)
movb PT_CS(%esp), %bl
andb $SEGMENT_RPL_MASK, %bl
cmpb $USER_RPL, %bl
jb resume_kernel
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace
#ifdef CONFIG_VM86
ALIGN
work_notifysig_v86:
pushl_cfi %ecx # save ti_flags for do_notify_resume
call save_v86_state # %eax contains pt_regs pointer
popl_cfi %ecx
movl %eax, %esp
jmp 1b
#endif
END(work_pending)
6. restore_all:
RESTORE_INT_REGS // 中断返回之前恢复相关寄存器的内容
7. irq_return:
INTERRUPT_RETURN // 这两行代码主要是返回到用户态
邵学锋
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
2017/3/25