2.6.32系统调用accept内核执行流程(3)传输层 inet_csk_accept

在前文的inet_accept()分析中我们知道inet_accept会通过如下方式调用传输层的accpet
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
    ... 
    
    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err)
    
    ...
}
对于tcpv4而言此函数为inet_csk_accept(),那么我们接下来自习看看此函数的实现

在分析代码前我们需要了解,套接字有监听套接字和具体通信的套接字
监听套接字的扩展结构inet_connection_sock中存在icsk_accept_queue成员,
此成员中有两个队列,一个用于完全建立连接(完成三次握手)的队列,此队列项中会包含新建的
用于通信的sock结构,在进程不在阻塞获得此sock结构后会把此队列项从完全建立连接的队列删除.此队列的最大长度即是listen(int s, int backlog)中第二个参数指定的
另一个队列是半连接队列,即还没有完成三次握手的队列项会加入到此队列,此队列项中的sock完成三次握手后会从此队列中移除,添加到完全建立连接的队列中

 /**********************************************
sk:监听sock
flags:这些是文件标志, 例如 O_NONBLOCK
err:传出参数 用于接收错误
******************************************************/
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct sock *newsk;
	int error;

	//获取sock锁将sk->sk_lock.owned设置为1
	//此锁用于进程上下文和中断上下文
	lock_sock(sk);

	/* We need to make sure that this socket is listening,
	 * and that it has something pending.
	 */
	//用于accept的sock必须处于监听状态
	error = -EINVAL;
	if (sk->sk_state != TCP_LISTEN)
		goto out_err;

	/* Find already established connection */
	//在监听套接字上的连接队列如果为空
	if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {

		//设置接收超时时间,若调用accept的时候设置了O_NONBLOCK,表示马上返回不阻塞进程
		long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

		/* If this is a non blocking socket don't sleep */
		error = -EAGAIN;
		if (!timeo)//如果是非阻塞模式timeo为0 则马上返回
			goto out_err;

		//将进程阻塞,等待连接的完成
		error = inet_csk_wait_for_connect(sk, timeo);
		if (error)//返回值为0说明监听套接字的完全建立连接队列不为空
			goto out_err;
	}

	//在监听套接字建立连接的队列中删除此request_sock连接项 并返回建立连接的sock
	//三次握手的完成是在tcp_v4_rcv中完成的
	newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);

	//此时sock的状态应为TCP_ESTABLISHED
	WARN_ON(newsk->sk_state == TCP_SYN_RECV);
out:
	release_sock(sk);
	return newsk;
out_err:
	newsk = NULL;
	*err = error;
	goto out;
}
 //等待连接的完成,连接完成在tcp_v4_do_rcv函数中
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	//初始化等待队列项,将当前进程关联到此项中
	DEFINE_WAIT(wait);
	int err;

	for (;;) {
		//将等待队列项 加入到等待队列中,并且是互斥等待,只有一个进程可以被唤醒
		//sk_sleep是一个等待队列,也就是所有阻塞在这个sock上的进程,
		//我们通知用户进程就是通过这个等待队列来做的   
		prepare_to_wait_exclusive(sk->sk_sleep, &wait,
					  TASK_INTERRUPTIBLE);  
		release_sock(sk);//释放sock锁 设置sk->sk_lock.owned为0来释放锁

		//监听套接字的全连接队列若为空 则进行等待
		if (reqsk_queue_empty(&icsk->icsk_accept_queue))
			timeo = schedule_timeout(timeo);/* 进入睡眠,直到超时或收到信号 */
		
		lock_sock(sk);//申请sock锁 设置sk->sk_lock.owned为1来占有锁
		err = 0;

		//若监听套接字全连接队列不为空则退出
		if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
			break;

		err = -EINVAL;
		//若不是监听套接字,则退出,等待连接完成是在监听套接字完成的
		if (sk->sk_state != TCP_LISTEN)
			break;

		//此函数是在有信号产生的情况下,对返回值的设置 
		//若timeo为MAX_SCHEDULE_TIMEOUT则err值设置为ERESTARTSYS,重新进行系统调用,
		  //timeo为MAX_SCHEDULE_TIMEOUT说明在调用schedule_timeout的时候无时间限制,直接进行schedule
		//若timeo不为MAX_SCHEDULE_TIMEOUT则err值设置EINTR
		err = sock_intr_errno(timeo);
		//检查当前进程是否有信号处理,返回不为0表示有信号需要处理
		if (signal_pending(current))
			break;
		
		err = -EAGAIN;
		//若不是信号中断 而是时间超时 将返回EAGAIN
		if (!timeo)
			break;
	}

	//将等待队列项从等待队列摘除
	finish_wait(sk->sk_sleep, &wait);
	
	return err;
}
static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
						 struct sock *parent)
{
    //在监听套接字的完全建立连接的队列中将此队列项删除
	struct request_sock *req = reqsk_queue_remove(queue);
	
	struct sock *child = req->sk;//获得此队列项中包含的通信用的新sock结构

	WARN_ON(child == NULL);
	sk_acceptq_removed(parent);//减少完全连接队列的队列项个数
	
	__reqsk_free(req);//释放队列项
	
	return child;//返回通信用的sock结构
}

猜你喜欢

转载自blog.csdn.net/yldfree/article/details/81977999