tcp connection setup 의 실현 (3)

먼저 accept 의 실현 을 살 펴 보 자.
사실 accept 의 역할 은 간단 합 니 다. accept 대기 열 에서 악수 로 완 성 된 socket 을 세 번 꺼 내 vfs 에 연결 하 는 것 입 니 다. (사실 sys socket 을 호출 할 때 socket 을 새로 만 드 는 것 과 유사 합 니 다) 그리고 돌아 갑 니 다. 여기 서 주의해 야 할 것 이 있 습 니 다. accept 에 전 달 된 socket 이 차단 되 지 않 으 면 accept 대기 열 이 비어 도 바로 돌아 갑 니 다. 막 히 면 휴면 합 니 다.accept 대기 열 에 데이터 가 있 을 때 까지 기 다 렸 다가 깨 워 주세요.
다음은 그 실현 을 살 펴 보 겠 습 니 다. accept 에 대응 하 는 시스템 호출 은 sys 입 니 다.accept, 그 는 do 를 호출 합 니 다.accept, 그래서 우 리 는 직접 do 를 보 러 왔 다.accept:


long do_accept(int fd, struct sockaddr __user *upeer_sockaddr,
	       int __user *upeer_addrlen, int flags)
{
	struct socket *sock, *newsock;
	struct file *newfile;
	int err, len, newfd, fput_needed;
	struct sockaddr_storage address;
.............................................
///            ,     fd,     socket.
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;

	err = -ENFILE;
///    socket,            socket.            socket,   sock.           .
	if (!(newsock = sock_alloc()))
		goto out_put;

	newsock->type = sock->type;
	newsock->ops = sock->ops;

	/*
	 * We don't need try_module_get here, as the listening socket (sock)
	 * has the protocol module (sock->ops->owner) held.
	 */
	__module_get(newsock->ops->owner);
///             ,  file  .         socket    .
	newfd = sock_alloc_fd(&newfile, flags & O_CLOEXEC);
	if (unlikely(newfd < 0)) {
		err = newfd;
		sock_release(newsock);
		goto out_put;
	}
///   socket file    .(                 socket       ,    ,        blog
	err = sock_attach_fd(newsock, newfile, flags & O_NONBLOCK);
	if (err < 0)
		goto out_fd_simple;

	err = security_socket_accept(sock, newsock);
	if (err)
		goto out_fd;
///  inet_accept
	err = sock->ops->accept(sock, newsock, sock->file->f_flags);
	if (err < 0)
		goto out_fd;
///       accept        .          upeer_sockaddr.
	if (upeer_sockaddr) {
		if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
					  &len, 2) < 0) {
			err = -ECONNABORTED;
			goto out_fd;
		}
		err = move_addr_to_user((struct sockaddr *)&address,
					len, upeer_sockaddr, upeer_addrlen);
		if (err < 0)
			goto out_fd;
	}

	/* File flags are not inherited via accept() unlike another OSes. */
///     file   fd    ,          fd        files .
	fd_install(newfd, newfile);
	err = newfd;

	security_socket_post_accept(sock, newsock);

out_put:
///            .
	fput_light(sock->file, fput_needed);
out:
///    .
	return err;
.......................................
}

절차 가 간단 하고 최종 실현 은 inet 에 집중 되 어 있 음 을 알 수 있 습 니 다.accept 에 당 첨 되 었 습 니 다. inetaccept 가 주로 하 는 게...
1 inet 호출csk_accept 에서 accept 대기 열 을 조작 합 니 다. 받 은 sock 을 되 돌려 줍 니 다.
2. inet 에서csk_accept 가 되 돌아 온 sock 은 전 달 된 (즉 do accept 에서 new socket) 에 연결 되 어 있 습 니 다. 여기 서 우리 위 에 왜 sock 이 아 닌 new socket 만 필요 한 지 알 수 있 습 니 다. sock 은 accept 대기 열 에서 직접 얻 었 기 때 문 입 니 다.
3 새로운 socket 의 상 태 를 SS 로 설정CONNECTED.
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
	struct sock *sk1 = sock->sk;
	int err = -EINVAL;
///  inet_csk_accept.
	struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);

	if (!sk2)
		goto do_err;

	lock_sock(sk2);
///  tcp     .
	WARN_ON(!((1 << sk2->sk_state) &
		  (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)));
///    sock   socket.
	sock_graft(sk2, newsock);
///    .
	newsock->state = SS_CONNECTED;
	err = 0;
	release_sock(sk2);
do_err:
	return err;
}

inet_csk_accept 는 accept 대기 열 에서 sock 을 꺼 내 서 돌아 오 는 것 입 니 다.
그의 소스 코드 를 보기 전에 관련 함수 의 실현 을 먼저 살 펴 보 자.
우선 reqskqueue_empty, accept 대기 열 이 비어 있 는 지 판단 하 는 데 사 용 됩 니 다.
static inline int reqsk_queue_empty(struct request_sock_queue *queue)
{
	return queue->rskq_accept_head == NULL;
}

