Linux核心中VLAN的實作過程(3)
本節主要關注和解析vlan子產品proc檔案系統接口功能實作,代碼位于
net/8021q/vlanproc.c
檔案中。
接口原型
// 在proc檔案系統中建立vlan條目(/proc/net/vlan)
int vlan_proc_init(struct net *net);
// 移除vlan裝置節點
void vlan_proc_rem_dev(struct net_device *vlandev);
// 添加vlan裝置節點
int vlan_proc_add_dev(struct net_device *vlandev);
// 從proc檔案系統中删除vlan條目(/proc/net/vlan)
void vlan_proc_cleanup(struct net *net);
建立vlan條目
root@penyforever-pc:~# vconfig add docker0 1
root@penyforever-pc:~# vconfig add enp2s0 1
root@penyforever-pc:~# cd /proc/net/vlan
root@penyforever-pc:/proc/net/vlan# ls
config docker0.1 enp2s0.1
root@penyforever-pc:/proc/net/vlan# cat config
VLAN Dev name | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
enp2s0.1 | 1 | enp2s0
docker0.1 | 1 | docker0
root@penyforever-pc:/proc/net/vlan# cat docker0.1
docker0.1 VID: 1 REORDER_HDR: 1 dev->priv_flags: 1001
total frames received 0
total bytes received 0
Broadcast/Multicast Rcvd 0
total frames transmitted 0
total bytes transmitted 0
Device: docker0
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
root@penyforever-pc:/proc/net/vlan# cat enp2s0.1
enp2s0.1 VID: 1 REORDER_HDR: 1 dev->priv_flags: 1021
total frames received 0
total bytes received 0
Broadcast/Multicast Rcvd 0
total frames transmitted 0
total bytes transmitted 0
Device: enp2s0
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
/*
* Create /proc/net/vlan entries
*/
int __net_init vlan_proc_init(struct net *net)
{
// 根據vlan命名空間id擷取vlan proc檔案系統資訊
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 在/proc/net/下建立vlan目錄
vn->proc_vlan_dir = proc_net_mkdir(net, name_root, net->proc_net);
if (!vn->proc_vlan_dir)
goto err;
// 在/proc/net/vlan/下建立config檔案,并設定檔案權限和檔案(seq fie)操作函數
vn->proc_vlan_conf = proc_create_net(name_conf, S_IFREG | 0600,
vn->proc_vlan_dir, &vlan_seq_ops,
sizeof(struct seq_net_private));
if (!vn->proc_vlan_conf)
goto err;
return 0;
err:
pr_err("can't create entry in proc filesystem!\n");
從proc檔案系統中删除vlan條目(/proc/net/vlan)
vlan_proc_cleanup(net);
return -ENOBUFS;
}
删除vlan條目
/*
* Clean up /proc/net/vlan entries
*/
void vlan_proc_cleanup(struct net *net)
{
// 根據vlan命名空間id擷取vlan proc檔案系統資訊
struct vlan_net *vn = net_generic(net, vlan_net_id);
// 删除config檔案
if (vn->proc_vlan_conf)
remove_proc_entry(name_conf, vn->proc_vlan_dir);
// 删除vlan目錄
if (vn->proc_vlan_dir)
remove_proc_entry(name_root, net->proc_net);
/* Dynamically added entries should be cleaned up as their vlan_device
* is removed, so we should not have to take care of it here...
*/
}
添加裝置節點
/*
* Add directory entry for VLAN device.
*/
int vlan_proc_add_dev(struct net_device *vlandev)
{
// 擷取vlan裝置私有資訊
struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
// 根據vlan命名空間id擷取vlan proc檔案系統資訊
struct vlan_net *vn = net_generic(dev_net(vlandev), vlan_net_id);
// 校驗,vlan裝置名字不能是“config”
if (!strcmp(vlandev->name, name_conf))
return -EINVAL;
// 在/proc/vlan/下建立裝置節點檔案,用來儲存vlan裝置資訊,并設定檔案權限和檔案(seq file)操作函數
vlan->dent = proc_create_single_data(vlandev->name, S_IFREG | 0600,
vn->proc_vlan_dir, vlandev_seq_show, vlandev);
if (!vlan->dent)
return -ENOBUFS;
return 0;
}
移除裝置節點
/*
* Delete directory entry for VLAN device.
*/
void vlan_proc_rem_dev(struct net_device *vlandev)
{
// 從/proc/net/vlan/移除vlan裝置資訊檔案
/** NOTE: This will consume the memory pointed to by dent, it seems. */
proc_remove(vlan_dev_priv(vlandev)->dent);
// vlan裝置私有資訊置為空
vlan_dev_priv(vlandev)->dent = NULL;
}
seq檔案操作函數
/****** Proc filesystem entry points ****************************************/
/*
* The following few functions build the content of /proc/net/vlan/config
*/
// 主要實作初始化工作,在周遊一個連結對象開始時調用,傳回一個連結對象的偏移或者SEQ_START_TOKEN(表征這是所有循環的開始)
/* start read of /proc/net/vlan/config */
static void *vlan_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(rcu)
{
struct net_device *dev;
struct net *net = seq_file_net(seq);
loff_t i = 1;
// 加rcu讀鎖
rcu_read_lock();
if (*pos == 0)
return SEQ_START_TOKEN;
// 周遊網絡命名空間下的所有裝置,找到偏移pos指向的vlan裝置
for_each_netdev_rcu(net, dev) {
if (!is_vlan_dev(dev))
continue;
if (i++ == *pos)
return dev;
}
return NULL;
}
// 用來在周遊中尋找下一個連結對象,傳回下一個連結對象或者NULL(周遊結束)
static void *vlan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct net_device *dev;
struct net *net = seq_file_net(seq);
++*pos;
// 找到下一個vlan裝置
dev = v;
if (v == SEQ_START_TOKEN)
dev = net_device_entry(&net->dev_base_head);
for_each_netdev_continue_rcu(net, dev) {
if (!is_vlan_dev(dev))
continue;
return dev;
}
return NULL;
}
// 當所有連結對象周遊結束時調用,主要完成一些清理工作
static void vlan_seq_stop(struct seq_file *seq, void *v)
__releases(rcu)
{
// 釋放rcu讀鎖
rcu_read_unlock();
}
// 對周遊對象進行檢視操作的函數,主要是調用seq_printf(), seq_puts()之類的函數,列印出這個對象節點的資訊
static int vlandev_seq_show(struct seq_file *seq, void *offset)
{
// 擷取vlan裝置資訊
struct net_device *vlandev = (struct net_device *) seq->private;
// 擷取vlan裝置私有資訊
const struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
struct rtnl_link_stats64 temp;
const struct rtnl_link_stats64 *stats;
static const char fmt64[] = "%30s %12llu\n";
int i;
if (!is_vlan_dev(vlandev))
return 0;
// 擷取vlan裝置接口統計資訊
stats = dev_get_stats(vlandev, &temp);
// 輸出裝置裝置名稱,vlan id,網絡裝置接口的辨別符(其狀态類型被定義在<linux/if.h>之中),網絡裝置接口的辨別符(但對使用者空間不可見)
seq_printf(seq,
"%s VID: %d REORDER_HDR: %i dev->priv_flags: %llx\n",
vlandev->name, vlan->vlan_id,
(int)(vlan->flags & 1), vlandev->priv_flags);
// 輸出接口收到的資料包個數
seq_printf(seq, fmt64, "total frames received", stats->rx_packets);
// 輸出接口收到的位元組數
seq_printf(seq, fmt64, "total bytes received", stats->rx_bytes);
// 輸出接口收到的廣播包群組播包個數
seq_printf(seq, fmt64, "Broadcast/Multicast Rcvd", stats->multicast);
seq_puts(seq, "\n");
// 輸出接口發送的資料包個數
seq_printf(seq, fmt64, "total frames transmitted", stats->tx_packets);
// 輸出接口發送的位元組數
seq_printf(seq, fmt64, "total bytes transmitted", stats->tx_bytes);
// 輸出vlan裝置宿主裝置名稱
seq_printf(seq, "Device: %s", vlan->real_dev->name);
// 輸出入口vlan優先級資訊
/* now show all PRIORITY mappings relating to this VLAN */
seq_printf(seq, "\nINGRESS priority mappings: "
"0:%u 1:%u 2:%u 3:%u 4:%u 5:%u 6:%u 7:%u\n",
vlan->ingress_priority_map[0],
vlan->ingress_priority_map[1],
vlan->ingress_priority_map[2],
vlan->ingress_priority_map[3],
vlan->ingress_priority_map[4],
vlan->ingress_priority_map[5],
vlan->ingress_priority_map[6],
vlan->ingress_priority_map[7]);
// 輸出出口vlan優先級資訊
seq_printf(seq, " EGRESS priority mappings: ");
for (i = 0; i < 16; i++) {
const struct vlan_priority_tci_mapping *mp
= vlan->egress_priority_map[i];
while (mp) {
seq_printf(seq, "%u:%d ",
mp->priority, ((mp->vlan_qos >> 13) & 0x7));
mp = mp->next;
}
}
seq_puts(seq, "\n");
return 0;
}