linux应用程序使用的C运行库是GNU的glibc,读者可以从GNU的官方网站下载该库的源码文件,也可以从Linux 的发布网站www.kernel.org 下载,本书例子中使用的glibc版本是2.3.6
服务器程序,客户端程序调用的库函数均可在glibc源码中找到。例如,服务器程序调用的socket()函数,读者就可以打开目录glibc-2.3.6 中的socket.c 文件
#include <errno.h>
#include <sys/socket.h>int __socket(domain, type, protocol)
int domain;
int type;
int protocol;
{
__set_errno(ENOSYS);
return -1;
}
weak_alias(__socket, socket)
stub_warning(socket)
#include <stub-tag.h>
#define weak_alias(name, aliasname) __weak_alias(name, aliasname)
#define _weak_alias(name, aliasname)
extern __typedef(name) aliasname __attribute__ ((weak, alias(#name)))
代码清淡1.2并没有socket()函数声明,取而代之的是使用weak_alia为socket 声明了一个函数别名__socket 因此_socket函数就是库函数socket()
Weak Alias是GCC编译器扩展内容,制定了函数的weak属性,因而,在glibc库中会经常使用weak_alias 指定库函数。 编译glibc时将函数别名 属性保存在weak symbol 中,这种用法可以在其它定义真实的_socket函数,编译器会自动识别哪个是真实定义的。这里_socket()真实定义是用汇编语言实现的,读者可以额打开目录glibc-2.3.6/sys=deps/unix/sysv/linux/i386
ENTRY(__socket)
.....
movl $SYS_ify(socketcall), %eax
movl $P(SOCKOP_, socket), %ebx
lea 4(%esp), %ecx //Address of args is 2nd arg
/**Do the system call trap/
ENTER_KERNEL
//对于使用i286系统内核来说,ENTER_KERNEL 和SYS_ify宏声明如下
#define ENTER_KERNEL int $0x80 系统调用中断
#define SYS_ify(syscall_name);
#define P(a, b) P2(a, b)
#define P2(a, b) a##b
将汇编代码宏替换一下
ENTRY (__socket)
movl $__NR_socketcall, %eax //system call number in %eax
//Use ## so socket is a separate token that might be #defined
movl $SOCKOP_socket, %ebx
lea 4(%esp), %ecx
//do the system call trap
int $0x80
当glibc在linux系统中编译时,__NR_socketcall会从内核include/asm-x86/unistd_32.h 文件中找到它的定义作为系统调用号
#define __NR_socketcall 102
SOCKOP_socket 的定义在glibc-2.3.6/sysdeps/unix/sysv/linux/socketcall.h中
#define SOCKOP _socket
汇编代码将这个宏值作为socke调用号保存到寄存器ebx中,代码 lea 4, (%esp), %ecx是将调用socket时的参数地址保存到寄存器ecx中。
使用寄存器ebx, ecx传值并没有使用堆栈是因为系统调用在内核空间,进入内核空间必然会引起堆栈的切换,这是CPU保护模式的要求,可是寄存器用于传值的数量毕竟有限,因此,对于服务器程序传递3个参数;AF_INET, SOCK_STREAM,0 采取传递指针的形式把指针保存到ecx中,我们后面会看到系统调用要用该指针取得每个参数。
当服务器程序运行后,调用socket()函数就会执行glibc中的上述代码,从而将系统调用号102 保存到寄存器eax中,然后执行int 0x80,这个函数也是用汇编实现的。在linux内核目录arch/x86/kernel/entry_32.S 文件中。
ENTRY(system_call)
syscall_call:
call *sys_call_table(,%eax,4);
代码清淡1.4中省去了中断执行的许多细节以及寄存器传递参数的内容,system_call最终使用汇编call指令执行sys_call_table 系统调用表102处的函数指针,系统调用表sys_call_table在arch/x86/kernel/syscall_table_32.S文件中。
ENTRY(sys_call_table)
long sys_socketcall 102
备注:这里是32位的系统调用,linux下,32
位编译后参数用栈传递的,64位是用6个寄存器,超出用栈。
参数 + 4是因为执行的返回地址,也压栈了,这里要跳过去。