socket()接口与内核协议栈的挂接

1、socket()到系统调用

    Linux下,用户空间的调用的socket()接口由glibc实现,man socket如下,三个参数domain,type和protocol:

1
2
3
4
5
6
7
NAME
        socket - create an endpoint for communication
 
SYNOPSIS
        #include <sys/socket.h>
 
        int socket( int domain, int type, int protocol);

    libc实现系统调用同名函数通常使用INT 0x80 + 系统调用号的方式陷入内核,一般来说,read对应sys_read,write对应sys_write.但是,socket系列却不是这样,为了节约系统调用号,将所有的socket系列的接口使用同一个系统调用号陷入内核(叫socketcall),glibc中通过socket.S陷入内核:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.globl __socket
ENTRY (__socket)
 
     /* Save registers.  */
     movl %ebx, %edx
     cfi_register (3, 2)
 
    /* System call number in %eax.  */
     movl $SYS_ify(socketcall), %eax
 
     /* Use ## so `socket' is a separate
     token that might be #define'd.  */
     /* Subcode is first arg to syscall.  */
     movl $P(SOCKOP_,socket), %ebx 
     /* Address of args is 2nd arg.  */
     lea 4(%esp), %ecx     
 
         /* Do the system call trap.  */
     ENTER_KERNEL
 
     /* Restore registers.  */
     movl %edx, %ebx
     cfi_restore (3)
 
     /* %eax is &lt; 0 if there was an error.  */
     cmpl $-125, %eax
     jae SYSCALL_ERROR_LABEL
 
     /* Successful; return the syscall's value.  */
     ret

    eax存系统调用号,应该是198,ebx存参数1,ecx存参数2(这个地方的参数1和参数2不是domian/type/protocol,而是参数1是socket systemcall的类型,参数二是栈顶减4,ecx存domain/type/protocol的整体指针),ENTER_KERNEL即通过INT 0x80陷入内核:

1
# define ENTER_KERNEL int $0x80

2、socketcall到协议簇的挂接

    内核中实现的socketcall代码片段如下,一个简单的逻辑分支将socketcall引向sys_socket():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SYSCALL_DEFINE2(socketcall, int , call,
                    unsigned long __user *, args)
{
...
     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 ;
     case SYS_ACCEPT:
         err = sys_accept4(a0, ( struct sockaddr __user *)a1,
                   ( int __user *)a[2], 0);
         break ;
...
}

    sys_socket()的实现从代码上有些曲折(net/socket.c),通过SYSCALL_DEFINE3实现sys_socket:

1
SYSCALL_DEFINE3(socket, int , family, int , type, int , protocol)

    SYSCALL_DEFINE3即定义一个3个参数的系统调用,具体细节不研究了。

    sys_socket()主要有两个操作,sock_create()和sock_map_fd(),前者用于创建具体的socket结构,后者用于为socket结构分配file sttuct和file fd。我们主要关注sock_create()。sock_create()又调用了__sock_create(),__sock_creat()仅列出主要代码:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __sock_create( struct net *net, int family, int type, int protocol,
              struct socket **res, int kern)
{
     int err;
     struct socket *sock;
     const struct net_proto_family *pf;
...
     err = security_socket_create(family, type, protocol, kern);
     if (err)
         return err;
 
     /*
      *  Allocate the socket and allow the family to set things up. if
      *  the protocol is 0, the family is instructed to select an appropriate
      *  default.
      */
     sock = sock_alloc();
...
     sock->type = type;
...
     rcu_read_lock();
     pf = rcu_dereference(net_families[family]);
...
     rcu_read_unlock();
 
     err = pf->create(net, sock, protocol, kern);
...
     err = security_socket_post_create(sock, family, type, protocol, kern);
...
}

    security_socket_*用于SELinux增强系统安全,此处不讨论了。那么__sock_create主要就调用了pf->create,因此此处也是挂接具体的内核协议簇的位置,如PF_INET,PF_NETLINK等。net_family定义如下(3.10.9内核共有39中协议簇,27,28空):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;
 
struct net_proto_family {
     int     family;
     int     (*create)( struct net *net, struct socket *sock,
                   int protocol, int kern);
     struct module   *owner;
};
 
#define NPROTO      AF_MAX
 
