2009년 10월 8일 목요일

[TCP] Sequence Number

Sequence Numbers

받은 ACK는 보낼 SEQ가 된다.
보낼 ACK는 받은 SEQ+N이 된다.
  • SYN : N = 1
  • FIN  : N = 1
  • PSH : N = LEN

[TCP] Pipeline 전송 2 : TCP에서의 Pipeline.

파이프 라인 기법에 대한 개괄적인 설명을 했으니 이제 TCP에서 왜 파이프라인 전송이 필요한지 알아보자. 앞으로 언급할 예에서 나올 수치는 계산하고, 이해하기 쉽도록 과장한 부분이 많을 것이기 때문에 실제적인 수치라 생각해서는 안된다.

 

다음과 같은 상황을 생각해보자.

 

- 서울에 있는 사람이 부산에 있는 사람에게  10Mbit짜리 파일을 전송한다. 파일 전송 시 거리에 따른 지연 시간은 10초이다.

- 네트웍에는 트래픽 부하가 전혀 걸려있지 않고 두 사람만 사용하고 있다. 즉 두 사람은 네트웍의 모든 전송 속도를 사용할 수 있다.

- 이 네트웍은 초당 1Mbit를 전송할 수 있다.

- 계산상의 편의를 위해 Gbit, Mbit에서의 단위는 1024가 아니라 1000로 정한다.

- 수신자는 패킷을 받자마자 ACK를 보내고, 그 ACK의 크기는 송신된 패킷과 같다고 가정한다.

 

이 상황에서 TCP가 파일을 1Kbit의 MSS로 분할하여, 한 패킷 전송 후 ACK를 받은 후 그 다음 패킷을 전송한다고 하면 서울의 송신자는 얼마동안 일을 하게되는가?

 

t=0 가 최초로 전송을 시작한 때라고 생각하고 각각의 상황을 좀 더 자세히 생각해보자.

 

t=0 : 서울의 송신자가 첫 번째 세그먼트를 전송하기 시작한다. 네트웍이 초당 1Mbit를 보낼 수 있으므로,

       첫 번째 비트부터 마지막 비트를 네트웍에 전달하기까지 0.1초가 걸릴 것이다.

t=0.1 : 송신자가 세그먼트의 마지막 비트를 네트웍에 전달했고, 이 마지막 비트가 부산에 도착해야 수신자는 패킷을 인식할 것이다.

t=10.1 : 서울부터 부산까지의 거리 지연 시간이 10초이므로 이 때에 마지막 비트가 부산에 도착한다. 수신자가 패킷을

         받자마자 Ack를 한다고, 또한 그 ACK의 size도 패킷과 동일하다고 가정했기 때문에 바로 Ack 과정이 시작된다.

t=20.1 : 수신자가 Ack한 패킷의 첫번째 비트가 서울에 도착한다. 이제부터 송신자는 네트웍으로부터 ACK 패킷을 받는 동작을 시작한다.

t=20.2 : 송신자가 모든 패킷을 받았다.

 

이 과정에서 송신자가 일하는 시간을 패킷의 전체 송,수신 시간에 대한 비율로 계산해보자.

총 시간 20.2초 중에 송신자는 네트웍으로 데이터를 전달하고, 수신하는 0.2초만 바빴다. 나머지 시간은 ACK가 오기를 기다리면서 멍청히 아무 일도 하지 않았다는 것이다. 비율로 따지면 0.99퍼센트이다. 이것은 웃기지도 않은 효율로서 이 프로토콜을 사용하여 예에서 들었던 네트웍을 사용하면, 아무런 트래픽 부하가 없는 즉, 1Mbps의 속도를 full로 사용할 수 있는 네트웍을 혼자 사용한다 하더라도, 9.9Kbps정도의 속도밖에 사용할 수 없다는 의미이다. 이것은 잘못 설계된 프로토콜이 얼마나 망의 속도를 제한하는지를 보여주는 예라 할 수있다.

 

이러한 문제점을 해결하기 위해 TCP는 파이프라이닝 전송을 사용한다. 즉 패킷에 대한 응답이 도착하기 전에 다음 패킷을 전송시킨다는 것이다. 이것은 우리가 다루었던 송, 수신자의 Idle time을 줄이는 올바른 해결책이지만, 언제나 그렇듯이 또 다른 문제를 발생시킨다. 그것은 패킷 전송 도중 손실된 패킷이나 손상된 패킷이 발생했을 때 그것을 해결하기 위한 좀 더 복잡한 방법을 필요로 한다는 것이다. 그 점에 대해서는 다음 포스트에서 다루도록 하겠다.


[TCP] Pipeline 전송 1 : Pipeline이란 무엇인가?

이제까지 신뢰적인 데이터 전송에 대해 살펴봤다. 데이터를 손실없이 목적했던 곳으로 보내는 것은 우리가 인터넷을 안심하고 사용하는데 있어서 가장 중요한 요소라고 말할 수 있다. 그래서 그런 중요한 부분을 해결하기 위해 TCP는 여러 가지 기법을 적용했다. 그렇다면 데이터가 신뢰적으로 전송된다면 그것으로 모든 것이 해결된 것인가?

 

요즘과 같이 콘텐츠가 홍수처럼 쏟아지고 동영상을 인터넷으로 실시간 플레이해서 보는 경우가 많은 때에는 데이터의 신뢰적인 전송 외에도 그 데이터의 속도 역시 매우 중요한 factor가 된다. 물론 데이터의 속도는 데이터를 많이 보낼 수 있는 매체(광 LAN과 같이...)에 기반하지만, 그 기반이 잘 갖춰진 상태라 하더라도 데이터를 보내는 송신자가 그 속도를 충분히 이용할 수 있도록 데이터를 보내지 않는다면 소용이 없는 것이다. 그렇다면 TCP는 어떤 방법으로 제공된 대역폭을 효율적으로 사용하는가? 그 방법 중 하나가 Pipeline 전송 방식이다. 이번 포스트에서는 TCP가 사용하는 pipeline 전송 방식이 어떤 것인지를 살펴보기위해, 우선 pipeline 전송 방식이라는 것이 무엇인지를 살펴볼 것이다.

 

누가 이름을 Pipeline이라고 붙였는지는 잘 모르겠지만.. 그리고 경우에 따라 여러 가지 용도로 그 의미가 해석되지만, 대체적으로 특정 process의 빠른 처리를 위해 어떤 하나의 operation이 완료되기 전에 그 다음 operation을 진행시키는 방식을 pipeline 방식이라고 한다. 이해를 돕기 위해 CPU에서의 pipeline 방식을 살펴보자. 보통 CPU는 특정 명령을 수행하기 위해 몇 가지 단계를 거치는 데 ARM7의 경우는

 

명령어를 메모리로부터 가져오고 (fetch)

가져온 명령어를 해석한 후(decode)

실행하는(excute)

 

세 가지의 단계를 거친다. 이러한 명령어 수행 단계 중에 CPU가 일을 안하고 놀게 되는 경우가 종종 생기는데, 예를 들어 첫번째 fetch의 과정에서 메모리가 CPU에 비해 속도가 많이 느릴 경우, 메모리에서 명령어를 가져오기까지 CPU가 일을 안하고 놀 수 있고, 또한 실행하는 과정에서도 CPU보다 속도가 현저히 느린 I/O장치 등의 결과를 기다려서 처리하는 과정에서도 그럴 수 있다. 이렇게 CPU가 일을 안하는 상태를 Idle상태라고 하는데 이처럼 CPU의 본래 성능을 다 활용하지 못하는 경우를 피하기 위해 명령어를 pipeline방식으로 실행시킨다. 즉 첫 번째 커맨드를 fetch할 때 메모리로부터 응답을 기다리는 동안 두 번째 커맨드의 fetch를 시작시키는 것이다. 이러한 방식은 CPU와 메모리를 모두 Idle상태에 들어가게 하지 않고 원래 가진 성능을 최대한 활용하게 해준다.

 

