BSD net源码分析(3)──SLIP接口

来源:互联网 发布:养老保险的算法 编辑:程序博客网 时间:2024/05/22 06:54

一、SLIP接口概述
SLIP接口通过一个标准的异步串行线与一个远程系统通信。通过SLIP帧为上层承载IP分组。
每个分组使用0xc0来隔开,如果分组中出现该字符,需要该字符前填充字符0xdb,并将该字符转换为0xdc。如果字符中出现0xdb,在字符前填充0xdb,并替换字符为0xdd。

SLIP接口依靠一个异步串行设备驱动器来发送和接收数据,驱动器称为tty。tty子系统包括一个线路规程(Line discipline)。这个线程作为物理设备和I/O系统调用(read和write)之间的过滤器。
一个线路规程实现以下的特性:如行编辑、换行和回车处理、制表符扩展等等。
SLIP接口作为tty子系统的一个线路规程,但它不把输入数据传给从设备读取数据的进程,也不接受来自设备写数据的进程的输出数据。SLIP接口将输入分组传给IP输入队列,并通过SLIP的ifnet结构的if_output来获得要输出的分组。内核通过一个整数常量来标识线路规程,对于SLIP,该常量是SLIPDISC。

函   数     网络接口 线路规程                   说  明
                         
slattach   •                    初始化s l _ s o f t c结构,并将它连接到 i f n e t列表
slinit         •                    初始化SLIP数据结构
sloutput   •                    对相关TTY设备上要传输的输出分组进行排队
slioctl       •                    处理插口i o c t l请求
sl_btom    •                    将一个设备缓存转换成一个 m b u f链表
slopen             •             将s l _ s o f t c结构连接到 TTY设备,并初始化驱动程序
slclose             •             取消TTY设备与s l _ s o f t c结构的连接,标记接口为关闭,并释放存储器
sltioctl             •             处理TTY i o c t l命令
slstart     •       •             从队列中取分组,并开始在 TTY设备上传输数据
slinput     •       •             处理从TTY设备输入的字节,如果整个帧被接收,就排列输入的分组

二、SLIP接口初始化
SLIP接口的驱动入口函数只有两个,if_output指向sloutput,用于接收将要传输的分组进行排队,if_ioctl指向slioctl,用于从一个进程控制控制接口。
/**********************************/
/* Attach pseudo-devices. */
    for (pdev = pdevinit; pdev->pdev_attach != NULL; pdev++)
        (*pdev->pdev_attach)(pdev->pdev_count);

    /*
     * Initialize protocols.  Block reception of incoming packets
     * until everything is ready.
     */
    s = splimp();
    ifinit();
    domaininit();
    splx(s);
/**********************************/
SLIP和环回接口,通过软件实现,而不通过内核发现硬件,中断触发进行初始化。
这些伪设备通过一个全局变量pdevinit存储。
/*************************************/
/*
 * Pseudo-device attach information (function + number of pseudo-devs).
 */
struct pdevinit {
    void    (*pdev_attach) __P((int));
    int    pdev_count;
};
/*************************************/
pdev_attach 指向初始化的函数
pdev_count 接口的个数
初始化SLIP接口时,pdev_attach指向函数slattach()。
和以太网接口一样,SLIP接口也有专用的ifnet结构体存放接口信息。
/****************************************/
/*
 * Definitions for SLIP interface data structures
 *
 * (This exists so programs like slstats can get at the definition
 *  of sl_softc.)
 */
struct sl_softc {
    struct    ifnet sc_if;        /* network-visible interface */
    struct    ifqueue sc_fastq;    /* interactive output queue */
    struct    tty *sc_ttyp;        /* pointer to tty structure */
    u_char    *sc_mp;            /* pointer to next available buf char */
    u_char    *sc_ep;            /* pointer to last available buf char */
    u_char    *sc_buf;        /* input buffer */
    u_int    sc_flags;        /* see below */
    u_int    sc_escape;    /* =1 if last char input was FRAME_ESCAPE */
    long    sc_lasttime;        /* last time a char arrived */
    long    sc_abortcount;        /* number of abort esacpe chars */
    long    sc_starttime;        /* time of first abort in window */
#ifdef INET                /* XXX */
    struct    slcompress sc_comp;    /* tcp compression data */
#endif
    caddr_t    sc_bpf;            /* BPF data */
};
struct sl_softc sl_softc[NSL];
/***************************************/
sc_if 通用的ifnet结构,保存接口共有的信息
结构体的其他信息为SLIP接口特有的信息

