TCP/IP详解卷2之插口层

插口层概述

主要功能

将进程发送的与协议有关的请求映射到产生插口时制定的与协议有关的实现。
TCP/IP协议相当复杂,因为是人为设定的规则,在不停的增加功能下,十分臃肿,需要			我们记忆的地方非常多。
建立一个基本的流程框架,再在此基础上补充完善。
插口层的调用函数,大家所熟知的有
socket函数插口的创建,
bind函数将本地的网络运输层地址和插口联系起来,
listen函数通知协议进程准备接收插口上的连接请求,并制定插口上可以排	队等候的连接数的门限值,
accept函数等待连接请求
connect函数建立一条与制定的外部地址的连接,
shutdown关闭连接的读通道、写通道或读写通道
close关闭各类描述符……

本文主要介绍插口及相关的系统调用的内部实现
在这里插入图片描述

splnet处理

	插口中包含很多对splnet和splx的成对调用
	这些调用保护访问在插口层和协议处理层间共享的数据结构的代码
	关于splnet以及splx这些调用,详情请见文章:

socket结构

插口代表一条通信链路的一端,存储或指向与链路有关的所有信息。信息包括:
使用的协议、协议的状态信息(包括源和目的地址)、到达的连接队列、数据缓存和可选标志。

正确理解socket结构的每一个变量十分重要!!!!每个变量都有十分丰富的语义

/*
 * Kernel structure per socket.
 * Contains send and receive buffer queues,
 * handle on protocol and pointer to protocol
 * private data and error information.
 */
struct socket {
	short	so_type;		/* generic type, see socket.h */
	short	so_options;		/* from socket call, see socket.h */
	short	so_linger;		/* time to linger while closing */
	short	so_state;		/* internal state flags SS_*, below */
	caddr_t	so_pcb;			/* protocol control block */
	struct	protosw *so_proto;	/* protocol handle */
/*
 * Variables for connection queueing.
 * Socket where accepts occur is so_head in all subsidiary sockets.
 * If so_head is 0, socket is not related to an accept.
 * For head socket so_q0 queues partially completed connections,
 * while so_q is a queue of connections ready to be accepted.
 * If a connection is aborted and it has so_head set, then
 * it has to be pulled out of either so_q0 or so_q.
 * We allow connections to queue up based on current queue lengths
 * and limit on number of queued connections for this socket.
 */
	struct	socket *so_head;	/* back pointer to accept socket */
	struct	socket *so_q0;		/* queue of partial connections */
	struct	socket *so_q;		/* queue of incoming connections */
	short	so_q0len;		/* partials on so_q0 */
	short	so_qlen;		/* number of connections on so_q */
	short	so_qlimit;		/* max number queued connections */
	short	so_timeo;		/* connection timeout */
	u_short	so_error;		/* error affecting connection */
	pid_t	so_pgid;		/* pgid for signals */	//如果SS_ASYNC被设置,则SIGIO信号被发送给进程(so_pgid>0)或进程组(so_pgid<0)
	u_long	so_oobmark;		/* chars to oob mark */	//标识在输入数据流中最近收到的带外数据的开始点
/*
 * Variables for socket buffering.
 */
	struct	sockbuf {
		u_long	sb_cc;		/* actual chars in buffer */
		u_long	sb_hiwat;	/* max actual char count */
		u_long	sb_mbcnt;	/* chars of mbufs used */
		u_long	sb_mbmax;	/* max chars of mbufs to use */
		long	sb_lowat;	/* low water mark */
		struct	mbuf *sb_mb;	/* the mbuf chain */
		struct	selinfo sb_sel;	/* process selecting read/write */
		short	sb_flags;	/* flags, see below */
		short	sb_timeo;	/* timeout for read/write */
	} so_rcv, so_snd;
#define	SB_MAX		(256*1024)	/* default for max chars in sockbuf */
#define	SB_LOCK		0x01		/* lock on data queue */
#define	SB_WANT		0x02		/* someone is waiting to lock */
#define	SB_WAIT		0x04		/* someone is waiting for data/space */
#define	SB_SEL		0x08		/* someone is selecting */
#define	SB_ASYNC	0x10		/* ASYNC I/O, need signals */
#define	SB_NOTIFY	(SB_WAIT|SB_SEL|SB_ASYNC)
#define	SB_NOINTR	0x40		/* operations not interruptible */

