分类
linux virtual nic 未分类

macvlan 虚拟网卡

macvlan 虚拟网卡设备
macvlan虚拟网卡设备时寄生在物理网卡设备上的。发包时调用自己的发包函数,查找到寄生的物理设备,然后通过物理设备发包。收包时,通过注册寄生的物理设备的rx_handler回调函数,处理数据包。
macvlan 虚拟网卡设备包括5种模式
private 模式:在这种模式下,macvlan设备不能接受寄生在同一个物理网卡的其他macvlan设备的数据包,即使是其他macvlan设备通过物理网卡发送出去并通过hairpin设备返回的包
vepa 模式:在这种模式下,macvlan设备不能直接接受寄生在同一个物理网卡的其他macvlan设备的数据包,但是其他macvlan设备可以将数据包通过物理网卡发送出去,然后通过hairpin设备返回的给其他macvlan设备
passthru 模式:在这种模式下,每一个物理设备只能寄生一个macvlan设备
bridge 模式:在这种模式下,寄生在同一个物理设备的macvlan设备可以直接通讯,不需要外接的hairpin设备帮助
source 模式: 在这种模式下,寄生在物理设备的这类macvlan设备,只能接受指定的源 mac source的数据包,其他数据包都不接受。

macvlan设备 关键数据结构
macvlan_port ,这个数据时在注册rx_handler时使用,作为回调函数的参数。

struct macvlan_port {
        struct net_device       *dev;                           //物理设备
        struct hlist_head       vlan_hash[MACVLAN_HASH_SIZE];   //macvlan设备私有数据
        struct list_head        vlans;                          //macvlan设备私有数据
        struct sk_buff_head     bc_queue;                       //广播报文队列
        struct work_struct      bc_work;                        //发送广播报文进程
        u32                     flags;                          //标志
        int                     count;                          //macvlan设备数量
        struct hlist_head       vlan_source_hash[MACVLAN_HASH_SIZE];    //mac vlan source类型设备专用
        DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
        unsigned char           perm_addr[ETH_ALEN];
};

macvlan_dev ,这个数据结构,时macvlan网卡的私有数据结构,每创建一个macvlan设备就会创建一个设备,并将这个数据结构挂在 macvlan_port 数据结构上。

struct macvlan_dev {
        struct net_device       *dev;           //macvlan网卡设备
        struct list_head        list;           //寄生的macvlan链表
        struct hlist_node       hlist;          //寄生的macvlanhash表
        struct macvlan_port     *port;          //macvlan_port
        struct net_device       *lowerdev;      //寄生的物理设备
        void                    *fwd_priv;      //如果物理网卡支持可以硬件加速
        struct vlan_pcpu_stats __percpu *pcpu_stats;

        DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);

        netdev_features_t       set_features;
        enum macvlan_mode       mode;
        u16                     flags;
        /* This array tracks active taps. */
        struct tap_queue        __rcu *taps[MAX_TAP_QUEUES];
        /* This list tracks all taps (both enabled and disabled) */
        struct list_head        queue_list;
        int                     numvtaps;
        int                     numqueues;
        netdev_features_t       tap_features;
        int                     minor;
        int                     nest_level;
#ifdef CONFIG_NET_POLL_CONTROLLER
        struct netpoll          *netpoll;
#endif
        unsigned int            macaddr_count;
};

两个私有结构之间的关系

digraph tun{
    node [shape = plaintext]
    rankdir = LR
    macvlan_port[label=<
            <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
            <TR><TD BORDER="0" ALIGN="CENTER">struct macvlan_port</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f0">struct net_device *dev</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f1">struct hlist_head vlan_hash[MACVLAN_HASH_SIZE]</TD></TR>
            <TR><TD ALIGN="CENTER">struct list_head vlans</TD></TR>
            <TR><TD ALIGN="LEFT">struct sk_buff_head bc_queue</TD></TR>
            <TR><TD ALIGN="CENTER">struct work_struct bc_work</TD></TR>
            <TR><TD ALIGN="LEFT">u32 flags</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f2">struct hlist_head vlan_source_hash[MACVLAN_HASH_SIZE]</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            </TABLE>>]
    macvlan_dev[label=<
            <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
            <TR><TD BORDER="0" ALIGN="CENTER">struct macvlan_dev</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f0">strut net_device *dev</TD></TR>
            <TR><TD ALIGN="LEFT">struct list_head list</TD></TR>
            <TR><TD ALIGN="LEFT">struct hlist_node hlist</TD></TR>
            <TR><TD ALIGN="LEFT">struct macvlan_port *port</TD></TR>
            <TR><TD ALIGN="LEFT">struct net_device *lowerdev</TD></TR>
            <TR><TD ALIGN="LEFT">void *fwd_priv</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            </TABLE>>]
    macvlan_port:f1 -> macvlan_dev:f0
    macvlan_port:f2 -> macvlan_dev:f0
}

模块注册

static int __init macvlan_init_module(void)
{
        int err;

        register_netdevice_notifier(&macvlan_notifier_block);   //注册网卡通知

        err = macvlan_link_register(&macvlan_link_ops);     //注册netlink方法
        if (err < 0)
                goto err1;
        return 0;
err1:
        unregister_netdevice_notifier(&macvlan_notifier_block);
        return err;
}

创建macvlan设备

int macvlan_common_newlink(struct net *src_net, struct net_device *dev,
                           struct nlattr *tb[], struct nlattr *data[])
{
        struct macvlan_dev *vlan = netdev_priv(dev);
        struct macvlan_port *port;
        struct net_device *lowerdev;
        int err;
        int macmode;
        bool create = false;

        if (!tb[IFLA_LINK])
                return -EINVAL;

        lowerdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
        if (lowerdev == NULL)
                return -ENODEV;

        /* When creating macvlans or macvtaps on top of other macvlans - use
         * the real device as the lowerdev.
         */
        if (netif_is_macvlan(lowerdev))
                lowerdev = macvlan_dev_real_dev(lowerdev);  //获取寄生的物理设备

        if (!tb[IFLA_MTU])
                dev->mtu = lowerdev->mtu;
        else if (dev->mtu > lowerdev->mtu)
                return -EINVAL;

        /* MTU range: 68 - lowerdev->max_mtu */
        dev->min_mtu = ETH_MIN_MTU;
        dev->max_mtu = lowerdev->max_mtu;

        if (!tb[IFLA_ADDRESS])
                eth_hw_addr_random(dev);

        if (!macvlan_port_exists(lowerdev)) {
                err = macvlan_port_create(lowerdev);    //如果物理设备还有寄生的macvlan设备创建macvlan_port结构,并注册rx_handler
                if (err < 0)
                        return err;
                create = true;
        }
        port = macvlan_port_get_rtnl(lowerdev);

        /* Only 1 macvlan device can be created in passthru mode */
        if (macvlan_passthru(port)) {   //判断是否有 passthru类型macvlan,如果有退出
                /* The macvlan port must be not created this time,
                 * still goto destroy_macvlan_port for readability.
                 */
                err = -EINVAL;
                goto destroy_macvlan_port;
        }

        vlan->lowerdev = lowerdev;
        vlan->dev      = dev;
        vlan->port     = port;
        vlan->set_features = MACVLAN_FEATURES;
        vlan->nest_level = dev_get_nest_level(lowerdev) + 1;

        vlan->mode     = MACVLAN_MODE_VEPA;
        if (data && data[IFLA_MACVLAN_MODE])
                vlan->mode = nla_get_u32(data[IFLA_MACVLAN_MODE]);

        if (data && data[IFLA_MACVLAN_FLAGS])
                vlan->flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);

        if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
                if (port->count) {          //如果新的macvlan设备时prassthru类型,且被寄生的物理设备上已经存在macvlan设备,直接退出失败
                        err = -EINVAL;
                        goto destroy_macvlan_port;
                }
                macvlan_set_passthru(port);
                eth_hw_addr_inherit(dev, lowerdev);
        }

        if (data && data[IFLA_MACVLAN_MACADDR_MODE]) {
                if (vlan->mode != MACVLAN_MODE_SOURCE) {
                        err = -EINVAL;
                        goto destroy_macvlan_port;
                }
                macmode = nla_get_u32(data[IFLA_MACVLAN_MACADDR_MODE]);
                err = macvlan_changelink_sources(vlan, macmode, data);  //如果设备时 source类型的设备,添加想收的源mac地址,其他地址。这种类型的macvlan设备只收指定源mac包
                if (err)
                        goto destroy_macvlan_port;
        }

        err = register_netdevice(dev);
        if (err < 0)
                goto destroy_macvlan_port;

        dev->priv_flags |= IFF_MACVLAN;
        err = netdev_upper_dev_link(lowerdev, dev);
        if (err)
                goto unregister_netdev;

        list_add_tail_rcu(&vlan->list, &port->vlans);   //将macvlan设备添加到列表中
        netif_stacked_transfer_operstate(lowerdev, dev);
        linkwatch_fire_event(dev);

        return 0;

unregister_netdev:
        unregister_netdevice(dev);
destroy_macvlan_port:
        if (create)
                macvlan_port_destroy(port->dev);
        return err;
}

