内核网络子系统之 ----socket系统调用 篇

来源:互联网 发布:ug数控车编程视频教程 编辑:程序博客网 时间:2024/06/08 11:05

  一切操作网络的应用都是通过socket,所以我们从最上层的BSD socket开始。以socket函数accpet为例。

  Linux系统使用0x80软中断支持系统调用,同样socket也是使用这个中断从用户态进入到内核态。一般而言,对于每个系统调用都有一个内核如何函数与之对应,比如代开文件open,对应的系统调用入口函数是sys_open,同理,read对应sys_readwrite对应sys_write。但是,socket的系统调用可不是这样的,它有一个统一的内核入口函数sys_socketcall

  贴段代码碎片:

  switch (call) {
    case SYS_SOCKET:
        err = sys_socket(a0, a1, a[2]);
        break;
    case SYS_BIND:
        err = sys_bind(a0, (struct sockaddr__user *)a1, a[2]);
        break;
    case SYS_CONNECT:
        err = sys_connect(a0, (struct sockaddr__user *)a1, a[2]);
        break;
    case SYS_LISTEN:
        err = sys_listen(a0, a1);
        break;

    ... ...

    }

    明白了吧,实际上里面还是有条件判断的,最终还是调用对应的sys_xxx了。但无论如何,内核入口函数就一个,确实是节省了不少系统调用号。那应用层调用了accept,它是如何找到sys_socketcall这个函数的呢?call这个又是怎么被传进去的呢?下面我们一一解决。

    首先看看,当应用层调用accept时,是如何进入到内核入口函数sys_socketcall的。

第一层入口:accepts.S(glibc里面的socket实现) /glib/sysdeps/unix/sysv/linux/accept.S

当应用层调用accept时,它会先跑到这里面来,下面看看它的部分源码:

#define socket accept

#define NARGS         3  //表明accept系统调用的参数个数

#define _socket        _lib_accept

#include   <socket.S>   //socket的通用实现

下面看个其他的,listen.S

#define socket listen

#define NARGS  3  //表明accept系统调用的参数个数

#define NO_WEAK_ALIAS  1  //表明没有别名

#include  <socket.S>   //socket的通用实现

从上面可以看出来,每个调用都有一个.S文件,都被定义成socket,都用同一个socket.S处理

第二层入口:socket.S/glib/sysdeps/unix/sysv/linux/i386/socket.S

/*这个要在上面定义其它函数中使用,如acceptbind等,在上面的具体函数中有些定义了_socket,有些没有,有些定义了NO_WEAK_ALIAS。这些都是为选择不同的函数做设置的*/

#include <sysdep.h>

#include <socketcall.h>

#define P(a, b)          P2(a, b)

#define P2(a, b)        a##b    //##表示连接符,将两个符号连接起来,比如ab

#ifndef _socket    

# ifndef NO_WEAK_ALIAS

#  define _socket P(_,socket)

# else

#  define _socket socket

# endif

#endif

 

.globl _socket

ENTRY (_socket)                                             

        /* Saveregisters.  */

        movl %ebx,%edx

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

 

        /* Use ##so `socket' is a separate token that might be #define'd.  */

       /* 这个号是来区别调用哪个具体函数的,是socket还是bind

        这里的socket的具体值是会发生变化的,就是在上面的#define socket bind

         这样类似的语句中变换这个socket的值,关于其具体的值在后面给出

       */

        movl $P(SOCKOP_, socket), %ebx  

        lea 4(%esp),%ecx    /* Address of args is 2nd arg. 这里以堆栈方式传递其它的参数 */

      /* softinterrupt */

        int 0x80

        /* Restoreregisters. 恢复寄存器 */

        movl %edx,%ebx

 

        /* %eax is< 0 if there was an error. 比较返回值 */

        cmpl $-125,%eax

        jae SYSCALL_ERROR_LABEL

 

        /*Successful; return the syscall's value. 正常返回 */

L(pseudo_end):

        ret

上面两个主要的宏定义如下:

glib/sysdeps/unix/sysdep.h

#define SYS_ify(syscall_name)    SYS_##syscall_name

所以,下面这行

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

翻译过来就是

movl $SYS_socketcall   %eax

在调用int $0x80软中断从用户态到内核态是,eax里面保存的是本次调用的系统调用号。那么究竟这个值是多少呢,下面给出了答案:

/include/linux/unistd.h

#define _NR_socketcall   102

/usr/include/asm/bits/syscall.h

#define SYS_socketcall     _NR_socketcall

至此,我们知道了socket调用是如何找到对应的系统调用号的了。下面我们还要关注上面提出的问题,如何知道是accept还是bind呢,因为我们此时只是找到了一个统一的socket内核入口sys_socketcall,如何将call参数传进去呢?答案在这里

movl $P(SOCKOP_, socket), %ebx 

因为我们在accept.S里面,将accept定义成socket,所以在这里的命令翻译过来就是:

movl $SOCKOP_accept, %ebx 

这里,ebx存储的是sys_socketcall的第一个参数。

然后,从/sysdeps/unix/sysv/linux/socketcall.h里面,我们将得知

#define SOCKOP_listen              4

#define SOCKOP_accept           5

#define SOCKOP_getsockname                 6

我们在sys_socketcall的实现里面,对应的第一个参数call,用来表示的均以SYS_开始,比如SYS_ACCEPT,这个与上面的SOCKOP_accept也不一致啊,这时我们对应一下/include/linux/net.h文件,就会发现

#define SYS_LISTEN          4                          /*sys_listen(2) */

#define SYS_ACCEPT                  5       /* sys_accept (2) */

#define SYS_GETSOCKNAME            6  /* sys_getsockname(2) */

至此,我们也知道sys_socketcall里面的条件call是如何传进去的了。

细心的朋友可能会意识到,上面只提到了一个SYS_socketcall的系统调用号102,根据这个值,是怎么最终callsys_socketcall这个函数的呢?下面继续解决这个问题,

第三层入口: entry.S /linux/arch/i386/kernel/entry.S

cmpl $(nr_syscalls), %eax

jae syscall_badsys

call *sys_call_table(,%eax,4)

nr_syscalls与系统中断号比较,如果相等,则在sys_call_table里面寻找对应的函数,该系统调用号和函数对应表的定义如下

sys_call_table:

  .long _syssocket   /* 102 */

  至此,我们明白了,accept系统调用,先找到对应的accept.S,并在通用的socket.S里面找到accept的系统调用号SYS_socketcall,并将这个系统调用号保存在eax中。

然后根据accept.S里面的socket定义,查出当前是accept调用SOCKOP_accept,并将其作为第一个参数保存在ebx中,接下来将第二个参数保存在ecx中。

最后在entry.S里面,通过系统调用号SYS_socketcall,在sys_call_table里面找到了最终的调用函数sys_socketcall,而sys_socketcall根据socket.S中设置的第一个参数SOCKOP_accpet,也就是call == SYS_ACCEPT,成功的找到了sys_accept函数。

总之,虽然看起来复杂一点,实际上还是比较简单的。

 

原创粉丝点击