需要下载 glibc, linux-2.6.26源码,使用source insight建立相应工程。
http://www.kernel.org/pub/linux/kernel/v2.6/
先看glibc中的库函数socket(),socket.c
#include <errno.h>
#include <sys/socket.h>
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
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>
Weak Alias是GCC编译器扩展内容,指定了函数的weak属性。编译glibc时将“函数别名”属性保存在weak symbol中,真实的定义在其他地方。这里的__socket就是库函数socket的别名,真实的定义是用汇编语言实现,见glibc/sysdeps/unix/sysv/linux/i386/socket.S
这里只列出和我们讨论相关的部分内容
//I386系统内核
#define ENTER_KERNEL int $0x80
#define SYS_ify(syscall_name) __NR_##syscal_name
#define P(a, b) P2(a, b)
#define P2(a, b) a##b
......
ENTRY (__socket)
......
movl $SYS_ify(socketcall), %eax/* System call number in %eax. */
/* Use ## so `socket' is a separate token that might be #define'd. */
movl $P(SOCKOP_,socket), %ebx/* Subcode is first arg to syscall. */
lea 4(%esp), %ecx/* Address of args is 2nd arg. */
......
/* Do the system call trap. */
ENTER_KERNEL
......
展开宏之后
......
ENTRY (__socket)
......
movl $__NR_socketcall, %eax /* System call number in %eax. */
/* Use ## so `socket' is a separate token that might be #define'd. */
movl $SOCKOP_socket, %ebx/* Subcode is first arg to syscall. */
lea 4(%esp), %ecx/* Address of args is 2nd arg. */
......
/* Do the system call trap. */
int $0x80
......
当 glibc 在Linux 系统中编译时,__NR_socketcall会从内核include/asm-x86/unistd_32.h中找到它的定义作为系统调用号#define __NR_socketcall 102
SOCKOP_socket的定义在glibc/sysdeps/unix/sysv/linux/socketcall.h
#define SOCKOP_socket 1
#define SOCKOP_bind 2
#define SOCKOP_connect 3
#define SOCKOP_listen 4
#define SOCKOP_accept 5
#define SOCKOP_getsockname 6
#define SOCKOP_getpeername 7
#define SOCKOP_socketpair 8
#define SOCKOP_send 9
#define SOCKOP_recv 10
#define SOCKOP_sendto 11
#define SOCKOP_recvfrom 12
#define SOCKOP_shutdown 13
#define SOCKOP_setsockopt 14
#define SOCKOP_getsockopt 15
#define SOCKOP_sendmsg 16
#define SOCKOP_recvmsg 17
#define SOCKOP_paccept 18
汇编代码将这个宏值作为socket的调用号保存到寄存器ebx中,代码lea 4 (%esp),%ecx是将调用socket时的参数地址保存到寄存器ecx中。
当服务器程序运行后,调用socket函数就会执行glibc中的上述代码,从而将系统调用号102保存到寄存器eax中,然后执行软件中断指令int $0x80到达系统调用的总入口system_call;汇编实现,
见linux-2.6.26/arch/x86/kernel/entry_32.S
ENTRY(system_call)
......
syscall_call:
call *sys_call_table(,%eax,4)
movl %eax,PT_EAX(%esp)# store the return value
见linux-2.6.26/arch/x86/kernel/syscall_table_32.S
ENTRY(sys_call_table)
.......
.long sys_socketcall/*102*/
见linux-2.6.26/net/socket.c
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0, a1;
int err;
if (call < 1 || call > SYS_RECVMSG)
return -EINVAL;
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
if (err)
return err;
a0 = a[0];
a1 = a[1];
switch (call) {
case SYS_SOCKET:
err = sys_socket(a0, a1, a[2]);
break;
......
sys_socketcall是Linux内核socket的函数调用入口,函数的两个参数分别通过ebx和ecx来传递,参数call是具体的socket的调用号,ebx寄存器保存值为1,也是SYS_SOCKET的值。见linux-2.6.26/include/linux/net.h
copy_from_user将参数从用户空间拷贝到内核空间,是因为系统调用函数在内核空间而服务器程序在用户空间。
nargs[]数组定义,见linux-2.6.26/net/socket.c
#define AL(x) ((x)*sizeof(unsigned long))
static const unsigned char nargs[18] = {
AL(0), AL(3),.....
};
对应处为AL(3),所以大小为12字节。args3个参数AF_INET,SOCK_STREAM,0。
注意,内核include/linux/net.h和glibc中sysdeps/unix/sysv/linux/socketcall.h中对调用号定义完全一致
#define SYS_SOCKET 1/* sys_socket(2) */
#define SYS_BIND 2/* sys_bind(2) */
#define SYS_CONNECT 3/* sys_connect(2) */
#define SYS_LISTEN 4/* sys_listen(2) */
#define SYS_ACCEPT 5/* sys_accept(2) */
#define SYS_GETSOCKNAME 6/* sys_getsockname(2) */
#define SYS_GETPEERNAME 7/* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8/* sys_socketpair(2) */
#define SYS_SEND 9/* sys_send(2) */
#define SYS_RECV 10/* sys_recv(2) */
#define SYS_SENDTO 11/* sys_sendto(2) */
#define SYS_RECVFROM 12/* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13/* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14/* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15/* sys_getsockopt(2) */
#define SYS_SENDMSG 16/* sys_sendmsg(2) */
#define SYS_RECVMSG 17/* sys_recvmsg(2) */
总结
函数调用
sys_socketcall (glibc) ->sys_socket (linux kernel)
asmlinkage long sys_socket(int family, int type, int protocol)//见linux-2.6.26/net/socket.
{
.......
retval = sock_create(family, type, protocol, &sock);
......
retval = sock_map_fd(sock);
.......
}
sock_create创建socket,sock_map_fd为新建的socket在网络文件系统中申请文件号和文件描述符结构。