그렇다면 TCP는 어떤 방식으로 pipeline 방식을 전송 시에 사용하는가? 쉽게 생각할 수 있듯이 세그먼트를 보내고 응답이 올 때까지 기다리는 시간(Idle)을 없애기 위해 pipeline을 사용한다. 즉 특정 데이터의 전송 시에 Idle 타임을 최소화 시키기 위하여 pipeline 전송 방식을 사용하는 것이다.


[TCP] 신뢰적인 전송 5 : TCP는 실제 Sequence number와 Ack number field에 무엇을 채우는가?

사실 이 포스트를 Timer 전에 넣을까 뒤에 넣을까 나름대로 고민을 많이 했었는데, 왜냐하면 또 다시 Sequence number와 Ack number 이야기를 해야하기 때문이다. 이전 포스트들을 통해 Sequence & Ack number가 어떤 역할을 하는지에 대해 충분히 다루었기 때문에 별도로 추가해야 할 이야기는 없다. 다만 이번 포스트에서는 실제 TCP가 해당 field에 어떤 값을 채우는지에 대해서 다룰 것이다.

 

TCP의 가장 중요한 목적인 신뢰성 있는 전송이라는 측면에서 보면, TCP 패킷 헤더에서 가장 중요한 정보는 Sequence & Ack number이다. 그렇다면 TCP는 이 field에 무엇을 채우는가? 이것과 관련해서 TCP가 데이터를 어떻게 다루는지를 이해하는 것이 중요하다. TCP는 데이터를 바이트 단위로 다룬다. 즉 전송해야할 데이터를 순서대로 정렬된 바이트 스트림으로 취급한다는 이야기다. 따라서 모든 Sequence number와 Ack number는 바이트 단위로 표시된다.

 

설명을 하기 전에 새로운 용어에 대해서 말할 필요가 있을 것 같다. 바로 MSS (Max Segment Size)인데, 이것은 TCP가 한번에 보낼 수 있는 최대의 데이터 량을 의미한다. 또한 MSS를 직역하면 최대 세그먼트 크기인데 여기에서 TCP가 자신이 보내는 패킷의 기본 단위를 세그먼트라고 부른다는 것을 알 수 있다. 이제까지 설명을 할 때 좀 더 친숙한 용어인 패킷으로 설명했지만, 이제부터 TCP와 관련된 설명을 할 때에는 세그먼트라는 단어를 사용하도록 하겠다.

 

예를 들어 5000byte 짜리 파일을 전송하는 경우를 살펴보자. 이 때의 MSS가 100byte라고 생각하면 TCP는 5000 byte짜리 파일을 전송하기 위해 50개의 세그먼트를 생성하게 될 것이다. (5000 / 100 = 50). 이런 경우 TCP는 각 세그먼트의 Sequence Number Field에 Byte 기준 번호를 붙인다. 즉, 첫 번째 세그먼트의 Sequence number가 0이라 가정하면 두 번째 세그먼트는 100번, 세번째 세그먼트는 200번의 식으로 Sequence number가 설정된다.

그렇다면 Ack 번호는 어떠한가? Ack번호의 경우에는 Sequence number보다 약간 복잡한데, 그것은 말 그대로 받은 것에 대한 응답이기 때문이다. 기본적으로 Ack번호는 수신자의 입장에서 송신자로부터 앞으로 받아야할 다음 데이터의 Sequence number이다. 말이 복잡한데, 예를 들어보자. 위에서 든 예에서 만일 송신자가 첫 번째 세그먼트를 보냈다고 생각해보자. 수신자가 데이터를 받으면 그 데이터의 Sequence number는 0번일 것이고 MSS가 100 byte이기 때문에 0번째 byte부터 99번째 byte까지의 데이터가 도착했을 것이다. 그러면 수신자는 자신이 99번째 byte까지 잘 받았고, 앞으로 100번째 byte로 시작되는 세그먼트를 받기 원한다는 표시로 Ack번호를 100으로 붙여 응답하는 것이다.

 

이것은 송신자에게 있어서 수신자의 상태에 대해 부가적인 정보를 주는데, 이 정보는 자신이 어떤 데이터를 보내야할지, 즉 새로운 데이터 스트림을 줘야할지 아니면 이미 보낸 데이터를 재전송해야할지를 결정할 수 있는 중요한 것이다.

이 점을 이해하기 위해 또 다른 예를 들어보자. 위의 파일 전송에서 송신자가 순서대로 3개의 세그먼트를 보냈다고 생각해보자. 즉 Sequence number 0, 100, 200번의 3개의 세그먼트를 통해 300byte를 전송한 것이다. 그런데 첫번째 세그먼트와 세번째 세그먼트는 수신자에게 잘 도착했는데 두 번째 세그먼트가 네트웍에서 손실되었다고 생각했을 때 수신자는 송신자에게 어떻게 응답해야 하는가?

수신자의 입장에서 생각해 보면 현재 두 개의 세그먼트를 받았다. Sequence number 0번의 0~99번째까지의 100byte와 Sequence number 200번의 200 ~ 299번째까지의 100byte이다. 이 때 수신자는 중간의 100 ~ 199번째의 데이터를 기대한다는 의미로 Ack number 100으로 응답할 수 있다. 그러면 송신자는 수신자가 두 번째 세그먼트를 못받았다는 것을 알고 다시 재전송할 수 있다.

 

Reference : Computer Networking (A top down approach featuring the internet), James F. Kurose, Keith W. Ross 저.


[TCP] 신뢰적인 전송 4 : Timer, Time out, RTT

지금까지 TCP가 신뢰적인 데이터 전송을 위해 사용하는 필수적인 정보인 Ack bit, Sequence number, Ack number 대해 살펴보았다. 이번 포스트에서는 그에 더하여 재전송의 필수적인 기능인 타이머에 대하여 알아보도록 하겠다.

 

패킷을 전송함에 있어서 TCP는 패킷이 어떻게 목적지까지 전달되는지를 전혀 알지 못한다(심지어 전달이나 제대로 될지조차 알 수 없다). 그 이유는 패킷의 전송 경로 및 전송 여부가 TCP의 하위 계층 프로토콜인 IP에 의해 결정되기 때문이다. 이미 언급하였듯이 IP는 기본적으로 Best effort 서비스를 제공하기 때문에 패킷이 어떤 경로로 전달 되는지를 관리할 필요가 없다. 라우터의 입장에서 특정 패킷을 받았을 경우, 패킷의 목적지 주소를 확인하고, 자신이 가지고 있는 라우팅 테이블에 등록되어 있는 라우터들 중에서 목적지 주소로 가기에 가장 비용이 저렴한 라우터를 선택해 패킷을 전달하면 라우터는 자신의 역할을 모두 한 것이다.(자세한 내용은 IP를 다룰 때 설명하겠다.) 즉 자신을 포함하여 다음 라우터까지의 단 1홉에 해당하는 경로만을 관리하는 프로토콜인 IP에 의해 패킷이 전달되기 때문에 TCP에서는 자신이 보낸 패킷이 어떤 경로로 목적지까지 갈 것인지를 알 수 있는 방법이 전혀 없다는 것이다.

 