	caddr_t	so_tpcb;		/* Wisc. protocol control block XXX */
	void	(*so_upcall) __P((struct socket *so, caddr_t arg, int waitf));
	caddr_t	so_upcallarg;		/* Arg for above */
};

so_type由产生插口的进程来制定,它指明插口和相关协议支持的通信语义。对于UDP,so_type = SOCK_DGRAM,对于TCP,so_type=SOCK_STREAM

so_linger等于当关闭一条连接时插口继续发送数据的时间间隔(单位为一个时钟滴答)

so_options是一组改变插口行为的标志
so_options的值
so_state表示插口的内部状态和一些其他的特点
so_state的值

设置了SO_ACCEPTCONN标志的插口维护两个队列,分别为还没有完全建立的连接队列so_q0和已经建立或将被接受的链接so_q,每一个被排队的插口中,so_head指向设置了SO_ACCEPTCONN的源插口。
so_qlimit控制可排队的连接数
对于TCP协议,so_q0队列存储TCP的三次握手还未完成时的连接,so_q存储TCP的三次握手已经完成的连接

在这里插入图片描述

每一个岔口包含两个数据缓存(struct sockbuf),so_rcv和so_snd,分别用来缓存接收或放的数据。后续会经常用到里面的变量进行流量控制,阻塞控制等等

系统调用

进程同内核交互是通过一组定义好的函数来进行的,这些函数称为系统调用。
BSD内核中,每一个系统调用均有一个编号,当进程执行一个系统调用时,硬件被配置成仅传送控制给一个内核函数,将标识系统调用的整数作为参数传给该内核函数。syscall将会利用系统调用的编号,在表中找到请求的系统调用的sysent结构。

extern struct sysent {		/* system call table */
	int	sy_narg;	/* number of arguments */
	int	(*sy_call)();	/* implementing function */
} sysent[];

sysent数组中存储有对应的系统调用函数。
syscall将参数从调用进程复制到内核中,并且分配一个数组来保存调用的结果。然后,当系统调用执行完毕后,syscall将结果返回给进程。

struct sysent *callp;
/*
参数说明:p	进程表项	args	作为参数传给系统调用	rval用来保存系统调用的返回结果的数组
*/
error = (*callp->sycall) (p,args,rval);

注意系统调用指的是sysycall调用的内核中的函数,而不是应用调用的进中的函数
syscall期望系统调用函数在没有差错时返回0,否则返回非0的差错代码
实现系统调用的函数“返回”两个值,一个给syscall函数;在没有差错的情况下,syscall将另一个(在rval中)返回给调用进程。
在这里插入图片描述
网络系统调用流程图
在这里插入图片描述

大多数插口系统调用至少被分成两个函数。
第一个函数从进程那里获取需要的数据,然后调用第二个函数soxxx来完成功能处理,然后返回结果给进程

进程、描述符和插口

在这里插入图片描述

如上图所示,实现系统调用的第一个参数总是指向调用进程proc的指针p,内核利用proc结构记录进程的有关信息。
p_fd指向filedesc结构,该结构的主要功能是管理fd_ofiles指向的描述符表。描述符表的大小是动态变化的,由一个指向file结构的指针数组组成。每一个file结构描述一个打开的文件,该结构可以被多个进程共享。
f_ops指向fileops结构,该结构包含一张实现read、write、ioctl、select和close系统调用的函数指针表(对于插口而言,指向一个全局的fileops结构,socketops)
f_data指向相关I/O对象的专用数据。对于插口而言,f_data指向与描述符相关的socket结构。
so_proto指向产生插口时选中的协议的protosw结构。

socket系统调用

插口的系统调用函数,十分复杂,牵扯的内容十分多,并且分散,包含对各种状态的判断,内存的释放,清理。需要结合各种场景的知识,不易理解,可以先理解基本实现流程,然后再将各种额外的功能添加上,进行理解,尤其是对于各种差错代码的判断

