티스토리 뷰

2024.06.07 - [개발/c 언어] - 커널 소스 코드 분석 - socket 3

 

커널 소스 코드 분석 - socket 3

2024.06.03 - [개발/리눅스] - 커널 소스 코드 분석 - socket 2 커널 소스 코드 분석 - socket 2이전 글:2024.05.30 - [개발/리눅스] - 커널 소스 코드 분석 - socket 1 커널 소스 코드 분석 - socket 1우리가 쓰는 리

lhs9602.tistory.com

 

tcp_v4_init_sock 함수를 분석하기 전에 저번 시간에서 넘아갔던 sock_init_data함수를 잠깐 살펴볼것이다.

sock_init_data


void sock_init_data(struct socket *sock, struct sock *sk)
{
	kuid_t uid = sock ?
		SOCK_INODE(sock)->i_uid :
		make_kuid(sock_net(sk)->user_ns, 0);

	sock_init_data_uid(sock, sk, uid);
}

해당 부분은 삼항 연산자로 sock이 NULL인지 여부에 따라 uid를 결정한다.

  • sock이 NULL이 아닌 경우, sock의 inode에서 UID를 가져온다.
  • sock이 NULL인 경우, 해당 네임스페이스에서 기본 UID(0)를 생성합니다.

inet_create에서 한 것이 sock을 할당하는 것이기에, sock이 NULL이 될 수 있는 경우가 없다.

그러면 왜 이런 코드가 있는가 하면, sock_init_data 함수가 다른 곳에서도 쓰이는 데 그때 필요하다.

 

예를 들어, accpet시 소켓을 새로 생성하고 반환하는 데 이때 사용하는 것으로 보여진다.

 

sock_init_data_uid


