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)