socket系统调用产生一个新的插口,并将插口通津城在参数domain\type和protocol中指定的协议联系起来。该函数分配一个新的描述符,用来在后续的系统调用中标识插口,将这个新的被描述符返回给进程。

//描述进程传递给内核的参数的结构
struct socket_args {
	int	domain;
	int	type;
	int	protocol;
};
socket(p, uap, retval)
	struct proc *p;	//指向调用进程的proc结构
	register struct socket_args *uap;	//指向包含进程传送给系统调用的参数的结构
	int *retval;	//用来接收系统调用的返回值
{
	.../*分配一个新的file结构和fd_ofiles数组中的一个元素,初始化。
	并将所有插口共享的fileops结构socketops连接到f_ops只想的file结构中*/
	socreate(uap->domain,&so,uap->type,uap->protocol);
	...
	/*将f_data指向新生成的socket结构*/
}

socreate函数

socket内部调用socreate来完成分配并初始化一个socket结构

socreate(dom, aso, type, proto)
	int dom;	//请求的协议域(如PF_INET)
	struct socket **aso;	//保存指向一个新的socket结构的指针
	register int type;	//请求的插口类型(如,SOCK_STREAM)
	int proto;	//请求的协议
{
	.../*寻找对应匹配的协议*/
	.../*分配空间给socket结构*/
	/*发送对应的PRU_ATTACH,该请求引起协议分配并初始化所有支持新的连接端点的数据结构*/
	error =	(*prp->pr_usrreq)(so, PRU_ATTACH,
	(struct mbuf *)0, (struct mbuf *)proto, (struct mbuf *)0);
	/*pr_usrreq用来处理从插口层来的请求	
	函数原型 int pr_usrreq(struct socket *so,int req, struct mbuf *m0,*m1,*m2);*/
	.../*如果发生错误,清理缓存*/
	
}

pr_usrreq用来处理从插口层来的请求
函数原型 int pr_usrreq(struct socket *so,int req, struct mbuf *m0,*m1,*m2);
so是一个指向相关插口的指针,req是一个标识请求的常数。后三个参数(m0,m1,m2)因请求不同而异。

在这里插入图片描述

getsock和sockargs函数

这两个函数重复出现在插口系统调用中。
getsock的功能是将描述符映射到一个文件表项中,
scokargs将进程传入的参数赋值到内核中的一个新分配的mbuf中。
这两个函数都要检查参数的正确性。

getsock(fdp, fdes, fpp)	//将描述符映射个文件表项
	struct filedesc *fdp;	//指向filedesc结构的指针
	int fdes;	//描述符
	struct file **fpp;	//保存映射到的文件表项
{
	register struct file *fp;
	//利用fdp查找描述符fdes指定的文件表项
	if ((unsigned)fdes >= fdp->fd_nfiles ||
	    (fp = fdp->fd_ofiles[fdes]) == NULL)	//描述符的值超过了范围而不是指向一个打开的文件
		return (EBADF);
	if (fp->f_type != DTYPE_SOCKET)	//描述符没有同插口建立联系
		return (ENOTSOCK);
	*fpp = fp;//将打开的文件结构指针赋值给fpp,返回
	return (0);
}
sockargs(mp, buf, buflen, type)//将进程传入的参数复制到内核中的一个新分配的mbuf中 注意!!是将进程传给系统调用的参数的指针从进程复制到内核,而不是复制指针所指向的数据
	struct mbuf **mp;//保存参数的mbuf指针的指针mp
	caddr_t buf;	//缓存地址
	int buflen, type;//缓存长度,类型
{
	...
}

bind系统调用

bind系统调用将一个本地的网络运输层地址和插口联系起来。作为客户的进程并不关心它的本地地址是什么,这种情况下,进程在进行通信之前没有必要调用bind,内核会自动为其选择一个本地地址。
服务器进程则总是需要绑定到一个已知的地址上,所以进程在接受连接或接收数据报之前必须调用bind。

