Linux 内核网络之 socket 的实现

(29) 2023-12-27 13:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说Linux 内核网络之 socket 的实现,希望能够帮助你!!!。

创建 socket 套接口的系统调用为 sys_socket( )。

该系统调用把套接口的创建和关于此套接口的关联文件描述符的分配做了简单的封装,从而完成套接口的创建功能。

其系统调用的关系如下:

Linux 内核网络之 socket 的实现_https://bianchenghao6.com/blog__第1张

sys_socket


/*
family:待创建接口的协议族,如PF_INET、PF_UNIX 等
type : 待创建套接口的类型,如SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等
protocol: 传输层协议,如IPPROTO_TCP、IPPROTO_UDP等
*/
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:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;

out_release:
sock_release(sock);
return retval;
}

关于套接口创建和初始化相关功能在 sock_create 中。


int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(family, type, protocol, res, 0);
}


/*
family:待创建接口的协议族,如PF_INET、PF_UNIX 等
type : 待创建套接口的类型,如SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等
protocol: 传输层协议,如IPPROTO_TCP、IPPROTO_UDP等
res: 输出参数,为创建成功的套接口指针
kern : 标识由应用程序还是由内核创建该套接口
*/
static int __sock_create(int family, int type, int protocol,
struct socket **res, int kern)
{
int err;
struct socket *sock;
const struct net_proto_family *pf;

// 有关参数和套接口的检查
...

/*
* 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.
*/
//创建与套接口关联的i结点和套接口,并初始化
sock = sock_alloc();
if (!sock) {
if (net_ratelimit())
printk(KERN_WARNING "socket: no more sockets\n");
return -ENFILE; /* Not exactly a match, but its the
closest posix thing */
}

sock->type = type;

rcu_read_lock();
/* 根据family参数获取已注册到net_families中对应的net_proto_family指针,
根据该指针获得域(比如inet,unix)特定的socket创建函数 */
pf = rcu_dereference(net_families[family]);
err = -EAFNOSUPPORT;
if (!pf)
goto out_release;

/*
* We will call the ->create function, that possibly is in a loadable
* module, so we have to bump that loadable module refcnt first.
*/
if (!try_module_get(pf->owner))
goto out_release;

/* Now protected by module ref count */
rcu_read_unlock();
/*
在ipv4协议族中调用inet_create()对已创建的套接口继续进行初始化,同时创建传输控制块。
ipv4协议族定义为inet_family_ops, 并在inet_init中进行注册。
*/
err = pf->create(sock, protocol); // inet_create()
if (err < 0)
goto out_module_put;

/*
* Now to bump the refcnt of the [loadable] module that owns this
* socket at sock_release time we decrement its refcnt.
*/
if (!try_module_get(sock->ops->owner))
goto out_module_busy;

// 安全检查
...

*res = sock;

return 0;

out_module_busy:
err = -EAFNOSUPPORT;
out_module_put:
sock->ops = NULL;
module_put(pf->owner);
out_sock_release:
sock_release(sock);
return err;

out_release:
rcu_read_unlock();
goto out_sock_release;
}

__socket_create 函数主要工作如下:

  • 调用 sock_alloc ( ) 分配一个 struct socket 结构体和 inode,并且标明 inode 是 socket 类型,这样对 inode 的操作最终可以调用 socket 操作;
  • 根据输入参数,查找 net_families 数组(该数组通过 inet_init 创建),获得域(比如inet,unix )特定的socket 创建函数,对于 inet 域是inet_create ( )。

sock_alloc

struct socket_alloc {
    struct socket socket;
    struct inode vfs_inode;
};

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

    /*创建inode和socket*/
    inode = new_inode(sock_mnt->mnt_sb);
    if (!inode)
        return NULL;

    /*返回创建的socket指针*/
    sock = SOCKET_I(inode);

    /*inode相关初始化*/
    inode->i_mode = S_IFSOCK | S_IRWXUGO;
    inode->i_uid = current->fsuid;
    inode->i_gid = current->fsgid;

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


sock_alloc 函数分配一个 struct socket_alloc 结构体,然后初始化socket_alloc 结构体的 vfs_inode;

同时 sock_alloc 最终返回 socket_alloc 结构体的 socket 指针。

pf->create

pf 函数指针由 net_families[] 数组获得,net_families[]数组的初始化在inet_init函数。


static const struct net_proto_family *net_families[NPROTO] __read_mostly;

static 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)
{
...

net_families[ops->family] = ops;

...

}

net_families[] 数组里存放的是各个协议族的信息,其中以family字段作为下标。对于 TCP 协议而言,family 字段是 AF_INET,因此 pf->create 函数将调用 inet_create 函数。


// 创建与该套接口对应的传输控制块,并与之关联
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 try_loading_module = 0;
int err;

//初始化套接口为 SS_UNCONNECTED 状态
sock->state = SS_UNCONNECTED;

