출처 : http://kuaaan.tistory.com/118


어가기에 앞서 : 이 글은 IIS 등 웹서버를 운영하시는 서버운영자를 위한 글이 아닙니다. 이 글에서는 Socket 프로그래밍을 통해 서버 프로그램을 개발할 때 안전한 소켓 종료절차에 대해 논해보고자 합니다. TIME_WAIT를 없애는 웹서버 설정을 찾고 계신 분은 다른 글을 검색하시기 바랍니다. ^^

----------------------------------------------------------------------------------------------------------
화장은 하는것보다 지우는 것이 중요하다고 하죠. 마찬가지로 네트워크도 세션을 시작하는 것보다 잘 마무리짓는 것이 중요할 수 있습니다. 이 글에서는 어떻게 세션을 종료해야 데이터 손실 없이 서버에 무리를 주지 않고 잘 마무리 지을 수 있는지를 논하고자 합니다.

1. TCP의 세션 종료 매커니즘과 TIME_WAIT
  흔히 알고 있듯이 TCP 프로토콜은 세션을 맺는 3-way Handshake라는 매커니즘을 가지고 있습니다. (아래 그림의 각 상태들은 cmd 창에서 netstat -n 명령을 치면 나타나는 상태들입니다.)


 마찬가지로 TCP 세션을 종료할 때도 이와 비슷한 4-way Handshake라는 매커니즘이 적용됩니다.
(그림 출처 : 갱주니 블로그)

   종료 절차를 잠시 설명하면 다음과 같이 되겠지요. (위의 그림에서 Client는 흔히 얘기하는 개념이 아니라 종료절차를 먼저 시작한 측을 말합니다. 말하자면 closesocket()을 먼저 호출한 측이 client가 되는 거죠)

① Client 에서는 세션 종료를 시작한다는 의미로 Server에 FIN 패킷을 전송합니다.(C : FIN_WAIT1시작) Server에서 FIN을 수신하면 "니가 보낸 FIN 잘 받았다"라는 의미의 ACK를 Client에 전송하게 됩니다. (S : CLOSE_WAIT, C : FIN_WAIT2 시작)
  코드상으로는 closesocket() 혹은 shutdown()함수를 호출하는 작업이 FIN 전송에 해당합니다.

② Client로부터 FIN을 받은 Server는 Client가 세션 종료를 시작했다는 것을 인지합니다. Server는 종료에 필요한 Application 적인 작업을 진행합니다. (CLOSE_WAIT) 종료할 준비가 되면 Server는 closesocket()을 호출하여 Client에 FIN을 전송합니다. 말하자면 "OK, 나도 종료할께"하고 알려주는 거죠 (S : LAST_ACK, C : TIME_WAIT시작) 
  코드상으로는, 서버에서 recv()를 호출했을 때 0을 return하는 경우가 Client가 먼저 closesocket()을 호출한 경우에 해당합니다.

③ Client에서는 Server에서 전송한 FIN을 수신하고, 이에 대한 ACK를 보내고 나면 세션을 종료할 수 있는 상태가 됩니다. 
그런데 만약 "Server에서 FIN을 전송하기 전에 전송한 패킷이 Routing 지연이나 패킷 유실로 인한 재전송 등으로 인해 FIN패킷보다 늦게 도착하는 상황"이 발생한다면 어떻게 될까요? Client에서 세션을 종료시킨 후 뒤늦게 도착하는 패킷이 있다면 이 패킷은 Drop되고 데이터는 유실될 것입니다. (이러한 현상이 발생할 수 있는 것은 패킷이 반드시 전송한 순서대로 도착하는 것이 아니기 때문입니다.) 이러한 현상에 대비하여Client는 Server로부터 FIN을 수신하더라도 일정시간(디폴트 240초) 동안 세션을 남겨놓고 잉여 패킷을 기다리는 과정을 거치게 되는데 이 과정을 "TIME_WAIT" 라고 합니다. (이런 내용은 Stevens 아저씨의 TCP/IP Illustrated vol 1을 보시면 자세히 나와 있습니다.)

TCP/IP의 상태를 나타내는 State Diagram에는 조금 다르게 그려놓은 그림도 있습니다만, 대략적인 내용은 비슷합니다.

(그림 출처 : 인터넷)

위의 그림을 잘 살펴보면 TIME_WAIT라는 상태에 대해서 몇가지 중요한 사실을 확인할 수 있습니다.

① TIME_WAIT는 반드시 세션 종료를 먼저 시작한 쪽(closesocket()을 먼저 호출한 쪽)에 남게 됨.
② 세션 종료 과정을 먼저 시작한 측에서는 반드시 TIME_WAIT를 거쳐야 CLOSED 상태로 갈 수 있음. 따라서 TIME_WAIT 상태 자체는 비정상적이거나 문제가 되는 상태가 아님.
③ 반드시 양쪽에서 모드 세션을 종료처리 해야 (즉, FIN을 전송해야) TIME_WAIT로 갈 수 있음.

  이 TIME_WAIT라는 상태가 중요한 이유는, 만약 종료절차가 잘못 진행되어 서버쪽에 TIME_WAIT가 남게 되면 심각한 문제가 발생할 수도 있기 때문입니다. 일단 TIME_WAIT가 시작되면 2분여 이상 상태가 지속되게 되는데 모든 클라이언트들의 세션 종료시마다 서버 측에 TIME_WAIT가 발생한다면, 서버측에 부하가 될 뿐만 아니라 최악의 경우 서버에서는 더이상 새로운 연결을 받아들일 수 없는 상황이 발생할 수 있습니다. 말하자면.. 장애 상황이 발생하는 거죠. (실제로 실 운영서버에 이런 일이 발생하는 것을 직접 목격한 적이 있습니다. )


2. Linger 옵션에 대하여
 Linger옵션이란 closesocket()를 호출했을 때 아직 send되지 않고 SendBuffer에 남아있는 Data를 어떻게 처리해야 할지를 OS에게 알려주는 옵션입니다. 이 Linger 옵션을 사용하면 TIME_WAIT를 남지 않게 소켓을 종료할 수 있습니다.

 Linger 옵션의 의미를 알아보기 위해, 먼저 일반적인 send() / recv() 가 호출될 때 어떤 일이 벌어지는지 살펴보겠습니다.


