【Linux5.4】【TUN】代码学习记录(6)–tun_chr_open
当用户调用open函数打开"/dev/net/tun"设备时,tun_chr_open函数被调用。
tun_chr_open
tun_chr_open主要功能是创建并初始化一个tun_file结构体变量tfile。
static int tun_chr_open(struct inode *inode, struct file * file)
{
struct net *net = current->nsproxy->net_ns;
struct tun_file *tfile;
DBG1(KERN_INFO, "tunX: tun_chr_open\n");
/* 创建tfile,申请一个tfile内存空间 */
tfile = (struct tun_file *)sk_alloc(net, AF_UNSPEC, GFP_KERNEL,
&tun_proto, 0);
if (!tfile)
return -ENOMEM;
if (ptr_ring_init(&tfile->tx_ring, 0, GFP_KERNEL)) {
sk_free(&tfile->sk);
return -ENOMEM;
}
mutex_init(&tfile->napi_mutex);
/* #define RCU_INIT_POINTER(p, v) do { (p) = (v); } while (0) */
RCU_INIT_POINTER(tfile->tun, NULL);
tfile->flags = 0;
tfile->ifindex = 0;
/* 初始化socket等待队列头 */
init_waitqueue_head(&tfile->socket.wq.wait);
/* socket回指file */
tfile->socket.file = file;
/* 一组socket操作集 */
tfile->socket.ops = &tun_socket_ops;
/* 初始化sk中字段 */
sock_init_data(&tfile->socket, &tfile->sk);
/* sk_write_space功能检查是否有缓存可写 */
tfile->sk.sk_write_space = tun_sock_write_space;
tfile->sk.sk_sndbuf = INT_MAX;
/* tfile作为file的私有数据 */
file->private_data = tfile;
INIT_LIST_HEAD(&tfile->next);
sock_set_flag(&tfile->sk, SOCK_ZEROCOPY);
return 0;
}
tun_socket_ops结构体使用模拟原始sockets操作
/* Ops structure to mimic raw sockets with tun */
static const struct proto_ops tun_socket_ops = {
.peek_len = tun_peek_len,
.sendmsg = tun_sendmsg,
.recvmsg = tun_recvmsg,
};
sock_init_data定义在 <net/core/sock.c>
参考这里:sock_init_data注释
/*\linux-5.10.x\net\core\sock.c
用于初始化套接字数据结构中的各个字段和锁,以及设置套接字的默认参数。具体功能如下:
1、初始化套接字的读写锁和对等锁
2、设置套接字的对等进程ID和对等进程凭证为NULL,并初始化对等锁
3、设置套接字的待发送数据长度为0,接收缓冲区低水位标记为1,接收超时和发送超时为最大超时值
4、设置套接字的时间戳为默认时间戳值
5、如果BITS_PER_LONG的值是32,初始化套接字的时间戳序列锁
6、设置套接字的零拷贝密钥为0
7、如果配置了网络接收忙碌轮询功能,设置套接字的NAPI ID为0,并从系统变量中读取并设置套接字的忙碌轮询时间
8、设置套接字的最大调度速率和调度速率为最大的无符号长整型值,调度速率的位移为10。将套接字的入站CPU设置为-1
9、清除套接字的接收队列,删除所有已接收但尚未处理的数据包
*/
void sock_init_data(struct socket *sock, struct sock *sk)
{
sk_init_common(sk); //初始化通用的sock数据结构
sk->sk_send_head = NULL; //将发送队列头部指针置为空
timer_setup(&sk->sk_timer, NULL, 0); //设置定时器,使用空回调函数和初始超时值0
sk->sk_allocation = GFP_KERNEL; //设置当前socket的内存分配标志为内核级别的分配
sk->sk_rcvbuf = READ_ONCE(sysctl_rmem_default); //读取系统变量sysctl_rmem_default的值作为接收缓冲区大小
sk->sk_sndbuf = READ_ONCE(sysctl_wmem_default); //读取系统变量sysctl_wmem_default的值作为发送缓冲区大小
sk->sk_state = TCP_CLOSE; //设置socket状态为TCP_CLOSE,表示关闭状态
sk_set_socket(sk, sock); //将sock与sk关联起来
sock_set_flag(sk, SOCK_ZAPPED); //设置SOCK_ZAPPED标志位,表示socket被重置
if (sock) { //如果sock非空
sk->sk_type = sock->type; //将socket类型设置为sock的类型
RCU_INIT_POINTER(sk->sk_wq, &sock->wq); //将socket的等待队列指针赋给sk的等待队列指针
sock->sk = sk; //将sk赋给socket的sk字段
sk->sk_uid = SOCK_INODE(sock)->i_uid; //获取socket所属文件描述符的用户ID,并赋给sk的用户ID
} else { //如果sock为空
RCU_INIT_POINTER(sk->sk_wq, NULL); //初始化sk的等待队列指针为空
sk->sk_uid = make_kuid(sock_net(sk)->user_ns, 0); //将套接字的用户ID设置为make_kuid创建的内核用户ID(root用户ID)
}
rwlock_init(&sk->sk_callback_lock); //对套接字的sk_callback_lock字段进行读写锁的初始化
if (sk->sk_kern_sock) //套接字是否为内核套接字(kern_sock)。kern_sock是一种特殊类型的套接字,它在内核空间中使用,与用户空间无关
lockdep_set_class_and_name( //lockdep_set_class_and_name用于设置锁的类和名称。是内核中的一个调试工具,用于跟踪锁的使用情况
&sk->sk_callback_lock, //要设置的锁对象
af_kern_callback_keys + sk->sk_family,
af_family_kern_clock_key_strings[sk->sk_family]); //将使用af_kern_callback_keys数组中与套接字家族(sk_family)相对应的元素作为锁类
else //如果套接字不是内核套接字
lockdep_set_class_and_name(
&sk->sk_callback_lock,
af_callback_keys + sk->sk_family,
af_family_clock_key_strings[sk->sk_family]); //将使用af_callback_keys数组中与套接字家族(sk_family)相对应的元素作为锁类
sk->sk_state_change = sock_def_wakeup; //套接字状态改变的回调函数,当套接字状态发生改变时,将调用此函数
sk->sk_data_ready = sock_def_readable; //套接字数据就绪的回调函数,当套接字有数据可读时,将调用此函数
sk->sk_write_space = sock_def_write_space; //套接字写空间可用的回调函数,当套接字的写空间可用时,将调用此函数
sk->sk_error_report = sock_def_error_report; //套接字错误报告的回调函数,当套接字发生错误时,将调用此函数
sk->sk_destruct = sock_def_destruct; //套接字析构的回调函数,当套接字被销毁时,将调用此函数
/* 以下字段用于在接收数据时进行分片处理 */
sk->sk_frag.page = NULL; //套接字的分段信息初始化为NULL
sk->sk_frag.offset = 0; //偏移量为0
sk->sk_peek_off = -1; //查看偏移量设置为-1
/* 以下字段和锁用于处理套接字的对等进程信息 */
sk->sk_peer_pid = NULL; //对等进程ID字段
sk->sk_peer_cred = NULL; //对等进程凭证字段
spin_lock_init(&sk->sk_peer_lock); //对对等锁sk_peer_lock进行自旋锁的初始化
/* 以下字段用于控制套接字的发送和接收行为 */
sk->sk_write_pending = 0; //套接字的待发送数据长度字段
sk->sk_rcvlowat = 1; //接收缓冲区低水位标记,表示最小可接收数据量
sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; //接收超时字段设置为最大超时值(MAX_SCHEDULE_TIMEOUT)
sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; //发送超时字段设置为最大超时值(MAX_SCHEDULE_TIMEOUT)
sk->sk_stamp = SK_DEFAULT_STAMP; //套接字的时间戳设置为默认时间戳值。用于记录套接字的最后一次状态改变的时间
#if BITS_PER_LONG==32 // 如果每个长整型数占32位
seqlock_init(&sk->sk_stamp_seq); // 初始化套接字时间戳序列锁
#endif
atomic_set(&sk->sk_zckey, 0); // 设置套接字的零拷贝密钥为0
#ifdef CONFIG_NET_RX_BUSY_POLL
sk->sk_napi_id = 0; // 网络适配器处理标识符初始化为0
sk->sk_ll_usec = READ_ONCE(sysctl_net_busy_read); // 读取网络繁忙忙等待时长
#endif
/* 以下字段用于控制数据包的调度和处理 */
sk->sk_max_pacing_rate = ~0UL; // 设置套接字最大传输速率为最大值
sk->sk_pacing_rate = ~0UL; // 设置套接字传输速率为最大值
WRITE_ONCE(sk->sk_pacing_shift, 10); // 设置套接字传输速率控制参数为10
sk->sk_incoming_cpu = -1; // 设置套接字的所属CPU为-1,表示未分配CPU
sk_rx_queue_clear(sk); // 清空套接字的接收队列,这将删除所有已接收但尚未处理的数据包
/*
* Before updating sk_refcnt, we must commit prior changes to memory
* (Documentation/RCU/rculist_nulls.rst for details)
*/
smp_wmb(); // 在多核系统中确保先前的写操作对于其他CPU可见
refcount_set(&sk->sk_refcnt, 1); // 设置套接字引用计数为1,表示有一个引用
atomic_set(&sk->sk_drops, 0); // 将套接字丢弃数据包计数器重置为0
}
tun_sock_write_space
static void tun_sock_write_space(struct sock *sk)
{
struct tun_file *tfile;
wait_queue_head_t *wqueue;
if (!sock_writeable(sk))
return;
if (!test_and_clear_bit(SOCKWQ_ASYNC_NOSPACE, &sk->sk_socket->flags))
return;
wqueue = sk_sleep(sk);
if (wqueue && waitqueue_active(wqueue))
wake_up_interruptible_sync_poll(wqueue, EPOLLOUT |
EPOLLWRNORM | EPOLLWRBAND);
tfile = container_of(sk, struct tun_file, sk);
/* 发送信号给tfile->fasync,即tun_chr_fasync */
kill_fasync(&tfile->fasync, SIGIO, POLL_OUT);
}
tun_chr_fasync
static int tun_chr_fasync(int fd, struct file *file, int on)
{
struct tun_file *tfile = file->private_data;
int ret;
if ((ret = fasync_helper(fd, file, on, &tfile->fasync)) < 0)
goto out;
if (on) {
__f_setown(file, task_pid(current), PIDTYPE_TGID, 0);
tfile->flags |= TUN_FASYNC;
} else
tfile->flags &= ~TUN_FASYNC;
ret = 0;
out:
return ret;
}