收包逻辑

static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
        struct macvlan_port *port;
        struct sk_buff *skb = *pskb;
        const struct ethhdr *eth = eth_hdr(skb);
        const struct macvlan_dev *vlan;
        const struct macvlan_dev *src;
        struct net_device *dev;
        unsigned int len = 0;
        int ret;
        rx_handler_result_t handle_res;

        port = macvlan_port_get_rcu(skb->dev);
        if (is_multicast_ether_addr(eth->h_dest)) { //广播报文处理逻辑
                unsigned int hash;

                skb = ip_check_defrag(dev_net(skb->dev), skb, IP_DEFRAG_MACVLAN);
                if (!skb)
                        return RX_HANDLER_CONSUMED;
                *pskb = skb;
                eth = eth_hdr(skb);
                macvlan_forward_source(skb, port, eth->h_source);//source 类型macvlan设备收包函数,
                src = macvlan_hash_lookup(port, eth->h_source);//查找发包设备是不是寄生在同一个物理网卡上的macvlan设备
                if (src && src->mode != MACVLAN_MODE_VEPA &&
                    src->mode != MACVLAN_MODE_BRIDGE) { //如果发包设备是寄生在同一个物理网卡上的macvlan设备,且设备类型是 source private passthru类型.则给自己发送广播报文
                        /* forward to original port. */
                        vlan = src;
                        ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?:
                              netif_rx(skb);
                        handle_res = RX_HANDLER_CONSUMED;
                        goto out;
                }

                hash = mc_hash(NULL, eth->h_dest);
                if (test_bit(hash, port->mc_filter))
                        macvlan_broadcast_enqueue(port, src, skb);  //添加到发送队列中,通过工作队列发送广播

                return RX_HANDLER_PASS; //给物理网卡上送广播报文
        }

        macvlan_forward_source(skb, port, eth->h_source); //单播报文source类型收包
        if (macvlan_passthru(port))
                vlan = list_first_or_null_rcu(&port->vlans,
                                              struct macvlan_dev, list);
        else
                vlan = macvlan_hash_lookup(port, eth->h_dest); //查找目的地址对应的macvlan设备
        if (vlan == NULL)
                return RX_HANDLER_PASS; //找不到,继续走物理网卡逻辑

        dev = vlan->dev;
        if (unlikely(!(dev->flags & IFF_UP))) {
                kfree_skb(skb);
                return RX_HANDLER_CONSUMED; //找到了网卡没启动,释放skb
        }
        len = skb->len + ETH_HLEN;
        skb = skb_share_check(skb, GFP_ATOMIC);
        if (!skb) {
                ret = NET_RX_DROP;
                handle_res = RX_HANDLER_CONSUMED;
                goto out;
        }

        *pskb = skb;
        skb->dev = dev;
        skb->pkt_type = PACKET_HOST; //找到了并且网卡没有问题,设置为本机

        ret = NET_RX_SUCCESS;
        handle_res = RX_HANDLER_ANOTHER;    //走macvlan 网卡逻辑
out:
        macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, false);
        return handle_res;
}

发送工作队列,工作函数

static void macvlan_process_broadcast(struct work_struct *w)
{       
        struct macvlan_port *port = container_of(w, struct macvlan_port,
                                                 bc_work);
        struct sk_buff *skb;
        struct sk_buff_head list;

        __skb_queue_head_init(&list);

        spin_lock_bh(&port->bc_queue.lock);
        skb_queue_splice_tail_init(&port->bc_queue, &list);
        spin_unlock_bh(&port->bc_queue.lock);

        while ((skb = __skb_dequeue(&list))) {
                const struct macvlan_dev *src = MACVLAN_SKB_CB(skb)->src;

                rcu_read_lock();

                if (!src) //如果不是寄生在同一个 物理设备上的macvlan发送,则所有macvlan都 收包
                        /* frame comes from an external address */
                        macvlan_broadcast(skb, port, NULL,
                                          MACVLAN_MODE_PRIVATE |
                                          MACVLAN_MODE_VEPA    |
                                          MACVLAN_MODE_PASSTHRU|
                                          MACVLAN_MODE_BRIDGE);
                else if (src->mode == MACVLAN_MODE_VEPA)//如果是寄生的同一个物理设备上的VEPA类型macvlan发送,则VEPA类型和BRIDGE类型macvlan都受到,private不收
                        /* flood to everyone except source */
                        macvlan_broadcast(skb, port, src->dev,
                                          MACVLAN_MODE_VEPA |
                                          MACVLAN_MODE_BRIDGE);
                else    //如果是寄生在同一个物理设备上的BRIDGE类型macvlan发送,则VEPA类型macvlan受到,private 和 bridge类型不收, bridge不收是因为在发送的时候已经上送给它了。
                        /*
                         * flood only to VEPA ports, bridge ports
                         * already saw the frame on the way out.
                         */
                        macvlan_broadcast(skb, port, src->dev,
                                          MACVLAN_MODE_VEPA);

                rcu_read_unlock();

                if (src)
                        dev_put(src->dev);
                kfree_skb(skb);
        }
}

通过控制广播报文的收发,macvlan保证寄生在同一个物理设备的private设备不能相互收到包。arp协议基于广播报文。

发包流程

static netdev_tx_t macvlan_start_xmit(struct sk_buff *skb,
                                      struct net_device *dev)
{                       
        unsigned int len = skb->len;
        int ret;
        struct macvlan_dev *vlan = netdev_priv(dev);

        if (unlikely(netpoll_tx_running(dev)))
                return macvlan_netpoll_send_skb(vlan, skb);

        if (vlan->fwd_priv) {
                skb->dev = vlan->lowerdev;
                ret = dev_queue_xmit_accel(skb, vlan->fwd_priv);//硬件加速发送
        } else {
                ret = macvlan_queue_xmit(skb, dev); //没有硬件加速发送方法
        }

        if (likely(ret == NET_XMIT_SUCCESS || ret == NET_XMIT_CN)) { //统计
                struct vlan_pcpu_stats *pcpu_stats;

                pcpu_stats = this_cpu_ptr(vlan->pcpu_stats);
                u64_stats_update_begin(&pcpu_stats->syncp);
                pcpu_stats->tx_packets++;
                pcpu_stats->tx_bytes += len;
                u64_stats_update_end(&pcpu_stats->syncp);
        } else {
                this_cpu_inc(vlan->pcpu_stats->tx_dropped);
        }
        return ret;
}

macvlan_queue_xmit函数

static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
        const struct macvlan_dev *vlan = netdev_priv(dev);
        const struct macvlan_port *port = vlan->port;
        const struct macvlan_dev *dest;

        if (vlan->mode == MACVLAN_MODE_BRIDGE) {    //macvlan如果是bridge模式
                const struct ethhdr *eth = (void *)skb->data;

                /* send to other bridge ports directly */
                if (is_multicast_ether_addr(eth->h_dest)) { //给其他设备发送广播报文,跟前面的收包对应。
                        macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
                        goto xmit_world;
                }

                dest = macvlan_hash_lookup(port, eth->h_dest);//查找是否为发给寄生在同一个物理网卡的其他macvlan设备
                if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {//如果是,且接受macvlan也是bridge模式,直接发送由寄生的物理网卡收包。
                        /* send to lowerdev first for its network taps */
                        dev_forward_skb(vlan->lowerdev, skb);

                        return NET_XMIT_SUCCESS;
                }
        }

xmit_world:
        skb->dev = vlan->lowerdev;
        return dev_queue_xmit(skb); //通过物理网卡发送数据包。
}

打开网络设备

static int macvlan_open(struct net_device *dev)
{
        struct macvlan_dev *vlan = netdev_priv(dev);
        struct net_device *lowerdev = vlan->lowerdev;
        int err;

        if (macvlan_passthru(vlan->port)) {
                if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) {
                        err = dev_set_promiscuity(lowerdev, 1);
                        if (err < 0)
                                goto out;
                }
                goto hash_add;
        }

        if (lowerdev->features & NETIF_F_HW_L2FW_DOFFLOAD &&
            dev->rtnl_link_ops == &macvlan_link_ops) {
                vlan->fwd_priv =
                      lowerdev->netdev_ops->ndo_dfwd_add_station(lowerdev, dev);

                /* If we get a NULL pointer back, or if we get an error
                 * then we should just fall through to the non accelerated path
                 */
                if (IS_ERR_OR_NULL(vlan->fwd_priv)) {
                        vlan->fwd_priv = NULL;
                } else
                        return 0;
        }   //如果可以硬件加速 初始化硬件加速。

        err = -EBUSY;
        if (macvlan_addr_busy(vlan->port, dev->dev_addr))
                goto out;

        err = dev_uc_add(lowerdev, dev->dev_addr);
        if (err < 0)
                goto out;
        if (dev->flags & IFF_ALLMULTI) {
                err = dev_set_allmulti(lowerdev, 1);
                if (err < 0)
                        goto del_unicast;
        }

        if (dev->flags & IFF_PROMISC) {
                err = dev_set_promiscuity(lowerdev, 1); //设置混杂模式收包
                if (err < 0)
                        goto clear_multi;
        }