자신이 전송한 패킷이 어떤 경로로 갈지 알 수 없다는 것은 무엇을 의미하는가? 그것은 패킷이 목적지까지 도착하는데 얼마나 시간이 걸릴지 전혀 알 수 없다는 뜻이며, 이에 따라 당연히 응답이 올 때까지 얼마나 기다려야 할지 전혀 알 수 없다는 뜻이기도 하다. 그렇다면 TCP는 타이머를 돌리면서 얼마나 기다려야 할지, 즉 타임 아웃 시간을 얼마로 설정해야할지 어떻게 결정하는가?

 

TCP가 타임 아웃 시간을 어떻게 설정하는지를 살펴보기 전에, 우선 타임 아웃 시간에 대한 기본적인 사항들을 생각해보자. 타임 아웃 시간은 얼마나 길어야 하는가? 쉽게 생각할 수 있듯이 "Time out > 패킷이 목적지까지 가는 시간 + 응답이 되돌아오는 시간" 이다. 만일 타임 아웃 시간이 왕복 시간보다 짧다면 패킷은 불필요하게 재전송 될 것이다. 그럼 타임 아웃을 좀 길게 잡으면 어떨까? 이것 역시 답이 될 수 없다. 이미 말했듯 왕복 시간은 얼마나 길어질 지 아무도 모르기 때문에 여유있게 잡는다고 잡은 타임 아웃 시간보다 훨씬 길 수 있으며, 또 다른 더 큰 문제는 실제로 패킷이 손실되어 정말로 재전송을 해야할 때에 너무 긴 타임 아웃으로 인해 심각한 시간 지연을 가져 올 수 있다는 것이다. 이와 같은 사실을 통해 타임 아웃 시간은 "고정된" 값을 가져서는 안된다는 것을 알 수 있다.

 

그러면 이제 실제로 TCP는 이러한 타이머의 타임 아웃 시간을 어떻게 결정하는지 살펴보자.

타임 아웃 시간을 결정하는 알고리즘은 여러 가지가 있을 수 있지만, 그 중 Van Jacobson이 1988에 제안한 방법에 근거하여 설명하겠다. 이와 관련된 더 자세한 내용을 알기 원한다면 RFC2988을 찾아보기 바란다.

 

우선 설명에 앞서서 계산식에 사용할 변수들을 정의하겠다. 이 정의는 RFC2988에 근거한 것이다.

- RTO (Retransmission Time Out) : 최종적으로 구해야 할 타임 아웃 시간.

- RTT (Round Trip Time) : 패킷이 목적지에 도달한 후, 그에 대한 응답이 돌아오기까지의 시간. 즉 패킷의 왕복 시간. 각 패킷마다 전송 경로 및 그 때마다의 네트웍 부하가 틀리므로 매번의 RTT는 각 패킷별로 틀릴 수밖에 없으며 RTT는 이러한 각 패킷마다의 개별적인 왕복 시간을 의미한다.

- SRTT (Smoothed RTT) : 각 RTT들의 가중 평균치.

- RTTVAR (RTT variation) : 최근의 RTT가 이전까지의 평균 RTT에 비해서 얼마나 빗나갔는지를 나타내는 가중치.

 

용어 설명에서 일부 설명이 되었지만, RTT는 패킷이 TCP단에서 IP단으로 넘어간 시점으로부터 ACK가 되돌아올 때까지의 총 시간을 의미하고, 각 패킷마다 그 때의 망 상황과 경로 선택에 의해 매번 변경될 수 밖에 없다. 따라서 우리는 RTO를 결정하기 위해 이 RTT의 어떤 평균치를 구해야 한다는 것을 알 수 있다. 그러나 단순히 각 RTT를 더한 후, 총 회수로 나누는 단순 평균은 적절치 않은데, 그 이유는 쉽게 생각할 수 있듯이 가장 최근의 RTT가 현재의 망 상황을 더 잘 반영하기 때문이다. 따라서 가장 최근에 측정된 RTT의 비중이 크게 적용되도록 평균을 구해야 하며 이런 식으로 구해진 평균 RTT를 SRTT이라고 부른다. (이와 같은 통계 기법을 EWMA- Exponential Weighted Moving Average라고 하며 예전 값들이 전체 평균에 미치는 영향이 지수적으로 감수하므로 Exponential이라고 부른다).

 

위와 같은 사실들을 기억하고 다음 식을 보자. 다음 식은 SRTT를 구하는 식이다.

 

SRTT_n = ((1-x) * SRTT_m) + (x * RTT)

 

위의 식을 보면 이전까지의 평균치(SRTT_m)와 최근에 측정된 RTT에 가중치(x)를 곱한값을 더해서 가중 평균치(SRTT_n)을 구하고 있다는 것을 알 수 있다. 여기에서의 가중치 x는 일반적으로 0.125 (1/8)인데 이 수치는 V. Jacobson과 M. Karels가 제안한 [JK88] - Congestion Avoidance and Control을 참조하면 된다. 이 수치를 넣어서 위의 식을 다시 정리하면

 

SRTT_n = (0.875 * SRTT_m) + (0.125 * RTT)

 

가 된다.

 

우리가 지금까지 구한 것은 RTT값이었다. 즉 패킷을 전송한 후, 그 패킷이 돌아올 때까지의 평균 시간을 구한 것이었다. 그렇다면 우리가 정작 구해야 할 RTO는 어떻게 결정해야 할까?  명백하게 타임아웃 값은 Ack가 돌아오기 전에 만료되어서는 안된다는 점을 생각해볼 때, RTT의 평균치에 약간의 여분을 더해서 설정되어야 한다. 즉

 

RTO = SRTT + (4*RTTVAR)

 

이다. 여기에서 왜 RTTVAR에 4를 곱하는지에 대한 이유는 아직 찾지 못했다. 어떤 근거가 있을텐데 나중에 찾게 되면 이 포스트를 다시 업데이트 할 예정이다. 우선 RTTVAR은 최근의 RTT가 SRTT에 비해 얼마나 빗나가있는지를 나타내는 정도라고 얘기했다. 그렇다면 위의 식을 생각해볼때, RTTVAR가 크면, 즉 최근의 RTT가 평균치인 SRTT보다 매우 크거나 매우 작으면 RTO는 크게 변한다는 것을 알 수 있다. 그렇다면 RTTVAR은 어떻게 구하는가? RFC2988에 따르면 다음과 같다.

 

RTTVAR_n = ((1-x)*RTTVAR_m) + (x*|RTT - SRTT|)

 

이다. RTTVAR 역시 SRTT와 마찬가지로 변동치 값에 대한 가중 평균치임을 알 수 있다. 여기에서 x는 0.25(1/4)로 제안되었는데, 이 수치 역시 [JK88] - Congestion Avoidance and Control을 참조하면 된다.

 

Reference : RFC2988, RFC1122

[TCP] 신뢰적인 전송 3 : Ack number

이전 포스트를 통해 Sequence Number가 송신자의 입장에서 오류가 있거나 변형된 Ack 혹은 Nak을 받았을 때, 오류로부터 회복하기 위한 방법으로 재전송을 쓸 수 있도록 해주고, 그 때 수신자로 하여금 송신자가 보낸 패킷이 자신이 이미 가지고 있는 패킷이 다시 재전송된 것인지, 아니면 정말 새로운 패킷인지 구분할 수 있게 해준다는 것을 다루었다.

 

사실 재전송은 송신자의 입장에서 봤을 때, 만병통치약과 같다. 내가 의사전달을 하려고 하는데 상대방이 못알아듣는다면, 알아들을때까지 다시 얘기하면 되는 것이다. 하지만 좀 더 깊이 생각해보면 재전송이라는 만병통치약을 무작정 아무 때나 사용할 수 없다는 것을 알 수 있다.

 

우선 생각해봐야 할 사항은 재전송을 해야할 때는 언제인가? 라는 것이다.

