linux5 syscall 流程_Linux内核第五课,分析system_call中断处理过程

本文介绍如何在MenuOS系统中实现系统调用sys_getpid,并通过修改根文件系统加入新功能。具体步骤包括更新代码、添加配置及函数,最后通过makerootfs生成新系统。同时,深入分析了系统调用的初始化过程及system_call函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值