1) send() 함수는 인자로 주어진 Buffer의 데이터를 해당 Socket에 할당된 SendBuffer에 복사하고 Return합니다. 중요한 것은 send()가 return했다고 해서 인자로 넘긴 데이터가 Peer에 전송된 것이 아니라는 것입니다.

2) OS는 SendBuffer에 들어온 Data를 Peer에게 전달합니다. 이때 Data가 전달되기 위해서는 상대편의 RecvBuffer에 공간이 남아있어야 하는데, 만약 Receiver.exe가 바쁘거나 해서 recv()를 제때 호출해주지 못할 경우 RecvBuffer에 공간이 부족하여 SendBuffer의 Data가 곧바로 전송되지 못한 채 지연될 수도 있습니다. 
  그렇다면 Sender는 Receiver의 RecvBuffer에 공간이 부족하다는 것을 어떻게 확인해서 전송을 하거나 중단하는 것일까요? TCP 프로토콜은 Ack패킷을 이용해 Sender와 Receiver 간에 RecvBuffer에 공간이 있는지를 체크하는 매커니즘을 제공하는데 이것을 "Sliding Window"라고 합니다. (이 부분에 대해서는 역시 스티븐스 아저씨의 TCP/IP Illustrated를 참고하세요. ^^)


3) 만약 위의 그림과 같이 SendBuffer에 Data가 남아있는 상황에서 Sender.exe가 closesocket()을 호출한 경우 어떤 일이 벌어질까요? 일단 Socket을 정리하려면 Socket에 할당된 SendBuffer도 파괴되어야 합니다. SendBuffer에는 아직 Data가 남아있는 상황이구요... 이런 상황에서 어떻게 처리해야 할지를 지정하는 방법이 LINGER 옵션입니다. LINGER 구조체의 값을 어떻게 설정하느냐에 따라 다음과 같은 세가지 처리가 가능합니다.

- 처리방법 1 : LINGER.l_onoff = 1, LINGER.l_linger = 0인 경우, closesocket() 함수는 즉시 return하고, 버퍼에 남아있는 Data는 버려집니다. 말하자면... 비정상종료(Abortive Shutdown)이 됩니다.
- 처리방법 2 : LINGER.l_onoff = 1, LINGER.l_linger = non-zero인 경우, 정상적인 종료과정(Graceful Shutdown)이 진행되며, 정상적 종료가 완료될때까지 closesocket()은 리턴하지 않습니다. 그렇지만, l_linger에 명시된 시간(초) 가 지나도 정상적인 종료가 완료되지 않을 경우 비정상종료(abortive shutdown)가 진행되고, closesocket()이 곧바로 리턴되며, Buffer에 아직 남아있는 데이터는 버려집니다.
- 처리방법 3 : LINGER.l_onoff = 0 인 경우, closesocket() 함수는 즉히 return한 후 정상적인 종료 과정(Graceful Shutdown)을 Background로 진행합니다. 이것이 Default 동작이지만, Application은 Background로 진행되는 종료작업이 언제 완료되었는지를 확인할 방법은 없습니다. 
(참고 : http://msdn.microsoft.com/en-us/library/ms738547.aspx)

  위에서 설명된 "처리방법 1" 대로 Linger 옵션을 지정했을 경우 TIME_WAIT가 남지 않습니다.명시적으로 Abortive Shutdown을 지정했기 때문이죠. 
다음과 같이 Linger 옵션을 사용하면 TIME_WAIT가 남지 않습니다.
  1. // LINGER 구조체의 값 설정  
  2. LINGER  ling = {0,};  
  3. ling.l_onoff = 1;   // LINGER 옵션 사용 여부  
  4. ling.l_linger = 0;  // LINGER Timeout 설정  
  5.   
  6. // LINGER 옵션을 Socket에 적용  
  7. setsockopt(Sock, SOL_SOCKET, SO_LINGER, (CHAR*)&ling, sizeof(ling));  
  8.   
  9. // LINGER 옵션이 적용된 Socket을 closesocket()한다.  
  10. closesocket(Sock);  


3. Graceful Shutdown에 관하여
  위에서 검토한 바와 같이 Buffer에 데이터가 남아있는 상태에서 연결을 강제로 종료할 경우 SendBuffer에 있는 데이터가 유실될 수도 있는데, 이러한 종료방식을 "Abortive Shutdown"이라고 합니다. 반대로 TCP 프로토콜의 4-way Handshake에 따라 데이터 유실 없이 종료하는 것을 "Graceful Shutdown"이라고 합니다.

  인터넷의 TIME_WAIT 관련된 글 중 일부는 Linger 옵션을 사용하여 TIME_WAIT를 남기지 않고 세션을 종료하는 것을 "Graceful Shutdown"이라고 표현한 글이 있는데, 이것은 잘못된 표현입니다. 오히려 TIME_WAIT는 Graceful Shutdown이 이루어지는 과정에서 자연스럽게 발생하는 과정입니다. 억지로 TIME_WAIT를 남기지 않기 위해 Linger 옵션을 사용하는 것은 데이터 유실을 초래할 수도 있으므로 조심해야 합니다. (비록 저도 실제로 이런 경우를 보지는 못했지만... 이론적으로는 그렇다고 합니다. ^^)
  또한 TIME_WAIT가 FIN 신호를 제대로 교환하지 못했기 때문에 발생한다는 의견도 잘못된 것입니다. 위에서 살펴보았듯이 TIME_WAIT는 Graceful Shutdown 과정에서 필수적으로 거쳐가야 할 과정입니다. (실제로 테스트를 해 본 결과, Linger옵션을 조작하는 경우를 제외하면, 어떠한 방식으로 세션을 종료하더라도 서버 혹은 클라이언트 양쪽에 모두 TIME_WAIT가 생기지 않도록 종료하는 방법을 찾지 못했습니다.)

  우리가 지향해야 할 세션 종료는 무조건 TIME_WAIT를 남기지 않는 것이 아니라, TIME_WAIT가 서버 측이 아닌 클라이언트 측에 생기도록 하는 Graceful Shutdown입니다. 이렇게 되기 위해서는 위에서 살펴본 바와 같이 "Client가 먼저 closesocket()을 호출하도록 하는 것"이 가장 중요합니다. 이것을 보장하기 위해서는Client와 Server 간에 종료 프로토콜을 설계할 때 Client가 먼저 closesocket()을 먼저 호출하도록 반영되어야 합니다. 이것만 확실히 지켜진다면 서버에는 TIME_WAIT가 남지 않습니다.

  다음은 종료 프로토콜의 예입니다. 

1) 서버에서 클라이언트에 "너 종료해라"는 커맨드를 전송합니다.
2) "너 종료해라"를 수신한 클라이언트는 서버에 "알았다 종료하겠다"를 전송한 후 즉시 closesocket()를 전송합니다. ( 마지막 통신이 클라이언트 -> 서버 방향으로 일어난다는 것이 중요합니다.)
3) "알았다 종료하겠다"를 수신한 서버는 해당 소켓에 대해 closesocket()을 호출합니다. 이때 안전장치로 Linger 옵션을 주어 Abortive Shutdown을 시키는 것이 좋습니다. 어차피 서버에서 클라이언트로는 더이상 보낼(유실될) 데이터가 없다는 것이 확인되었고, 간혹 Client 중에 프로토콜을 따르지 않고 종료하는 녀석들이 있기 때문입니다.
※ IOCP 를 사용한 서버에서는 주기적으로 마지막 통신한 TimeStamp를 체크하여 Idle Session에 대해 Gabage Collection(?)을 수행해주어야 합니다. 이러한 경우에는 서버측에서 먼저 closesocket()을 호출할 수 밖에 없기 때문에, TIME_WAIT를 남기지 않기 위해 반드시 Linger 옵션으로 Abortive Shutdown을 시켜주어야 합니다.


