searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Linux tun网卡驱动原理介绍

2023-09-01 05:45:41
109
0

数据结构

tun模块数据结构

模块加载

static int __init tun_init(void)
{
       ret = rtnl_link_register(&tun_link_ops);
       ret = misc_register(&tun_miscdev);
       ret = register_netdevice_notifier(&tun_notifier_block);
}

 

模块初始化时,主要注册一些操作函数和接口。

1. tun_link_ops

static struct rtnl_link_ops tun_link_ops __read_mostly = {
	.kind		= DRV_NAME,
	.priv_size	= sizeof(struct tun_struct),
	.setup		= tun_setup,
	.validate	= tun_validate,
	.get_size       = tun_get_size,
	.fill_info      = tun_fill_info,
};

tun_setup(struct net_device *dev),该函数在创建tun设备时,用来初始化设备。

2. tun_miscdev

static struct miscdevice tun_miscdev = {
	.minor = TUN_MINOR,
	.name = "tun",
	.nodename = "net/tun",
	.fops = &tun_fops,
};
static const struct file_operations tun_fops = {
	.owner	= THIS_MODULE,
	.llseek = no_llseek,
	.read_iter  = tun_chr_read_iter,
	.write_iter = tun_chr_write_iter,
	.poll	= tun_chr_poll,
	.unlocked_ioctl	= tun_chr_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = tun_chr_compat_ioctl,
#endif
	.open	= tun_chr_open,
	.release = tun_chr_close,
	.fasync = tun_chr_fasync,
#ifdef CONFIG_PROC_FS
	.show_fdinfo = tun_chr_show_fdinfo,
#endif
};

3. tun_notifier_block

static struct notifier_block tun_notifier_block __read_mostly = {
	.notifier_call	= tun_device_event,
};

向netdev_chain通知链中注册该接口,订阅以下事件:

enum netdev_cmd {
       NETDEV_UP  = 1,
       NETDEV_DOWN,
       NETDEV_REBOOT,
       NETDEV_CHANGE,
       NETDEV_REGISTER,
       NETDEV_UNREGISTER,
       NETDEV_CHANGEMTU,
       NETDEV_CHANGEADDR,
       NETDEV_GOING_DOWN,
       NETDEV_CHANGENAME,
       …
}

网络子系统初始化

在网络模块初始化的时候,注册网络数据软中断的执行函数

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

这里的tx和rx是相对于内核协议栈来说的:

NET_TX_SOFTIRQ:发送数据到用户空间

NET_RX_SOFTIRQ:从用户空间接收数据

 

net_tx_action:

qdisc_run,循环发送报文
    qdisc_restart,
      dequeue_skb,从缓存区中得到待发送的skb
        sch_direct_xmit,
          xmit_one,
              netdev_start_xmit
	          ops->ndo_start_xmit,tun_net_xmit将skb加入socket的sk_receive_queue中
static const struct net_device_ops tun_netdev_ops = {
	.ndo_uninit		= tun_net_uninit,
	.ndo_open		= tun_net_open,
	.ndo_stop		= tun_net_close,
	.ndo_start_xmit		= tun_net_xmit,
	.ndo_fix_features	= tun_net_fix_features,
	.ndo_select_queue	= tun_select_queue,
	.ndo_set_rx_headroom	= tun_set_headroom,
	.ndo_get_stats64	= tun_net_get_stats64,
};

 

net_rx_action:

napi_poll调用napi_struct的poll函数获取数据,poll函数注册的是tun_napi_poll,该函数轮询从socket的sk_write_queue中取出skb,传入协议栈。

tun_napi_poll
    tun_napi_receive
        napi_gro_receive
            napi_skb_finish
                netif_receive_skb_internal,将skb数据包送进协议栈。

虚拟网卡创建

1. 打开字符设备/dev/net/tun,获得文件描述符;

open操作执行tun_fops的 open函数(tun_chr_open)。该函数在内核中创建一个tun协议的socket,将该socket与打开的文件描述符做关联。tun_socket_ops为该socket的操作函数集,包含发送和接收接口:

/* 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,
};

2. 执行ioctl,传入命令字TUNSETIFF,该命令字的处理函数是tun_set_iff:

(1) alloc_netdev_mqs,如果执行ioctl的时候传入了IFF_MULTI_QUEUE,则调用该函数创建网卡设备,分配net_device和其private data(tun_struct)的内存,调用tun_setup初始化tun_struct。

(2) tun_net_init,初始化net_device

(3) tun_attach,设备上报

如果执行ioctl时传入了IFF_NAPI参数,则TUN驱动将创建napi_struct结构体,将注册一个NAPI的poll处理函数tun_napi_poll,该poll函数用于轮询地从缓冲区获取数据传入内核协议栈,即从tun设备文件描述符的socket的sk_write_queue中取出skb,传入协议栈。

(4) register_netdevice,向内核注册新创建的net_device,广播NETDEV_REGISTER事件。

从用户空间获取数据包

tun_get_user,从用户空间缓冲区读取数据packet,封装skb

如果使能了napi,则将一定数量的skb放入sock的sk_write_queue队列中,执行napi_schedule,该函数会触发软中断NET_RX_SOFTIRQ,中断处理函数取出skb发到协议栈,参考【网络子系统初始化】部分。

发送数据包到用户空间

tun_put_user将一定数量的skb放到缓冲区,触发软中断NET_TX_SOFTIRQ,参考【网络子系统初始化】部分。

数据流向

0条评论
0 / 1000
周朋肖
3文章数
0粉丝数
周朋肖
3 文章 | 0 粉丝
周朋肖
3文章数
0粉丝数
周朋肖
3 文章 | 0 粉丝
原创

Linux tun网卡驱动原理介绍

2023-09-01 05:45:41
109
0

数据结构

tun模块数据结构

模块加载

static int __init tun_init(void)
{
       ret = rtnl_link_register(&tun_link_ops);
       ret = misc_register(&tun_miscdev);
       ret = register_netdevice_notifier(&tun_notifier_block);
}

 

模块初始化时,主要注册一些操作函数和接口。

1. tun_link_ops

static struct rtnl_link_ops tun_link_ops __read_mostly = {
	.kind		= DRV_NAME,
	.priv_size	= sizeof(struct tun_struct),
	.setup		= tun_setup,
	.validate	= tun_validate,
	.get_size       = tun_get_size,
	.fill_info      = tun_fill_info,
};

tun_setup(struct net_device *dev),该函数在创建tun设备时,用来初始化设备。

2. tun_miscdev

static struct miscdevice tun_miscdev = {
	.minor = TUN_MINOR,
	.name = "tun",
	.nodename = "net/tun",
	.fops = &tun_fops,
};
static const struct file_operations tun_fops = {
	.owner	= THIS_MODULE,
	.llseek = no_llseek,
	.read_iter  = tun_chr_read_iter,
	.write_iter = tun_chr_write_iter,
	.poll	= tun_chr_poll,
	.unlocked_ioctl	= tun_chr_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = tun_chr_compat_ioctl,
#endif
	.open	= tun_chr_open,
	.release = tun_chr_close,
	.fasync = tun_chr_fasync,
#ifdef CONFIG_PROC_FS
	.show_fdinfo = tun_chr_show_fdinfo,
#endif
};

3. tun_notifier_block

static struct notifier_block tun_notifier_block __read_mostly = {
	.notifier_call	= tun_device_event,
};

向netdev_chain通知链中注册该接口,订阅以下事件:

enum netdev_cmd {
       NETDEV_UP  = 1,
       NETDEV_DOWN,
       NETDEV_REBOOT,
       NETDEV_CHANGE,
       NETDEV_REGISTER,
       NETDEV_UNREGISTER,
       NETDEV_CHANGEMTU,
       NETDEV_CHANGEADDR,
       NETDEV_GOING_DOWN,
       NETDEV_CHANGENAME,
       …
}

网络子系统初始化

在网络模块初始化的时候,注册网络数据软中断的执行函数

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

这里的tx和rx是相对于内核协议栈来说的:

NET_TX_SOFTIRQ:发送数据到用户空间

NET_RX_SOFTIRQ:从用户空间接收数据

 

net_tx_action:

qdisc_run,循环发送报文
    qdisc_restart,
      dequeue_skb,从缓存区中得到待发送的skb
        sch_direct_xmit,
          xmit_one,
              netdev_start_xmit
	          ops->ndo_start_xmit,tun_net_xmit将skb加入socket的sk_receive_queue中
static const struct net_device_ops tun_netdev_ops = {
	.ndo_uninit		= tun_net_uninit,
	.ndo_open		= tun_net_open,
	.ndo_stop		= tun_net_close,
	.ndo_start_xmit		= tun_net_xmit,
	.ndo_fix_features	= tun_net_fix_features,
	.ndo_select_queue	= tun_select_queue,
	.ndo_set_rx_headroom	= tun_set_headroom,
	.ndo_get_stats64	= tun_net_get_stats64,
};

 

net_rx_action:

napi_poll调用napi_struct的poll函数获取数据,poll函数注册的是tun_napi_poll,该函数轮询从socket的sk_write_queue中取出skb,传入协议栈。

tun_napi_poll
    tun_napi_receive
        napi_gro_receive
            napi_skb_finish
                netif_receive_skb_internal,将skb数据包送进协议栈。

虚拟网卡创建

1. 打开字符设备/dev/net/tun,获得文件描述符;

open操作执行tun_fops的 open函数(tun_chr_open)。该函数在内核中创建一个tun协议的socket,将该socket与打开的文件描述符做关联。tun_socket_ops为该socket的操作函数集,包含发送和接收接口:

/* 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,
};

2. 执行ioctl,传入命令字TUNSETIFF,该命令字的处理函数是tun_set_iff:

(1) alloc_netdev_mqs,如果执行ioctl的时候传入了IFF_MULTI_QUEUE,则调用该函数创建网卡设备,分配net_device和其private data(tun_struct)的内存,调用tun_setup初始化tun_struct。

(2) tun_net_init,初始化net_device

(3) tun_attach,设备上报

如果执行ioctl时传入了IFF_NAPI参数,则TUN驱动将创建napi_struct结构体,将注册一个NAPI的poll处理函数tun_napi_poll,该poll函数用于轮询地从缓冲区获取数据传入内核协议栈,即从tun设备文件描述符的socket的sk_write_queue中取出skb,传入协议栈。

(4) register_netdevice,向内核注册新创建的net_device,广播NETDEV_REGISTER事件。

从用户空间获取数据包

tun_get_user,从用户空间缓冲区读取数据packet,封装skb

如果使能了napi,则将一定数量的skb放入sock的sk_write_queue队列中,执行napi_schedule,该函数会触发软中断NET_RX_SOFTIRQ,中断处理函数取出skb发到协议栈,参考【网络子系统初始化】部分。

发送数据包到用户空间

tun_put_user将一定数量的skb放到缓冲区,触发软中断NET_TX_SOFTIRQ,参考【网络子系统初始化】部分。

数据流向

文章来自个人专栏
虚拟化杂谈
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0