Linux socket系统调用(一)

注:本文分析基于3.10.107内核版本

1、函数原型

int socket(int domain, int type, int protocol);

参数说明:

domain:指定通信协议族。常用的协议族有AF_INET、AF_UNIX等,对于TCP协议,该字段应为AF_INET(ipv4)或AF_INET6(ipv6)。

type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM等。SOCK_STREAM针对于面向连接的TCP服务应用。SOCK_DGRAM对应于无连接的UDP服务应用。

protocol:指定socket所使用的协议,一般我们平常都指定为0,使用type中的默认协议。严格意义上,IPPROTO_TCP(值为6)代表TCP协议。

  • -

2、内核实现源码分析

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    int retval;
    struct socket *sock;
    int flags;

...
    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        goto out;

    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    if (retval < 0)
        goto out_release;

out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

可以看到,除去一些参数合法性校验,socket函数主要由sock_createsock_map_fd这两个函数完成,因此我们主要分析这两个函数,看它们是怎么在内核一步步创建和管理我们使用的socket。

2.1 sock_create函数

sock_create() 实际调用的是 __sock_create()。

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);//SElinux相关,暂不关注
    /*
     *  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();//创建struct socket结构体

    sock->type = type;//设置套接字的类型
...
    pf = rcu_dereference(net_families[family]);//获取对应协议族的协议实例对象
...
    err = pf->create(net, sock, protocol, kern);
    if (err < 0)
        goto out_module_put;

...
    err = security_socket_post_create(sock, family, type, protocol, kern);//SElinux相关,暂不关注

    *res = sock;
...
}

这里比较重要的是sock_alloc()和pf->create()这两个函数。其中sock_alloc()里体现了linux一切皆文件(Everything is a file)理念,即使用文件系统来管理socket,这也是VFS所要达到的效果。

2.1.1 sock_alloc()函数

关于sock_alloc()函数的分析详见Linux socket系统调用(二)—-sock_alloc()

总的来说,sock_alloc()函数分配一个struct socket_alloc结构体,将sockfs相关属性填充在socket_alloc结构体的vfs_inode变量中,以限定后续对这个sock文件允许的操作。同时sock_alloc()最终返回socket_alloc结构体的socket变量,用于后续操作。

2.1.2 pf->create()函数

下面我们继续看下pf->create()这个函数。

pf由net_families[]数组获得,我们先看下net_families[]数组的定义:

#define AF_MAX      41  /* For now.. */
#define NPROTO      AF_MAX

static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

net_families[]数组的初始化在inet_init()函数,

static const struct net_proto_family inet_family_ops = {
    .family = PF_INET,
    .create = inet_create,
    .owner  = THIS_MODULE,
};

static int __init inet_init(void)
{
...
    (void)sock_register(&inet_family_ops);
...
}

int sock_register(const struct net_proto_family *ops)
{
...
    rcu_assign_pointer(net_families[ops->family], ops);
...
}

因此,net_families[]数组里存放的是各个协议族的信息,以family字段作为下标。此处我们针对TCP协议分析,因此我们family字段是AF_INET,另外一般会有

#define PF_INET     AF_INET

所以pf->create调用的就是inet_create()函数。

static int inet_create(struct net *net, struct socket *sock, int protocol,
                       int kern)
{
    struct sock *sk;
    struct inet_protosw *answer;
    struct inet_sock *inet;
    struct proto *answer_prot;
    unsigned char answer_flags;
    char answer_no_check;
    int try_loading_module = 0;
    int err;
...
    sock->state = SS_UNCONNECTED;

    /* Look for the requested type/protocol pair. */
lookup_protocol:
    err = -ESOCKTNOSUPPORT;
    rcu_read_lock();
    //根据socket传入的protocal在inetsw[]数组中查找对应的元素
    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

        err = 0;
        /* 如果我们在socket的protocal传入的是6,即TCP协议,那么走这个分支 */
        if (protocol == answer->protocol) {
            if (protocol != IPPROTO_IP)
                break;
        } else {
            /* 如果socket的protocal传入的是0,那么走这个分支 */
            if (IPPROTO_IP == protocol) {
                protocol = answer->protocol;//重新给protocal赋值,因此socket中protocal传入的是0或者6,都是可以的
                break;
            }
            if (IPPROTO_IP == answer->protocol)
                break;
        }
        err = -EPROTONOSUPPORT;
    }
