C언어에서 getrusage 시스템 콜로 리소스 사용량 확인하기

C언어에서 시스템 자원의 효율적인 관리는 성능 최적화의 핵심입니다. 특히, CPU 시간이나 메모리 사용량과 같은 자원 정보를 확인하는 것은 중요합니다. getrusage 시스템 콜은 이러한 데이터를 실시간으로 수집할 수 있는 강력한 도구로, 프로세스와 스레드의 리소스 사용량을 상세히 추적할 수 있습니다. 본 기사에서는 getrusage의 기본 개념부터 실제 활용 방법까지 알아보겠습니다.

목차

`getrusage` 시스템 콜의 개요


getrusage는 C언어에서 제공되는 시스템 콜로, 프로세스와 스레드의 리소스 사용량을 조회할 수 있습니다. 이 함수는 실행된 프로세스 또는 해당 프로세스의 자식 프로세스가 소비한 CPU 시간, 메모리 사용량, 페이지 폴트, 입력/출력 등 다양한 정보를 반환합니다.

리눅스 및 유닉스 계열 시스템에서 흔히 사용되며, 성능 분석이나 리소스 최적화에 유용합니다. 개발자는 이를 활용해 특정 코드 블록의 리소스 소비를 측정하거나 애플리케이션의 병목 지점을 식별할 수 있습니다.

다음 섹션에서는 getrusage가 반환하는 구조체와 제공하는 주요 정보를 더 자세히 살펴보겠습니다.

`getrusage`의 주요 기능과 구조체

getrusage는 프로세스와 스레드의 리소스 사용 정보를 반환하며, 결과는 struct rusage 구조체에 저장됩니다. 이 구조체는 다양한 필드를 포함하고 있어 시스템 자원 사용량을 상세히 추적할 수 있습니다.

주요 기능

  • CPU 사용량 추적: 사용자 모드와 커널 모드에서 소비된 CPU 시간을 제공합니다.
  • 메모리 사용량 확인: 메모리 페이지 폴트 및 스왑 사용 횟수를 확인할 수 있습니다.
  • 입출력 정보 제공: 파일 시스템의 입력/출력 동작 횟수를 기록합니다.

`struct rusage` 구조체


getrusage의 반환 값은 다음과 같은 필드를 포함한 struct rusage입니다:

struct rusage {
    struct timeval ru_utime; /* 사용자 모드에서 소비된 CPU 시간 */
    struct timeval ru_stime; /* 시스템 모드에서 소비된 CPU 시간 */
    long ru_maxrss;          /* 최대 메모리 사용량 (KB 단위) */
    long ru_ixrss;           /* 공유 메모리 사용량 */
    long ru_idrss;           /* 비공유 메모리 사용량 */
    long ru_isrss;           /* 스택 메모리 사용량 */
    long ru_minflt;          /* 소프트 페이지 폴트 횟수 */
    long ru_majflt;          /* 하드 페이지 폴트 횟수 */
    long ru_nswap;           /* 스왑 동작 횟수 */
    long ru_inblock;         /* 입력 블록 횟수 */
    long ru_oublock;         /* 출력 블록 횟수 */
    long ru_msgsnd;          /* 메시지 전송 횟수 */
    long ru_msgrcv;          /* 메시지 수신 횟수 */
    long ru_nsignals;        /* 수신된 시그널 횟수 */
    long ru_nvcsw;           /* 자발적 컨텍스트 스위칭 횟수 */
    long ru_nivcsw;          /* 비자발적 컨텍스트 스위칭 횟수 */
};

주요 필드 설명

  • ru_utimeru_stime: 사용자와 시스템 모드에서 소요된 CPU 시간.
  • ru_maxrss: 최대 메모리 사용량.
  • ru_minfltru_majflt: 페이지 폴트 수로 메모리 관리 상태를 나타냅니다.
  • ru_nvcswru_nivcsw: 컨텍스트 스위칭 횟수를 기록하여 스레드 간 전환 빈도를 파악할 수 있습니다.

