1,初始化中断向量表。
系统调用使用的是int 0x80号中断。当执行int 0x80后会执行中服务子程序。
所执行的中断服务子程序就是系统调用的“主管”程序,system_call
函数所在的位置:arch/x86/kernel/entry_32.S
源代码如下:(2.6.39.2版本)
- 497 # system call handler stub
- 498 ENTRY(system_call)
- 499 RING0_INT_FRAME # can't unwind into user space anyway
- 500 pushl_cfi %eax # save orig_eax
- 501 SAVE_ALL
- 502 GET_THREAD_INFO(%ebp)
- 503 # system call tracing in operation / emulation
- 504 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
- 505 jnz syscall_trace_entry
- 506 cmpl $(nr_syscalls), %eax
- 507 jae syscall_badsys
- 508 syscall_call:
- 509 call *sys_call_table(,%eax,4)
- 510 movl %eax,PT_EAX(%esp) # store the return value
- 511 syscall_exit:
- 512 LOCKDEP_SYS_EXIT
- 513 DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
- 514 # setting need_resched or sigpending
- 515 # between sampling and the iret
- 516 TRACE_IRQS_OFF
- 517 movl TI_flags(%ebp), %ecx
- 518 testl $_TIF_ALLWORK_MASK, %ecx # current->work
- 519 jne syscall_exit_work
------------------------------------------------------------------------
2,下面重点分析system_call函数的实现。
linux内核中定义了一个表,sys_call_table这个表实际上是一个函数指针数组,里面全部是函数指针,
形象点说就是钩子函数,系统调用的实现就是调用内核函数实现这些钩子函数。
-----------------------------
上面图的左边有一列数字,这就是“系统调用号”,定义的位置:arch/x86/include/asm/unistd_32.h
系统调用号实际上可以理解为系统调用的ID,每个系统调用都有个“名字”对其标识,每个“名字”的
背后都绑定着一个系统调用的ID。
-----------------------------------------------------------------------------------------
执行int 0x80时,由硬件完成一些寄存器的压栈。比如说,用户栈的栈地址(ss),栈顶地址(esp),状态寄存器,(eflags),程序计数器(cs:eip)
------------------------------------------------------------
- pushl_cfi %eax # save orig_eax
该条指令是对系统调用号留一个副本,以防需要的时候使用。
-----------------------------------------------------------
这实际上是汇编实现的一个宏。对部分寄存器进行了压栈,主要是为了保留系统调用的系统调用号,
和系统调用所传入的参数。具体对哪些寄存器入了栈可以查看下面的“注释二”中反汇编中的一系列
push操作。
-----------------------------------------------------------
- 506 cmpl $(nr_syscalls), %eax
- 507 jae syscall_badsys
上面是对系统调用号做安全性检查,因为在arch/x86/include/asm/unistd_32.h文件中
定义了一个系统调用的最大值,#define NR_syscalls *** 如果系统调用号非法将不会
执行系统的服务例程,转而执行syscall_badsys作一系列善后工作返回一个出错码后退出。
------------------------------------------------------------
- call *sys_call_table(,%eax,4)
该语句的执行就是调用系统调用的服务例程(我们习惯把实现系统调用的内核函数称为系统调用的服务例程)。服务例程的地址所在的位置 = sys_call_table + eax * 4
eax 中是系统调用号。每个系统调用号占用4个字节。
-----------------------------------------------------------------------------------------
- 510 movl %eax,PT_EAX(%esp) # store the return value
执行完服务例程后返回的结果存放在eax中,这时候将返回的结果压栈到指定的位置。
注释一:
每个进程都会有两个栈,一个内核态栈和一个用户态栈。当执行int中断执行时就会由用户态栈转向内核栈
当执行iret时又会从内核态栈返回到用户态栈,一般来说,用户态栈的大小比较大,内核态栈的大小比较小。
--------------------------------------------------------------------------------------
注释二:使用objdump -d vmlinux反汇编system_call函数的汇编代码
- 1659088 c1508438 :
- 1659089 c1508438: 50 push %eax ------------------>将eax压栈
- 1659090 c1508439: fc cld
- ------------------------------------------------------------------------------------------
- 1659091 c150843a: 0f a8 push %gs
- 1659092 c150843c: 0f a0 push %fs
- 1659093 c150843e: 06 push %es
- 1659094 c150843f: 1e push %ds
- 1659095 c1508440: 50 push %eax ----------------------->SAVE_ALL
- 1659096 c1508441: 55 push %ebp
- 1659097 c1508442: 57 push %edi
- 1659098 c1508443: 56 push %esi
- 1659099 c1508444: 52 push %edx
- 1659100 c1508445: 51 push %ecx
- 1659101 c1508446: 53 push %ebx
- ------------------------------------------------------------------------------------------
- 1659102 c1508447: ba 7b 00 00 00 mov $0x7b,%edx
- 1659103 c150844c: 8e da mov %edx,%ds
- 1659104 c150844e: 8e c2 mov %edx,%es
- 1659105 c1508450: ba d8 00 00 00 mov $0xd8,%edx
- 1659106 c1508455: 8e e2 mov %edx,%fs ----------------------->GET_THREAD_INFO(%ebp)
- 1659107 c1508457: ba e0 00 00 00 mov $0xe0,%edx
- 1659108 c150845c: 8e ea mov %edx,%gs
- 1659109 c150845e: bd 00 e0 ff ff mov $0xffffe000,%ebp
- 1659110 c1508463: 21 e5 and %esp,%ebp
- ------------------------------------------------------------------------------------------
- 1659111 c1508465: f7 45 08 d1 01 00 10 testl $0x100001d1,0x8(%ebp)
- 1659112 c150846c: 0f 85 fe 00 00 00 jne c1508570
- 1659113 c1508472: 3d 59 01 00 00 cmp $0x159,%eax ------------->对系统调用号作有效性判断 (159)十六进制 = (345)十进制
- 1659114 c1508477: 0f 83 42 01 00 00 jae c15085bf ------------>如果你调用的系统号大于345则跳转到syscall_badsys
- 1659115
- 1659116 c150847d :
- 1659117 c150847d: ff 14 85 60 11 51 c1 call *-0x3eaeeea0(,%eax,4) ------>调用内核服务例程
- 1659118 c1508484: 89 44 24 18 mov %eax,0x18(%esp) ----------->将返回值存放在栈上。
- 1659119
- 1659120 c1508488 :
- 1659121 c1508488: 2e ff 15 e4 7b 72 c1 call *%cs:0xc1727be4
- 1659122 c150848f: 8b 4d 08 mov 0x8(%ebp),%ecx
- 1659123 c1508492: f7 c1 ff fe 00 10 test $0x1000feff,%ecx
- 1659124 c1508498: 0f 85 f2 00 00 00 jne c1508590
-----------------------------------------------------------------------------------------
注释三:
- 23 * 0(%esp) - %ebx
- 24 * 4(%esp) - %ecx
- 25 * 8(%esp) - %edx
- 26 * C(%esp) - %esi
- 27 * 10(%esp) - %edi
- 28 * 14(%esp) - %ebp
- 29 * 18(%esp) - %eax
- 30 * 1C(%esp) - %ds
- 31 * 20(%esp) - %es
- 32 * 24(%esp) - %fs
- 33 * 28(%esp) - %gs saved iff !CONFIG_X86_32_LAZY_GS
- 34 * 2C(%esp) - orig_eax
- 35 * 30(%esp) - %eip
- 36 * 34(%esp) - %cs
- 37 * 38(%esp) - %eflags
- 38 * 3C(%esp) - %oldesp
- 39 * 40(%esp) - %oldss
通过注释三可以查看寄存器压栈所在的位置。
阅读(6453) | 评论(0) | 转发(0) |