4. 비고
 closesocket() 함수는 내부적으로 두가지 역할을 수행합니다.

1) 세션의 종료 절차
2) 소켓 핸들의 Close 등 자원 해제 절차

이 중, 첫번째 종료절차만을 수행하는 shutdown()이라는 함수가 있습니다. 세션 종료 과정을 명시적으로 수행하고 싶을 때 사용하는 함수입니다.
아래의 MSDN 문서를 보면 shutdown 함수를 사용해 완전한 Graceful Shutdown을 수행하는 방법이 안내되어 있습니다. (제 개인적인 생각으로는... 이렇게까지 할 필요는 없을 것 같습니다. ^^)

'리눅스 서버에 대해서 > python' 카테고리의 다른 글

파이선 간단한 captchr 소스  (0) 2013.09.20
파이선 PIL 간단 예제  (0) 2013.09.20
파이썬의 PIL 이미지 라이브러리 사용 강좌  (0) 2013.09.18
captcha 우회하기  (0) 2013.09.18
CAPTCHA 만들기  (0) 2013.05.15

출처 : http://kuaaan.tistory.com/116


들어가기에 앞서 : 이 글은 크리티컬 섹션등의 동기화 개체나 관련 API 사용법 혹은 동기화가 이루어지지 않았을 때 발생하는 문제점 등을 설명하지 않습니다. 이 글은 성능저하를 보다 줄이면서 안전한 멀티스레드 프로그램을 구성하는 방법(스레드 모델)에 대해 얘기합니다.


1. 동기화 개체에 대한 잘못된 생각들

  스레드를 동기화한다 함은 스레드 간의 실행순서를 정하거나, 스레드 간 특정작업이 동시에 일어나지 않도록 구현하는 것을 말합니다. 

1) Manager Thread가 Worker Thread를 생성한 후, Worker Thread의 초기화가 끝날 때까지 기다려야 하는 경우. 이런 경우에는 보통 Event 개체나 메시지를 사용합니다.

2) 전역 데이터에 접근할 때에 한번에 스레드 한개씩만 해당 데이터를 사용하도록 구현하는 경우 : 이 경우에는 보통 CriticalSection 을 사용합니다. 일반적으로 스레드 동기화라 함은 이러한 경우를 말합니다.

3) 일정 숫자 이상의 스레드(혹은 프로세스)가 동시에 특정 자원을 사용하거나 동작하지 못하도록 하는 경우 : 이 경우에는 보통 Semaphore를 사용합니다.


CriticalSection을 사용하는 데에 있어서 초보자분들은 다음과 같은 착각을 하기 쉽습니다.

착각1) EnterCriticalSection()을 호출하면 Data에 Lock이 걸리고 다른 Thread가 접근하지 못하게 된다. (X)

==> CriticalSection 등의 동기화 개체에 Lock을 건다 함은 화장실이라는 Resource에 문을 걸어 잠그는 개념이 아니라, 화장실의 문에 "사용중"이라고 써붙이는 개념입니다. 이게 무슨 얘기냐 하면, "g_szData 라는 Data에 접근할 때는 crit_szData에 Lock을 걸어야 한다"라고 개발자가 스스로 규칙을 세우고, 코드의 모든 부분에서 g_szData를 사용할 때 CriticalSection에 Lock을 걸도록 구현해야 한다는 뜻입니다. 화장실의 문에 "사용중"이라고 써붙이고 들어갔는데, 어떤 사람이 문에 붙어있는 표시를 확인하지 않고 문을 열고 들어간다면? 운이 좋으면 문제가 안생기겠지만 재수가 없으면 민망한 상황이 벌어질 것입니다. 

엄밀히 말하면 데이터에 동기화개체는 스레드 들이 특정 데이터에 동시 Read/Write하지 못하도록 하는 기능을 제공하지 않습니다. 단지 동기화개체에 동시에 Lock을 걸지 못하는 기능을 제공할 뿐이죠. :)


착각2) 데이터에 Read할 때는 동기화가 필요 없다. 따라서 EnterCriticalSection은 Data에 Write를 수행할 때만 수행하면 된다. (X)