NSL 内核配置的支持最大SLIP接口个数。

slattach()函数:
/*******************************************/
void
slattach()
{
    register struct sl_softc *sc;
    register int i = 0;

    for (sc = sl_softc; i < NSL; sc++) {
        sc->sc_if.if_name = "sl";
        sc->sc_if.if_next = NULL;
        sc->sc_if.if_unit = i++;
        sc->sc_if.if_mtu = SLMTU;
        sc->sc_if.if_flags =
            IFF_POINTOPOINT | SC_AUTOCOMP | IFF_MULTICAST;
        sc->sc_if.if_type = IFT_SLIP;
        sc->sc_if.if_ioctl = slioctl;
        sc->sc_if.if_output = sloutput;
        sc->sc_if.if_snd.ifq_maxlen = 50;
        sc->sc_fastq.ifq_maxlen = 32;
        if_attach(&sc->sc_if);
#if NBPFILTER > 0
        bpfattach(&sc->sc_bpf, &sc->sc_if, DLT_SLIP, SLIP_HDRLEN);
#endif
    }
}
/*******************************************/
该函数循环的初始化NSL个SLIP接口,设置接口标记,以及一些接口驱动函数的入口。
之后再通过函数if_attach将接口插入接口链表。

以上是对SLIP接口结构的初始化过程。要使用SLIP接口,除了初始化接口结构外,还需要打开一个tty设备,并发送ioctl命令用SLIP规程代替标准的线路规程。同时tty子系统调用线路规程打开函数(slopen),此函数在一个特定tty设备和一个特定SLIP接口间建立关系。
slopen函数
/********************************************/
/*
 * Line specific open routine.
 * Attach the given tty to the first available sl unit.
 */
/* ARGSUSED */
int
slopen(dev, tp)
    dev_t dev;
    register struct tty *tp;
{
    struct proc *p = curproc;        /* XXX */
    register struct sl_softc *sc;
    register int nsl;
    int error;
    int s;

    if (error = suser(p->p_ucred, &p->p_acflag))    /* 判断是否超级用户 */
        return (error);

    if (tp->t_line == SLIPDISC)     /* 如果不是SLIP线程,直接返回*/
        return (0);

    for (nsl = NSL, sc = sl_softc; --nsl >= 0; sc++)
        if (sc->sc_ttyp == NULL) {  /* 查找一个未使用的项 */
            if (slinit(sc) == 0)   
                return (ENOBUFS);
            tp->t_sc = (caddr_t)sc;
            sc->sc_ttyp = tp;   /* 关联tty和slip线路 */
            sc->sc_if.if_baudrate = tp->t_ospeed;
            s = spltty();
            tp->t_state |= TS_ISOPEN | TS_XCLUDE;   /* 设置tty的状态 */
            splx(s);
            ttyflush(tp, FREAD | FWRITE); /* 丢弃任何tty队列中输入和输出的数据 */
            return (0);
        }
    return (ENXIO);
}
/********************************************/
slinit函数,初始化SLIP接口专用的结构,初始化接收发送缓存
/********************************************/
static int
slinit(sc)
    register struct sl_softc *sc;
{
    register caddr_t p;

    if (sc->sc_ep == (u_char *) 0) {
        MCLALLOC(p, M_WAIT);    /* 分配一个mbuf */
        if (p)
            sc->sc_ep = (u_char *)p + SLBUFSIZE; /* sc_ep指向这个簇的结束 */
        else {  /* 如果分配失败,禁用该接口 */
            printf("sl%d: can't allocate buffer/n", sc - sl_softc);
            sc->sc_if.if_flags &= ~IFF_UP;
            return (0);
        }
    }
    sc->sc_buf = sc->sc_ep - SLMAX;     /* sc_buf 指向簇中分组的起始位置 */
    sc->sc_mp = sc->sc_buf;     /* sc_mp指向要接收的下一个字节的位置 */
    sl_compress_init(&sc->sc_comp);     /* 初始化TCP首部的压缩状态 */
    return (1);
}
/********************************************/
sc_buf并不指向簇的第一个字节,slinit保留148字节(BUFOFFSET)的空间,为输入分组的压缩首部做扩展。

