허접한 운영체제 개발 – 부트로더 제작과 운영체제 이미지 로딩, 보호모드 전환, 그리고 C 코드 실행까지 한방에!

20170529_040409.png

리눅스 환경에서의 개발은 내가 갖고있는 바이오 노트북에서 이루어지는데, 바이오 노트북이 사실 해상도가 무지 낮다. 게다가 11인치짜리 모델이라…ㅠㅠ 게다가 책상 위가 난잡해지는 게 싫어서 평상시엔 켜놓고 나서 원격으로 이용중이다. 뭐, 이 글과는 관련은 없다. 스샷과는 관련이 좀 있다. ㅠㅠ

규링은 운영체제를 가지고 석사 논문을 쓰고 있다. (여럿 쓴 것도 있다.) 그런 녀석이 운영체제 하나 직접 만들어보는 게 어려울까 싶어서 도전해 보려 해도… 어렵다. ㅇㅅㅇ

자료는 뒤지면 여러모로 나온다. 게다가 여기까지의 부트로더 기술과 32비트 보호보드는 인텔의 메뉴얼과 몇몇 정형화된 코드들이 있어서 어렵지 않게 작업할 수 있긴 하다. 근데 그걸 머릿속에서 막 쥐어짜면서 직접 코드로 짜니깐 안쉽다. (게다가 어셈블리어다.)

근데 막상 만들고 나니 실제로 다른곳에서 만든 것과 거의 유사한 코드들이 나온다. 그래서 그나마 조금은 덜 걸렸을지도 모르겠다.

p.s. 왜 사람들이 이 수준이 되면 비슷한 변수명을 가져다가 막 예시로 만드는지 알겠다. 그게 제일 안전하다. 내 코드스타일 식의 변수명을 붙이고 하는 건 좀 있다가의 작업이 되겠다. ㅇㅅㅇ

운영체제 개발의 기본 자료: wiki.OSDev.org

운영체제를 개발해 보고 싶다고 맘먹는 사람들은 많아도 실제로 운영체제를 직접 개발해보는 사람은 실제로 많지 않다. 진입장벽 자체가 엄청 어렵다. 대학 학부에서는 솔직히 별 도움도 안되는 수준의 어셈블리어를 가르치는 경우도 많고, C언어도 그닥 잘 배워서 오지 않는 경우도 많다.

그런데 운영체제의 개발에 대한 자료는 많다. 그리고 자작 운영체제에 대한 것들도 사실 엄청 많이 존재한다. 우리가 주로 쓰는 운영체제의 숫자가 그렇게 많지 않을 뿐이지, 개개인이 직접 만들어보겠다고 하면서 자작으로 만드는 운영체제의 숫자는 연간 몇 백 에서 몇 천 단위가 된다고 할 정도로 많이 존재한다. 홈브류 컴퓨터 만들어보겠다고 여러모로 취미로 굴려보는 사람들하고 같다고 보면 된다.

게다가 직접 동작하고자 하는 운영체제는 x86 기반이라면 자료가 진짜로 넘치고 넘친다. 요즘은 ARM 보드에서 동작하는 것들도 자료 보고 많이 만들고 하는 거 같다만, ARM쪽은 내가 모르는 게 아직 많아서… 게다가 자료 올려주는 사람들이 뭔 특정 수치를 주고 거기에 입력을 하는데, 그 값들이 뭔지 정확하게 알고 적는 사람이 은근 없어서 되게 불편하다. (운영체제 개발에는 그것들이 엄청 중요하다.)

그런 대표적인 자료를 올려놓은 위키가 바로 이번 제목에 있는 위키이다. 운영체제에 조금이라도 관심이 있는 사람들은 아마 여기 있는 자료를 한번도 보지 않은 사람은 없을 것이다. 아주 기초적인 개발 환경 뿐만 아니라 부트로더부터 시작해서 운영체제 이론에 등장하는 여러 내용들, 그리고 각종 장치 드라이버 및 레퍼런스 북까지…

운영체제에 관심 있는 사람들은 여기 내용 좀 살펴봐도 좋을 것이다.

101 – 기타 시간 관련 함수

시간과 관련된 기타 함수에서 많이 쓰는 게 difftime과 clock이 있다. difftime  함수는 두 개의 time_t형 함수로부터 시간 차를 구하는 함수고, clock함수는 프로그램 실행 후 현재깨지 경과한 시간을 반환한다.

  • 함수 이름 | 기능
  • difftime | 초 단위의 시간차를 구한다.
  • clock | 프로그램 실행 후 현재까지의 시간을 구한다.

