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로 실행하면, 문자열들이 출력된 것을 확인할 수 있습니다.
