本文主要是学习了解一下整个Linux网络中loopback的原理,代码基于5.17版本。
在Linux启动的过程中,会初始化网络子系统从而来完成网卡上面的网络包的接受和发送。
/*
* Initialize the DEV module. At boot time this walks the device list and
* unhooks any devices that fail to initialise (normally hardware not
* present) and leaves us with a valid list of present and active devices.
*
*/
/*
* This is called single threaded during boot, so no need
* to take the rtnl semaphore.
*/
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
if (dev_proc_init())
goto out;
if (netdev_kobject_init())
goto out;
INIT_LIST_HEAD(&ptype_all);
for (i = 0; i < PTYPE_HASH_SIZE; i++) // 注册十六个类型的队列
INIT_LIST_HEAD(&ptype_base[i]);
if (register_pernet_subsys(&netdev_net_ops))
goto out;
/*
* Initialise the packet receive queues.
*/
for_each_possible_cpu(i) { // 给每个cpu初始化包的接受队列
struct work_struct *flush = per_cpu_ptr(&flush_works, i);
struct softnet_data *sd = &per_cpu(softnet_data, i);
INIT_WORK(flush, flush_backlog);
skb_queue_head_init(&sd->input_pkt_queue);
skb_queue_head_init(&sd->process_queue);
#ifdef CONFIG_XFRM_OFFLOAD
skb_queue_head_init(&sd->xfrm_backlog);
#endif
INIT_LIST_HEAD(&sd->poll_list);
sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
INIT_CSD(&sd->csd, rps_trigger_softirq, sd);
sd->cpu = i;
#endif
init_gro_hash(&sd->backlog);
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
}
dev_boot_phase = 0;
/* The loopback device is special if any other network devices
* is present in a network namespace the loopback device must
* be present. Since we now dynamically allocate and free the
* loopback device ensure this invariant is maintained by
* keeping the loopback device as the first device on the
* list of network devices. Ensuring the loopback devices
* is the first device that appears and the last network device
* that disappears.
*/
if (register_pernet_device(&loopback_net_ops)) // loopback网络设备必须注册一个
goto out;
if (register_pernet_device(&default_device_ops)) // 注册网络的操作
goto out;
open_softirq(NET_TX_SOFTIRQ, net_tx_action); // 注册网络发送的回调函数 软中端
open_softirq(NET_RX_SOFTIRQ, net_rx_action); // 注册网络接受的回调函数 软中断
rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
NULL, dev_cpu_dead);
WARN_ON(rc < 0);
rc = 0;
out:
return rc;
}
subsys_initcall(net_dev_init);
Linux操作系统通过在启动的过程中其他中断softirq线程来响应在网卡接受或者发送的数据。
static struct smp_hotplug_thread softirq_threads = {
.store = &ksoftirqd,
.thread_should_run = ksoftirqd_should_run,
.thread_fn = run_ksoftirqd,
.thread_comm = "ksoftirqd/%u",
};
static __init int spawn_ksoftirqd(void)
{
cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
takeover_tasklets);
BUG_ON(smpboot_register_percpu_thread(&softirq_threads)); // 注册处理线程
return 0;
}
early_initcall(spawn_ksoftirqd);
通过注册run_ksoftirqd来执行中断注册的回调函数。
static void run_ksoftirqd(unsigned int cpu)
{
ksoftirqd_run_begin();
if (local_softirq_pending()) {
/*
* We can safely run softirq on inline stack, as we are not deep
* in the task stack here.
*/
__do_softirq(); // 执行注册的中断号对应的回调函数
ksoftirqd_run_end();
cond_resched();
return;
}
ksoftirqd_run_end();
}
此时,整个网络数据的收发的主要的流程就是如上,Linux在启动的时候开启软中断softtirqd线程,当有网络数据操作的时候该线程就会调用net_tx_action或net_rx_action来处理网卡数据。
在网络子系统的初始化过程中,在不同的命名空间中一定会注册一个loopback_net_ops,即回环设备。回环设备的主要逻辑如下。
register_pernet_device(&loopback_net_ops)
该函数最终在经历一系列检查和设备队列的添加之后,就会调用loopback_net_ops的init方法即loopback_net_init。
/* Setup and register the loopback device. */
static __net_init int loopback_net_init(struct net *net)
{
struct net_device *dev;
int err;
err = -ENOMEM;
dev = alloc_netdev(0, "lo", NET_NAME_UNKNOWN, loopback_setup); //获取一个dev并命名为lo 并通过loopback_setup来初始化
if (!dev)
goto out;
dev_net_set(dev, net);
err = register_netdev(dev); // 注册dev 在检查过方法名称和特性之后注册到dev列表中
if (err)
goto out_free_netdev;
BUG_ON(dev->ifindex != LOOPBACK_IFINDEX);
net->loopback_dev = dev;
return 0;
out_free_netdev:
free_netdev(dev);
out:
if (net_eq(net, &init_net))
panic("loopback: Failed to register netdevice: %d\n", err);
return err;
}
在创建lo的过程中,通过loopback_setup初始化函数,在创建的过程中就将对应网络的传输的流程赋值好了。
static const struct net_device_ops loopback_ops = {
.ndo_init = loopback_dev_init,
.ndo_start_xmit = loopback_xmit,
.ndo_get_stats64 = loopback_get_stats64,
.ndo_set_mac_address = eth_mac_addr,
};
...
/* The loopback device is special. There is only one instance
* per network namespace.
*/
static void loopback_setup(struct net_device *dev)
{
gen_lo_setup(dev, (64 * 1024), &loopback_ethtool_ops, ð_header_ops,
&loopback_ops, loopback_dev_free);
}
此时当传入lo的数据包的时候,就会将数据发送。
/* The higher levels take care of making this non-reentrant (it's
* called with bh's disabled).
*/
static netdev_tx_t loopback_xmit(struct sk_buff *skb,
struct net_device *dev)
{
int len;
skb_tx_timestamp(skb);
/* do not fool net_timestamp_check() with various clock bases */
skb->tstamp = 0;
skb_orphan(skb); // 将目标端地址置为空 从而在后续接受处理的过程中按照本地lo来进行执行 并且设置loopback_dev为空
/* Before queueing this packet to netif_rx(),
* make sure dst is refcounted.
*/
skb_dst_force(skb); // 远端的引用计数减一
skb->protocol = eth_type_trans(skb, dev); // 获取协议
len = skb->len;
if (likely(netif_rx(skb) == NET_RX_SUCCESS)) // 直接调用netif_rx函数
dev_lstats_add(dev, len);
return NETDEV_TX_OK;
}
至此,loopback回环设备就简单的将数据通过netif_rx放入到了接受的队列中,从而唤醒在等待的进程来获取数据。
本文只是很简单的分析了一下有关linux的loopback设备的原理,原理相对简单即在接受到数据之后然后直接放入到待接受数据的队列中将数据转出,从而完成loopback本地回环的流程。后续将进一步学习了解Linux网络的整个收发包的流程与细节。
http://vger.kernel.org/~davem/skb_sk.html
https://blog.csdn.net/frank_jb/article/details/115841909
https://blog.csdn.net/hzj_001/article/details/104327771
https://toutiao.io/posts/fizyuy/preview
https://blog.csdn.net/frank_jb/article/details/115841622
https://blog.csdn.net/yangguosb/article/details/103562983
https://zhuanlan.zhihu.com/p/256428917
http://m.blog.chinaunix.net/uid-25518484-id-5765715.html
https://www.halolinux.us/kernel-architecture/registering-network-devices.html
https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch17.html