一文分析网络套接字入口函数

//所有的网络套接字系统调用函数(socket bind listen connect )都使用一个共同的入口函数:sys_socketcall/*第一个参数call表示被调用的应用层接口函数,第二个参数是一个指针,指向具体被调用函数(如accept函数)所需要的参数。这些在用户系统调用时传入的参数将原封不动地传递给内核网络栈相关底层函数使用*/asmlinkage int sys_socketcall(int call, unsigned long *args){int er;switch(call){case SYS_SOCKET:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_socket(get_fs_long(args+0),get_fs_long(args+1),get_fs_long(args+2)));case SYS_BIND:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_bind(get_fs_long(args+0),(struct sockaddr *)get_fs_long(args+1),get_fs_long(args+2)));case SYS_CONNECT:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_connect(get_fs_long(args+0),(struct sockaddr *)get_fs_long(args+1),get_fs_long(args+2)));case SYS_LISTEN:er=verify_area(VERIFY_READ, args, 2 * sizeof(long));if(er)return er;return(sock_listen(get_fs_long(args+0),get_fs_long(args+1)));case SYS_ACCEPT:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_accept(get_fs_long(args+0),(struct sockaddr *)get_fs_long(args+1),(int *)get_fs_long(args+2)));case SYS_GETSOCKNAME:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_getsockname(get_fs_long(args+0),(struct sockaddr *)get_fs_long(args+1),(int *)get_fs_long(args+2)));case SYS_GETPEERNAME:er=verify_area(VERIFY_READ, args, 3 * sizeof(long));if(er)return er;return(sock_getpeername(get_fs_long(args+0),(struct sockaddr *)get_fs_long(args+1),(int *)get_fs_long(args+2)));case SYS_SOCKETPAIR:er=verify_area(VERIFY_READ, args, 4 * sizeof(long));if(er)return er;return(sock_socketpair(get_fs_long(args+0),get_fs_long(args+1),get_fs_long(args+2),(unsigned long *)get_fs_long(args+3)));case SYS_SEND:er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long));if(er)return er;return(sock_send(get_fs_long(args+0),(void *)get_fs_long(args+1),get_fs_long(args+2),get_fs_long(args+3)));case SYS_SENDTO:er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long));if(er)return er;return(sock_sendto(get_fs_long(args+0),(void *)get_fs_long(args+1),get_fs_long(args+2),get_fs_long(args+3),(struct sockaddr *)get_fs_long(args+4),get_fs_long(args+5)));case SYS_RECV:er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long));if(er)return er;return(sock_recv(get_fs_long(args+0),(void *)get_fs_long(args+1),get_fs_long(args+2),get_fs_long(args+3)));case SYS_RECVFROM:er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long));if(er)return er;return(sock_recvfrom(get_fs_long(args+0),(void *)get_fs_long(args+1),get_fs_long(args+2),get_fs_long(args+3),(struct sockaddr *)get_fs_long(args+4),(int *)get_fs_long(args+5)));case SYS_SHUTDOWN:er=verify_area(VERIFY_READ, args, 2* sizeof(unsigned long));if(er)return er;return(sock_shutdown(get_fs_long(args+0),get_fs_long(args+1)));case SYS_SETSOCKOPT:er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long));if(er)return er;return(sock_setsockopt(get_fs_long(args+0),get_fs_long(args+1),get_fs_long(args+2),(char *)get_fs_long(args+3),get_fs_long(args+4)));case SYS_GETSOCKOPT:er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long));if(er)return er;return(sock_getsockopt(get_fs_long(args+0),get_fs_long(args+1),get_fs_long(args+2),(char *)get_fs_long(args+3),(int *)get_fs_long(args+4)));default:return(-EINVAL);}}


更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取.


套接字系统调用如何到达sys_socketcall

accept函数为例

系统调用中参数从用户态向内核态的传递是通过寄存器完成的,eax表示系统调用,ebx表示第一个参数,ecx表示第二个参数,edx表示第三个参数(主要针对socke.S)

第一层:accept.s文件

(glibc函数库)sysdeps\unis\sysv\linux\accept.S

#define socket accept    //将socket定义为accept#define _socket _libc_accept#define NARGS 3           //系统调用的个数#include      //这几个套接字函数的通用实现

第二层:socket.S文件

#include #include #define P(a, b) P2(a, b)#define P2(a, b) a##b.text/* The socket-oriented system calls are handled unusally in Linux.  They are all gated through the single `socketcall' system call number.  `socketcall' takes two arguments: the first is the subcode, specifying  which socket function is being called; and the second is a pointer to  the arguments to the specific function.  The .S files for the other calls just #define socket and #include this. */.globl P(__,socket)ENTRY (P(__,socket))/* Save registers. */movl %ebx, %edxmovl $SYS_ify(socketcall), %eax/* System call number in %eax. *///展开$SYS_iff()宏/* 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. */int $0x80/* Restore registers. */movl %edx, %ebx/* %eax is < 0 if there was an error. */cmpl $-125, %eaxjae syscall_error/* Successful; return the syscall's value. */retPSEUDO_END (P(__,socket))cweak_alias (P(__,socket), socket)

重点看:

movl $SYS_ify(socketcall), %eax /* System call number in %eax. */

