C/C++教程

socket 源码分析

本文主要是介绍socket 源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Socket 源码分析

我们使用 socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 语句创建了一个 socket ,那么实际上发生了什么呢?

从下面的注释可以看的出来,之前是通过调用 sys_socketcall 再跳转, 现在是直接调用 sys_socket 函数。

下面的宏就是完成 sys_socket 到 SYSCALL_DEFINE3 宏包围的函数的调用关系。如果一大堆宏比较麻烦,可以直接参考 -->Linux系统调用之SYSCALL_DEFINE 的分析。

    /include/linux/syscalls.h
    
    /* obsolete: net/socket.c */
    asmlinkage long sys_socketcall(int call, unsigned long __user *args);
    
    /* net/socket.c */
    asmlinkage long sys_socket(int, int, int);
    asmlinkage long sys_socketpair(int, int, int, int __user *);
    asmlinkage long sys_bind(int, struct sockaddr __user *, int);
    asmlinkage long sys_listen(int, int);
    asmlinkage long sys_accept(int, struct sockaddr __user *, int __user *);
    //omit...



    #define SYSCALL_DEFINEx(x, sname, ...)				\
    	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
    	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
    
    #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
    
    /*
     * The asmlinkage stub is aliased to a function named __se_sys_*() which
     * sign-extends 32-bit ints to longs whenever needed. The actual work is
     * done within __do_sys_*().
     */
    #ifndef __SYSCALL_DEFINEx
    #define __SYSCALL_DEFINEx(x, name, ...)					\
    	__diag_push();							\
    	__diag_ignore(GCC, 8, "-Wattribute-alias",			\
    		      "Type aliasing is used to sanitize syscall arguments");\
    	asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))	\
    		__attribute__((alias(__stringify(__se_sys##name))));	\
    	ALLOW_ERROR_INJECTION(sys##name, ERRNO);			\
    	static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
    	asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__));	\
    	asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
    	{								\
    		long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
    		__MAP(x,__SC_TEST,__VA_ARGS__);				\
    		__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));	\
    		return ret;						\
    	}								\
    	__diag_pop();							\
    	static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
    #endif /* __SYSCALL_DEFINEx */

  

    #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
    #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

关于 SYSCALL_DEFINE 解释比较详细的一篇文章

到此为止,用户 调用 socket 已经进行到了内核中此处的函数

代码位置: net/socket.c

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
    {
    	int retval;
    	struct socket *sock;
    	int flags;
    	//Omit...
    	flags = type & ~SOCK_TYPE_MASK;
    	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
    		return -EINVAL;
    	type &= SOCK_TYPE_MASK;
    
    	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
    		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    	
        // 可以看出就是这里进行的 socket 的创建工作,接下来重点对这个代码进行分析
    	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;
    }

sock_create 实际上是调用了 __socket_create 

    int sock_create(int family, int type, int protocol, struct socket **res)
    {
    	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
    }



    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;
    
    	/*
    	 *      Check protocol is in range
    	 */
    	if (family < 0 || family >= NPROTO)
    		return -EAFNOSUPPORT;
    	if (type < 0 || type >= SOCK_MAX)
    		return -EINVAL;
    
    	/* Compatibility.
    
    	   This uglymoron is moved from INET layer to here to avoid
    	   deadlock in module load.
    	 */
    	if (family == PF_INET && type == SOCK_PACKET) {
    		pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
    			     current->comm);
    		family = PF_PACKET;
    	}
    
    	/*
    	 *	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.
    	 */
        // 申请一个与 inode 绑定的 socket 。
    	sock = sock_alloc();
    	if (!sock) {
    		net_warn_ratelimited("socket: no more sockets\n");
    		return -ENFILE;	/* Not exactly a match, but its the
    				   closest posix thing */
    	}
    
    	sock->type = type;
    
    	rcu_read_lock();
        // 从 RCU 中找到协议对应的数据
    	pf = rcu_dereference(net_families[family]);
    	err = -EAFNOSUPPORT;
    	if (!pf)
    		goto out_release;
    
    	/* Now protected by module ref count */
    	rcu_read_unlock();
    	// 调用其中的 create 函数
    	err = pf->create(net, sock, protocol, kern);
    	if (err < 0)
    		goto out_module_put;
    
    	// Omit...
    	*res = sock;
    
    	return 0;
    
    	// Omit...
    }