이 함수들을 직접 확인하려면 시간차가 있는 프로그램을 만들어야 하는데, 예시처럼 만들어 보았다. sleep이라는 함수를 사용하였는데, 이 함수는 일정 시간동안 프로그램이 정지를 하는 함수이다.

이렇게 스크린샷으로만 봐선 잘 모를 거 같아서 실행 화면을 녹화해서 유튜브에 올렸다. sleep으로 지정한 딱 5초 있다가 프로그램이 다시 실행되어서 시간차 5를 반환했다.

 

100 – 형식 변환 함수

시간 정보를 여러 형식으로 변환할 수 있는데, localtime과 gmtime은 time_t 형 시간 정보를 struct tm형으로 변환한다. 이 형을 한번 살펴보자.

struct tm {
int tm_sec;           //초 (0~59)
int tm_min;         //분 (0~59)
int tm_hour;       //시간 (0~23)
int tm_mday;      //일 (1~31)
int tm_mon;        //월 (0~11)
int tm_year;        //연도(1990~)
int tm_wday;      //요일 (0~6)
int tm_yday;       //1월 1일 이후의 날짜
int tm_isdst;       //썸머타임(양수일 때 유효, 0일때 무효, 음수일 때 사용 불가)
}

  • 함수 이름 | 기능
  • localtime | time_t형 시간 정보를 struct tm형 지역 시간으로 변환한다.
  • gmtime  | time_t형  시간 정보를 struct tm형 세계 표준 시간으로 변환한다.

기능에 보면 시간 표기의 차이가 있다는 것을 볼 수 있다. 리눅스 설치 시에 지정한 로컬 타임존에 따라서 지역 시간으로 변환하느냐 아니면 글로벌 시간으로 변환하느냐의 차이다. 사용법은 아래의 예시를 확인하자.

이와 반대로 struct tm 형 시간을 time_t형으로 변환하는 함수 또한 존재한다.

  • 함수 이름 | 기능
  • mktime | struct tm형 시간 정보를 time_t형으로 변환한다.

또한 struct tm형의 시간 정보를 사용자가 알아보기 쉽게 문자열로 변환해주는 함수 또한 존재한다. 기본적으로 제공되는 서식 형태로도 만들 수 있지만, 개발자가 직접 서식을 지정할 수 잇는 함수 또한 존재한다.

  • 함수 이름 | 기능
  • asctime | struct tm 형 시간 정보를 문자열로 변환한다.
  • strftime | struct tm형 시간 정보를 서식을 갖춘 시간 정보로 변환한다.

변환에 필요한 변환 형식은 변환 문자열을 확인하여 직접 작성하면 된다. 두 번째에 변환 포맷을 지정할 수 있고, 변환 문자열은 다음과 같다.

  • 변환 문자열 |  의미
  • %a | 요일 이름의 약자
  • %A | 요일 이름
  • %b | 월 이름의 약자
  • %B | 월 이름
  • %c | 지역 날짜와 시간
  • %d | 날짜
  • %H | 시간 (24시간)
  • %I | 시간 (12시간)
  • %j | 1월 1일 이후의 날짜
  • %m | 월
  • %M | 분
  • %p  | AM, PM
  • %S | 초
  • %w | 요일
  • %x | 지역 날짜
  • %X | 지역 시간
  • %y | 연도
  • %Y | 연도(4자리수)
  • %% | 퍼센트 기호

어기까지의 내용만 가지고 예제를 확인해보자. 함수 여럿 좀 써보겠다고 해서 자잘하게 해봤다.

99 – 시간 표시 함수

시스템의 시간을 얻는 함수인 time을 사용하면 “1970년 1월 1일 00:00:00 UTC” 이후의 시간을 초 단위로 반환하고 이 때 t가 시간 정보를 가리킨다. 이 시작시간이 되게 중요하다. 흔히들 POSIX Time 또는 Unix Time이라고 한다.

  • 함수 이름 | 기능
  • time | 초 단위의 현재 시간 정보를 얻는다.
  • ctime | time_t형 시간 정보를 문자열로 반환한다.

반환되거나 time이 가리키는 시간 정보는 별도로 정의되어 있다.

typedef long time_t;

예제를 보면 사용법을 금방 알 수 있다.

실행 결과에서 볼 수 있듯이 long형 정수로 표현된다. 정의된 대로 받아온 것이긴 한데,  이걸 일일이 따져서 계산하기 전에는 어떤 시간인지를 알 수 없다. 그래서 이걸 사용자가 알아볼 수 있게 변환하는 함수가 ctime  함수이다. ctime 예제도 같이 살펴보자.