그리고 reqskqueue_get_child, 그 는 주로 accept 대기 열 에서 sock 을 얻 었 습 니 다.
static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
						 struct sock *parent)
{
///   accept   remove  socket   .
	struct request_sock *req = reqsk_queue_remove(queue);
///  socket.
	struct sock *child = req->sk;

	WARN_ON(child == NULL);
///      sk_ack_backlog  ,   accept       .
	sk_acceptq_removed(parent);
	__reqsk_free(req);
	return child;
}

여기 또 하나의 inetcsk_wait_for_connect, accept 대기 열 이 비어 있 는 상태 에서 한동안 휴면 하 는 데 사 용 됩 니 다.
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
///    waitqueue.
	DEFINE_WAIT(wait);
	int err;
..................................................
	for (;;) {
///                  sk  sk_sleep  ,sk       socket.
		prepare_to_wait_exclusive(sk->sk_sleep, &wait,
					  TASK_INTERRUPTIBLE);
		release_sock(sk);
///        .
		if (reqsk_queue_empty(&icsk->icsk_accept_queue))
///         timeo  (  schedule  cpu),    accept        (          )        .,
			timeo = schedule_timeout(timeo);
		lock_sock(sk);
		err = 0;
///     .
		if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
			break;
		err = -EINVAL;
		if (sk->sk_state != TCP_LISTEN)
			break;
		err = sock_intr_errno(timeo);
		if (signal_pending(current))
			break;
///     .
		err = -EAGAIN;
///   0     .
		if (!timeo)
			break;
	}
///      sk_sleep remove    wait  .
	finish_wait(sk->sk_sleep, &wait);
	return err;
}

그리고 inet csk accept 의 원본 코드 를 살 펴 보 겠 습 니 다. 차단 되 지 않 으 면 바로 돌아 갑 니 다. accept 대기 열 이 비어 있어 도 errno 를 - EAGAIN 으로 설정 합 니 다.
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;

	lock_sock(sk);

	/* We need to make sure that this socket is listening,
	 * and that it has something pending.
	 */
	error = -EINVAL;
///sk    socket,               TCP_LISTEN.
	if (sk->sk_state != TCP_LISTEN)
		goto out_err;

///    accept      
	if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
///   O_NONBLOCK,   0,     inet_csk_wait_for_connect       .
		long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

		/* If this is a non blocking socket don't sleep */
		error = -EAGAIN;
		if (!timeo)
			goto out_err;
///        .
		error = inet_csk_wait_for_connect(sk, timeo);
		if (error)
			goto out_err;
	}
///  sock  accept   remove.
	newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
	WARN_ON(newsk->sk_state == TCP_SYN_RECV);
out:
	release_sock(sk);
	return newsk;
out_err:
	newsk = NULL;
	*err = error;
	goto out;
}

마지막 으로 connect 의 실현 을 대충 분석 해 보 자. 그 구체 적 인 절 차 는:
1. fd 에서 socket 을 얻 고 주 소 를 커 널 공간 으로 복사 합 니 다.
2 inet stream connect 를 호출 하여 주요 처 리 를 합 니 다.
여기 서 connect 도 차단 과 비 차단 의 차이 가 있 습 니 다. 차단 하면 inet wait for connect 휴면 을 호출 하여 악수 가 완 료 될 때 까지 기 다 려 야 합 니 다. 그렇지 않 으 면 바로 돌아 갑 니 다.


asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
			    int addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;
///  socket.
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;
///    .
	err = move_addr_to_kernel(uservaddr, addrlen, (struct sockaddr *)&address);
	if (err < 0)
		goto out_put;

	err =
	    security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
	if (err)
		goto out_put;
///      .
	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
				 sock->file->f_flags);
out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
}

그리고 inet stream connect 를 보 세 요. 그의 주요 업 무 는:
1 socket 의 상 태 를 판단 합 니 다. SS UNCONNECTED 즉 비 접속 상태 일 때 만 tcp v4 connect 를 호출 하여 연결 처 리 를 합 니 다.
2 tcp 의 상 태 를 판단 하 는 sk state 는 TCPF SYN SENT 또는 TCPF SYN RECV 만 해당 처리 에 들 어 갈 수 있 습 니 다.
3. 상태 가 적당 하고 socket 이 차단 모드 라면 inet wait for connect 를 사용 하여 휴면 에 들 어가 악 수 를 기다 리 고 있 습 니 다. 그렇지 않 으 면 바로 돌아 가 고 오류 번 호 를 EINPROGRESS 로 설정 합 니 다.


int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	int err;
	long timeo;

	lock_sock(sk);
............................................

	switch (sock->state) {
	default:
		err = -EINVAL;
		goto out;
	case SS_CONNECTED:
		err = -EISCONN;
		goto out;
	case SS_CONNECTING:
		err = -EALREADY;
		/* Fall out of switch with err, set for this state */
		break;
	case SS_UNCONNECTED:
		err = -EISCONN;
		if (sk->sk_state != TCP_CLOSE)
			goto out;
///  tcp_v4_connect     .     syn.
		err = sk->sk_prot->connect(sk, uaddr, addr_len);
		if (err < 0)
			goto out;
///    .
		sock->state = SS_CONNECTING;
///     .
		err = -EINPROGRESS;
		break;
	}