三、SLIP接口的输入处理
tty驱动程序每次调用slinput函数,输入字符传给SLIP线路规程。
slinput函数
/********************************************/
/*
 * tty interface receiver interrupt.
 */
void
slinput(c, tp)
    register int c;     /* 输入的字符 */
    register struct tty *tp; /* tty设备的指针 */
{
    register struct sl_softc *sc;
    register struct mbuf *m;
    register int len;
    int s;
#if NBPFILTER > 0
    u_char chdr[CHDR_LEN];
#endif

    tk_nin++;
    sc = (struct sl_softc *)tp->t_sc;  /* 线程对应的接口 */ 
    if (sc == NULL)
        return;
    if ((c & TTY_ERRORMASK) || ((tp->t_state & TS_CARR_ON) == 0 &&
        (tp->t_cflag & CLOCAL) == 0)) {     /* 丢弃错误的字符 */
        sc->sc_flags |= SC_ERROR;
        return;
    }
    c &= TTY_CHARMASK;  /* 丢弃c中的控制比特*/

    ++sc->sc_if.if_ibytes;  /* 更新接口接收到的字节计数 */

    if (sc->sc_if.if_flags & IFF_DEBUG) {
        if (c == ABT_ESC) {
            /*
             * If we have a previous abort, see whether
             * this one is within the time limit.
             */
            if (sc->sc_abortcount &&
                time.tv_sec >= sc->sc_starttime + ABT_WINDOW)
                sc->sc_abortcount = 0;
            /*
             * If we see an abort after "idle" time, count it;
             * record when the first abort escape arrived.
             */
            if (time.tv_sec >= sc->sc_lasttime + ABT_IDLE) {
                if (++sc->sc_abortcount == 1)
                    sc->sc_starttime = time.tv_sec;
                if (sc->sc_abortcount >= ABT_COUNT) {
                    slclose(tp);
                    return;
                }
            }
        } else
            sc->sc_abortcount = 0;
        sc->sc_lasttime = time.tv_sec;
    }

    switch (c) {

    case TRANS_FRAME_ESCAPE:
        if (sc->sc_escape)
            c = FRAME_ESCAPE;   /* ESC转义字符替换成ESC */
        break;

    case TRANS_FRAME_END:
        if (sc->sc_escape)
            c = FRAME_END;      /* END转义字符替换成END */
        break;

    case FRAME_ESCAPE:
        sc->sc_escape = 1;      /* 记录END,并返回 */
        return;

    case FRAME_END:
        if(sc->sc_flags & SC_ERROR) {   /* 如果是错误,直接丢弃该包 */
            sc->sc_flags &= ~SC_ERROR;
            goto newpack;
        }
        len = sc->sc_mp - sc->sc_buf;   /* 计算包的长度 */
        if (len < 3)    /* 小于3,直接忽略 */
            /* less than min length packet - ignore */
            goto newpack;

#if NBPFILTER > 0
        if (sc->sc_bpf) {   /* 注册了sc_bpf,需要拷贝一份数据 */
            /*
             * Save the compressed header, so we
             * can tack it on later.  Note that we
             * will end up copying garbage in some
             * cases but this is okay.  We remember
             * where the buffer started so we can
             * compute the new header length.
             */
            bcopy(sc->sc_buf, chdr, CHDR_LEN);
        }
#endif

        if ((c = (*sc->sc_buf & 0xf0)) != (IPVERSION << 4)) {   /* 不是IP分组 */
            if (c & 0x80)   /* 压缩TCP分段 */
                c = TYPE_COMPRESSED_TCP;
            else if (c == TYPE_UNCOMPRESSED_TCP)    /* 未压缩TCP分段  */
                *sc->sc_buf &= 0x4f; /* XXX */
            /*
             * We've got something that's not an IP packet.
             * If compression is enabled, try to decompress it.
             * Otherwise, if `auto-enable' compression is on and
             * it's a reasonable packet, decompress it and then
             * enable compression.  Otherwise, drop it.
             */
            if (sc->sc_if.if_flags & SC_COMPRESS) {     /* 分组允许压缩,sl_uncompress_tcp解压缩 */
                len = sl_uncompress_tcp(&sc->sc_buf, len,
                            (u_int)c, &sc->sc_comp);
                if (len <= 0)
                    goto error;
            } else if ((sc->sc_if.if_flags & SC_AUTOCOMP) &&
                c == TYPE_UNCOMPRESSED_TCP && len >= 40) { /* 设置了自动允许压缩,并且分组足够大,仍然调用解压缩 */
                len = sl_uncompress_tcp(&sc->sc_buf, len,
                            (u_int)c, &sc->sc_comp);
                if (len <= 0)
                    goto error;
                sc->sc_if.if_flags |= SC_COMPRESS; /* 设置允许压缩标志 */
            } else
                goto error;
        }
#if NBPFILTER > 0
        if (sc->sc_bpf) {
            /*
             * Put the SLIP pseudo-"link header" in place.
             * We couldn't do this any earlier since
             * decompression probably moved the buffer
             * pointer.  Then, invoke BPF.
             */
            register u_char *hp = sc->sc_buf - SLIP_HDRLEN;

            hp[SLX_DIR] = SLIPDIR_IN;   /* 标记分组的方向为输入 */
            bcopy(chdr, &hp[SLX_CHDR], CHDR_LEN);   /* 拷贝压缩的首部 */
            bpf_tap(sc->sc_bpf, hp, len + SLIP_HDRLEN); /*  将首部和分组传给bpf */
        }
#endif
        m = sl_btom(sc, len);   /* 将簇转换为一个mbuf链表 */
        if (m == NULL)
            goto error;

        sc->sc_if.if_ipackets++;    /* 更新统计 */
        sc->sc_if.if_lastchange = time;
        s = splimp();   /* 将分组放入ipintrq队列 */
        if (IF_QFULL(&ipintrq)) {
            IF_DROP(&ipintrq);
            sc->sc_if.if_ierrors++;
            sc->sc_if.if_iqdrops++;
            m_freem(m);
        } else {
            IF_ENQUEUE(&ipintrq, m);
            schednetisr(NETISR_IP);
        }
        splx(s);
        goto newpack;
    }
    if (sc->sc_mp < sc->sc_ep) {    /* 将输入的字符放入簇中 */
        *sc->sc_mp++ = c;
        sc->sc_escape = 0;  /* 清除接收到ESC的标志 */
        return;
    }

    /* can't put lower; would miss an extra frame */
    sc->sc_flags |= SC_ERROR;

error:
    sc->sc_if.if_ierrors++;
newpack: /* 开始接收新的分组,清除记录 */
    sc->sc_mp = sc->sc_buf = sc->sc_ep - SLMAX;
    sc->sc_escape = 0;
}
/********************************************/

