기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

Inotify를 이용한 Linux 파일 시스템 모니터

기술자료
DBMS별 분류
DB일반
작성자
dataonair
작성일
2000-01-01 00:00
조회
4428



Inotify를 이용한 Linux 파일 시스템 모니터

Linux 2.6 커널에서 효과적인 파일 시스템 이벤트 모니터하기

Linux? 파일 시스템 이벤트를 효과적으로 세밀하게 비동기적으로 모니터해야 하는 경우에는 Inotify를 사용할 수 있습니다. Inotify를 사용하면 보안, 성능이나 기타 다른 목적으로 사용자 공간을 모니터할 수 있습니다.

Inotify가 Linux 커널에 최종적으로 통합되기 전에 이 기사의 초기 버전을 작성한 IBM의 Eli Dow에게 감사드린다. 특히, 다운로드 섹션에 있는 샘플 코드는 여전히 Eli의 원본 샘플 코드를 기반으로 작성되었다.

Inotify 소개

파일 시스템 이벤트 모니터링은 파일 관리자에서 보안 도구에 이르는 다양한 유형의 프로그램에 필수적인 기능이다. Linux에는 Linux 2.6.13 커널부터 Inotify가 포함되었으며 이 도구를 이용하면 모니터링 프로그램에서 하나의 파일 디스크립터를 열어서 하나 이상의 파일이나 디렉토리에서 열기, 닫기, 이동/이름 변경, 삭제, 작성이나 속성 변경과 같은 지정된 이벤트가 발생하는지 감시할 수 있다. 이후 커널에서는 몇 가지 기능이 개선되었으므로 이러한 기능을 사용하기 전에 커널 레벨을 확인한다.

이 기사에서는 간단한 모니터링 애플리케이션에서 Inotify 기능을 사용하는 방법을 살펴본다. 샘플 코드를 다운로드한 다음, 진행을 위해 해당 시스템에서 컴파일한다.

간단한 히스토리

Inotify가 있기 전에는 dnotify가 있었다. 불행히도 dnotify에는 제한사항이 있었으며 이 때문에 사용자들은 좀 더 기능이 개선되기를 원했다. Inotify의 장점은 다음과 같다.

-Inotify는 하나의 파일 디스크립터를 사용하는 데 비해 dnotify에서는 변화를 감시할 각 디렉토리마다 하나의 파일 디스크립터를 열어야 한다. 이렇게 되면 한 번에 여러 개의 디렉토리를 모니터할 경우, 자원의 손실이 커지게 되며 프로세스당 파일 디스크립터 제한에 걸릴 수도 있다.
-Inotify에서 사용하는 파일 디스크립터는 시스템 호출을 사용하여 얻게 되며 연관된 장치나 파일이 없다. dnotify를 사용하면 파일 디스크립터가 디렉토리에 고정되고 지원 장치가 마운트 해제되지 않게 되어 이동식 매체에 특수한 문제를 일으킨다. Inotify를 사용하는 경우에는 마운트 해제된 파일 시스템에 있는 감시 파일이나 디렉토리에서 이벤트가 생성되면 감시가 자동으로 제거된다.
-Inotify는 파일이나 디렉토리를 감시할 수 있다. Dnotify는 디렉토리를 모니터하며 따라서 프로그래머는 감시할 디렉토리에 있는 파일이 반영된 동등한 데이터 구조나 상태 구조를 유지한 다음, 이벤트가 발생한 이후에 현재의 상태와 비교하여 디렉토리에 있는 항목에서 발생한 변화를 확인해야 했다.
-위에서 설명한 바와 같이 Inotify는 파일 디스크립터를 사용하며 프로그래머는 표준 select 함수나 poll 함수를 사용하여 이벤트를 감시할 수 있다. 그 결과 효과적으로 I/O를 다중화하거나 Glib의 mainloop와 통합할 수 있다. 이와는 대조적으로 dnotify는 때로는 개발자들이 다소 확인하기 어려운 신호를 사용한다. 또한, 커널 2.6.25에서는 Signal-drive I.O 알림이 Inotify에 추가되었다.