/* Supported address families. */
#define AF_UNSPEC   0
#define AF_UNIX     1   /* Unix domain sockets      */
#define AF_LOCAL    1   /* POSIX name for AF_UNIX   */
#define AF_INET     2   /* Internet IP Protocol     */
#define AF_AX25     3   /* Amateur Radio AX.25      */
#define AF_IPX      4   /* Novell IPX           */
#define AF_APPLETALK    5   /* AppleTalk DDP        */
#define AF_NETROM   6   /* Amateur Radio NET/ROM    */
#define AF_BRIDGE   7   /* Multiprotocol bridge     */
#define AF_ATMPVC   8   /* ATM PVCs         */
#define AF_X25      9   /* Reserved for X.25 project    */
#define AF_INET6    10  /* IP version 6         */
...
#define AF_NETLINK  16
#define AF_ROUTE    AF_NETLINK /* Alias to emulate 4.4BSD */
...
#define AF_MAX      41  /* For now.. */

    协议簇,Protocol Family,在内核中使用net_familes[]树组维护,目前3.10.9内核中共有39个协议簇。协议簇代表socket的第一个参数domain,而不是第二个type或者第三个protocol,协议簇的意思是一簇协议的集合,根据ISO/OSI的7层模型,应该都包含,而非其中某一层上的一种协议。而对于具体的create函数,就是会调用具体的协议簇自己的create,如unix_create,inet_craete,netlink_create等。至此,完成了协议簇和应用层socket的挂接工作:

socket_syscall

 3、socket结构体初始化 

    sock_create()函数的目的是为了socket结构分配空间,并根据family/type/protocol对其进行初始化。先看一下socket结构(include/linux/net.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
  *  struct socket - general BSD socket
  *  @state: socket state (%SS_CONNECTED, etc)
  *  @type: socket type (%SOCK_STREAM, etc)
  *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
  *  @ops: protocol specific socket operations
  *  @file: File back pointer for gc
  *  @sk: internal networking protocol agnostic socket representation
  *  @wq: wait queue for several uses
  */
struct socket {
     socket_state        state;
 
     kmemcheck_bitfield_begin(type);
     short           type;
     kmemcheck_bitfield_end(type);
 
     unsigned long       flags;
 
     struct socket_wq __rcu  *wq;
 
     struct file     *file;
     struct sock     *sk;
     const struct proto_ops  *ops;
};

    其中后三个指针比较重要,分别为文件指针、sock指针、协议ops指针,sock_create()主要也就是初始化这三个指针。*file指针用于指向VFS创建的文件结构体,而*ops则为具体的协议的操作结构体,内含有bind、accept、recvmsg、sendmsg、setsockopt、getsockopt等具体与协议相关的函数指针。而*sk,具体负责传输层的工作,非常复杂。

1)socket->file的初始化 

    首先,前面提到sock_map_fd()主要用于对socket的*file指针初始化,经过sock_map_fd()操作后,socket就通过其*file指针与VFS管理的文件进行了关联,便可以进行文件的各种操作,如read、write、lseek、ioctl等,sock_map_fd()主要有两个调用:get_unused_fd_flags用于获取一个无用的fd号,sock_alloc_file用于将分配并初始化*file,通过将file->fop指针挂接到socket_file_ops的地址完成该操作(通过alloc_file接口完成,但此时其实仍为完全完成对file->fop的赋值,因为socket_file_ops本身也有需要挂接的地方):

sock_map_fd

    当然了,alloc_file同时还做了很多其他的工作,这里就不关心了,那么socket_file_ops结构体指针就存储了socket的文件操作指针(任何内核对象要想以文件的方式进行处理,都需要定义file_operations结构题变量):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static const struct file_operations socket_file_ops = {
     .owner =    THIS_MODULE,
     .llseek =   no_llseek,
     .aio_read = sock_aio_read,
     .aio_write =    sock_aio_write,
     .poll =     sock_poll,
     .unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
     .compat_ioctl = compat_sock_ioctl,
#endif
     .mmap =     sock_mmap,
     .open =     sock_no_open,   /* special open code to disallow open via /proc */
     .release =  sock_close,
     .fasync =   sock_fasync,
     .sendpage = sock_sendpage,
     .splice_write = generic_splice_sendpage,
     .splice_read =  sock_splice_read,
};

    具体的sock_*函数就是将socket当成文件时的具体操作,对于一些没有的操作,例如open,就定义为sock_no_xxx,直接返回错误。

    至此,socket->file->fop已经初始化为socket_file_ops。当然,一个fop指针的初始化并不能算是一个file结构体变量完成了初始化(inode等等都需要做初始化,VFS一系列的),但是这里暂时就不关注了。

    另外,VFS的三个比较重要的结构inode,file,dentry也相互关联,而file结构又与socket结构相互管理,因此通过socket也可以索引到相关的file结构,反之也可以;通过socket索引到的file结构是一个通用的file结构,具备文件的通用功能和属性,这也是为什么在Linux操作系统中socket能够被当作文件使用的原因:

此处应该有一张inode,dentry,file,socket相互关联的图,等到写VFS时再补充吧。

2)socket->ops的初始化 

    除了socket->file->fop初始化为socket_file_ops,socket自身的操作集也需要初始化,即socket作为一个socket理所应当的操作(BSD socket)。由于socket的具体操作,如send,recv,setsockopt,getsockopt等与具体的协议簇、类型、协议有关系,那么这个socket->ops的初始化过程也应该是一个逐层挂接的过程,即种协议簇自己的xxx_create()接口实现的socket->ops的初始化。以PF_INET为例,具体分析一下inet_create如何初始化socket->ops指针。

    首先,有几种与协议簇相关的结构先介绍一下:

a)struct net_proto_family,用于描述协议簇,也即glibc的socket()接口的第一个参数domain,目前内核工支持39个协议簇,PF_INET协议簇为其定义了变量inet_family_ops

1
2
3
4
5
6
struct net_proto_family {
     int     family;
     int     (*create)( struct net *net, struct socket *sock,
                   int protocol, int kern);
     struct module   *owner;
};

b)struct proto_ops,用于描述每种协议簇的不同类型协议集合,即glibc的socket()接口的第二个参数type,PF_INET协议簇工支持三种type:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW,Linux内核中关于sock_type的定义如下,共7种(include/linux/net.h):

1
2
3
4
5
6
7
8
9
enum sock_type {
     SOCK_STREAM = 1,
     SOCK_DGRAM  = 2,
     SOCK_RAW    = 3,
     SOCK_RDM    = 4,
     SOCK_SEQPACKET  = 5,
     SOCK_DCCP   = 6,
     SOCK_PACKET = 10,
};

    proto_ops结构体定义如下,INET定义了它的变量inet_stream_opsinet_dgram_opsinet_sockraw_ops,分别代表SOCK_STREAM、SOCK_DGRAM、SOCK_RAW类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct proto_ops {
     int     family;
     struct module   *owner;
     int     (*release)   ( struct socket *sock);
     int     (*bind)      (...);
     int     (*connect)      (...);
     int     (*socketpair)      (...);
     int     (*accept)      (...);
     int     (*getname)      (...);
     int     (*ioctl)      (...);
    ...
     int     (*listen)      (...);
     int     (*setsockopt)      (...);
     int     (*getsockopt)      (...);
     int     (*sendmsg)      (...);  
     int     (*mmap)      (...);  
     ...
}

    其次,PF_INET为了维护方便定义了自己的一些结构体和变量:

a)struct inet_protosw,用于INET注册自己的子协议使用,也即代表glibc的socket()的第三个参数,protocol。使用type表示该protocol所属的类型,使用protocol表示该协议,使用ops指针用于具体的该protocol的操作集。相关变量为inetsw_array[]inetsw[](inetsw[]为inet_init时将inetsw_array[]按照type作为数组下标重新赋值的变量):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* This is used to register socket interfaces for IP protocols.  */
struct inet_protosw {
     struct list_head list;
 
         /* These two fields form the lookup key.  */
     unsigned short   type;     /* This is the 2nd argument to socket(2). */
     unsigned short   protocol; /* This is the L4 protocol number.  */
 
     struct proto     *prot;
     const struct proto_ops *ops;
 
