BSD net源码分析(2-3)

三、以太网接口数据输出
当网络层协议调用接口ifnet结构体中的if_output时,开始输出。所有以太网设备的if_output都指向ether_output函数,该函数封装以太网的头部,并将数据输入到接口的发送队列。
(1)验证接口状态:主要是接口状态的校验,判断接口是否启用。
/******************************************************/
    if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
        senderr(ENETDOWN);
    ifp->if_lastchange = time;
/*****************************************************/
(2)输出路由处理:包括主机路由和网关路由的处理,避免ARP泛滥处理
/*****************************************************/
    if (rt = rt0) {
        if ((rt->rt_flags & RTF_UP) == 0) {
            if (rt0 = rt = rtalloc1(dst, 1))
                rt->rt_refcnt--;
            else
                senderr(EHOSTUNREACH);
        }
        if (rt->rt_flags & RTF_GATEWAY) {
            if (rt->rt_gwroute == 0)
                goto lookup;
            if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) {
                rtfree(rt); rt = rt0;
            lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1);
                if ((rt = rt->rt_gwroute) == 0)
                    senderr(EHOSTUNREACH);
            }
        }
        if (rt->rt_flags & RTF_REJECT)
            if (rt->rt_rmx.rmx_expire == 0 ||
                time.tv_sec < rt->rt_rmx.rmx_expire)
                senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
    }
/*****************************************************/
rt0 上层协议(IP ip_output)找到的路由信息,该值也可能为空,此时将跳过路由验证部分直接进入分组处理。
如果输入的路由无效,参考路由表,获取一条有效的路由,rt0 = rt = rtalloc1(dst, 1)。
如果下一跳是网关,找到一条到网关的路由。
当路由的结果是RTF_REJECT,表示对方不准备响应一个ARP,此时丢弃改分组。

(3)分组的按不同的协议处理:
IP分组处理:
/***************************************************/
    case AF_INET:
        if (!arpresolve(ac, rt, m, dst, edst))
            return (0);    /* if not yet resolved */
        /* If broadcasting on a simplex interface, loopback a copy */
        if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX))
            mcopy = m_copy(m, 0, (int)M_COPYALL);
        off = m->m_pkthdr.len - m->m_len;
        type = ETHERTYPE_IP;
        break;
/***************************************************/
如果dst_safamily地址族是AF_INET,表示该分组携带IP协议数据。
首先,通过arpresolv函数查找IP地址到以太网地址的对应。如果ARP缓存中存在地址对应,返回1,否则,该IP分组将被ARP程序接管,直道ARP知道获取到具体的物理地址后,通过in_arpintr调用ether_output重新发送分组。ARP具体的实现,后面会有详细介绍。
如果数据是广播分组且接口是单向的,需要复制一份分组,并在后面将分组输入到接口的接收队列。

显式输出分组处理:
/********************************************************/
    case AF_UNSPEC:
        eh = (struct ether_header *)dst->sa_data;
         bcopy((caddr_t)eh->ether_dhost, (caddr_t)edst, sizeof (edst));
        type = eh->ether_type;
        break;
/*********************************************************/
地址族是AF_UNSPEC,表示某些显式的分组输出,由调用者显式的指定目标的物理地址,类别字段为接口首部的地址类别。

除了以上的两种类型的分组外,还有ISO和AF_NS类别的分组,在这里不详细介绍。

如果之前有拷贝分组,在对不同类型的分组处理完后调用
    if (mcopy)
        (void) looutput(ifp, mcopy, dst, rt);
将分组输入到接口输入队列,具体的实现在loop接口代码分析时详细解析。

(4)帧构造:构造以太网的数据帧。
/***************************************************/
    /*
     * Add local net header.  If no space in first mbuf,
     * allocate another.
     */
    M_PREPEND(m, sizeof (struct ether_header), M_DONTWAIT);
    if (m == 0)
        senderr(ENOBUFS);
    eh = mtod(m, struct ether_header *);
    type = htons((u_short)type);
    bcopy((caddr_t)&type,(caddr_t)&eh->ether_type,
        sizeof(eh->ether_type));
     bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst));
     bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost,
        sizeof(eh->ether_shost));
/******************************************************/
构造帧主要的工作是构造以太网的帧首部。

(5)分组入接口输出队列
/******************************************************/
    s = splimp();
    /*
     * Queue message on interface, and start output if interface
     * not yet active.
     */
    if (IF_QFULL(&ifp->if_snd)) {
        IF_DROP(&ifp->if_snd);
        splx(s);
        senderr(ENOBUFS);
    }
    IF_ENQUEUE(&ifp->if_snd, m);
    if ((ifp->if_flags & IFF_OACTIVE) == 0)
        (*ifp->if_start)(ifp);
    splx(s);
    ifp->if_obytes += len + sizeof (struct ether_header);
    if (m->m_flags & M_MCAST)
        ifp->if_omcasts++;
    return (error);
/******************************************************/
最后部分是将分组入输出队列,如果队列满了就丢弃该分组,并返回没有缓存的错误码。
入队后,判断接口是否激活,如果未激活,需要调用if_start实例启动接口的输出。
函数的末尾更新接口统计信息并返回。

if_start指向具体设备的驱动函数,如lestart函数。
lestart首先检查接口是后工作,如果不工作直接返回。
/*********************************************/
    tmd = &le->sc_r2->ler2_tmd[le->sc_tmd];
    do {
        if (tmd->tmd1 & LE_OWN) {
            le->sc_xown2++;
            return (0);
        }
        IF_DEQUEUE(&le->sc_if.if_snd, m);
        if (m == 0)
            return (0);
        len = leput(le->sc_r2->ler2_tbuf[le->sc_tmd], m);
#if NBPFILTER > 0
        /*
         * If bpf is listening on this interface, let it
         * see the packet before we commit it to the wire.
         */
        if (ifp->if_bpf)
            bpf_tap(ifp->if_bpf, le->sc_r2->ler2_tbuf[le->sc_tmd],
                len);
#endif

        tmd->tmd3 = 0;
        tmd->tmd2 = -len;
        tmd->tmd1 = LE_OWN | LE_STP | LE_ENP;
        if (++le->sc_tmd == LETBUF) {
            le->sc_tmd = 0;
            tmd = le->sc_r2->ler2_tmd;
        } else
            tmd++;
    } while (++le->sc_txcnt < LETBUF);
    le->sc_if.if_flags |= IFF_OACTIVE;
    return (0);
/*********************************************/
数据退队:一旦开始数据输出,函数会循环的从输出队列中退队并写入硬件缓存中。
bpf输出:如果接口注册了bpf,此时也会调用bpf_tap扔出数据。
输出结束:当输出队列为空或者接口发送的数据包等于接口的最大输出缓存时,会跳出循环,并将接口置为输出激活状态。

到此,完成了一个数据分组输出的全过程。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mythfish/archive/2008/10/26/3152370.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值