지난 페이지에서는 TCP layer에서 socket이 어떻게 만들어지는지와, 그 구조에 대해 알아보았다.
이번 페이지에서는 socket을 이용해 데이터를 주고(TX) 받는(RX) 과정 중 TCP layer에서 어떤 일이 일어나는지 알아보겠다.
TX routine (sending routine)은 프로세스가 데이터 전송을 위해 socket에 write() syscall을 날릴 때 어떻게 작동하는지에 대한 설명이다.
유저가 write()를 날리는 속도나 수가 socket에서 처리해서 날리는 속도보다 비교적 클 수 밖에 없다.
따라서 이를 관리하기 위해 queue가 존재한다. 이 queue가 바로 struct sock 안에 있는 write queue 이다.
write queue는 skb를 doubly linked list로 구현한 queue이다.
전송 과정을 개괄하자면, 유저가 write()를 통해 소켓에 데이터를 쓰면, segment(TCP에서의 데이터 단위)가 write queue로 위치한다.
신뢰성 있는 전송을 위해, 수신자가 보내는 ACK가 올 때까지 queue에 저장해놨다가, ACK가 오면 내보낸다.
아래 function chain 을 통해 더 자세히 알아보자.
컴퓨터 간 커넥션이 established되고 나서, 유저가 write()를 통해 데이터를 전달할 수 있다.
데이터 전송의 과정에서 호출되는 function들은 다음과 같다.
sys_write() -> vfs_write() -> sock_write_iter() -> sock_sendmsg() -> inet_sendmsg() (여기까지 Socket Layer) ->
tcp_sendmsg() -> tcp_write_xmit() -> tcp_trasmit_skb() (여기까지 TCP layer) -> ip_queue_xmit() -> ip_local_out() -> ip_output() -> ip_finish_output() -> ip_finish_output2() (여기까지 IP layer)->

*강의 ppt 재구성
socket layer에서는 처음에 vfs로 관리되는 모습을 볼 수 있다. 하지만 file의 operation에 tcp, ip operation이 hook되어있기 때문에, 관련 함수를 호출한다.
tcp_sendmsg()tcp_sendmsg()는 skb를 생성한 후, 유저가 보내려고 하는 데이터를 커널에 복사함으로써 TCP segment로 만드는 역할을 한다.
구체적으로는
sk_buff)를 하나 만들고,memcpy_from_msg를 통해 유저데이터를 커널에 만들어진 skb에 복사한 후, tcp_write_xmit() congestion control, flow control을 통해 잠시 전송을 중단해야할 경우가 생긴다.
이 함수 이후에 불리는 함수는 바로 skb를 writing queue에 보내는 함수이기 때문에, 이 함수를 통해 전송량을 조절한다.
tcp_transmit_skb() 이 함수가 불렸다는 것은, 오류가 있지 않는 한, 바로 패킷을 write queue로 보낼 수 있다는 것을 의미한다.
보내기 전, 마지막으로 유효한지 체크하며 패킷에 TCP header을 만드는 함수이다.
이후 ip_queue_xmit() 을 호출해 패킷을 IP layer로 보낸다.
RX routines (receive routine)은 프로세스가 L2, L3을 거쳐 전달된 세그먼트를 어떻게 받는지에 대한 동작 설명이다.
NIC가 새로운 패킷을 받으면, CPU에게 알려 interrupt를 발생시킨다. interrupt가 발생하면, interrupt handler을 작동시켜 처리한다. RX routine또한 패킷이 올때 작동하는 interrupt handler 내부에서 일어나는 동작이다.
cf)
패킷이 도착했을 때, 두가지 시나리오가 있을 수 있다.
프로세스가 read() syscall 하면서 기다리고 있을 때
-> *read()는 blocking syscall이어서, 데이터가 다 올 때까지 기다리고 있음.
-> user buffer로 세그먼트 복사.
아직 패킷에 맞는 read() syscall이 호출 안되었을 때(아직 프로세스가 안 읽었을 때)
-> receive queue에서 대기
-> L2, L3, L4까지의 관련 동작들이 모두 처리 되고 난 뒤에 올라와서 기다리고 있는 데이터(세그먼트)이기 때문에, 프로세스가 read()로 소켓 만들면 바로 해당 세그먼트 copy해 감.
cf)
TCP RX operation 방식은 크게 두가지로 나눌 수 있다.
1. slow path
2. fast path
말 그대로, 더 빠른 ops가 이루어질 수 있는지 아닌지에 따라 나눈 것이다.
Header prediction : 헤더(메타데이터)를 보고 추가적인 TCP operation이 필요한지를 판단. 만약 특이사항 없으면 fast path로 직행. (*예측 X)
ex)
ACK 값이 일치할 경우. (내가 보낸게 제대로 전달 됨)
sequence # 가 맞을 경우. (순서가 맞음)
optional flag가 없는 경우 (SYN, URG, FIN, RST와 같이 특이사항 없음)
window size 그대로인 경우 (capacity 그대로)
--> fast path
However...
ACK 값이나 sequence #이 이상한 경우
optinal flag가 enabled된 경우
--> slow path
NIC를 통해 패킷이 들어오면, L2, L3를 지나 TCP layer에 들어오게 된다.
L3, 즉 IP layer부터 TCP layer로 들어오는 과정에서 사용되는 핵심 과정과 함수는 다음과 같다.
ip_rcv() -> ip_rcv_finish() -> ip_local_deliver() -> ip_local_deliver_finish() (여기까지 ip layer) ->
tcp_v4_rcv() -> tcp_v4_do_rcv() -> tcp_rcv_established() -> if slow path, tcp_data_queue(), if fast path tcp_queue_rcv() (여기까지 tcp layer) ->
이후 user의 read()가 호출하는 tcp_recvmsg() 가 데이터 복사할 때까지 receive queue에서 기다리기