...
    sock->ops = answer->ops;//将查找到的对应协议族的协议函数操作集赋值给我们之前创建的socket
    answer_prot = answer->prot;
...
    //创建sock结构体,注意这里创建的结构体类型是sock,之前我们通过sock_alloc创建的结构体类型是socket
    sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
    if (sk == NULL)
        goto out;
...
    sock_init_data(sock, sk);//sock初始化

    //另一部分初始化,里面有对各个socket连接定时器的初始化
    if (sk->sk_prot->init) {
        err = sk->sk_prot->init(sk);
        if (err)
            sk_common_release(sk);
    }
...
}
}

inet_create()函数里有几处是比较重要的,一个是通过inetsw[]数组获取对应协议类型的接口操作集信息,另一个是创建struct sock类型的变量,最后是对创建的sock进行初始化。

2.1.2.1 inetsw[]数组

inetsw[]数组存放的是各个sock_type的信息,它也是在inet_init()函数中初始化。

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

#define SOCK_MAX (SOCK_PACKET + 1)
static int __init inet_init(void)
{
    ...
    /* Register the socket-side information for inet_create. */
    for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
        INIT_LIST_HEAD(r);

    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
        inet_register_protosw(q);
    ...
}

其中inetsw_array[]存放的就是具体的每种sock的信息,包括操作函数,协议号等,其中protops两个成员是比较重要的,后续很多操作依赖于这两个成员。

static struct inet_protosw inetsw_array[] = {
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .no_check =   0,
        .flags =      INET_PROTOSW_PERMANENT |
        INET_PROTOSW_ICSK,
    },

    {
        .type =       SOCK_DGRAM,
        .protocol =   IPPROTO_UDP,
        .prot =       &udp_prot,
        .ops =        &inet_dgram_ops,
        .no_check =   UDP_CSUM_DEFAULT,
        .flags =      INET_PROTOSW_PERMANENT,
    },

    {
        .type =       SOCK_DGRAM,
        .protocol =   IPPROTO_ICMP,
        .prot =       &ping_prot,
        .ops =        &inet_dgram_ops,
        .no_check =   UDP_CSUM_DEFAULT,
        .flags =      INET_PROTOSW_REUSE,
    },
...
};

然后通过inet_register_protosw()函数,将上述inetsw_array[]里的元素,按照type字段挂在inetsw[]数组的链表上。因为type字段是有重复的,所以每个inetsw[]元素的链表上可能会有多个成员。

void inet_register_protosw(struct inet_protosw *p)
{
    struct list_head *lh;
    struct inet_protosw *answer;
    int protocol = p->protocol;
    struct list_head *last_perm;
...
    answer = NULL;
    last_perm = &inetsw[p->type];
    list_for_each(lh, &inetsw[p->type]) {
        answer = list_entry(lh, struct inet_protosw, list);

        /* Check only the non-wild match. */
        if (INET_PROTOSW_PERMANENT & answer->flags) {
            if (protocol == answer->protocol)//如果是要覆盖已有的协议族,则失败
                break;
            last_perm = lh;
        }

        answer = NULL;
    }
    if (answer)
        goto out_permanent;

    list_add_rcu(&p->list, last_perm);//按照type的值,添加到inetsw[type]数组中的链表中
...
}
2.1.2.2 创建struct sock类型变量

通过sk_alloc()函数创建struct sock类型变量

struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
                      struct proto *prot) {
    struct sock *sk;

    sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
    if (sk) {
        sk->sk_family = family;
        //这里留意一下,后续会使用到sk_prot这个变量
        sk->sk_prot = sk->sk_prot_creator = prot;
...
    }

    return sk;
}