hash_add:
        macvlan_hash_add(vlan); //如果不存在硬件加速将开启的vlan添加到hash表中
        return 0;

clear_multi:
        if (dev->flags & IFF_ALLMULTI)
                dev_set_allmulti(lowerdev, -1);
del_unicast:
        dev_uc_del(lowerdev, dev->dev_addr);
out:
        if (vlan->fwd_priv) {
                lowerdev->netdev_ops->ndo_dfwd_del_station(lowerdev,
                                                           vlan->fwd_priv);
                vlan->fwd_priv = NULL;
        }
        return err;
}

关闭

static int macvlan_stop(struct net_device *dev)
{
        struct macvlan_dev *vlan = netdev_priv(dev);
        struct net_device *lowerdev = vlan->lowerdev;

        if (vlan->fwd_priv) {
                lowerdev->netdev_ops->ndo_dfwd_del_station(lowerdev,
                                                           vlan->fwd_priv); //删除硬件加速。
                vlan->fwd_priv = NULL;
                return 0;
        }

        dev_uc_unsync(lowerdev, dev);
        dev_mc_unsync(lowerdev, dev);

        if (macvlan_passthru(vlan->port)) {
                if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC))
                        dev_set_promiscuity(lowerdev, -1);
                goto hash_del;
        }

        if (dev->flags & IFF_ALLMULTI)
                dev_set_allmulti(lowerdev, -1);

        if (dev->flags & IFF_PROMISC)
                dev_set_promiscuity(lowerdev, -1); //关闭混杂模式收包

        dev_uc_del(lowerdev, dev->dev_addr); //从 hash表中删除

hash_del:
        macvlan_hash_del(vlan, !dev->dismantle);
        return 0;
}

macvlan当前有很多应用,在docker 和虚拟化中应用很多,在虚拟化中多数使用macvtap设备,在docker中使用macvlan设备。另外,vrrp等一些需要接受其他mac地址的应用也可以使用macvlan设备。

分类
linux virtual nic

tun/tap虚拟网卡

tun/tap虚拟网卡设备
物理网卡设备在发送数据包时,将由线缆将数据包发送到对端设备上。
VETH网卡设备,在发送数据包时,将数据包直接交由配对的网卡收包并上送协议栈。
tun/tap虚拟网卡设备在网卡发包时,将数据包存放在一个缓冲区,当进程读取特定字符设备时,将缓冲区的数据包上送给进程。当进程写特定字符设备时,将数据包发送给网卡收包并上送协议栈。
个人觉得 这种特性就是讲raw socket 和 VETH网卡设备结合起来。当前使用这种网卡设备较多的场景有两个,VPN设备,通过协议栈将隧道数据上送到用户态进程,对隧道中的数据进行解密,然后将数据通过写入字符设备,再次通过协议栈对解密后的数据进行路由或者上送到其他进程处理。第二个比较常用的场景是在虚拟化中。tun/tap设备加入网桥中,客户机发包时通过字符设备将数据包下发到桥中,客户机收包时,在缓冲区收取桥送过来的数据。

由于这个网卡涉及到字符设备和网卡设备,因此有两个关键的私有数据结构
字符设备私有数据结构

struct tun_file {
        struct sock sk;                         //用来唤醒阻塞的用户态读取进程
        struct socket socket;                   //在vhost_net中用来在内核直接读取和写入数据
        struct socket_wq wq;                    //读取进程阻塞等待变量
        struct tun_struct __rcu *tun;           //字符设备对应的网卡设备的私有数据结构
        struct fasync_struct *fasync;
        /* only used for fasnyc */
        unsigned int flags;
        union {
                u16 queue_index;
                unsigned int ifindex;
        };
        struct list_head next;
        struct tun_struct *detached;
        struct skb_array tx_array;               //该字符设备的缓冲区,网卡发送数据时直接将数据填入该缓冲区
};

网卡设备私有数据结构

struct tun_struct {
        struct tun_file __rcu   *tfiles[MAX_TAP_QUEUES]; //网卡设备对应的字符设备。一个网卡设备可以对应多个字符设备的打开实例,多队列
        unsigned int            numqueues;               //总共的队列数量
        unsigned int            flags;                  //tun/tap设备特性描述
        kuid_t                  owner;
        kgid_t                  group;

        struct net_device       *dev;                   //对应的网卡设备
        netdev_features_t       set_features;
#define TUN_USER_FEATURES (NETIF_F_HW_CSUM|NETIF_F_TSO_ECN|NETIF_F_TSO| \
                          NETIF_F_TSO6|NETIF_F_UFO)

        int                     align;
        int                     vnet_hdr_sz;
        int                     sndbuf;
        struct tap_filter       txflt;
        struct sock_fprog       fprog;
        /* protected by rtnl lock */
        bool                    filter_attached;
#ifdef TUN_DEBUG
        int debug;
#endif
        spinlock_t lock;
        struct hlist_head flows[TUN_NUM_FLOW_ENTRIES];
        struct timer_list flow_gc_timer;
        unsigned long ageing_time;
        unsigned int numdisabled;
        struct list_head disabled;
        void *security;
        u32 flow_count;
        u32 rx_batched;
        struct tun_pcpu_stats __percpu *pcpu_stats;    //网卡收发包统计数据
};

两个私有数据结构之间的关系

digraph tun{
    node [shape = plaintext]
    rankdir = LR
    tun_struct[label=<
            <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
            <TR><TD BORDER="0" ALIGN="CENTER">struct tun_struct</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f0">struct tun_file *tfiles[MAX_TAP_QUEUES]</TD></TR>
            <TR><TD ALIGN="LEFT">unsigned int numqueues</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            <TR><TD ALIGN="LEFT">struct net_device *dev</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            <TR><TD ALIGN="LEFT">struct hlist_head flows[TUN_NUM_FLOW_ENTRIES]</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            <TR><TD ALIGN="LEFT">struct tun_pcpu_stats __percpu *pcpu_status</TD></TR>
            </TABLE>>]
    tun_file[label=<
            <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
            <TR><TD BORDER="0" ALIGN="CENTER">struct tun_file</TD></TR>
            <TR><TD ALIGN="LEFT" PORT="f0">struct sock sk</TD></TR>
            <TR><TD ALIGN="LEFT">struct socket socket</TD></TR>
            <TR><TD ALIGN="LEFT">socket_wq wq</TD></TR>
            <TR><TD ALIGN="LEFT">struct tun_struct *tun</TD></TR>
            <TR><TD ALIGN="CENTER">......</TD></TR>
            <TR><TD ALIGN="LEFT">struct tun_struct *detached</TD></TR>
            <TR><TD ALIGN="LEFT">struct skb_array tx_array</TD></TR>
            </TABLE>>]
    tun_struct:f0 -> tun_file:f0
}

模块注册

static int __init tun_init(void)
{
        int ret = 0;

        pr_info("%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);

        ret = rtnl_link_register(&tun_link_ops);                          //注册netlink创建网卡的接口,当前应该没有通过这个创建
        if (ret) {
                pr_err("Can't register link_ops\n");
                goto err_linkops;
        }

        ret = misc_register(&tun_miscdev);        //注册字符设备,看代码用户态通过字符设备的ioctl接口创建网卡。
        if (ret) {
                pr_err("Can't register misc device %d\n", TUN_MINOR);
                goto err_misc;
        }

        register_netdevice_notifier(&tun_notifier_block);
        return  0;
err_misc:
        rtnl_link_unregister(&tun_link_ops);
err_linkops:
        return ret;
}

打开字符设备

static int tun_chr_open(struct inode *inode, struct file * file)
{       
        struct net *net = current->nsproxy->net_ns;
        struct tun_file *tfile;

        DBG1(KERN_INFO, "tunX: tun_chr_open\n");

        tfile = (struct tun_file *)sk_alloc(net, AF_UNSPEC, GFP_KERNEL,
                                            &tun_proto, 0); //创建字符文件的私有数据结构,该结构用来缓存skb及当有数据时唤醒读进程
        if (!tfile)
                return -ENOMEM;
        RCU_INIT_POINTER(tfile->tun, NULL);
        tfile->flags = 0;
        tfile->ifindex = 0;

        init_waitqueue_head(&tfile->wq.wait);
        RCU_INIT_POINTER(tfile->socket.wq, &tfile->wq);

        tfile->socket.file = file;
        tfile->socket.ops = &tun_socket_ops;            //vhost-net会使用这个调用发送和接受网卡数据。

        sock_init_data(&tfile->socket, &tfile->sk);     //初始化sk 和socket变量,

        tfile->sk.sk_write_space = tun_sock_write_space;
        tfile->sk.sk_sndbuf = INT_MAX;

        file->private_data = tfile;
        INIT_LIST_HEAD(&tfile->next);

        sock_set_flag(&tfile->sk, SOCK_ZEROCOPY);

        return 0;
}

通过 ioctl设置字符设备对应的网卡设备。

static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
                            unsigned long arg, int ifreq_len)
{
......
       if (cmd == TUNSETIFF) {
                ret = -EEXIST;
                if (tun)
                        goto unlock;

                ifr.ifr_name[IFNAMSIZ-1] = '\0';

                ret = tun_set_iff(sock_net(&tfile->sk), file, &ifr); //设置关联网卡

                if (ret)
                        goto unlock;

                if (copy_to_user(argp, &ifr, ifreq_len))
                        ret = -EFAULT;
                goto unlock;
        }
......
}