展开SYS_iff()宏(glibc函数库: \sysdeps\unix\sysdep.h)

#ifdef __STDC__#define SYS_ify(syscall_name) SYS_##syscall_name#else#define SYS_ify(syscall_name) SYS_/**/syscall_name#endif

预处理后为:

movl $SYS_socketcall,%eax//将系统调用号放入eax中,但是对于网络套接字所有的接口函数而言,他们的共同入口函数是sys_socketcall
//在内核中,总入口函数sys_socketcall的系统调用号为_NR_socketcall,定义在include/linux/unistd.h#define __NR_socketcall102
//在glib函数库中,有如下的预定义#define SYS_socketcall _NR_socketcall

所以:

movl $SYS_socketcall,%eax//将sys_socketcall函数对应的系统调用号赋予eax寄存器,从而使套接字系统调用进入到正常的入口函数中

这样套接字系统调用进入到正确的函数中了。

那么第一个参数是识别系统调用的具体函数的,这个参数在socket.S(glibc库)中:

movl $P(SOCKOP_,socket), %ebx//预处理后:movl $SOCKOP_accept,%ebx//这就完成了第一个参数的传递

关于SOCKOP_accept:

查看(glibc库:\sysdeps\unix\sysv\linux\socketcall.h)

#ifndef _SYS_SOCKETCALL_H#define _SYS_SOCKETCALL_H1/* Define unique numbers for the operations permitted on socket.  Linux   uses a single system call for all these functions.  The relevant code   file is /usr/include/linux/net.h.   We cannot use a enum here because the values are used in assembler   code.  */#define SOCKOP_socket1#define SOCKOP_bind2#define SOCKOP_connect3#define SOCKOP_listen4//可以看到SOCKOP_accept对应的值为5#define SOCKOP_accept5#define SOCKOP_getsockname6#define SOCKOP_getpeername7#define SOCKOP_socketpair8#define SOCKOP_send9#define SOCKOP_recv10#define SOCKOP_sendto11#define SOCKOP_recvfrom12#define SOCKOP_shutdown13#define SOCKOP_setsockopt14#define SOCKOP_getsockopt15#define SOCKOP_sendmsg16#define SOCKOP_recvmsg17#endif /* _SYS_SOCKETCALL_H */

在linux内核(include/linux/net.h):

#ifndef _SYS_SOCKETCALL_H#define _SYS_SOCKETCALL_H1/* Define unique numbers for the operations permitted on socket.  Linux   uses a single system call for all these functions.  The relevant code   file is /usr/include/linux/net.h.   We cannot use a enum here because the values are used in assembler   code.  */#define SOCKOP_socket1#define SOCKOP_bind2#define SOCKOP_connect3#define SOCKOP_listen4//这与glibc库中的SOCKOP_accept对应的值相同#define SOCKOP_accept5#define SOCKOP_getsockname6#define SOCKOP_getpeername7#define SOCKOP_socketpair8#define SOCKOP_send9#define SOCKOP_recv10#define SOCKOP_sendto11#define SOCKOP_recvfrom12#define SOCKOP_shutdown13#define SOCKOP_setsockopt14#define SOCKOP_getsockopt15#define SOCKOP_sendmsg16#define SOCKOP_recvmsg17#endif /* _SYS_SOCKETCALL_H */

第二个参数

在袜子。S中:以指针的方式设置了sys_socketcall的第二个参数

lea 4(%esp), %ecx/* Address of args is 2nd arg.  */

设置完以上的系统调用号还有参数后进入软中断:int $0x80,进入了内核态进行处理

第三层:entry.S文件

_system_call:pushl %eax# save orig_eaxSAVE_ALLmovl $-ENOSYS,EAX(%esp)cmpl $(NR_syscalls),%eaxjae ret_from_sys_call    /*对应上面,未调用软中断之前,eax寄存器中被初始化为系统调用号,即_NR_socketcall,       这个调用号别用于在_sys_call_table数组中进行函数指针的寻址,将对应的函数地址对eax寄存器赋值      */movl _sys_call_table(,%eax,4),%eaxtestl %eax,%eaxje ret_from_sys_callmovl _current,%ebxandl $~CF_MASK,EFLAGS(%esp)# clear carry - assume no errorsmovl $0,errno(%ebx)movl %db6,%edxmovl %edx,dbgreg6(%ebx)  # save current hardware debugging statustestb $0x20,flags(%ebx)# PF_TRACESYSjne 1f    /*上面给eax复制后,这里进行调用该函数,到这为止,accept系统调用将请求传递给了sys_socketcall,        然后将socket.S中设置的参数传递给sock_accept函数,就完成了应用层接口函数accept到BSD socket层函数的请求传递工作        */call *%eaxmovl %eax,EAX(%esp)# save the return valuemovl errno(%ebx),%edxnegl %edxje ret_from_sys_callmovl %edx,EAX(%esp)orl $(CF_MASK),EFLAGS(%esp)# set carry to indicate errorjmp ret_from_sys_call

以上源码来自:linux内核源码1.2.12

格列克-2.0.1

转载地址:网络套接字入口函数 - 网络协议栈 - 我爱内核网 - 构建全国最权威的内核技术交流分享论坛

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章