Ack나 Nak이 정확하게 도착을 했다면 좋지만, 설령 오류가 생겼더라 하더라도 송신자에게 도착만 한다면, 송신자는 재전송 여부를 쉽게 결정할 수 있다. 그러나 실제 우리가 사용하는 네트웍에서는 종종 패킷이 아주 많이 지체가 되거나 아예 손실이 되어 도착하지 못하는 경우가 있을 수 있다. 그러면 송신자는 그 점에 대한 판단을 무엇으로 해야하는가?

쉽게 생각할 수 있듯이 그 점에 대한 답은 바로 타이머다. 일정 시간을 기다려보고 그 시간이 지났는데도, 응답이 오지 않는다면 다시 재전송하면 되는 것이다. 그렇다면 이번에는 그 일정 시간은 어떻게 정해야 하는가라는 질문이 생긴다. 사실 이 기준은 명확하지가 않다. 송신자는 일단 내보낸 패킷이 어떤 경로를 통해 목적지까지 갈지 전혀 알 수가 없기 때문에 정상적인 왕복 시간을 알 수 있는 방법이 없다. 그럼에도 불구하고, 송신자는 이 타이머의 타임 아웃 시간을 "현명하게" 결정해야 하는데, 이 점을 해결하기 위한 방법은 추후 Round Trip Time에 대해 다룰 때 설명할 것이다.

 

재전송과 관련하여 생각해봐야 할 두번 째 문제는 재전송이 만병통치약인 동시에 골칫거리라는 점이다.

송신자의 입장에서, 송신 경로는 트래픽 상황이 좋은데, 수신 경로는 과도한 트래픽에 의해 정체되고 있다고 가정을 해보자. 이러한 상황에서 송신자가 A라는 패킷을 수신자에게 보냈다면, 송신 경로의 상태는 좋기 때문에, 수신자는 이 패킷을 잘 수신했을 것이다. 그러나 수신 경로는 상황이 안좋기 때문에 수신자가 보낸 B라는 Ack는 송신자의 타임아웃이 지날만큼 오랜 시간동안 네트웍에서 지연된 후에 송신자에게 도착할 것이다.

이러한 상황은 정상적인 Ack가 송신자에게 도착했음에도 불구하고 재전송을 일으키게 하고 수신자 입장에서는 이미 받은 패킷을 또 받는 상황을 발생하게 한다. 이미 이전 포스트에서 언급했듯이 Sequence Number가 있기 때문에 수신자 입장에서 패킷을 수용할 것인지의 여부를 결정하는 것은 어렵지 않다. 하지만 수신자가 다시 이 중복된 패킷에 대해 Ack를 보낼 때 송신자는 이 Ack가 가장 최근에 전송된 패킷에 대한 응답인지, 중복된 패킷에 대한 응답인지 알 수 없는 문제가 생긴다.  

 이 문제를 해결할 수 있는 것이 바로 Ack Number이다. Ack number는 송신자로 하여금 자신이 받은 Ack 혹은 Nak이 자신이 보낸 패킷 중 어느 패킷에 대한 응답인지를 알 수 있게 해주면서, 그와 동시에 수신자가 어떤 패킷까지 잘 받고 있는지에 대한 상황 정보를 알려준다.

 

즉 신뢰적인 전송의 핵심은 바로 재전송이며, 그 재전송을 언제, 어떻게 할 것인지에 대한 정보를 Ack bit와 Sequence number, Ack Number를 통해 결정한다고 생각하면 되겠다. 물론 그와 관련된 timer의 역할 역시 중요하지만 그 점은 추후에 다시 다루도록 하겠다.

 

Reference : Computer Networking (A top down approach featuring the internet), James F. Kurose, Keith W. Ross 저.


[TCP] 신뢰적인 전송 2 : Sequence Number

이전 포스트에서는 신뢰적인 전송을 위한 가장 기초적인 정보인 Ack bit에 대해 살펴보았다. Ack bit만 생각한다면 단지 1bit 뿐인 Ack bit 정보로도 통신에 어려움이 없을 듯 보일지 모르지만, 사실은 전혀 그렇지 않다. 이전 포스트에서 예로 들었던 부부를 생각해보자. 그 예에서 남편은 귀가 어두운 사람으로만 가정했지만, 말도 잘 못한다고 생각해보자.

 

아내 : 여보~ 밥 먹어요.

남편 : 미ㅏ울ㄴ~~ (뭐라고?? 라는 말이다.)

아내 : -_-;;

 

남편은 귀가 어두우므로 아내가 밥 먹으라고 부르는 말을 제대로 알아듣지 못했다. 그래서 NAK을 보낸다고 보냈는데 말을 제대로 잘 하지 못했기 때문에 아내는 남편의 응답을 이해할 수 없었다. 1bit 뿐인 Ack bit의 문제는 바로, Ack이던 Nak이던 수신자의 응답이 변형되어 송신자가 그 응답을 제대로 해석할 수 없는 경우가 생길 때 대처할 방법이 없다는 것이다. 이러한 문제에 대한 해답으로는 어떤 것을 생각해볼 수 있을까?

 

1.먼저 남편이 제대로 말을 못했기 때문에 아내가 자신이 알아들을 때까지 남편에게 응답을 다시 요청하는 방법이다. 그러나 이 방법은 남편이 아내의 재요청조차 제대로 알아듣지 못할 가능성이 많다는 점에서 성공 확률이 높지 않을 뿐더러 새로운 제어 패킷, 즉 아내 입장에서 "내가 뭐라고 말했는지 알아들었어요?" 라는 새로운 패킷을 만들고 그에 대한 응답을 관리해야 한다는 측면에서 송신자의 복잡도를 증가시킬 수 있다.

 

2.남편의 응답을 못알아들었다면 아내가 했던 말을 무조건 다시 하는 것. 즉 수신자의 응답이 Ack인지 Nak인지 판단할 수 없다면 송신자가 무조건 데이터를 재전송하는 것이다. 이 방법은 송신자의 입장에서 무척 간단하면서도 올바른 해답이 될 수 있다. 그러나 이 방법의 문제는 아내가 재전송을 했을 때 남편 입장, 즉 수신자 입장에서는 새로온 받은 패킷이 아내가 재전송한 패킷인지, 아니면 새로운 패킷인지 구별할 수 있는 방법이 없다는 것이다.

 

2번의 문제를 해결할 수 있는 방법이 바로 Sequence Number이다. 송신자가 데이터를 전송할 때, 각 패킷에 그 패킷의 번호를 붙여서 전송하면 수신자는 그 번호를 보고, 새로 받은 패킷이 재전송된 패킷인지, 아니면 새로운 패킷인지를 구분할 수 있는 것이다.

 

Reference : Computer Networking (A top down approach featuring the internet), James F. Kurose, Keith W. Ross 저.


[TCP] 신뢰적인 전송 1 : ACK bit

우선 TCP가 신뢰적인 전송을 위해 어떤 동작을 하는지 살펴 보기 전에 TCP가 실제로 네트웍에 전송하는 패킷의 구조를 살펴본다면 이해에 도움이 될 것이라 생각한다.

 

아래 그림과 같이 TCP 헤더는 여러가지 field로 구성이 되어 있다.

 

 

오늘 올릴 글의 제목이 "신뢰적인 전송"인 만큼 그에 해당하는 Field를 먼저 살펴보도록 하겠다. 신뢰적인 전송을 위한 주요 정보는 위 그림에서 주황색으로 표시된 ACK bit와 Sequence number field, Ack number field인데 먼저 ACK bit에 대해 살펴보도록 하자.

 