四、SLIP接口的输出处理
sloutput函数
当一个网络层协议调用接口的if_output(sloutput)函数时,开始处理输出。
/*******************************************/
/*
 * Queue a packet.  Start transmission if not active.
 * Compression happens in slstart; if we do it here, IP TOS
 * will cause us to not compress "background" packets, because
 * ordering gets trashed.  It can be done for all packets in slstart.
 */
int
sloutput(ifp, m, dst, rtp)
    struct ifnet *ifp;
    register struct mbuf *m;
    struct sockaddr *dst;
    struct rtentry *rtp;
{
    register struct sl_softc *sc = &sl_softc[ifp->if_unit];
    register struct ip *ip;
    register struct ifqueue *ifq;
    int s;

    /*
     * `Cannot happen' (see slioctl).  Someday we will extend
     * the line protocol to support other address families.
     */
    if (dst->sa_family != AF_INET) {    /* 目标地址是否IP地址 */
        printf("sl%d: af%d not supported/n", sc->sc_if.if_unit,
            dst->sa_family);
        m_freem(m);
        sc->sc_if.if_noproto++;
        return (EAFNOSUPPORT);
    }

    if (sc->sc_ttyp == NULL) {          /* 是否打开了tty */
        m_freem(m);
        return (ENETDOWN);    /* sort of */
    }
    if ((sc->sc_ttyp->t_state & TS_CARR_ON) == 0 &&
        (sc->sc_ttyp->t_cflag & CLOCAL) == 0) {     /* 接口是否正在运行 */
        m_freem(m);
        return (EHOSTUNREACH);
    }
    ifq = &sc->sc_if.if_snd;
    ip = mtod(m, struct ip *);
    if (sc->sc_if.if_flags & SC_NOICMP && ip->ip_p == IPPROTO_ICMP) { /* 如果接口被设置为SC_NOICMP且分组是ICMP则丢弃 */
        m_freem(m);
        return (ENETRESET);        /* XXX ? */
    }
    if (ip->ip_tos & IPTOS_LOWDELAY)    /* 如果输出的分组TOS字段指明为低时延服务,输出队列改为sc_fastq */
        ifq = &sc->sc_fastq;
    s = splimp();
    if (IF_QFULL(ifq)) {
        IF_DROP(ifq);
        m_freem(m);
        splx(s);
        sc->sc_if.if_oerrors++;
        return (ENOBUFS);
    }
    IF_ENQUEUE(ifq, m);
    sc->sc_if.if_lastchange = time;
    if (sc->sc_ttyp->t_outq.c_cc == 0) /* 如果tty的输出队列是空的,调用slstart往tty队列送数据 */
        slstart(sc->sc_ttyp);
    splx(s);
    return (0);
}
/*******************************************/
sloutput说明了SLIP接口在接口层对输出数据的处理。
其中的slstart函数用于往tty设备写入输出的数据。
tty子系统通过一个clist结构管理它的队列,如果输出队列t_outq不为空,
slstart调用设备的输出函数t_oproc,当队列的剩余字节数超过一定值,slstart返回。
如果tty的队列为空,从sc_fastq中退队一个分组,或者若sc_fastq为空,则从if_snd队列退队一个分组。