/* Look for the requested type/protocol pair. */
answer = NULL;
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
//以 sock->type 为关键字,遍历inetsw散列表
list_for_each_rcu(p, &inetsw[sock->type]) {
//通过计算偏移的方法获取指向inet_protosw 结构的指针
answer = list_entry(p, struct inet_protosw, list);

//根据协议类型获取匹配的inet_protosw 结构实例
/* 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;
}
err = -EPROTONOSUPPORT;
answer = NULL;
}
/*若未能在inetsw中获取匹配的inet_protosw实例,则需加载相应的内核模块,之后再次回到
lookup_protocol处,获取匹配的inet_protosw实例。
尝试加载模块最多不超过2次。
*/
if (unlikely(answer == NULL)) {
if (try_loading_module < 2) {
rcu_read_unlock();
/*
* Be more specific, e.g. net-pf-2-proto-132-type-1
* (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
*/
if (++try_loading_module == 1)
request_module("net-pf-%d-proto-%d-type-%d",
PF_INET, protocol, sock->type);
/*
* Fall back to generic, e.g. net-pf-2-proto-132
* (net-pf-PF_INET-proto-IPPROTO_SCTP)
*/
else
request_module("net-pf-%d-proto-%d",
PF_INET, protocol);
goto lookup_protocol;
} else
goto out_rcu_unlock;
}

err = -EPERM;
/* 每个进程描述符中有个成员为cap_effective,主要用来标志当前进程的能力。其中每种能力有一位表示,1表示具备某种能力
, 0 表示没有,这里判断当前进程是否有answer->capability能力,若没有则不创建套接口 */
if (answer->capability > 0 && !capable(answer->capability))
goto out_rcu_unlock;

//设置套接口中套接口层和传输层之间的接口ops
sock->ops = answer->ops;
//临时获取 inet_protosw 中的一些参数,以备后面使用。
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, 1);
if (sk == NULL)
goto out;

err = 0;
// 设置传输控制块是否需要校验和以及是否可以重用地址和端口的标志
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = 1;
// 设置 inet_sk 中的 is_icsk,标志是否为面向连接的传输控制块
inet = inet_sk(sk);
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;

//若为原始套接口,则设置本地端口为协议号,并且若协议为RAW协议,则设置inet_sock块中的 hdrincl,表示需要自己构建ip首部。 
if (SOCK_RAW == sock->type) {
inet->num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}

/*根据系统参数 no_pmtu_disc 设置创建的传输控制块是否支持 PMTU */
if (ipv4_config.no_pmtu_disc)
inet->pmtudisc = IP_PMTUDISC_DONT;
else
inet->pmtudisc = IP_PMTUDISC_WANT;

inet->id = 0;
// 对传输控制块进行初始化
sock_init_data(sock, sk);
//inet_sock_destruct 在套接口释放时被回调,进行一些资源回收和清理工作
sk->sk_destruct = inet_sock_destruct;
sk->sk_family = PF_INET;
sk->sk_protocol = protocol;
// 设置传输控制块中 sk_backlog_rcv 后备队列接收函数
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

inet->uc_ttl = -1; //设置单播TTL
inet->mc_loop = 1; //设置组播是否发向回路标志
inet->mc_ttl = 1; //设置组播的TTL
inet->mc_index = 0; //设置组播使用的本地设备接口的索引
inet->mc_list = NULL; //初始化组播组列表

sk_refcnt_debug_inc(sk);

/* 
若传输控制块中的num设置了本地端口号,则设置传输控制块中的sport 的网路字节序
格式的本地端口号。并且调用传输层接口上的hash(), 把传输控制块加入到管理的散列表中。
*/
if (inet->num) {
/* It assumes that any protocol which allows
* the user to assign a number at socket
* creation time automatically
* shares.
*/
inet->sport = htons(inet->num);
/* Add to protocol hash chains. */
sk->sk_prot->hash(sk);
}
/*若init() 被设置,则调用init()进行具体传输控制块的初始化。
TCP中为 tcp_v4_init_sock(), 而UDP中则没有对应的实现。
*/
if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err)
sk_common_release(sk);
}
out:
return err;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}

该函数主要工作如下:

  • 设置 socket 状态为 SS_UNCONNECTED;
  • 查找全局数组 inetsw(在 inet_init 函数中初始化)中对应的协议操作集合,最重要的是 struct proto 和 struct proto_ops,分别用于处理四层和 socket 相关的内容;
  • 调用 sk_alloc(),分配一个 struct sock,并将 proto 类型的指针指向第二步获得的内容。
  • struct inet_sock 是struct sock的超集,具体参见include/net/inet_sock.h中inet_sock 的定义。初始化 inet_sock,调用sock_init_data,形成 socket 和 sock 一一对应的关系,相互有指针指向对方。
  • 最后调用proto中注册的init函数,err = sk->sk_prot->init(sk),如果对应于 TCP,其函数指针指向 tcp_v4_init_sock。

sock_map_fd


int sock_map_fd(struct socket *sock)
{
struct file *newfile;

/*分配文件描述符*/
int fd = sock_alloc_fd(&newfile);

if (likely(fd >= 0)) {
/*分配file对象,socket和file对象进行关联*/
int err = sock_attach_fd(sock, newfile);

if (unlikely(err < 0)) {
put_filp(newfile);
put_unused_fd(fd);
return err;
}
/* 文件描述符和file对象进行关联 */
fd_install(fd, newfile);
}
return fd;
}

该函数的作用就是把分配文件描述符、file 对象、socket,三者进行关联。

Linux 内核网络之 socket 的实现_https://bianchenghao6.com/blog__第2张

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复