*강의 ppt 재구성
tcp_v4_rcv()TCP layer에서 처음 불러와지는 함수. 이 함수에서는
read()로 소켓 안의 skb를 복사하고 있으면) -> backlog queue로 이동 (backlog queue 안에 있는 패킷은 tcp ops 처리가 덜 됐다고 하는 이유)tcp_v4_do_rcv() 호출tcp_v4_do_rcv()모든 전송은 TCP_ESTABLISHED상태일 때만 가능하다. 따라서 이 함수에서 ESTABLISHED 상태인지 체크한다.
만약 ESTABLISHED 상태이면, tcp_rcv_established() 호출
아니라면, tcp_rcv_state_process() 호출해서 처리. (다른 상태의 처리는 이 강의에서는 다루지 않을 예정)
tcp_v4_established()fast path로 갈 지, slow path로 갈 지를 결정하기 위해 header prediction 진행한다.
fast path -> tcp_queue_rcv() 호출
slow path -> tcp_data_queue() 호출
tcp_queue_rcv()바로 receive queue에 enqueue 하는 함수. 유저에게 전달될 수 있는 상태.
tcp_data_queue()추가적인 option, flag 수행.
order가 섞여서 온 패킷은 잠깐 out-of-order queue에 넣어놨다가, 중간에 빠진게 왔으면 reordering.
이상 없는거 다 체크 되면 tcp_queue_rcv() 호출(fast path에서 바로 호출한 것과 동일)
read())유저가 read() (또는 recv() )syscall을 할 경우, 다음과 같은 function chain을 따른다.
recvmsg() -> __sys_recvfrom() -> sock_recvmsg() -> tcp_prot.recvmsg -> tcp_recvmsg() -> 데이터 유저 buffer로 복사
read()를 호출하면, 일단 backlog queue에 있는 것을 모두 꺼내 나머지 ops (tcp_v4_established() 부터) 수행시켜 receive queue나 out-of-order queue로 옮겨 read()진행.tcp_recvmsg() 가 호출되면, lock_sock()이 호출되어 소켓이 사용중임을 나타낸다. 그동안 패킷은 receive queue가 아니라 backlog queue로 간다.TCP를 통한 데이터 복사가 다 끝나면
tcp_send_ack를 통해 잘 받았다고 전달.
TX routine에 있어 sending rate를 고려해야 함.
tcp_write_xmit()으로 제어.