Linux源码-sys_socket()

(本文部分参考《Linux内核源代码情景分析》)
本文主要介绍函数sys_socket()在内核中的实现。
1、系统调用总入口: sys_socketcall()
Linux 内核为所有与 socket 有关的操作提供了一个统一的系统调用入口,但是在用户程序界面上则通过 C 语言程序库 c.lib 提供诸多库函数,看起来好像都是独立的系统调用一样。内核中为 socket 设置的总入口为 sys_socketcall(),其代码在 net/socket.c :

/*与socket相关的系统调用总入口。 */
/*
*函数的第一个参数 call 即为具体的操作码,而参数 args 为指向一个数组的指针,可以根据具体操作码的不同,确定从用户空间复制参数的数量;
*/
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
    unsigned long a[6];
    unsigned long a0,a1;
    int err;

    if(call<1||call>SYS_RECVMSG)
        return -EINVAL;

    /* copy_from_user should be SMP safe. */
    if (copy_from_user(a, args, nargs[call]))
        return -EFAULT;

    a0=a[0];
    a1=a[1];

    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_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_GETSOCKNAME:
            err = sys_getsockname(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_GETPEERNAME:
            err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
            break;
        case SYS_SOCKETPAIR:
            err = sys_socketpair(a0,a1, a[2], (int __user *)a[3]);
            break;
        case SYS_SEND:
            err = sys_send(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_SENDTO:
            err = sys_sendto(a0,(void __user *)a1, a[2], a[3],
                     (struct sockaddr __user *)a[4], a[5]);
            break;
        case SYS_RECV:
            err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_RECVFROM:
            err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
                       (struct sockaddr __user *)a[4], (int __user *)a[5]);
            break;
        case SYS_SHUTDOWN:
            err = sys_shutdown(a0,a1);
            break;
        case SYS_SETSOCKOPT:
            err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
            break;
        case SYS_GETSOCKOPT:
            err = sys_getsockopt(a0, a1, a[2], (char __user *)a[3], (int __user *)a[4]);
            break;
        case SYS_SENDMSG:
            err = sys_sendmsg(a0, (struct msghdr __user *) a1, a[2]);
            break;
        case SYS_RECVMSG:
            err = sys_recvmsg(a0, (struct msghdr __user *) a1, a[2]);
            break;
        default:
            err = -EINVAL;
            break;
    }
    return err;
}

2、函数sys_socket()——创建插口
操作 SYS_SOCKET 是由 sys_socket()实现的,其代码在 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);
    if (retval < 0)
        goto out_release;

out:
    return retval;

out_release:
    sock_release(sock);
    return retval;
}

sys_socket()函数中主体函数由两个函数实现:sock_create()和sock_map_fd(),接下来分别介绍。
2.1 sock_create()只是起了中转作用,它调用了函数__sock_create():

int sock_create(int family, int type, int protocol, struct socket **res)
{
    /* 传入0表示是用户态进程创建套接口 */
    return __sock_create(family, type, protocol, res, 0);
}

函数__sock_create()代码如下:

/**
 * 创建一个套接口
 *  family:     套接口协议族
 *  type:       套接口类型
 *  protocol:       传输层协议
 *  res:            输出参数,创建成功的套接口指针
 *  kern:       由内核还是应用程序创建。
 */
static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
{
    int err;
    struct socket *sock;

    if (family < 0 || family >= NPROTO)/* 参数合法性检测 */
        return -EAFNOSUPPORT;
    if (type < 0 || type >= SOCK_MAX)
        return -EINVAL;
    /**
     * IPV4协议族的SOCK_PACKET类型套接口已经不被支持
     * 为兼容旧程序,转换为PF_PACKET 
*/
    if (family == PF_INET && type == SOCK_PACKET) {
        static int warned; 
        if (!warned) {
            warned = 1;
            printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm);
        }
        family = PF_PACKET;
    }

    /* 由安全模块对创建过程进行审计 */
    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;