==> Data를 Read하는 스레드에서는 그냥 Read를 하면서 Data에 Write를 하는 스레드에서만 Lock을 건다고 해서 다른 스레드가 그 Data에 접근을 못하게 되는 것이 아닙니다. 어떤 CriticalSection에 Lock을 건다고 해서 스레드간 ContextSwitching이 중지되는 건 더더욱 아닙니다. 보호하고자 하는 데이터를 참조하는 모든 부분에 동기화 처리를 해주지 않으면 동기화 개체를 사용하는 것은 아무런 의미도 없습니다. 
간단하게 생각하면 CriticalSection이란 보호할 데이터를 사용하는 중에 BOOL형 변수에 "사용중"이라는 의미로 "TRUE" 값을 Setting하는 이상의 의미는 없습니다. 단지 두 Thread에서 동시에 TRUE를 설정하지 못하도록 OS에서 보장해준다는 점과 TRUE를 Setting하려고 할 때 이미 TRUE라는 값이 Setting되어 있다면 이 변수가 FALSE로 바뀔 때까지 스레드가 BLOCK되는 등의 처리가 자동으로 구현되어 있다는 정도가 차이가 있을 뿐입니다.
단지 프로그램 전체에 걸쳐서 어떠한 "Write"도 수행되지 않고 "Read"만 수행되는 데이터라면 동기화가 필요 없습니다. 

대부분의 개발자 분들은 이런 얘길 보면서 "이렇게 당연한  얘기를 왜 하나?"라고 생각하시겠지만... 바로 얼마 전에 십 수년간 개발을 하신 서버 개발자께서 "CriticalSection에 Lock을 걸면 말이쥐... 다른 스레드들은 이 데이터에 접근을 못하게 되는 거란다"라고 친절하게 설명해주시는 걸 듣고... 까무라치는 줄 알았습니다. ㅡ.ㅡ

※ 선언 함수나 초기화 함수를 보면 알수 있듯이, CriticalSection은 그 자체로서 어떠한 데이터와도 연관되지 않습니다. 어떠한 데이터를 보호하는데 어떤 CriticalSection 개체를 사용하겠다 하는 것은 순전히 개발자의 로직상으로 구현되는 부분입니다. 위에서 얘기했듯이 OS에서는 동시에 한개의 스레드만 EnterCriticalSection에 성공할 수 있다는 것 외에는 아무것도 보장하지 않습니다. 나머지는 개발자의 몫인 거죠.


2. Case I : Multiple-Reader + Multiple-Writer 환경 (CriticalSection Per Data Model)

  멀티스레드 환경에서 발생할 수 있는 가장 일반적인 경우는 다수의 스레드가 동시에 데이터를 읽고 쓰는 경우입니다. 이 경우에는 어떠한 "읽기"와 "읽기", "읽기"와 "쓰기" 혹은 "쓰기"와 "쓰기" 도 동시에 발생해서는 안되며, 완전하게 동시접속이 차단되어야 합니다. 이렇게 처리하는 것을 직렬화(Serialize)한다고 합니다.

  이런 환경을 구현하기 위해서는 CriticalSection을 접속하고자 하는 Data의 수만큼 만들고, (읽기/쓰기에 상관없이) Data에 접근할 때마다 해당 Data에 Matching 되는 CriticalSection에 대해 EnterCriticalSection을 수행해주어야 합니다. 말하자면 "CriticalSection Per Data" 정도 되겠네요.

가장 안전한 환경이 구현되지만, 병렬수행이 불가능해지므로 성능은 최악이 됩니다. 

그림으로 표현하면 다음과 같이 되겠네요.



3. Case II : Multiple-Reader + Single-Writer 환경 (CriticalSection Per Thread Model)

  모든 스레드가 읽기와 쓰기를 행한다면 Case I  과 같이 직렬화를 시켜야겠지만, 대부분의 Server용 Application에서는 다수의 Worker Thread는 전역 데이터에 대해 Read만을 수행하고 한개의 Manager Thread가 Write를 수행하는 구조인 경우가 많습니다. 대표적인 예가 Manager Thread가 기록한 정책을 참조하여 Worker Thread가 클라이언트의 접속을 처리하는 경우를 예로 들 수 있겠죠. (Thread Pool 이 보통 이런 동작을 합니다.)

이러한 경우는 다음과 같은 특징이 있습니다.


1) Read의 빈도가 매우 높고, Write의 빈도는 낮다. 

2) Read와 Read는 동시에 이루어지는 것이 바람직하지만 Write와 Read는 동시에 이루어져선 안된다.

3) WokerThread간의 병렬처리가 이루어져야 하므로 Read의 속도가 매우 중요하며 Write의 성능은 상대로 덜 중요하다.


위의 조건을 만족시키기 위해서는 스레드와 동기화 개체들을 어떤 식으로 구성해야 할까요? 제가 내린 결론은 다음과 같습니다. (더 좋은 방법을 아시는 분은 가르쳐주세요. ^^)

1) WorkerThread 마다 CriticalSection을 하나씩 생성한다. WorkerThread는 각자 자기의 CriticalSection을 가지고 있다. 말하자면 워커스레드가 6개라면 CriticalSection도 6개가 생성되며 이것이 Array처럼 접근되도록 구현된다.

2) WorkerThread 는 작업을 시작할 때마다 자기의 CriticalSection에 대해 EnterCriticalSection()을 호출하고 작업을 끝낸 후에는 LeaveCriticalSection을 호출합니다. 이렇게 되면 각 WorkerThread 간에는 작업이 병렬수행되며, 전역 Data에 대해서도 병렬 Read가 이루어집니다. (이때 ManagerThread 는 쉬고 있겠죠)

3) ManagerThread 가 정책을 업데이트해야 할 때는 For Loop 를 돌면서 모든 CriticalSection에 대해 EnterCriticalSection()을 호출합니다. 일단 ManagerThread가 CriticalSection에 Lock을 걸고 나면 해당 WorkerThread는 새로운 작업을 시작하지 못하기 때문에, For Loop가 끝나게 되면 결과적으로 ManagerThread는 모든 WorkerThread를 정지시키게 됩니다. (이때는 물론 시간이 좀 걸리겠죠? ^^)

4) ManagerThread는 정책을 수정합니다.

5) ManagerThread는 For Loop를 돌면서 모든 CriticalSection에 대해 LeaveCriticalSection()을 호출합니다. 이제 WorkerThread들은 다시 자신의 CriticalSection을 획득하고 밀린 일을 처리할 수 있습니다.


그림으로 표현하면 대략 다음과 같이 되겠네요.


WorkerThread간의 읽기작업을 표현하면 다음과 같습니다.


ManagerThread가 쓰기 작업을 진행할 때는 다음과 같이 됩니다.


말하자면... Write속도를 희생시켜서 Read속도를 증가시킨다고나 할까요? ^^


