边干边学 ——Linux内核指导
Chapter 5,系统调用
?为什么需要系统调用
?相关数据和代码
?例:系统调用 getuid()的实现
?添加一个系统调用 mysyscall
?再实现一个稍复杂的系统调用
边干边学 ——Linux内核指导
为什么需要系统调用 (1)
公 共 系 统 调 用 接 口 P O S I X, 1
具 体 的 系 统 实 现
K e r n e l
用 户 程 序 调 用 内 核 提 供 的 功 能
边干边学 ——Linux内核指导
为什么需要系统调用 (2) s y s _ f o o,,,
用 户 空 间 内 核 空 间
模 式 切 换
模 式 切 换
边干边学 ——Linux内核指导
相关数据和代码
? arch/i386/kernel/traps.c
? arch/i386/kernel/entry.S
系统调用时的内核栈
sys_call_table
system_call和 ret_from_sys_call
? include/linux/unistd.h
系统调用编号
宏定义展开系统调用
glibc展开系统调用 INLINE_SYSCALL (getuid,0);
边干边学 ——Linux内核指导
系统调用时的内核栈
用户空间ss
用户空间esp
EFLAGS
用户空间cs
用户空间eip
系统调用号
可用空间
可用空间
eip
函数返回地址
局部变量
用户栈内核栈
中断后(SAVE_ALL前 )
及iret前
(RESTORE_ALL后 )的
esp
内核ss
向外返回
向内中断
中断前及iret
(系统调用返回)后
的esp
用户ss
task_struct
陷入内核时,系统自动从当前进程的 TSS( 任
务状态段)中获得内核栈的 SS和 ESP,并完
成栈切换
边干边学 ——Linux内核指导
系统调用时的内核栈
18 * Stack layout in 'ret_from_system_call':
19 * ptrace needs to have all regs on the stack.
20 * if the order here is changed,it needs to be
21 * updated in fork.c:copy_process,signal.c:do_signal,
22 * ptrace.c and ptrace.h
23 *
24 * 0(%esp) - %ebx
25 * 4(%esp) - %ecx
26 * 8(%esp) - %edx
27 * C(%esp) - %esi
28 * 10(%esp) - %edi
29 * 14(%esp) - %ebp
30 * 18(%esp) - %eax
31 * 1C(%esp) - %ds
32 * 20(%esp) - %es
33 * 24(%esp) - orig_eax
34 * 28(%esp) - %eip
35 * 2C(%esp) - %cs
36 * 30(%esp) - %eflags
37 * 34(%esp) - %oldesp
38 * 38(%esp) - %oldss
39 *
40 * "current" is in register %ebx during any slow entries.
边干边学 ——Linux内核指导
系统调用时的内核栈
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__KERNEL_DS),%edx; \
movl %edx,%ds; \
movl %edx,%es;
边干边学 ——Linux内核指导
系统调用时的内核栈
#define RESTORE_ALL \
popl %ebx;\
popl %ecx;\
popl %edx;\
popl %esi;\
popl %edi;\
popl %ebp;\
popl %eax;\
1,popl %ds; \
2,popl %es; \
addl $4,%esp; \
3,iret; \
边干边学 ——Linux内核指导
sys_call_table
398 ENTRY(sys_call_table)
399,long SYMBOL_NAME(sys_ni_syscall)
400,long SYMBOL_NAME(sys_exit)
401,long SYMBOL_NAME(sys_fork)
402,long SYMBOL_NAME(sys_read)
403,long SYMBOL_NAME(sys_write)
404,long SYMBOL_NAME(sys_open) /* 5 */

