Linux网络解读(4) - 数据包的发送接收之设备层

简介: 数据包的发送接收之设备层

arp

上节看到了数据包到达IP层后,进入链路层。需要获取这个数据包的MAC地址。这个地址有邻居系统arp协议提供。

arp机制

Linux内核中的arp高速缓存是通过:rtable + dst_entry + hh_cache 实现的。

arp协议的功能是为了维护 IP ----> MAC 的映射关系。

rarp协议的功能为了查询 MAC ----> IP的映射。

arp的请求应答都是广播。

rarp的请求是广播,应该是单播,由rarp服务器响应。

先看一下arp的初始化:

void __init arp_init(void)
{
        neigh_table_init(&arp_tbl);
        dev_add_pack(&arp_packet_type);
        arp_proc_init();
#ifdef CONFIG_SYSCTL
        neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,
                              NET_IPV4_NEIGH, "ipv4", NULL);
#endif
        register_netdevice_notifier(&arp_netdev_notifier);
}

arp_tbl是一个类型为neigh_table的全局指针,也称为邻居表。

struct neigh_table
{
        struct neigh_table        *next;
        int                        family;
        // 一个邻居表项的实际大小,因为neighbour的尾端是一个primary_key[0]
        int                        entry_size;
        int                        key_len;
        // 一个neighbour的hash值计算函数
        __u32                        (*hash)(const void *pkey, const struct net_device *);
        int                        (*constructor)(struct neighbour *);
        int                        (*pconstructor)(struct pneigh_entry *);
        void                        (*pdestructor)(struct pneigh_entry *);
        void                        (*proxy_redo)(struct sk_buff *skb);
        char                        *id;
        // 这是一个链表,系统每个网络接口的设备对应parms的一个节点,表示该设备对应的邻居的一些参数,
        // 比如该接口的arp刷新频率,允许不同的接口有不同的频率。
        struct neigh_parms        parms;
        int                        gc_interval;
        int                        gc_thresh1;
        int                        gc_thresh2;
        int                        gc_thresh3;
        unsigned long                last_flush;
        struct timer_list         gc_timer;
        struct timer_list         proxy_timer;
        struct sk_buff_head        proxy_queue;
        // 邻居的总数
        atomic_t                entries;
        rwlock_t                lock;
        unsigned long                last_rand;
        struct neigh_parms        *parms_list;
        kmem_cache_t                *kmem_cachep;
        struct neigh_statistics        *stats;
        // hash表
        struct neighbour        **hash_buckets;
        unsigned int                hash_mask;
        __u32                        hash_rnd;
        unsigned int                hash_chain_gc;
        struct pneigh_entry        **phash_buckets;
#ifdef CONFIG_PROC_FS
        struct proc_dir_entry        *pde;
#endif
};

neigh_table,neighbour,net_device之间的关系如下

image.png

arp响应报文的处理是在arp_process():

static int arp_process(struct sk_buff *skb)
{
        struct net_device *dev = skb->dev;
        struct in_device *in_dev = in_dev_get(dev);
        unsigned char *arp_ptr;
        unsigned char *sha, *tha;
        u32 sip, tip;
        u16 dev_type = dev->type;
        int addr_type;
        struct neighbour *n;
        arp = skb->nh.arph;
        n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
        neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
}

注意:在neigh_resolve_output中会先在neigb_table中插入一条neighbour表项,然后发送arp请求。

所以,__neigh_lookup会返回一个没有被arp更新的表项,然后调用neigh_update更新。

image.png

设备驱动层

在IP层把arp的协助下把mac地址填上去后,就走完了最后一层协议。

在ip_finish_output2中判断dst->neighbour->hh非空,直接把dst_entry->hh->hh_data拷贝成以太网的首部,hh->output发送出去了。

hh->output实际指向的是dev_queue_xmit,这个函数是设备无关的,IP层或其他底层需要发送报文,都要调用这个函数。