Inotify API

Inotify는 최소한의 파일 디스크립터를 사용하면서 세밀한 모니터링을 할 수 있게 도움을 주는 간단한 API를 제공한다. Inotify와의 통신은 시스템 호출을 통해 설정된다. 사용 가능한 함수는 다음과 같다.

inotify_init
이 함수는 inotify 인스턴스를 작성하고 이 인스턴스가 참조하는 파일 디스크립터를 리턴하는 시스템 호출이다.

inotify_init1
이 함수는 inotify_init와 비슷하지만 추가 플래그가 있다. 플래그가 지정되지 않으면 이 함수는 inotify_init와 동일하게 작동한다.

inotify_add_watch
이 함수는 파일이나 디렉토리에 감시를 추가하며 감시할 이벤트를 지정한다. 기존 감시에 이벤트를 추가해야 하는지, 경로에 디렉토리가 표시되는 경우에만 감시를 수행해야 하는지, 기호 링크를 수행해야 하는지 그리고 감시가 첫 번째 이벤트가 발생하면 중지되어야 하는 일회성 감시인지를 플래그를 통해 제어한다.

inotify_rm_watch
이 함수는 감시 목록에서 감시 항목을 제거한다.

read
이 함수는 하나 이상의 이벤트에 관한 정보가 포함된 버퍼를 읽는다.

close
이 함수는 파일 디스크립터를 닫고 파일 디스크립터에 남아 있는 모든 감시를 제거한다. 인스턴스의 모든 파일 디스크립터가 닫히면 해당 자원과 기본 오브젝트가 해제되며 따라서 커널에서 이러한 자원과 오브젝트를 다시 사용할 수 있게 된다.

따라서 일반적인 모니터링 프로그램은 다음과 같은 작업을 수행한다.

1.inotify_init를 사용하여 파일 디스크립터를 연다.
2.하나 이상의 감시를 추가한다.
3.이벤트를 대기한다.
4.이벤트를 처리한 후 대기 상태로 돌아간다.
5.더 이상 활성화된 감시가 없거나 어떤 신호를 수신하는 경우에는 파일 디스크립터를 닫고 정리한 후 종료된다.

다음 섹션에서는 감시할 수 있는 이벤트와 샘플 프로그램에서 이 이벤트가 어떻게 작동하는지를 살펴본다. 마지막에는 이벤트 모니터링이 어떻게 작동하는지 확인한다.

알림

애플리케이션이 알림을 읽으면 하나 이상의 이벤트로 구성된 시퀀스가 사용자가 제공한 버퍼로 읽혀진다. Listing 1에 표시된 바와 같이 이벤트는 가변 길이 구조로 리턴된다. 모든 데이터가 버퍼에 채워지면 마지막 항목의 부분 이름이나 부분 이벤트 정보와 같은 사항을 처리해야 한다.

Listing 1. Inotify의 이벤트 구조

struct inotify_event
{
int wd; /* Watch descriptor. */
uint32_t mask; /* Watch mask. */
uint32_t cookie; /* Cookie to synchronize two events. */
uint32_t len; /* Length (including NULs) of name. */
char name __flexarr; /* Name. */
};

name 필드는 감시 항목이 디렉토리이고 이벤트가 디렉토리 자체가 아니라 디렉토리에 있는 항목에 해당하는 경우에만 표시된다. IN_MOVED_FROM 이벤트와 IN_MOVED_TO 이벤트가 감시할 항목과 관련된 경우에는 두 이벤트의 연관성을 확인하기 위해 쿠키를 사용한다. mask 필드에는 커널에 의해 설정되는 플래그와 함께 이벤트 유형이 리턴된다. 예를 들면, 이벤트 대상이 디렉토리인 경우에는 커널에서 IN_ISDIR 플래그를 설정한다.

감시할 수 있는 이벤트

여러 이벤트를 감시할 수 있다. IN_DELETE_SELF와 같은 것은 감시할 항목에만 적용하는 데 반해서 IN_ATTRIB나 IN_OPEN과 같은 것은 감시 항목이나 해당 항목이 디렉토리인 경우에는 디렉토리 안에 있는 디렉토리나 파일에 적용할 수 있다.