truct bind_args {
	int	s;	//插口描述符
	caddr_t	name;	//包含传输地址(如sockaddr_in结构)的缓存指针
	int	namelen;	//缓存大小
};
/* ARGSUSED */
bind(p, uap, retval)	//bind系统调用是将一个本地的网络运输层地址和插口联系起来
	struct proc *p;
	register struct bind_args *uap;
	int *retval;
{
	struct file *fp;
	struct mbuf *nam;
	int error;
	...error = getsock(p->p_fd,uap->s,&fp);//得到打开的文件结构指针
	...error = sockargs(&nam, uap->name, uap->namelen, MT_SONAME);//得到系统调用的参数的指针
	...error = sobind((struct socket *)fp->f_data, nam);
}

sobind函数

sobind(so, nam)	//一个封装器,给与插口相关联的协议发送PRU_BIND请求
	struct socket *so;
	struct mbuf *nam;
{
	int s = splnet();
	int error;

	error =
	    (*so->so_proto->pr_usrreq)(so, PRU_BIND,
		(struct mbuf *)0, nam, (struct mbuf *)0);	//请求成功,将本地地址nam同插口联系起来
	splx(s);
	return (error);
}

listen系统调用

listen系统调用的功能是通知协议进程准备接受插口上的连接请求,并制定插口上可以排队等待的连接数的门限值。

struct listen_args {
	int	s;	//插口描述符
	int	backlog;	//连接队列门限值
};
/* ARGSUSED */
listen(p, uap, retval)	//listen系统调用的功能是通知协议进程准备接收插口上的连接请求
	struct proc *p;
	register struct listen_args *uap;
	int *retval;
{
	struct file *fp;
	int error;

	if (error = getsock(p->p_fd, uap->s, &fp))
		return (error);
	return (solisten((struct socket *)fp->f_data, uap->backlog));
}

solisten函数