이 구조체는 getrusage 호출 시 상세한 리소스 사용 데이터를 제공합니다. 다음 섹션에서는 이를 활용하는 코드 예제를 다뤄보겠습니다.

`getrusage` 호출 방법

getrusage를 사용하려면 sys/time.hsys/resource.h 헤더 파일을 포함해야 합니다. 함수는 호출 대상과 결과를 저장할 struct rusage 포인터를 매개변수로 받습니다.

다음은 기본적인 getrusage 호출 방법입니다:

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>

int main() {
    struct rusage usage;

    // 현재 프로세스의 리소스 사용량 가져오기
    if (getrusage(RUSAGE_SELF, &usage) == 0) {
        printf("User CPU time: %ld.%06ld seconds\n",
               usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
        printf("System CPU time: %ld.%06ld seconds\n",
               usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
        printf("Maximum resident set size: %ld KB\n", usage.ru_maxrss);
        printf("Page faults (no I/O): %ld\n", usage.ru_minflt);
        printf("Page faults (with I/O): %ld\n", usage.ru_majflt);
    } else {
        perror("getrusage failed");
    }

    return 0;
}

주요 매개변수

  • who: 리소스 정보를 조회할 대상.
  • RUSAGE_SELF: 호출한 프로세스의 리소스 사용량.
  • RUSAGE_CHILDREN: 자식 프로세스의 리소스 사용량.
  • 특정 스레드에 대한 정보를 얻으려면 플랫폼에 따라 추가 옵션이 제공될 수 있습니다(예: RUSAGE_THREAD).
  • usage: 결과를 저장할 struct rusage 포인터.

실행 결과 예시


위 코드를 실행하면 다음과 같은 출력이 나타날 수 있습니다:

User CPU time: 0.000123 seconds  
System CPU time: 0.000045 seconds  
Maximum resident set size: 2048 KB  
Page faults (no I/O): 12  
Page faults (with I/O): 0  

이 예제는 현재 프로세스의 리소스 사용량을 출력합니다. 이후 섹션에서는 rusage 구조체의 주요 필드와 의미를 더 자세히 다루겠습니다.

자주 사용하는 필드

getrusage 시스템 콜의 결과로 반환되는 struct rusage에는 다양한 정보가 포함됩니다. 여기서는 소프트웨어 개발에서 특히 자주 사용하는 주요 필드를 살펴봅니다.

CPU 사용 시간

  • ru_utime: 사용자 모드에서 소비된 CPU 시간입니다. 프로세스가 사용자 코드 실행에 사용한 시간을 나타냅니다.
  • ru_stime: 시스템 모드에서 소비된 CPU 시간입니다. 운영 체제가 프로세스 요청을 처리하는 데 사용한 시간을 나타냅니다.

예:

printf("User CPU time: %ld.%06ld seconds\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("System CPU time: %ld.%06ld seconds\n", usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);

메모리 사용량

  • ru_maxrss: 프로세스가 사용한 최대 메모리 크기(킬로바이트 단위).
  • ru_minflt: 소프트 페이지 폴트 횟수로, 필요한 메모리를 디스크에서 읽지 않고 해결한 경우를 나타냅니다.
  • ru_majflt: 하드 페이지 폴트 횟수로, 필요한 메모리를 디스크에서 읽어야 했던 경우를 나타냅니다.

메모리 관련 예:

printf("Maximum resident set size: %ld KB\n", usage.ru_maxrss);
printf("Minor page faults: %ld\n", usage.ru_minflt);
printf("Major page faults: %ld\n", usage.ru_majflt);

I/O 동작

  • ru_inblock: 파일 읽기와 같은 블록 입력 동작의 횟수.
  • ru_oublock: 파일 쓰기와 같은 블록 출력 동작의 횟수.

예:

printf("Input operations: %ld\n", usage.ru_inblock);
printf("Output operations: %ld\n", usage.ru_oublock);

컨텍스트 스위칭

  • ru_nvcsw: 자발적인 컨텍스트 스위칭 횟수. 프로세스가 CPU 사용을 스스로 포기한 경우입니다.
  • ru_nivcsw: 비자발적인 컨텍스트 스위칭 횟수. 다른 프로세스가 CPU를 강제로 차지한 경우입니다.

컨텍스트 스위칭 예:

printf("Voluntary context switches: %ld\n", usage.ru_nvcsw);
printf("Involuntary context switches: %ld\n", usage.ru_nivcsw);

활용 예시


위 필드들은 프로그램 성능을 측정하고 최적화할 때 중요한 데이터를 제공합니다. CPU 사용량을 기준으로 병목 지점을 찾거나 페이지 폴트 데이터를 분석해 메모리 관리를 개선할 수 있습니다. 다음 섹션에서는 이러한 데이터를 활용한 실제 사례를 살펴보겠습니다.

`getrusage` 활용 예시

getrusage는 리소스 사용량을 측정하는 데 유용하며, 프로세스 성능 최적화 및 디버깅에 자주 활용됩니다. 아래는 다양한 실제 활용 사례를 소개합니다.

사례 1: 코드 실행 시간 측정


특정 코드 블록의 CPU 사용 시간을 측정하여 성능을 분석할 수 있습니다.

#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>

void heavy_computation() {
    for (volatile long i = 0; i < 100000000; i++); // 단순 반복 작업
}

int main() {
    struct rusage usage_start, usage_end;

    // 시작 시간 기록
    getrusage(RUSAGE_SELF, &usage_start);
    heavy_computation(); // 측정 대상 작업
    // 종료 시간 기록
    getrusage(RUSAGE_SELF, &usage_end);

    // CPU 시간 계산
    long sec = usage_end.ru_utime.tv_sec - usage_start.ru_utime.tv_sec;
    long usec = usage_end.ru_utime.tv_usec - usage_start.ru_utime.tv_usec;
    if (usec < 0) {
        sec -= 1;
        usec += 1000000;
    }
    printf("Execution time: %ld.%06ld seconds\n", sec, usec);

    return 0;
}

출력 예시:

Execution time: 0.456789 seconds

사례 2: 메모리 사용량 모니터링


최대 메모리 사용량(ru_maxrss)을 모니터링하여 프로그램의 메모리 효율성을 평가할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>

int main() {
    struct rusage usage;

    // 대규모 메모리 할당
    int *arr = (int *)malloc(1000000 * sizeof(int));

    // 리소스 사용량 확인
    getrusage(RUSAGE_SELF, &usage);
    printf("Maximum resident set size: %ld KB\n", usage.ru_maxrss);

    free(arr);
    return 0;
}

출력 예시:

Maximum resident set size: 10240 KB

사례 3: 병렬 프로세스의 자원 추적


getrusageRUSAGE_CHILDREN 매개변수를 사용하여 자식 프로세스의 리소스 사용량을 추적할 수 있습니다.

#include <stdio.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 자식 프로세스 작업
        for (volatile long i = 0; i < 50000000; i++);
        return 0;
    } else {
        wait(NULL); // 자식 프로세스 종료 대기
        struct rusage usage;
        getrusage(RUSAGE_CHILDREN, &usage);
        printf("Child process CPU time: %ld.%06ld seconds\n",
               usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
    }
    return 0;
}

출력 예시:

Child process CPU time: 0.234567 seconds

사례 4: 컨텍스트 스위칭 분석


자발적 및 비자발적 컨텍스트 스위칭 횟수를 추적하여 멀티스레드 프로그램의 성능을 평가할 수 있습니다.

printf("Voluntary context switches: %ld\n", usage.ru_nvcsw);
printf("Involuntary context switches: %ld\n", usage.ru_nivcsw);

이와 같은 활용을 통해 getrusage는 소프트웨어 성능 개선과 효율적인 자원 관리에 기여할 수 있습니다. 다음 섹션에서는 이 함수 사용 시 발생할 수 있는 문제와 해결 방안을 다룹니다.

잠재적인 문제와 해결책

getrusage를 사용할 때 몇 가지 주의해야 할 문제와 이를 해결하기 위한 방안을 살펴보겠습니다.

문제 1: `getrusage` 지원 플랫폼의 제한

  • 문제: getrusage는 주로 리눅스 및 유닉스 계열 시스템에서 지원됩니다. 일부 운영 체제나 환경(특히 Windows)에서는 사용할 수 없습니다.
  • 해결책:
  • 플랫폼 독립적인 코드를 작성하려면 컴파일러나 플랫폼을 확인하는 조건부 컴파일을 사용합니다.
  • Windows 환경에서는 대체 도구(예: GetProcessTimes)를 사용해 유사한 정보를 가져옵니다.

예:

#ifdef _WIN32
    // Windows에서 대체 함수 사용
#else
    getrusage(RUSAGE_SELF, &usage);
#endif

문제 2: 값이 정확하지 않거나 업데이트되지 않음

  • 문제: 일부 시스템에서 getrusage 값이 업데이트되지 않거나 정확하지 않을 수 있습니다. 특히 빠른 연속 호출 시 의미 있는 데이터를 얻기 어렵습니다.
  • 해결책:
  • 충분한 시간 간격을 두고 호출하여 정확한 데이터를 수집합니다.
  • 필요에 따라 다른 시스템 콜(예: clock_gettime)과 함께 사용하여 보완합니다.

문제 3: 구조체 필드의 단위 차이

  • 문제: ru_utime, ru_stime는 초와 마이크로초로 구성된 timeval 구조체로 반환되며, ru_maxrss는 킬로바이트 단위로 제공됩니다. 이러한 단위 차이는 데이터 해석에 혼란을 줄 수 있습니다.
  • 해결책:
  • 반환된 값을 명확히 변환하여 동일한 단위로 통일합니다. 예를 들어, CPU 시간을 밀리초 단위로 변환하거나 메모리 크기를 메가바이트로 변환합니다.

예:

double user_time_ms = usage.ru_utime.tv_sec * 1000.0 + usage.ru_utime.tv_usec / 1000.0;
printf("User CPU time: %.2f ms\n", user_time_ms);

문제 4: 시스템 호출 비용

  • 문제: getrusage는 시스템 콜로 호출마다 커널 모드로 전환되므로 호출 비용이 있습니다. 빈번한 호출은 성능 저하를 초래할 수 있습니다.
  • 해결책:
  • 최소한의 호출로 필요한 데이터를 수집합니다.
  • 데이터를 캐싱하여 이후 호출 횟수를 줄입니다.

문제 5: 자식 프로세스 리소스 사용량 측정 실패

  • 문제: 자식 프로세스가 종료되기 전에 RUSAGE_CHILDREN으로 getrusage를 호출하면 자식 프로세스의 리소스 사용량이 완전히 반영되지 않을 수 있습니다.
  • 해결책:
  • 자식 프로세스가 종료된 후 wait 함수와 함께 getrusage를 호출합니다.

요약


getrusage를 효과적으로 사용하려면 위와 같은 문제를 이해하고 적절한 대처 방안을 적용해야 합니다. 이를 통해 보다 신뢰할 수 있는 데이터를 수집하고, 프로그램 성능을 개선할 수 있습니다.

요약

getrusage는 C언어에서 프로세스와 스레드의 리소스 사용량을 추적하는 강력한 도구입니다. CPU 시간, 메모리 사용량, 페이지 폴트, I/O 동작, 컨텍스트 스위칭 등 다양한 정보를 제공하며, 성능 분석과 최적화에 유용하게 활용됩니다.

효과적인 사용을 위해 플랫폼 지원, 호출 비용, 데이터 정확성 문제를 고려해야 하며, 적절한 대처 방안을 통해 신뢰도 높은 데이터를 얻을 수 있습니다. getrusage를 활용하면 애플리케이션의 병목 지점을 파악하고 시스템 자원을 효율적으로 관리할 수 있습니다.

목차