4. Case III : 동기화를 하지 않는 방법(?)

  "멀티스레드 프로그래밍에 관한 고찰 (1)" 에서 논했듯이 멀티스레드가 Data에 동시접근한다고 해도 경우에 따라서는 동기화 하지 않고 사용할 수 있습니다. 만약 이게 가능하다면 멀티스레드 프로그램은 최고의 효율을 얻을 수 있습니다. 이렇게 구성하기에 앞서 해당 Case가 이런 방식으로 구성 가능한지 검토해야 하며, 가능하다면 자료구조가 안전한 동시접근이 가능하도록 설계되어야 합니다.

정수형 Data가 Linked List에 저장되는 경우의 예를 들어 보겠습니다.

1) Data가 변경될 때 Linked List에 Entry가 추가/삭제되는 경우에는 반드시 스레드 동기화가 필요합니다. 반대로 시종일관 LinkedList 자체는 변화가 없이 각 Entry의 정수형 데이터값만 변경시키는 경우에는 동기화 없이도 Concurrent Read/Write가 안전하게 수행될 수 있습니다. 
   이러한 경우의 예를 들면 오목게임의 바둑판을 들 수 있습니다. 모든 좌표에 대해 게임을 시작하기 전에 19 X 19 개의 좌표 Data를 생성한 후, 게임이 진행되는 동안에는 해당 좌표에 대한 Data(흑/백/無)만 Update될 뿐 새로운 좌표가 추가되지는 않습니다. 이런 경우라면 동기화가 필요하지 않습니다.

2) 만약 Data가 Linked List에 추가/삭제되어야 하는 경우라면, 추가되는 데이터의 "경우의 수"에 대해 생각해보아야 합니다. 경우에 따라서는 Insert될 수 있는 Data의 경우의 수가 한정되어 있어 초기화시에 모든 경우의 수에 대한 메모리를 미리 생성해놓고 시작할 수 있는 경우가 있습니다. 이러한 경우라면 동기화 없이 멀티스레드가 동작할 수 있습니다.
   예를 들어, 사내 IP체계를 B Class를 사용하는 회사에서 IP를 사용하는 Mac주소와 사용자 정보를 저장하는 자료구조가 있다고 가정해보겠습니다. 

. 새로운 IP가 생성될 때마다 Linked List에 Entry가 생성되는 방식으로 설계한다면 스레드 동기화가 필요하며, 동기화하지 않을 경우 Entry를 LinkedList에 Insert하는 순간 Access Violation이 발생할 수 있습니다.

. 모든 경우의 수 (255 * 255 = 65525개)만큼의 Entry를 처음에 일괄 생성해놓고, 사용하지 않는 IP 에 대해서는 FALSE, 사용하는 IP에 대해선 TRUE를 기록하여 미사용 IP를 관리하고, 사용중인 IP에 대해서는 Mac주소와 사용자정보를 미리 할당된 메모리에 memcpy하는 방식으로 자료구조를 설계할 경우 동기화 처리 없이도 안전하게 Concurrent Read/Write가 가능합니다. 한마디로 "메모리를 희생하여 속도를 향상"시키는 방법이죠 :)



이 글에 있는 내용들은 저의 경험에 비추어 적은 것입니다. 물론 원칙적으로 얘기하면 정수형 데이터 하나에 대해서도 동기화를 해 주어야 합니다. 알고 있으니까요... 이런 태클은 사양합니다. ^^;;

출처 : http://kuaaan.tistory.com/114


예전에는 고사양이라 하면 힘쎈 CPU를 의미했지만 지금 시대의 고사양이란 CPU 여러 개를 의미합니다. 이른바 멀티코어의 시대죠. 예전에 3.4GHz 4CPU가 최고사양 서버였다고 하면 요즘에는 1.6GHz 16Core (4Core * 4ea)가 동급으로 받아들여집니다. 

 이러한 H/W적인 패러다임의 변화는 S/W 개발에도 영향을 주어 예전에는 (어셈블리) 코드 한줄이라도 줄이는 게 퍼포먼스 향상의 열쇠였다고 한다면 지금은 여러 스레드(Thread) 들이 한 머쉰에서 서로 엉키지 않고(!) 조화롭게 돌아가는 구조를 구현하는 것이 퍼포먼스 향상의 열쇠라고 볼 수 있습니다. 

 그렇다면 멀티스레드 프로그램의 성능을 결정하는 열쇠는 무엇일까요?

 먼저 "스레드의 수"를 생각해 볼 수 있습니다.
 멀티스레드 프로그래밍을 해보시지 않으신 분들은 일단 스레드를 많이 만들면 퍼포먼스도 올라갈 것이라고 생각하시는 경향이 있읍니다만 이것은 잘못된 생각입니다. 
 중요한 것은 한개의 CPU는 동시에 한 개 씩의 스레드만 실행시킬 수 있다는 사실입니다. 스레드가 여러개가 생성되면 CPU는 각각의 스레드를 시분할하여 각각의 스레드를 번갈아가며 실행하게 되는데, 이때 이전 스레드의 문맥 정보 (레지스터 값, 실행중인 스택 정보 등)을 백업받고 백업받아놓았던 다음 스레드의 문맥정보를 로딩하는 과정을 거치게 됩니다. 이 과정을 Context Switching(문맥 교환이라고 번역하더군요) 이라고 하는데, 이러한 스레드가 많아질 수록 Context Switching 에 많은 부하가 걸리기 때문에 오히려 퍼포먼스는 떨어지게 됩니다. 
 그렇다면 스레드가 적을수록 퍼포먼스가 좋아질까요? 물론 그렇지는 않습니다. ^^
 

 위 그림과 같이 스레드는 작업을 진행함에 따라 Running <-> Waiting , Sleeping, Blocked 등으로 상태변화를 하게 되는데, 멀티스레드 프로그램은 한 스레드가 Waiting, Sleeping, Blocked 중일 때 CPU가 다른 스레드를 실행시킬 수 있기 때문에 동시성(Concurrency)이 높아져서 성능이 좋아지는 것입니다. 바꾸어 말하면 I/O가 많이 발생하는 프로그램일 수록 멀티스레딩의 효과를 크게 볼 수 있다고 말할 수도 있습니다.