其中 net_families[family] 这个结构数组的定义可以下面,其中注释中已经说了,所有协议都将被注册到这个数组中。所以从 rcu 中找到的就是这个结构,调用的就是这个结构中的 create 函数。

    /*
     *	The protocol list. Each protocol is registered in here.
     */
    
    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;
    };

回到开始,我们创建 socket 使用的 family 参数是 AP_PACKET, 于是找到对应的 net_proto_family 结构。

位置: net/packet/af_packet.c

    static const struct net_proto_family packet_family_ops = {
    	.family =	PF_PACKET,
    	.create =	packet_create,
    	.owner	=	THIS_MODULE,
    };

这个是kernel中队 sock 结构的解释。接下来继续分析 packet_create 函数。

    // struct sock - network layer representation of sockets    

    static int packet_create(struct net *net, struct socket *sock, int protocol,
    			 int kern)
    {
            struct sock *sk;
            struct packet_sock *po;
            __be16 proto = (__force __be16)protocol; /* weird, but documented */
            int err;
        if (!ns_capable(net->user_ns, CAP_NET_RAW))
            return -EPERM;
        if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW &&
            sock->type != SOCK_PACKET)
            return -ESOCKTNOSUPPORT; 
    
        sock->state = SS_UNCONNECTED; /* unconnected to any socket	*/
    
        err = -ENOBUFS;
        //根据参数创建 sock 对象。
        sk = sk_alloc(net, PF_PACKET, GFP_KERNEL, &packet_proto, kern);
        if (sk == NULL)
            goto out;
        
    	// 这里面有关于套接字的各种操作,比如 bind、accept 等。
        sock->ops = &packet_ops;
        if (sock->type == SOCK_PACKET)
            sock->ops = &packet_ops_spkt;
    	
        // 经过前面的初始化过程后,此处将 sock 结构 与 socket 发生关联(将 socket 绑定到 sock 上),并初始化 sock 结构。
        sock_init_data(sock, sk);
    
        po = pkt_sk(sk);
        sk->sk_family = PF_PACKET;
        po->num = proto;	// 将协议添加到此处
        po->xmit = dev_queue_xmit;	//设置发送数据包函数
    
        err = packet_alloc_pending(po);
        if (err)
            goto out2;
    
        packet_cached_dev_reset(po);
    
        sk->sk_destruct = packet_sock_destruct; // 销毁数据包
        sk_refcnt_debug_inc(sk);
    
        /*
         *	Attach a protocol block
         */
    
        spin_lock_init(&po->bind_lock);
        mutex_init(&po->pg_vec_lock);
        po->rollover = NULL;
       // 指定 hook 的函数
        po->prot_hook.func = packet_rcv;
    	// 如果参数使用的是 SOCK_PACKET 那么函数就要使用 packet_rcv_spkt
        if (sock->type == SOCK_PACKET)
            po->prot_hook.func = packet_rcv_spkt;
    
        po->prot_hook.af_packet_priv = sk;
    	// 这里就可以看出指定的参数中的协议类型的作用,将会将 hook 的类型设置为协议类型
        if (proto) {
            po->prot_hook.type = proto;
            register_prot_hook(sk); // 这个函数还可以分析一下
        }
    
        mutex_lock(&net->packet.sklist_lock);
        sk_add_node_rcu(sk, &net->packet.sklist);
        mutex_unlock(&net->packet.sklist_lock);
    
        preempt_disable();
        sock_prot_inuse_add(net, &packet_proto, 1);
        preempt_enable();
    
        return 0;
        out2:
            sk_free(sk);
        out:
            return err;
    }
    ```

这样就创建出了一个sock结构,客户端的句柄实际上在内核中就是这个的对应对应关系。接下来的 bind、 send 等函数就是调用  packet_ops 结构中的函数。





## 拓展

[关于 Linux RCU 的详细介绍](http://www2.rdrop.com/users/paulmck/RCU/)

[Linux网络之设备接口层:发送数据包流程dev_queue_xmit](https://blog.csdn.net/wdscq1234/article/details/51926808)

这篇关于socket 源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!