리눅스 커널의 이해(5): 디바이스에 쓰기 동작에 대한 구체적인 작성 예  
등록: 한빛미디어(주) (2005-06-22 15:57:51)

저자: 서민우
출처: Embedded World

[ 관련 기사 ]
리눅스 커널의 이해(1) : 커널의 일반적인 역할과 동작
리눅스 커널의 이해(2): 리눅스 커널의 동작
리눅스 커널의 이해(3): 리눅스 디바이스 작성시 동기화 문제
리눅스 커널의 이해(4): Uni-Processor & Multi-Processor 환경에서의 동기화 문제

이 번 기사에서는 [디바이스에 쓰기 동작]에 대한 구체적인 작성 예를 살펴보고, 동기화 문제에 대한 처리를 적절히 해 주지 않을 경우 어떤 문제가 발생하는지 보기로 하자. 또한 지난 기사에서 살펴 보았던 동기화 문제에 대한 해결책을 이용하여 발생하는 문제점을 해결해 보기로 하자.

다음은 [디바이스에 쓰기 동작]을 중심으로 작성한 리눅스 디바이스 드라이버의 한 예다. 여기서는 독자가 모듈 형태의 리눅스 디바이스 드라이버를 작성할 줄 알고, 동적으로 리눅스 커널에 모듈을 삽입할 줄 안다고 가정한다.


# vi devwrite.c
#include
#include

#include
#include
#include

ssize_t dev_write(struct file * filp, const char * buffer,
size_t length, loff_t * offset);

struct file_operations dev_fops = {
write:   dev_write,
};

static int major = 0;
int init_module()
{
        printk("Loading devwrite module\n");
        major = register_chrdev(0, "devwrite", &dev_fops);
        if(major < 0) return major;
        return 0;
}

void cleanup_module()
{
        unregister_chrdev(major, "devwrite");
        printk("Unloading devwrite module\n");
}

#define SLOT_NUM    8

char dev_buffer;
int dev_key = 1;

char data_slot[SLOT_NUM];
int full_slot_num = 0;
int empty_slot_num = SLOT_NUM;
int full_slot_pos = 0;
int empty_slot_pos = 0;

void dev_working();

ssize_t dev_write(struct file * filp, const char * buffer,
size_t length, loff_t * offset)
{
        char user_buffer;

        if(length != 1) return -1;

        copy_from_user(&user_buffer, buffer, 1);

        if(dev_key == 0) {                                                   // ①
                if(empty_slot_num <= 0) return -1;                    // ④ start
                empty_slot_num --;

                data_slot[empty_slot_pos] = user_buffer;
                empty_slot_pos ++;
                if(empty_slot_pos == SLOT_NUM)
empty_slot_pos = 0;

                full_slot_num ++;                                             // ④ end
                return 1;

        }
        dev_key = 0;                                                           // ②
        dev_buffer = user_buffer;                                          // ③

        dev_working();                                                       // ⑤

        return 1;
}

static struct timer_list dev_interrupt;
void dev_interrupt_handler(unsigned long dataptr);

void dev_working()
{
        init_timer(&dev_interrupt);                                        // ⑨

        dev_interrupt.function = dev_interrupt_handler;            // ⑦
        dev_interrupt.data = (unsigned long)NULL;
        dev_interrupt.expires = jiffies + 1;                              // ⑥

        add_timer(&dev_interrupt);                                       // ⑧
}

void dev_interrupt_handler(unsigned long dataptr)
{
        printk("%c\n", dev_buffer);
       
        if(full_slot_num <= 0) {dev_key = 1; return;}                // ⑩
        full_slot_num --;                                                      // ⑪ start  

        dev_buffer = data_slot[full_slot_pos];
        full_slot_pos ++;
        if(full_slot_pos == SLOT_NUM) full_slot_pos = 0;

        empty_slot_num ++;                                                  // ⑪ end

        dev_working();

        return;
}


그러면 동기화 문제와 관련한 부분을 중심으로 소스를 살펴 보자.

dev_write 함수는 write 시스템 콜 함수에 의해 시스템 콜 루틴 내부에서 수행된다. dev_write 함수에서 ①, ②, ③ 부분은 논리적으로 다음과 같다.


디바이스를 사용하고 있지 않으면
    디바이스를 사용한다고 표시하고
    데이터를 디바이스 버퍼에 쓰고 나간다


③ 부분에서 dev_buffer 변수는 가상 디바이스의 버퍼를 나타낸다. 그리고 ①과 ② 부분에서 사용한 dev_key 변수는 가상 디바이스의 버퍼를 하나 이상의 프로세스가 동시에 접근하지 못하게 하는 역할을 한다.

우리는 전월 호에서 이와 같은 루틴에서 발생하는 동기화 문제를 다음과 같이 처리할 수 있음을 보았다.


cli
디바이스를 사용하고 있지 않으면
    디바이스를 사용한다고 표시하고
    데이터를 디바이스 버퍼에 쓰고 나간다
    sti


리 눅스 커널에는 cli와 sti에 해당하는 local_irq_save와 local_irq_restore라는 매크로가 있다. 이 두 매크로를 이용하여 dev_write 함수의 ①, ②, ③ 부분에서 발생할 수 있는 동기화 문제를 다음과 같이 처리할 수 있다.


unsigned long flags;
local_irq_save(flags);
if(dev_key == 0) {

}
dev_key = 0;
dev_buffer = user_buffer;
local_irq_restore(flags);


여 기서 local_irq_save(flags) 매크로는 CPU 내에 있는 flag 레지스터를 flags 지역 변수에 저장한 다음에 인터럽트를 끄는 역할을 한다. local_irq_restore(flags) 매크로는 flags 지역 변수의 값을 CPU 내에 있는 flag 레지스터로 복구함으로써 인터럽트를 켜는 역할을 한다.

또 dev_write 함수에서 ①과 ④ 부분은 논리적으로 다음과 같다.


디바이스를 사용하고 있으면
    데이터를 데이터 큐에 넣고 나간다


④ 부분에서 data_slot 배열 변수는 원형 데이터 큐를 나타낸다. empty_slot_pos 변수는 데이터를 채워 넣어야 할 큐의 위치를 나타낸다. empty_slot_num 변수는 큐의 비어 있는 데이터 공간의 개수를 나타낸다. 그래서 큐에 데이터를 채워 넣기 전에 empty_slot_num 변수의 값을 하나 감소시킨다. full_slot_num 변수는 큐에 채워진 데이터 공간의 개수를 나타낸다. 그래서 큐에 데이터를 채워 넣은 후에 full_slot_num 변수의 값을 하나 증가시킨다.

우리는 전월 호에서 이와 같은 루틴에서 발생하는 동기화 문제를 다음과 같이 처리할 수 있음을 보았다.


cli
디바이스를 사용하고 있으면
    데이터를 데이터 큐에 넣고 나간다
    sti


따라서 dev_write 함수의 ①과 ④ 부분에서 발생할 수 있는 동기화 문제를 다음과 같이 처리할 수 있다.