위의 두가지 면을 종합할 때 멀티스레드 프로그램의 성능은 스레드의 "동시성 향상 효과"와 "Context Switching 비용"의 Trade Off 에 의해 결정된다고 말할 수 있습니다.
 따라서 가장 적절한 스레드의 수는 CPU의 수와 스레드가 수행하는 작업의 성격을 함께 고려하여 결정되는데 일반적으로는 연산 위주의 작업의 경우 CPU당 2~3개, I/O 위주 작업의 경우 CPU당 5개 내외를 적절한 스레드의 수로 가이드하며, 보통 스레드 풀(Thread Pool)을 생성할 때 워커 스레드 (Worker Thread)의 수를 산정하는 방식으로 사용됩니다.

 요즘 네트워크 프로그램에서 많이 사용되는 IOCP (Input Output Completion Port)가 성능이 좋은 이유는 I/O가 진행되는 동안 스레드가 스위칭되거나 블러킹되지 않기 때문에 스레드의 Context Switching을 최소화할 수 있고, 따라서 필요한 스레드의 수를 최소화할 수 있기 때문입니다. 그런 면에서 어찌보면 최소한의 스레드를 써야 높은 성능을 낼 수 있다고 볼 수 있는 거죠.


   그렇다면 "동시성 향상 효과"를 높이는 요인은 무엇이 있을까요? 바로 "효율적인 스레드 동기화" 를 들 수 있습니다.
  멀티스레드 프로그래밍은 프로세스의 동시성을 향상시켜 성능을 향상시키는 효과가 있지만, 여러 스레드 들이 동시에 데이터를 접근하면서 생기는 문제들이 빛과 그림자처럼 쫓아다니게 됩니다. 예를 들어 스레드 A가 링크드 리스트에서 Read를 시도하려고 하는 순간에 스레드 B가 해당 링크드 리스트에 ClearAll() 을 수행한다면 어떤일이 벌어질까요? 스레드 A는 메모리 폴트를 발생시키고 해당 프로세스는 중지되고 말 것입니다. 이러한 일을 방지하기 위해서는 스레드들이 링크드 리스트에 동시에 접근하지 못하도록 직렬화(Serialize)시킬 필요가 있는데 이런 작업을 "스레드 동기화"라고 합니다. 
  스레드 동기화가 잘못되었을 때 생기는 문제들이나 동기화 개체 사용법 등에 대해서는 인터넷에 좋은 포스트가 많이 공개되어 있으니, 여기서는 어떻게 하면 보다 효율적인 스레드 동기화를 구현할 수 있을 것인가에 대해 생각해보겠습니다. 
 문제는 스레드 동기화의 정도가 높아질수록 데이터에 대한 단일접근이 보장되어 안정성과 데이터 무결성은 높아지지만, 반면에 데이터에 접근할 때의 동시성이 저하되어 성능은 (형편없이) 떨어지게 됩니다. 여기서도 Trade Off 문제가 발생하는 거죠. 원칙적으로 모든 전역 데이터(Global Data)에 접근할 때 CriticalSection등의 동기화 개체를 사용하여 동시접속을 차단하는 것이 원칙이겠지만 그렇게 하게 되면 너무 성능이 떨어지기 때문에, 대부분의 서버 개발자들은 "이정도는 괜찮더라"는 나름대로의 선을 정해놓고, 그 범위 안에서 "적당한 물타기"를 시도하게 됩니다. 
 멀티스레드 프로그래밍을 할 때 제 나름대로의 동기화 기준을 공개한다면 대략 다음과 같습니다.

1. 전역 데이터에 멀티스레드가 읽기를 동시에 시도하는 경우에는 동기화 할 필요가 없습니다. 

2. 전역 데이터에 멀티스레드가 읽기와 쓰기를 혹은 쓰기와 쓰기를 동시에 시도하는 경우에는 "원칙적으로" 직렬화를 시켜야 합니다. (직렬화를 한다 함은 동시접근을 차단한다는 뜻입니다.) 이때, 저는 읽기 및 쓰기가 행해지는 데이터의 성격에 따라 다음과 같이 "적당한 대처"를 합니다. ^^
   1) 멀티CPU 환경에서는 기본적으로는 4바이트 정수 연산 하나에 대해서도 원자성을 100% 보장할 수 없습니다. 따라서 동기화의 비용(성능 감소)와 데이터 무결성이 깨어졌을 때의 발생가능한 손해를 비교하여 동기화 수준을 결정해야 합니다.
   2) 포인터 데이터에 대한 무결성 문제는 바로 메모리 폴트로 이어집니다. 따라서 대상 데이터 중에 "포인터"가 포함된 경우에는 반드시 동기화를 시켜야 합니다. 
   3) 단순한 정수/실수형의 경우 "대부분의 경우"에는 동기화에 신경쓸 필요가 없습니다. 하지만 이 데이터가 무결성에 얼마나 민감한지를 검토해볼 필요는 있습니다. 예를 들어, 어떤 일을 해야 할지 말지를 나타내는 Boolean (True/False) 값 등은 굳이 동기화할 필요가 없습니다. 만약 어떤 값을 카운트하는 변수일 경우, 카운트가 1~2개정도 어긋나면 어떤 문제가 생길지를 체크해봅니다. 만약 ++연산을 100번했을 때 99만 증가해도 큰 문제가 되지 않는다면 동기화하지 않아도 되겠지만, 정확한 카운트가 보장되어야 하는 경우라면 해당 변수를 Volatile 로 선언한 후 InterlockedAdd 함수 등을 이용해 동기화해주어야 합니다.
   4) Linked List, Binary Tree 등의 자료구조는 각 Entry들이 포인터로 연결되어 있습니다. 따라서 반드시 동기화해주어야 합니다. STL도 예외는 아닙니다. 다만, Linked List에 Entry가 Insert, Delete되지 않는다면 (포인터 연산이 일어나지 않기 때문에) 동기화가 필요하지 않을 수도 있습니다.
   5) 구조체나 클래스, 배열 등의 경우에는 각 Entry 의 데이터 타입이나 성격에 따라 위의 1)~3) 기준에 따라 판단해주면 됩니다.
   6) Memory Mapped File 등 프로세스간 공유되는 데이터의 경우에는 가급적이면 동기화해주는 것이 좋습니다.

