마이컴 1993년 8월호 

 활용 C - 램상주 프로그램 

 

 

우리는 램상주 프로그램을 한 두 번쯤 사용해 보았다. 램상주의 개념을 모르더라도, 알게 모르게 램상주 프로그램을 많이 쓰고 있다. 최근 많이 사용되고 있는 램상주 프로그램으로 doskey가 있다. 이는 도스에서 기본적으로 지원하는 것인데 위쪽 화살표 키를 눌렀을 때에 이전에 행했던 도스 명령어들을 다시 출력하는 기능이다.

 

그 밖에도 사이드킥과 같은 램상주 에디터 등 많은 램상주 프로그램이 사용되고 있다. 그러면 램상주 프로그램은 왜 필요할까? 우리가 사용하는 프로그램은 그 이름을 프롬프트 상에서 치면 실행이 되지만, 램상주 프로그램은 그 이름을 입력하면 단지 메모리에 프로그램을 올려 놓는다. 

 

프로그램의 실행은 doskey에서도 보듯이 핫키(위쪽 화살표 키)가 눌려질 때 작동된다. 램상주 프로그램의 동작은 첫째 프로그램을 램으로 올리는 것, 둘째 핫키를 식별하여 그 때 프로그램을 기동시키는 것으로 나누어진다.

 

프로그램을 램으로 올리는 것과 핫키를 인식하는 것은 도스 내부의 구조를 잘 알아야 한다. 램상주 프로그램이라는 것은 도스라는 환경에서 특수하게 존재하는 것이다. 실제로 윈도우 NT, OS/2, 유닉스 등과 같이 도스와 다른 운영체제에서는 멀티태스킹을 기본으로 하기 때문에 램상주 프로그램이란 존재할 필요가 없다.

 

램상주 프로그램을 이용하면 하던 일을 중단하지 않고 다른 일을 하거나, 하던 일을 계속 수행하면서 다른 일을 할 수 있다는 것이다. IBM-PC 호환기종만을 사용한 필자도 대다수의 OS가 멀티태스킹을 지원한다는 것을 안 것은 얼마 되지 않았다. 따라서 제대로된 램상주 프로그램을 작성하기 위해서는 도스의 시스템 동작들을 잘 이해한 후 여러가지 경우를 고려해야 한다.

 

그 중의 하나가 핫키를 인식하는 것이다. 키가 눌렸을 때 컴퓨터 내부에서는 이를 처리하기 위한 작업이 있을 것이다.  이것을 살짝 바꾸어서 우리의 목적에 알맞는 것으로 작업을 추가하거나 수정 할 수 있다.  <Ctrl-C>를 입력하면 화면을 깨끗이 지우게 할 수도 있고, <Ctrl-Alt-E>를 누르면 에디터를 실행시킬 수 도 있다.

 

키값이 들어온 것을 검사하여 자신이 하고자 하는 일을 하면 된다. 그러기 위해서는 키를 눌렀을 때 도스가 하던 일에 자신이 하고자 하는 일(핫키를 검사하는 일)등을 첨가할 수 있다. 그러한 작업을 마치면 도스는 키를 누를 때마다 새로 우리가 정의한 일을 하게 된다.

 


램상주 프로그램의 문제

램상주 프로그램을 만들려면 위에서 설명한 것보다 더욱 많은 부분에 주의를 기울여야 한다. 즉, 핫키를 누르면 텍스트 모드에서 메시지를 출력하는 프로그램을 작성한다고 하자.

 

램에 상주하는 것과 핫키를 설정하는 작업을 모두 마친 다음 다른 프로그램을 사용하다가 핫키를 눌렀다. 그러면 화면에 자신이 만들었던 메시지가 나타날 것이다. 그리고는 다시 사용하던 프로그램에 돌아오게 되면 메시지가 그대로 남게 된다. 

 

이전의 화면을 보호하려면 이전의 화면을 저장하여야 한다. 또 가장 근본적인 제약이 있는데 C 언어에 있는 아무 함수나 쓸 수 없다는 것이다. printf등 이러한 기본 적인 함수도 사용할 수 없다. 따라서 비디오 메모리를 직접 써야 한다.

 

사실은 입출력에 관계한 함수는 모두 다시 정의해야 한다는 것이다. 도스는 입출력에 관계하는 기본적인 루틴들을 제공하는데 이것을 도스 인터럽트 핸들러라고 하고 이 루틴은 소프트웨어 인터럽트라는 방식을 통해 도스에게 원하는 일을 수행하도록 요청한다.

 