unsigned long flags;
local_irq_save(flags);
if(dev_key == 0) {
           if(empty_slot_num <= 0) {
                      local_irq_restore(flags);
                      return -1;
           }
           empty_slot_num --;

           data_slot[empty_slot_pos] = user_buffer;
           empty_slot_pos ++;
           if(empty_slot_pos == SLOT_NUM)
                       empty_slot_pos = 0;

           full_slot_num ++;
           local_irq_restore(flags);
           return 1;
}


⑤ 부분은 ③ 부분에서 디바이스 버퍼에 데이터를 쓰고 나면, 디바이스가 동작하기 시작함을 논리적으로 나타낸다.




[그림 1] 디바이스에 쓰기 예


앞 에서 우리는 ③ 부분에서 가상 디바이스의 버퍼를 사용한다고 했다. 따라서 이 디바이스에 의한 hardware interrupt는 발생할 수 없다. 그래서 여기서는 주기적으로 발생하는 timer interrupt를 가상 디바이스에서 발생하는 hardware interrupt라고 가정한다. 그럴 경우 timer interrupt는 [그림 1]과 같이 발생할 수 있으며, 이 그림은 전월호의 [그림 2]와 논리적으로 크게 다르지 않음을 볼 수 있다.

[그림 1]에서 ⓐ 부분은 dev_working 함수의 ⑥ 부분을 나타낸다. 여기서는 dev_interrupt 구조체 변수의 멤버 변수인 expires 변수 값을 커널 변수인 jiffies 변수 값에 1을 더해서 설정한다. jiffies 변수는 커널 변수로 주기적으로 발생하는 timer interrupt를 처리하는 루틴의 top_half 부분에서 그 값을 하나씩 증가시킨다. 리눅스 커널 버전 2.6에서는 초당 1000 번 timer interrupt가 발생하도록 설정되어 있다.

[그림 1]의 ⓑ 부분에서는 jiffies 변수 값을 증가시키고 있다. jiffies 변수 값을 증가시키는 함수는 do_timer 함수이며, timer interrupt handler 내에서 이 함수를 호출한다. do_timer 함수는 리눅스 커널 소스의 linux/kernel/timer.c 파일에서 찾을 수 있다.

[그림 1]의 ⓒ 부분에서는 dev_interrupt 구조체 변수의 expires 변수 값과 현재의 jiffies 변수 값을 비교하여 작거나 같으면 dev_interrupt 구조체 변수의 function 함수 포인터 변수가 가리키는 함수를 수행한다. 이 부분은 timer interrupt를 처리하는 루틴의 bottom_half 부분이며 timer_bh 함수 내에서 run_timer_list 함수를 호출하여 수행한다. timer_bh 함수는 리눅스 커널 소스의 linux/kernel/timer.c 파일에서 찾을 수 있다. [그림 1]의 ⓒ 부분에서는 dev_working 함수의 ⑦ 부분에 의해 실제로는 dev_interrupt_handler 함수가 수행된다.

dev_interrupt_handler 함수를 살펴보기 전에 timer_bh 함수 내의 run_timer_list 함수의 역할을 좀 더 보기로 하자. run_timer_list 함수는 timer_list 구조체 변수로 이루어진 linked list에서 timer_list 구조체 변수를 소비하는 역할을 한다. 구체적으로 timer_list 구조체 변수의 expires 변수 값이 현재 jiffies 변수 값보다 작거나 같을 경우 해당하는 timer_list 구조체 변수를 linked list에서 떼내어, timer_list 구조체 변수의 function 포인터 변수가 가리키는 함수를 수행한다. dev_working 함수의 ⑧ 부분에서 사용한 add_timer 함수는 커널 함수이며 run_timer_list 함수가 소비하는 linked list에 timer_list 구조체 변수를 하나 더해 주는 생산자 역할을 한다. dev_working 함수의 ⑨ 부분에서 사용한 init_timer 함수는 timer_list 구조체 변수를 초기화해주는 커널 함수이다.

그러면 dev_interrupt_handler 함수를 보기로 하자. dev_interrupt_handler 함수는 가상 디바이스의 top_half 루틴과 bottom_half 루틴을 나타낸다. dev_interrupt_handler 함수에서 ⑩ 부분은 논리적으로 다음과 같다.


데이터 큐가 비어 있으면
    디바이스를 다 사용했다고 표시하고 나간다


또 dev_interrupt_handler 함수에서 ⑩과 ⑪ 부분은 논리적으로 다음과 같다.


데이터 큐가 비어 있지 않으면
    데이터를 하나 꺼내서
    디바이스 버퍼에 쓰고 나간다


⑪ 부분에서 full_slot_pos 변수는 데이터를 비울 큐의 위치를 나타낸다.

이상에서 dev_write 함수에서 동기화 문제가 발생할 수 있으며 다음과 같이 해결할 수 있다.


ssize_t dev_write(struct file * filp, const char * buffer,
                       size_t length, loff_t * offset)
{
            char user_buffer;
            unsigned long flags;

            if(length != 1) return -1;

            copy_from_user(&user_buffer, buffer, 1);

            local_irq_save(flags);
            if(dev_key == 0) {
                       if(empty_slot_num <= 0) {
                                   local_irq_restore(flags);
                                   return -1;
                       }
                       empty_slot_num --;

                       data_slot[empty_slot_pos] = user_buffer;
                       empty_slot_pos ++;
                       if(empty_slot_pos == SLOT_NUM)
                                    empty_slot_pos = 0;

                       full_slot_num ++;
                       local_irq_restore(flags);
                       return 1;
            }
            dev_key = 0;
            dev_buffer = user_buffer;
            local_irq_restore(flags);
       
            dev_working();

            return 1;
}


완 성된 소스를 다음과 같이 컴파일한 후 insmod 명령어를 이용하여 커널에 devwrite.o 모듈을 끼워 넣는다. 컴파일하는 부분에서 –D__KERNEL__ 옵션은 #define __KERNEL__ 이라는 매크로 문장을 컴파일하고자 하는 파일의 맨 위쪽에 써 넣는 효과와 같으며, __KERNEL__ 매크로는 컴파일하는 소스가 커널의 일부가 될 수 있다는 의미를 가진다. MODULE 매크로는 컴파일하는 소스를 커널에 모듈형태로 동적으로 끼워 넣거나 빼 낼 수 있다는 의미이다. -I/usr/src/linux-2.4/include 옵션은 파일 내에서 참조하는 헤더파일을 찾을 디렉토리를 나타낸다. 일반적으로 PC 상에서 리눅스 커널 소스를 설치할 경우 /usr/src 디렉토리 아래 linux 내지는 linux-2.4 와 같은 디렉토리 아래 놓인다. 모듈 프로그램은 커널의 일부가 되어 동작하며 따라서 그 모듈이 동작할 커널을 컴파일하는 과정에서 참조했던 헤더파일을 참조해야 한다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# lsmod
# insmod devwrite.o -f
# lsmod
Module                         Size  Used by    Tainted: PF
devwrite                        3581   0  (unused)


/proc/devices 파일은 커널내의 디바이스 드라이버에 대한 정보를 동적으로 나타낸다. 이 파일을 들여다보면 방금 끼워 넣은 디바이스 드라이버의 주 번호가 253임을 알 수 있다. 주 번호는 바뀔 수도 있으니 주의하기 바란다.