int dev_queue_xmit(struct sk_buff *skb)
{
        struct net_device *dev = skb->dev;
        struct Qdisc *q;
        int rc = -ENOMEM;
        // 关闭中断,并且停止抢占
        local_bh_disable(); 
        // 流量控制(也是需要重点研究的对象)
        q = rcu_dereference(dev->qdisc);
        if (q->enqueue) {
                spin_lock(&dev->queue_lock);
                rc = q->enqueue(skb, q);
                qdisc_run(dev);
                spin_unlock(&dev->queue_lock);
                rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;
                goto out;
        }
        // loopback等 soft devices直接通过hard_start_xmit发送
        if (dev->flags & IFF_UP) {
                int cpu = smp_processor_id(); /* ok because BHs are off */
                if (dev->xmit_lock_owner != cpu) {
                        HARD_TX_LOCK(dev, cpu);
                        if (!netif_queue_stopped(dev)) {
                                if (netdev_nit)
                                        dev_queue_xmit_nit(skb, dev);
                                rc = 0;
                                if (!dev->hard_start_xmit(skb, dev)) {
                                        HARD_TX_UNLOCK(dev);
                                        goto out;
                                }
                        }
                        HARD_TX_UNLOCK(dev);
                        if (net_ratelimit())
                                printk(KERN_CRIT "Virtual device %s asks to "
                                       "queue packet!\n", dev->name);
                } else {
                        if (net_ratelimit())
                                printk(KERN_CRIT "Dead loop on virtual device "
                                       "%s, fix it urgently!\n", dev->name);
                }
        }
        local_bh_enable();
}

looback的hard_start_xmit指向了loopback_xmit,下面进入loopback_xmit函数

static int loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{
        struct net_device_stats *lb_stats;
        skb_orphan(skb);
        skb->protocol=eth_type_trans(skb,dev);
        skb->dev=dev;
        if (skb_shinfo(skb)->tso_size) {
                BUG_ON(skb->protocol != htons(ETH_P_IP));
                BUG_ON(skb->nh.iph->protocol != IPPROTO_TCP);
                emulate_large_send_offload(skb);
                return 0;
        }
        dev->last_rx = jiffies;
        lb_stats = &per_cpu(loopback_stats, get_cpu());
        lb_stats->rx_bytes += skb->len;
        lb_stats->tx_bytes += skb->len;
        lb_stats->rx_packets++;
        lb_stats->tx_packets++;
        put_cpu();
        netif_rx(skb);
        return(0);
}

对于loopback设备,在它的hard_start_xmit函数中,直接把skb通过netif_rx接收过去了。

从中断到网络层

终于到了收数据包的流程了。

报文接收路径:

PKT Arrive INT --> Driver --> 0) alloc_skb; 1) netif_rx --> RX_SOFTIRQ --> net_rx_action软中断处理函数 (dev->poll) --> process_backlog --> netif_receive_skb
![4_18](https://img.alicdn.com/L1/461/1/62df474ba6bdd22f47c8a5dc97784504aba815a7.png)
注意软中断产生的位置

硬中断 netif_rx

在硬件中断中分配完skb之后,就要把这个skb挂载在一个queue上,然后mark软中断。

对于有backlog_dev的设备来说,netif_rx发生在硬件中断中,这个函数的任务是把新接收进来的skb挂载到queue->input_pkt_queue中,然后触发软中断。

这个函数再终端服务程序中调用。

int netif_rx(struct sk_buff *skb)
{
        int this_cpu;
        struct softnet_data *queue;
        unsigned long flags;
        if (!skb->stamp.tv_sec)
                net_timestamp(&skb->stamp);
        local_irq_save(flags);
        this_cpu = smp_processor_id();
        queue = &__get_cpu_var(softnet_data);
        __get_cpu_var(netdev_rx_stat).total++;
        // 队列的长度还没有超长,注意此处是一个丢包点
        if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
                // 下面这几行代码很精妙
                // queue->input_pkt_queue.qlen非0,说明接收队列input_pkt_queue还有数据包没有被及时处理,那么,本次接收进来的数据包,就没有必要触发软中断,只需要把这个数据包加入到队列中即可。
                if (queue->input_pkt_queue.qlen) {
                        // 如果发送队列非空,并且此时触发了throttle阀值,则丢弃当前这个包
                        if (queue->throttle)
                                goto drop;
enqueue:
                        // 无需触发软中断,直接加入队列,并且返回。
                        dev_hold(skb->dev);
                        __skb_queue_tail(&queue->input_pkt_queue, skb);
                        local_irq_restore(flags);
                        return queue->cng_level;
                }
                if (queue->throttle)
                        queue->throttle = 0;
                // 如果队列已经空了,则触发一次软中断,然后跳转到enqueue,very good!!!
                netif_rx_schedule(&queue->backlog_dev);
                goto enqueue;
        }
drop:
        // 在ISR中丢包的过程超级简单,直接不予理睬,把dropped更新即可。
        __get_cpu_var(netdev_rx_stat).dropped++;
        local_irq_restore(flags);
        kfree_skb(skb);
        return NET_RX_DROP;
}

