1. 代码运行环境:
glibc:2.3.6
Linux:2.6.26
2. 库函数socket():
glibc-2.3.6/sysdeps/generic中的socket.c文件。
weak_alias()为socket()声明了一个“函数别名”__socket()。
glibc库中经常使用weak_alias()指定库函数。
__socket()函数是socket()的真实实现。
__socket()函数使用汇编语言实现,glibc-2.3.6/sysdeps/unix/sysv/linux/i386中的socket.S文件。
3. 汇编函数__socket():
从内核include/asm-x86/unistd_32.h文件中找到系统调用号:
#define __NR_socketcall 102
__socket()将系统调用号102保存到寄存器eax中:
movl $__NR_socketcall, %eax
从glibc-2.3.6/sysdeps/unix/sysv/linux/socketcall.h文件中找到socket()函数的调用号:
#define SOCKOP_socket 1
__socket()将socket()的调用号保存到寄存器ebx中:
movl $SOCKOP_socket, %ebx
__socket()将调用socket()时的参数地址保存到寄存器ecx中:
lea 4(%esp), %ecx
__socket()执行软中断指令到达系统系统调用的总入口system_call()函数:
int $0x80
4. 汇编函数system_call():
定义于Linux内核目录arch/x86/kernel/entry_32.S文件中。
system_call()最终使用汇编call指令执行sys_call_table系统调用表102处的函数指针。
call *sys_call_table(, %eax, 4)
5. 系统调用表sys_call_table:
定义在arch/x86/kernel/syscall_table_32.S文件中。
.long sys_socketcall /* 102 */
6. sys_socketcall()函数:
sys_socketcall()是bind(), socket(), listen(), accept()等函数的系统调用入口,是内核提供给socket通信的总入口。
sys_socketcall()将参数从服务器程序用户空间复制到内核空间:
参数call是具体的socket调用号,存储在寄存器ebx中。
参数args是参数数组指针,由ecx寄存器传递。
参数a为内核空间数组。
需要复制的参数个数由nargs[]数组决定,nargs[]数组在call下标处存储相对应于call调用号的函数的参数个数。
copy_from_user(a, args, nargs[call])
根据系统调用号确定执行函数:
switch(call)
{
case SYS_SOCKET:
err = sys_socket(a0, a1, a[2]);
}
7. 参数call:
定义在Linux内核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) */
8. 系统调用sys_socket():
socket()库函数对应的系统调用函数为sys_socket()。
创建socket:
sock_create(family, type, protocol, &sock);
与文件系统建立关联,为新建的socket在网络文件系统中申请文件号和文件描述符结构:
sock_map_fd(sock);
9. 调用流程:
socket() -> __socket() -> system_call() -> sys_socketcall() -> sys_socket()