대부분의 고급언어로 된 프로그램에서 위에서 말한 도스 인터럽트를 사용한다. 어떤 프로그램을 수행하다가 핫키가 눌리면 램상주 프로그램이 기동되는데 이전 프로그램이 도스에게 요청했던 일과 램상주 프로그램이 요청한 일이 같으면 어떻게 될까.

 

먼저 요청한 일을 다하고서 그 다음에 요청한 것을 하는 것이 당연해 보이겠지만 그렇게 할 수가 없다. 도스와 같이 싱글태스킹 운영체계에서는 한 사람(한 인터럽트 핸들러)에게 동시에 두가지 일을 시킨 셈이 된다. 그럴 경우 시스템이 정지된다.

 

프로그램을 정교하게 짠다면 이전의 프로그램이 도스에 요청한 인터럽트를 확인 하며, 그것을 피하여 도스에 인터럽트를 요청하면 된다. 그것은 번거롭고 프로그램의 목적에 따라 다른 것이므로 여기에서는 화면출력에 관한 것을 알아보기로 한다.

 


getvect(INTR);
우리가 변경하려는 인터럽트 번호가 0x9번이다. 키보드가 눌려겼을 때마다 처리해 준다. 여러가지 일중에 0x9번이라고 번호 붙여진 일을 하는 하나의 함수를 받는 것이라 생각하면 된다. 정확히 말하면 0x9번 인터럽트 핸들러의 code의 주소를 얻는 것이다.

 

 

setvect(INTR, handler);
0×9번의 일을 우리가 원하는 일로 바꾼다. handler라는 함수 안의 맨 끝에 다시 oldhandler라는 함수를 쓴 것에 주의해야 한다. 즉 핸들러를 완전히 바꾸는 것이 아니라 첨가하겠다는 의미이다.

 

 

keep(0, (_SS + (_SP/16) - _psp));
이 명령은 프로그램을 램에 올리는 작업을 한다. 두번째 인자는 램에 상주될 프로그램의 크기가 되는데, 어떤 경우에도 인자를 이렇게 하면 그 크기가 계산되므로 이렇게 써주면 된다. _SS와 _SP, _psp는 각각 스택 세그먼트, 현재 스택 포인터, 프로그램 세그먼트 프리픽스로 어셈블리하는 사람은 잘 알것이다.

 

이것을 쓰면 프로그램의 길이를 코드를 일일이 세어보지 않아도 알수 있게 한다. 이런 것을 C 프로그램으로 구현한다는 것은 그 만큼 도스의 특수한 상황을 고려 해야 한다는 것이다.

 

이제 다시 정의된 handler를 살펴보자. peek(0x0040, 0x0017)은 어떤 키가 눌렸나의 정보가 담긴 곳이다. #define문을 보면 RIGHT_SHIFT 부터 ALT까지 01, 02..로 2의 배수로 되어 있다. 이진수로 바꾸어 생각해 본다. 즉 한 비트에 한 가지 키가 할당된다. #define된 고유의 값과 peek한 값을 and 하면 그 키가 눌렸나를 알 수 있다.

 

또, getkey = peekb( 0x0040, peek(0×0040, 0x001C) - 2);는 키보드 버퍼에서 방금 눌려진 키의 아스키값을 받는 것이다.

 


printStrXY( 5, 8, "MY TSR!", NORMAL );
이 함수는 printf를 대신하여 만들어졌다. 이 출력 함수는 비디오 메모리에 문자열을 쓰게 된다. 비디오 메모리란 화면이 저장되어 있는 장소로 컴퓨터는 일정한 주기로 그곳의 내용을 화면에 전달한다. B800 | 0000은 비디오 램의 주소이다. 허큘리스 모니터인 경우는 B800 대신 B000 이라고 써주어야 한다. 

 

그 곳의 내용을 바꾸면 화면도 바뀌는 것이다. 이렇게 하면 속도도 빠르지만, 작업이 번거롭고 도스 환경 이외에는 쓸 수 없는 방식이므로 보통의 경우에는 잘 사용되지 않는다. 비디오램의 한 줄당 한 문자의 아스키값인 2바이트와 속성 2바이트를 int형의 배열로 잡으면 된다.

 

그곳에 아스키값과 속성을 넣으면 그대로 출력이 된다. attr이라는 인자는 각 문자의 속성에 해당한다. 속성을 NORMAL과 TWINKLE를 정의한 곳의 앞의 2 바이트를 바꾸어 보면 문자의 속성이 바뀌는 것을 알게 된다.

 


saveStrXY( 5, 8, 7 );