#if defined(CONFIG_KMOD)
    if (net_families[family]==NULL)/* 相应的协议族在内核中尚不存在,加载模块以支持该协议族 */
    {
        request_module("net-pf-%d",family);
    }
#endif

    /* 等待,直到锁被释放 */
    net_family_read_lock();
    if (net_families[family] == NULL) {/* 如果协议族仍然不存在,说明不支持此协议族 */
        err = -EAFNOSUPPORT;
        goto out;
    }


    if (!(sock = sock_alloc())) {/* 分配与inode关联的套接口 */
        printk(KERN_WARNING "socket: no more sockets\n");
        err = -ENFILE;      
        goto out;
    }

    sock->type  = type;/* 设置套接口类型。 */

    err = -EAFNOSUPPORT;
    if (!try_module_get(net_families[family]->owner))/* 增加对协议族模块的引用,如果失败则退出 */
        goto out_release;

    /* 调用协议族的创建方法,对IPV4来说,调用的是inet_create */
    if ((err = net_families[family]->create(sock, protocol)) < 0)
        goto out_module_put;
    if (!try_module_get(sock->ops->owner)) {/* 增加传输层模块的引用计数 */
        sock->ops = NULL;
        goto out_module_put;
    }
    /* 增加了传输层模块的引用计数后,可以释放协议族的模块引用计数 */
    module_put(net_families[family]->owner);
    *res = sock;
    /* 通知安全模块,对创建过程进行检查。 */
    security_socket_post_create(sock, family, type, protocol, kern);

out:
    net_family_read_unlock();
    return err;
out_module_put:
    module_put(net_families[family]->owner);
out_release:
    sock_release(sock);
    goto out;
}

本函数中sock_alloc()函数和inet_create()函数最为关键。

2.1.1 函数 sock_alloc()分配一个 socket 数据结构并进行一些初始化:

static struct socket *sock_alloc(void)
{
    struct inode * inode;
    struct socket * sock;

    inode = new_inode(sock_mnt->mnt_sb);
    if (!inode)
        return NULL;

    sock = SOCKET_I(inode);

/*
*在 inode 结构中还要将 i_mode 里的 S_IFSOCK 标志位设成 1,并将 i_sock 也设成 1,以示这个 inode 结构所代表的并不是磁盘文件,而是一个插口。
*/
    inode->i_mode = S_IFSOCK|S_IRWXUGO;
    inode->i_sock = 1;
    inode->i_uid = current->fsuid;
    inode->i_gid = current->fsgid;

    get_cpu_var(sockets_in_use)++;
    put_cpu_var(sockets_in_use);
    return sock;
}

由上述代码可知,取得一个 inode 结构是取得一个 socket 结构的必要条件。不仅如此, socket 结构其实只是 inode结构中的一部分。具体数据结构参看文末。
实际上,本函数主体功能又分担给两个函数new_inode()和sock = SOCKET_I();
2.1.1.1

struct inode *new_inode(struct super_block *sb)
{
    ...
    inode = alloc_inode(sb);
    if (inode) {
        ...
    }
    return inode;
}

在本函数中,首先通过函数alloc_inode(sb)创建一个inode节点,然后对节点进行一些设置。
alloc_inode()代码如下:

static struct inode *alloc_inode(struct super_block *sb)
{
    /*
*如果当前文件系统超级块有自己分配inode节点的操作函数就调用自己的,否则从公用高速缓冲区分配一块inode
*/
    if (sb->s_op->alloc_inode)
        inode = sb->s_op->alloc_inode(sb);
    else
        inode = (struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL);

    ...
    return inode;
}

2.1.1.2 在 inode 结构中有一个关键性的成分 u。这是一个 union,要按具体的文件类型和格式而解释成不同的数据结构。目前 Linux 支持 20 多种不同的文件系统,因此对这个 union 有 20 多种不同的解释,而 socket 结构正是其中之一。sock = SOCKET_I(inode);只是将 inode 结构中的这个 union 解释为 socket 结构而已:

static inline struct socket *SOCKET_I(struct inode *inode)
{
/* 
* container_of(ptr, type, member)宏的作用:
*返回ptr指针所在的结构体;其中ptr为结体体type的变量中member成员的指针;
*将ptr指针转化为char *,然后减去其在结构体中的偏移量,
*得到的是ptr所在的结构体的地址,最后强制转换成type *;
*/
    return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}

2.1.2 调用协议族的创建方法创建一个套接口,对IPV4来说,就是调用inet_create:

/**
 * 创建一个IPV4的socket
 *  sock:       已经创建的套接口
 *  protocol:   套接口的协议号
 */
static int inet_create(struct socket *sock, int protocol)
{
    struct sock *sk;
    struct list_head *p;
    struct inet_protosw *answer;
    struct inet_sock *inet;
    struct proto *answer_prot;
    unsigned char answer_flags;
    char answer_no_check;
    int err;
/* 
* 初始化套接口状态 
* 插口的初始状态设置成SS_UNCONNECTED,“有连接”模式,既类型为 SOCK_STREAM 的插口必须在建立了连接以后才能使用。
*/
    sock->state = SS_UNCONNECTED;

    answer = NULL;
    rcu_read_lock();
    list_for_each_rcu(p, &inetsw[sock->type]) {/* 根据套接口类型遍历IPV4链表  */
        answer = list_entry(p, struct inet_protosw, list);


        if (protocol == answer->protocol) {/* 比较传输层协议 */
            if (protocol != IPPROTO_IP)
                break;
        } else {

            if (IPPROTO_IP == protocol) {
                protocol = answer->protocol;
                break;
            }
            if (IPPROTO_IP == answer->protocol)
                break;
        }
        answer = NULL;
    }

    err = -ESOCKTNOSUPPORT;
    if (!answer)/* 找不到对应的传输层协议 */
        goto out_rcu_unlock;
    err = -EPERM;
    /* 创建该类型的套接口需要特定能力,而当前进程没有这种能力,则退出 */
    if (answer->capability > 0 && !capable(answer->capability))
        goto out_rcu_unlock;
    err = -EPROTONOSUPPORT;
    if (!protocol)
        goto out_rcu_unlock;

    /* 设置套接口层的接口 */
    sock->ops = answer->ops;
    answer_prot = answer->prot;
    answer_no_check = answer->no_check;
    answer_flags = answer->flags;
    rcu_read_unlock();

    BUG_TRAP(answer_prot->slab != NULL);

    err = -ENOBUFS;
    /* 根据协议族等参数分配传输控制块。 */
    sk = sk_alloc(PF_INET, GFP_KERNEL,
              answer_prot->slab_obj_size,
              answer_prot->slab);
    if (sk == NULL)
        goto out;

    err = 0;
    sk->sk_prot = answer_prot;
    /* 设置是否需要校验和 */
    sk->sk_no_check = answer_no_check;
    /* 是否可以重用地址和端口 */
    if (INET_PROTOSW_REUSE & answer_flags)
        sk->sk_reuse = 1;

    inet = inet_sk(sk);

    if (SOCK_RAW == sock->type) {/* 是原始套接口 */
        inet->num = protocol;/* 设置本地端口为协议号 */
        if (IPPROTO_RAW == protocol)/* 如果是RAW协议,则需要自己构建IP首部 */
            inet->hdrincl = 1;
    }

    if (ipv4_config.no_pmtu_disc)/* 是否支持PMTU */
        inet->pmtudisc = IP_PMTUDISC_DONT;
    else
        inet->pmtudisc = IP_PMTUDISC_WANT;

    inet->id = 0;

    sock_init_data(sock, sk);/* 初始化传输控制块 */
    sk_set_owner(sk, sk->sk_prot->owner);

    /* 在套接口被释放时,进行一些清理工作。 */
    sk->sk_destruct    = inet_sock_destruct;
    /* 设置协议族和协议号 */
    sk->sk_family      = PF_INET;
    sk->sk_protocol    = protocol;
    /* 设置后备队列接收函数。 */
    sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

    inet->uc_ttl    = -1;
    inet->mc_loop   = 1;
    inet->mc_ttl    = 1;
    inet->mc_index  = 0;
    inet->mc_list   = NULL;

#ifdef INET_REFCNT_DEBUG
    atomic_inc(&inet_sock_nr);
#endif

    if (inet->num) {/* 如果设置了本地端口号 */
        inet->sport = htons(inet->num);/* 设置网络序的本地端口号 */
        sk->sk_prot->hash(sk);/* 将传输控制块加入到静列表中 */
    }

    if (sk->sk_prot->init) {/* 调用传输层的初始化回调,对TCP来说,就是tcp_v4_init_sock。UDP没有设置回调 */
        err = sk->sk_prot->init(sk);
        if (err)
            sk_common_release(sk);
    }
out:
    return err;
out_rcu_unlock:
    rcu_read_unlock();
    goto out;
}

