最近又看linux的0.11版本内核,看main.c函数的时候,发现在一个函数调用fork()。竟找不到它的定义,仔细一看,原来都是宏搞的鬼。
在main.c里有这样一句:
static inline _syscall0(int,fork)
查找它的定义,在unistd.h里找到:
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
//略过N行.....
#define _syscall0(type,name) /
type name(void) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
: "=a" (__res) /
: "0" (__NR_##name)); /
if (__res >= 0) /
return (type) __res; /
errno = -__res; /
return -1; /
}
现在分析一下,unistd.h应该就是定义了unix操作系统的标准。所以__NR_setup、__NR_exit、__NR_fork等宏应该就是一些标准。而在宏_syscall0里的__NR_##name也就是将“__NR_”和“name”连结起来。
对于“static inline _syscall0(int,fork)”进行面预处理后就会变成:
int fork(void)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (2));
if (__res >= 0)
return (type) __res;
errno = -__res;
return -1;
}
这个函数的关键代码就是下面三行:
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (2));
从代码可以看到2就是系统调用的参数,__res就是返回值,而且参数和返回值都存放在eax里。int是中断指令,那一定存在着设置中断门的语句,在sched.c里找到:
set_system_gate(0x80,&system_call);
而在文件system_call.s里,找到它的定义:
system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call sys_call_table(,%eax,4)
pushl %eax
movl current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
关键这是一句“call sys_call_table(,%eax,4)”,在文件sys.h里可以找到它的定义:
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 };
很明显sys_call_table[2]就是sys_fork,而它的定义是这样的:
sys_fork:
call find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call copy_process
addl $20,%esp
1: ret
于是又调用到copy_process,而copy_process则是fork.c里的函数。
总结一下linux系统的主要线路:
首先使用set_system_gate(0x80,&system_call)设置中断门。这样这后使用int指令时就会调用到system_call,在它里面又调用到了sys_call_table,而sys_call_table存放着各个系统调用的指针。