tun_set_iff 函数实体,这个函数主要是将文件的私有结构和网卡的私有结构关联。当网卡有数据时,直接存入文件的私有结构的缓冲区,当文件写入时,调用文件的关联网卡上送数据到协议栈

static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
{
        struct tun_struct *tun;
        struct tun_file *tfile = file->private_data;
        struct net_device *dev;
        int err;

        if (tfile->detached)
                return -EINVAL;

        dev = __dev_get_by_name(net, ifr->ifr_name);  //查找网卡设备是否存在
        if (dev) {  
                if (ifr->ifr_flags & IFF_TUN_EXCL)
                        return -EBUSY;
                if ((ifr->ifr_flags & IFF_TUN) && dev->netdev_ops == &tun_netdev_ops)
                        tun = netdev_priv(dev);
                else if ((ifr->ifr_flags & IFF_TAP) && dev->netdev_ops == &tap_netdev_ops)
                        tun = netdev_priv(dev);
                else
                        return -EINVAL;

                if (!!(ifr->ifr_flags & IFF_MULTI_QUEUE) !=
                    !!(tun->flags & IFF_MULTI_QUEUE))
                        return -EINVAL;

                if (tun_not_capable(tun))
                        return -EPERM;
                err = security_tun_dev_open(tun->security);
                if (err < 0)
                        return err;

                err = tun_attach(tun, file, ifr->ifr_flags & IFF_NOFILTER); // 将当前文件的私有数据结构和网卡的私有数据结构关联。将tfile 存入tun结构的tfiles数组中
                if (err < 0)
                        return err;

                if (tun->flags & IFF_MULTI_QUEUE &&
                    (tun->numqueues + tun->numdisabled > 1)) {
                        /* One or more queue has already been attached, no need
                         * to initialize the device again.
                         */
                        return 0;
                }
        }
        else {
                char *name;
                unsigned long flags = 0;
                int queues = ifr->ifr_flags & IFF_MULTI_QUEUE ?
                             MAX_TAP_QUEUES : 1;

                if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
                        return -EPERM;
                err = security_tun_dev_create();
                if (err < 0)
                        return err;

                /* Set dev type */
                if (ifr->ifr_flags & IFF_TUN) {
                        /* TUN device */
                        flags |= IFF_TUN;
                        name = "tun%d";
                } else if (ifr->ifr_flags & IFF_TAP) {
                        /* TAP device */
                        flags |= IFF_TAP;
                        name = "tap%d";
                } else
                        return -EINVAL;

                if (*ifr->ifr_name)
                        name = ifr->ifr_name;

                dev = alloc_netdev_mqs(sizeof(struct tun_struct), name,
                                       NET_NAME_UNKNOWN, tun_setup, queues,
                                       queues); //如果网卡设备不存在则创建网卡设备

                if (!dev)
                        return -ENOMEM;

                dev_net_set(dev, net);
                dev->rtnl_link_ops = &tun_link_ops;
                dev->ifindex = tfile->ifindex;
                dev->sysfs_groups[0] = &tun_attr_group;

                tun = netdev_priv(dev);
                tun->dev = dev;
                tun->flags = flags;
                tun->txflt.count = 0;
                tun->vnet_hdr_sz = sizeof(struct virtio_net_hdr);

                tun->align = NET_SKB_PAD;
                tun->filter_attached = false;
                tun->sndbuf = tfile->socket.sk->sk_sndbuf;
                tun->rx_batched = 0;

                tun->pcpu_stats = netdev_alloc_pcpu_stats(struct tun_pcpu_stats);
                if (!tun->pcpu_stats) {
                        err = -ENOMEM;
                        goto err_free_dev;
                }

                spin_lock_init(&tun->lock);

                err = security_tun_dev_alloc_security(&tun->security);
                if (err < 0)
                        goto err_free_stat;

                tun_net_init(dev);
                tun_flow_init(tun);

                dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST |
                                   TUN_USER_FEATURES | NETIF_F_HW_VLAN_CTAG_TX |
                                   NETIF_F_HW_VLAN_STAG_TX;
                dev->features = dev->hw_features | NETIF_F_LLTX;
                dev->vlan_features = dev->features &
                                     ~(NETIF_F_HW_VLAN_CTAG_TX |
                                       NETIF_F_HW_VLAN_STAG_TX);

                INIT_LIST_HEAD(&tun->disabled);    //上面所有的步骤都是在初始化网卡和网卡的私有数据结构tun
                err = tun_attach(tun, file, false); //将当前文件的私有数据结构和网卡的私有数据结构关联。将tfile 存入tun结构的tfiles数组中
                if (err < 0)
                        goto err_free_flow;

                err = register_netdevice(tun->dev);
                if (err < 0)
                        goto err_detach;
        }

        netif_carrier_on(tun->dev);

        tun_debug(KERN_INFO, tun, "tun_set_iff\n");

        tun->flags = (tun->flags & ~TUN_FEATURES) |
                (ifr->ifr_flags & TUN_FEATURES);

        /* Make sure persistent devices do not get stuck in
         * xoff state.
         */
        if (netif_running(tun->dev))
                netif_tx_wake_all_queues(tun->dev);

        strcpy(ifr->ifr_name, tun->dev->name);
        return 0;

err_detach:
        tun_detach_all(dev);
err_free_flow:
        tun_flow_uninit(tun);
        security_tun_dev_free_security(tun->security);
err_free_stat:
        free_percpu(tun->pcpu_stats);
err_free_dev:
        free_netdev(dev);
        return err;
}

至此,tun设备 已经创建完成。

通过tun设备发送数据。
发送函数是tun_chr_write_iter

static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
        struct file *file = iocb->ki_filp;
        struct tun_struct *tun = tun_get(file);
        struct tun_file *tfile = file->private_data;
        ssize_t result;

        if (!tun)
                return -EBADFD;

        result = tun_get_user(tun, tfile, NULL, from,
                              file->f_flags & O_NONBLOCK, false);   //获取用户态发送过来的skb结构,然后调用netif_rx_ni 收包

        tun_put(tun);
        return result;
}

tun_get_user函数 实体

static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                            void *msg_control, struct iov_iter *from,
                            int noblock, bool more)
{
......
        skb = tun_alloc_skb(tfile, align, copylen, linear, noblock);
        if (IS_ERR(skb)) {
                if (PTR_ERR(skb) != -EAGAIN)
                        this_cpu_inc(tun->pcpu_stats->rx_dropped);
                return PTR_ERR(skb);
        }

        if (zerocopy)
                err = zerocopy_sg_from_iter(skb, from);
        else
                err = skb_copy_datagram_from_iter(skb, 0, from, len);   //创建并获取数据
......
#ifndef CONFIG_4KSTACKS
        tun_rx_batched(tun, tfile, skb, more);  //数据批量上送协议栈
#else
        netif_rx_ni(skb);       //数据上送协议栈
#endif

        stats = get_cpu_ptr(tun->pcpu_stats);
        u64_stats_update_begin(&stats->syncp);
        stats->rx_packets++;
        stats->rx_bytes += len;
        u64_stats_update_end(&stats->syncp);
        put_cpu_ptr(stats);

        tun_flow_update(tun, rxhash, tfile);
        return total_len;
}

通过tun 设备收数据包
网卡设备最终调用tun_net_xmit函数发送数据包

static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
        struct tun_struct *tun = netdev_priv(dev);
        int txq = skb->queue_mapping;
        struct tun_file *tfile;
        u32 numqueues = 0;

        rcu_read_lock();
        tfile = rcu_dereference(tun->tfiles[txq]);//获取网卡关联的文件私有数据结构。
        numqueues = ACCESS_ONCE(tun->numqueues);

        /* Drop packet if interface is not attached */
        if (txq >= numqueues)
                goto drop;
......
        if (skb_array_produce(&tfile->tx_array, skb))   //将数据存放到文件私有数据结构的缓冲区内
                goto drop;

        /* Notify and wake up reader process */
        if (tfile->flags & TUN_FASYNC)
                kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
        tfile->socket.sk->sk_data_ready(tfile->socket.sk);     //唤醒读文件阻塞的进程

        rcu_read_unlock();
        return NETDEV_TX_OK;

drop:
        this_cpu_inc(tun->pcpu_stats->tx_dropped);
        skb_tx_error(skb);
        kfree_skb(skb);
        rcu_read_unlock();
        return NET_XMIT_DROP;
}

文件读取数据函数

static ssize_t tun_chr_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
        struct file *file = iocb->ki_filp;
        struct tun_file *tfile = file->private_data;
        struct tun_struct *tun = __tun_get(tfile);
        ssize_t len = iov_iter_count(to), ret;

        if (!tun)
                return -EBADFD;
        ret = tun_do_read(tun, tfile, to, file->f_flags & O_NONBLOCK, NULL);    //读取文件缓冲区的数据
        ret = min_t(ssize_t, ret, len);
        if (ret > 0)
                iocb->ki_pos = ret;
        tun_put(tun);
        return ret;
}