TCP/IP의 개요를 소개하는 글에서 TCP를 간단하게 요약할 때, "무조건 안전하게 데이터를 목적지까지 보내"주는 프로토콜이라고 설명한 바 있다. 이것이 바로 신뢰적인 데이터 전송을 뜻하는 말이라 할 수 있는데, 그렇다면 차분히 생각해보도록 하자. 어떻게 하면 송신자가 수신자에게 전달하려는 데이터를 안전하게 전달할 수 있는가? TCP라는 복잡한 프로토콜을 떠나서, 우리가 일상 생활에서 나누는 대화를 생각해보자. 귀가 다소 어두운 남편에게 말을하고 있는 아내의 가정해보면 이 아내는 남편이 말을 잘 못알아 듣는다는 것을 알고 있기 때문에 자신의 말을 잘 알아들을 수 있도록 노력을 할 것이다.

 

아내 : 여보~ 밥 먹어요.

남편 : 뭐라고?

아내 : 밥 먹으라구요~

 

여기서 아내는 송신자로, 귀가 어두운 남편은 신뢰적이지 못한 전송 채널로 구성된 네트웍의 반대편에 있는 수신자로 이해할 수 있다. 잘 알아듣지 못하는 남편을 위해 아내는 어떤 행동을 하고 있는가? 알아들을 때까지 다시 말하는 것, 즉 재전송을 하고 있다. 재전송을 통해 남편은 아내가 하는 말을 언젠가는 반드시 듣게 된다는 것을 알 수가 있다.

 

 그렇다면 재전송은 과연 의사전달을 위해 필요한 단 한가지의 조건인가? 우리가 추가적으로  한가지 더 생각해야 할 것은 "재전송을 언제해야하는 것인가?" 이다. 우리가 충분히 생각할 수 있듯이, 재전송은 상대방이 알아듣지 못했을 때만 필요한 것으로, 이미 알아들었는데 똑같은 데이터 트래픽을 다시 발생시킬 필요는 전혀 없는 것이다.  

 위의 부부를 생각해봤을때, 아내는 무엇을 기준으로 다시 한번 말할 것인지를 결정할 수 있는가?  그것은 바로 남편의 응답이다. 아내가 말을 걸었을 때, 남편은 알았들었다면 "응,알았어"하고 ACK를, 못알아들었다면 "뭐라고?"하면서 NAK을 아내에게  표시해야하는 것이다.

 

TCP segment의 ACK bit는 바로 이러한 역할을 한다. 송신자가 송신한 데이터를 잘 받았다면 수신자는 응답시 ACK bit를 set하여 보낸다. 그러면 송신자는 응답 패킷에서 ACK bit를 확인한 후 재전송 여부를 결정할 수 있다.

 

Reference : Computer Networking (A top down approach featuring the internet), James F. Kurose, Keith W. Ross 저.


[TCP] TCP(Transport Control Protocol) 개요

TCP는 transport 계층의 연결 지향형(Connection Oriented) 프로토콜로서, 같은 transport 계층 프로토콜이면서 비연결형 서비스인 UDP와 자주 비교되곤 한다. 연결 지향형과 비연결형이라는 용어에서 알 수 있듯이 TCP와 UDP는 데이터 전송 방식에 있어서 큰 차이점이 있다. 이 차이점을 이해하고 그것에 필요한 프로토콜 요구 사항을 잘 숙지하게 되면 꼭 네트웍 프로토콜이 아니라 하더라도 다른 프로토콜을 공부해야할 때가 왔을 때 도움이 많이 될 것이다. 심지어 USB protocol에서도 UDP와 TCP의 컨셉과 비슷한 여러가지 통신 방식을 제공하고 있다. 우선 내가 블로그에서 설명하려 하는 것은 TCP이므로 UDP는 논외로 하도록 하겠다.

 

TCP는 매우 복잡한 protocol이지만, 연결 지향형이라는 프로토콜의 컨셉에 대해 아주 간략히 핵심만 정리하면 "네트웍이 제 기능을 하고 있는 한 무조건 안전하게 데이터를 보내준다"는 것이다. 즉 TCP를 사용하는 application에서는 자신이 보내려고 TCP layer로 넘겨준 데이터에 대해서는 목적지에 도착하지 않을 가능성에 대해 걱정할 필요가 없다는 것이다. 이와 같은 기능을 위해 TCP는 ACK & NAK, Ack number, Sequence number, GBN & Selective Repeat, Flow control 등의 매우 다양한 전송 기법을 사용하고 있다. 이 각각의 기법에 대한 설명은 뒤에 계속 하도록 하겠다.

 

 연결 지향형이라는 TCP의 컨셉은 비신뢰적인 하위 프로토콜인 IP로 인해 두드러진다. IP는 Best effort service를 제공하는데, 말 그대로 최선의 노력을 다하는 서비스를 제공한다. 여기서 최선을 다하는 서비스라는 것은 "넘겨준 데이터를 원하는 목적지로 최선을 다해서 보내기는 하겠지만, 만약에 전달이 안되면 그건 나도 어쩔 수 없다" 라는 의미를 가지고 있는데, 이것을 바꿔 말한다면 "보내고 싶으면 보내라. 전달을 하긴 하겠는데 잘 갈지는 모르겠다"라는 말과 동일한 것이다. 만일 TCP같은 프로토콜이 없다고 생각한다면 우리가 사용하고 있는 Telnet, HTTP, e-mail, FTP같은 수많은 application 들은 사용이 불가능하였을 것이다. 생각해보라. Telnet으로 서버에 로그인하려고 하는데 내가 타이핑한 password가 전송 도중에 깨진다던가, HTTP로, 혹은 FTP로 다운받는 파일이 깨지는 경우... 내가 보낸 e-mail이 전송 도중 데이터가 변경되어 엉뚱한 내용으로 전달이 되는 경우... 이러한 모든 경우는 우리가 결코 원하지 않는 상황인 것이다.

 

TCP의 또 다른 중요한 컨셉은 혼잡 제어(Congestion control)이다. 처음에 혼잡 제어를 공부할 때는 흐름 제어(Flow control)와 많이 혼동이 되었는데, 이것은 분명한 차이가 있는 것으로 혼잡제어는 송신자와 네트웍간의 관계이며, 흐름 제어는 송신자와 수진자 간의 문제인 것이다.

 

 혼잡 제어의 필요는 네트웍의 Band width를 효율적으로 사용하는 문제와 trade off의 관계에 있다. 예를 들어 초당 100byte를 전송할 수 있는 네트웍을 혼자 사용할 때에는 초당 100byte를 보냄으로써 네트웍이 제공하는 최고의 속도를 사용할 수 있다. 그러나 사용자가 2명으로 늘어난다면 둘은 공평하게 초당 50byte만을 보내야 할 것이다. 100byte/sec의 용량을 가진 네트웍이라고 해서, 둘 다 최대 속도로 전송한다면 네트웍은 과부하에 걸려 두 명 모두 인터넷을 사용할 수 없을 것이기 때문이다.

 

 TCP는 모든 TCP 사용자가 공평한 네트웍 사용률을 가지도록 하기 위해 사용자가 전송하는 데이터 량을 "사용자가 모르게" 조절한다. 이것은 내가 보내려고 시도하는 데이터 전송 속도가 네트웍의 혼잡도에 의해 TCP로부터 억제될 수 있다는 것이다. 이러한 TCP의 특성은 속도를 중요시하는 application 설계자로 하여금 TCP의 사용을 주저하게 만들 수 있다. 이와 대조적으로 UDP는 이러한 혼잡 제어 매커니즘을 가지고 있지 않으므로, TCP가 제공하는 신뢰적인 데이터 전송 기능을 application layer에서 구현을 한다면, TCP를 사용하면서 가지게 되는 속도 억제를 피하면서 traffic을 네트웍으로 내보낼 수 있다는 것이다.

 

 UDP가 어떻게 동작하는 지는 논외로 하고, 추후 TCP가 이러한 혼잡 제어를 위해 사용하는 여러 가지 기법, 즉 Slow start, adaptive round trip time, additive increase multiplicative decrease algorithm 등을 설명할 것이다.  

 

