我们知道,在 Linux 设备中有一个 lo 设备,在此稍微介绍下。
简介
对于 loopback 设备,我们可以直接通过 ifconfig 查看 lo 设备的配置。
$ ifconfig lo
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 0 (Local Loopback)
RX packets 442824 bytes 208834219 (199.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 442824 bytes 208834219 (199.1 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
如上,可以看出 lo 的 mask 仅为 8bit,也就是说,只要 127 开头,任何网络主机号都可以,也就是说 lo 接口可以理解为一个网络号。
可以 ping 该网段中的 IP 地址,例如 127.0.0.1、127.255.255.200 等。
另外常用的是,通过 ifconfig 设置一个浮动 IP 地址,该地址在 127 网段,从而可以在本地使用非 127.1 进行测试,可以通过如下命令设置。
# ifconfig lo:1 127.168.1.1 netmask 255.0.0.0
然后,像 MySQL 就可以绑定该 IP 即可。
源码解析
在内核初始化的时候,会调用 net_dev_init()
函数,其中通过 register_pernet_device()
注册 lo 设备。
/* Registered in net/core/dev.c */
struct pernet_operations __net_initdata loopback_net_ops = {
.init = loopback_net_init,
};
static int __init net_dev_init(void)
{
... ...
if (register_pernet_device(&loopback_net_ops))
goto out;
... ...
}
subsys_initcall(net_dev_init);
在进行注册的过程中会调用 loopback_net_init()
做初始化操作,也就是 loopback NIC 的驱动程序。
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
static __net_init int loopback_net_init(struct net *net)
{
... ...
/* 申请一个net_device实例,并进行初始化 */
dev = alloc_netdev(0, "lo", NET_NAME_UNKNOWN, loopback_setup);
if (!dev)
goto out;
dev_net_set(dev, net);
err = register_netdev(dev); /* 注册loopback NIC设备 */
if (err)
goto out_free_netdev;
BUG_ON(dev->ifindex != LOOPBACK_IFINDEX);
net->loopback_dev = dev;
return 0;
... ...
}
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,
};
static void loopback_setup(struct net_device *dev)
{
... ...
dev->netdev_ops = &loopback_ops;
... ...
}
在调用 alloc_netdev()
初始化设备时,该函数同时会初始化接收和发送队列,该函数在初始化完数据结构之后,同时会调用回调函数 loopback_setup()
,该函数会初始化一些与 lo 设备相关的参数。
其中,最重要的是 dev->netdev_ops
,其中发消息的方法就是调用 loopback_xmit()
函数即可,该函数的声明如下。
netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev);
其中,skb 是待发送的数据缓冲区,dev 是网络设备的一个指针。lo 设备是要把数据报文发给本机,所以其发送数据报文的函数比较特殊,它把 skb 稍加处理后,又转回给协议栈的数据报接收函数 netif_rx()
。
netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct pcpu_lstats *lb_stats;
int len;
skb_orphan(skb);
/* Before queueing this packet to netif_rx(),
* make sure dst is refcounted.
*/
skb_dst_force(skb);
skb->protocol = eth_type_trans(skb, dev);
/* it's OK to use per_cpu_ptr() because BHs are off */
lb_stats = this_cpu_ptr(dev->lstats);
len = skb->len;
if (likely(netif_rx(skb) == NET_RX_SUCCESS)) { // 调用该函数发送
u64_stats_update_begin(&lb_stats->syncp);
lb_stats->bytes += len;
lb_stats->packets++;
u64_stats_update_end(&lb_stats->syncp);
}
return NETDEV_TX_OK;
}
首先会调用 skb_orphan()
把 skb 孤立,使它跟发送 socket 和协议栈不再有任何联系,也即对本机来说,这个 skb 的数据内容已经发送出去了,而 skb 相当于已经被释放掉了。