void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
{
	sk_init_common(sk);
	sk->sk_send_head	=	NULL;

	timer_setup(&sk->sk_timer, NULL, 0);

sk_init_common에서 sk의 락과 큐를 초기화 시킨다.

그 후 sk의 송신 큐의 헤드를 NULL로 설정하고, 타이머를 초기화한다.

타이머는 소켓의 타임아웃 이벤트를 처리하는데 사용한다.

 

static void sk_init_common(struct sock *sk)
{
    // 수신 큐, 송신 큐, 에러 큐를 초기화
    skb_queue_head_init(&sk->sk_receive_queue);
    skb_queue_head_init(&sk->sk_write_queue);
    skb_queue_head_init(&sk->sk_error_queue);

    // 콜백 락 초기화
    rwlock_init(&sk->sk_callback_lock);

    // 수신 큐 락에 클래스와 이름 설정
    lockdep_set_class_and_name(&sk->sk_receive_queue.lock,
            af_rlock_keys + sk->sk_family,
            af_family_rlock_key_strings[sk->sk_family]);

    // 송신 큐 락에 클래스와 이름 설정
    lockdep_set_class_and_name(&sk->sk_write_queue.lock,
            af_wlock_keys + sk->sk_family,
            af_family_wlock_key_strings[sk->sk_family]);

    // 에러 큐 락에 클래스와 이름 설정
    lockdep_set_class_and_name(&sk->sk_error_queue.lock,
            af_elock_keys + sk->sk_family,
            af_family_elock_key_strings[sk->sk_family]);

    // 콜백 락에 클래스와 이름 설정
    lockdep_set_class_and_name(&sk->sk_callback_lock,
            af_callback_keys + sk->sk_family,
            af_family_clock_key_strings[sk->sk_family]);
}

수신 큐, 송신 큐, 에러 큐, 콜백 락을 초기화 시킨다.

송신 큐는 소켓을 통해 전송할 데이터 패킷을, 수신 큐는 소켓으로 수신된 데이터 패킷을 저장한다.

에러 큐는 소켓에서 발생한 에러 메시지를 저장하고, 콜백 락은 소켓의 콜백 함수들이 실행되는 동안 동시 접근을 제어하기 위해 사용되는 락이다.

 

void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
{
	...
    // 소켓의 메모리 할당 정책, 수신 버퍼, 송신 버퍼 설정
    sk->sk_allocation = GFP_KERNEL;
    sk->sk_rcvbuf = READ_ONCE(sysctl_rmem_default);
    sk->sk_sndbuf = READ_ONCE(sysctl_wmem_default);

    // 소켓 상태를 TCP_CLOSE로 초기화
    sk->sk_state = TCP_CLOSE;

    // 작업 조각 사용 플래그 설정
    sk->sk_use_task_frag = true;

    // 소켓과 sock 구조체 연결
    sk_set_socket(sk, sock);

    // 소켓 플래그 설정
    sock_set_flag(sk, SOCK_ZAPPED);

    // 소켓이 존재하는 경우와 존재하지 않는 경우의 처리
    if (sock) {
        // 소켓 타입 설정
        sk->sk_type = sock->type;
        // RCU 포인터 초기화 및 연결
        RCU_INIT_POINTER(sk->sk_wq, &sock->wq);
        sock->sk = sk;
    } else {
        // RCU 포인터를 NULL로 설정
        RCU_INIT_POINTER(sk->sk_wq, NULL);
    }

    // UID 설정
    sk->sk_uid = uid;

    // 콜백 락 초기화
    rwlock_init(&sk->sk_callback_lock);

    // 커널 소켓인지 여부에 따라 락 클래스와 이름 설정
    if (sk->sk_kern_sock) {
        lockdep_set_class_and_name(
            &sk->sk_callback_lock,
            af_kern_callback_keys + sk->sk_family,
            af_family_kern_clock_key_strings[sk->sk_family]);
    } else {
        lockdep_set_class_and_name(
            &sk->sk_callback_lock,
            af_callback_keys + sk->sk_family,
            af_family_clock_key_strings[sk->sk_family]);
    }

    // 소켓 상태 변경 콜백 설정
    sk->sk_state_change = sock_def_wakeup;
    sk->sk_data_ready = sock_def_readable;
    sk->sk_write_space = sock_def_write_space;
    sk->sk_error_report = sock_def_error_report;
    sk->sk_destruct = sock_def_destruct;

    // 프래그먼트 초기화
    sk->sk_frag.page = NULL;
    sk->sk_frag.offset = 0;
    sk->sk_peek_off = -1;

    // 피어 관련 설정 초기화
    sk->sk_peer_pid = NULL;
    sk->sk_peer_cred = NULL;
    spin_lock_init(&sk->sk_peer_lock);

    // 송신 대기 플래그 및 타임아웃 설정
    sk->sk_write_pending = 0;
    sk->sk_rcvlowat = 1;
    sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
    sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;

    // 타임스탬프 초기화
    sk->sk_stamp = SK_DEFAULT_STAMP;

    // 32비트 시스템에서 시퀀스 락 초기화
#if BITS_PER_LONG == 32
    seqlock_init(&sk->sk_stamp_seq);
#endif

    // ZC 키 초기화
    atomic_set(&sk->sk_zckey, 0);

#ifdef CONFIG_NET_RX_BUSY_POLL
    //busy_poll 관련 설정 초기화
    sk->sk_napi_id = 0;
    sk->sk_ll_usec = READ_ONCE(sysctl_net_busy_read);
#endif

    // 페이싱 속도 초기화
    sk->sk_max_pacing_rate = ~0UL;
    sk->sk_pacing_rate = ~0UL;
    WRITE_ONCE(sk->sk_pacing_shift, 10);
    sk->sk_incoming_cpu = -1;

    // 수신 큐 초기화
    sk_rx_queue_clear(sk);

    // 메모리 커밋 (RCU 관련 설명은 Documentation/RCU/rculist_nulls.rst 참조)
    smp_wmb();

    // 참조 카운트 초기화
    refcount_set(&sk->sk_refcnt, 1);

    // 드롭 카운트 초기화
    atomic_set(&sk->sk_drops, 0);
}

나머지도 살펴보면 sk의 맴버들을 초기화시키는 코드다.

해당 속성은 현재로써는 어디 사용되는지 파악이 힘들어서 생략하겠다.

나중에 다른 소켓함수에서 사용될 때 다룰 예정이다.

 

 

tcp_v4_init_sock


static int tcp_v4_init_sock(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	tcp_init_sock(sk);

	icsk->icsk_af_ops = &ipv4_specific;

#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
	tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific;
#endif

	return 0;
}

#define inet_csk(ptr) container_of_const(ptr, struct inet_connection_sock, icsk_inet.sk)

sk->sk_prot->init(sk)에서 호출된 tcp_v4_init_sock이다.

inet_csk 매크로를 사용해 sk와 연결된 inet_connection_sock 구조체인 icsk을 캐스팅하고 있다.

inet_connection_sock은 inet_sock의 상위 구조체로, inet_sock가 주로 ip와 포트 레벨의 정보를 담고 있다면  inet_connection_sock는 TCP 프로토콜 관리에 필요한 정보를 추가로 가지고 있다.

 

inet_sock의 경우 기본적인 ip나 포트번호 같은 정보를 담고 있기에 가볍지만, inet_connection_sock은 tcp 프로토콜에 관한 정보를 전부 보유하기에 무겁다.

때문에 두 구조체를 2개를 분리하는 것이다.

 

그 다음 tcp_init_sock을 호출하여 sk와 inet_csk에 tcp 프로토콜에 대한 초기화 작업을 진행한다. 

마지막으로 icsk->icsk_af_ops에 ipv4_specific을 할당하면서 IPv4 함수 포인터들을 설정한다.

 

tcp_init_sock


void tcp_init_sock(struct sock *sk)
{
    // inet_connection_sock 구조체와 tcp_sock 구조체를 가져옴
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    // 초기화 작업 수행
    tp->out_of_order_queue = RB_ROOT; // 순서가 맞지 않는 패킷을 저장할 큐를 초기화
    sk->tcp_rtx_queue = RB_ROOT; // 재전송 큐를 초기화
    tcp_init_xmit_timers(sk); // 전송 타이머를 초기화
    INIT_LIST_HEAD(&tp->tsq_node); // TSQ 노드를 초기화
    INIT_LIST_HEAD(&tp->tsorted_sent_queue); // 정렬된 전송 큐를 초기화

    icsk->icsk_rto = TCP_TIMEOUT_INIT; // 재전송 타임아웃 초기화
    icsk->icsk_rto_min = TCP_RTO_MIN; // 최소 재전송 타임아웃 초기화
    icsk->icsk_delack_max = TCP_DELACK_MAX; // 최대 지연 ACK 초기화
    tp->mdev_us = jiffies_to_usecs(TCP_TIMEOUT_INIT); // mdev_us 초기화
    minmax_reset(&tp->rtt_min, tcp_jiffies32, ~0U); // RTT 최소값 초기화

    tcp_snd_cwnd_set(tp, TCP_INIT_CWND); // 초기 혼잡 윈도우 설정

    tp->app_limited = ~0U; // app_limited를 초기화
    tp->rate_app_limited = 1; // rate_app_limited를 1로 설정

    tp->snd_ssthresh = TCP_INFINITE_SSTHRESH; // 초기 혼잡 윈도우 한계 설정
    tp->snd_cwnd_clamp = ~0; // 혼잡 윈도우 클램프 설정
    tp->mss_cache = TCP_MSS_DEFAULT; // MSS 기본값 설정

    tp->reordering = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_reordering); // 재정렬 값 설정
    tcp_assign_congestion_control(sk); // 혼잡 제어 할당

    tp->tsoffset = 0; // 타임스탬프 오프셋 초기화
    tp->rack.reo_wnd_steps = 1; // RACK 재정렬 창 스텝 초기화

    sk->sk_write_space = sk_stream_write_space; // 쓰기 공간 콜백 설정
    sock_set_flag(sk, SOCK_USE_WRITE_QUEUE); // 쓰기 큐 사용 플래그 설정

    icsk->icsk_sync_mss = tcp_sync_mss; // MSS 동기화 함수 설정

    WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[1])); // 송신 버퍼 설정
    WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_rmem[1])); // 수신 버퍼 설정
    tcp_scaling_ratio_init(sk); // 스케일링 비율 초기화

    set_bit(SOCK_SUPPORT_ZC, &sk->sk_socket->flags); // ZC 지원 플래그 설정
    sk_sockets_allocated_inc(sk); // 할당된 소켓 수 증가
}

 tcp 프로토콜에 대한 초기화 작업을 진행하는 함수이다.