Reference : Computer Networking (A top down approach featuring the internet), James F. Kurose, Keith W. Ross 저.


[Linux] lsof 이용한 시스템 조사 및 분석

1. lsof 란?

# whatis lsof
lsof(8) - list open files ( 열려진 파일들을 보는 명령어 )

일반적으로 시스템에서 동작하고 있는 모든 프로세스에 의해서 열려진 파일들에
관한 정보를 보여주는 시스템 관리 명령어


2. lsof 활용법

lsof sample usage
---------------------

파일
# lsof /etc/passwd
특정 파일을 엑세스하고 있는 프로세스 확인

# lsof /tmp
list all files open in /tmp

# lsof L
list the link counts of open files

# lsof L1
select only files that have a link count less than the upper bound

# lsof -a L1 /home
-a (AND 옵션) /home 디렉토리 밑에서 검색



인터넷 소켓

# lsof -i
list all open Internet socket files
# lsof -iTCP@bae.nuri.net
# lsof -iTCP@bae.nuri.net:ftp-data
# lsof -iTCP:327771-327776
# lsof -i@192.168.0.1

특정 호스트(또는 ip)에 대한 접속 확인

# lsof -i @test.sun.com:80
특정 포트로 접속한 리스트 확인

# lsof -u $UID
특정 유저가 사용하는 프로세스 확인

# lsof -u ^root,yskim,admin
특정 사용자를 제외한 정보를 출력하고자 할때는 "^" 심볼을 사용하여 사용자명을 기술하면 되고,
여러명을 동시에 지정하려면 ","를 이용하여 사용자명을 나열하면 된다.


프로세스

# lsof -p $PID
지정한 프로세스가 오픈한 파일 리스트 확인

# lsof -c $CMD
지정한 실행 프로그램이 엑세스하고 있는 파일 리스트 확인

출 처 : http://blog.pages.kr/292


[Linux] HDD 사용량 확인

HDD의 사용량을 보기 위해서는 df를 이용한다.
[CF.AS4:/usr] df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/hda2             4.0G  3.2G  587M  85% /
/dev/hda1             251M   20M  219M   9% /boot
none                  248M     0  248M   0% /dev/shm
/dev/hda5              33G   23G  8.0G  74% /users

해당 디렉토리의 사용량을 보려면 du를 이용한다.
[CF.AS4:/] du -sh /usr
3.1G    /usr

해당 디렉토리의 하위 디렉토리의 사용량을 보려면 xargs를 이용한다.
[CF.AS4:/] ls /usr | xargs du -sh 2> /dev/null
26M    etc/
75M    lib/
16M    sbin/
8.4M    bin/


2009년 10월 7일 수요일

[Linux] ulimit

ulimit 사용법

ulimit [옵션] 값
    -a: 모든 제한 사항을 보여준다.
    -c: 최대 코어 파일 사이즈
    -d: 프로세스 데이터 세그먼트의 최대 크기
    -f: shell에 의해 만들어질 수 있는 파일의 최대 크기
    -s: 최대 스택 크기
    -p: 파이프 크기
    -n: 오픈 파일의 최대수
    -u: 프로세스 최대수
    -v: 최대 가상메모리의 량

ulimit
    사용자별 프로세스(태스크)와 사용 파일 수의 제한을 결정할 수 있는 ulimit이 있는데
    먼저 프롬프트에서 다음과 같이 입력해 보자.

    [root@malsik]# ulimit -a
        그러면 다음과 같은 결과가 출력될 것이다.

    core file size (blocks) 0
    data seg size (kbytes) unlimited
    file size (blocks) unlimited
    max memory size (kbytes) unlimited
    stack size (kbytes) 8192
    cpu time (seconds) unlimited
    max user processes 256
    pipe size (512 bytes) 8
    open files 1024
    virtual memory (kbytes) 2105343
    max user process의 값이 256인 것을 확인할 수 있다. 이 값을 무한대로 바꾸기
    위해서는 다음과 같이 한다.

    [root@malsik]# ulimit -u unlimited
        한 사용자가 열 수 있는 파일 수를 바꾸려면 다음과 같이 한다.

    [root2maso]# ulimit -n 4096
        다시 ulimit -a를 입력하면 변경된 사항을 살펴볼 수 있을 것이다. 설정하려는 값이
        항상 적용되기 위해서는 쉘 스크립트(/root/ .bashrc)에 사용한 명령어를 추가시켜 준다.

====================================================================

- 참고사항 -
[ ulimit & 커널 소스 헤더 분석 ]
[ /usr/src/linux-2.4.17/include/linux/fs.h ]
/*
* It's silly to have NR_OPEN bigger than NR_FILE, but you can change
* the file limit at runtime and only root can increase the per-process
* nr_file rlimit, so it's safe to set up a ridiculously high absolute
* upper limit on files-per-process.
*
* Some programs (notably those using select()) may have to be
* recompiled to take full advantage of the new limits..
*/

/* Fixed constants first: */


/* 한 프로세스당 화일 수의 절대 한계점
*/
#define NR_OPEN (1024*1024) /* Absolute upper limit on fd num */

/* 한 유저가 열 수 있는 최대 화일수
*/
#define INR_OPEN 1024 /* Initial setting for nfile rlimits
*/

/* 시스템에서 동시에 열수 있는 최대 화일수 */
#define NR_FILE 8192 /* this can well be larger on a larger
system */
/* /proc/sys/fs/file_max file-nr 값과 같다.

[root@vm_master fs]# cat file-max
8192
[root@vm_master fs]# cat file-nr
298 50 8192

현재 할당되 화일 핸들수
현재 할당된 화일 핸들중 사용중이 핸들수 최대 사용 할 수 있는 화일 핸들수

[ /usr/src/linux-2.4.17/Documentation/filesystems/proc.txt ]
[ /usr/src/linux-2.4.17/include/linux/limit.h ]

#ifndef _LINUX_LIMITS_H
#define _LINUX_LIMITS_H
#define NR_OPEN 1024 # 한 프로세스당 열수 있는 파일수
#define NGROUPS_MAX 32 /* supplemental group IDs are
available */
#define ARG_MAX 131072 /* # bytes of args + environ for exec
() */
#define CHILD_MAX 999 /* no limit :-) */
#define OPEN_MAX 256 /* # open files a process may have */
#define LINK_MAX 127 /* # links a file may have */
#define MAX_CANON 255 /* size of the canonical input queue
*/
#define MAX_INPUT 255 /* size of the type-ahead buffer */
#define NAME_MAX 255 /* # chars in a file name */
#define PATH_MAX 4095 /* # chars in a path name */
#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe
*/
#define RTSIG_MAX 32
#endif

==================================================

#usr/src/linux~~/include/linux/posix_types.h

       FD_SETSIZE 값 8192로 변경      <--- 원격에서 ulimit -a  검색시 보여주는 형식


출처 : http://malsik2004.tistory.com/

[Linux] 시스템 최적화 - 동시사용자 늘리기위한 커널 조정

