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

發佈了30 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章