635,long SYMBOL_NAME(sys_ni_syscall) /* reserved for lremovexattr */
636,long SYMBOL_NAME(sys_ni_syscall) /* reserved for fremovexattr */
637
638,rept NR_syscalls-(.-sys_call_table)/4
639,long SYMBOL_NAME(sys_ni_syscall)
640,endr
边干边学 ——Linux内核指导
sys_call_table
+
e a x
* 4
E N T R Y ( s y s _ c a l l _ t a b l e )
, l o n g S Y M B O L _ N A M E ( s y s _ n i _,,, )
, l o n g S Y M B O L _ N A M E ( s y s _ e x i t )
, l o n g S Y M B O L _ N A M E ( s y s _ f o r k )
, l o n g S Y M B O L _ N A M E ( s y s _ r e a d )
, l o n g S Y M B O L _ N A M E ( s y s _ w r i t e )
, l o n g S Y M B O L _ N A M E ( s y s _ o p e n )
? ?
? ?
, l o n g S Y M B O L _ N A M E ( s y s _ g e t u i d )
s y s _ c a l l _ t a b l e 首 地 址
E N T R Y ( s y s _ c a l l _ t a b l e )
, l o n g S Y M B O L _ N A M E ( s y s _ n i _,,, )
, l o n g S Y M B O L _ N A M E ( s y s _ e x i t )
, l o n g S Y M B O L _ N A M E ( s y s _ f o r k )
, l o n g S Y M B O L _ N A M E ( s y s _ r e a d )
, l o n g S Y M B O L _ N A M E ( s y s _ w r i t e )
, l o n g S Y M B O L _ N A M E ( s y s _ o p e n )
? ?
? ?
, l o n g S Y M B O L _ N A M E ( s y s _ g e t u i d )
s y s _ c a l l _ t a b l e 首 地 址
e a x
边干边学 ——Linux内核指导
system_call
194 ENTRY(system_call) # int 0x80后执行
195 pushl %eax # save orig_eax
196 SAVE_ALL
197 GET_CURRENT(%ebx)
198 testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
199 jne tracesys
200 cmpl $(NR_syscalls),%eax
201 jae badsys
202 call *SYMBOL_NAME(sys_call_table)(,%eax,4)
203 movl %eax,EAX(%esp) # save the return value
204 ENTRY(ret_from_sys_call)
205 cli
边干边学 ——Linux内核指导
ret_from_sys_call
ENTRY(ret_from_sys_call)
cli # need_resched and signals atomic test
cmpl $0,need_resched(%ebx)
jne reschedule
cmpl $0,sigpending(%ebx)
jne signal_return
restore_all:
RESTORE_ALL
边干边学 ——Linux内核指导
? ?
R E S T O R E _ A L L
( s y s t e m _ c a l l ),
p u s h l % e a x
S A V E _ A L L
? ?
? ?
? ?
i n t 0 x 8 0
? ?
i n t 0 x 8 0
( s y s t e m _ c a l l ),
p u s h l % e a x
S A V E _ A L L
? ?
? ?
压 入 用 户 s s
压 入 用 户 e s p
压 入 E F L A G S
压 入 c s
压 入 e i p
压 入 e a x
p u s h l % e s ;
p u s h l % d s ;
p u s h l % e a x ;
p u s h l % e b p ;
p u s h l % e d i ;
p u s h l % e s i ;
p u s h l % e d x ;
p u s h l % e c x ;
p u s h l % e b x
? ?
R E S T O R E _ A L L
p o p l % e b x ;
p o p l % e c x ;
p o p l % e d x ;
p o p l % e s i ;
p o p l % e d i ;
p o p l % e b p ;
p o p l % e a x ;
p o p l % d s ;
p o p l % e s ;
a d d l $ 4,% e s p
i r e t
由 于 是 陷 入 进 来
内 核 的, 所 以 机
器 自 动 保 存 与 转
换 堆 栈 。
等 价 于 弹 出 e a x
弹 出 e i p
弹 出 c s
弹 出 E F L A G S
弹 出 用 户 e s p
弹 出 用 户 s s
用 户 空 间 内 核 空 间
内 核 堆 栈 中 的 变 化
注 解
回 到 用 户 空 间
边干边学 ——Linux内核指导
arch/i386/kernel/traps.c
916 void __init trap_init(void) //初始化中断描述符表 IDT
{
……
923 set_trap_gate(0,&divide_error);
924 set_trap_gate(1,&debug);
925 set_intr_gate(2,&nmi);
926 set_system_gate(3,&int3);
……
942 set_trap_gate(19,&simd_coprocessor_error);
944 set_system_gate(SYSCALL_VECTOR,&system_call);
边干边学 ——Linux内核指导
系统调用编号
include/asm-i386/unistd.h
8 #define __NR_exit 1
9 #define __NR_fork 2
10 #define _NR_read 3
11 #define NR_write 4
12 #define NR_open 5
13 #define NR_close 6
……
240 #define __NR_llistxattr 233
241 #define __NR_flistxattr 234
242 #define __NR_removexattr 235
243 #define __NR_lremovexattr 236
244 #define __NR_fremovexattr 237
边干边学 ——Linux内核指导
宏定义展开系统调用
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
,"=a" (__res) \
,"0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)),\
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
边干边学 ——Linux内核指导
宏定义展开系统调用
movl $__NR_##name,%eax //先为输入参数分配寄存器
movl arg1,%ebx
movl arg2,%ecx
movl arg3,%edx
#APP
int $0x80 //汇编代码
#NO_APP
movl %eax,__res //最后处理输出参数
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 一个简单的程序,但包含系统调用和库函数调用
#include <linux/unistd.h>
/* all system calls need this header */
int main(){
int i = getuid();
printf(“Hello World! This is my uid,%d\n”,i);
}
? 假定 <unistd.h>定义了“宏”
_syscall0( int,getuid);
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 这个“宏”被 getuid()展开后
int getuid(void)
{
long __res;
__asm__ volatile ("int $0x80"
:,=a” (__res) #输出
:,” (__NR_getuid)); # 输入
__syscall_return(int,__res);
}
? 显然,__NR_getuid( 24) 放入 eax,并 int 0x80
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 因为系统初始化时设定了
set_system_gate(SYSCALL_VECTOR,&system_call);
? 所以进入 system_call
194 ENTRY(system_call)
195 pushl %eax # save orig_eax
196 SAVE_ALL
197 GET_CURRENT(%ebx)
198 testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
199 jne tracesys
200 cmpl $(NR_syscalls),%eax
201 jae badsys
202 call *SYMBOL_NAME(sys_call_table)(,%eax,4)
203 movl %eax,EAX(%esp)
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 注意第 202行,此时 eax为 24
? 查 sys_call_table,得到 call指令的操作数
sys_getuid16
? 调用函数 sys_getuid16()
145 asmlinkage long sys_getuid16(void)
146 {
147 return high2lowuid(current->uid);
148 }
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 第 202行完成后,接着执行第 203行后面的指令
203 movl %eax,EAX(%esp)
204 ENTRY(ret_from_sys_call)
205 cli
206 cmpl $0,need_resched(%ebx)
207 jne reschedule
208 cmpl $0,sigpending(%ebx)
209 jne signal_return
210 restore_all:
211 RESTORE_ALL
边干边学 ——Linux内核指导
例:系统调用 getuid()的实现
? 第 203行:返回值从 eax移到堆栈中 eax的位置
? 假设没有什么意外发生,于是 ret_from_sys_call直
接到 RESTORE_ALL,从堆栈里面弹出保存的寄
存器,堆栈切换( iret)
? 进程回到用户态,返回值保存在 eax中
? printf打印出
Hello World! This is my uid,551
边干边学 ——Linux内核指导
添加一个系统调用 mysyscall
? 功能要求
调用这个 mysyscall,使用户的 uid等于 0
边干边学 ——Linux内核指导
添加一个系统调用 mysyscall
? 系统调用的编号名字 __NR_mysyscall
? 改写 /usr/include/asm/unistd.h
240 #define __NR_llistxattr 233
241 #define __NR_flistxattr 234
242 #define __NR_removexattr 235
243 #define __NR_lremovexattr 236
244 #define __NR_fremovexattr 237
245 #define __NR_mysyscall 238
边干边学 ——Linux内核指导
添加一个系统调用 mysyscall
? 内核中实现该系统调用的程序的名字 sys_mysyscall
? 改写 arch/i386/kernel/entry.S
398 ENTRY(sys_call_table)
399,long SYMBOL_NAME(sys_ni_syscall)
……
636,long SYMBOL_NAME(sys_ni_syscall)
637,long SYMBOL_NAME(sys_mysyscall)
638
639,rept NR_syscalls-(.-sys_call_table)/4
640,long SYMBOL_NAME(sys_ni_syscall)
641,endr
边干边学 ——Linux内核指导
添加一个系统调用 mysyscall
? 把一小段程序添加在 kernel/sys.c
asmlinkage int sys_mysyscall(void)
{
current->uid = current->euid = current->suid = current->fsuid = 0;
return 0;
}
? 记得重新编译内核,启动新内核
边干边学 ——Linux内核指导
添加一个系统调用 mysyscall
? 编写一段测试程序检验实验结果
#include <linux/unistd.h>
_syscall0(int,mysyscall) /* 注意这里没有分号 */
int main()
{
mysyscall();
printf(“em…,this is my uid,%d,\n”,getuid());
}
边干边学 ——Linux内核指导
一个稍复杂的系统调用
? 用到了 module
? 5-6-3.c,sys_pedagogictime(struct timeval *tv)
? 5-6-4.c,测试 pedagogictime