글쓴날 : 2000년 2월 24일(목)
글쓴이 : 문태준
(http://www.taejun.pe.kr, taejun@taejun.pe.kr, taejun@hitel.net)


참고자료
하이텔 리눅스동호회 Tip&경험담 lt 동시로 검색
20만통의 전자메일과 sendmail(마소 99. 05) 중 내용
기타 리눅스 및 유닉스 시스템 관리 관련 서적
운영체제론 서적

** 이글은 실제로 제가 대형서버를 운영하고 있는 상황에서 나온
글은 절대 아니며 그냥 여기저기서 주워들은 이야기들과 자료들을
토대로 해서 작성한 것입니다. 동시접속이 몇백명에서 몇천명되는
서비스를 하려면 단순하게 커널 컴파일한번 하고 끝나는 것이 아니라
여러가지 조정을 해주어야할 것이 많습니다. 또한 하드웨어와 OS
제한이 있으므로 로드밸런싱(부하분산), 트래픽분산등을 하는
스위칭 장비, 클러스터링 시스템 구성이 필요할 수 있습니다.
그만큼 설계를 잘 해야합니다. 여기에 언급한것은 단지 쉽게
할 수 있는 맛보기정도이지요. 이로인해서 생기는 손해에 대해서는
본인이 절대 책임을 지지 않습니다. 또한 내용중 틀린 부분도
많이 있을 것입니다.


0. 들어가며

대규모 서비스를 준비하는 경우 운영체제의 제한사항을 먼저 확인해야한다.
동시에 열수 있는 총파일수, 한 프로세스가 열수 있는 파일수 등등.

예를 들어 대형 웹서버를 아파치로 서비스하는 경우를 생각해보자.
아파치는 기본적으로 프로세스 방식으로 서비스를 처리한다. 이건
사용자의 요구가 올때마다 하나의 프로세스를 띄우므로 만약 동시에
10명의 사용자가 접속을 하면 10개의 프로세스가 떠야한다는 것이다.
최근의 아파치 서버는 MaxClients 150 이라고 설정되어있다. 이건
동시에 150개의 프로세스를 띄울수 있으며 결국 동시에 150명을 받아
들일 수 있다는 것이다.(실제로 이정도만 하더라도 절대로 작은 규모는
아니다) 그런데 만약 nobody가 만들어낼 수 있는 최대 프로세스 개수가
그 이하라면? 당연히 문제가 생길 것이다. 물론 최근 레드햇 6.0 이상
버전은 그 이상으로 맞추어져 있어서 문제가 생기지는 않겠지만.

문제는 프로세스가 많이 뜨면 프로세스뿐만이 아니라 열 수 있는 파일
수에서도 문제가 된다.

그러면 먼저 프로세스의 자원 한도에 대해서 알아보자.


1. 프로세스의 자원한도
프로세스의 자원한도를 리눅스에서는 ulimit 를 통해서 알 수 있다.
(Redhat 6.0, PowerLinux 등)

# ulimit -a (또는 ulimit -Sa) --> soft 한도
core file size (blocks) 0
data seg size (kbytes) unlimited
file size (blocks) unlimited
max memory size (kbytes) unlimited
stack size (kbytes) 8192
cpu time (seconds) unlimited
max user processes 2048
pipe size (512 bytes) 8
open files 1024
virtual memory (kbytes) 2105343



# ulimit -Ha ------>> hard 한도
core file size (blocks) unlimited
data seg size (kbytes) unlimited
file size (blocks) unlimited
max memory size (kbytes) unlimited
stack size (kbytes) unlimited
cpu time (seconds) unlimited
max user processes 2048
pipe size (512 bytes) 8
open files 1024
virtual memory (kbytes) 4194302


소프트한도는 새로은 프로세스가 만들어졌을때 디폴트로 적용되는
자원의 한도입니다. 이것을 하드한도까지 증가시킬 수 있습니다.
그렇지만 이 한도를 넘어서 확장하는것은 슈퍼유저만이 가능합니다.
하드한도는 절대적인 선이지요.
그렇다면 하드한도는 슈퍼유저라고 무한대로 늘릴수 있는가?
절대 아니지요. 이건 커널차원에서 지정을 해야합니다.
이에 대해서는 뒤에서 설명합니다.

위에서 각 항목을 볼까요?

코어파일의 최대크기
프로세스의 데이타 세그먼트 최대크기
쉘에서 생성되는 파일ㅢ 최대크기
resident set size의 최대크기(메모리 최대크기)
프로세스의 스택 최대크기
총 누적된 CPU시간(초)
단일 유저가 사용가능한 프로세스의 최대갯수
512-바이트 블락의 파이프 크기
open file descriptors의 최대 숫자(열수있는 최대파일수)
쉘에서 사용가능한 가상 메모리의 최대용량


각 설정을 수정할 수 있는데 학교에서 실습용 워크스테이션으로
쓰는게 아니라면 보통 이런작업을 할 일은 없을듯.
여기서 각 항목에 대해서 잘 모른다면 프로그래밍, OS 관련책을
보셔야할듯. (실은 저도 다 까먹었음)
위에서 하나의 프로세스가 열 수 있는 파일의 최대값은 ulimit
명령을 이용해서는 수정이 되지 않습니다. 단지 보여주기만
할 뿐이죠. 이건 커널소스를 뜯어고치든지 /proc 에서
직접 수정하든지 해야합니다.

최근의 리눅스배포판에서는 유저당 총 2048개의 프로세스를
띄울 수 있고 하나의 프로세스가 총 1024개의 파일을 열 수
있습니다. 실제로 이건 커널 2.0대에서 2.2대로 올라가면서
가장 크게 변한 부분중의 하나입니다. 실제로 이정도만해도
일반적인 서비스에서는 충분하고 이걸 바꾸어야 할 경우는
거의 없을 것입니다. 따른 말로 해서 "떡을 칩니당"
그런데 우리의 목표는 대규모 서비스를 하는 것이잖아요?




2. 파일, 프로세스 갯수 조정

주요하게 살펴야할 것듯.

리눅스에서 동시에 열 수 있는 파일수 : NR_FILE , 4096
한 프로세스가 열 수 있는 파일수 : NR_OPEN, 1024


# vi /usr/src/linux/include/linux/fs.h

#define NR_OPEN (1024*1024) /* Absolute upper limit on fd num */

#define NR_FILE 4096 /* this can well be larger on a larger system */


여기서 한 프로세스가 열수 있는 파일수를 수정하려면
/usr/src/linux/include/linux/limits.h 에서

#define NR_OPEN 1024

이부분도 수정을 해주어야합니다.

수정을 하였으면 컴파일을 해서 테스팅을 해야겠지요.
여기서 주의할 것은 메모리가 적은 시스템의 경우라면 부팅이 되지
않을 수도 있습니다.


그런데 굳이 컴파일을 하지 않아도 /proc 를 이용 변경할 수 있습니다.

# cat /proc/sys/fs/file-max
4096

# echo 8192 > /proc/sys/fs/file-max
# cat /proc/sys/fs/file-max
8192


# cat /proc/sys/fs/file-nr
591 184 8192

여기서 591는 현재 할당된 파일핸들, 184는 그중사용된 파일핸들,
8192는 파일핸들의 최대숫자입니다. 할당된 파일핸들이 최대치로
가더라도 실제 사용된 파일핸들의 숫자가 여유가 있다면 걱정할
필요는 없습니다.
(만약 시스템에서 "running out of file handles" 라는 메시지가
나온다면 열수 있는 파일에 문제가 있다는 것입니다)


그런데 안타깝게도 한 프로세스당 열수 있는 파일수(NR_OPEN)은
쉽게 바꿀 수가 없습니다. 기본 1024인데 이건 위에서 말을 하대로
컴파일을 하세요. 그다음 시스템이 뻗을지 계속 유지될지는...


여기서 좀만 더 살펴보지요.
(다시 NR_FILE 이 4096이라 하구요)

# cat /proc/sys/fs/inode-max
8319

# cat /proc/sys/fs/inode-nr
8340 1006


파일 핸들에 따라 커널에서 동적으로 inde structures를 할당합니다.
inode-max는 inode 핸들러의 최대값입니다.
여기서 inode-max 값은 3-4배이상으로 하라고 추천합니다.

/usr/src/linux/Documentation/proc.txt 에 /proc 에 대한
상세한 설명이 나와있는데 이에 대해서 살펴보지요.


This value should be 3 to 4 times larger than the value
in file-max, since stdin, stdout, and network sockets also need an
inode struct to handle them.

그러니깐 stdin(표준입력), stdout(표준출력), 네트웍 소켓에서
파일을 다루기 위해 inode struct 가 필요하다고 하네요.


메일서버가 200개 이상 실행되거나 웹서버가 동시에 400개 이상
실행되는 대형 사이트에는 최대값을 크게 만드는 것이 좋다고
하네요. 그런데 중요한건 무조건 늘린다고 좋은것은 아니겠지요?
최대값이 늘어날 수록 더 많은 메모리가 필요하고 자원할당도
더 많이 필요하기요.

그래서 가능하면 어떤 프로그램에서 쓸데없는 파일은 열지 않도록
하면 파일핸들을 할당하지 않기때메 열린 파일수가 줄어들겠지요.
하나의 열린 파일수를 줄이면 268개의 파일을 닫는 효과가
있다고 하는데 제가 이부분은 아직 정확히 이해가 가지 않습니다.


이번엔 시스템의 최대 프로세스수를 생각해볼까요?
예를 들어 텔넷으로 접근하는 경우 최소한 로그인프로그램과
쉘프로그램 두개의 프로세스가 생성되겠지요? 256명이 접근하면
최소 512. 그렇다면 시스템의 최대 프로세스 갯수를 설정하는
것도 위와 비슷합니다.

/usr/src/linux/include/linux/tasks.h

여기서보면

#define NR_TASKS 2560 /* On x86 Max 4092, or 4090 w/APM configured. */

#define MAX_TASKS_PER_USER 2048

x86 시스템에서는 최대 4092까지 가능하다고 되어있고
유저당 최대 프로세스수는 2048입니다. 이건 ulimit에서
보았지요?


자 이제 마지막으로 프로세스에서 열어놓은 파일을 어떻게
확인할 것인가?

lsof 프로그램을 이용하면 됩니다.

man 페이지만 하더라도 프린트하면 한 30여페이지가 되는것
같네요. 한번 연구해볼 가치가 있을듯.

오픈파일은 정규파일, 디렉토리, 블락 및 캐릭터파일(device),
라이브러리, executing text reference, 스트림 또는
네트웍파일(인터넷 소켓, NFS file 또는 유닉스 도메인 소켓)
등이 있습니다.

2009년 10월 6일 화요일

HTTP Status Code

1. Informational
100 : Continue
101 : Switching Protocols

2. Successful
200 : OK
201 : Created
202 : Accepted
203 : Non-Authoritative Information
204 : No Content
205 : Reset Content
206 : Partial Content

3. Redirection
300 : Multiple Choices
301 : Moved Permanently
302 : Found
303 : See Other
304 : Not Modified
305 : Use Proxy
306 : (Unused)
307 : Temporary Redirect

4. Client Error
400 : Bad Request
401 : Unauthorized
402 : Payment Required
403 : Forbidden(접근권한 제어)
404 : Not Found(파일 없음)
405 : Method Not Allowed
406 : Not Acceptable
407 : Proxy authentication Required
408 : Request Timeout
409 : Conflict
410 : Gone
411 : Length Required
412 : Precondition Failed
413 : Request Entity Too Large
414 : Request URI Too Long
415 : Unsupported Media Type
416 : Requested Range Not Satisfiable
417 : Expectation Failed

5. Server Error
500 : Internal Server Error
501 : Not Implemented
502 : Bad Gateway
503 : Service Unavailable
504 : Gateway Timeout
505 : HTTP Version Not Supported

출처 : http://blueb.net/blog/815

[Programming] Client Socket Port 지정

클라이언트 소켓에서 포트를 지정해서 바인딩 해주면 된다.
간단하지만 알아내는 데 한~참 걸렸다.

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if(argc !=4){
        printf("Usage : %s <host> <port> <client port>", argv[0]);
        return 0;
    }
    
    int sockfd;
    char *host = argv[1];
    int sport = atoi(argv[2]);    //Server Port
    int cport = atoi(argv[3]);    //Client Port

    struct sockaddr_in svr, cli;

    int recvlen = 0;
    int on = 1;

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("[Server]Socket Opening Failed!");
        return 0;
    }

    memset(&svr, 0x00, sizeof(svr));
    svr.sin_family = AF_INET;
    svr.sin_port = htons(sport);
    svr.sin_addr.s_addr = inet_addr(host);

    memset(&cli, 0x00, sizeof(cli));
    cli.sin_family = AF_INET;
    cli.sin_port = htons(cport);
    cli.sin_addr.s_addr = htonl(0x0f0102fc);    // 15.1.2.252

    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0){
            perror("Set Error");
        }

    bind(sockfd, (struct sockaddr *)&cli, sizeof(struct sockaddr));

    // Connect    
    if(connect(sockfd, (struct sockaddr *)&svr, sizeof(struct sockaddr)) == -1){
        perror("Connect Error in connection!!");
    }
    printf("Connect!!\t[Server: %s:%d]\n", host, sport);
    printf("\t\t[Client: 15.1.2.252:%d]\n", cport);
    
    sleep(100);

    close(sockfd);

    return 0;
}