solisten(so, backlog)	//发送PRU_LISTEN请求,并使接口准备接收连接
	register struct socket *so;
	int backlog;
{
	int s = splnet(), error;

	error =
	    (*so->so_proto->pr_usrreq)(so, PRU_LISTEN,
		(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
	if (error) {
		splx(s);
		return (error);
	}
	if (so->so_q == 0)
		so->so_options |= SO_ACCEPTCONN;
	if (backlog < 0)
		backlog = 0;
	so->so_qlimit = min(backlog, SOMAXCONN);	//设置连接队列门限值
	splx(s);
	return (0);
}

tsleep和wakeup函数

当一个在内核中执行的进程因为得不到内核资源而不能继续徐执行时,它就调用tsleep等待。
tsleep原型:

int tsleep(caddr_t chan, int pri, char *mesg, int timeo);

第一个参数chan,被称之为等待通道。它标志进程等待的特定资源或事件。许多进程能同时在同一个等待通道上睡眠
第二个参数pri,指定被唤醒进程的优先级。pri中还包括几个用于tsleep的可选的控制标志。如:通过设置pri中的PCAtCH标志,当一个信号出现时,tsleep也返回。
第三个参数mesg是一个说明调用tsleep的字符串,它将被放在调用报文或ps的输出中。
最后一个参数timeo设置睡眠间隔的上限值,单位是时钟滴答。
在这里插入图片描述

当资源可用或事件出现时,内核调用wakeup,并将等待通道作为唯一的参数传入。
wakeup的原型为:

void wakeup(caddr_t chan);

所有等待在该通道上的进程均被唤醒,并被设置成运行状态。当每一个进程均恢复执行时,内核安排tsleep返回。

每一个被唤醒的进程在继续执行之前必须检查等待的资源是否可得到,因为另一个被唤醒的进程可能已经先一步得到了资源。如果仍然得不到资源,进程再调用tsleep等待。
多个进程在一个插口睡眠等待的情况实不多见的,所以,通常情况下,一次wakeup只有一个进程被唤醒。

accept系统调用

调用listen后,进程调用accept等待连接请求。accept返回一个新的描述符,指向一个连接到客户的新的插口。原来的插口s仍然是未连接的,并准备接收下一个连接

struct accept_args {
	int	s;	//插口描述符
	caddr_t	name;	//缓存指针	accept将把外部主机的运输地址填入该缓存
	int	*anamelen;	//保存缓存大小的指针
};
accept(p, uap, retval)
	struct proc *p;
	register struct accept_args *uap;
	int *retval;
{
	.../*验证参数*/
	.../*等待连接*/
	while (so->so_qlen == 0 && so->so_error == 0) {
		if (so->so_state & SS_CANTRCVMORE) {
			so->so_error = ECONNABORTED;
			break;
		}
		if (error = tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH,
		    netcon, 0)) {
			splx(s);
			return (error);
		}
	}
	.../*将插口同描述符相关联*/
	nam = m_get(M_WAIT, MT_SONAME);//分配一个新的mbuf来保存外部地址
	(void) soaccept(so, nam);/*提交协议处理*/
	...
}

soaccept函数

soaccept函数通过协议层获得新的连接的客户地址。发送PRU_ACCEPT请求给协议。

soaccept(so, nam)
	register struct socket *so;
	struct mbuf *nam;
{
	int s = splnet();
	int error;

	if ((so->so_state & SS_NOFDREF) == 0)
		panic("soaccept: !NOFDREF");
	so->so_state &= ~SS_NOFDREF;
	error = (*so->so_proto->pr_usrreq)(so, PRU_ACCEPT,
	    (struct mbuf *)0, nam, (struct mbuf *)0);//函数返回后,nam包含插口的名字
	splx(s);
	return (error);
}

sonewconn和soisconnected函数

tcp_input函数调用sonewconn为新的连接产生一个插口来处理进入的TCP SYN。sonewconn将产生的插口放入so_q0排队,因为三次握手还未完成。
当TCP握手协议的最后一个ACK到达时,tcp_input调用soisconnected来更新产生的插口,并将它从so_q0队列移动到so_q队列中,唤醒所有调用accept等待进入的连接的进程。

在这里插入图片描述

sonewconn函数

struct socket *
sonewconn1(head, connstatus)	//sonewconn为新的连接产生一个插口来处理进入的TCP SYN,然后将产生的插口放入so_q0排队
	register struct socket *head;	//指向正在接收连接的插口的指针
	int connstatus;	//指示新连接的状态的标志,对TCP而言,connstatus总是等于0
{
	...//限制进入的连接,因为连接队列有门限值
	...//分配一个新的插口
	...soqinsque(head, so, soqueue);//根据soqueue排队连接,0将新的插口插入到so_q0中,非0,插入到so_q
	...(*so->so_proto->pr_usrreq)(so, PRU_ATTACH,	//发送PRU_ATTACH请求,启动协议层对新的连接的处理
	    (struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0)
	 ...
	 if (connstatus) {	//connstatus非0,唤醒在accept中睡眠或查询插口的可读性的所有进程
		sorwakeup(head);
		wakeup((caddr_t)&head->so_timeo);
		so->so_state |= connstatus;
	}
}

soisconnected函数

soisconnected(so)//某些将进入的连接首先插入so_q0队列中的协议在连接建立阶段完成时调用soiscoonected(如,TCP)
	register struct socket *so;
{

	...//更新产生的插口,将其从so_q0移动到so_q中
	register struct socket *head = so->so_head;

	so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING);
	so->so_state |= SS_ISCONNECTED;
	if (head && soqremque(so, 0)) {//本地进程正在调用accept时,head为非空	
	//如果soqremque(so,0)返回去,就将插口放入so_q排队
		soqinsque(head, so, 1);
		sorwakeup(head);
		wakeup((caddr_t)&head->so_timeo);
	} else {//head为空,说明进程用connect系统调用初始化连接
		wakeup((caddr_t)&so->so_timeo);//唤醒所有阻塞在connect中的进程
		sorwakeup(so);
		sowwakeup(so);
	}
	...//唤醒所有被阻塞的进程
}

connect系统调用

如果进程想要初始化一条连接(即客户端),则调用connect
对于面向连接的协议如TCP,connect建立一条与制定的外部地址的连接
对于无连接协议如UDP或ICMP,connect记录我外部地址,以便发送数据报时使用。

在这里插入图片描述

connect系统调用函数

struct connect_args {
	int	s;	//插口描述符
	caddr_t	name;	//指向存放外部地址的缓存
	int	namelen;	//缓存的长度
};
/* ARGSUSED */
connect(p, uap, retval)
	struct proc *p;
	register struct connect_args *uap;
	int *retval;
{
	...error = getsock(p->p_fd, uap->s, &fp)
	...error = sockargs(&nam, uap->name, uap->namelen, MT_SONAME)
	...error = soconnect(so, nam);//调用soconnect,开始连接处理
	...
	//等待连接建立
	s = splnet();
	while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0)	//while循环直到连接已建立或出现差错时才推出
		if (error = tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH,
		    netcon, 0))
			break;
	if (error == 0) {
		error = so->so_error;
		so->so_error = 0;
	}
	splx(s);
	...
}