3. 요즘에는 그런 일은 거의 없겠지만, 멀티CPU에서 실행되지 않는다는 것이 보장된다면... 사실 왠만한 동기화는 신경 안써도 됩니다. 뒤집어서 말하면... 멀티CPU에서는 동기화에 대해 깊이 고민해야 합니다.


다음번에는 각 케이스 별로 데이터 무결성을 보장하면서 성능 저하를 최소화하는 동기화 방법(구조)에 대해 생각해 보려고 합니다.

※ 위 글에는 필자의 개발 경험에서 우러나온 "적당"한 통밥이 사용되고 있습니다. 위의 내용을 실제로 개발에 적용하였다가 낭패를 보더라도 당연히 저는 책임지지 않습니다. ^^

렌카드 설정을 했는데 ping이 안 간다면..

아래의 path_to_inst 내용에서 

ethernet 번호와 ifconfig 상 나오는 index번호가 같은지 확인해 봐야 한다.



path_to_inst 출처 : http://radiocom.kunsan.ac.kr/lecture/unix_cmd/path_to_inst.html


/etc/path_to_inst 파일 내용


Disk Administration
물리적 장치 이름과 인스턴스 이름(instance name)을 매핑한 내용이 담긴 파일임

이 path_to_inst 파일 내용을 수동으로 변경하려면 devfsadm 명령을 사용하면 된다. 시스템에 어떤 장치를 hot-plugging에 의해서 자동으로 /dev, /devices 파일을 수정하는 것이 바로 devfsadmd라는 데몬이 수행한다.

또한 prtconf 명령을 이용하여 확인할 수 도 있다.

다음 내용은 x86 솔라리스 시스템의 예이다.

# cat /etc/path_to_inst
#
#       Caution! This file contains critical kernel state
#
"/options"                                0 "options"
"/pci@0,0"                                0 "pci"
"/pci@0,0/pci-ide@1f,1"                   0 "pci-ide"
"/pci@0,0/pci-ide@1f,1/ide@0"             0 "ata"
"/pci@0,0/pci-ide@1f,1/ide@0/cmdk@0,0"    0 "cmdk"
"/pci@0,0/pci-ide@1f,1/ide@1"             1 "ata"
"/pci@0,0/pci-ide@1f,1/ide@1/sd@0,0"      0 "sd"
"/pci@0,0/pci-ide@1f,1/ide@1/st@0,0"      0 "st"
"/pci@0,0/pci8086,2561@1"                 0 "pci_pci"
"/pci@0,0/pci8086,2561@1/display@0"       0 "vgatext"
"/pci@0,0/pci8086,244e@1e"                1 "pci_pci"
"/pci@0,0/pci8086,244e@1e/pci10b7,9055@b" 0 "elxl"
"/pci@0,0/pci174b,174b@1d"                0 "uhci"
"/pci@0,0/pci174b,174b@1d,1"              1 "uhci"
"/pci@0,0/pci174b,174b@1d,2"              2 "uhci"
"/pci@0,0/pci174b,174b@1d,7"              0 "usba10_ehci"
"/objmgr"                                 0 "objmgr"
"/pseudo"                                 0 "pseudo"
"/isa"                                    0 "isa"
"/isa/i8042@1,60"                         0 "i8042"
"/isa/i8042@1,60/keyboard@0"              0 "kb8042"
"/isa/i8042@1,60/mouse@1"                 0 "mouse8042"
"/isa/asy@1,3f8"                          0 "asy"
"/isa/asy@1,2f8"                          1 "asy"
"/isa/fdc@1,3f0"                          0 "fdc"
"/isa/fdc@1,3f0/fd@0,0"                   0 "fd"
"/isa/fdc@1,3f0/fd@0,1"                   1 "fd"
"/isa/lp@1,378"                           0 "lp"
"/xsvc"                                   0 "xsvc"
# 
여기서 나타내는 각 필드의 의미는 다음과 같다.

fielddescription
physical namefull physical device name or full path device name
instance nameThe unique number(typically starting with 0)
driver binding namename assigned to the device driver


% cat /etc/path_to_inst
#
#       Caution! This file contains critical kernel state
#
"/pseudo"                                        0 "pseudo"
"/scsi_vhci"                                     0 "scsi_vhci"
"/options"                                       0 "options"
"/pci@1f,0"                                      0 "pcipsy"
"/pci@1f,0/pci@1,1"                              0 "simba"
"/pci@1f,0/pci@1,1/ide@3"                        0 "uata"
"/pci@1f,0/pci@1,1/ide@3/sd@2,0"                 1 "sd"
"/pci@1f,0/pci@1,1/ide@3/dad@0,0"                1 "dad"
"/pci@1f,0/pci@1,1/ebus@1"                       0 "ebus"
"/pci@1f,0/pci@1,1/ebus@1/power@14,724000"       0 "power"
"/pci@1f,0/pci@1,1/ebus@1/su@14,3083f8"          0 "su"
"/pci@1f,0/pci@1,1/ebus@1/su@14,3062f8"          1 "su"
"/pci@1f,0/pci@1,1/ebus@1/se@14,400000"          0 "se"
"/pci@1f,0/pci@1,1/ebus@1/ecpp@14,3043bc"        0 "ecpp"
"/pci@1f,0/pci@1,1/ebus@1/fdthree@14,3023f0"     0 "fd"
"/pci@1f,0/pci@1,1/ebus@1/SUNW,CS4231@14,200000" 0 "audiocs"
"/pci@1f,0/pci@1,1/SUNW,m64B@2"                  0 "m64"
"/pci@1f,0/pci@1,1/network@1,1"                  0 "hme"
"/pci@1f,0/pci@1"                                1 "simba"
"/iscsi"                                         0 "iscsi"
%
openBoot

file system Admin

* 작업 환경

  - 4 port 랜카드가 서버에 2개 꽂여 있음

    즉 8개의 port 를 사용할 수 있는 NIC (ce0 ~ ce7)

  - 고객사에서 LAN 선을 하나 추가적으로 할당 받아 서버에 설정할 경우

  - 추가적인 물리적인 랜선이 꽂을때 네트워크 인터페이스 장치명이 ce0 인지? ce1 인지?  ce2 인지 알수 없음

 

 

* 인터페이스 확인