这个函数执行完,skb就进入了内核,接下来等待软中断的处理。

软中断 net_rx_action

static void net_rx_action(struct softirq_action *h)
{
        // softnet_data是数据包的停靠的地方,这个结构每个CPU都会有一个。
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;
        int budget = netdev_max_backlog;
        local_irq_disable();
        // 遍历poll_list
        while (!list_empty(&queue->poll_list)) {
                struct net_device *dev;
                if (budget <= 0 || jiffies - start_time > 1)
                        goto softnet_break;
                local_irq_enable();
                dev = list_entry(queue->poll_list.next,
                                 struct net_device, poll_list);
                // dev->poll,对于backlog_dev的poll是process_backlog
                // 可以看出:软中断的服务程序中仅仅对softnet_data中的poll_list依次调用dev->poll方法
                // 对于有数据包要处理的net_device调用其poll()方法,至于如何处理这个net_device上数据包,则有具体的net_device决定
                // 这样比较灵活。
                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                        local_irq_disable();
                        list_del(&dev->poll_list);
                        list_add_tail(&dev->poll_list, &queue->poll_list);
                        if (dev->quota < 0)
                                dev->quota += dev->weight;
                        else
                                dev->quota = dev->weight;
                } else {
                        dev_put(dev);
                        local_irq_disable();
                }
        }
out:
        local_irq_enable();
        return;
softnet_break:
        __get_cpu_var(netdev_rx_stat).time_squeeze++;
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        goto out;
}

image.png

在ping本地的时候,looback_xmit中会通过netif_rx(skb)直接把这个skb插入到softnet_data中,并标记软中断。此时的skb的dev是dev_backlog而它的poll函数是process_backlog

最终会进入netif_receive_skb。

net_rx_action() --> process_backlog --> netif_receive_skb()

先看process_backlog

static int process_backlog(struct net_device *backlog_dev, int *budget)
{
        int work = 0;
        int quota = min(backlog_dev->quota, *budget);
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;
        // 在softnet_dat中的backlog_dev的poll函数中,会处理这个设备上所有的数据包。
        for (;;) {
                struct sk_buff *skb;
                struct net_device *dev;
                local_irq_disable();
                // 取出skb
                skb = __skb_dequeue(&queue->input_pkt_queue);
                if (!skb)
                        goto job_done;
                local_irq_enable();
                dev = skb->dev;
                // 开始解析数据包
                netif_receive_skb(skb);
                dev_put(dev);
                work++;
                if (work >= quota || jiffies - start_time > 1)
                        break;
        }
        backlog_dev->quota -= work;
        *budget -= work;
        return -1;
job_done:
        backlog_dev->quota -= work;
        *budget -= work;
        list_del(&backlog_dev->poll_list);
        smp_mb__before_clear_bit();
        netif_poll_enable(backlog_dev);
        if (queue->throttle)
                queue->throttle = 0;
        local_irq_enable();
        return 0;
}

netif_receive_skb开始解析数据包

int netif_receive_skb(struct sk_buff *skb)
{
        struct packet_type *ptype, *pt_prev;
        int ret = NET_RX_DROP;
        unsigned short type;
        if (!skb->stamp.tv_sec)
                net_timestamp(&skb->stamp);
        skb_bond(skb);
        __get_cpu_var(netdev_rx_stat).total++;
        skb->h.raw = skb->nh.raw = skb->data;
        skb->mac_len = skb->nh.raw - skb->mac.raw;
        pt_prev = NULL;
        rcu_read_lock();
        // 遍历 ptype_all,把数据包通过deliver_skb提交给上层
        // tcpdump就是通过创建af_packet套接字,向ptype_all注册一个packet_type的处理函数实现把数据包递交用户层
        list_for_each_entry_rcu(ptype, &ptype_all, list) {
                if (!ptype->dev || ptype->dev == skb->dev) {
                        if (pt_prev) 
                                ret = deliver_skb(skb, pt_prev);
                        pt_prev = ptype;
                }
        }
        handle_diverter(skb);
        // Linux网桥的入口
        if (handle_bridge(&skb, &pt_prev, &ret))
                goto out;
        type = skb->protocol;
        // 通过type取得ptype_base,
        // 判断type是否一致
        // 目前ptype_base有:arp_recv, rarp_recv, ip_recv
        list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
                if (ptype->type == type &&
                    (!ptype->dev || ptype->dev == skb->dev)) {
                        if (pt_prev) 
                                ret = deliver_skb(skb, pt_prev);
                        pt_prev = ptype;
                }
        }
        if (pt_prev) {
                ret = pt_prev->func(skb, skb->dev, pt_prev);
        } else {
                kfree_skb(skb);
                ret = NET_RX_DROP;
        }