우선 sk를 통해 icsk와 tcp_sock *tp을 캐스팅한다.

 

tp는 tcp 소켓에 대한 정보를 담고 있는 구조체이다.

inet_connection_sock과 공통된 맴버를 공유하며, tp의 경우 연결 상태 관리, 재전송 메커니즘, 혼잡 제어 등 TCP 소켓에 특화된 기능이나 상태를 저장하는데 사용한다.

 

그 이후는 sk와 icsk, tp의 기본값을 설정한다.

커널에 관련되 설정이 많아서 알아볼 수 있는 부분만 파악한다면 타이머와 큐,타임아웃을 초기화하는 것이 보인다.

 

tcp 연결시, 3way-handshake를 진행한다.

icsk->icsk_delack_max = TCP_DELACK_MAX;

이때 syn패킷을 보내고 일정시간 응답이 없으면 연결되지 않는데, icsk->icsk_delack_max에서 지연시간을 설정하는 것이다.

굳이 함수에서 이런 옵션을 초기화 하는 것은 setsocket 같은 함수로 해당 옵션을 조정하기 위함인것 같다.

 

 

 

 

tcp부분은 커널 쪽 옵션이 많아서 파악이 많이 힘들다.

깊게 들어갈려면 함수의 호출이 끊어지지 않아서 tcp는 여기까지 하겠다.

솔직히 한번 제대로 파볼까하는 생각도 했지만, 거의 socket 함수의 몇배나 되는 양을 파악해야하기에 일단은 소켓 관련부분만 하기로 한다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함