Linux网络解读(2) - 套接字的初始化

socket的创建

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

socket返回了一个fd的值。

当在用户态调用socket创建套接字时发生了什么呢?

fd和socket之间的关系

Linux网络解读(2) - 套接字的初始化

socket结构体

struct socket {
    socket_state state;
    unsigned long flags;
    const struct proto_ops *ops;
    struct fasync_struct *fasync_list;
    struct file *file;
    struct sock *sk;
    wait_queue_head_t wait;
    short type;
};

sys_socket

socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

主要看一下创建一个套接字过程中都分配了哪些资源。

// net/socket.c
asmlinkage long sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;
        retval = sock_create(family, type, protocol, &sock);
        if (retval < 0)
                goto out;
        retval = sock_map_fd(sock);
}

可以看出,创建一个套接字需要从两方面入手:

1) 根据family, type, protocol分配socket资源

2) 把socket和VFS关联起来。

先看如何分配socket相关的资源。

1) 分配套接字资源sock_create

分配的过程是:从上层开始,分配上层对应的数据结构,然后根据参数设置函数。然后再分配下层对应的数据结构。
static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
{
    sock = sock_alloc();
    sock->type  = type;
    net_families[family]->create(sock, protocol)    
}

1) sock = sock_alloc();

分配最上层的代表:socket结构体。

这个结构体和具体的协议无关。

有了这个结构体就可以设置相关的协议选项和buffer了。

另外,这个sock_alloc()同时分配了file的结构体,在内存布局上是相邻的。

Linux网络解读(2) - 套接字的初始化

2) net_families[family]->create(sock, protocol)

net_families[family]通过套接字domain的值family,选择出一个协议族,然后调用其create函数,同时把socket的结构体和下层的参数protocol传进入。

这里的family为AF_INET,根据net_families的初始化,可以知道net_families[family]->create()对应inet_create()函数。

inet层的构造:inet_create

// net/ipv4/af_inet.c
static int inet_create(struct socket *sock, int protocol)
{
        sock->state = SS_UNCONNECTED;
        
        // 根据type找到inetsw,这里的type是SOCK_RAW
        list_for_each_rcu(p, &inetsw[sock->type]) {
                answer = list_entry(p, struct inet_protosw, list);
                /* 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;
                }
                answer = NULL;
        }
        // 通过上面的循环,此处的answer是raw_proto,把协议的ops设置给socket层
        sock->ops = answer->ops;
        answer_prot = answer->prot;
        answer_no_check = answer->no_check;
        answer_flags = answer->flags;
        
        // 分配sock结构体
        sk = sk_alloc(PF_INET, GFP_KERNEL,
                      answer_prot->slab_obj_size,
                      answer_prot->slab);
       // 把协议的结构体设置给sock                      
       sk->sk_prot = answer_prot;
       // 把sock结构体转换成inet_sock,因为此时的上下文正是inet的create方法
       inet = inet_sk(sk);
       
       // 如果是协议是raw,则把protocol赋值给inet->num,这个值本身应该是端口的数值,但raw没有端口。
       if (SOCK_RAW == sock->type) {
                inet->num = protocol;
                if (IPPROTO_RAW == protocol)
                        inet->hdrincl = 1;
        }
        // 初始化socket,sock结构体
        // 初始化sock结构体中的receive,write,error queue
        // 初始化sock结构体中的revbuf,sendbuf大小
        // 初始化sock中其他的回调:sk_state_change(), sk_data_ready()等
        sock_init_data(sock, sk);
        // 把sock结构体添加到raw协议中的hash中?
        if (inet->num) {
                inet->sport = htons(inet->num);
                /* Add to protocol hash chains. */
                sk->sk_prot->hash(sk);
        }
        
}

sock结构体是用来管理具体协议的结构体,协议不同这个结构体的意义也不同,sock在不同协议的数据块:

Linux网络解读(2) - 套接字的初始化

在sk_alloc中会根据具体的协议proto的obj_size大小分配一片内存:

struct sock *sk_alloc(int family, gfp_t priority,
              struct proto *prot, int zero_it)
{
    struct sock *sk = NULL;
    kmem_cache_t *slab = prot->slab;
    if (slab != NULL)
        sk = kmem_cache_alloc(slab, priority);
    else
               // 根据协议proto的obj_size分配一片内存!!!proto是在inet_init中初始化的
        sk = kmalloc(prot->obj_size, priority);
    if (sk) {
        if (zero_it) {
            memset(sk, 0, prot->obj_size);
            sk->sk_family = family;
            /*
             * See comment in struct sock definition to understand
             * why we need sk_prot_creator -acme
             */
            sk->sk_prot = sk->sk_prot_creator = prot;
            sock_lock_init(sk);
        }
        
        if (security_sk_alloc(sk, family, priority))
            goto out_free;
        if (!try_module_get(prot->owner))
            goto out_free;
    }
    return sk;
out_free:
    if (slab != NULL)
        kmem_cache_free(slab, sk);
    else
        kfree(sk);
    return NULL;
}

2) 把socket和VFS关联起来:sock_map_fd()

int sock_map_fd(struct socket        *sock)
{
    fd = get_unused_fd();
    struct file        *file = get_empty_filp();
    file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
    
    // 设置d_op
    file->f_dentry->d_op = &sockfs_dentry_operations;
    d_add(file->f_dentry, SOCK_INODE(sock));
    file->f_vfsmnt             = mntget(sock_mnt);
    file->f_mapping             = file->f_dentry->d_inode->i_mapping;
    // 设置socket结构体中file的指针,关联起socket和file
    sock->file    = file;
    // 设置f_op
    file->f_op    = SOCK_INODE(sock)->i_fop = &socket_file_ops;
    file->f_mode  = FMODE_READ | FMODE_WRITE;
    file->f_flags = O_RDWR;
    file->f_pos   = 0;
    fd_install(fd, file);
}

fd,socket,sock之间的关系:

Linux网络解读(2) - 套接字的初始化

上一篇:numatop 和 perf event


下一篇:cgroup