一、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