out:
        rcu_read_unlock();
        return ret;
}

这个函数的目标是数据包级别的处理。根据数据帧中的type,提交给对应的处理函数。

image.png

对于IP报文pt_prev->func为ip_rcv,然后进入ip_rcv_finish

static inline int ip_rcv_finish(struct sk_buff *skb)
{
        struct net_device *dev = skb->dev;
        struct iphdr *iph = skb->nh.iph;
        // 如果skb->dst为空,说明这个数据包的目的地址还没设置
        // ip_route_input在路由系统中找到数据包的路由项,以此来确认如何处理这个数据包,是转发还是本地分发
        if (skb->dst == NULL) {
                if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))
                        goto drop; 
        }
        if (iph->ihl > 5) {
                struct ip_options *opt;
                if (skb_cow(skb, skb_headroom(skb))) {
                        IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
                        goto drop;
                }
                iph = skb->nh.iph;
                if (ip_options_compile(NULL, skb))
                        goto inhdr_error;
                opt = &(IPCB(skb)->opt);
                if (opt->srr) {
                        struct in_device *in_dev = in_dev_get(dev);
                        if (in_dev) {
                                if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
                                        if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
                                                printk(KERN_INFO "source route option %u.%u.%u.%u -> %u.%u.%u.%u\n",
                                                       NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
                                        in_dev_put(in_dev);
                                        goto drop;
                                }
                                in_dev_put(in_dev);
                        }
                        if (ip_options_rcv_srr(skb))
                                goto drop;
                }
        }
        return dst_input(skb);
inhdr_error:
        IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
        kfree_skb(skb);
        return NET_RX_DROP;
}

在ip_route_input中,会找到一个dst_entry,并设置input的函数指针是ip_local_deliver

image.png

int ip_local_deliver(struct sk_buff *skb)
{
        if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
                skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
                if (!skb)
                        return 0;
        }
        return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
                       ip_local_deliver_finish);
}
static inline int ip_local_deliver_finish(struct sk_buff *skb)
{
        int ihl = skb->nh.iph->ihl*4;
        __skb_pull(skb, ihl);
        nf_reset(skb);
        skb->h.raw = skb->data;
        rcu_read_lock();
        {
                int protocol = skb->nh.iph->protocol;
                int hash;
                struct sock *raw_sk;
                struct net_protocol *ipprot;
        resubmit:
                hash = protocol & (MAX_INET_PROTOS - 1);
                raw_sk = sk_head(&raw_v4_htable[hash]);
                // 对每个报文都要先检查是否是raw报文。
                if (raw_sk)
                        raw_v4_input(skb, skb->nh.iph, hash);
                // skb->nh.iph->protocol根据IP报文头部,直接找到相应的协议!
                // 对应的handler分别为:tcp_v4_rcv,icmp_rcv,udp_rcv,igmp_rcv
                if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
                        int ret;
                        if (!ipprot->no_policy &&
                            !xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                                kfree_skb(skb);
                                goto out;
                        }
                        ret = ipprot->handler(skb);
                        if (ret < 0) {
                                protocol = -ret;
                                goto resubmit;
                        }
                        IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
                } else {
                        if (!raw_sk) {
                                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                                        IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
                                        icmp_send(skb, ICMP_DEST_UNREACH,
                                                  ICMP_PROT_UNREACH, 0);
                                }
                        } else
                                IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
                        kfree_skb(skb);
                }
        }
 out:
        rcu_read_unlock();
        return 0;
}

总结一下;一个skb从软中断上来的时候,必须根据mac头部的type从ptype_base[]中找到相应的处理函数,目前内核中有3个和IP相关ip_packet_type,arp_packet_type,rarp_packet_type。

如果是IP报文,在ip_local_deliver_finish时,从inet_protos[]找到对应的函数:icmp, udp, tcp。