tun_do_read函数实体

static ssize_t tun_do_read(struct tun_struct *tun, struct tun_file *tfile,
                           struct iov_iter *to,
                           int noblock, struct sk_buff *skb)
{
        ssize_t ret;
        int err;

        tun_debug(KERN_INFO, tun, "tun_do_read\n");

        if (!iov_iter_count(to))
                return 0;

        if (!skb) {
                /* Read frames from ring */
                skb = tun_ring_recv(tfile, noblock, &err); //读取缓冲区的数据
                if (!skb)
                        return err;
        }

        ret = tun_put_user(tun, tfile, skb, to);        //将数据存放到用户态进程内存中
        if (unlikely(ret < 0))
                kfree_skb(skb);
        else
                consume_skb(skb);

        return ret;
}

tun_ring_recv函数实体

static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
                                     int *err)
{
        DECLARE_WAITQUEUE(wait, current);
        struct sk_buff *skb = NULL;
        int error = 0;

        skb = skb_array_consume(&tfile->tx_array);  //如果缓冲区里面有skb,获取skb
        if (skb)
                goto out;
        if (noblock) {
                error = -EAGAIN;
                goto out;
        }

        add_wait_queue(&tfile->wq.wait, &wait);
        current->state = TASK_INTERRUPTIBLE;    //如果缓冲区没有skb,将进程设置为可中断等待状态

        while (1) {
                skb = skb_array_consume(&tfile->tx_array);
                if (skb)
                        break;
                if (signal_pending(current)) {
                        error = -ERESTARTSYS;
                        break;
                }
                if (tfile->socket.sk->sk_shutdown & RCV_SHUTDOWN) {
                        error = -EFAULT;
                        break;
                }

                schedule();                     //切换到别的进程执行。等待直到缓冲区中有skb
        }

        current->state = TASK_RUNNING;
        remove_wait_queue(&tfile->wq.wait, &wait);

out:
        *err = error;
        return skb;                             //返回skb
}

至此,tun/tap设备的收发包过程就已经清楚了。
tun/tap 设备在做虚拟机backend时,如果有vhost-net,还有另外一条收发包路径,等分析vhost-net我在细说

分类
linux virtual nic

loopback 网卡

LOOPBACK 回环网卡私有数据结构
该网卡没有私有数据结构

内核模块注册

struct pernet_operations __net_initdata loopback_net_ops = {
        .init = loopback_net_init,
};

在网络命令空间中每个网络命名空间必须有一个lo网卡,因此新创建的命令空间需要先运行 loopback_net_init函数给当前命名空间创建 lo网卡

网卡创建

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);//创建网卡
        if (!dev)
                goto out;

        dev_net_set(dev, net);//设置网卡所属命令空间
        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;
}

初始化网卡,及环回网卡操作函数

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)
{
        dev->mtu                = 64 * 1024;
        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;
        dev->header_ops         = &eth_header_ops;
        dev->netdev_ops         = &loopback_ops; //设置网卡操作函数
        dev->needs_free_netdev  = true;
        dev->priv_destructor    = loopback_dev_free;
}

网卡发送函数

static netdev_tx_t loopback_xmit(struct sk_buff *skb,
                                 struct net_device *dev)
{
        struct pcpu_lstats *lb_stats;
        int len;

        skb_tx_timestamp(skb);
        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)) {  //通过netif_rx将数据包上送到协议栈
                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;
}

loopback网卡,是linux 设备中的第一个网卡,在本机发往本地任何网卡IP的数据包都是走lo网卡。

分类
linux virtual nic

VETH 网卡

VETH 类型网卡私有数据结构

struct veth_priv {
    struct net_device  __rcu *peer;
    atomic64_t      dropped;
    unsigned        requested_headromm;
}

veth_link_ops结构体实例

static struct rtnl_link_ops veth_link_ops ={
    .kind           = DRV_NAME,                 //虚拟网卡名字
    .priv_size      = sizeof(struct veth_priv), //虚拟网卡private data 长度
    .setup          = veth_setup,               //在创建虚拟网卡时调用
    .validate       = veth_validate,            //检查用户态传过来参数是否合法
    .newlink        = veth_newlink,             //根据用户态参数设置网卡 
    .dellink        = veth_dellink,             //删除网卡时调用,用于释放资源
    .policy         = veth_policy,              //没有跟踪用法,不影响逻辑
    .maxtype        = VETH_INFO_MAX,            //没有跟踪用法,不影响逻辑
    .get_link_net   = veth_get_link_net,        //获取网卡所在的网络命令空间
};

内核模块注册

static __init int veth_init(void)
{
    return rtnl_link_register(&veth_link_ops); //注册veth_link_ops结构,在用户态通过netlink,创建这种类型的虚拟网卡
}

创建虚拟网卡 逻辑
用户态通过 netlink 通知创建虚拟网卡,创建时先调用 veth_setup 函数 再调用 veth_newlink函数
veth_setup 函数

static void veth_setup(struct net_device *dev)
{
    ether_setup(dev);

    dev->priv_flags &= ~IFF_TX_SKB_SHARING;
    dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
    dev->priv_flags |= IFF_NO_QUEUE;
    dev->priv_flags |= IFF_PHONY_HEADROOM;

    dev->netdev_ops = &veth_netdev_ops;   //设置网卡处理函数 发送函数是 veth_xmit 这个函数跟数据流有关系。
    dev->ethtool_ops = &veth_ethtool_ops; //ethtool 命令通过ioctl获取网卡信息
    dev->features |= NETIF_F_LLTX;
    dev->features |= VETH_FEATURES;
    dev->vlan_features = dev->features &
                        ~(NETIF_F_HW_VLAN_CTAG_TX |
                        NETIF_F_HW_VLAN_STAG_TX |
                        NETIF_F_HW_VLAN_CTAG_RX |
                        NETIF_F_HW_VLAN_STAG_RX);
    dev->needs_free_netdev = true;
    dev->priv_destructor = veth_dev_free;
    dev->max_mtu = ETH_MAX_MTU;

    dev->hw_features = VETH_FEATURES;
    dev->hw_enc_features = VETH_FEATURES;
    dev->mpls_features = NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE;
    }

veth_newlink 函数 主要逻辑如下

static int veth_newlink(struct net *src_net, struct net_device *dev,
                        struct nlattr *tb[], struct nlattr *data[],
                        struct netlink_ext_ack *extack)
{
    .......
    peer = rtnl_create_link(net, ifname, name_assign_type,
                                &veth_link_ops, tbp); //创建 配对网卡
    .......
    priv = netdev_priv(dev);
    rcu_assign_pointer(priv->peer, peer); //将private 数据结构中的 peer 设置为对端网卡

    priv = netdev_priv(peer);
    rcu_assign_pointer(priv->peer, dev); //将private 数据结构中的 peer 设置为对端网卡。
    return 0;

}

到这里网卡 创建完成。接下来是收发数据包。
发包流程
在 veth_setup 函数中我们看到 veth_xmit 是网卡发包函数。
veth_xmit 函数体如下

static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)
{       
    struct veth_priv *priv = netdev_priv(dev);
    struct net_device *rcv;
    int length = skb->len;

    rcu_read_lock();
    rcv = rcu_dereference(priv->peer); //获取对端设备。
    if (unlikely(!rcv)) {
        kfree_skb(skb);
        goto drop;
    }

    if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) {  //发送数据
        struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats); //统计发送成功的数据

        u64_stats_update_begin(&stats->syncp);
        stats->bytes += length;
         stats->packets++;
        u64_stats_update_end(&stats->syncp);
    } else {
drop:
        atomic64_inc(&priv->dropped);   //统计发送失败的数据
    }
    rcu_read_unlock();
    return NETDEV_TX_OK;
}

dev_forward_skb 函数体

int dev_forward_skb(struct net_device *dev, struct sk_buff *skb)
{
    return __dev_forward_skb(dev, skb) ?: netif_rx_internal(skb); //通过__dev_forward_skb设置skb ,通过netif_rx_internal 将数据上送到协议栈
}
EXPORT_SYMBOL_GPL(dev_forward_skb);

__dev_forward_skb 函数体

int __dev_forward_skb(struct net_device *dev, struct sk_buff *skb)
{
    int ret = ____dev_forward_skb(dev, skb);

    if (likely(!ret)) {
        skb->protocol = eth_type_trans(skb, dev); //获取以太网以上的网络协议,将收包网卡设置dev
        skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
    }

    return ret;
}
EXPORT_SYMBOL_GPL(__dev_forward_skb);

收报流程。收报流程在发包流程中可以看到,在对端网卡发包时,通过调用netif_rx_internal函数将数据包上送到协议栈,之后的处理流程就是网卡的收包流程

分类
dpdk analysis

dpdk 网卡驱动注册之虚拟网卡驱动注册

dpdk 中比较常用的虚拟网卡是af_packet类型网卡,这种网卡可以收发ip命令创建的vnet类型网卡数据包