static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,
        int family)
{
    struct sock *sk;
    struct kmem_cache *slab;

    slab = prot->slab;
    if (slab != NULL) {
        //使用对应协议的slab高速缓存分配,减小内存碎片
        sk = kmem_cache_alloc(slab, priority & ~__GFP_ZERO);
...
}

由此可见,最后是通过prot->slab,也就是TCP的slab高速缓存里分配的。

关于TCP协议的slab高速缓存建立,详见Linux socket系统调用(三)—-TCP协议的slab高速缓存建立及各结构体关系

2.1.2.3 sock初始化

创建完sock结构体,那么自然要做初始化。初始化又分为两个大块,一个在sock_init_data()函数,另一个在sk->sk_prot->init()

a. sock_init_data()

sock_init_data()函数我认为比较重要的一点就是将之前分配的struct socket和struct sock联系在一起,把逻辑填充完整。

void sock_init_data(struct socket *sock, struct sock *sk)
{
    skb_queue_head_init(&sk->sk_receive_queue);
    skb_queue_head_init(&sk->sk_write_queue);
    skb_queue_head_init(&sk->sk_error_queue);
#ifdef CONFIG_NET_DMA
    skb_queue_head_init(&sk->sk_async_wait_queue);
#endif

    sk->sk_send_head    =   NULL;

    init_timer(&sk->sk_timer);

    sk->sk_allocation   =   GFP_KERNEL;
    //socket的接收和发送缓存,怎么用的,暂时不太清楚,后续再分析
    //这两个参数可以在proc下设置,两个分别是/proc/sys/net/core/rmem_default和/proc/sys/net/core/wmem_default
    sk->sk_rcvbuf       =   sysctl_rmem_default;
    sk->sk_sndbuf       =   sysctl_wmem_default;
    sk->sk_state        =   TCP_CLOSE;//socket一开始的状态就是close
    sk_set_socket(sk, sock);

    sock_set_flag(sk, SOCK_ZAPPED);

    if (sock) {
        sk->sk_type =   sock->type;
        sk->sk_wq   =   sock->wq;
        //struct socket里有指针指向对应的struct sock
        //加上上面sk_set_socket()的设置,struct socket和struct sock就能通过各自的成员访问到对方
        sock->sk    =   sk;
    } else
        sk->sk_wq   =   NULL;

    //指定一些回调函数
    sk->sk_state_change =   sock_def_wakeup;
    sk->sk_data_ready   =   sock_def_readable;
    sk->sk_write_space  =   sock_def_write_space;
    sk->sk_error_report =   sock_def_error_report;
...
}
static inline void sk_set_socket(struct sock *sk, struct socket *sock)
{
    sk_tx_queue_clear(sk);
    //让struct sock的sk_socket成员指向对应的struct socket
    sk->sk_socket = sock;
}

b. sk->sk_prot->init()
对于TCP协议,这个init成员指向的是tcp_v4_init_sock()函数,具体参看struct proto tcp_prot结构体的成员。

static int tcp_v4_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);

    tcp_init_sock(sk);//初始化操作
    //设置操作集,这个赋值比较重要,后续icsk->icsk_af_ops的调用要知道最终调用的是什么
    icsk->icsk_af_ops = &ipv4_specific;

#ifdef CONFIG_TCP_MD5SIG
    tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
#endif

    return 0;
}

tcp_v4_init_sock()函数里调用tcp_init_sock()函数完成主要的初始化动作。

void tcp_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    skb_queue_head_init(&tp->out_of_order_queue);
    //初始化定时器,包括超时重传定时器,延迟ack定时器,保活定时器等的初始化
    tcp_init_xmit_timers(sk);
    tcp_prequeue_init(tp);
    INIT_LIST_HEAD(&tp->tsq_node);

    icsk->icsk_rto = TCP_TIMEOUT_INIT;//设置初始的超时时间,为1HZ,即1s
...
    tcp_enable_early_retrans(tp);
    icsk->icsk_ca_ops = &tcp_init_congestion_ops;//设置默认拥塞算法,reno

    tp->tsoffset = 0;

    sk->sk_state = TCP_CLOSE;//这里又再次设置了sock状态,重复了sock_init_data()的动作
...
    //socket的接收和发送缓存,sock_init_data()也有设置,不过是设置默认的大小
    //这里使用的是/proc/sys/net/ipv4/tcp_wmem和/proc/sys/net/ipv4/tcp_rmem的值
    sk->sk_sndbuf = sysctl_tcp_wmem[1];
    sk->sk_rcvbuf = sysctl_tcp_rmem[1];
...
}

至此,我们已经创建好了socket,也完成了相关的初始化,但是我们知道socket系统调用返回的是一个int型的文件描述符,而目前我们好像还没涉及到。

下面就进入第二部分,fd的创建。

2.2 sock_map_fd()

这个函数主要有两个部分,一个是创建file文件结构,fd文件描述符,另一部分是将file文件结构和fd文件描述符关联,同时将上一步返回的socket也一起绑定,形成一个完整的逻辑。