slstart()函数
/*******************************************/
/*
 * Start output on interface.  Get another datagram
 * to send from the interface queue and map it to
 * the interface before starting output.
 */
void
slstart(tp)
    register struct tty *tp;
{
    register struct sl_softc *sc = (struct sl_softc *)tp->t_sc;
    register struct mbuf *m;
    register u_char *cp;
    register struct ip *ip;
    int s;
    struct mbuf *m2;
#if NBPFILTER > 0
    u_char bpfbuf[SLMTU + SLIP_HDRLEN];
    register int len;
#endif
    extern int cfreecount;

    for (;;) {
        /*
         * If there is more in the output queue, just send it now.
         * We are being called in lieu of ttstart and must do what
         * it would.
         */
        if (tp->t_outq.c_cc != 0) {
            (*tp->t_oproc)(tp);
            if (tp->t_outq.c_cc > SLIP_HIWAT)
                return;
        }
        /*
         * This happens briefly when the line shuts down.
         */
        if (sc == NULL)
            return;

        /*
         * Get a packet and send it to the interface.
         */
        //从sc_fastq队列退队或从if_snd队列退队
        s = splimp();
        IF_DEQUEUE(&sc->sc_fastq, m);
        if (m)
            sc->sc_if.if_omcasts++;        /* XXX */
        else
            IF_DEQUEUE(&sc->sc_if.if_snd, m);
        splx(s);
        if (m == NULL)
            return;

        /*
         * We do the header compression here rather than in sloutput
         * because the packets will be out of order if we are using TOS
         * queueing, and the connection id compression will get
         * munged when this happens.
         */
#if NBPFILTER > 0
        if (sc->sc_bpf) {
            /*
             * We need to save the TCP/IP header before it's
             * compressed.  To avoid complicated code, we just
             * copy the entire packet into a stack buffer (since
             * this is a serial line, packets should be short
             * and/or the copy should be negligible cost compared
             * to the packet transmission time).
             */
            register struct mbuf *m1 = m;
            register u_char *cp = bpfbuf + SLIP_HDRLEN;

            len = 0;
            do {
                register int mlen = m1->m_len;

                bcopy(mtod(m1, caddr_t), cp, mlen);
                cp += mlen;
                len += mlen;
            } while (m1 = m1->m_next);
        }
#endif
        //压缩TCP首部
        if ((ip = mtod(m, struct ip *))->ip_p == IPPROTO_TCP) {
            if (sc->sc_if.if_flags & SC_COMPRESS)
                *mtod(m, u_char *) |= sl_compress_tcp(m, ip,
                    &sc->sc_comp, 1);
        }
#if NBPFILTER > 0
        //分组数据送到bpf_tap
        if (sc->sc_bpf) {
            /*
             * Put the SLIP pseudo-"link header" in place.  The
             * compressed header is now at the beginning of the
             * mbuf.
             */
            bpfbuf[SLX_DIR] = SLIPDIR_OUT;
            bcopy(mtod(m, caddr_t), &bpfbuf[SLX_CHDR], CHDR_LEN);
            bpf_tap(sc->sc_bpf, bpfbuf, len + SLIP_HDRLEN);
        }
#endif
        sc->sc_if.if_lastchange = time;

        /*
         * If system is getting low on clists, just flush our
         * output queue (if the stuff was important, it'll get
         * retransmitted).
         */
        if (cfreecount < CLISTRESERVE + SLMTU) {
            m_freem(m);
            sc->sc_if.if_collisions++;
            continue;
        }
        /*
         * The extra FRAME_END will start up a new packet, and thus
         * will flush any accumulated garbage.  We do this whenever
         * the line may have been idle for some time.
         */
        //当SLIP线为空时,往t_outq队列输出FRAME_END
        if (tp->t_outq.c_cc == 0) {
            ++sc->sc_if.if_obytes;
            (void) putc(FRAME_END, &tp->t_outq);
        }

        while (m) {
            register u_char *ep;

            cp = mtod(m, u_char *); ep = cp + m->m_len;
            while (cp < ep) {
                /*
                 * Find out how many bytes in the string we can
                 * handle without doing something special.
                 */
                //发送特殊字符外的其他字符
                register u_char *bp = cp;

                while (cp < ep) {
                    switch (*cp++) {
                    case FRAME_ESCAPE:
                    case FRAME_END:
                        --cp;
                        goto out;
                    }
                }
                out:
                if (cp > bp) {
                    /*
                     * Put n characters at once
                     * into the tty output queue.
                     */
                    if (b_to_q((char *)bp, cp - bp,
                        &tp->t_outq))
                        break;
                    sc->sc_if.if_obytes += cp - bp;
                }
                /*
                 * If there are characters left in the mbuf,
                 * the first one must be special..
                 * Put it out in a different form.
                 */
                //发送特殊字符
                if (cp < ep) {
                    if (putc(FRAME_ESCAPE, &tp->t_outq))
                        break;
                    if (putc(*cp++ == FRAME_ESCAPE ?
                       TRANS_FRAME_ESCAPE : TRANS_FRAME_END,
                       &tp->t_outq)) {
                        (void) unputc(&tp->t_outq);
                        break;
                    }
                    sc->sc_if.if_obytes += 2;
                }
            }
            MFREE(m, m2);
            m = m2;
        }

        //发送帧结束字符
        if (putc(FRAME_END, &tp->t_outq)) {
            /*
             * Not enough room.  Remove a char to make room
             * and end the packet normally.
             * If you get many collisions (more than one or two
             * a day) you probably do not have enough clists
             * and you should increase "nclist" in param.c.
             */
            (void) unputc(&tp->t_outq);
            (void) putc(FRAME_END, &tp->t_outq);
            sc->sc_if.if_collisions++;
        } else {
            ++sc->sc_if.if_obytes;
            sc->sc_if.if_opackets++;
        }
    }
}
/*************************************************************/
该函数的函数体内部是个大的循环,从数据输出队列退队分组,并组成SLIP帧。

五、SLIP的性能考虑
(1)小的MTU值能改善交互数据的延迟,但会降低批量数据的吞吐量。
大的MTU能改进批量数据的吞吐量,但会增加交互时延。
如果为交互比较频繁的应用提供服务,可以采用一个足够大的MTU满足交互响应时间和吞吐率的要求。
如果在SLIP上传输TCP/IP分组,采用压缩TCP/IP首部来减少每个分组的负荷。
(2)SLIP_HIWAT的大小会影响到slstart调用的频率。
如果值太大,会有比较多的数据在缓存中,新的交互数据不能及时的发送。如果值太小,会比较频繁的调用slstart函数。
(3)SLIP驱动程序提供了TOS排队。
其策略是先从sc_fastq队列中发送交互数据,然后在标准接口队列if_snd中发送其他的通信数据

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