# cat /proc/devices
Character devices:
              ...
253 devwrite
              ...
Block devices:


우리가 작성한 디바이스 드라이버를 접근하기 위해 문자 디바이스 파일을 다음과 같이 만든다.


# mknod /dev/devwrite c 253 0
# ls -l /dev/devwrite
crw-r--r--    1 root     root     253,   0  7월 12 00:03 /dev/devwrite


그리고 우리가 작성한 디바이스 드라이버를 사용할 응용 프로그램을 다음과 같이 작성한다.


# vi devwrite-app.c
#include
#include
#include

int main()
{
        int fd;

        fd = open("/dev/devwrite", O_RDWR);

        write(fd, "A", 1);

        close(fd);
}


그리고 다음과 같이 응용 프로그램을 컴파일한 후 응용 프로그램을 수행해 본다. 화면에는 아무 내용도 뜨지 않는다.


# gcc devwrite-app.c -o devwrite-app
# ./devwrite-app


이 젠 모듈을 커널에서 빼낸후 /var/log/messages 파일의 맨 뒷부분을 읽어 본다. 각각 insmod 명령어를 수행하는 과정에서 커널내에서 수행한 init_module 함수, 좀 전에 수행한 응용 프로그램을 수행하는 과정에서 커널내에서 수행한 dev_interrupt_handler 함수, rmmod 명령어를 수행하는 과정에서 커널내에서 수행한 cleanup_module 함수에서 찍은 메시지를 볼 수 있다.


# rmmod devwrite
# tail /var/log/messages
...
Jul 12 00:16:44 localhost kernel: Loading devwrite module
Jul 12 00:17:00 localhost kernel: A
Jul 12 00:17:13 localhost kernel: Unloading devwrite module


그 러면 위와 같이 동기화 문제를 처리 하지 않을 경우 어떤 문제가 발생할 수 있는지 예를 하나 보기로 하자. 다음 예는 전월호의 [그림 5]와 [그림 6]의 경우에서 보았던 루틴간 경쟁 상태를 발생시킨다. 먼저 dev_write 함수와 dev_interrupt_handler 함수를 각각 다음과 같이 고친다.


ssize_t dev_write(struct file * filp, const char * buffer,
                       size_t length, loff_t * offset)
{
             char user_buffer;
             int i;

             if(length != 1) return -1;

             copy_from_user(&user_buffer, buffer, 1);

             for(i=0;i<600;i++) {                                             // ⑫-⑴
                        user_buffer = (char)(i%10)+48;

                        if(dev_key == 0) {
                                   printk("<--1\n");                         // ⑬-⑴
                                   if(empty_slot_num <= 0) {
                                               printk("slot is full\n");
                                               while(1) if(empty_slot_num > 0) break;         // ⑫-⑵
                                   }
                                   empty_slot_num --;

                                   data_slot[empty_slot_pos] = user_buffer;
                                   empty_slot_pos ++;
                                   if(empty_slot_pos == SLOT_NUM)
                                               empty_slot_pos = 0;

                                   {
                                               int j;
                                               for(j=0;j<0x1000000;j++);
                                    }
                                    printk("<--2\n");                       // ⑬-⑵
                                    full_slot_num ++;

                                    continue;                                  // ⑫-⑶
                        }
                        dev_key = 0;
                        dev_buffer = user_buffer;

                        dev_working();
                }

                return 1;
}

void dev_interrupt_handler(unsigned long dataptr)
{
                printk("%c\n", dev_buffer);

                if(full_slot_num <= 0) {
                         printk("no data\n");
                         dev_key = 1;
                         return;
                }
                full_slot_num --;

                dev_buffer = data_slot[full_slot_pos];
                full_slot_pos ++;
                if(full_slot_pos == SLOT_NUM) full_slot_pos = 0;

                empty_slot_num ++;

                dev_working();

                return;
}


dev_write 함수의 ⑫-⑴, ⑫-⑵, ⑫-⑶ 부분은 동기화 문제가 발생할 수 있는 영역을 반복적으로 수행함으로써 dev_interrupt_handler 함수와 충돌이 날 가능성을 높이는 역할을 한다. dev_write 함수의 ⑬-⑴, ⑬-⑵ 사이에 dev_interrupt_handler 함수가 끼어 들 경우 문제가 발생한다. ⑭ 부분은 ⑬-⑴, ⑬-⑵ 사이에 dev_interrupt_handler 함수가 끼어 들 가능성을 높이기 위해 끼워 넣었다. 다음과 같이 테스트해 본다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# insmod devwrite.o -f
# ./devwrite-app
# tail /var/log/messages -n 600
...
Jul 12 06:19:46 localhost kernel: <--1                  ⒜
Jul 12 06:19:46 localhost kernel: 6
Jul 12 06:19:46 localhost kernel: no data              ⒞
Jul 12 06:19:46 localhost kernel: <--2                  ⒝
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: <--2
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: 8                       ⒟
Jul 12 06:19:46 localhost kernel: <--2
Jul 12 06:19:46 localhost kernel: <--1
Jul 12 06:19:46 localhost kernel: 7                       ⒠
Jul 12 06:19:46 localhost kernel: <--2
...
# rmmod devwrite


테스트를 수행한 결과 ⒜와 ⒝ 사이에 ⒞가 끼어 듦으로써 동기화의 문제가 발생하였다. 그 결과 ⒟와 ⒠에서 8과 7의 데이터 역전 현상이 발생하였으며, 또한 7 데이터에 starvation이 발생하였음을 알 수 있다.

그럼 여기서 발생한 동기화 문제를 해결해 보자. 먼저 dev_write 함수를 다음과 같이 고친다.


ssize_t dev_write(struct file * filp, const char * buffer,
                size_t length, loff_t * offset)
{
        char user_buffer;
        int i;
        unsigned long flags;

        if(length != 1) return -1;

        copy_from_user(&user_buffer, buffer, 1);

        for(i=0;i<600;i++) {
                user_buffer = (char)(i%10)+48;

                local_irq_save(flags);
                if(dev_key == 0) {
                        printk("<--1\n");
                        if(empty_slot_num <= 0) {
                                local_irq_restore(flags);
                                printk("slot is full\n");
                                while(1) if(empty_slot_num > 0) break;
                        }
                        empty_slot_num --;

                        data_slot[empty_slot_pos] = user_buffer;
                        empty_slot_pos ++;
                        if(empty_slot_pos == SLOT_NUM)
                                empty_slot_pos = 0;

                        {
                                int j;
                                for(j=0;j<0x1000000;j++);
                        }
                        printk("<--2\n");
                        full_slot_num ++;
                        local_irq_restore(flags);

                        continue;
                }
                dev_key = 0;
                dev_buffer = user_buffer;
                local_irq_restore(flags);

                dev_working();
        }

        return 1;
}


다음과 같이 테스트를 수행하다.