前者的回调是func(),处理的是packet;

后者的回调是handler(),处理的是协议。

到目前为止skb都是在内核空间中的,并没有和哪个套接字关联起来。下面的函数通过protocol, saddr, daddr, ifindex查找这个数据包所属于的socket,并把skb递交给sock上的skb_receive_queue。

void raw_v4_input(struct sk_buff *skb, struct iphdr *iph, int hash)
{
        struct sock *sk;
        struct hlist_head *head;
        read_lock(&raw_v4_lock);
        head = &raw_v4_htable[hash];
        if (hlist_empty(head))
                goto out;
        // 通过protocol, saddr, daddr, ifindex查找这个数据包所属于的socket !!!        
        sk = __raw_v4_lookup(__sk_head(head), iph->protocol,
                             iph->saddr, iph->daddr,
                             skb->dev->ifindex);
        while (sk) {
                if (iph->protocol != IPPROTO_ICMP || !icmp_filter(sk, skb)) {
                        struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
                        if (clone)
                                raw_rcv(sk, clone);
                }
                sk = __raw_v4_lookup(sk_next(sk), iph->protocol,
                                     iph->saddr, iph->daddr,
                                     skb->dev->ifindex);
        }
out:
        read_unlock(&raw_v4_lock);
}
相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
10天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
10天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
10天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
18天前
|
Linux
Linux中centos桌面消失网络图标
Linux中centos桌面消失网络图标
13 0
|
8天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
24 6
|
10天前
|
网络协议 Linux
在Linux中,管理和配置网络接口
在Linux中管理网络接口涉及多个命令,如`ifconfig`(在新版本中被`ip`取代)、`ip`(用于网络设备配置)、`nmcli`(NetworkManager的CLI工具)、`nmtui`(文本界面配置)、`route/ip route`(处理路由表)、`netstat/ss`(显示网络状态)和`hostnamectl/systemctl`(主机名和服务管理)。这些命令帮助用户启动接口、设置IP地址、查看连接和路由信息。不同发行版可能有差异,建议参考相应文档。
19 4
|
10天前
|
边缘计算 网络虚拟化 虚拟化
虚拟网络设备性能优化
在现代网络架构中,虚拟网络设备扮演着越来越重要的角色🌐,特别是在云计算☁️和容器化技术📦广泛应用的背景下。虚拟网络设备如虚拟以太网设备(veth)、虚拟交换机(vSwitch)、和虚拟路由器等,提供了灵活的网络连接和隔离方案🔗。然而,与物理网络设备相比,虚拟网络设备在处理能力💪、带宽利用率📈和延迟⏳方面可能存在性能瓶颈。因此,性能优化成为了虚拟网络设备管理中的一个重要议题🛠️。本文将探讨虚拟网络设备的性能优化手段,帮助网络管理员更有效地利用这些设备。
|
10天前
|
安全 网络安全 网络虚拟化
虚拟网络设备与网络安全:深入分析与实践应用
在数字化时代📲,网络安全🔒成为了企业和个人防御体系中不可或缺的一部分。随着网络攻击的日益复杂和频繁🔥,传统的物理网络安全措施已经无法满足快速发展的需求。虚拟网络设备🖧,作为网络架构中的重要组成部分,通过提供灵活的配置和强大的隔离能力🛡️,为网络安全提供了新的保障。本文将从多个维度深入分析虚拟网络设备是如何保障网络安全的,以及它们的实际意义和应用场景。
|
10天前
|
网络协议 Linux SDN
虚拟网络设备与Linux网络协议栈
在现代计算环境中,虚拟网络设备在实现灵活的网络配置和隔离方面发挥了至关重要的作用🔧,特别是在容器化和虚拟化技术广泛应用的今天🌐。而Linux网络协议栈则是操作系统处理网络通信的核心💻,它支持广泛的协议和网络服务🌍,确保数据正确地在网络中传输。本文将深入分析虚拟网络设备与Linux网络协议栈的关联,揭示它们如何共同工作以支持复杂的网络需求。
|
11天前
|
存储 缓存 固态存储
Linux设备全览:从字符到块,揭秘每种设备的秘密
在Linux的世界里,设备是构成系统的基础,它们使得计算机能够与外界互动。Linux设备可以大致分为几种类型,每种类型都有其独特的特性和用途。🌌让我们一起探索这些设备类型及其特性。