soconnect函数

soconnect函数确保插口处于正确的连接状态。如果插口没有连接或连接没有被挂起,则连接请求总是正确的。如果插口已经连接或连接正等待处理,则新的连接请求将被面向连接的协议(TCP)拒绝。对于无连接的协议(UDP),多个连接时允许的,但是每一个新的请求中的外部地址会取代原来的外部地址

soconnect(so, nam)
	register struct socket *so;
	struct mbuf *nam;
{
	int s;
	int error;

	if (so->so_options & SO_ACCEPTCONN)	//如果插口已被标识准备接收连接,则返回EOPNOTSUPP,因为已经调用了listen,则进程不能再初始化连接
		return (EOPNOTSUPP);
	s = splnet();
	/*
	 * If protocol is connection-based, can only connect once.
	 * Otherwise, if connected, try to disconnect first.
	 * This allows user to disconnect by connecting to, e.g.,
	 * a null address.
	 */
	if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&	//如果协议是面向连接的,且一条连接已经被初始化,则返回EISOCNN
	    ((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
	    (error = sodisconnect(so))))
		error = EISCONN;
	else	//对于无连接协议任何已有的同外部地址的联系都被sodissconnect切断
		error = (*so->so_proto->pr_usrreq)(so, PRU_CONNECT,
		    (struct mbuf *)0, nam, (struct mbuf *)0);
	splx(s);
	return (error);
}

shutdown系统调用

shutdown系统调用关闭连接的读通道、写通道或读写通道。对于读通道,shutdown丢弃所有进程还没有读走的数据以及调用shutdown之后到达的数据,对于写通道,shutdown使协议作相应的处理。对于TCP而言,所有剩余的数据将被发送,发送完成后发送FIN。(这是TCP的半关闭的特点)

struct shutdown_args {
	int	s;	//插口描述符
	int	how;	//指明关闭连接的方式
};
/* ARGSUSED */
shutdown(p, uap, retval)//shutdown系统调用关闭连接的读通道、写通道或者读写通道
	struct proc *p;
	register struct shutdown_args *uap;
	int *retval;
{
	struct file *fp;
	int error;

	if (error = getsock(p->p_fd, uap->s, &fp))
		return (error);
	return (soshutdown((struct socket *)fp->f_data, uap->how));
}

soshutdown和sorflush函数

关闭连接的读通道是由插口层调用sorflush处理,写通道的关闭是由系一层的PRU_SHUTDOWN请求处理的。