# gcc devwrite.c -c -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4/include
# insmod devwrite.o -f
# ./devwrite-app
# tail /var/log/messages -n 600
...
Nov 19 18:41:07 localhost kernel: 0
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 1
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 2
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 3
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 4
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 5
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 6
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 7
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 8
Nov 19 18:41:07 localhost kernel: <--1
Nov 19 18:41:07 localhost kernel: <--2
Nov 19 18:41:07 localhost kernel: 9
...


데이터의 역전 현상이나 starvation 없이 순서대로 data가 가상 디바이스에 전달되는걸 볼 수 있다.

이 상에서 <디바이스에 쓰기 동작>에 대한 구체적인 작성 예를 보았다. 참고로 모듈 프로그래밍은 일반적으로 루트 사용자의 권한으로 해야 한다. 본 기사에서는 리눅스 커널 2.6 버전의 내용을 위주로 동기화의 문제를 다루고 있지만, 실제 동기화에 대한 테스트는 리눅스 커널 2.4 버전의 파란 리눅스 7.3에서 하였으니 이 점 주의 하기 바란다.


rpm으로 풀고자 하는 파일들을 gzip으로 압축을 한다.
ex) mkdir medic-1.0.0
      cp medic_test/medic medic_test/libdfb_grp.so.0.0.0 medic-1.0.0
      cd medic-1.0.0
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so
      ln -s libdfb_grp.so.0.0.0 libdfb_grp.so.0
      cd ..
      tar cvfz medic-1.0.0.tar.gz medic-1.0.0
그리고 압축된 파일을 rpm만들고자 하는 딜렉토리로 옮긴다.
      cp medic-1.0.0.tar.gz /usr/src/booyo/SOURCES
이제 Spec파일을 만들차례다.
      cd /usr/src/booyo/SPEC
      vi medic-1.0.0.spec


%define version 0.0.1
%define name medic

Summary: Medical equipment
Name:  %{name}
Version: %{version}
Release: 1
Prefix:  /root
Group:  /root
Source:  %{name}-%{version}.tar.gz
BuildRoot: /var/tmp/rpm/%{name}-%{version}
License: GPL
URL:  http://www.etri.re.kr

%description

%prep
%setup
mkdir -p $RPM_BUILD_ROOT/root/medic
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/%{name} $RPM_BUILD_ROOT/root/medic/%{name}
mkdir -p $RPM_BUILD_ROOT/lib
cp -af /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so $RPM_BUILD_ROOT/lib/libdfb_grp.so
mv /usr/src/booyo/BUILD/%{name}-%{version}/libdfb_grp.so.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so
#ln -s $RPM_BUILD_ROOT/lib/libdfb_grp.so.0.0.0 $RPM_BUILD_ROOT/lib/libdfb_grp.so.0

%files
%{prefix}/medic/%{name}
/lib/libdfb_grp.so.0.0.0
/lib/libdfb_grp.so.0
/lib/libdfb_grp.so

%clean
rm -rf $RPM_BUILD_ROOT

이제 모든 준비는 끝났다.
바이너리 RPM을 생성해 보자.
     cd /usr/src/booyo/SPEC
     rpmbuild -ba --target=armv4l medic-1.0.0.spec

이렇게 하면 /usr/src/booyo/RPMS/armv4l/medic-1.0.0.armv4l.rpm 이 생성됨을 알수 있다.
우리는 이제 이것을 배포하기만 하면 된다. (근데 아직 테스트를 해보지 않아서 잘될지 알수가 없구나~~)

참조 : http://www.coffeenix.net/doc/HOWTOs/html/RPM-HOWTO/RPM-HOWTO-7.html
         http://blog.naver.com/yong0872?Redirect=Log&logNo=70001568086

# rpm -Uvh gtk+-2.0.0-1.armv4l.rpm
이건 현재 host에 설치하라는 소리인데~~~~
rpm2cpio xxx.rpm | cpio -i --make-directories
or
rpm2cpio xxx.rpm | cpio -id

이런식으로 하면 현재 디렉토리에gtk+-2.0.0-1.armv4l.rpm 이 풀린다...
풀린 rpm을 이제 타겟보드에 포팅하면 된다. ^^

#include <stdio.h>

int main()
{
        FILE * tp;
        char lines[1024];
        int j=0;

        tp = popen("top -b -n1 | sed -n 3p | awk '{print $2}'", "r");

        while(fgets(lines, 1024, tp))
        {
                if (lines[j] != ' ') j++;
        }
        printf("%s\r\n", lines);
        pclose(tp);
        return 0;
}

참조 : directfb예제중 df_cpuload.c 를 이용.
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <gtk/gtk.h>

#define SET_IF_DESIRED(x,y) do{  if(x) *(x) = (y); }while(0)
#define JT unsigned long

static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret);
static int get_load();
void* cpu_load(int t);


GThread *adc_insert_thrs;
GError *adc_error;


static void four_cpu_numbers(int *uret, int *nret, int *sret, int *iret)
{
     int       tmp_u, tmp_n, tmp_s, tmp_i;
     static JT old_u, old_n, old_s, old_i, old_wa, old_hi, old_si;
     JT        new_u, new_n, new_s, new_i, new_wa = 0, new_hi = 0, new_si = 0;
     JT        ticks_past; /* avoid div-by-0 by not calling too often :-( */
     char      dummy[16];
     FILE     *stat;

     stat = fopen ("/proc/stat", "r");
     if (!stat)
          return;

     if (fscanf (stat, "%s %lu %lu %lu %lu %lu %lu %lu", dummy,
                 &new_u, &new_n, &new_s, &new_i, &new_wa, &new_hi, &new_si) < 5)
     {
          fclose (stat);
          return;
     }

     fclose (stat);

     ticks_past = ((new_u + new_n + new_s + new_i + new_wa + new_hi + new_si) -
                   (old_u + old_n + old_s + old_i + old_wa + old_hi + old_si));
     if (ticks_past) {
          tmp_u = ((new_u - old_u) << 16) / ticks_past;
          tmp_n = ((new_n - old_n) << 16) / ticks_past;
          tmp_s = ((new_s - old_s) << 16) / ticks_past;
          tmp_i = ((new_i - old_i) << 16) / ticks_past;
     }
     else {
          tmp_u = 0;
          tmp_n = 0;
          tmp_s = 0;
          tmp_i = 0;
     }

     SET_IF_DESIRED(uret, tmp_u);
     SET_IF_DESIRED(nret, tmp_n);
     SET_IF_DESIRED(sret, tmp_s);
     SET_IF_DESIRED(iret, tmp_i);

     old_u  = new_u;
     old_n  = new_n;
     old_s  = new_s;
     old_i  = new_i;
     old_wa = new_wa;
     old_hi = new_hi;
     old_si = new_si;
}
#undef JT

static int
get_load()
{
     static int old_load = 0;

     int u = 0, n = 0, s = 0, i, load;

     four_cpu_numbers( &u, &n, &s, &i );

     //load = u + n + s;
     load = u + n + s + i;

     //old_load = (load + load + load + old_load) >> 2;
     old_load = ((u+n+s)*100) / (load);

     //return old_load >> 10;
     return old_load;
}

void* cpu_load(int t)
{
 int load;
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 
 return (void*)0;
}

