系统调用整体机制
由用户态触发0x80号中断,参数传递使用寄存器来实现,其中eax存系统调用号,ebx、ecx、edx存系统调用函数的参数,最多三个参数。大于三个参数可以通过结构体指针来实现。
内核态与用户态数据交换
在系统调用过程中,段寄存器ds、es指向内核数据空间,fs指向用户数据空间,因此在内核态中可以利用fs寄存器来执行内核数据空间与用户数据空间之间的数据复制,数据复制过程中的边界检查由CPU自动完成。
linux内核封装了get_fs_byte()和put_fs_byte()函数来进行内核态和用户态之间的数据交换,函数定义在include/asm/segment.h
system_call:
# eax存放的是系统调用号,如果设置的系统调用号大于当前的系统调用数,抛异常
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
# 保存原有的段寄存的值
push %ds
push %es
push %fs
# ebx、ecx、edx分别存储参数,最多可传三个参数,如果参数很多,可以通过指针的形式来传递
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
# ds、es寄存器指向内核空间
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
# fs指向用户空间
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
# 参数都准备好了,调用系统调用表中的函数
call sys_call_table(,%eax,4)
pushl %ax
movl current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl current,%eax # task[0] cannot have signals
cmpl task,%eax
je 3f
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
以write系统调用为例,说明如何通过封装的函数来调用系统调用,以及系统调用的流程是怎样的
lib/write.c中代码 // write函数的实现,使用宏实现的 _syscall3(int,write,int,fd,const char *,buf,off_t,count)
include/unistd.h // 系统调用号 #define __NR_write 4
// 传三个参数的系统调用的宏定义
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
type name(atype a,btype b,ctype c)
{
long __res;
asm volatile ("int $0x80"
: "=a" (__res)
: "0" (_NR##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c)));
if (__res>=0)
return (type) __res;
errno=-__res;
return -1;
}
// write函数的声明 int write(int fildes, const char * buf, off_t count);
上面涉及到的代码的含义
宏展开,相当于
int write(int fd, const char * buf, off_t, count)
{
long __res;
asm volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_write),”b” ((long)(fd)),”c” ((long)(buf)),"d" ((long)(count)));
if (__res>=0)
return (int) __res;
errno=-__res;
return -1;
}
C嵌入汇编语句的基本格式为: asm(“汇编语句” : 输出寄存器 : 输入寄存器 : 会被修改的寄存器)
asm也可以写成__asm__
上面这个write代码的流程就是:
- 定义一个变量__res;
- 嵌入汇编代码,volatile表示不希望汇编语句被gcc优化修改,汇编语句int $0x80含义为触发int 0x80中断;“=a”代表输出寄存器为eax(a代表eax,b、c、d分别代表ebx、ecx、edx),=表示这是输出寄存器;输入寄存器中,“0”代表使用与上面相同位置上的输出寄存器,因此传参中,eax是 4(write的系统调用号),ebx、ecx、edx分别代表fd、buf、count。
- 如果返回值__res(eax)中的值大于0,返回__res,否则把返回值变为负数,存到errno中,然后返回-1。
进入系统调用
触发int 0x80中断后,进入上面system_call.s中代码,找到在include/linux/sys.h中定义的sys_call_table,根据系统调用号4,找到数组index为4的函数sys_write,调用sys_write函数,然后返回返回值。 fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid, sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask, sys_setreuid,sys_setregid };