在前文的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结构
}