OS/OS from Scratch

[OS] 16. Video Driver

jschang 2021. 7. 2. 14:08

서론

이번 강의에서는 이전 강의에서 사용했던 이론과 코드를 응용해 화면에 문자열을 출력해보겠습니다. 해당 Github 강의는 다음과 같습니다.

https://github.com/jschang0215/os-tutorial/tree/master/16-video-driver

 

jschang0215/os-tutorial

How to create an OS from scratch. Contribute to jschang0215/os-tutorial development by creating an account on GitHub.

github.com


코드

screen.h

#define VIDEO_ADDRESS 0xb8000
#define MAX_ROWS 25
#define MAX_COLS 80
#define WHITE_ON_BLACK 0x0f
#define RED_ON_WHITE 0xf4

// 스크린 I/O 포트 
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5

// Public Kernel API
void clear_screen();
void kprint_at(char *message, int col, int row);
void kprint(char *message);

 

screen.c

kprint_at, kprint 함수를 통해 문자열을 출력할 수 있습니다. 코드가 비교적 직관적이어서 설명은 주석으로 대체하겠습니다.

#include "screen.h"
#include "ports.h"

// Private Function 선언
int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char c, int col, int row, char attr);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);

// Public Kernel API Functions

// 특정 위치에 문자열 출력
// col, row 음수이면 현재 offset에 출력
void kprint_at(char *message, int col, int row) {
    int offset;
    if(col>=0 && row>=0) {
        offset = get_offset(col, row);
    } else {
        offset = get_cursor_offset();
        row = get_offset_row(offset);
        col = get_offset_col(offset);
    }

    int i=0;
    while(message[i]!=0) {
        offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
        row = get_offset_row(offset);
        col = get_offset_col(offset);
    }
}

// kprint_at의 기본 Wrapper
void kprint(char *message) {
    kprint_at(message, -1, -1);
}

// Private Kernel Functions

// VGA 직접 접근해 문자 출력
// col, row 음수이면 현재 커서 위치에 출력
// attr 0이면 white on black
// 다음 문자의 offset 리턴
// 커서를 리턴된 offset위치로 이동
int print_char(char c, int col, int row, char attr) {
    unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
    if(!attr) {
        attr = WHITE_ON_BLACK;
    }

    // 좌표가 범위 밖일 때 경고 문자 출력
    if(col>=MAX_COLS || row>=MAX_ROWS) {
        vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
        vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
        return get_offset(col, row);
    }

    int offset;
    if(col>=0 && row>=0) {
        offset = get_offset(col, row);
    } else {
        offset = get_cursor_offset();
    }

    // 줄바꿈일때는 현재 row에 +1
    if(c=='\n') {
        row = get_offset_row(offset);
        offset = get_offset(0, row+1);
    } else {
        vidmem[offset] = c;
        vidmem[offset+1] = attr;
        offset += 2;
    }
    set_cursor_offset(offset);
    return offset;
}

// VGA port이용해 현재 커서 위치 반환
int get_cursor_offset() {
    // (data 14)로 상위 바이트로 커서의 offset 알아냄
    port_byte_out(REG_SCREEN_CTRL, 14);
    int offset = port_byte_in(REG_SCREEN_DATA) << 8;
    port_byte_out(REG_SCREEN_CTRL, 15);
    offset += port_byte_in(REG_SCREEN_DATA);
    // offset은 커서위치의 2배 (문자+설정)
    return offset*2;
}

// 커서 위치 설정
void set_cursor_offset(int offset) {
    // 하나의 셀은 2개의 데이터로 이루어짐
    offset /= 2;
    port_byte_out(REG_SCREEN_CTRL, 14);
    // 레지스터에 입력
    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
    port_byte_out(REG_SCREEN_CTRL, 15);
    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}

// 빈 화면으로
void clear_screen() {
    int screen_size = MAX_ROWS*MAX_COLS;
    int i;
    char *screen = VIDEO_ADDRESS;

    for(i=0; i<screen_size; i++) {
        screen[i*2] = ' ';
        screen[i*2+1] = WHITE_ON_BLACK;
    }
    set_cursor_offset(get_offset(0, 0));
}

int get_offset(int col, int row) {
    return 2*(row*MAX_COLS + col);
}

int get_offset_row(int offset) {
    return offset/(2*MAX_COLS);
}

int get_offset_col(int offset) {
    return (offset - (get_offset_row(offset)*2*MAX_COLS))/2;
}

kernel.c

#include "../drivers/screen.h"

void main() {
    clear_screen();
    kprint_at("X", 1, 6);
    kprint_at("This text spans multiple lines", 75, 10);
    kprint_at("There is a line\nbreak", 0, 20);
    kprint("There is a line\nbreak");
    kprint_at("What happens when we run out of space?", 45, 24);
}

실행 결과

make로 실행하면, 문자열들이 출력된 것을 확인할 수 있습니다.

실행결과