af_packet类型网卡驱动在driver/net/af_packet目录下,主要的文件是rte_eth_af_packet.c文件

在文件的最后有一个 pmd_af_packet_drv结构体

static struct rte_vdev_driver pmd_af_packet_drv =
{
    .probe = rte_pmd_af_packet_probe,
    .remove=rte_pmd_af_packet_remove,
}; //创建驱动结构体

RTE_PMD_REGISTER_VDEV(net_af_packet, pmd_af_packet_drv); //注册驱动
RTE_PMD_REGISTER_ALIAS(net_af_packet, eth_af_packet);  //注册驱动别名
RTE_PMD_REGISTER_PARAM_STRING(net_af_packet,    //驱动参数设置
    "iface=<string> "
    "qpairs=<int> "
    "blocksz=<int> "
    "framesz=<int> "
    "framecnt=<int>");

RTE_PMD_REGISTER_VDEV 宏源代码

#define RTE_PMD_REGISTER_VDEV(nm, vdrv)\
RTE_INIT(vdrvinitfn_ ##vdrv);\    //申明 vdrvinitfn_ ##vdrv 函数 为constructor ,在main函数之前执行
static const char *vdrvinit_ ## nm ## _alias;\  //申明别名指针
static void vdrvinitfn_ ##vdrv(void)\
{\
    (vdrv).driver.name = RTE_STR(nm);\                //设置名称
    (vdrv).driver.alias = vdrvinit_ ## nm ## _alias;\ //设置别名指针指向别名
    rte_eal_vdrv_register(&vdrv);\                    //注册驱动
} \
RTE_PMD_EXPORT_NAME(nm, __COUNTER__)

rte_eal_vdrv_register 驱动注册函数

void rte_eal_vdrv_register(struct rte_vdev_driver *driver)
{
    rte_vdev_bus_register();

    TAILQ_INSERT_TAIL(&vdev_driver_list, driver, next);//注册到虚拟驱动列表中
    rte_eal_driver_register(&driver->driver);   //注册到所有驱动列表中
}

注册完成以后,在处理用户输入参数时,会 调用 rte_eal_devargs_add添加 虚拟设备的参数到devargs_list中

注册虚拟bus,在dpdk中有一个bus的概念,这个概念类似于内核的bus概念,设备都挂载在bus上。因此,dpdk为所有的虚拟设备注册了一个虚拟bus

static struct rte_bus rte_vdev_bus = { //定义虚拟bus结构
    .scan = vdev_scan,
    .probe = vdev_probe,
};

RTE_INIT(rte_vdev_bus_register); //申明rte_vdev_bus_register函数 添加constructor属性,在执行main函数之前执行

static void rte_vdev_bus_register(void)
{
    static int registered;

    if (registered)
            return;

    registered = 1;
    rte_vdev_bus.name = RTE_STR(virtual); //设置bus名字
    rte_bus_register(&rte_vdev_bus); //注册bus
}

rte_bus_scan 函数 扫描 bus上的设备,代码如下

int rte_bus_scan(void)
{
int ret;
struct rte_bus *bus = NULL;

    TAILQ_FOREACH(bus, &rte_bus_list, next) { //遍历 所有类型bus
            ret = bus->scan();   //扫描设备 当前调用 vdev_scan 扫描
            if (ret) {
                    RTE_LOG(ERR, EAL, "Scan for (%s) bus failed.\n",
                            bus->name);
                    return ret;
            }
    }

    return 0;
}

vdev_scan 函数如下

static int vdev_scan(void)
{
    struct rte_vdev_device *dev;
    struct rte_devargs *devargs;

    /* for virtual devices we scan the devargs_list populated via cmdline */

    TAILQ_FOREACH(devargs, &devargs_list, next) { //遍历用户输入的虚拟网卡参数列表

            if (devargs->type != RTE_DEVTYPE_VIRTUAL)
                    continue;

            dev = find_vdev(devargs->virt.drv_name);
            if (dev)
                    continue;

            dev = calloc(1, sizeof(*dev));  //创建虚拟网卡设备
            if (!dev)
                    return -1;

            dev->device.devargs = devargs;
            dev->device.numa_node = SOCKET_ID_ANY;
            dev->device.name = devargs->virt.drv_name;

            rte_eal_device_insert(&dev->device);
            TAILQ_INSERT_TAIL(&vdev_device_list, dev, next); //添加到虚拟设备链表中
    }

    return 0;
}

rte_bus_probe 函数查找 设备的驱动

int rte_bus_probe(void)
{
    int ret;
    struct rte_bus *bus, *vbus = NULL;

    TAILQ_FOREACH(bus, &rte_bus_list, next) { // 遍历所有的bus
            if (!strcmp(bus->name, "virtual")) {
                    vbus = bus; //虚拟bus最后查找驱动
                    continue;
            }

            ret = bus->probe(); //查找驱动
            if (ret) {
                    RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
                            bus->name);
                    return ret;
            }
    }

    if (vbus) {
            ret = vbus->probe(); //查找虚拟bus上的虚拟设备的驱动 调用vdev_probe函数
            if (ret) {
                    RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
                            vbus->name);
                    return ret;
            }
    }

    return 0;
}

vdev_probe 函数如下

static int vdev_probe(void)
{
struct rte_vdev_device *dev;

    /*
     * Note that the dev_driver_list is populated here
     * from calls made to rte_eal_driver_register from constructor functions
     * embedded into PMD modules via the RTE_PMD_REGISTER_VDEV macro
     */

    /* call the init function for each virtual device */
    TAILQ_FOREACH(dev, &vdev_device_list, next) {//遍历bus上的所有设备

            if (dev->device.driver)
                    continue;

            if (vdev_probe_all_drivers(dev)) { //查找设备的驱动
                    RTE_LOG(ERR, EAL, "failed to initialize %s device\n",
                            rte_vdev_device_name(dev));
                    return -1;
            }
    }

    return 0;
}

vdev_probe_all_drivers 函数代码如下

static int vdev_probe_all_drivers(struct rte_vdev_device *dev)
{
    const char *name;
    char *drv_name;
    struct rte_vdev_driver *driver;
    int ret = 1;

    drv_name = parse_driver_arg(rte_vdev_device_args(dev));
    name = drv_name ? drv_name : rte_vdev_device_name(dev); //获取指定的驱动名字

    RTE_LOG(DEBUG, EAL, "Search driver %s to probe device %s\n", name,
            rte_vdev_device_name(dev));

    TAILQ_FOREACH(driver, &vdev_driver_list, next) {//遍历驱动列表
            /*
             * search a driver prefix in virtual device name.
             * For example, if the driver is pcap PMD, driver->name
             * will be "net_pcap", but "name" will be "net_pcapN".
             * So use strncmp to compare.
             */
            if (!strncmp(driver->driver.name, name,
                        strlen(driver->driver.name))) {
                    dev->device.driver = &driver->driver;
                    ret = driver->probe(dev); //找到匹配名字,调用驱动尝试初始化
                    if (ret)
                            dev->device.driver = NULL;
                    goto out;
            }
    }

    /* Give new names precedence over aliases. */
    TAILQ_FOREACH(driver, &vdev_driver_list, next) {//遍历驱动列表
            if (driver->driver.alias &&
                !strncmp(driver->driver.alias, name,
                        strlen(driver->driver.alias))) {//找到匹配别名,尝试初始化
                    dev->device.driver = &driver->driver;
                    ret = driver->probe(dev); //找到匹配别名,尝试初始化
                    if (ret)
                            dev->device.driver = NULL;
                    break;
            }
    }

out:
    free(drv_name);
    return ret;
}

到这里虚拟网卡驱动 及虚拟网卡加载完毕。

分类
dpdk analysis

dpdk 无锁 队列解析

在dpdk中有一个无锁环形队列,用来解决多生产者和多消费者之间的同步,据说无锁以后性能 有很大的提升。这里面主要使用CAS的思想。

我们首先分析一下思路。

无锁队列有四个关键变量。 cons_head cons_tail prod_head prod_tail。

在没有读者和写着消费信息的情况下 cons_head 等于cons_tail prod_head 等于prod_tail

1.生产者拷贝 首先拷贝一份cons_tail prod_head prod_tail。

2.将prod_head 加上生产的数据长度,得到prod_next,判断prod_next是否覆盖cons_tail。如果是,生产失败,否则进入下一步

3.使用compare and swap指令,对比队列中的prod_head 和拷贝的prod_head对比,如果一样,则将队列中的prod_head等于prod_next,如果不一样则返回到第一步重新来过

4.生产数据

5.判断队列中的prod_tail是否等于拷贝后的prod_head,如果等于则将prod_tail等于prod_next,否则等待直到 队列中的prod_tail等于拷贝后的prod_head,再将prod_tail等于prod_next

仔细分析以上5步,我们可以发现,在多生产者同时生产数据时,1-3步是序列化的,多个生产者必须一个一个来, 第4步是可以并行拷贝,第5步必须按照1-3步的序列化顺序来。

当前生产者为了生产数据花费的时候 应该是他前面所有生产者中花费时间最长的,稍微长一点点。因此性能比锁要好很多