이것은 화면의 내용을 프로그램의 영역에 쓴 것이다. 그러면 화면의 일정한 영역이 저장되는 셈이다.

 

 

restoreStrXY( 5, 8, 7 );

이것은 역으로 다시 저장하였던 것을 비디오 램에 쓴다. 그러한 과정으로 이전의 화면을 보존하였다. 프로그램을 실행시킨 후, <왼쪽 시프트키-a>를 누른 경우는 화면에 MY TSR!이 나타나는 것이 그만이지만 <왼쪽 시프트키-b>한 경우는 MY TSR!이 화면에 잠시 머물다가 사라질 것이다.

 

 

saveStrXY로 쓸 부분의 이전 화면을 저장하였다가 루프를 수행하여 지연 시간을 준 후에 restoreStrXY 를 하였으므로 이전 화면이 보존된다.

 

제공된 <리스트>를 수정하여 몇가지 해보려고 하면 많은 제약이 있는 것을 실감하게 될 것이다. 하지만 여러가지로 아이디어를 내면 간단하고 독특한 것을 만들어 낼 수 있을 것이다. 램상주 프로그램을 C로 만들때 또 생각할 것은 램에 올려지기 위해서는 코드가 짧아야 좋으므로 모델을 작은 것으로 한다. small 이나 Tiny 로 짜면 된다.

 


[리스트] 간단한 램상주 프로그램

#include <dos.h>


#define INTR   0x9               /* key interrupt */

#define VEDIORAM 0xB800    /* 허큘리스인 경우는 0xB000 으로 바꿈 */

 

#define RIGHT_SHIFT       0x01

#define LEFT_SHIFT         0x02

#define CTRL                0x04 

#define ALT                  0x08


#define NORMAL         0x0700

#define TWINKLE         0xF900


/* C++ 로 컴파일하는 경우에는  각각의 핸들러에 ... 인자가 필요하다 */
#ifdef __cplusplus
    #define __CPPARGS ...

#else
    #def ine __CPPARGS

#endif


typedef unsigned int (far *PScreen);

char getkey;

int pre_screen [80];


void interrupt ( *oldhandler ) (__CPPARGS);

void interrupt handler (__CPPARGS);


void printStrXY ( int, int, char *, int );

void saveStrXY ( int, int, int );

void restoreStrXY ( int, int, int );


int main( void )

{
    oldhandler = getvect( INTR );

    setvect( INTR, handler );

    keep(0, (_SS + (_SP/16) - _psp));

    return 0;

}


/* 새로 정의된 핸들러 */
void interrupt handler (__CPPARGS)

{
    int i, j;

    if (peek (0x0040, 0x0017) & 0x02 ) {
        getkey = peekb( 0x0040, peek (0x0040, 0x001C) -2 );

        if ( getkey == 'a' ) {
            printStrXY( 5, 8, "MY TSR!", NORMAL );

        }


        if (getkey == 'b' ) {

            saveStrXY (5, 8, 7 );
            printStrXY( 5, 8, "MY TSR!", TWINKLE );
            for( i=0; i<1000; i++ ) 

                for (j=0; j<1000; j++ );
            restoreStrXY(5,8, 7);

        }

    }
    oldhandler( );

}

 


void printStrXY( int x, int y, char *st, int attr )

{
    PScreen screen[80];

    screen[0] = (PScreen) MK_FP(VEDIORAM, (y-1)*0xA0 + (x-1));

    for( int a=0; st[a] !='\0'; a++ ) {

        screen[0][a] = st[a]+attr;

    }

}    

 


void saveStrXY( int x, int y, int num )

{
    PScreen screen [80];

    screen[0] = (PScreen) MK_FP(VEDIORAM, (y-1)*0xA0 + (x-1));

    for( int a=0; a<num; a++ ) {

        pre_screen[a] = screen[0][a];

    }

}

 


void restoreStrXY( int n, int y, int nun )

{
    PScreen screen [80];

    screen[0] = (PScreen) MK_FP(VEDIORAM, (y-1)*0XA0 + (x-1));

    for( int a=0; a<num; a++ )
        screen[0][a] = pre_screen[a];

    }

}

 

 

 

 

  이글은 지금은 없어진 컴퓨터 잡지, 마이컴 1993년 8월호 기사에서 발췌한 내용입니다

 

글이 마음에 드시면 아래 공감버튼 살짝 눌러주세요.

공감과 댓글은 저에게 큰 힘이 됩니다. 

 

 

 

 

 

728x90
반응형
Posted by 전화카드
,