int main(int argc, char** argv)
{
 int load;
 g_thread_init(NULL);
 adc_insert_thrs = g_thread_create((GThreadFunc)cpu_load,0,TRUE,&adc_error);
 
 g_thread_join(adc_insert_thrs );
 /*
 while(1){
  usleep(100000);
  load = get_load();
  printf("cpu load = %d\n",load);
 }
 //*/
 
  return 0;
}

1 CPU load
2 --------
3
4 Linux exports various bits of information via `/proc/stat' and
5 `/proc/uptime' that userland tools, such as top(1), use to calculate
6 the average time system spent in a particular state, for example:
7
8     $ iostat
9     Linux 2.6.18.3-exp (linmac)     02/20/2007
10
11     avg-cpu:  %user   %nice %system %iowait  %steal   %idle
12               10.01    0.00    2.92    5.44    0.00   81.63
13
14     ...
15
16 Here the system thinks that over the default sampling period the
17 system spent 10.01% of the time doing work in user space, 2.92% in the
18 kernel, and was overall 81.63% of the time idle.
19
20 In most cases the `/proc/stat' information reflects the reality quite
21 closely, however due to the nature of how/when the kernel collects
22 this data sometimes it can not be trusted at all.
23
24 So how is this information collected?  Whenever timer interrupt is
25 signalled the kernel looks what kind of task was running at this
26 moment and increments the counter that corresponds to this tasks
27 kind/state.  The problem with this is that the system could have
28 switched between various states multiple times between two timer
29 interrupts yet the counter is incremented only for the last state.
30
31
32 Example
33 -------
34
35 If we imagine the system with one task that periodically burns cycles
36 in the following manner:
37
38 time line between two timer interrupts
39 |--------------------------------------|
40 ^                                    ^
41 |_ something begins working          |
42                                       |_ something goes to sleep
43                                      (only to be awaken quite soon)
44
45 In the above situation the system will be 0% loaded according to the
46 `/proc/stat' (since the timer interrupt will always happen when the
47 system is executing the idle handler), but in reality the load is
48 closer to 99%.
49
50 One can imagine many more situations where this behavior of the kernel
51 will lead to quite erratic information inside `/proc/stat'.
52
53
54 /* gcc -o hog smallhog.c */
55 #include <time.h>
56 #include <limits.h>
57 #include <signal.h>
58 #include <sys/time.h>
59 #define HIST 10
60
61 static volatile sig_atomic_t stop;
62
63 static void sighandler (int signr)
64 {
65      (void) signr;
66      stop = 1;
67 }
68 static unsigned long hog (unsigned long niters)
69 {
70      stop = 0;
71      while (!stop && --niters);
72      return niters;
73 }
74 int main (void)
75 {
76      int i;
77      struct itimerval it = { .it_interval = { .tv_sec = 0, .tv_usec = 1 },
78                              .it_value = { .tv_sec = 0, .tv_usec = 1 } };
79      sigset_t set;
80      unsigned long v[HIST];
81      double tmp = 0.0;
82      unsigned long n;
83      signal (SIGALRM, &sighandler);
84      setitimer (ITIMER_REAL, &it, NULL);
85
86      hog (ULONG_MAX);
87      for (i = 0; i < HIST; ++i) v[i] = ULONG_MAX - hog (ULONG_MAX);
88      for (i = 0; i < HIST; ++i) tmp += v[i];
89      tmp /= HIST;
90      n = tmp - (tmp / 3.0);
91
92      sigemptyset (&set);
93      sigaddset (&set, SIGALRM);
94
95      for (;;) {
96          hog (n);
97          sigwait (&set, &i);
98      }
99      return 0;
100 }
101
102
103 References
104 ----------
105
106 http://lkml.org/lkml/2007/2/12/6
107 Documentation/filesystems/proc.txt (1.8)
108
109
110 Thanks
111 ------
112
113 Con Kolivas, Pavel Machek
참조 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/QOS/SMS/Cpu_Usage
// Standard C++ Library
#include <iostream>

// Common Library
#include <cinterface.h>

// Standard C Library
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>

#define K4 0
#define K6 1

using namespace std;

int isTotal=0;
int findCpu=0;

int GetCpuNum();
int Type = 0;

struct _CpuStat
{
    unsigned int User;
    unsigned int System;
    unsigned int Idle;
};

struct _CpuStat CpuStat[2];

char *rtvstr;

int Init()
{
    GetCpuNum();
    rtvstr = (char *)malloc(80);
    return 1;
}

int cidx = 0;

// CPU 갯수를 얻어온다.
int GetCpuNum()
{
    FILE *fp;
    fp = fopen("/proc/stat", "r");
    char buf[80];
    isTotal=0;
    findCpu=0;
    if (fp == NULL)
    {
        return 0;
    }
    while(fgets(buf, 80, fp))
    {
        if (strncmp(buf, "cpu", 3) != 0)
        {
            continue;
        }
        if (isdigit(buf[3]))
        {
            findCpu++;
        }
        else
        {
            isTotal = 1;
        }
    }
    fclose(fp);
    return 1;
}

// Cpu 갯수를 리턴한다.
int NumRows()
{
    return findCpu;
}

// proc 파일시스템을 분석해서 Cpu 사용율 정보를 얻어온다.
// /proc/stat 의 사용율 총합라인만 읽어올 것이다.
char *Read()
{
    FILE *fp;
    char buf[80];
    fp = fopen("/proc/stat", "r");
    char cpuid[8];
    int nused;
    if (fp == NULL)
    {
        return NULL;
    }
    while(fgets(buf, 80, fp))
    {
        if (!strncmp(buf, "cpu", 3))
        {
            sscanf(buf, "%s %d %d %d %d",
                    cpuid,
                    &CpuStat[cidx%2].User,
                    &nused,
                    &CpuStat[cidx%2].System,
                    &CpuStat[cidx%2].Idle
                    );
            break;
        }
    }
    // 처음실행했다면, 1초를 쉰다음 재귀호출 한다.
    if (!cidx)
    {
        sleep(1);
        cidx++;
        *Read();
    }
    cidx++;
    int diff1;
    int diff2;
    int diff3;
    int Idle, System, User;
    diff1 = CpuStat[(cidx+1)%2].User - CpuStat[(cidx)%2].User;
    diff2 = CpuStat[(cidx+1)%2].System - CpuStat[(cidx)%2].System;
    diff3 = CpuStat[(cidx+1)%2].Idle - CpuStat[(cidx)%2].Idle;

    Idle = (diff3*100)/(diff1+diff2+diff3);
    System = (diff2*100)/(diff1+diff2+diff3);
    User = (diff1*100)/(diff1+diff2+diff3);

    sprintf(rtvstr,"CPU=%d,%d,%d\n", User,System,Idle);
    return rtvstr;
}
int Clean()
{
    if (rtvstr != NULL)
        free(rtvstr);
    rtvstr = NULL;
    return 1;
}

참조 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/system_programing/proc/MakePS

나만의 ps를 만들어보자

윤 상배

yundream@joinc.co.kr

교정 과정
교정 0.8 2003년 4월 7일 23시
최초 문서작성


1절. 소개

대부분의 Unix운영체제는 proc파일시템을 제공한다. proc는 process infomation pseudo의 줄임말이다. 즉 proc파일시스템은 커널에서제공하는 프로세스정보를 저장하고 있는 파일시스템이라고 정리할수 있다.

이 문서는 proc파일시스템을 이용해서 실제 프로세스목록을 출력하는 프로그램제작과 관련된 내용을 담고 있다. 문서는 리눅스운영체제(kernel 2.4.x)를 기준으로 작성되었다.


2절. proc 파일시스템

2.1절. proc 파일시스템에 대하여

리눅스 커널의 주된임무중 하나는 프로세스를 실행시키고 이들을 유지하는 일이며, 이를 위해서 커널데이타구조체를 내부적으로 사용하게 된다. 그런데 이런 프로세스정보는 커널에게만 필요한게 아니고 시스템 관리자혹은 프로그래머들에게도 절대적으로 필요한 정보이다.

그렇다면 필요한 정보를 얻어내기 위해서 어떻게 해야할까? 직접 커널에게 커널데이타구조체를 요청해서 그걸 일일이 분석해야 할까 ? 물론 그렇게도 할수 있겠지만, 이것은 매우 복잡한 작업이며, 또한 (커널모드에서 직접이루어지는 작업임으로)위험한작업이기도 하다. 그래서 리눅스시스템은 사용자레벨에서 프로세스의 상태를 간단하게 확인가능하도록 하기위해서 proc파일시스템을 제공한다.

우리는 복잡하게 커널로부터 여러가지 커널데이타구조체를 요청할필요 없이 proc파일시스템에서 제공하는 정보들을 읽어들이는 정도로 간단하게 프로세스 상태를 얻어올수 있다.

작은 정보: proc파일시스템은 대부분의 유닉스운영체제에서 채택되어지고 있지만, 데이타를 저장하는 범위와 포맷에 있어서 운영체제간 차이점을 보인다. 저장하는 범위의 경우 대부분의 유닉스운영체제는 단지 프로세스 정보만을 제공하는 반면, 리눅스는 프로세스 정보뿐만 아니라 네트웍정보, 파일시스템, 디바이스 정보, 메모리정보, CPU정보등 다양한 정보들을 제공한다.

특히 리눅스의 경우 몇몇정보들에 대해서는 단지 열람만 가능한 수준이 아닌 직접수정을 통해서 커널의 행동을 변경시켜줄수도 있다. 이것은 다른 유닉스에 비해서 매우 확장된 부분이라고 할수 있다.

예를 들어 ICMP요청에 대한 응답을 막고 싶다면 "echo 0 /proc/sys/net/ipv4/icmp_echo_ignore_all"하는 정도로 간단하게 커널의 행동을 변경시켜줄수 있다. 다른운영체제에서의 이러한 작업은 전용관리도구를 사용하든지 리붓팅을 시키든지 해야한다.

저장되는 포맷을 보자면 리눅스는 일반 ASCII문자로 이루어진 반면 다른 유닉스들은 구조체로 정보가 이루어져 있다. 리눅스의 경우 프로세스 정보가 일반문자로 이루어져 있어서 직관적으로 확인하기에 좋기는 하지만 프로그래밍을 할경우 이를 파싱해야되기 때문에 다른 유닉스들에 비해서 좀 불편한점이 되기도 한다.


2.2절. 프로세스 정보가져오기

리눅스의 경우 기능이 확장되긴 했지만 proc파일시스템의 가장큰 사용목적은 뭐니뭐니 해도 프로세스정보를 얻어오는 일이다.

기본적으로 proc파일시스템은 /proc디렉토리안에서 이루어지며, 프로세스정보는 /proc디렉토리밑에 각 프로세스의 PID를 이름으로하는 서브디렉토리 밑에 위치하게 된다. 예를들어 PID가 912인 프로세스라면, 912 프로세스의 정보는 /proc/912(이하 /proc/[PID])밑에 위치하게 된다.

/proc/[PID] 디렉토리밑에는 다시 몇개의 디렉토리와 몇개의 파일들로 이루어져 있다.

 /proc/[PID]/ -+-- cmdline
               |
               +-- cwd
               |
               +-- environ
               |
               +-- exe 
               |
               +-- fd -------+-- 0
               |             |
               +-- maps      +-- 1
               |
               +-- root 
               |
               +-- stat 
               |
               +-- statm 
               |
               +-- status
			
리눅스의 경우 위와 같은 파일들로 이루어져 있다. 각 파일이 가지고 있는 자세한 정보들에 대해서는 proc의 man페이지를 참고하기 바란다.

리눅스의 경우 각각의 정보들은 일반 ASCII텍스트문자로 이루어져있고, 대부분의 경우 공백문자(' ')로 필드의 구분이 되어있음으로, 쉽게 원하는 정보들을 얻어올수 있다.


3절. 나만의 ps제작

프로세스정보를 확인하기 위해서 리눅스는 ps라는 도구를 제공한다. ps를 사용함으로써, 우리는 프로세스의 각종중요한 정보들을 얻어오고, 얻어온 정보는 시스템관리와 프로그래밍을 위한 중요한 데이타로 사용한다.

그림 1. ps를 이용한 프로세스상태 확인

우리가 얻고자하는 프로세스데이타는 다음과 같다.

  • 프로세스의 실행유저

  • 프로세스 아이디(PID)

  • 부모프로세스 아이디(PPID)

  • 부모프로세스 상태(Zombie, Run, Sleep 등)

  • 프로세스 이름

  • CPU 사용율(%)

  • VMEM(가상메모리) 사용율

ps에 비해서 몇가지 빠진것들이 있긴하지만 프로세스를 관리하는데 필요한 최소한의 정보는 가져온다.

이 ps도 기본적으로 proc파일시스템에있는 프로세스정보를 이용해서 가져온다. 더 정확히 말하자면 stats에서 필요한 정보를 가져온다. 다음은 실제 stats의 파일내용이다. 원래는 하나의 행으로 되어있으나 출력하기 쉽게 여러개의 행으로 분리했다.

[root@localhost 2489]# cat stat
2489 (vi) T 2251 2489 2251 772 2581 0 187 0 455 0 12 4 0 0 9 0 0 0 181334 
6950912 0 4294967295 134512640 136413760 3221223720 3221222316 
1074893473 0 0 12288 1333808895 3222310480 0 0 17 0
		
우리가 만들고자하는 프로그램은 위의 stat 정보를 분석하게 될것이다.


3.1절. 예제코드

프로그램의 이름은 qps로 하도록 하겠다.

이프로그램의 쏘쓰는 몇개의 모듈로 이루어져 있으며, 쏘쓰관리를 위해서 Makefile을 사용할것이다. 다음은 만들고자 하는 qps의 쏘쓰트리 구조이다.

-+-- Makefile
 |
 +-- main.cc
 |
 +-- proc.cc
 |
 +-- qps.cc
 |
 +-- include ----+-- proc.h 
                 |
                 +-- qps.h
			
다음은 각 파일들에 대한 설명이다.

표 1. qps 쏘쓰파일 설명

Makefile make에서 사용할 make rule 파일
main.cc main함수를 포함하는 코드, 최소한의 코드만을 가진다
proc.cc 실제 proc파일시스템을 참조해서 각종 프로세스정보를 얻어온다.
qps.cc proc.cc에 정의된 함수를 호출하여 프로세스정보를 얻어오고 이를 화면에 보기좋게 출력한다.
include/proc.h proc.cc에서 사용될 함수선언
include/qps.h qps.cc에서 사용될 함수선언

3.1.1절. Makefile

쏘쓰코드들을 관리하기 위한 Makefile이다. 이해하는데 별다른 어려움은 없을것이다.

	
#############################################################################
# Makefile for building qps
# Generated by tmake at 23:10, 2003/04/08
#     Project: qps
#    Template: app
#############################################################################

####### Compiler, tools and options

CC  =   gcc
CXX =   g++
CFLAGS  =   -pipe -Wall -W -O2 -DNO_DEBUG
CXXFLAGS=   -pipe -Wall -W -O2 -DNO_DEBUG
INCPATH =   -I./include
LINK    =   g++
LFLAGS  =

TAR =   tar -cf
GZIP    =   gzip -9f

####### Files

HEADERS =   include/proc.h \
        include/qps.h
SOURCES =   main.cc \
        proc.cc \
        qps.cc
OBJECTS =   main.o \
        proc.o \
        qps.o

TARGET  =   qps
INTERFACE_DECL_PATH = .

####### Implicit rules

.SUFFIXES: .cpp .cxx .cc .C .c

.cpp.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

.cxx.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

.cc.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

.C.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

.c.o:
    $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $<

####### Build rules
all: $(TARGET)

$(TARGET): $(UICDECLS) $(OBJECTS) $(OBJMOC)
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(LIBS)

dist:
    $(TAR) qps.tar qps.pro $(SOURCES) $(HEADERS) $(INTERFACES) $(DIST)
    $(GZIP) qps.tar

clean:
    -rm -f $(OBJECTS) $(OBJMOC) $(SRCMOC) $(UICIMPLS) $(UICDECLS) $(TARGET)
    -rm -f *~ core

####### Compile

main.o: main.cc

proc.o: proc.cc

qps.o: qps.cc
				


3.1.2절. proc.h, proc.cc

실질적으로 stat를 분석해서 프로세스데이타를 얻어오는 함수들을 포함한다. opendir(2)함수를 이용해서 /proc 디렉토리밑에 있는 파일들의 목록을 얻어오고, 만약 얻어온 파일이 디렉토리이면서 숫자로되어있을경우 프로세스정보 디렉토리라고 판단하고, 서브디렉토리에 있는 stat 파일을 읽어들인다.

읽어들인 stat정보는 " "를 기준으로 파싱해서 배열(vector)에 집어넣는다. 더불어 우리가 만들고자하는 qps프로그램은 해당프로세스의 유저이름도 가져와야 한다. /proc/[PID]/stat 파일은 프로세스소유자의 권한으로 만들어진다. 우리는 stat(2)계열함수를 사용하면 해당 파일의 UID를 얻어올수 있다는걸 알수있다. 또한 getpwuid(3)를 이용하면 해당 UID에 대한 유저이름도 얻어올수 있다.

이렇게 해서 하나의 프로세스에 대한정보가 만들어졌다. 그런데 우리는 프로세스의 목록을 가져와야 함으로 이들 정보는 다시 배열의 원소로 들어가야 할것이다. 이러한 자료구조(배열의 배열)를 위해서 필자는 (속편하게)vector를 사용했다.

다음은 실제 코드들이다. 위의 내용들은 코드를 통해서 이해하기 바란다. 그리어려운 코드들은 아님으로 주석만으로도 충분히 이해가능할것이다.

예제 : include/proc.h

#ifndef _PROC_H_
#define _PROC_H_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <dirent.h>
#include <string>

using namespace std;

// 프로세스 데이타 저장 
typedef struct _process_info
{
    char            username[32];  // 유저이름
    vector<string>  process;       // stat정보들이 들어간다   	
} process_info;

// 프로세스의 리스트를 유지하기 위한 자료구조
// 결국은 배열의 배열이다. 
typedef vector<process_info> Plist;

class Proc
{
    private:
        int         Processnum;     // 현재 프로세스 갯수
        Plist       ProcList;       // 프로세스정보 리스트

    public:
      
        // 생성자및 소멸자 
        Proc();
        ~Proc();

        void            MakeProcInfo();      // 프로세스 정보를 얻어온다.
        vector<string>  ProcParser(char *);  // stat파일을 파싱한다 
        int             ProcNum();           // 현재프로세스 갯수를 넘겨준다.
        int             IsDigit(char *);     // 문자열이 숫자인지 확인한다. 
        Plist           GetProcList();       // 프로세스정보 리스트를 되돌려준다.
};

#endif
				

예제 : proc.cc

#include "proc.h"
#include <iostream>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>

Proc::Proc()
{
}

Proc::~Proc()
{
}

/*
 * 프로세스 정보를 가져온다. 
 * 그러기 위해서 /proc/[PID]/stat파일을 읽어들이고 이를 
 * 필드별로 파싱한다. 파싱은 " "문자를 기준으로 한다.  
 * 또한 프로세스를 생성한 유저 이름도 얻어온다. 
 */
void Proc::MakeProcInfo()
{
    DIR *directory;
    struct dirent *entry = NULL;
    char proc_file[40];
    vector<string> procinfo; 
    process_info lprocess_info;
    struct stat lstat;

    ProcList.clear();

    // proc 디렉토리를 열어서 파일(디렉토리포함)의 리스트를 
    // 얻어온다.
    if ((directory = opendir("/proc")) == NULL)
    {
        perror("opendir error :");
        exit(0);
    }

    while((entry = readdir(directory)) != NULL)
    {
        if (strcmp(entry->d_name, ".") !=0 &&
            strcmp(entry->d_name, "..") != 0)
        {
            sprintf(proc_file,"/proc/%s/stat", entry->d_name);
            // stat 파일이 존재하는지 확인하고 확인하고
            if (access(proc_file, F_OK) != 0)
            {
                continue;
            }

            // 디렉토리 이름이 숫자인지 확인한다. 
            // 디렉토리 이름이 숫자라면 이를 파싱한다.  
            // 또한 stat파일의 소유 UID를 이용해서 유저이름도 얻어온다. 
            if (IsDigit(entry->d_name))
            {
                struct passwd *upasswd;
                stat(proc_file,&lstat);
                lprocess_info.process  = ProcParser(proc_file);

                upasswd = getpwuid(lstat.st_uid);
                strncpy(lprocess_info.username, upasswd->pw_name, 32);
                if(atoi(lprocess_info.process[0].c_str()) == atoi(entry->d_name))
                {
                    ProcList.push_back(lprocess_info);
                }
            }
            else
            {
            }
        }
    }

}

/*
 * MakeProcInfo를 통해만들어진 프로세스정보 리스트를 되돌려준다.
 */
Plist Proc::GetProcList()
{
    return ProcList;
}

/* 
 * 프로세스의 갯수를 되돌려준다.  
 * 갯수는 프로세스자료구조(vector)의 원소의 크기가 된다. 
 */
int Proc::ProcNum()
{
    return ProcList.size();
}

/*
 * stat 파일을 열어서 " "문자를 기준으로 파싱해서 
 * 배열(vector)에 집어넣는다.   
 */
vector<string> Proc::ProcParser(char *proc_file)
{
    FILE *fp;
    char buf[512] = {0x00,};
    char field[80];
    int index = 0;
    unsigned int i, j = 0;
    vector<string> vproc;

    fp = fopen(proc_file, "r");
    if (fp == NULL)
    {
        perror("error : ");
        exit(0);
    }

    memset(field, 0x00, 80);
    fgets(buf, 511, fp);
    for(i = 0; i < strlen(buf); i++)
    {
        if (buf[i] != ' ' && i != (strlen(buf) -1))
        {
            field[j++] = buf[i];
        }
        else
        {
            if (index == 1)
            {
                field[strlen(field)-1] = 0x00;
                vproc.push_back(field+1);
            }
            else
                vproc.push_back(field);
            memset(field, 0x00, 80);
            j = 0;
            index++;
        }
    }
    fclose(fp);
    return vproc;
}   

/*
 * 파일이름이 숫자인지 확인한다. 
 */ 
int Proc::IsDigit(char *str)
{
    int i;
    for (i = 0; i < strlen(str); i++)
    {
        if (isdigit(str[i])==0)
            return 0;
    }
    return 1;
}
				


3.1.3절. qps.h, qps.cc

위에서 설명한 proc.cc를 통해서 stat를 분석한 프로세스데이타가 만들어졌음으로 이제 이것을 가지고 와서 화면에 적당히 뿌려줘야 할것이다. 다음은 이와 관련된 함수들이다. 프로세세의 CPU사용율을 가져오기 위한 getcputime()함수외에는 별특별한건 없을것이다.

include/qps.h

#ifndef _QPS_H_
#define _QPS_H_
#include "proc.h"

// 프로세스 목록을 적당히 가공해서 출력한다.
int viewProc();

// 각프로세스의 CPU 사용율을 얻어온다. 
int getcputime(ulong utime, ulong stime, ulong starttime, int seconds);

// OS의 uptime(부팅후 지금까지의 가동시간)을 얻어온다.  
int uptime();
#endif
				

qps.cc

// Local 헤더파일
#include "qps.h"

// 표준 C++ 헤더파일들
#include <iostream>
#include <vector>
#include <string>

// 표준 C 헤더파일
#include <stdio.h>

using namespace std;

/*
 * 프로세스정보 리스트를 얻어와서 보기좋게 출력한다.  
 */
int viewProc()
{
    Proc *mproc;
    unsigned i;
    int pcpu;
    int seconds = 0;

    Plist ProcList;
    mproc = new Proc;
    mproc->MakeProcInfo();

    // 프로세스정보 리스트를 얻어온다. 
    ProcList = mproc->GetProcList(); 

    int total_cpu = 0;  

    // OS의 uptime을 얻어온다. 
    // 얻어온 정보는 프로세스의 CPU 사용율을 구하기 위한 기초정보로 
    // 사용된다.  
    seconds = uptime();
    printf("%-10s %7s %7s %2s %16s %4s %9s\n", "USER", "PID", "PPID",
                                              "ST", "NAME", "CPU", "VMEM"); 
    printf("==============================================================\n");
    for (i = 0; i < mproc->ProcNum(); i++)
    {
        // CPU사용율을 얻어온다. 
        pcpu = getcputime(atol(ProcList[i].process[13].c_str()),
            atol(ProcList[i].process[14].c_str()),
            atol(ProcList[i].process[21].c_str()), seconds);

        // 보기좋게 출력한다. 
        printf("%-10s %7s %7s %2s %16s %2d.%d %9s\n", ProcList[i].username, 
                                    ProcList[i].process[0].c_str(),
                                    ProcList[i].process[3].c_str(),
                                    ProcList[i].process[2].c_str(), 
                                    ProcList[i].process[1].c_str(), pcpu/10, pcpu%10,
                                    ProcList[i].process[22].c_str());
    }   
    return 1;
}

/*
 * 프로세스의 CPU사용율을 얻기 위해서 사용한다. 
 * utime     : 유저모드에서 프로세스가 스케쥴링되면서 사용한 jiffies 수이다. 
 *             프로세스가 스케쥴링될때마다 증가되는 수치이다.  
 * stime     : 커널모드에서 프로세스가 스케쥴링되면서 사용한 jiffies 수이다. 
 * starttime : 운영체제가 시작되고나서 몇 jiffies가 지난후 
 *             프로세스가 시작되었는지 
 */
int getcputime(ulong utime, ulong stime, ulong starttime, int seconds)
{   
    unsigned long long total_time;
    int pcpu=0;

    // 유저 jiffies 와 커널 jiffies를 더한다.
    total_time = utime + stime;

    // 100은 HZ값이다. HZ이 작아질수록 context switching가 빨라진다.  
    // 이값은 /usr/include/asm/param.h에 선언되어 있다. 
    // 100. 대신에 (unsigned long long)HZ 정도로 코드를 
    // 작성하는게 좀더 안전할것이다. 여기에서는 직관적으로 설명하기 
    // 위해서 하드코딩했다.  
    seconds = seconds - (int)(starttime/100.);

    if(seconds)
    {
        pcpu = (int)(total_time * 1000ULL/100.)/seconds;
    }

    return pcpu;
}

/*
 * 운영체제가 부팅하고 나서 얼마의 시간이 지났는지
 */
int uptime()
{
    FILE *fp;
    char buf[36];
    double stime;
    double idletime;

    if ((fp = fopen("/proc/uptime", "r")) == NULL)
    {
        perror("fopen error : ");
        exit(0);
    }
    fgets(buf, 36, fp);
    sscanf(buf, "%lf %lf", &stime, &idletime);
    fclose(fp);

    return (int)stime;
}
				


3.1.4절. main.cc

main함수다. 더이상 설명할 필요도 없는 간단한 코드이다.

예제 main.cc

#include <iostream>
#include "qps.h"

int main(int argc, char **argv)
{
        viewProc();
}
				


3.2절. 테스트

컴파일은 make를 이용하면 된다. 다음은 우리가 만든 프로그램을 실행시킨 화면이다.

그림 2. qps 테스트화면

그럭저럭 잘돌아가는걸 확인할수 있을것이다.

4절. 결론

이상 proc파일시스템을 이용해서 어떻게 프로세스정보를 얻어오는지에 대해서 알아고, 이 얻어온정보를 가공해서 실제 관리자나, 프로그래머에게 유용한 정보로 만드는 방법을 알아보았다.

시간이 남는다면 몇가지 다른 부가적인 시스템정보까지 포함시켜서 Top와 같은 좀더 강력한 프로그램을 만드는것도 재미있을것이다. 혹은 QT, GTK등을 이용해서 GUI환경에서 작동하는 시스템프로세스 모니터링 프로그램을 만들수도 있을것이다.

+ Recent posts