파일 시스템의 클러스터링에 대해서는 이미 이슈화 된지 몇년이 지난 상태여서 본 내용에 대해서 이미 확인해 보거나 BMT 까지 수행 해 보신 분들도 많으시겠지만, 간략한 소개의 의미 및 nexenta.org 의 관련 내용 번역으로 간략히 포스팅 하고자 한다. pNFS 를 Nexenta 에서 서비스로 사용하는데에는 다소 신중함이 필요 하겠지만, VM 에서 여러대의 Nexenta 를 올려두고 간단히 테스트 하여 성능을 비교하는 재미는 쏠쏠하지 않겠는가.
pNFS 에서의 p 의 의미는, Parallel 이다. 전통적인 NFS v4에서 지원하는 단일 서버 - 클라이언트 서버 간의 Data 와 Meta Data 통신을 구분하여 병렬로 나누어 놓았다고 생각하면 이상적이라 할 수 있겠다.
클러스터 파일 시스템은 굉장히 여러가지 종류가 있고 구현 방법에 따라 설치가능한 패키지들이 있겠지만, 전통적으로 사용되는 NFS 에 대한 병렬 확장은 이미 2005년 경 부터 각 벤더 ( ex. EMC, IBM, NetApp ) 에 의해 연구되어 왔고, 종래에 와서 File 기반 서비스에 대해서는 어느정도 사용 할 만한 규모가 되었다 생각 한다.
물론 Block Level 의 클러스터FS 에 대해서는 여기서는 논외로 하고.
소개 ( 본 내용의 날짜는 2010년 01월 24일에 포스팅 된 wiki 를 근거로 한다 )
병렬 NFS ( pNFS )는 NFS v4.1 표준에 포함된, 클라이언트가 스토리지에 병렬로 직접 엑세스 하는 방법을 다루고 있다.
pNFS 는 오늘날 사용되고 있는 NFS의 확장성 및 성능상의 이슈를 해결하기 위하여 대두된 구조로서, 기본적인 아이디어는 데이터와 메타데이터를 분리하는 것이며, 이는 아래의 그림과 같이 전통적인 NFS 와 pNFS가 어떻게 다른지 설명 될 수 있다. ( 이미지는 pNFS 위키페이지의 것을 성능상의 이유로 본 블로그에 사진으로 첨부한다. )
Traditional NFS
How pNFS works.
차이점을 설명하면,
1. pNFS는 클라이언트들이 데이터 서버에 직접 엑세스가 가능하도록 하고 있다.
2. 데이터는 데이터 서버들 사이에서 분리되어 저장이 가능하다.
3. 데이터에 대한 엑세스는 메타 데이터 서버들을 통해 하위 호환성을 유지한다.
이로 인한 잇점은,
1. 고성능 : 확장 가능한 여러대의 서버로 파일을 분산함으로서 성능 및 확장을 노릴 수 있다.
2. 수평적 확장 : 단일 NFS 를 사용함으로서 발생 할 수 있는 제한에서 자유로울 수 있다.
3. 데이터와 메타데이터의 분리 : 데이터에 대한 변경 ( 생성, 추가, 삭제 기타 등 ) 은 메타데이터 서버에서 유지한다.
4. 전통적 NFS서버의 사용성을 유지한다.
5. 전통적 NFS서버와 같은 방법으로 마운트 할 수 있다.
pNFS 를 위한 3가지의 주요 설정 항목이 있다. 클라이언, 메타데이터 서버 ( MDS ) , 데이터 서버 ( DS )
현재, DS와 MDS는 반드시 별도의 머신에 설치 되어야 한다. 기본적으로 NCP - pNFS 설정을 위해서는 3 개의 머신을 추천한다. 한대는 클라이언트, 한대는 메타데이터 서버, 마지막 한대는 데이터 서버.
지금까지 설명 된 내용을 구동하기 위한 설정은 아래를 참고 한다.
NOTE: 본 단계에서 사용하는 파일 시스템은 임시적으로 사용하는 것이다. 데이터 서버는 지정한 파일 시스템을 사용하지 않는다. 데이터 서버의 SMF 서비스 ( svc:/network/dserv:default ) 는 NFS 서버의 SMF 서비스 ( svc:/network/nfs/server:default) 에 의존적이다. 파일 시스템의 공유는 NFS 서버의 서비스가 활성화 되었으며, 구동중임을 보장한다. ( 즉, NFS 서비스가 올라오면 관련된 서비스가 정상 동작함을 의미 한다는 표현 )
데이터 서버는 dservadm 툴을 사용하여 관리되며, 현재 man 페이지는 제공되고 있지 않으나 --help 옵션은 가능하다.
#dservadm --help
MDS 의 주소를 DS에 등록한다.
1. #dservadm addmds [MDS_IP_ADDR]
2. /etc/hosts 파일에 엔트리로 등록
NOTE: 데이터 서버에는 적어도 1개의 pNFS 데이터셋이 존재해야 한다. 따라서, 여러개의 인스턴스가 데이터 서버에 존재한다면, 인스턴스별로 반드시 1개 이상의 데이터셋을 생성해야 함을 잊지 말자.
dserv 서비스 시작 및 온라인 체크
#dservadm enable
#svcs dserv
STATE STIME FMRI
online 20:45:24 svc:/network/dserv:default
클라이언트 설정
MDS를 클라이언트로 마운트.
#mount -F nfs -o vers=4 [mds:/path] [mount_point]
현재 기능상의 문제. ( 이 부분의 번역은 다소 오류가 있을 수 있어 원문을 함께 첨부합니다. 오류 없으시길 )
클라이언트로 부터 파일이나 디렉토리가 제거될 때 주요한 병목이 발생하고 있다.
The major bottleneck is the fact that currently even though the files and directories are deleted from the client.. the dataset that is created on the withholding pool does not show any reduction in size.. This mean, only the namespace is being deleted from the client and mds.. Thus over a period of time the data server will run fill up and the only solution is to delete the entire zpool.
이에 대한 내용은 링크를 참조.
도움 요청은 IRC.frenoode.net 의 nexenta 채널로.
메일링 리스트는 gnu-sol-discuss@opensolaris.org
조금만 더 살펴보면, 이 pNFS 는 다양한 방법으로 각 스토리지 벤더에서 지원하고 있다. 이중 여기서 소개된 nexenta 의 opensolaris 진영에서도 infiniband 와 같은 고속 통신을 사용하여 pNFS over RDMA 와 같은 방법으로 구현하는 것도 있다. 물론 NetApp 의 OnTAP 에서도 그들의 독자적인 방법으로 기능을 제공한다.
주요한 것은 어떠한 파일 시스템을 사용하던, 이제는 데이터가 페타 또는 그이상의 크기를 필요로 하는 곳이 많아지게 되므로, 이와같은 병렬화된 파일레벨의 공유시스템 및 이를 사용했을때 발생하는 성능상의 이슈를 어떠한 방식의 캐싱으로 풀어나갈 것인가, 어디에 적용해야 할 것인가 등이 주요한 고민으로 작용 할 것 같다.
# Source downloads from http://ftp.novell.com/pub/mono/sources-stable/
root@mono:~# mkdir /usr/src/apps
root@mono:~# cd /usr/src/apps
root@mono:/usr/src/apps# wget http://ftp.novell.com/pub/mono/sources/mono/mono-2.6.4.tar.bz2
root@mono:/usr/src/apps# wget http://ftp.novell.com/pub/mono/sources/xsp/xsp-2.6.4.tar.bz2
root@mono:/usr/src/apps# wget http://ftp.novell.com/pub/mono/sources/mod_mono/mod_mono-2.6.3.tar.bz2
# Extract tarball
root@mono:/usr/src/apps# tar xvjf mono-2.6.4.tar.bz2
root@mono:/usr/src/apps# tar xvjf xps-2.6.4.tar.bz2
root@mono:/usr/src/apps# tar xvjf mod_mono-2.6.3.tar.bz2
# Build MONO
root@mono:/usr/src/apps# cd /usr/src/apps/mono-2.6.4
root@mono:/usr/src/apps# ./configure --prefix=/usr/local # if you wants any customize, type --help and add configure options
root@mono:/usr/src/apps# make && make install
# Build xsp
root@mono:/usr/src/apps/xsp-2.6.4# ./configure --prefix=/usr/local/
root@mono:/usr/src/apps/xsp-2.6.4# make && make install
# Build mod_mono for apache
root@mono:/usr/src/apps/mod_mono-2.6.3# ./configure --prefix=/usr/local --with-apr-config=/usr/bin/apr-config
# Add mod_mono.conf to apache2.conf ( or sites that you want to run MONO )
root@mono:/etc/apache2# echo "Include /etc/apache2/mod_mono.conf" >> /etc/apache2/apache2.conf
root@mono:/etc/apache2# tail apache2.conf
회사라는 조직생활을 한 지도 어느덧 10년이 되어간다. 그 동안 이런 회사도 있고 저런 회사도 있었지만, 동네 구멍가게 같은 회사 말고는 매출이 안나서 고생하는 회사에 있어 본 적은 별로 없는 듯 하다.
그도 그럴 것이, 시스템이라는 포지션이 안정되게 회사 안에서 자리잡은 IT 기업도 별로 없게 마련이거니와, 대부분의 회사에서 개발자 또는 조직 관리자가 서버 몇대 정도는 "정" 타이틀 달고 대충 꾸려나가는 경우가 많고, 시스템 엔지니어가 필요하기 시작한 지점은 이미 고객이든 매출이든 뭔가 사용량이 증가해서 이제는 도무지 버틸 수 없는 경우가 많기 때문이 아니었을까 하고 나름 결론을 내려 본다.
이제 또 한번의 턴을 끝내고 돌아서려고 마음을 먹었는데 문득, "업무와 열정사이" 라는 타이틀이 떠오르게 되었네.
언제나 나는, 장애에 대해 전투적으로 대처하고 해결 못하면 서비스 말아먹는다는 각오와, 또 장애가 터질때를 대비해서 사전에 삽질해 두는 과정에 항상 무게를 두며, 장애 또는 무언가 안되는 일이 있을때는 그 안되는 것을 빨리 전파해야 보다 많은 사람들이 머리를 굴려 문제를 해결하기 좋게 끔 만들 수 있다는 나만의 일처리 규칙을 세워놓은게 있다.
항상 이 방법에 따라 어딘가에서 일을 하지 않고 있을 때에도 무언가에 매달렸으며, 어딘가에서 일을 하고 있을 때에는 그들의 문제를 해결하고, 서비스 구조를 개선하기 위해 무던히 입방정을 떨고 문서를 만들어 당위성을 입증하고자 했었지만. 디스플레이 능력의 부족인지 내 자신이 그들에게 믿음을 충분히 주지 못해서 인지는 모르겠지만, 최근들어 한계에 맞딱드리게 되었던 것이,
"그거 그렇게 하려면 고객한테 이렇게 이렇게 해야 하는데 고객들은 그거 안할 꺼야"
"그거 해 드리려면 저희 지금 개발 스케줄이 이런데 그게 딜레이 될 것 같은데요."
"말씀하신 시스템만 사용하는 방법은 글쎄요.."
"그거는 저희가 할 수 있는 일이 아닌 것 같은데요."
나는 내가 제안 하는 분야에 대해 저들보다는 전문가라고 생각해서, 그렇게 하는 것이 고객도 좋고 우리도 좋을 것이다 라는 기준으로 준비해서 가져가면 의례 나오는 답변들. 하나 같이 현상에 대한 문제에 대해 해결책이라고 제시해서 들고 가면 그냥 아무 대안도 없이 브레이크를 거는 전형적인 답변들이다. 물론 브레이크는 브레이크 자체가 가진 순기능(이를 테면 사업 규모가 제법 크게 진행되거나 할 때, Warning 의 측면 같은 부분 ) 도 있기는 하지만, 장애 상황에서 또는 장애가 거듭 발생하는 상황에서의 저런 말같지도 않은 브레이크는 치가 떨리는 경우가 많았다.
뭐, 이 부분에 대해서는 이제 나는 수긍한다.
그들과 나는 살아가는 방법이 다르므로, 또는 함께 서비스를 만들어가는 요원으로서의 공감대 형성은 고사하고, 서비스 운영 주체의 밥그릇에 대한 철통경계로 여념이 없으신 분들이기에.
그러한 "성실"이라는 가치로 무장하고 출퇴근 시간 칼같이 지키며 회사서 사주는 저녁밥 잘 챙겨먹고 8시 언저리 퇴근을 즐기는 무리와 애초에 비교 당하고 싶은 생각은 없지만 그래도 이런 사람들 하고도 일을 할 수 있어야 진정한 조직생활이다 라는 일념으로 "이거 이렇게 하면 당신한테도 좋아요" 를 남발하고 구슬리고 얼르며 20대 후반과 30대 초반을 맞았다.
다만 내가 열이 받는 것은, 나의 열정에 대해 고작 30센티미터 자를 들이대는 것이다.
실력이나 능력은 떨어질 지 모르겟지만, 적어도 나는 내 밥그릇 지키는 것 보다 전체 밥그릇을 키우는게 모두가 더 배부를 수 있는 것이다는 사실을 체험했기 때문이고, 내 위에는 항상 그 위가 있다는 진리도 알고 있다.
그렇기에 해외 고객/국내 고객을 모두 만족시키며, 제품으로서 제 역할을 하게끔 하는 내 전문 분야에 대한 조언/수정방향 또는 질타에 대해 1년 넘게 아무것도 반영된게 없고 또 아무것도 이루어 진게 없는 것이, 또 그러한 실망 및 여러가지 기타 개인적 사정으로 인해 퇴직하게 되는 것에 대해 내 열정이 식어서 라고 매도하지 말아라.
"성실" 이라고 하면 그건 내가 수긍 해 줄 수 있겠다. 다만, 어디가서 사람 잡고 물어봐라.
글로벌 사업하는데서 시스템 1명 두고 내부관리 서비스관리 해외 고객사 시스템 다 해 주면서,
그 1명한테는 적어도 신입 3명분 급여는 주고 탓을 해도 해야지 않겠는가.
원래 나는 설치 엔지니어 따위가 아닌데 그렇게 밖에 일할 수 밖에 없는, 그런걸 원하기 때문에 해 주었으면
내 기술에 대한 기회비용 및 감가상각 정도는 바라지도 않는다.
원래 예능에 더럽게 소질이 없는터라 피아노나 리코더 혹은 단소 때문에 학창시절에 맞아 본 적은 있어도 감상에 젖어본 적은 거의 없는 이 삭막한 인생에 아련한 추억 하나가 떠오르는 바람에 급작스럽게 피아노를 배우고 싶어졌다.
Play Station 과 같은 콘솔 게임기로는 그저 위닝이나 하고 철권이나 하는 뭔가 내기 대결용 게임만이 전부라고 생각할 때, 까까머리 군생활 시절 어느새인가 모든 게임잡지 지면을 장식하고있던 Final Fantasy X 는 아름다운 화면과 절절한 스토리등에 대한 각종 오덕스런 기자들의 찬양으로 도배가 되어 해묵은 고정관념을 깨고 나도 한번? 하면서 FFX 에 대해 호감을 갖기 시작했다.
Final Fantasy X
사격인가 대전차 화기인가 아무튼 뭔가 군대서 5일짜리 휴가를 받아 지하철로 집에 가던 내 머릿속엔 온통 FF X 에 대한 생각 뿐. 집에 도착하자마자 친구에게 PS2 를 강탈하여 FFX를 거짓말 안하고 4박 5일 휴가 중 부대 왔다갔다 하는 시간 빼고 2박 3일을 밤새고 했다. 물론, 짧은 시간에 엔딩을 보아야 했으므로 액션리플레이의 마법과 같은 힘을 빌린 것은 부정하지 않겠다.
Zanrkand
폭풍간지 아론 사마
어딘가의 폐허가 된 도시에 울적한 군상들이 앉아서 청승을 떠는 도중 주인공의 "내 이야기를 들어봐봐좀" 하며 시작된 이 게임은 갖은 갈굼과 산악행군 구보로 단련된 군바리의 눈밑에 다크서클을 턱까지 내려오게 하는데 전혀 부족함이 없었다. 마치 내 여자친구 같던 주인공과 아름다운 세상, 게임속의 존재가 현실이 되어 버린 것 같은 착각속에 빠져 3일을 살고 난 후에는 이미 나는 남자친구를 잃은 슬픔을 참고 살아가는 여주인공과 함께 현실을 공유하는 군바리가 되어있었다. 아.... 휴가 복귀 못할 뻔 했지..
FFX 팬의 그녀, 유우나
이후 군생활은 온통 FFX 와 FF X-2 에 관한 잡지들로 지루함과 싸워내고 이겨내어 무사히 전역하게 되었고 이에 지대한 영향을 끼친 그녀를 위해 잠시 묵념..;; 은 아니고.
아무튼 그시절 그렇게나 큰 감동이었던 본 게임의 OST가 최근들어 Piano Collection 으로 발매 되었다는 사실을 깨닫게 되었다. 그리고, 이 모든 곡들 중 To Zanarkand 라는 곡이 피아노로 배우고 싶은 그 곡.
필수 구매 핫 아이템
돌이켜 보면 그까짓 게임이 무어 그런 대수겠냐고 할 사람들도 많겠지만, 이 Square Soft 가 만들어 내는 본 FF 시리즈는
화려한 비주얼과 각종 전문가의 사운드효과, 그리고 그 OST가 백미다. 그런 요소들이 '스토리'와 조합되어 하나의 세상을 만들어 낼 때, 영화보다 심각한 수준의 감동이 .... 뭐 그런게 있다.
진정한 삶의 고됨에 눈 뜨기전 접했던 사랑이라는 모호한 감정을 아련하게나마 어린시절에 접할 수 있게 해 주었던 음악.
듣기만 해도 눈물이 쏟아질 것 같던 감성시절은 지났지만, 얼마전에 구입한 YDP-140 으로 신나게 연주 해 주어야겠다.
"Ipps" - 초당 패킷 유입량
"Tput" - 1M 를 전송하는데 사용된 패킷의 총 갯수 packets out of total 1M that made it
out
"txint" - 전송을 완료하는데 필요한 시스템 인터럽트 카운트
"Done" - rx ring 에서 모든 패킷이 전송되는동안 poll() 시스템 함수가 호출된 횟수.
( Note from this that the lower the load the more we could
clean up the rxring )
"Ndone" - "Done" 의 반대 개념.
( Note again, that the higher the load the more times we
couldn't clean up the rxring )
NIC가 890Kpackets/sec 을 수신할때 인터럽트는 단지 17회만 발생하게 된다. 시스템은 이와같은 부하 상황에서
1 인터럽트/패킷 을 처리 할 수 없다. 반면, rx 인터럽트가 증가하면 인터럽트/패킷의 비율이 함께 증가하는것을
볼 수있다. (표 참조)
따라서, 적당히 낮은 양의 패킷이 유입되는 상황에서는, 1 개의 패킷을 처리하기 위해 1회의 인터럽트가 필요하게
될수 있으며, 만약 시스템이 이를 처리 할 수 없는 인터럽트의 양에 도달하게 되면, 시스템은 그때부터 뻗기 시작할
것이다.
( 역자 주. 표를 그냥 vmstat 로 표시했으면 편할껀데... 어쨌든 위와 같이 인터럽트가 허용 범위 이상에 도달하는
상황은 예전 NForce Chipset 에 포함되어 나온 NIC 드라이버 문제 외에 광케이블을 크로스로 2대를 연결하여
네트워크 시뮬레이션 해 보면 알 수 있다. NAPI 가 적용되지 않은 드라이버의 경우, 인터럽트가 20만회 이상을
육박하며 , 시스템이 퍼지는 현상을 쉽게 목격 할 수 있다. 당연히 시스템의 인터럽트 능력은 CPU 및 해당 NIC 인터
페이스와 관계 되므로, 한계 성능은 시스템에 따라 다르게 나타 날 수 있다. 아무튼, 이와 같은 현상에 대한 수치적
증거로서 위의 표를 이해 하면 되겠다. )
0) 전제 조건
NAPI 변경으로 인한 잇점이 아난 네트워크 스택을 제어하기 위해 2.4 커널을 사용 할 수도 있다. ( 본문서가 마지막으로
수정된건 2002년 4월임을 양지하자 ) NAPI 추가 기능은 커널 하위 호환성을 해치지는 않지만, 다음과 같은 feature를
사용 가능하게끔 해 주어야 동작한다.
A) 소프트웨어 디바이스에 패킷을 저장하기 위한 DMA ring 또는 충분한 RAM 크기
B) 인터럽트를 끌 수 있는 기능 또는 스택에 패킷을 넣을 수 있는 이벤트.
NAPI는 패킷 이벤트를 dev->poll() 메서드를 통해 처리한다.
tulip 드라이버의 인터럽트처리를 dev->poll() 로 변경된 드라이버를 사용한 테스트에서는 약간의 지연이 나타났으며,
MII 드라이버에서는 조금 더 심했다.
본 예제는 이 문서에 사용하기위해 제작된 것이며, 인터럽트 핸들러를 dev->poll() 로 변경한 것이다. 이 코드를
보려면
아래의 포팅된 e1000 코드를 참조하기 바란다.
하지만 이와 같은 방법에는 문제가 있을 수 있다. 서로 다른 기종의 NIC 들은 각각 다르게 동작하며, 이 동작에는
status/event 와 같은 상태에 따른 다른 셋업이 요구된다.
아래와 같은 두가지의 서로 다른 ACK 이벤트 처리를 위한 매카니즘이 존재한다.
I ) Clear-on-read (COR) 로 알려진 매카니즘. status/event 레지스터를 읽는 순간 모두
비워버리는 형태.
natsemi 와 sunbmac NIC가 이 동작으로 유명하며, 이와같은 경우 모든걸 dev->poll() 로
변경해야 함.
II) Clear-on-write ( COW )
i) bit-location 에 1을 write 할때 status 가 지워지는 형태. NAPI와 가장 잘 맞는
형태이며, 많은 벤더의 NIC가
지원. 수신 이벤트 ( receive event ) 에 대해서만 dev-poll() 처리. 나머지는 기존의
인터럽트 핸들러로 처리.
ii) 무엇을 쓰던 write 액션이 발생하면 status 레지스터를 비우는 형태. 이와 같은 형태로 동작하는 리눅스
드라이버
는 보이지 않음.
C) 새로운 work 를 정확하게 감지 할 수 있는 능력.
NAPI 는 처리할 work 가 있는 상태에 shutdown 되며, 처리할 work 가 없는 상태에서 켜지게 된다.
작은 윈도우 사이즈의 새로운 패킷이 인터럽트가 재가동 되는 순간 유입된다면, 이 패킷은 인터럽트가 활성화 되는 순간
유실 될 수 있다. 따라서 우리는 이와 같은 새로 유입된 패킷은 다음번 인터럽트때 처리 될 수 있도록 분류 되어지길
원한다.
정리하면, 이와 같은 패킷은 경쟁상태(시스템이 하나의 자원에 한개 이상의 프로세스가 몰린 상태, 그냥 busy. 역자주 )
의 시스템에서는 "rotting packet" ( 쓸 수 없는 패킷 ) 으로 처리 되어야 한다.
이는 매우 중요한 프로세스이며, appendix 2 에서 추가적으로 논의 된다.
Locking 룰 과 환경에 대한 보장.
- Guarantee : 반드시 단 1개의 CPU가 dev->poll() 호출이 가능해야 한다.
- 코어 레이어에서는 패킷을 보내기 위한 디바이스 호출에 round robin 포멧을 사용해야 한다.
- contention 은 다른 CPU의 rx ring 엑세스의 결과로만 되어야 한다. ( 뭔가 번역 이상 @_@ )
이는 close() 및 suspend() 호출시에만 발생한다. ( rx ring을 비우고자 할때 )
***** guarantee : 드라이버 개발자는 이에대해 신경 쓸 필요가 없다. 이는 net의 최상위 레벨에서 구현 되기
때문에.
- local interrupts 활성화 ( dev->poll() 로 처리하고 싶지 않은 경우 )
예를 들면, link/MII , txcomplete 는 옛날 방식으로 동작한다.
본 문서의 이후로 부터는, dev->poll() 이 수신 이벤트를 위한 단 하나의 프로세스라고 가정한다.
NAPI 에서 제공하는 새로운 메서드
a) netif_rx_schedule(dev)
디바이스 poll 스케줄을 위한 IRQ 핸들러
b) netif_rx_schedule_prep(dev)
CPU 폴링리스트가 가용한 상태라면 여기에 장치 상태를 넣기 위해 사용된다.
c) __netif_rx_schedule(dev)
CPU의 폴 리스트에 넣기 위해 사용된다. _prep 가 이미 호출 되었고 1을 리턴 했음을 가정한다.
d) netif_rx_reschedule(dev, undo)
이미 호출된 장치 이외의 다른 장치를 폴링하기 위해 사용된다. 자세한 내용은 Appendix 2 를 참조한다.
e) netif_rx_complete(dev)
CPU poll 리스트에서 장치를 제거한다. 이는 반드시 현재 cpu 의 poll list 여야 한다.
이는 모든 작업이 완료 되었을때, dev->poll() 를 호출한다.
위에 기술된 내용은 아래에 반영되어 있다.
NAPI를 사용하게끔 포팅할때 변경되는 디바이스 드라이버의 내용
NAPI동작을 위해서는 아래의 내용이 변경될 필요가 있다.
1) dev->poll() 메서드의 소개
이는 새로운 패킷이 수신되었을때 네트워크 코어에서 드라이버로 호출하는 메서드다.
드라이버는 네트워크 서브 시스템의 하위 유연성을 위해 수신된 패킷을 CPU에 의해 dev->quota 로
패킷을 보낼 수 있다. ( 따라서 다른 장치들이 stack 에 접근 할 수 있다. )
dev->poll() 프로토타입은 다음과 같다.
int my_poll(struct net_device *dev, int *budget)
budget 은 네트워크 서브시스템에 남아있는 패킷의 수량을 나타낸다.
*각 드라이버는 보내진 패킷 만큼 감소된 카운트를 변경해 주어야 한다.
패킷의 총 수량은 dev->quota 보다 같거나 커질 수 없다.
dev->poll() 메서드는 시스템의 가장 높은 레벨에서 호출되며, 드라이버는 요청된 양의 패킷을 스택으로 보낼
수 있게된다. 인터럽트 이후의 dev->poll() 에 관한 상세 내용은 아래에 기술된다.
2) registering dev->poll() method
dev->poll should be set in the dev->probe() method.
e.g:
dev->open = my_open;
.
.
/* two new additions */
/* first register my poll method */
dev->poll = my_poll;
/* next register my weight/quanta; can be overriden in /proc */
dev->weight = 16;
.
.
dev->stop = my_close;
3) dev->poll() 의 스케쥴링
이 예제는 인터럽트 핸들러에 수정을 가하였으며, NIC에서 패킷을 꺼내 스택으로 보내는 동작을 보여준다.
int work_count = my_work_count;
status = read_interrupt_status_reg();
if (status == 0)
return; /* Shared IRQ: not us */
if (status == 0xffff)
return; /* Hot unplug */
if (status & error)
do_some_error_handling()
do {
acknowledge_ints_ASAP();
if (status & link_interrupt) {
spin_lock(&tp->link_lock);
do_some_link_stat_stuff();
spin_lock(&tp->link_lock);
}
if (status & rx_interrupt) {
receive_packets(dev);
}
if (status & rx_nobufs) {
make_rx_buffs_avail();
}
if (status & tx_related) {
spin_lock(&tp->lock);
tx_ring_free(dev);
if (tx_died)
restart_tx();
spin_unlock(&tp->lock);
}
status = read_interrupt_status_reg();
} while (!(status & error) || more_work_to_be_done);
status = read_interrupt_status_reg();
if (status == 0)
return; /* Shared IRQ: not us */
if (status == 0xffff)
return; /* Hot unplug */
if (status & error)
do_some_error_handling();
do {
/************************ start note *********************************/
acknowledge_ints_ASAP(); // dont ack rx and rxnobuff here
/************************ end note *********************************/
if (status & link_interrupt) {
spin_lock(&tp->link_lock);
do_some_link_stat_stuff();
spin_unlock(&tp->link_lock);
}
/************************ start note *********************************/
if (status & rx_interrupt || (status & rx_nobuffs)) {
if (netif_rx_schedule_prep(dev)) {
/* disable interrupts caused
* by arriving packets */
disable_rx_and_rxnobuff_ints();
/* tell system we have work to be done. */
__netif_rx_schedule(dev);
} else {
printk("driver bug! interrupt while in poll\n");
/* FIX by disabling interrupts */
disable_rx_and_rxnobuff_ints();
}
}
/************************ end note note *********************************/
if (status & tx_related) {
spin_lock(&tp->lock);
tx_ring_free(dev);
if (tx_died)
restart_tx();
spin_unlock(&tp->lock);
}
status = read_interrupt_status_reg();
/************************ start note *********************************/
} while (!(status & error) || more_work_to_be_done(status));
/************************ end note note *********************************/
I ) 패킷의 수신으로 발생한 어떠한 인터럽트도 동작하지 않게끔 변경한다. .
다음의 두가지 상태로 인해 하드웨어는 패킷이 수신되면 인터럽트를 유발한다. (NAPI 에서는 원하지 않는 동작 )
첫째는, 패킷이 수신되고 (rxint) , 둘째는 패킷이 도착 했으나 가용한 DMA 영역이 없음을 발견
(rxnobuff) 했을
때 이다.
이는, 위의 두가지 상황에 대해 acknowledge_ints_ASAP() 호출로 status 레지스터를 비울 수 없음을
의미한다.
NAPI를 사용하는 경우에는 work 가 어느 단계에서든 적절히 종료되면 언제든 status 레지스터를 비울 수 있다.
이에 관하여 poll() 및 refill_rx_ring() 은 아래에 논의 된다.
netif_rx_schedule_prep() 는 장치가 폴링 리스트에 정상적으로 등록이 되고 동작 가능한 상태이면 1을
리턴한다.
0이 리턴되는 경우는 장치가 이미 추가 되었거나, 장치가 동작하지 않는 경우 등으로 추정 할 수 있다.
이러한 오류를 rx 와 rxnobuf 인터럽트를 비활성화 함으로서 미연에 방지 할 수 있게 된다.
II ) receive_packets(dev) 와 make_rx_buffs_avail() 은 없어질 가능성이 있다. 아직은
존재하지만...
( 8년전 문서이기에 궁금하신 분은 최신 소스를 까 보도록 한다 )
사실, receive_packets(dev) 는 my_poll() 과 매우 비슷하며,
make_rx_buffs_avail() 은 my_poll() 에서 호출한다.
4) receive_packets() 을 dev->poll() 로 변환
D Becker 의 전통적인 receive_packet(dev) 를 my_poll() 로 변환해 줄 필요가 있다.
/* this is called by interrupt handler */
static void receive_packets (struct net_device *dev)
{
struct my_private *tp = (struct my_private *)dev->priv;
rx_ring = tp->rx_ring;
cur_rx = tp->cur_rx;
int entry = cur_rx % RX_RING_SIZE;
int received = 0;
int rx_work_limit = tp->dirty_rx + RX_RING_SIZE - tp->cur_rx;
while (rx_ring_not_empty) {
u32 rx_status;
unsigned int rx_size;
unsigned int pkt_size;
struct sk_buff *skb;
/* read size+status of next frame from DMA ring buffer */
/* the number 16 and 4 are just examples */
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
pkt_size = rx_size - 4;
/* process errors */
if ((rx_size > (MAX_ETH_FRAME_SIZE+4)) ||
(!(rx_status & RxStatusOK))) {
netdrv_rx_err (rx_status, dev, tp, ioaddr);
return;
}
if (--rx_work_limit < 0)
break;
/* grab a skb */
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
.
.
netif_rx (skb);
.
.
} else { /* OOM */
/*seems very driver specific ... some just pass
whatever is on the ring already. */
}
/* move to the next skb on the ring */
entry = (++tp->cur_rx) % RX_RING_SIZE;
received++ ;
}
/* store current ring pointer state */
tp->cur_rx = cur_rx;
/* Refill the Rx ring buffers if they are needed */
refill_rx_ring();
.
.
}
-------------------------------------------------------------------
새로운 파라메터에 주의하며 아래의 변경된 코드를 참조한다.
/* this is called by the network core */
static void my_poll (struct net_device *dev, int *budget)
{
struct my_private *tp = (struct my_private *)dev->priv;
rx_ring = tp->rx_ring;
cur_rx = tp->cur_rx;
int entry = cur_rx % RX_BUF_LEN;
/* maximum packets to send to the stack */
/************************ note note *********************************/
int rx_work_limit = dev->quota;
/************************ end note note *********************************/
do { // outer beggining loop starts here
clear_rx_status_register_bit();
while (rx_ring_not_empty) {
u32 rx_status;
unsigned int rx_size;
unsigned int pkt_size;
struct sk_buff *skb;
/* read size+status of next frame from DMA ring buffer */
/* the number 16 and 4 are just examples */
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
pkt_size = rx_size - 4;
/* process errors */
if ((rx_size > (MAX_ETH_FRAME_SIZE+4)) ||
(!(rx_status & RxStatusOK))) {
netdrv_rx_err (rx_status, dev, tp, ioaddr);
return;
}
/************************ note note *********************************/
if (--rx_work_limit < 0) { /* we got packets, but no quota */
/* store current ring pointer state */
tp->cur_rx = cur_rx;
/* Refill the Rx ring buffers if they are needed */
refill_rx_ring(dev);
goto not_done;
}
/********************** end note **********************************/
/* grab a skb */
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
.
.
/************************ note note *********************************/
netif_receive_skb (skb);
/********************** end note **********************************/
.
.
} else { /* OOM */
/*seems very driver specific ... common is just pass
whatever is on the ring already. */
}
/* move to the next skb on the ring */
entry = (++tp->cur_rx) % RX_RING_SIZE;
received++ ;
}
/* store current ring pointer state */
tp->cur_rx = cur_rx;
/* Refill the Rx ring buffers if they are needed */
refill_rx_ring(dev);
/* no packets on ring; but new ones can arrive since we last
checked */
status = read_interrupt_status_reg();
if (rx status is not set) {
/* If something arrives in this narrow window,
an interrupt will be generated */
goto done;
}
/* done! at least thats what it looks like ;->
if new packets came in after our last check on status bits
they'll be caught by the while check and we go back and clear them
since we havent exceeded our quota */
} while (rx_status_is_set);
/* If RX ring is not full we are out of memory. */
if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL)
goto oom;
/* we are happy/done, no more packets on ring; put us back
to where we can start processing interrupts again */
netif_rx_complete(dev);
enable_rx_and_rxnobuf_ints();
/* The last op happens after poll completion. Which means the following:
* 1. it can race with disabling irqs in irq handler (which are done to
* schedule polls)
* 2. it can race with dis/enabling irqs in other poll threads
* 3. if an irq raised after the begining of the outer beginning
* loop(marked in the code above), it will be immediately
* triggered here.
*
* Summarizing: the logic may results in some redundant irqs both
* due to races in masking and due to too late acking of already
* processed irqs. The good news: no events are ever lost.
*/
if (!received) {
printk("received==0\n");
received = 1;
}
dev->quota -= received;
*budget -= received;
return 1; /* not_done */
oom:
/* Start timer, stop polling, but do not enable rx interrupts. */
start_poll_timer(dev);
return 0; /* we'll take it from here so tell core "done"*/
/************************ End note note *********************************/
}
-------------------------------------------------------------------
위의 내용으로 인해 우리는,
0) rx_work_limit = dev->quota
1) refill_rx_ring() 은 정상적으로 동작하지 않게 될때 rxnobuff 비트를 지워줄 필요가 있다.
2) done / not_dont 상태가 있다.
3) netif_rx() 대신 netif_receive_skb() 가 skb 를 패스하기 위해 호출된다.
4) 새로운 OOM ( Out of memory ) 조건을 가진다.
5) 새로운 for 루프가 추가되었다. 이는 새로운 패킷의 유입의 확인, 모든 세팅의 정상 동작 및 패킷을
보내는 동안 qouta 를 초과하지 않기 위한 동작등이 포함된다.
/* If RX ring is not full we are still out of memory.
Restart the timer again. Else we re-add ourselves
to the master poll list.
*/
if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL)
restart_timer();
else netif_rx_schedule(dev); /* we are back on the poll list */
5) dev->close() , dev->suspend() 이슈
드라이버 개발자는 본 내용에 대해 신경쓰지 않아도 된다. 본 내용은 향후 채워진다.
6) /proc 에 새로운 스탯의 추가.
새로운 기능의 디버깅으로 인해 본 내용 역시 나중에 추가하기로 한다.
APPENDIX 1: HW FC 사용에 대한 논의
대부분의 FC 칩들은 Rx buffer 에 더이상 공간이 없을때 pause packet 을 전송한다. NAPI 의 softirq 에 의해 DMA ring
에서 패킷을 빼 낼때, 패킷의 유입 량에 비해 시스템이 패킷을 가져오는 속도가 느린 경우 ( 시스템이 패킷을 지우는
속도 보다 유입되는 속도가 더 빠를때 ) 이론적으로 이와 같은 packet storm 으로 발생한 모든 패킷에 대해 단 1회의
rx 인터럽트를 호출 하여 해결 할 수 있다.
낮은 부하상태에서는, 패킷 당 1회의 인터럽트가 발생이 가능하다.
FC는 충분히 빠른 속도로 시스템이 패킷을 끄집어 내지 못할때에 대비하여 프로그램 되어야 한다. ( send a pause
when out of rx buffers )
FC가 좋은 솔루션이긴 하지만, 너무 고가이다. ( 옛날 문서니까. 역자주 )
APPENDIX 2: rotting packet / race-window avoidance 스키마
( 이 부분 번역은 생략. 쓰다보니 appendix 까지 번역해야 하는 생각이 스멀스멀.. )
here are two types of associations seen here
1) status/int which honors level triggered IRQ
If a status bit for receive or rxnobuff is set and the corresponding
interrupt-enable bit is not on, then no interrupts will be generated. However,
as soon as the "interrupt-enable" bit is unmasked, an immediate interrupt is
generated. [assuming the status bit was not turned off].
Generally the concept of level triggered IRQs in association with a status and
interrupt-enable CSR register set is used to avoid the race.
If we take the example of the tulip:
"pending work" is indicated by the status bit(CSR5 in tulip).
the corresponding interrupt bit (CSR7 in tulip) might be turned off (but
the CSR5 will continue to be turned on with new packet arrivals even if
we clear it the first time)
Very important is the fact that if we turn on the interrupt bit on when
status is set that an immediate irq is triggered.
If we cleared the rx ring and proclaimed there was "no more work
to be done" and then went on to do a few other things; then when we enable
interrupts, there is a possibility that a new packet might sneak in during
this phase. It helps to look at the pseudo code for the tulip poll
routine:
--------------------------
do {
ACK;
while (ring_is_not_empty()) {
work-work-work
if quota is exceeded: exit, no touching irq status/mask
}
/* No packets, but new can arrive while we are doing this*/
CSR5 := read
if (CSR5 is not set) {
/* If something arrives in this narrow window here,
* where the comments are ;-> irq will be generated */
unmask irqs;
exit poll;
}
} while (rx_status_is_set);
------------------------
CSR5 bit of interest is only the rx status.
If you look at the last if statement:
you just finished grabbing all the packets from the rx ring .. you check if
status bit says theres more packets just in ... it says none; you then
enable rx interrupts again; if a new packet just came in during this check,
we are counting that CSR5 will be set in that small window of opportunity
and that by re-enabling interrupts, we would actually triger an interrupt
to register the new packet for processing.
[The above description nay be very verbose, if you have better wording
that will make this more understandable, please suggest it.]
2) non-capable hardware
These do not generally respect level triggered IRQs. Normally,
irqs may be lost while being masked and the only way to leave poll is to do
a double check for new input after netif_rx_complete() is invoked
and re-enable polling (after seeing this new input).
Sample code:
---------
.
.
restart_poll:
while (ring_is_not_empty()) {
work-work-work
if quota is exceeded: exit, not touching irq status/mask
}
.
.
.
enable_rx_interrupts()
netif_rx_complete(dev);
if (ring_has_new_packet() && netif_rx_reschedule(dev, received)) {
disable_rx_and_rxnobufs()
goto restart_poll
} while (rx_status_is_set);
---------
Basically netif_rx_complete() removes us from the poll list, but because a
new packet which will never be caught due to the possibility of a race
might come in, we attempt to re-add ourselves to the poll list.
Authors:
========
Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
Jamal Hadi Salim <hadi@cyberus.ca>
Robert Olsson <Robert.Olsson@data.slu.se>
Acknowledgements:
================
People who made this document better:
Lennert Buytenhek <buytenh@gnu.org>
Andrew Morton <akpm@zip.com.au>
Manfred Spraul <manfred@colorfullife.com>
Donald Becker <becker@scyld.com>
Jeff Garzik <jgarzik@mandrakesoft.com>
결국, 이런저런 내용이 많지만, NAPI 의 핵심은 다음의 몇가지로 압축 될 수 있다.
1)
고부하 상태건 저부하 상태건 패킷의 유입으로 인한 하드웨어로 부터 ( 하드웨어 드라이버로 부터) 의
직접적인 인터럽트는 시스템에 심각한 부하를 일으킬 가능성이 있다.
2)
따라서 이와 같은 인터럽트는 NAPI 및 기타 하드웨어 ( NIC )의 인터럽트 제어를 통해 부하율을 낮출 수 있으나,
다소간의 지연이 발생 할 수 있다.
3)
NAPI 가 동작하는 기본적인 방식은 Polling 이며, 이를 쉽게 말하면 유입된 패킷이 발생할때 마다 인터럽트 하여
가져오는 것이 아니라 드라이버가 원하는 때에 적절한 스케줄링을 통하여 폴링으로 한꺼번에 가져온다.
4)
따라서 이러한 패킷을 저장하기 위한 공간이 필요하며, 이러한 공간이 포화가 된 경우/ 또는 시스템에서 처리
불가능한 경우 등에 대한 대비가 필요하다.
5)
NAPI 는 기본적으로 rx ring 에 동작한다.
로 압축 할 수 있겠다.
번역한 문서가 아주 옛날 버전이기는 하지만, 최신 문서 소개해 봐야 다 기본 내용은 거기서 거기.
실제 netpipe 등과 같은 툴을 통해 packet storming 이나 하드웨어 레벨의 테스팅을 하다 보면
쉽게 접하고 튜닝이 가능한 부분이며, 힘들어하는 시스템에 idle 을 조금이나마 안겨 줄 수 있는
방법이라 하겠다. 배포판 또는 드라이버별로 적용이 되고 안되는 등의 차이가 있는 듯 하니
궁금하면 dmesg 를 확인 해 보길 바란다.