[Linux] Compile

  • 헤더파일 디렉토리 지정 컴파일 :
    • $ gcc -I/usr/openwin/include fred.c

  • 정적 라이브러리 지정 컴파일-1 :
    • $ gcc -o fred fred.c /usr/lib/libm.a

  • 정적 라이브러리 지정 컴파일-2 :
    •  $ gcc -o fred fred.c -lm
    • 만약, 공유라이브러리가 있다면 자동으로 컴파일러가 공유라이브러리를 선택

  • 라이브러리 검색 디렉토리 추가 컴파일 :
    • $ gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11

  • 정적 라이브러리 생성 :
    • $ ar crv libfoo.a bill.o fred.o

  • 공유 라이브러리 확인 :
    • $ ldd program

[Linux] 개발 시스템 로드맵

  1. 응용프로그램
    • 시스템 제공 응용 프로그램 : /usr/bin
    • 시스템 관리자 추가 응용 프로그램 : /usr/local/bin, /opt
    • X윈도우 시스템 : /usr/X11
    • GCC : /usr/bin, /usr/local/bin

  2. 헤더 파일
    • /usr/include
    • /usr/include/sys, /usr/include/linux

  3. 라이브러리 파일
    • /lib, /usr/lib
    • 전통적인 정적 라이브러리(static library)는 .a
    • 공유 라이브러리(shared library)는 .so

[VirtualBox] 200TB 설정 후 블루 스크린

VirtualBox에서 하드디스크를 동적으로 200TB 설정했더니,
Windows XP 사용 중 블루 스크린이 뜬다.

200TB가 영향을 미치는 지는 확실하지 않지만 실험해볼 수 없어서
100GB로 재설정 후, 비디오 메모리도 64MB로 늘려준후 재 설치한다.

2009년 10월 5일 월요일

블로그 시작~~

기록하는 습관을 위해

블로그를 시작한다...

언제까지 쓸수 있을런지..