Linux内核——loopback驱动

跟着简叔学的,可以B站搜索 简说linux

loopback设备网卡设备

本地环回接口(或地址),亦称回送地址(loopback address)。真实的网卡设备工作在数据链路层,负责不同网卡设备之间的信号传输和转化。如果ifconfig查看,ens代表着真实的网卡设备,lo代表loopback网卡设备。该网卡设备并不是真实存在的,它存在的意义是什么呢。

1678235466381

lo网卡设将发送队列和接收队列接在了一起,因此上层应用给lo发送了什么,它就给你返回什么。通过这种特殊结构可测试协议栈是否正常工作,当使用命令,数据从应用层转到协议栈最后到lo设备,如果说协议栈功能是ok的,那么数据包从lo发出去后就应该收到正确的回应。

1
ping 127.0.0.1 -c l

1678235993619

那么基于lo这种机制,还可以实现不同进程之间的通信,这是一种网络通信。通过不同端口号就可以进行不同进程的通信,就和正常的socket通信一样。

loopback驱动源码

文件路径:/drivers/net/loopback.c

1.入口函数

总的来看,也还是初始化一个device,把自定义的函数挂载到device的函数指针上。然后把这个device拿去注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

/* Setup and register the loopback device. */
static __net_init int loopback_net_init(struct net *net)
{
struct net_device *dev; //定义一个指针变量 指向网络设备结构体struct net_device
int err;

err = -ENOMEM;
//初始化 net_device
/*
alloc_netdev 同样在net/core/dev.c中定义 在这里面申请net_device的空间 并且进行一系列初始化
第四个参数是一个函数指针,具体放在下面,也是相当于对dev做进一步的初始化
因此这个函数结束时候呢相当于给函数指针挂在了我们自定义的实现
*/
dev = alloc_netdev(0, "lo", NET_NAME_UNKNOWN, loopback_setup);
if (!dev)
goto out;

dev_net_set(dev, net); //注册net dev
err = register_netdev(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;
}

/* Registered in net/core/dev.c */
//定义一个结构体,该结构体是在 net/core/dev.c 里面去注册的
struct pernet_operations __net_initdata loopback_net_ops = {
.init = loopback_net_init,
};

/*
* The loopback device is special. There is only one instance
* per network namespace.
*/
static void loopback_setup(struct net_device *dev)
{
dev->mtu = 64 * 1024; //最大数据包长度 64K 但是一般真实网络设备受限于底层设备的传输能力,但是lo没有这种显示
dev->hard_header_len = ETH_HLEN; /* 14 数据链路层头部长度 */
dev->min_header_len = ETH_HLEN; /* 14 */
dev->addr_len = ETH_ALEN; /* 以太网地址 6个字节 */
dev->type = ARPHRD_LOOPBACK; /* 0x0001*/
dev->flags = IFF_LOOPBACK;
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE;
netif_keep_dst(dev);
dev->hw_features = NETIF_F_GSO_SOFTWARE;
dev->features = NETIF_F_SG | NETIF_F_FRAGLIST
| NETIF_F_GSO_SOFTWARE
| NETIF_F_HW_CSUM
| NETIF_F_RXCSUM
| NETIF_F_SCTP_CRC
| NETIF_F_HIGHDMA
| NETIF_F_LLTX
| NETIF_F_NETNS_LOCAL
| NETIF_F_VLAN_CHALLENGED
| NETIF_F_LOOPBACK;
//针对操作进行一些赋值
dev->ethtool_ops = &loopback_ethtool_ops; //用来支持ethtool的 ethtool可以查询和配置一些网卡信息
dev->header_ops = &eth_header_ops;
dev->netdev_ops = &loopback_ops;
dev->destructor = loopback_dev_free; //析构
}

针对上面的:dev->netdev_ops = &loopback_ops;,就是网络设备固定的一些操作,net_device_ops包含的操作实际上非常多,这里面就实现了4个。如果没有这一步的话,就会使用系统提供的默认的实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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, //设置mac地址
};

//像是统计信息的初始化 dev->lstats是统计cpu发送接收的统计
static int loopback_dev_init(struct net_device *dev)
{
dev->lstats = netdev_alloc_pcpu_stats(struct pcpu_lstats);
if (!dev->lstats)
return -ENOMEM;
return 0;
}
// 这个最后是被网络层发送 软中断来调用的 协议栈调用发送函数会调用这个发送数据
//第一个参数就包含了应用层所有发送的数据
static 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);

//netif_rx()是协议栈提供的函数 网卡驱动可以通过这个函数把数据分包提交给协议栈
//loopback的发送与接收倒是在一个函数实现 没接收到一个报就又转交给协议栈了
//所以判断语句内 是直接加了状态统计信息
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;
}
//返回统计信息
static struct rtnl_link_stats64 *loopback_get_stats64(struct net_device *dev,
struct rtnl_link_stats64 *stats)
{
u64 bytes = 0;
u64 packets = 0;
int i;
//统计所有cpu的首发数据的统计信息
for_each_possible_cpu(i) {
const struct pcpu_lstats *lb_stats;
u64 tbytes, tpackets;
unsigned int start;

lb_stats = per_cpu_ptr(dev->lstats, i);
do {
start = u64_stats_fetch_begin_irq(&lb_stats->syncp);
tbytes = lb_stats->bytes;
tpackets = lb_stats->packets;
} while (u64_stats_fetch_retry_irq(&lb_stats->syncp, start));
bytes += tbytes;
packets += tpackets;
}
//status保存的所有cpu的
stats->rx_packets = packets;
stats->tx_packets = packets;
stats->rx_bytes = bytes;
stats->tx_bytes = bytes;
return stats;
}

主要就是定义了几个callback会在调用alloc_netdev时候注册进来。当上层的应用程序发包的时候协议栈里面的tx_softirq会去处理这些信息,然后调用ndo_start_xmit= loopback_xmit进行数据分包。另外还有统计功能。