 |  |
|
난이도 : 중급 Martin Streicher, 웹 개발자, 프리랜서
옮긴이 : 박재호 이해영 dwkorea@kr.ibm.com
원문 게재일 : 2008 년 9 월 16 일 번역 게재일 : 2008 년 12 월 09 일 inotify는 읽기, 쓰기, 생성하기와 같은 파일 시스템 연산을 감시하는 리눅스(Linux®) 기능입니다. inotify는 반응성이 좋으며, 놀랍도록 사용하기 쉬우며, 크론 작업으로 바쁘게 폴링하는 경우보다 훨씬 더 효율적입니다. inotify를 응용 프로그램으로 통합하는 방식을 익히고 시스템 관리를 자동화하는 데 사용하는 명령행 도구 집합을 살펴봅시다.
시스템 관리는 일상 생활과 상당히 비슷하다. 세수하고 야채를 먹듯이, 자그마한 일상 유지 보수 작업은 기계를 제대로 돌아가게 만든다. 문서 꾸미기, 호출에 응답하기, 업데이트 내려받기, 프로세스 감시와 같은 작업 때문에 방해 받는 상황에서 주기적으로 임시 파일이나 진단 로그 파일과 같은 거추장스러운 찌꺼기를 청소해야 한다. 다행스럽게도 셸 스크립트를 활용한 자동화, Nagios와 같은 도구를 사용한 감시 작업, 만능 크론으로 수행하는 자동화된 작업 일정은 어깨에서 짐을 덜어준다.
아주 묘한 이야기지만, 이런 도구는 모두 반응성이 떨어진다. 조건을 감시하기 위해 크론 작업으로 일정을 지정해 놓을 수 있지만, 자원을 많이 사용하고 계산이 바쁜 작업을 바쁘게 폴링하면 확장성이 그다지 좋지 않다. 예를 들어, FTP 디렉터리로 들어오는 자료를 감시해야 한다면, 새로운 파일이 들어왔는지 찾기 위해 find 명령으로 개별 폴더를 감시해야 한다. 하지만 동작이 매끄럽게 돌아가는 듯이 보일지라도 find 명령어 수행과 더불어 새로운 셸을 매번 띄워야 하므로, 디렉터리를 열고 탐색하기 위해 시스템 호출이 여러 번 필요하다. 아주 잦거나 여러 번에 걸쳐 바쁘게 일어나는 폴링 작업은 오래 지나지 않아 누적될 수 있다. (설상가상으로, 바쁘게 일어나는 폴링이 항상 적절하지는 않다. 맥 OS X의 파인더와 같은 파일 시스템 브라우저가 갱신 내용을 폴링하기 위해 치루는 비용과 복잡성을 상상해 보자.)
그러면 관리자가 해야 할 일은 무엇일까? 다행스럽게 믿을 만한 컴퓨터에 다시 한번 의지할 수 있다.
inotify란?
inotify는 리눅스 커널 기능으로 파일 시스템을 감시하고 있다가 삭제, 읽기, 쓰기, 심지어 unmount 연산에 이르기까지 관련 사건을 기다리는 응용에 즉시 경고를 준다. 세부 기능으로 파일 시작과 목적지를 파악해 이동까지도 추적이 가능하다.
inotify의 용법은 단순하다. 파일 기술자를 만들어 워치를 한 개 이상 붙인다(워치는 경로와 사건 집합이다). 그러고 나서 read() 메서드를 사용해 기술자에서 사건 정보를 받는다. 아까운 CPU 사이클을 낭비하는 대신에, read()는 사건이 발생할 때까지 차단된다.
금상첨화로 inotify는 전통적인 파일 기술자로 동작하기에, 전통적인 select() 시스템 호출을 활용해서 동시에 여러 입력이 들어오는 상황에서도 워치를 감시하도록 만들어준다. 파일 기술자를 사용한 차단이나 select()를 사용한 멀티플렉싱이라는 양쪽 접근 방법 모두가 바쁘게 일어나는 폴링을 회피한다.
이제 inotify를 살펴보고, 자그마한 C 코드를 작성해보고, 빌드할 수 있는 명령행 도구를 살펴보며 파일 시스템 사건에 반응하는 명령과 스크립트를 구성해보자. inotify는 한 밤중에 고양이(역주: cat을 중의적인 의미로 사용하고 있다)를 풀어놓지는 않지만, 필요하다면 cat과 wget을 돌려 의도한 작업을 정확하게 수행할 수 있다.
inotify를 사용하려면, 리눅스 커널 2.6.13 이상을 탑재한 리눅스 기계가 있어야 한다(2.6.13 이전 리눅스 커널은 dnotify라는 기능이 다소 뒤떨어지는 파일 감시 기능을 사용한다). 커널 버전을 모르겠다면, 셸에서 uname -a 명령을 내려보자.
% uname -a
Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux
|
여기서 나온 커널 버전이 최소한 2.6.13이라면, 시스템은 inotify를 지원해야 한다. /usr/include/sys/inotify.h 파일도 살펴보자. 이 파일이 있다면 커널이 inotify를 지원할 가능성이 높다.
참고: FreeBSD와 (이에 영향을 받은) 맥 OS X은 kqueue라는 inotify 유사 기능을 제공한다. FreeBSD 기계에서 man 2 kqueue를 입력하면 더 많은 정보를 얻을 수 있다.
이 기사는 맥 OS X 버전 10.5 레퍼드에 패러렐즈 데스크톱 버전 3.0을 설치한 다음에 가상으로 돌리는 (하디라고 알려진) 우분투 데스크톱 버전 8.04.1에 기반을 둔다.
inotify C API
inofity는 파일 시스템 모니터를 만드는 세 가지 시스템 호출을 제공한다.
inotify_init()는 커널에서 inofity 하위 시스템 인스턴스를 만든다. 성공하면 파일 기술자를 반환하며, 실패하면 -1을 반환한다. 다른 시스템 호출과 마찬가지로 inotify_init()가 실패하면, errno를 점검해서 문제 원인을 진단하자.
inotify_add_watch()inotify_add_watch()는 이름이 암시하듯이, 워치를 추가한다. 각 워치는 경로 이름과 적절한 사건 목록을 제공해야 한다. 여기서 사건은 IN_MODIFY와 같은 상수로 지정한다. 사건을 두 개 이상 감시하려면, 논리 OR 연산자(C에서 |)로 각 사건을 연결한다. inotify_add_watch()가 성공하면, 호출 결과로 등록된 워치를 가리키는 고유 식별자가 반환된다. 그렇지 않으면 -1이 반환된다. 이 식별자를 활용해 연관된 워치를 변경하거나 삭제한다.
inotify_rm_watch()는 워치를 제거한다.
read()와 close() 시스템 호출 역시 필요하다. inotify_init()에서 받은 기술자로 read()를 호출해 경고를 기다린다. 전형적인 파일 기술자 용법에 따라, 응용 프로그램은 스트림에서 자료로 표현되는 사건 송신을 기다리는 동안 차단 상태로 들어간다. inotify_init()에서 받은 기술자를 넘기면서 close() 시스템 호출을 부르면 inotify 인스턴스와 관련된 모든 메모리와 모든 활성 워치를 삭제하고 할당을 해제한다. (전형적인 참조 카운터 관련 주의 사항이 여기에도 적용된다. 인스턴스와 관련된 모든 파일 기술자는 워치와 inotify가 소비하는 메모리가 해제되기 전에 닫혀야만 한다.)
단지 강력한 세 가지 API와 "모든 것이 파일"이라는 익숙한 패러다임이 전부다. 이제 예제 응용 프로그램 작성으로 옮겨갈 준비가 끝났다.
예제 응용 프로그램: 사건 감시
Listing 1은 짧은 C 프로그램으로 파일 생성과 삭제라는 두 가지 사건이 일어나는지 디렉터리를 감시한다.
Listing 1. 디렉터리에서 생성, 삭제, 변경 사건을 감시하는 예제 inotify 응용
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
int main( int argc, char **argv )
{
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];
fd = inotify_init();
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch( fd, "/home/strike",
IN_MODIFY | IN_CREATE | IN_DELETE );
length = read( fd, buffer, BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
while ( i < length ) {
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->len ) {
if ( event->mask & IN_CREATE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was created.\n", event->name );
}
else {
printf( "The file %s was created.\n", event->name );
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was deleted.\n", event->name );
}
else {
printf( "The file %s was deleted.\n", event->name );
}
}
else if ( event->mask & IN_MODIFY ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was modified.\n", event->name );
}
else {
printf( "The file %s was modified.\n", event->name );
}
}
}
i += EVENT_SIZE + event->len;
}
( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );
exit( 0 );
}
|
응용은 fd = inotify_init();로 inofity 인스턴스를 만들고 나서 wd = inotify_add_watch(...)로 /home/strike에서 파일 생성과 삭제를 감시하는 워치를 하나 추가한다. read() 메서드는 경고가 도착할 때까치 차단된다. 각 파일/사건 쌍으로 이뤄진 경고 명세는 바이트 스트림으로 전송된다. 따라서 응용 프로그램 루프를 도는 동안에 들어온 바이트 스트림을 일련의 사건 구조체로 형변환해야 한다.
사건 구조체 정의는 /usr/include/sys/inotify.h에서 C 구조체 형식으로 정의되어 있다.Listing 2를 참조하자.
Listing 2. 사건 구조체 정의
struct inotify_event
{
int wd; /* 워치 기술자 */
uint32_t mask; /* 워치 마스크 */
uint32_t cookie; /* 두 사건을 하나로 묶는 쿠키 */
uint32_t len; /* name 필드에 들어있는 파일 이름 길이 */
char name __flexarr; /* 파일 이름, NUL로 끝난다 */
}
|
wd 필드는 사건과 관련된 워치를 가리킨다. inotify 인스턴스당 워치를 두 개 설정하면, 향후 처리 진행 방법을 결정하는 과정에 이 필드를 사용한다. mask 필드는 어떤 일이 벌어질지를 명세하는 비트 집합이다. 각 비트를 독립적으로 검사하자.
파일이 어떤 디렉터리에서 다른 디렉터리로 옮겨지는 경우에 cookie를 활용해 두 사건을 하나로 묶을 수 있다. 출발지와 목적지 디렉터리를 감시해야 inofity는 이동 사건 두 가지를 생성한다. 여기서 하나는 출발지이며 하나는 목적지며, cookie를 설정하는 방식으로 두 개를 하나로 묶는다. 이동을 감시하려면 IN_MOVED_FROM과 IN_MOVED_TO를 명세하거나 양쪽 다 감시하는 단축 상수인 IN_MOVE를 명세한다. 사건 유형을 검사하려면 IN_MOVED_FROM과 IN_MOVED_TO를 사용하자.
마지막으로 name과 len은 (경로를 제외한) 파일 이름과 영향을 받는 파일 이름 길이를 포함한다.
예제 응용 코드 빌드
코드를 빌드하려면, 디렉터리를 홈으로 옮겨 파일에 코드를 저장하고 C 컴파일러를 호출한다. 대다수 리눅스 시스템에는 gcc가 설치되어 있다. 그러고 나서 Listing 3처럼 실행 파일을 수행한다.
Listing 3. 실행 파일 수행
% cc -o watcher watcher.c
% ./watcher
|
프로그램을 실행하고 나서 다른 터미널 창을 연 다음에 Listing 4처럼 홈 디렉터리 내용을 touch, cat, rm으로 변경해보자. 실험할 때마다 응용 프로그램을 새로 띄우자.
Listing 4. touch, cat, rm 테스트
% cd $HOME
% touch a b c
The file a was created.
The file b was created.
The file c was created.
% ./watcher &
% rm a b c
The file a was deleted.
The file b was deleted.
The file c was deleted.
% ./watcher &
% touch a b c
The file a was created.
The file b was created.
The file c was created.
% ./watcher &
% cat /etc/passwd >> a
The file a was modified.
% ./watcher &
% mkdir d
The directory d was created.
|
다른 워치 플래그로 실험해보자. 권한 변경을 잡아내려면 IN_ATTRIB을 마스크에 붙인다.
inotify 활용 팁
차단을 피하기 위해 select(),pselect(), poll(), epoll()로 실험할 수 있다. 주 사건 처리 루프를 도는 GUI의 일부로 워치를 감시하기를 원하거나 데몬의 일부로 들어오는 연결을 감시하려면 이런 기능이 특히 유용하다. 단순히 inotify 기술자를 병렬로 감시하려는 기술자 집합에 추가하면 된다. Listing 5는 select()를 구현하는 전형적인 예를 보여준다.
Listing 5. select()를 활용하는 전형적인 예
int return_value;
fd_set descriptors;
struct timeval time_to_wait;
FD_ZERO ( &descriptors );
FD_SET( ..., &descriptors );
FD_SET ( fd, &descriptors );
...
time_to_wait.tv_sec = 3;
time.to_waittv_usec = 0;
return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait);
if ( return_value < 0 ) {
/* 오류 */
}
else if ( ! return_value ) {
/* 타임아웃 */
}
else if ( FD_ISSET ( fd, &descriptors ) ) {
/* inotify 사건 처리 */
...
}
else if ...
|
select() 메서드는 time_to_wait 초 동안 프로그램을 멈춘다. 하지만 멈춘 동안에 설정이 끝난 파일 기술자 중 하나에 활동이 일어나면, 수행은 즉시 복귀된다. 그렇지 않고 호출 시간이 지나면 응용이 GUI 도구에서 마우스, 키보드 사건에 반응하도록 다른 작업을 허용한다.
몇 가지 다른 inotify 관련 팁을 정리했다.
- 관찰 대상 파일이나 디렉터리가 삭제되면, (등록되었다면 삭제 이벤트가 전송된 다음에) 자동으로 워치가 삭제된다.
- 마운트되지 않은 파일 시스템에 존재하는 파일이나 디렉터리를 감시하려면, 워치는 모든 영향을 받는 워치를 삭제되기 전에 unmount 사건을 받는다.
- 일회용 경고를 설정하려면 워치 마스크에
IN_ONESHOT 플래그를 추가한다. 경고를 한번 보내고 나면 삭제된다.
- 사건을 변경하려면, 동일한 경로에 다른 마스크를 제공한다. 새로운 워치는 예전 워치를 대신한다.
- 실용적인 관점에서, 특정 inotify 인스턴스에 워치를 과도하게 추가할 가능성은 희박하다. 하지만 종종 사건 처리 방법에 따라 사건 큐에 자리가 부족한 경우가 있다. 큐 넘침은
IN_Q_OVERFLOW 사건을 발생시킨다.
close() 메서드는 inotify 인스턴스와 모든 관련된 워치를 제거하고 큐에 쌓여서 기다리는 모든 사건을 비운다.
inotify-tools 설치하기
inotify 프로그래밍 인터페이스는 사용하기 쉽지만 독자적인 도구를 작성하지 않을 경우에도 오픈 소스 공동체가 멋지고 유연한 대안을 제공한다. inotify-tools 라이브러리(아래 참고자료 링크 참조)는 파일 시스템 활동 내역을 감시하는 데 명령행 유틸리티 쌍을 제공한다.
inotifywait: inotify 사건을 기다리려고 단순히 차단 상태에 들어간다. 디렉터리와 파일 집합은 물론이고 전체 디렉터리 트리(디렉터리, 하위 디렉터리, 하위-하위 디렉터리 등)를 감시할 수도 있다. inotifywait를 셸 스크립트에서 활용하자.
inotifywatch는 각 inotify 사건이 얼마나 많이 일어났는지를 포함해서 감시 중인 파일 시스템에 대한 통계를 수집한다.
이 기사를 작성할 무렵에, inotify-tools 최신 버전은 2008년 1월에 출시된 3.13이었다. inotify-tools 설치에는 두 가지 방법이 있다. 직접 내려받아 빌드하는 방법과 배포판 저장소가 inotify-tools를 포함한다면 리눅스 배포판 패키지 관리자를 사용해서 이진 파일을 설치하는 방법이다. 데비안 기반 배포판에서 이진 파일을 설치하려면 Listing 6처럼 apt-cache search inotify 명령을 돌린 다음에, 일치하는 도구를 찾는다. 이 기사를 쓰기 위해 사용한 예제 시스템인 우분투 데스크톱 버전 8.04에서 이 도구는 사용 가능한 상태였다.
Listing 6. inotify-tools 탐색
% apt-cache search inotify
incron - cron-like daemon which handles filesystem events
inotail - tail replacement using inotify
inoticoming - trigger actions when files hit an incoming directory
inotify-tools - command-line programs providing a simple interface to inotify
iwatch - realtime filesystem monitoring program using inotify
libinotify-ruby - Ruby interface to Linux's inotify system
libinotify-ruby1.8 - Ruby interface to Linux's inotify system
libinotify-ruby1.9 - Ruby interface to Linux's inotify system
libinotifytools0 - utility wrapper around inotify
libinotifytools0-dev - Development library and header files for libinotifytools0
liblinux-inotify2-perl - scalable directory/file change notification
muine-plugin-inotify - INotify Plugin for the Muine music player
python-kaa-base - Base Kaa Framework for all Kaa Modules
python-pyinotify - Simple Linux inotify Python bindings
python-pyinotify-doc - Simple Linux inotify Python bindings
% sudo apt-get install inotify-tools
...
Setting up inotify-tools.
|
하지만 코드 빌드도 쉽다. Listing 7에 나와 있듯이 원시 코드를 내려받아 압축을 풀고 configure 명령에 이어 빌드와 설치를 진행한다. 전체 과정은 3분 정도 걸린다.
Listing 7. 코드 빌드하기
% wget \
http://internap.dl.sourceforge.net/sourceforge/inotify-tools/inotify-tools-3.13.tar.gz
% tar zxvf inotify-tools-3.13.tar.gz
inotify-tools-3.13/
inotify-tools-3.13/missing
inotify-tools-3.13/src/
inotify-tools-3.13/src/Makefile.in
...
inotify-tools-3.13/ltmain.sh
% cd inotify-tools.3.13
% ./configure
% make
% make install
|
이제 도구를 활용할 시점이다. 예로, 전체 홈 디렉터리 변경을 감시하고 싶다면 inotifywait 명령을 내리자. 가장 간단한 실행 방법은 inotifywait -r -m으로 재귀적(-r)으로 감시하며 매 사건 이후에 유틸리티를 떠나도록(-m) 만든다.
% inotifywait -r -m $HOME
Watches established.
|
다른 터미널 창을 열어 홈 디렉터리에서 작업을 진행하자. 심지어 단순한 디렉터리 목록 보기 작업도 사건을 일으킨다.
특정 사건만 제한하려면 inotifywait 매뉴얼 페이지를 읽어서 옵션을 찾아본다(-e event_name 옵션은 반복적으로 목록을 생성해낸다). 재귀적인 워치에서 일정 패턴을 따르는 파일을 제외하는 방법도 찾아보자(--exclude pattern)
다른 inotify 도구
위에서 소개한 apt-cache로 찾아보면 도구 상자에 넣을 만한 inofity 기반 유틸리티가 몇 가지 더 있다. incron 유틸리티는 cron과 비슷하나 일정 대신 inotify 사건에 반응한다. inoticoming 유틸리티는 email/ftp 디렉터리 감시에 특화되어 있다. 펄, 루비, 파이썬 개발자라면, 자신이 사용하는 스크립트 언어에 맞춰 inotify를 호출하도록 지원하는 모듈과 라이브러리를 찾을 수 있다.
예를 들어, 펄 개발자는 Linux::Inotify2(세부 사항은 참고자료를 참조한다)로 펄 응용 내부에 inofity 기능을 내장할 수 있다. Linux::Inotify2 README 코드에서 가져온 예는 사건 감시를 위한 콜백 인터페이스를 보여준다. Listing 8을 살펴보자.
Listing 8. 사건 감시를 위한 콜백 인터페이스
use Linux::Inotify2;
my $inotify = new Linux::Inotify2
or die "Unable to create new inotify object: $!";
# 이벤트:
Event->io (fd =>$inotify->fileno, poll => 'r', cb => sub { $inotify->poll });
# Glib:
add_watch Glib::IO $inotify->fileno, in => sub { $inotify->poll };
# 수동:
1 while $inotify->poll;
# 워치 추가
$inotify->watch ("/etc/passwd", IN_ACCESS, sub {
my $e = shift;
my $name = $e->fullname;
print "$name was accessed\n" if $e->IN_ACCESS;
print "$name is no longer mounted\n" if $e->IN_UNMOUNT;
print "$name is gone\n" if $e->IN_IGNORED;
print "events for $name have been lost\n" if $e->IN_Q_OVERFLOW;
# 워치 취소: 더 이상 사건을 받지 않는다.
$e->w->cancel;
});
|
리눅스에서 모든 것이 파일이므로 inotify가 감시하는 사례는 무수히 많다.
그렇다면 다음과 같은 질문이 나온다. "워치는 누가 감시하지?"
참고자료 교육
제품 및 기술 얻기
토론
필자소개  | 
|  | Martin Streicher는 프리랜서 웹 개발자이며, 전직 Linux Magazine 편집장이었다. Martin은 퍼듀 대학교 석사 학위를 취득했고, 1986년 이래 유닉스 시스템에서 프로그램 작업을 해왔다. 예술 작품과 장남감 수집가다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|  |