98 – 날짜와 시간 함수

리눅스 시스템 앞에 앉아 있다면 시계가 없어도 date 명령어를 이용하여 현재의 시간을 정확히 알 수 있다. 라이브러리에서는 이렇게 날짜와 시간 관련 함수를 제공하고 있으므로 이를 이용하면 date 명령어와 같이 시스템의 현재 시간을 알아내는 프로그램을 간단하게 구현할 수 있다.

또한 이러한 날짜와 시간 함수를 이용하면 현재 시간을 알 수 있을 뿐만 아니라 사용자들이 보기 좋게 변환하거나 시간차를 구할 수도 있다.

날짜와 시간은 프로그램에서 여러모로 관리되는 기능이기 때문에 날짜와 시간 함수가 사용되는 경우는 많이 존재한다.

97 – 기타 수학 함수

수학 함수는 몇몇 특정한 성질이 있는 함수가 아닌 경우에는 그냥 직접 보고 실행해 보면 되는 함수들이 많이 있다. 게다가 기초적인 수학을 할 줄 안다는 전재로 있기 때문에 별도로 수학에 대한 설명은 더 이상 필요가 없다. 따라서 수학 함수 중에서도 좀 자주 이용되는 몇 가지 함수들에 대해서 이름과 기능만 좀 적어보겠다.

  • 함수 이름  | 기능
  • sin | 사인 값을 구한다.
  • cos | 코사인 값을 구한다.
  • tan | 탄젠트 값을 구한다.
  • exp | e^x를 구한다.
  • log | 로그(자연 대수)를 구한다.
  • log10 | 상용 로그(상용 대수)를 구한다.
  • frexp | double형 데이터를 가수부와 지수부로 나눈다.
  • ldexp | 가수부와 지수부로부터 double형 데이터를 구한다.

95 – 난수 함수

난수 함수에 대해서는 여러곳에서 말들이 많아서 그런지 다들 그냥 들어봤다라고 해서 유명하다. rand 함수에 대해서 주로 그렇게 이야기를 하는데, 난수 함수를 쓰는데 있어서 주의해야 할 점이 있다.

기본적으로 난수를 만들기 위해서는 rand와 srand 함수를 사용하면 된다. rand함수는 0과 RAND_MAX 사이의 난수를 생성하고 srand는 해당되는 seed를 초기화 하기 때문에 rand함수가 생성하는 난수가 달라지게 된다.

  • 함수 이름 | 기능
  • rand | 난수를 생성한다.
  • srand | rand를 초기화한다.

가장 기본적인 예시부터 살펴보겠다. srand로 처음 seed를 정해서 생성한 다음, 난수를 생성해서 100으로 나눈 나머지를 출력해서 0~99까지 출력한다.

여기서 보면 프로그램을 여러 번 실행해도 똑같은 난수를 계속 생성하는 것을 볼 수 있다. 즉, seed가 고정된 상태에서는 동일한 난수밖에 생성되지 않는다.

이를 해결하기 위해서는 시간과 연동을 해야 한다. 시스템 타이머는 클락 단위로 세세하게 처리되기 때문에 프로그램 실행 때마다 항상 다른 seed로 만들어 낼 수 있다. 그래서 seed값을 유용하게 만들어서 이용할 수 있고, 이렇게 하면 실행할 때마다 rand 함수가 전부 다른 난수를 생성하게 된다.

94 – 나머지 함수

나머지를 구하는 함수 역시 구하고자 하는 유형에 따라 여러모로 다양하게 구성되어 있다. 나머지 함수이지만 몫 또한 같이 구해지는 함수들도 존재한다.

  • 함수 이름 | 기능
  • div | int형 나눗셈의 몫과 나머지를 구한다.
  • ldiv | long int 형 나눗셈의 몫과 나머지를 구한다.
  • fmod | double형 나눗셈의 나머지를 구한다.

예시를 보고 주어진 수의 몫과 나머지를 구하는 과정을 보면 함수 사용법을 알 수 있다.

또한 올림 및 버림 함수와 정수부 소수부를 나누는 함수 또한 존재한다. 이런 함수들은 암호학에서 이용하는 경우가 많다.

  • 함수 이름 | 기능
  • cell | double형 데이터의 소수점 이하를 올림한다.
  • floor | double형 데이터의 소수점 이하를 버린다.
  • modf | double형 데이터를 정수부와 소수부로 분할한다.

이 함수 또한 예제로 보면 이해하기 어려운 것은 없을 것이다.