soshutdown(so, how)
	register struct socket *so;
	register int how;
{
	register struct protosw *pr = so->so_proto;

	how++;
	if (how & FREAD)	//读通道关闭调用sorflush处理
		sorflush(so);	//关闭插口的读通道,sorflush丢弃接收缓存中的数据,禁止读连接
	if (how & FWRITE)	//写通道关闭由协议层的PRU_SHUTDOWN请求处理
		return ((*pr->pr_usrreq)(so, PRU_SHUTDOWN,
		    (struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0));
	return (0);
}
sorflush(so)
	register struct socket *so;
{
	register struct sockbuf *sb = &so->so_rcv;
	register struct protosw *pr = so->so_proto;
	register int s;
	struct sockbuf asb;

	sb->sb_flags |= SB_NOINTR;	//设置SB_NOINTR,所以当中断出现时,sblock并不返回
	(void) sblock(sb, M_WAITOK);
	s = splimp();	//在修改插口状态时,splimp阻塞网络中断和协议处理
	socantrcvmore(so);//socantrcvmore标识插口拒绝接受进入的分组
	sbunlock(sb);
	asb = *sb;//保存一个副本,当shutdown被调用时,存储在接收队列中的控制信息可能引用了一些内核资源,通过sockbuf结构的副本中的sb_mb仍然可以访问mbuf链
	bzero((caddr_t)sb, sizeof (*sb));
	splx(s);//恢复中断
	if (pr->pr_flags & PR_RIGHTS && pr->pr_domain->dom_dispose)
		(*pr->pr_domain->dom_dispose)(asb.sb_mb);
	sbrelease(&asb);	//sbrelease释放队列中所有mbuf时,丢弃所有调用shutdown时还没有被处理的数据
}

#close系统调用

close系统调用能用来关闭各类描述符

soo_close是soclose的封装器

soo_close(fp, p)
	struct file *fp;
	struct proc *p;
{
	int error = 0;

	if (fp->f_data)
		error = soclose((struct socket *)fp->f_data);
	fp->f_data = 0;
	return (error);
}

soclose函数

soclose函数取消插口上所有未完成的连接(即还没有完全被进程接受的连接),等待护具被传输到外部系统,释放不需要的数据结构

soclose(so)	//soclose函数取消插口上所有未完成的连接,等待数据被传输到外部系统,释放不需要的数据结构
	register struct socket *so;
{
	int s = splnet();		/* conservative */
	int error = 0;

	if (so->so_options & SO_ACCEPTCONN) {//如果插口正在接收连接,遍历两个接受队列,调用soabort取消每个挂起的连接
		while (so->so_q0)
			(void) soabort(so->so_q0);
		while (so->so_q)
			(void) soabort(so->so_q);
	}
	if (so->so_pcb == 0)//如果协议快为空,则协议已同插口分伦理,跳转到discard进行推出处理
		goto discard;
	if (so->so_state & SS_ISCONNECTED) {//如果插口没有同任何外部地址相连接,则跳转到drop处继续执行,否则必须断开插口与对等地址之间的连接。
		if ((so->so_state & SS_ISDISCONNECTING) == 0) {
			error = sodisconnect(so);
			if (error)
				goto drop;
		}
		if (so->so_options & SO_LINGER) {
			if ((so->so_state & SS_ISDISCONNECTING) &&
			    (so->so_state & SS_NBIO))
				goto drop;
			while (so->so_state & SS_ISCONNECTED)
				if (error = tsleep((caddr_t)&so->so_timeo,
				    PSOCK | PCATCH, netcls, so->so_linger))
					break;
		}
	}
drop:
	if (so->so_pcb) {
		int error2 =
		    (*so->so_proto->pr_usrreq)(so, PRU_DETACH, //发送PRU_DETACH请求断开插口与协议的联系
			(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0);
		if (error == 0)
			error = error2;
	}
discard:
	if (so->so_state & SS_NOFDREF)
		panic("soclose: NOFDREF");
	so->so_state |= SS_NOFDREF;	
	sofree(so);
	splx(s);
	return (error);
}

/*
 * Must be called at splnet...
 */
soabort(so)
	struct socket *so;
{

	return (
	    (*so->so_proto->pr_usrreq)(so, PRU_ABORT,
		(struct mbuf *)0, (struct mbuf *)0, (struct mbuf *)0));
}
sofree(so)
	register struct socket *so;
{

	if (so->so_pcb || (so->so_state & SS_NOFDREF) == 0)
		return;
	if (so->so_head) {
		if (!soqremque(so, 0) && !soqremque(so, 1))
			panic("sofree dq");
		so->so_head = 0;
	}
	sbrelease(&so->so_snd);
	sorflush(so);
	FREE(so, M_SOCKET);
}

小结

本节讨论了所有与网络操作相关的系统调用。描述了系统调用的机制,并且跟踪系统调用直到它们通过pr_usrreq函数进入协议处理层进行相应的处理。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值