///        ,       0,    timeo.
	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);

	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
///          .                  .(        inet_csk_wait_for_connect       ,         )
		if (!timeo || !inet_wait_for_connect(sk, timeo))
			goto out;

		err = sock_intr_errno(timeo);
		if (signal_pending(current))
			goto out;
	}

	/* Connection was closed by RST, timeout, ICMP error
	 * or another process disconnected us.
	 */
	if (sk->sk_state == TCP_CLOSE)
		goto sock_error;
///  socket  .    .
	sock->state = SS_CONNECTED;
	err = 0;
out:
	release_sock(sk);
	return err;

sock_error:
	err = sock_error(sk) ? : -ECONNABORTED;
	sock->state = SS_UNCONNECTED;
	if (sk->sk_prot->disconnect(sk, flags))
		sock->state = SS_DISCONNECTING;
	goto out;
}

tcp v4 connect 의 소스 코드 는 분석 하지 않 습 니 다. 저 는 그의 절 차 를 대충 소개 하 겠 습 니 다.
1. 주소 의 합 법성 을 판단 한다.
2. ip route connect 를 호출 하여 나 가 는 길 을 찾 습 니 다 (임시 포트 찾기 포함).
3. sock 상 태 를 TCP SYN SENT 로 설정 하고 inet hash connect 를 호출 하여 임시 포트 (즉, 우리 가 나 간 포트) 를 찾 고 해당 하 는 hash 링크 에 추가 합 니 다 (구체 적 인 동작 은 get port 와 비슷 합 니 다).
4. tcp connect 를 호출 하여 최종 작업 을 수행 합 니 다. 이 함 수 는 보 낼 syn 패 키 지 를 초기 화 하 는 데 사 용 됩 니 다. (창 크기 isn 등 포함) 그리고 이 sk buffer 를 socket 의 쓰기 대기 열 에 추가 합 니 다. 최종 적 으로 tcp transmit skb 를 3 층 으로 호출 합 니 다. 다음 작업 을 하면 제 앞의 blog 를 볼 수 있 습 니 다.
마지막 으로 악 수 를 세 번 하 는 클 라 이언 트 의 상태 변 화 를 살 펴 보 겠 습 니 다. tcp rcv state process 함 수 를 보 겠 습 니 다. 여기 서 우리 가 들 어 온 socket 가설 은 TCP SYN SENT 상태 입 니 다. 즉, syn 과 ack 분 절 을 기다 리 고 있 습 니 다.


int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  struct tcphdr *th, unsigned len)
{
..........................................

	switch (sk->sk_state) {
	case TCP_CLOSE:
		goto discard;

	case TCP_LISTEN:
		..................................

	case TCP_SYN_SENT:
///            .
		queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
		if (queued >= 0)
			return queued;

		/* Do step6 onward by hand. */
		tcp_urg(sk, skb, th);
		__kfree_skb(skb);
		tcp_data_snd_check(sk);
		return 0;
	}

그리고 tcp rcv synsent state process 의 상태 변 화 를 살 펴 보 겠 습 니 다.
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
					 struct tcphdr *th, unsigned len)
{
..................

	if (th->ack) {
....................................
///   rst  ,       ,
		if (th->rst) {
			tcp_reset(sk);
			goto discard;
		}
///     ack    syn          ,    .
		if (!th->syn)
			goto discard_and_undo;

..................................................
///             TCP_ESTABLISHED,          ack  .
		tcp_set_state(sk, TCP_ESTABLISHED);

		.......................................
	}

....................................................

	if (th->syn) {
///    syn  ,        TCP_SYN_RECV.
		tcp_set_state(sk, TCP_SYN_RECV);

...................................
///  ack     .
		tcp_send_synack(sk);
		goto discard;
#endif
	}
...................
}

여기 서 syn 만 받 아들 이면 세 번 의 악수 가 완료 되 지 않 았 습 니 다. 마지막 ack 를 기다 리 고 있 습 니 다. 따라서 데이터 보고 가 있 으 면 tcp rcv state process 함수 에 다시 떨 어 집 니 다.


if (th->ack) {
///    ack    .
		int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);

		switch (sk->sk_state) {
		case TCP_SYN_RECV:
			if (acceptable) {

				tp->copied_seq = tp->rcv_nxt;
				smp_mb();
///   TCP_ESTABLISHED,      .
				tcp_set_state(sk, TCP_ESTABLISHED);
				sk->sk_state_change(sk);
///     connect   .
				if (sk->sk_socket)
					sk_wake_async(sk,
						      SOCK_WAKE_IO, POLL_OUT);

			........................................
			} else {
				return 1;
			}
			break;


좋은 웹페이지 즐겨찾기