[command]# cat /etc/path_to_inst


#
#       Caution! This file contains critical kernel state
#
"/options" 0 "options"
"/pci@8,700000" 0 "pcisch"
"/pci@8,700000/ide@6" 0 "uata"
"/pci@8,700000/ide@6/sd@0,0" 0 "sd"
"/pci@8,700000/SUNW,XVR-100@2" 0 "pfb"
"/pci@8,600000" 1 "pcisch"
"/pci@8,600000/pci@1" 0 "pci_pci"
"/pci@8,600000/pci@1/network@0" 0 "ce"
"
/pci@8,600000/pci@1/network@1" 1 "ce"
"
/pci@8,600000/pci@1/network@2" 2 "ce"
"
/pci@8,600000/pci@1/network@3" 3 "ce"
"/pci@8,600000/pci@2" 1 "pci_pci"
"/pci@8,600000/pci@2/network@0" 4 "ce"
"
/pci@8,600000/pci@2/network@1" 5 "ce"
"
/pci@8,600000/pci@2/network@2" 6 "ce"
"
/pci@8,600000/pci@2/network@3" 7 "ce"
"/pci@9,700000" 2 "pcisch"
"/pci@9,700000/ebus@1" 0 "ebus"
"/pci@9,700000/ebus@1/rtc@1,300070" 0 "todds1287"
"/pci@9,700000/ebus@1/i2c@1,30" 0 "pcf8584"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,80" 0 "ssc050"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,82" 1 "ssc050"
"/pci@9,700000/ebus@1/i2c@1,30/temperature@0,30" 0 "max1617"
"/pci@9,700000/ebus@1/i2c@1,30/temperature@0,34" 1 "max1617"
"/pci@9,700000/ebus@1/i2c@1,30/fru@0,a0" 20 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,30/fru@0,a2" 21 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,30/fru@0,a6" 22 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,30/fru@0,a8" 23 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,30/fru@0,ae" 24 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,44" 0 "pcf8574"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,46" 1 "pcf8574"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,4c" 2 "pcf8574"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,70" 3 "pcf8574"
"/pci@9,700000/ebus@1/i2c@1,30/ioexp@0,72" 4 "pcf8574"
"/pci@9,700000/ebus@1/i2c@1,30/temperature-sensor@0,9c" 0 "lm75"
"/pci@9,700000/ebus@1/serial@1,400000" 0 "se"
"/pci@9,700000/ebus@1/rsc-control@1,3062f8" 0 "su"
"/pci@9,700000/ebus@1/rsc-console@1,3083f8" 1 "su"
"/pci@9,700000/ebus@1/i2c@1,2e" 1 "pcf8584"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,a0" 0 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,a2" 1 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,a4" 2 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,a6" 3 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,a8" 4 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,aa" 5 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,ac" 6 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@0,ae" 7 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,a0" 8 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,a2" 9 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,a4" 10 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,a6" 11 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,a8" 12 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,aa" 13 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,ac" 14 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@2,ae" 15 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@4,a0" 16 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/nvram@4,a4" 17 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@4,a8" 18 "seeprom"
"/pci@9,700000/ebus@1/i2c@1,2e/fru@4,aa" 19 "seeprom"
"/pci@9,700000/ebus@1/pmc@1,300700" 0 "pmc"
"/pci@9,700000/ebus@1/power@1,30002e" 0 "power"
"/pci@9,700000/ebus@1/gpio@1,300600" 0 "gpio_87317"
"/pci@9,700000/usb@1,3" 0 "ohci"
"/pci@9,700000/usb@1,3/hub@2" 0 "hubd"
"/pci@9,700000/usb@1,3/hub@2/keyboard@4" 0 "hid"
"/pci@9,700000/network@2" 8 "ce"
"/pci@9,600000" 3 "pcisch"
"/pci@9,600000/SUNW,qlc@2" 0 "qlc"
"/pci@9,600000/SUNW,qlc@2/fp@0,0" 0 "fp"
"/pci@9,600000/SUNW,qlc@2/fp@0,0/ssd@w2100001d3851de19,0" 0 "ssd"
"/pci@9,600000/network@1" 9 "ce"
"/memory-controller@0,400000" 0 "mc-us3"
"/memory-controller@2,400000" 1 "mc-us3"
"/pseudo" 0 "pseudo"
"/scsi_vhci" 0 "scsi_vhci"

 

* link_status 를 체크

  - ndd 명령의 결과에서 link_status 가 있을 경우 ndd -get /dev/ce link_status 할 때 결과로 링크 상태 체크

  - ce 장치의 경우 link_status 항목이 없음

 

[command]# ndd -get /dev/ce \? 
?                             (read only)
instance                      (read and write)
adv_autoneg_cap               (read and write)
adv_1000fdx_cap               (read and write)
adv_1000hdx_cap               (read and write)
adv_100T4_cap                 (read and write)
adv_100fdx_cap                (read and write)
adv_100hdx_cap                (read and write)
adv_10fdx_cap                 (read and write)
adv_10hdx_cap                 (read and write)
adv_asmpause_cap              (read and write)
adv_pause_cap                 (read and write)
master_cfg_enable             (read and write)
master_cfg_value              (read and write)
use_int_xcvr                  (read and write)
enable_ipg0                   (read and write)
ipg0                          (read and write)
ipg1                          (read and write)
ipg2                          (read and write)
rx_intr_pkts                  (read and write)
rx_intr_time                  (read and write)
red_dv4to6k                   (read and write)
red_dv6to8k                   (read and write)
red_dv8to10k                  (read and write)
red_dv10to12k                 (read and write)
tx_dma_weight                 (read and write)
rx_dma_weight                 (read and write)
infinite_burst                (read and write)
disable_64bit                 (read and write)
accept_jumbo                  (read and write)
laggr_multistream             (read and write)

 

* IP 셋팅


[command]# ifconfig ce5 plumb 192.168.100.100 netmask 255.255.255.0 up

 

[command]# ping 192.168.100.1
192.168.100.1 is alive

 

[command]# vi /etc/hostname.ce5
192.168.100.100

 

* 백승찬 강사님의 도움 감사드립니다 (^^)(__)(^^)

 

* http://cafe.naver.com/eitlinux.cafe - 바두기 짱 -

 

 

+ Recent posts