分类
GCC

GCC ifunc attribute

今天发现了一个gcc的属性 ifunc
先看代码

#include <stdio.h>
void *test(int a) __attribute__ ((ifunc("if_test")));

void test_printf(int a)
{
    printf("my if test func %d\n",a);
}

static void (*if_test(void))(int)
{
    printf("run if_test \n");
    return &test_printf;
}

int main()
{
    test(100);
    test(200);
    test(300);
    test(400)
    test(500);
    test(500);
    test(600);
    test(700);
    test(800);
    test(900);
    return 0;
}

这段代码的执行结果如下:

run if_test
my if test func 100
my if test func 200
my if test func 300
my if test func 400
my if test func 500
my if test func 500
my if test func 600
my if test func 700
my if test func 800
my if test func 900

可以看出 if_test 只执行了一次。之后多次执行了test_printf

由此可以看出这是gcc 的新特性。在执行 test前通过执行if_test判断需要执行那个函数。然后将test_printf地址赋值给test。
这样test执行的就是test_printf

这样可以达到运行是动态选定执行函数的目的。特别是在当需根据不同cpu,使用不同优化指令集时使用。
分类
shell

Shell Colors

Shell Colors是什么?

Shell Colors是在终端下显示的颜色,利用其可以让输出多姿多彩。在shell脚本中一般使用ANSI转义码来实现带颜色的输出。

从示例开始

让我们从一个示例开始认识Shell Colors。

echo -e "\033[0;34m Shell Colors\033[0m"

以上一句简单的echo语句实现了在终端输出蓝色的Shell Colors,效果如下: blue "shell colors"