     char             no_check;   /* checksum on rcv/xmit/none? */
     unsigned char    flags;      /* See INET_PROTOSW_* below.  */
};

    struct proto结构与struct proto_ops有些类似,不过定义的是传输层的操作集,比较多,这里就不列举了。

    写到这个位置,Linux系统对网络这部分的分层是非常明显的,一些理解应该是这样的:

  • linux kernel遵从ISO/OSI的7层模型,但是仅仅实现其中的三层链路层、网络层、传输层(物理层由硬件负责,绘画层、表示层、应用层由用户空间的具体应用负责)。
  • socket的三个参数domain,type,protocol分别对应协议簇、协议类型、具体协议号;而这个协议号,是指传输层的协议号。三者之间属于包含关系,即协议簇下面分为具体不同类型的子协议集合,不同类型的子协议集合中又包含具体的协议。
  • Linux系统有一些结构体分别用于定义不同层次的通信句柄,用户空间使用int fd作为通信句柄,Linux内核中的socket层使用struct socket作为通信的句柄,Linux内核中传输层使用struct sock作为通信的句柄。
  • 不同层上对应了不同的操作集合,用户空间应用使用glibc的接口,如socket().bind(),recv(),send()等;socket层使用struct proto_ops指针作为操作集,传输层使用struct proto指针作为具体的协议相关的操作集合。

    OK,回到inet_create()。inet_create()通过以sock->type作为数组inetsw[]的下标,找到相关inet_protosw指针,并为socket->ops赋值为inet_protosw->ops,具体过程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static int inet_create( struct net *net, struct socket *sock, int protocol,
                int kern)
{
     struct sock *sk;
     struct inet_protosw *answer;
 
     ...
     rcu_read_lock();
     list_for_each_entry_rcu(answer, &amp;inetsw[sock->type], list) {
 
         err = 0;
         /* Check the non-wild match. */
         if (protocol == answer->protocol) {
             if (protocol != IPPROTO_IP)
                 break ;
         } else {
             /* Check for the two wild cases. */
             if (IPPROTO_IP == protocol) {
                 protocol = answer->protocol;
                 break ;
             }
             if (IPPROTO_IP == answer->protocol)
                 break ;
         }
         err = -EPROTONOSUPPORT;
     }
     ...
     sock->ops = answer->ops;
     ...
     rcu_read_unlock();
     ...
}

    当然,inet_create()接口还做了很多其他工作,在这里就不讨论了。

3)socket->sk的初始化 

    socket->sk指针为struct sock指针,而struct sock是传输层的的句柄。在PF_INET协议簇中,仍然使用inet_create()接口为socket->sk指针做初始化的工作,下面选择另一种协议簇PF_UNIX进行socket->sk指针初始化的分析,类似与PF_INET,PF_UNIX也定义了协议簇变量和具体协议类型变量,他们分别是unix_family_opsunix_stream_opsunix_dgram_opsunix_seqpacket_ops(PF_UNIX也支持三种类型的协议,分别是SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET)。

    unix_create()接口调用了unix_create1()为socket->sk赋值,摘录unix_create1()接口代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct sock *unix_create1( struct net *net, struct socket *sock)
{
     struct sock *sk = NULL;
     ...
     sk = sk_alloc(net, PF_UNIX, GFP_KERNEL, unix_proto);
     ...
     sock_init_data(sock, sk);
     ...
}
 
void sock_init_data( struct socket *sock, struct sock *sk)
     some init operations;
     ...
     sk_set_socket(sk, sock);
     ...
     other init operations;
}
 
static inline void sk_set_socket( struct sock *sk, struct socket *sock)
{
     sk_tx_queue_clear(sk);
     sk->sk_socket = sock;
}

    大量的具体的初始化工作都在sock_init_data()接口中,本文中就不讨论了,重点在sock_init_data()为struct sock结构分配了内存,并做了初始化工作,sock_init_data()接口负责与具体协议簇无关的初始化,而其他初始化在family_create接口中其他代码完成,如:

1
2
3
4
5
6
lockdep_set_class(&sk->sk_receive_queue.lock,
             &af_unix_sk_receive_queue_lock_key);
 
sk->sk_write_space  = unix_write_space;
sk->sk_max_ack_backlog  = net->unx.sysctl_max_dgram_qlen;
sk->sk_destruct     = unix_sock_destructor;

unix_create

4、总结 

network 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值