static int sock_map_fd(struct socket *sock, int flags)
{
    struct file *newfile;
    //获取一个未使用的文件描述符,文件描述符的管理这里就暂不分析了
    int fd = get_unused_fd_flags(flags);
    if (unlikely(fd < 0))
        return fd;

    //分配file结构体
    newfile = sock_alloc_file(sock, flags, NULL);
    if (likely(!IS_ERR(newfile))) {
        fd_install(fd, newfile);
        return fd;
    }
...
}

2.2.1 sock_alloc_file()函数

file结构体的创建及初始化,以及将socket和file结构体关联的操作主要在sock_alloc_file()函数中完成,

struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
    struct qstr name = { .name = "" };
    struct path path;
    struct file *file;
...
    //在sock文件系统上分配一个dentry结构
    path.dentry = d_alloc_pseudo(sock_mnt->mnt_sb, &name);
    if (unlikely(!path.dentry))
        return ERR_PTR(-ENOMEM);
    path.mnt = mntget(sock_mnt);

    //使用之前我们分配socket结构体来初始化dentry
    d_instantiate(path.dentry, SOCK_INODE(sock));
    SOCK_INODE(sock)->i_fop = &socket_file_ops;//设置该inode的函数操作集

    file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
          &socket_file_ops);//从cache里分配一个file结构体
...
    //socket结构体里有file指针,指向我们刚分配的file结构体
    sock->file = file;
    file->f_flags = O_RDWR | (flags & O_NONBLOCK);
    //同样file结构体了也有指针指向socket结构体
    //至此file和socket已经做好关联,就差fd和file关联了
    file->private_data = sock;
    return file;
}

d_instantiate()函数里我们可以看到dentry和之前创建socket结构体时分配的struct socket_alloc的联系。

void d_instantiate(struct dentry *entry, struct inode * inode)
{
...
    __d_instantiate(entry, inode);
...
}

static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
...
    //还记得之前我们分配socket结构体时,其实分配的是struct socket_alloc,
    //该结构体里包含了struct socket和struct inode,这里就是将这个struct inode和上面分配的dentry关联
    dentry->d_inode = inode;
...
}

alloc_file()函数则是将file dentry inode socket四者柔和到一起,形成sockfs的整套逻辑。

struct file *alloc_file(struct path *path, fmode_t mode,
        const struct file_operations *fop)
{
    struct file *file;

    file = get_empty_filp();//在filp_cachep缓存中分配file结构体
    if (IS_ERR(file))
        return file;

    //path结构体里包含了刚才struct socket_alloc的inode成员信息
    file->f_path = *path;
    //file结构体里也有变量指向刚才struct socket_alloc的inode
    //至此,file dentry inode socket四者总算是走到一起,实现了完整的sock文件系统逻辑
    file->f_inode = path->dentry->d_inode;
    file->f_mapping = path->dentry->d_inode->i_mapping;
    file->f_mode = mode;
    //fop其实就是socket_file_ops,这样就能表明这个file结构体对应的是socket
    file->f_op = fop;
...
}

最后就是返回用户使用的文件描述符fd和file的关联了。

2.2.2 fd_install()函数

void fd_install(unsigned int fd, struct file *file)
{
    //获得当前进程的打开文件列表files
    __fd_install(current->files, fd, file);
}

void __fd_install(struct files_struct *files, unsigned int fd,
        struct file *file)
{
    struct fdtable *fdt;
    spin_lock(&files->file_lock);
    fdt = files_fdtable(files);//获得文件描述符位图表
    BUG_ON(fdt->fd[fd] != NULL);
    //将file指针放入文件描述符位图表中,这样我们之后就能通过fd找回file结构
    rcu_assign_pointer(fdt->fd[fd], file);
    spin_unlock(&files->file_lock);
}

至此,socket系统调用就结束了,将fd返回用户使用。

我们大概概述一下,socket系统调用的操作:首先在内核生成一个socket_alloc 和tcp_sock类型的对象,其中sock_alloc对象中的socket和tcp_sock对象的sock绑定,sock_alloc对象中的inode和file类型对象绑定。然后将分配的文件描述符fd和file对象关联,最后将这个文件描述符fd返回给用户使用。

经过这一连串操作,用户只要使用fd,内核就能根据这个fd进行网络连接管理的各种操作。

最后上张图,描述从fd到内核sock各个结构体的关系。
这里写图片描述


参考资料:
1、https://baike.baidu.com/item/socket/281150?fr=aladdin
2、http://blog.csdn.net/u011130578/article/details/44220999
3、http://blog.csdn.net/idwtwt/article/details/50964302

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值