在这个语句中要注意的地方有三个,一是-e选项,有了这个选项,echo便能解释转义序列(在macos的终端上-e选项是否存在并不影响转义!);二是\033[0;34m,其中\033[表示开始转义序列,也可使用等价的\e[,紧随其后的0表示正常样式(regular or normal),34表示颜色为蓝色,最后的m表示结束转义序列;三是\033[0m,有了前面的知识,这一项便很容易理解,其中的ANSI转义码为0,表示重置文本的显示,即恢复正常显示。

对于上述的echo语句,通俗的解释是\033[0;34m设置了其后直到\033[0m间的文本的显示样式,即正常样式,颜色为蓝色,\033[0m后的文本正常显示。

再给出几个例子。

禁用转义: disable escape

Linux下: under centos

可以看到,在LinuxMacos下的表现差别还是挺明显的,在Centos下命令的提示符也改变了颜色!至于什么导致了这种差别,我暂时没有弄清楚。

在Shell终端下可用的颜色都有哪些?

我们已经知道了在shell脚本中可以利用以上技巧实现带色彩的输出,自然而然的一个问题就是可用的颜色都有哪些呢?下面给出可用颜色,一睹为快。

终端下的颜色: Shell Colors Table

是不是看着很cool?以上效果的生成代码如下:

echo -e '\033[00;30m 00;30m \033[00m \033[02;30m 02;30m \033[00m \033[01;30m 01;30m \033[00m \033[01;40m 01;40m \033[00m
\033[00;31m 00;31m \033[00m \033[02;31m 02;31m \033[00m \033[01;31m 01;31m \033[00m \033[01;41m 01;41m \033[00m
\033[00;32m 00;32m \033[00m \033[02;32m 02;32m \033[00m \033[01;32m 01;32m \033[00m \033[01;42m 01;42m \033[00m
\033[00;33m 00;33m \033[00m \033[02;33m 02;33m \033[00m \033[01;33m 01;33m \033[00m \033[01;43m 01;43m \033[00m
\033[00;34m 00;34m \033[00m \033[02;34m 02;34m \033[00m \033[01;34m 01;34m \033[00m \033[01;44m 01;44m \033[00m
\033[00;35m 00;35m \033[00m \033[02;35m 02;35m \033[00m \033[01;35m 01;35m \033[00m \033[01;45m 01;45m \033[00m
\033[00;36m 00;36m \033[00m \033[02;36m 02;36m \033[00m \033[01;36m 01;36m \033[00m \033[01;46m 01;46m \033[00m
\033[00;37m 00;37m \033[00m \033[02;37m 02;37m \033[00m \033[01;37m 01;37m \033[00m \033[01;47m 01;47m \033[00m'

以上代码在有了前面介绍的色彩设定知识后不难写出,以上代码归于DerZyklop

你可能注意到了,以上一堆数字码不好记忆,很难与具体的色彩对应起来,一个自然的想法就是建立一个符号表,用时使用对应的符号变量即可。所以请看下面:

Shell下方便的颜色符号定义:

RCol='\e[0m'    # Text Reset

# Regular           Bold                Underline           High Intensity      BoldHigh Intens     Background          High
Intensity Backgrounds
Bla='\e[0;30m';     BBla='\e[1;30m';    UBla='\e[4;30m';    IBla='\e[0;90m';    BIBla='\e[1;90m';   On_Bla='\e[40m';
On_IBla='\e[0;100m';
Red='\e[0;31m';     BRed='\e[1;31m';    URed='\e[4;31m';    IRed='\e[0;91m';    BIRed='\e[1;91m';   On_Red='\e[41m';
On_IRed='\e[0;101m';
Gre='\e[0;32m';     BGre='\e[1;32m';    UGre='\e[4;32m';    IGre='\e[0;92m';    BIGre='\e[1;92m';   On_Gre='\e[42m';
On_IGre='\e[0;102m';
Yel='\e[0;33m';     BYel='\e[1;33m';    UYel='\e[4;33m';    IYel='\e[0;93m';    BIYel='\e[1;93m';   On_Yel='\e[43m';
On_IYel='\e[0;103m';
Blu='\e[0;34m';     BBlu='\e[1;34m';    UBlu='\e[4;34m';    IBlu='\e[0;94m';    BIBlu='\e[1;94m';   On_Blu='\e[44m';
On_IBlu='\e[0;104m';
Pur='\e[0;35m';     BPur='\e[1;35m';    UPur='\e[4;35m';    IPur='\e[0;95m';    BIPur='\e[1;95m';   On_Pur='\e[45m';
On_IPur='\e[0;105m';
Cya='\e[0;36m';     BCya='\e[1;36m';    UCya='\e[4;36m';    ICya='\e[0;96m';    BICya='\e[1;96m';   On_Cya='\e[46m';
On_ICya='\e[0;106m';
Whi='\e[0;37m';     BWhi='\e[1;37m';    UWhi='\e[4;37m';    IWhi='\e[0;97m';    BIWhi='\e[1;97m';   On_Whi='\e[47m';
On_IWhi='\e[0;107m';

(由于本站markdown的支持不够友好,以上代码的格式化很不美观,给出一个本机端显示的截图) colors table 有了以上定义,在shell中使用颜色就方便很多了。请看如下例子:

echo -e "${Blu}blue ${Red}red ${RCol}etc...."

一些补充

前面关于颜色的设定遗漏了一些知识,在此补充一下,或许你已经注意到了从\033[开始至m间可以有多个数字,这些数字由分号;分割,其中一些表示文本的颜色,而另一些则表示文本的属性,如斜体、粗体等。详尽的含义请参考ANSI escape code

文本属性码对照表:

ANSI CODE   Meaning
   0    Normal Characters
   1    Bold Characters
   4    Underlined Characters
   5    Blinking Characters
   7    Reverse video Characters

终端颜色的一个妙用

颜色的使用可以使得我们对不同级别的内容进行强调,比如错误用红色显示,警告用黄色显示,这也是很多IDE所支持的,很多项目的开发都在shell下进行,对日志进行色彩化的输出就是一个很好的运用。

color c++ log

References

ANSI escape code colored-shell-script-output-library

分类
dpdk analysis

dpdk 线程管理之 线程创建

解析完参数以后,首先调用 eal_thread_init_master 将主线程绑定到指定的CPU上。代码如下

RTE_PER_LCORE(_lcore_id) = lcore_id; //设置当前线程的绑定CPU
/* set CPU affinity */
if (eal_thread_set_affinity() < 0)  //绑定 线程到指定CPU
     rte_panic("cannot set affinity\n");

eal_thread_set_affinity 代码如下,
unsigned lcore_id = rte_lcore_id(); //获取线程绑定的CPU

    /* acquire system unique id  */
    rte_gettid();

    /* update EAL thread core affinity */
    return rte_thread_set_affinity(&lcore_config[lcore_id].cpuset);//根据cpuset绑定

eal_thread_dump_affinity 函数 获取 主线程绑定了那些cpu。

创建工作线程,

 RTE_LCORE_FOREACH_SLAVE(i) {
            if (pipe(lcore_config[i].pipe_master2slave) < 0)    //创建主线程向工作线程的管道
                    rte_panic("Cannot create pipe\n");
            if (pipe(lcore_config[i].pipe_slave2master) < 0)    //创建工作线程向主线程的管道
                    rte_panic("Cannot create pipe\n");

            lcore_config[i].state = WAIT;

            /* create a thread for each lcore */
            ret = pthread_create(&lcore_config[i].thread_id, NULL,
                                 eal_thread_loop, NULL);       //创建工作线程
            if (ret != 0)
                    rte_panic("Cannot create thread\n");

            /* Set thread_name for aid in debugging. */
            snprintf(thread_name, RTE_MAX_THREAD_NAME_LEN,
                    "lcore-slave-%d", i);
            ret = rte_thread_setname(lcore_config[i].thread_id,
                                            thread_name); //设置工作线程的名字
            if (ret != 0)
                    RTE_LOG(DEBUG, EAL,
                            "Cannot set name for lcore thread\n");
}

线程起来以后将运行eal_thread_loop 函数, 该函数代码如下

thread_id = pthread_self();    //获取线程id

    /* retrieve our lcore_id from the configuration structure */
    RTE_LCORE_FOREACH_SLAVE(lcore_id) {
            if (thread_id == lcore_config[lcore_id].thread_id) //通过线程id获取配置id
                    break;
    }
    if (lcore_id == RTE_MAX_LCORE)
            rte_panic("cannot retrieve lcore id\n");

    m2s = lcore_config[lcore_id].pipe_master2slave[0];  //获取通讯管道
    s2m = lcore_config[lcore_id].pipe_slave2master[1];

    /* set the lcore ID in per-lcore memory area */
    RTE_PER_LCORE(_lcore_id) = lcore_id;

    /* set CPU affinity */
    if (eal_thread_set_affinity() < 0)  //设置线程CPU绑定
            rte_panic("cannot set affinity\n");

    ret = eal_thread_dump_affinity(cpuset, RTE_CPU_AFFINITY_STR_LEN);

    RTE_LOG(DEBUG, EAL, "lcore %u is ready (tid=%x;cpuset=[%s%s])\n",
            lcore_id, (int)thread_id, cpuset, ret == 0 ? "" : "...");

while (1) {
            void *fct_arg;

            /* wait command */
            do {
                    n = read(m2s, &c, 1);
            } while (n < 0 && errno == EINTR); //等待执行命令

            if (n <= 0)
                    rte_panic("cannot read on configuration pipe\n");

            lcore_config[lcore_id].state = RUNNING; //设置状态为执行

            /* send ack */
            n = 0;
            while (n == 0 || (n < 0 && errno == EINTR)) //回复主线程在执行命令
                    n = write(s2m, &c, 1);
            if (n < 0)
                    rte_panic("cannot write on configuration pipe\n");

            if (lcore_config[lcore_id].f == NULL)
                    rte_panic("NULL function pointer\n");

            /* call the function and store the return value */
            fct_arg = lcore_config[lcore_id].arg;
            ret = lcore_config[lcore_id].f(fct_arg); //执行主线程指定的函数
            lcore_config[lcore_id].ret = ret;
            rte_wmb();
            lcore_config[lcore_id].state = FINISHED; //设置状态为执行完成
    }

rte_eal_remote_launch 函数指定工作线程执行函数

int m2s = lcore_config[slave_id].pipe_master2slave[1];
    int s2m = lcore_config[slave_id].pipe_slave2master[0];

    if (lcore_config[slave_id].state != WAIT)
            return -EBUSY;

    lcore_config[slave_id].f = f;    //设置要执行的函数
    lcore_config[slave_id].arg = arg; //设置参数

    /* send message */
    n = 0;
    while (n == 0 || (n < 0 && errno == EINTR))
            n = write(m2s, &c, 1);   //发送命令
    if (n < 0)
            rte_panic("cannot write on configuration pipe\n");

    /* wait ack */
    do {
            n = read(s2m, &c, 1);  //等待回复
    } while (n < 0 && errno == EINTR);

    if (n <= 0)
            rte_panic("cannot read on configuration pipe\n");

    return 0;

通过以上分析,我们可以看到dpdk在初始化时,会根据用户命令行参数创建对应的线程,并将线程绑定到指定的cpu上。 用户通过rte_eal_remote_launch接口即可指定特定的线程执行特定的函数。

分类
未分类

线程通讯

线程通讯

线程与线程之间通信,除了直接访问共享变量之外,还有一种更安全的方式,那就是消息循环。

建立线程消息循环

除了每个窗口都有一个消息循环以外,每个线程也都可以有一个消息循环。但是需要注意的是,为了节约资源,线程默认情况下不会建立消息循环,因为不是所有线程都会去接收消息。但是,如果你在线程函数中调用获取消息的函数的话,系统就会自动为线程创建消息循环。

总的来说,创建一个线程的消息循环,格式大致如下:

“`C++
DWORD WINAPI 线程函数(LPVOID lpParam)
{
// 一些初始化操作

<pre><code>// 调用 PeekMessage 去让系统建立消息循环
// 因为参数的最后一个传入的是 PM_NOREMOVE,所以原来的消息依然存在消息循环中
// 这里并不去判断消息,只是为了建立消息循环
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
// 下面是消息循环主体
while (GetMessage(&msg, NULL, NULL, NULL))
{
switch (msg.message)
{
case 消息1:
消息1的处理;
break;
case 消息2:
消息2的处理;
break;
// 更多的消息处理
default:
// 和窗口消息循环不一样,线程消息循环可以没有默认消息处理
}
}
// 清理操作
return 0;
</code></pre>

}

<pre class="line-numbers prism-highlight" data-start="1"><code class="language-null"><br />&lt;p&gt;一旦你的线程函数执行在消息循环中,就会一直等待有人给这个线程发消息,直到线程收到了 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息之后,线程才会退出。&lt;/p&gt;

&lt;p&gt;注意,当 &lt;code&gt;GetMessage()&lt;/code&gt; 获取到了 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息的时候,就会返回 &lt;code&gt;FALSE&lt;/code&gt;。&lt;/p&gt;

&lt;h2&gt;向线程发消息&lt;/h2&gt;

&lt;p&gt;那么,怎样从另一个线程给某个指定的线程发消息呢?发送消息需要用到 &lt;code&gt;PostThreadMessage()&lt;/code&gt; 函数,这个函数的用法如下:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
BOOL PostThreadMessage(
DWORD idThread, // 目标线程的 ID
UINT Msg, // 要发送的消息
WPARAM wParam, // 消息的附加参数1
LPARAM IParam // 消息的附加参数2
);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这个函数的第一个参数,目标线程的ID 怎么获取呢?当我们在创建线程的时候,&lt;code&gt;CreateThread()&lt;/code&gt; 这个函数的最后一个参数返回的就是线程的ID,所以,传入这个ID就可以了。例如:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
DWORD id;
CreateThread(NULL, 0, fun, NULL, NULL, &amp;id);
PostThreadMessage(id, 消息, 0, 0);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;另外需要注意的一点是,因为线程执行时机的不确定性,当你调用 &lt;code&gt;PostThreadMessage()&lt;/code&gt; 的时候目标线程的消息循环可能还没建立,这个时候这个函数就会返回 FALSE。所以,你在发送线程消息的时候,需要检查一下这个函数的返回值,如果发送失败了,就需要重新发送这个消息。例如,当你确认目标线程消息循环是可用的时候,可以这样发送消息:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
while (!PostThreadMessage(id, 消息, 0, 0));&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;定义消息&lt;/h2&gt;

&lt;p&gt;如果你希望让你的线程退出,只需要发送一个 &lt;code&gt;WM_QUIT&lt;/code&gt; 消息就可以了。例如:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C++
PostThreadMessage(id, WM_QUIT, 0, 0);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;你可以向线程发送Windows定义的消息,也可以发送自定义的消息。可以看到,消息的变量类型是 &lt;code&gt;UINT&lt;/code&gt;,也就是说,任何正整数都可以当成消息发送。&lt;/p&gt;

&lt;p&gt;Windows 预定义了很多消息(1024条消息以内),如果你希望向线程发消息的话,使用的数字要大于 1024。微软将 &lt;code&gt;WM_USER&lt;/code&gt; 定义成了 1024,所以,你要定义自己的消息的话,可以这么定义:&lt;/p&gt;

&lt;p&gt;“`C++

<h1>define WM_MY_MESSAGE1 (WM_USER + 1)</h1>

<h1>define WM_MY_MESSAGE2 (WM_USER + 2)</h1>


下面给出一个例子,这个例子要实现:
1. 建议一个线程,线程建立一个消息循环;
2. 主线程给新建线程发送自定义的打印Hello world消息之后,新建线程输出Hello world

“`C++

define WM_HELLO_WORLD (WM_USER + 10086)

DWORD WINAPI fun(LPVOID lpParam)
{
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);

while (GetMessage(&msg, NULL, NULL, NULL))
{
    switch (msg.message)
    {
    case WM_HELLO_WORLD:
        printf("Hello wolrd!");
        break;
    }
}

return 0;

}

int main()
{
DWORD idThread;
HANDLE hThread = CreateThread(NULL, 0, fun, NULL, NULL, &idThread);

while(!PostThreadMessage(idThread, WM_HELLO_WORLD, 0, 0))
{
    Sleep(100);
}

PostThreadMessage(idThread, WM_QUIT, 0, 0);
WaitForSingleObject(hThread, INFINITE);

return 0;

}