IN_ACCESS
감시 디렉토리에 있는 감시 항목이 액세스되었다. 예를 들면, 열린 파일이 읽힌 경우

IN_MODIFY
감시 디렉토리에 있는 감시 항목이 수정되었다. 예를 들면, 열린 파일이 업데이트된 경우

IN_ATTRIB
감시 디렉토리에 있는 감시 항목에서 메타데이터가 변경되었다. 예를 들면, 시간소인이나 사용 권한이 변경된 경우

IN_CLOSE_WRITE
쓰기 위해 연 파일이나 디렉토리가 닫혔다.

IN_CLOSE_NOWRITE
읽기 전용으로 연 파일이나 디렉토리가 닫혔다.

IN_CLOSE
이전의 두 가지 닫기 이벤트(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)를 논리적 OR 연산을 하는 편리한 마스크이다.

IN_OPEN
파일이나 디렉토리가 열렸다.

IN_MOVED_FROM
감시 디렉토리에 있는 감시 항목이 감시 위치에서 이동되었다. 또한, 이 이벤트에는 쿠키가 포함되어 있으며 사용자는 이 쿠키를 이용하여 IN_MOVED_FROM와 IN_MOVED_TO의 연관성을 확인할 수 있다.

IN_MOVED_TO
파일이나 디렉토리가 감시 위치에서 이동되었다. 이 이벤트에는 IN_MOVED_FROM과의 연관성을 확인할 수 있는 쿠키가 포함되어 있다. 파일이나 디렉토리의 이름이 변경되면 두 가지 이벤트가 모두 표시된다. 파일이나 디렉토리가 감시하고 있지 않은 위치로 이동하거나 이 위치에서 이동되는 경우에는 한 가지 이벤트만 표시된다. 사용자가 감시 항목을 이동하거나 이름을 변경하는 경우에도 감시는 계속된다. 아래에 있는 IN_MOVE-SELF를 살펴보자.

IN_MOVE
이전의 두 가지 이동 이벤트(IN_MOVED_FROM | IN_MOVED_TO)를 논리적 OR 연산을 하는 편리한 마스크이다.

IN_CREATE
서브디렉토리나 파일이 감시 디렉토리에서 작성되었다.

IN_DELETE
서브디렉토리나 파일이 감시 디렉토리에서 삭제되었다.

IN_DELETE_SELF
감시 항목 자체가 삭제되었다. 감시가 종료되고 IN_IGNORED 이벤트를 수신하게 된다.

IN_MOVE_SELF
감시 항목 자체가 이동되었다.

이벤트 플래그 외에도 사용자가 Inotify 헤더(/usr/include/sys/inotify.h)에서 찾을 수 있는 몇 가지 다른 플래그가 있다. 예를 들어, 첫 번째 이벤트만 감시하려고 하는 경우에는 감시를 추가할 때 IN_ONESHOT 플래그를 설정한다.

간단한 Inotify 애플리케이션

샘플 애플리케이션(다운로드 섹션 참조)은 위에 있는 일반적인 로직을 따른다. 여기에서는 신호 핸들러를 사용하여 ctrl-c(SIGINT)를 확인한 다음, 애플리케이션이 종료되는 것을 알 수 있도록 플래그(keep_running)를 다시 설정한다. 사실상, Inotify 호출은 유틸리티 루틴에서 수행된다. 또한, 기본 Inotify 오브젝트에서 이벤트를 지운 다음 나중에 처리할 수 있도록 큐를 작성한다. 실제 애플리케이션에서는 큐를 사용하여 이벤트를 처리하기보다는 다양한 스레드(우선순위가 높은 스레드)에서 이벤트가 처리되기를 원할 수도 있다. 이 애플리케이션에서는 단순히 일반적인 원칙을 설명한다. 여기에서는 이벤트로 구성된 매우 간단한 연결 목록을 사용하며 여기서 각 큐 항목은 원본 이벤트와 큐에 있는 다음 이벤트를 가리키는 포인터를 위한 공간으로 구성된다.

