ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [시스템프로그래밍] Curses
    시스템프로그래밍 2023. 12. 16. 23:42

     

    이번 글에서는 화면 공간을 프로그래밍하는 curses library에 대해서 알아볼 예정입니다. 

     

    초창기 컴퓨터 시절, 우주를 탐험하는 컨셉의 비디오 게임이 하나 나왔습니다. 

    이 게임을 구현하기 위해서는 크게 4가지의 기능이 필요했습니다.

     

    1) space: 게임은 컴퓨터 화면 스크린의 특정 위치에 이미지를 그려야 한다.

    2) Time: 이미지들은 스크린 안에서 각기 다른 속도로 움직여야 하며, 특정 시간 간격마다 위치 변경이 일어나야 한다.

    3) Interruptions: 프로그램이 스크린 안에서 물체들을 부드럽게 움직여야 하고, 유저는 원하는 타이밍에 입력을 넣을 수 있다.

    4) Doing several things: 게임은 물체들을 움직이는 것과 유저의 입력에 대해 반응하는 것을 동시에 수행해야 한다(멀티태스킹).

     

     

    사실 이 요구사항들은 컴퓨터의 운영체제 시스템에서도 비슷하게 요구됩니다. 운영체제에서 커널은

    1) 프로그램을 메모리 공간에 로드하고, 계속해서 각 프로그램들의 위치를 추적해야 합니다. 

    2) 프로그램들을 짧은 시간 간격을 두고 스케줄링해야 하며, 내부 작업들을 특정 시간까지 완료해야 합니다.

    3) 사용자나 외부 기기로부터 불시에 입력이 들어왔을 때 바로 반응해야 합니다. 

     

    그럼 이 요구사항들을 어떻게 구현할지에 대해 알아보도록 하겠습니다. 

     


    Writing a Video game

     

    그럼 위 요구사항들을 하나하나 구현해보기 위해 간단한 탁구 게임을 만들어 볼까요?

    탁구 게임이 가져야 할 기능들을 먼저 정리해 보면,

    1) 공은 특정 속도로 계속 움직여야 하고,

    2) 공이 벽면이나 탁구채에 맞으면 튕겨 나와야 하며,

    3) 사용자는 키보드 입력키를 이용해 탁구채를 위아래로 움직일 수 있어야 합니다.

     

    그럼 이 기능들을 구현하기 위해 필요한 것들은

    1) Space Programming: 공이나 벽면, 탁구채 등을 특정 위치에 그려야 합니다.

    2) Time Handling: 공, 탁구채 등에 움직임 효과를 줍니다.

    3) Signal Handling: 키보드 입력이 들어오면 탁구채를 움직입니다.

    4) Inter-Process Communication(IPC): 프로세스 간 시그널을 송수신합니다.

     


    Space Programming : curses Library

     

    첫번째인 Space Programming부터 해볼까요?

    스크린의 특정 위치에 이미지를 그리기 위해서는 curses 라이브러리를 이용할 수 있습니다. 

    그럼 curses 라이브러리가 무엇일까요?

     

    curses 라이브러리는 터미널을 제어하는 라이브러리입니다.

    커서의 위치, 터미널 스크린에 표시되는 텍스트를 제어하는 함수들로 구성되어 있습니다.

     

    스크린상에서 커서의 위치를 어떻게 제어할까요?

    터미널 스크린은 문자 cell들로 이루어진 하나의 그리드입니다.

    왼쪽 상단을 (0,0)으로 설정하고 이를 기준으로 잡은 좌표를 줘서 위치를 제어할 수 있습니다. 

    예를 들어 좌표 (5,9)를 주면 왼쪽 상단에서부터 행 방향으로 5칸(오른쪽), 열 방향으로 9칸(아래쪽)으로 이동한 위치이겠죠?

     

    그럼 curses 라이브러리에 정의되어 있는 함수들을 좀 더 보도록 하겠습니다.

    1) move(): 스크린 상에서 커서를 설정한 위치로 옮겨줍니다.

    2) addstr(): 스크린에 문자열을 추가 혹은 삭제합니다.

    3) standout(): 화면에 표시되는 문자열의 시각적 요소를 설정합니다(색깔, 밝기 등). standend()로 해당 모드를 끌 수 있습니다.

    4) initscr(), endwin(): 창이나 다른 텍스트 영역들을 생성하고 제어합니다.

    그 외에도 refresh() (스크린에 변경한 정보 업데이트), clear() (스크린 clear) 등이 있습니다. 

     

    함수들을 한번 실습해볼까요?

     

    #include <stdio.h>
    #include <curses.h>
    
    int main()
    {
        initscr();  //turn on curses
    
        clear();   //clear the screen
        move(10,20);
        addstr("Hello, world");
        move(LINES-1, 0);
    
        refresh();  //update the screen
        getch();    //wait for user input
    
        endwin();   //turn off the curses
    
        return 0;
    
    }

     

    스크린의 (10,20) 위치에 문자열 Hello, world를 출력하고 커서를 다시 왼쪽 아래로 옮기는 프로그램입니다.

    LINES는 curses.h에 정의되어 있는 정수형 변수로, 현재 화면의 세로 길이를 나타냅니다. 가로 길이 COLS로 정의합니다.

    중요한 것은, curses 라이브러리를 이용한 프로그램을 컴파일할 때는 컴파일 명령 뒤에 '-lcurses'를 붙여 주어야 합니다.

    즉 'gcc -o hello1 hello1.c -lcurses' 이런 식으로 컴파일 명령을 내려야 합니다. 

     

     

    설정한 위치에 문자열이 잘 출력되는 것을 볼 수 있습니다. 

     

     

    이번에는 standout()을 이용해 시각적인 요소를 추가하고, 반복문으로 문자열을 여러 번 출력해 보겠습니다.

     

    #include <unistd.h>
    #include <curses.h>
    
    int main()
    {
        int i;
    
        initscr();       //turn on curses
        clear();         
        for(i = 0; i<LINES; i++) {
            move(i, i+1);
            if (i%2 == 1)     //홀수 번째에
                standout();   //standout mode turn on
            addstr("Hello, world");  //문자열을 출력한 다음
            if (i%2 == 1)
                standend();   //standout mode turn off
        }
    
        refresh();
        sleep(3);
        endwin();
    
        return 0;
    }

     

    현재 화면 길이만큼 반복문을 돌면서 홀수 번째에 시각적 요소를 추가했다가, 다음 번째에는 나타나지 않도록 끄는 프로그램입니다.

     

     

    실행 결과입니다. Hello, world가 반복적으로 출력되면서 홀수 번째와 짝수 번째의 모습이 다른 것을 볼 수 있습니다.

     

     

    위에서 refresh() 함수는 스크린에 변경된 정보를 업데이트해주는 함수라고 했었는데요, 이에 대해 좀 더 알아보도록 하겠습니다. 

     

    프로그래머가 curses로 화면 구성을 변경할 때마다 바로바로 실제 화면 스크린에 업데이트가 되면, 

    실시간으로 프로그램과 화면 스크린 사이 정보 전송이 이루어져야 하므로 시간이 오래 걸리고,

    그만큼 성능 저하가 일어나겠죠?그래서 curses는 '가상 스크린'을 이용해 데이터 전송하는 시간을 최소화합니다. 

     

    즉, 처음에 addstr() 등의 함수로 화면에 어떤 요소를 추가하면,

    curses는 실제 스크린을 복사해 온 가상 스크린, 즉 스크린 버퍼에 변경 정보를 담습니다. 

    그 후, refresh()가 호출되면 스크린 버퍼와 실제 스크린을 비교해 업데이트해야 할 부분을 확인하고,

    터미널 드라이버 문자나 스크린 제어 코드를 통해 변경된 정보를 전송합니다.

    지난 글의 canonical input에 나왔던 buffering과 유사한 매커니즘이죠!

     

    https://scwcart.tistory.com/entry/Termianal-Driver

     

    Termianal Driver

    이번 글에서는 터미널 드라이버에 대해 알아보겠습니다. 터미널 드라이버를 다루기 전에, 간단히 파일에 대해 짚고 넘어가 보도록 하겠습니다. Files 리눅스/유닉스 시스템 안의 모든 것은 파일

    scwcart.tistory.com

    canonical mode에 대해 좀 더 자세히 알고 싶다면 위 글을 참고하시기 바랍니다.

     

     

    다음 글에서는 두 번째 주제인 Time handling에 대해 알아보겠습니다. 

     

     

    끝!

Designed by Tistory.