2.1.2.1分配传输控制块sk = sk_alloc()

struct sock *sk_alloc(int family, int priority, int zero_it, kmem_cache_t *slab)
{
    struct sock *sk = NULL;

    if (!slab)/* 如果传输层没有指定缓存分配区,则默认使用sk_cachep */
        slab = sk_cachep;
    /* 在指定的分配区中,用指定的分配参数分配传输控制块  */
    sk = kmem_cache_alloc(slab, priority);
    if (sk) {
        if (zero_it) {/* 需要初始化它 */
            memset(sk, 0,
                   zero_it == 1 ? sizeof(struct sock) : zero_it);
            sk->sk_family = family;
            sock_lock_init(sk);
        }
        /* 释放时需要使用 */
        sk->sk_slab = slab;

        if (security_sk_alloc(sk, family, priority)) {/* 安全审计,如果失败则释放传输层接口 */
            kmem_cache_free(slab, sk);
            sk = NULL;
        }
    }
    return sk;
}

2.1.2.2 inet = inet_sk(sk)
将sock强转为inet_sock,之所以可以强转是因为在分配sock结构体变量时,分配的真实结构体是tcp_sock,而sock、inet_sock、tcp_sock之间均为0处偏移。

2.1.2.3 初始化传输控制块sock_init_data(sock, sk)
本函数主要完成的工作是:
a.初始化sock结构的缓冲区和队列
b.初始化sock结构的一些状态
c.建立socket和sock结构的相互引用关系

2.2 sock_map_fd()代码如下:

/*将套接口与文件描述符绑定。 */
int sock_map_fd(struct socket *sock)
{
    int fd;
    struct qstr this;
    char name[32];

    /* 获得空闲的文件描述符。   */
    fd = get_unused_fd();
    if (fd >= 0) {/* 成功分配文件描述符 */
        struct file *file = get_empty_filp();

        if (!file) {
            put_unused_fd(fd);
            fd = -ENFILE;
            goto out;
        }

        sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino);
        this.name = name;
        this.len = strlen(name);
        this.hash = SOCK_INODE(sock)->i_ino;

        /*分配文件目录项。       */
        file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
        if (!file->f_dentry) {
            put_filp(file);
            put_unused_fd(fd);
            fd = -ENOMEM;
            goto out;
        }
        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;

        sock->file = file;
        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);
    }

out:
    return fd;
}

其中fd_install函数如下,只是做了相应指针的改变:

void fastcall fd_install(unsigned int fd, struct file * file)
{
    struct files_struct *files = current->files;
    spin_lock(&files->file_lock);
    if (unlikely(files->fd[fd] != NULL))
        BUG();
    files->fd[fd] = file;
    spin_unlock(&files->file_lock);
}

整体流程图:
这里写图片描述
重要数据结构:
这里写图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值