기본 프로그램

신호 핸들러와 기본 루틴은 Listing 2에 표시되어 있다. 이 간단한 예제에서는 명령행에서 전달되는 각 파일이나 디렉토리에 감시를 설정하고 이벤트 마스크 IN_ALL_EVENTS를 사용하여 각 파일이나 디렉토리에 대한 모든 이벤트를 감시한다. 실제 애플리케이션에서는 파일 및 디렉토리 작성 이벤트나 삭제 이벤트만을 추적하고자 할 수 있으며 이런 경우에는 속성 변경뿐만 아니라 열기와 닫기 이벤트 마스크를 제거할 수 있다. 파일이나 디렉토리가 이동되거나 이름이 변경되는 것을 추적할 필요가 없는 경우에도 다양한 이동 이벤트 마스크를 제거할 수 있다. 더 자세한 사항은 Inotify man 페이지를 참조한다.

Listing 2. inotify-test.c의 샘플 기본 루틴

/* Signal handler that simply resets a flag to cause termination */
void signal_handler (int signum)
{
keep_running = 0;
}

int main (int argc, char **argv)
{
/* This is the file descriptor for the inotify watch */
int inotify_fd;

keep_running = 1;

/* Set a ctrl-c signal handler */
if (signal (SIGINT, signal_handler) == SIG_IGN)
{
/* Reset to SIG_IGN (ignore) if that was the prior state */
signal (SIGINT, SIG_IGN);
}

/* First we open the inotify dev entry */
inotify_fd = open_inotify_fd ();
if (inotify_fd > 0)
{

/* We will need a place to enqueue inotify events,
this is needed because if you do not read events
fast enough, you will miss them. This queue is
probably too small if you are monitoring something
like a directory with a lot of files and the directory
is deleted.
*/
queue_t q;
q = queue_create (128);

/* This is the watch descriptor returned for each item we are
watching. A real application might keep these for some use
in the application. This sample only makes sure that none of
the watch descriptors is less than 0.
*/
int wd;


/* Watch all events (IN_ALL_EVENTS) for the directories and
files passed in as arguments.
Read the article for why you might want to alter this for
more efficient inotify use in your app.
*/
int index;
wd = 0;
printf("\n");
for (index = 1; (index < argc) && (wd >= 0); index++)
{
wd = watch_dir (inotify_fd, argv[index], IN_ALL_EVENTS);
}

if (wd > 0)
{
/* Wait for events and process them until a
termination condition is detected
*/
process_inotify_events (q, inotify_fd);
}
printf ("\nTerminating\n");

/* Finish up by closing the fd, destroying the queue,
and returning a proper code
*/
close_inotify_fd (inotify_fd);
queue_destroy (q);
}
return 0;
}

inotify_init를 사용하여 파일 디스크립터 열기

Listing 3에는 Inotify 인스턴스를 작성하고 이 인스턴스에 해당하는 파일 디스크립터를 가져오는 데 필요한 간단한 유틸리티 함수가 있다. 이 파일 디스크립터는 호출자에게 리턴된다. 오류가 발생하는 경우에는 리턴값이 음수가 된다.

Listing 3. inotify_init 사용하기

/* Create an inotify instance and open a file descriptor
to access it */
int open_inotify_fd ()
{
int fd;

watched_items = 0;
fd = inotify_init ();

if (fd < 0)
{
perror ("inotify_init () = ");
}
return fd;
}

inotify_add_watch를 사용하여 감시 추가하기

Inotify 인스턴스에 해당하는 파일 디스크립터가 있으면 하나 이상의 감시를 추가할 필요가 있다. 해당 마스크를 사용하여 감시할 특정 이벤트를 설정한다. 예제에서는 사용 가능한 모든 이벤트를 감시하는 IN_ALL_EVENTS 마스크를 사용한다.

Listing 4. inotify_add_watch 사용하기

int watch_dir (int fd, const char *dirname, unsigned long mask)
{
int wd;
wd = inotify_add_watch (fd, dirname, mask);
if (wd < 0)
{
printf ("Cannot add watch for \%s\" with event mask %lX""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""