1.       Windows 객체 윈도우

윈도우는 Windows 관리하는 객체 하나이며, 사용자가 보고 있는 창은 윈도우 객체가 출력된 형태이다.

윈도우의 속성은 구조체로 관리되며 CreateWindow API 인자와 대응된다.

 

HWND CreateWindow(                                           // 반환값: 생성된 윈도우 핸들

  LPCTSTR                  lpClassName,        // 윈도우 클래스명

  LPCTSTR                  lpWindowName, // 윈도우 이름

  DWORD                   dwStyle,                  // 윈도우 스타일

  int                              x,                                // 좌측상단의 x좌표

  int                              y,                                // 좌측상단의 y좌표

  int                              nWidth,                   // 수평 방향 크기

  int                              nHeight,                  // 수직 방향 크기

  HWND                      hWndParent,        // 부모 윈도우/소유 윈도우 핸들

  HMENU                   hMenu,                   // 메뉴 핸들/컨트롤 ID

  HINSTANCE            hInstance,              // 인스턴스 핸들

  LPVOID                    lpParam                   // WM_CREATE 메시지에 넘길 전달 인자

);

 

윈도우의 크기는 픽셀 좌표계로 4~7번째 인자에 지정되며, 윈도우의 위치와 크기를 고정하고 싶을 경우가 아니라면 신경쓰지 않아도 된다.

 

2.       윈도우 스타일

윈도우 스타일은 윈도우의 외관이나 종류를 정하기 위한 32비트 정수 값이다. 제목표시줄의 유무, 크기 변경 가능 여부 등을 결정할 있다.

비트 필드로 정의되어 있으므로, WS_ 상수를 논리 OR 명령어로 조합해서 지정할 있다.

 

스타일

해당 윈도우의 구성요소

WS_CAPTION

제목표시줄

WS_BORDER

가는(크기 변경 불가능) 윈도우 프레임

WS_DLGFRAME

대화상자에서 사용하는 윈도우 프레임

WS_THCKFRAME

굵은(크기 변경 가능) 윈도우 프레임

WS_HSCROLL

수평 스크롤바

WS_VSCROLL

수직 스크롤바

WS_SYSMENU

시스템 메뉴, WS_CAPTION 함께 지정

WS_MAXIMIZEBOX

최대화 버튼,WS_SYSMENU 함께 지정

WS_MINIMIZEBOX

최소화 버튼, WS_SYSMENU 함께 지정

 

 

3.       윈도우 종류

(1)    오버랩 윈도우

애플리케이션의 메인 윈도우로 사용하는 형식

제목 표시줄과 윈도우 프레임이 필수 이며, 최대화/최소화 버튼과 메뉴 표시줄을 가지고 있음

 

(2)    팝업 윈도우

메인 윈도우 보다 임시 윈도우로 사용되는 경우가 많음

제목 표시줄과 윈도우 프레임이 필수가 아니라는 것을 제외하면 오버랩 윈도우와 동일

 

(3)    자식 윈도우

메인윈도우가 없다.

부모 윈도우가 필요하다.

 

스타일

해당 윈도우의 종류

WS_OVERLAPPED

오버랩 윈도우, 제목표시줄과 프레임이 있다

WS_POPUP

팝업 윈도우

WS_CHILD

자식 윈도우, 메뉴 표시줄을 사용하지 못한다

WS_OVERLAPPEDWINDOW

WS_OVERLAPPED WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MAXIMIZEBOX, WS_MINIMIZEBOX 조합한 윈도우

WS_POPUPWINDOW

WS_POPUP WS_BORDER WS_SYSMENU 조합한 윈도우로, WS_CAPTION 조합했을 경우에만 메뉴가 나옴

WS_CHILDWINDOW

WS_CHILD 같음

 

 

4.       부모 윈도우 자식윈도우

CreateWindow API hWndParent 부모 윈도우 핸들을 지정하면 자식 윈도우가 생성

부모 윈도우의 클라이언트 영역의 바깥쪽에 출력하거나 이동 불가능

자식 윈도우는 부모 윈도우의 클라이언트 영역을 여러 개로 분할 하여 다른 기능으로 사용가능

 

5.       소유/피소유 의존관계

 

부모/자식 관계와 공통점 : 닫거나 최소화 하면 자동으로 닫히거나 최소화

부모/자식 관계와 차이점 : 표시 영역이 소유 윈도우의 내부로 한정(소유 윈도우를 캔버스로 비유할 있음)

 

소유관계 설정

CreateWindow API 윈도우 스타일에 WS_CHILD 설정하지 않으면서 hWndParent 소유 윈도우 핸들 지정

 

자식 윈도우면서 피소유 윈도우가 없음

소유 윈도우 생성 소유관계 수정 불가(부모/자식 관계에서는 부모 윈도우 변경 가능)

 

#include<windows.h>

 

#define MYWNDCLSNAME L"MyWindowClass"

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

  switch (uMsg){

  case WM_DESTROY :

                   PostQuitMessage(0);

                   return 0;

  }

 

  return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

 

int WINAPI WinMain(HINSTANCE hInst,

  HINSTANCE hPrevInst,

  LPSTR lpCmdLine, int nCmdShow)

{

  WNDCLASS wndcls;

  HWND hWnd, hWndOwned, hWndChild;

  MSG msg;

 

  // 윈도우 클래스 등록

  ZeroMemory(&wndcls, sizeof(wndcls));

  wndcls.lpfnWndProc = WndProc;

  wndcls.hInstance = hInst;

  wndcls.hIcon = LoadIcon(0, IDI_APPLICATION);

  wndcls.hCursor = LoadCursor(0, IDC_ARROW);

  wndcls.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

  wndcls.lpszClassName = MYWNDCLSNAME;

  if (RegisterClass(&wndcls) == 0) return -1;

 

  // 메인 윈도우 생성

  hWnd = CreateWindow(MYWNDCLSNAME, L"Main Window",

                   WS_OVERLAPPEDWINDOW,

                   CW_USEDEFAULT, CW_USEDEFAULT, 400, 200,

                   0, 0, hInst, NULL);

  if (hWnd == 0) return -2;

 

  // 자식 윈도우 생성

  hWndChild = CreateWindow(MYWNDCLSNAME, L"Child Window",

                   WS_CHILDWINDOW | WS_CAPTION | WS_VISIBLE,

                   0, 0, 200, 100,

                   hWnd, 0, hInst, NULL);

  if (hWndChild == 0) return -2;

 

  //피소유 윈도우 생성

  hWndOwned = CreateWindow(MYWNDCLSNAME, L"Ownd Window",

                   WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE,

                   0, 0, 200, 100,

                   hWnd, 0, hInst, NULL);

  if (hWndOwned == 0) return -2;

 

  ShowWindow(hWnd, nCmdShow);

  UpdateWindow(hWnd);

 

  // 메시지 루프

  while (GetMessage(&msg, 0, 0, 0)){

                   DispatchMessage(&msg);

  }

 

  return msg.wParam;

}

 

소스를 실행 하였을 , 다음과 같이 Child Window 경우 Main Window안에서 이동할 있으며, Owned Window 같은 경우, Main Window 밖으로 이동이 되는 것을 있다.

 

1.       컨트롤 ID 자식 윈도우

 

hMenu 인자는 자식 윈도우의 상태에 따라 의미가 달라지며 컨트롤 ID 역할을 수행한다.

hMenu인자는 자식 윈도우가 아닐 경우에는 원래 윈도우 클래스에서 지정한 것과 다른 메뉴를 설정할 사용되며, 윈도우 클래스에서 지정한 값과 같을 때는 0 가진다.

 

컨트롤 아이디

자식 윈도우를 식별하는 32비트 정수 값으로, 대화 상자에 배치된 컨트롤을 식별할 쓰일 있다.

 

CreateWindow API 결과로 받은 윈도우 핸들은 프로그래머가 무슨 값이 예상하기 힘들지만 컨트롤 아이디는 프로그래머가 원하는 값을 할당할 있다.

GetDlgItem API 사용하여 컨트롤 ID 대응하는 핸들을 얻어올 있어, 윈도우 핸들을 보존하지 않아도 된다.

프로그래머는 컨트롤 ID 유일성이 지켜지도록 컨트롤 ID 부여하여야 한다.

 

 

2.       SetWindowsPos

 

SwhoWindow API

윈도우의 출력 상태 설정을 하며, 윈도우 생성 직후 외에 실행중에도 출력상태를 변경하기 위해 호출 있다.

 

BOOL Showwindow(             // 반환값 : 성공이면 TRUE

  HWND hWnd,       // 대상 윈도우 핸들

  int nCmdShow      // 설정할 출력 상태

);

 

상수

의미

SW_HIDE

윈도우를 숨기고, 다른 윈도우를 활성 상태로 전환

SW_MAXIMIZE

윈도우를 최대화

SW_MINIMIZE

윈도우를 최소화하고 다른 윈도우를 활성

SW_RESOTRE

최대/최소화를 원래 상태로 복원

SW_SHOW

윈도우를 나타내고 활성 상태로 만듬

SW_SHOWNA

윈도우를 나타내고 활성 상태로 하지 않음

SW_SHOWDEFAULT

윈도우를 처음 프로그램 시작할 지정된 값으로 변경

SW_SHOWNORMAL

윈도우를 나타내고 활성 상태로 전환

최대화/최소화 경우에 복원

SW_SHOWACTIVATE

윈도우를 활성 상태로 하지 않는 것을 하고 SW_SHOWNORAML과 같음

 

윈도우 Z 순서

ShowWindow 윈도우를 보여주는 방법에서 여러 윈도우가 있을 깊이 방향 위치의 요소를 포함한다.

(여러 윈도우가 겹쳐 있을 어느 윈도우 내용이 표시되는지에 대함)

 

Windows에서 윈도우를 생성할 때나 마우스로 윈도우를 선택할 윈도우 z 순서를 설정

-          마우스로 윈도우 클릭했을

활성 윈도우는 윈도우z 순서가 위인 것이다.

Z순서의 명시적 설정 – SetWindowPosAPI 사용

윈도우의 크기, 위치, 크기, Z순서를 한꺼번에 설정할 있으며, uflags 플래그를 설정하는 방법으로 위치나 z 순서만 설정할 수도 있다.

 

BOOL SetWindowPos (                         // 반환값: 성공이면 TRUE

                  HWND hWnd,                         // 대상 윈도우 핸들

                  HWND hWndInsertAfter,  // z순서상 바로 위에 있는 윈도우 핸들

                  int x,                                           // 윈도우 좌측상단의 x좌표

                  int y,                                           // 윈도우 좌측상단의 y좌표

                  int cx,                                         // 윈도우

                  int cy,                                         // 윈도우 높이

                  UINT uFlags                             // 설정할 상태의 종류

);

 

플래그

의미

SWP_HIDEWINDOW

윈도우를 숨긴다

SWP_NOACTIVATE

윈도우를 활성화하지 않는다

SWP_NOMOVE

윈도우를 이동하지 않는다

SWP_NOOWNERZORDER

소유 윈도우의 Z 순서를 변경하지 않는다

SWP_NOREDRAW

윈도우를 다시 그리지 않는다

SWP_NOSIZE

윈도우의 크기를 변경하지 않는다

SWP_NOZORDER

윈도우 Z 순서를 변경하지 않는다

SWP_SHOWNDOW

윈도우를 나타낸다

 


1. 이벤트 반응형

 

콘솔에서 동작하는 프로그램은 순차적으로 실행을 하지만, GUI 프로그램은 메뉴 선택, 클릭 등의 사용자에 의해 생긴 이벤트에 따라 코드가 실행된다

 

이벤트 반응형

 사용자 조작으로 이벤트가 발생하고 대응되는 처리를 하는 프로그램 형식

 

이벤트의 발생

하드웨어의 장치 드라이버에서 마우스나 키보드 등의 이벤트를 감시하여 이벤트를 이벤트 처리 대기 행렬(시스템 행렬) 추가하여 Window 통지

Windows에서 필요에 따라 애플리케이션에 알림

큐에서 꺼낸 이벤트 정보와 시점의 애플리케이션의 상태를 이용하여 이벤트가 통지될 애플리케이션 결정

 

2.       메시지

 

애플리케이션 윈도우에 이벤트 통지하기 위한 데이터 구조체

typedef struct tagMSG {

                  HWND                      hwnd;                      // 윈도우 핸들

                  UNIT                         message;               // 메시지 ID

                  WPARAM               wParam;                // 메시지 전달인자 1

                  LPARAM                  lParam;                    // 메시지 전달인자 2

                  DWORD time;                       // 이벤트 발생 시각

                  POINT                      pt;                            //이벤트 발생 커서 위치

                  } MSG, *PMSG;   

 

1)      윈도우 핸들

메시지의 행선지가 윈도우를 식별하는 정수

윈도우 핸들 값을 조사하여 조작 대상 윈도우 구별

 

2)      메시지 ID
메시지의 종류를 나타내는 아이디

일반적으로 ‘WM_’으로 시작되는 ID 수백 정의

발생한 이벤트의 종류 알아낼 있음

 

3)      메시지 전달 인자 1 & 메시지 전달 인자 2

메시지의 전달 인자

메시지의 종류에 따라 의미 상이

 

4)      이벤트 발생 시각

5)      이벤트 발생 커서 위치

 

메시지 큐에서 메시지를 꺼내 처리하는 것을 반복한다 (메시지 루프)

3.       윈도우 프로시저

 

메시지의 행선지인 윈도우의 윈도우에 관련된 특별함수(윈도우 프로시저)에서 메시지 처리

 

윈도우 프로시저가 Windows 직접 호출함으로, 정해진 규약이 있음 -> CALLBACK

 

-          인자 4

-          MSG 구조체 멤버와 대응

 

LRESULT CALLBACK WindowProc (

                  HWND hwnd,        // 윈도우 핸들

                  UINT uMSG,          // 메시지 ID

                  WPARAM wParam,              // 메시지 전달 인자1

                  LPARAM lParam   // 메시지 전달 인자2

                  );

 

윈도우 클래스

동일한 기능을 하는 윈도우가 여러 존재 경우, 윈도우 클래스로 그룹화하여 윈도우 프로시저로 여러 윈도우 처리

윈도우 클래스와 윈도우의관계는 클래스와 인스턴스의 관계

같은 윈도우 프로시저 공유

 

윈도우 클래스 등록

애플리케이션의 메인 윈도우로 사용할 있는 윈도우 클래스는 미리 제공되지 않아, 직접 생성하는 윈도우 클래스는 직접 등록

 

4.       WinMain

 



 

#include <Windows.h> //(1)

 

#define MYWNDCLSNAME                  L"MyWindowsClass"

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParma);

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) //(2)

{

  WNDCLASS wndcls;

  HWND hWnd;

  MSG msg;

 

  //윈도우 클래스 등록 (3)

  ZeroMemory(&wndcls, sizeof(wndcls));

  wndcls.lpfnWndProc = WndProc;

  wndcls.hInstance = hInst;

  wndcls.hIcon = LoadIcon(0, IDI_APPLICATION);

  wndcls.hCursor = LoadCursor(0, IDC_ARROW);

  wndcls.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

  wndcls.lpszClassName = MYWNDCLSNAME;

  if (RegisterClass(&wndcls) == 0) return -1;

 

  // 메인 윈도우 생성 (4)

  hWnd = CreateWindow(MYWNDCLSNAME, L"My Window",

                   WS_OVERLAPPEDWINDOW,

                   CW_USEDEFAULT, CW_USEDEFAULT,

                   CW_USEDEFAULT, CW_USEDEFAULT,

                   0, 0, hInst, NULL);

  if (hWnd == 0) return -2; //(5)

 

  // 윈도우 초기 출력 상태 지정

  ShowWindow(hWnd, nCmdShow); //(6)

  UpdateWindow(hWnd); //(7)

 

  //메시지 루프(8)

  while(GetMessage(&msg, 0, 0, 0)){

                   DispatchMessage(&msg);

  }

 

  //WM_QUIT 메시지의 wParam 종료 코드로 한다.

  return msg.wParam;

}

 

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) //(9)

{

  switch (uMsg){

  case WM_DESTROY :

                                     PostQuitMessage(0);

                                     return 0;

  }

  // 직접 처리하지 않은 메시지는 Windows 맡긴다

  return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

 

(1)    Windows.h

Win32 API C언어 함수에서 호출할 필요한 프로토타입 선언이 들어 있음

(2)    WinMain

Windows GUI 애플리케이션이 실행될 제일 먼저 실행되는 함수

hInst

실행중인 애플리케이션을 식별하는 정수값(인스턴스 핸들)

Win32에서는 값으로 애플리케이션 식별 불가능

애플리케이션 자체를 참조하기 위해 인자로 사용하는 경우 필요한 경우가 있어 전역 변수로 보존

hPrevInst

16비트 Windows에서 동일한 애플리케이션이 이미 실행중일 먼저 실행된 애플리케이션의 인스턴스 핸들이 저장됨

Win32에서는 항상 0

호환성을 위해 유지

lpCmdLine

명령행 문자열의 포인터로, main argv 달리 스스로 문자열 해석해야함

nCmdShow

메인 윈도우의 최초 출력 상태를 지정

 

(3)    윈도우 클래스

(4)    윈도우 클래스 등록

RegisterClass API 윈도우 클래스를 등록

등록하려는 윈도우 클래스 정보를 WNDCLASS 구조체에 저장하여 RegisterClass WNDCLASS 주소를 인자로 넘김

 

 

typedef struct _WNDCLASS{

  UINT                         style;                        // 윈도우 클래스 스타일

  WNDPROC                               lpfnWndProc;       // 윈도우 프로시저의 주소

  int                              cbClsExtra;             // 윈도우 클래스의 추가 데이터 크기

  int                              cbWndExtra;         // 윈도우의 추가 데이터 크기

  HINSTANCE            hInstance;              // 윈도우 프로시저를 소유한 프로그램의

                                                                        // 인스턴스 핸들(대부분 자기자신)

  HICON                      hIcon;                       // 애플리케이션 아이콘

  HCURSOR                                 hCursor;                  // 마우스 커서

  HBRUSH                  hbrBackground;   // 윈도우 배경색

  LPCTSTR                  lpszMenuName;  // 메뉴 리소스명

  LPCTSTR                  lpszClassName;    // 윈도우의 클래스 등록명

                  } WNDCLASS, *PWNDCLASS;

 

(5)    윈도우 생성

 

CreateWindow API 윈도우 클래스의 이름, 윈도우의 이름, 윈도우 속성을 인자로 넘김

WS_OVERLAPPEDWINDOW  - 메인 윈도우에서 주로 사용되는 윈도우 속성의 상수

 

CW_USEDEFAULT

윈도우를 출력하는 위치와 윈도우 크기를 Windows 맡김

부모윈도우와 메뉴핸들은 0 넘김

 

CreateWindow

윈도우 생성에 성공하면 윈도우 핸들 반환

반환값이 0 경우 윈도우 생성이 실패했음을 의미하므로 애플리케이션 종료

 

ShowWindow

생성한 윈도우의 출력 상태를 설정하는 API

 

UpdateWindow

실제로 윈도우를 그리는 API

 

(6)    메시지 루프

 

GetMessage

메시지 큐에서 메시지를 꺼냄

MSG 구조체의 주소에 꺼내온 메시지 정보를 저장

BOOL 형태의 반환값을 가지며, 애플리케이션 종료를 의미하는 WM_QUIT 메시지를 꺼냈을 때만 FALSE 메시지 반환

 

DispatchMessage

MSG hWnd 멤버를 참조하여 윈도우 프로시저를 윈도우에 호출 요청

 

(7)    DefWindowProc

 

메시지의 종류가 매우 많으므로 애플리케이션 고유의 처리를 원하는 메시지를 제외하고 DefWindowProc API에서 기본 처리

 

 

PostQuitMessage

WM_DESTROY 메시지를 DefWindowProc 호출하여 처리할 경우, 윈도우만 종료되고 애플리케이션을 종료하지 않기 때문에 따로 처리

WM_QUIT 메시지를 메시지큐에 추가하는 API

 

   

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

  switch (uMsg){

  case WM_LBUTTONDOWN:

                   MessageBox(hWnd, L"Hello, World", L"Message",

                                     MB_OK | MB_ICONINFORMATION);

  case WM_DESTROY :

                                     PostQuitMessage(0);

                                     return 0;

  }

  // 직접 처리하지 않은 메시지는 Windows 맡긴다

  return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

 

'Operating Systems > Windows' 카테고리의 다른 글

윈도우  (0) 2015.04.23
API로 배우는 커널 - 1.운영체제 구조  (0) 2015.04.12

운영체제는 어플리케이션의 입장에서 보면 여러 어플리케이션이 공통으로 사용되는 기능을 API와 시스템 호출로 제공하는 라이브러리와 같다.

 

멀티태스크 운영체제가 해야 할 일

프로세스 관리 - 어플리케이션에 CPU시간을 나누어 준다

메모리 관리 - 다른 어플리케이션에서 사용하는 메모리 영역에 간섭하지 못하도록 한다.

외부기억장치 관리 - 하드 디스크 같은 외부 기억장치에 대한 접근 관리

파일 관리 - 어플리케이션이 파일로 다루어지는 데이터를 마음대로 쓸 수 없게 한다

 

CPU가 운영체제를 위하여 제공하는 기능

1. 특권레벨

어떤 시점에서 CPU의 상태를 나타내는 것으로, CPU가 어떤 명령어를 실행할 수 있고 어떤 메모리 영역에 참조 할 수 있는지 권한을 나타내는 정도이다.

현재 동작중인 특권레벨을 CPL ( Current Privilege Level ) 이라고 하며, 숫자가 낮을수록 권한은 높아진다.

x86 프로세서에는 0에서 3까지의 특권레벨이 있으며, 0은 커널의 코드를 실행하는 특권레벨, 3은 애플리케이션의 코드를 실행하는 특권레벨이다.

 

명령어 중에 HLT CPU CPL 0일 때만 실행이 가능하다.

메모리 세그먼트의 DPL CPL과 일치할 때만 세그먼트에 접근 할 수 있다.

(CALL  JMP 명령어를 이용하여 CPL을 참조하고자 하는 메모리 세그먼트의 DPL로 바꿀수 있다.)

Windows Linux는 세그먼트에 의한 메모리 보호 기능을 일부만 사용하며, 페이징을 이용하여 메모리를 보호한다.

 

2. 세그먼트의 속성과 디스크립터 테이블

각 세그먼트의 정보를 기술한 것으로 ,세그먼트의 수 만큼 나열된다.

크기는 각 8바이트며, 메모리 영역의 선두 주소, 세그먼트 크기, 세그먼트 속성을 설정할 수 있다.

글로벌 디스크립터 테이블인 GDT와 로컬 디스크립터 테이블 LDT가 있다.

 

LDT

태스크 전환에 연동되어 전환이 가능한 테이블이며, 애플리케이션 고유한 세그먼트를 정의할 때 사용된다.

LDT의 위치를 나타내는 디스크립터를 GDT에 기술한 뒤 LDTR레지스터에 등록할 수 있다.

 GDT 참고 : OS 개발 / 3. Protected Mode 1 3. GDT (http://0x200.tistory.com/entry/3-Protected-Mode-1)

3. I/O 특권레벨

 

애플리케이션에 메모리 외의 I/O 포트에 접근하는 것을 제한한다.

WindowsNT/2000/XP LINUX와 같은 대부분의 운영체제는 I/O 포트에 대한 읽기와 쓰기가 금지되어 있다.

 

EPLAGS 레지스터에 IOPL 값을 설정하여 I/O 포트로 접근을 제한한다.

IOPL 값을 변경할 수 있을 때는 오직 특권레벨이 0일 때이다.

 

접근 금지된 어플리케이션이 I/O 포트에 접근하려고 하면 CPU에서 예외를 발생시켜 운영체제로 제어를 넘긴다.

 

4. 콜게이트와 소프트웨어 인터럽트

사용자 모드에서 커널모드로 전환하는 방법은 다음과 같으며, 진하게 표시된 첫번째 두번째 방법이 API 호출에 사용된다.

 - 콜게이트를 경유

 - 소프트웨어 인터럽트(트랩)

 - 태스크 전환

 - 하드웨어 인터럽트 사용

 

(1) 콜게이트

컴퓨터 시작시에 글로벌/로컬 디스크립터 테이블에 게이트 디스크립터를 만들어서 준비하여야 한다.

실행

세그먼트 셀렉터에 콜게이트 인덱스 설정

CALL 명령어 실행

 - 콜게이트에 지정된 셀렉터 값과 오프셋이 나타내는 주소가 실행

처리 완료시 RET 명령어로 복귀

 콜게이트 참조 : 7. 유저모드 Task Switching (1) – 유저모드와 콜게이트

 (2) 소프트웨어 인터럽트

IDT를 준비하여, 코드의 셀렉터값, 오프셋, 속성을 설정

INT 명령어를 사용하여 인터럽트 실행

 소프트웨어 인터럽트 참조 : 7. 유저 모드 Task Switching (2) – 여러 개의 유저 모드 태스크

 

5. Windows에서의 API 호출 처리 순서

윈도우의 주요 API - kernel32.dll, User32,dll, Gdi32.dll 의 세개의 DLL에 주로 공개

ex ) WriteFile API 사용 예시

 

 

Application

 

1) Win32 진입점, App의 가상주소공간매핑 (사용자 모드에서 동작)

호출

↓호출

 

Kernel32.dll1)

WriteFile API

User32.dll

Gdi32.dll API6)

6) WIN32 고유, 따라서 Kernel32.dll과 달리 Ntdll.dll을 호출하지 않음

 

 

 

2) NT/2000/Xp, WIN32, OS/2, POSIX 공통 처리 부분

Ntdll.dll2)

NtWriteFile3) 함수

 

3)실제로 작업을 처리하는 함수 사용자 모드에서 처리 가능한 작업 후 3), 4)실행  

INT 0x2e4)

 

4) 시스템 서비스의 입구

5) 서비스 번호

 

EAX = XX5)

 

사용자모드

 

 

 

커널모드

 

Ntoskrnl.exe의 디스패치7)

7) 디스패치 테이블을 참조하여 주소를 얻음

 

 

 

 

Ntoskrnl.exe

서비스 루틴

Win32k.sys

서비스 루틴

 

 

6. Windows 애플리케이션 시작

(1) 실행파일 열기

실행 파일을 연 뒤, 파일 매핑 객체를 생성한다.

 

파일 매핑[1]

파일 매핑은 프로세스의 가상 주소 공간의 일부에 파일의 내용을 결합하는 것이다.

결합하기 위해 시스템은 'File mapping Object(파일 매핑 객체)'를 물리 메모리에 생성한다.

즉 파일 매핑 객체는 CPU의 페이징 기능을 이용하여 파일 내용을 주소 공간에 매핑한다.

이 때 결합된 가상 메모리 공간의 일부를 FILE VIEW라고 한다. 프로세스는 file view를 포인터로 접근하여 파일 내용을 참조한다.

파일 매핑을 통해, 파일뷰를 사용하는 방법은 파일이 디스크에 존재하는 것과 달리 메모리에 상주하기 때문에 효율성이 증가된다.

 



내부에서 프로세스를 관리하는 메모리 영역(프로세스 객체)를 생성하여 초기화 하며, 프로세스 모안 정보 프라이머리 액세스 토큰을 생성한다.



프로세스 객체[2]

Process Performance Object는 실행중인 어플리케이션 프로그램과 시스템 프로세스를 모니터 하는 카운터로 구성된다.

Privileged Time, Processor Time, User Time, Page Faults/sec, Page File Bytes, Page File Bytes Peak, Priority Base, Private Bytes, Working Set, Creating Process ID, Elapsed Time, Handle Count, ID Process, IO Data Bytes/sec, IO Data Operations/sec, IO Other Bytes/sec

 

액세스 토큰[3]

프로세스 혹은 스레드의 보안 컨텍스트를 기술하는 객체

프로세스와 연관된 유저 계정의 권한 혹은 identity 포함

언제 생성?

유저가 로그온 할 때, 인가된 패스워드인 경우 생성

유저의 행위에 따라 프로세스가 생성될 때 access token을 복제

언제 사용?

스레드가 securable 객체와 상호작용하거나 권한이 필요한 시스템 태스크를 수행할 때 시스템이 사용

어떤 내용?

The security identifier (SID) for the user's account

SIDs for the groups of which the user is a member

A logon SID that identifies the current logon session

A list of the privileges held by either the user or the user's groups

An owner SID

The SID for the primary group

The default DACL that the system uses when the user creates a securable object without specifying a security descriptor

The source of the access token

Whether the token is a primary or impersonation token

An optional list of restricting SIDs

Current impersonation levels

Other statistics

 

(2) 프로세스의 주소공간 생성

프로세스 고유의 페이지 디렉토리와 페이지 테이블을 준비하여 프로세스의 주소 공간을 설정한다. 운영체제의 일부 영역과 모든 프로세스가 공유하는 페이지를 가지고 있는 페이지 테이블은 기존의 것을 매핑 하며, 새로 생성된 실행 파일의 파일 매핑 객체를 앞서 설정한 프로세스 공간에 매핑 한다.

프로세스가 이용하는 객체의 핸들을 관리하는 테이블을 초기화한다. 부모 프로세스의 핸들을 상속하는 경우, 부모 프로세스의 핸들 테이블을 복제한다.

(3) 메인스레드 생성

프로세스의 메인 스레드를 생성한다. 실행 파일에 설정된 크기에 따라 메인스레드용 스택을 생성한우, 스레드의 컨텍스트 정보를 보존할 영역을 초기화 한다.

스레드 전환 후 실행 시작 주소는 진입점이 아니고 스레드 초기화용 함수 주소로 설정된다. 이때 특권레벨은 커널모드로 설정된다.

(4) 스레드 실행 시작

 (5) 프로세스 초기화 처리

스레드로 제어가 넘어가면 이전에 설정한 스레드 초기화용 함수가 커널모드에서 스레드 로컬 스토리지와 크리티컬 섹션을 초기화 한다.

임포트 테이블을 참조하여 DLL을 로드하고 진입점을 호출하여 DLL 초기화를 한다.

(6) 진입점에서 실행 시작

커널 모드에서 사용자 모드로 특권레벨을 변경하고, 메모리 스택에 실행파일의 진입점 주소를 넣는다.

 

[1] : https://msdn.microsoft.com/en-us/library/windows/desktop/aa366556(v=vs.85).aspx

[2] : https://msdn.microsoft.com/en-us/library/ms804621.aspx

[3] : https://msdn.microsoft.com/en-us/library/windows/desktop/aa374909(v=vs.85).aspx


'Operating Systems > Windows' 카테고리의 다른 글

윈도우  (0) 2015.04.23
윈도우 어플리케이션의 구조  (1) 2015.04.21

0. 페이징

가상기억장치를 같은 크기의 블록, 즉 페이지(page)로 나누어 사용하는 기법입니다.

  • 프레임과 페이지

프레임은 물리 메모리를 일정한 크기로 나눈 블록이고, 페이지는 가상 메모리를 일정한 크기로 나눈 블록입니다. 페이지가 하나의 프레임을 할당 받아 물리 메모리에 위치함으로, 페이지는 알맹이, 프레임은 알맹이가 들어갈 틀이라고 생각하시면 쉽습니다. 프레임을 할당 받지 못한 페이지는 외부 저장장치, (하드디스크 등등)에 저장됩니다. 프레임과 페이지는 같은 크기로 관리됩니다.

  • 페이지 테이블(page table)

페이지 테이블은 프로세스의 페이지 정보를 저장하고 있는 테이블입니다. 하나의 프로세스는 하나의 페이지 테이블을 가지고, 테이블은 페이지 번호를 뜻하는 색인과, 해당 페이지에 할당된 물리 메모리(프레임)의 시작주소를 의미하는 내용인 내용으로 구성됩니다.

  • 페이지 테이블 엔트리

페이지 테이블 엔트리는(PTE : PageTable Entry)는 페이지 테이블의 레코드, 즉 각 항목으로 페이지 기본주소(Page base address)와 플래그 비트의 내용이 기록됩니다. 플래그 비트에는 접근 비트(페이지에 대한 접근이 있었는가?), 변경 비트(Dirty bit – 페이지 내용의 변경이 있었는가?), 현재비트(Present bit – 현재 페이지에 할당된 프레임이 있는가?), 읽기/쓰기 비트(Read/Write bit – 읽기/쓰기에 대한 권한을 표시)와 같은 내용이 기록됩니다.

  • 페이지의 크기

페이지의 크기는 X86과 amd64에서는 4kb, ia64에서는 8kb를 가집니다.

  • 동적 주소 변환

페이징 기법에서 동적 주소 변화는 다음과 같은 과정을 거칩니다.

페이징 기법이 적용된 시스템에서 가상주소는 순써쌍 v(p, d)로 나타나는데, p는 가상기억장치 내에서 참조될 항목이 속해있는 페이지 번호이고, d는 페이지 p 내에서 참조될 항목이 위치하고 있는 곳의 변위입니다.

1) 수행중인 프로세스가 가상 주소 V(p,d) 를 참조

2) 페이징 기법을 통해 페이지 p가 페이지 프레임 P' 에 있음을 알림

3) 실 주소 r = p' + d

 

 

1. 페이지 디렉토리와 페이지 테이블

페이징 기능을 사용하지 않았을 때 선형 주소는 물리주소와 같습니다. 그러나 페이징 기능을 사용하면, 논리 주소와 물리 주소의 사이에 선형 주소가 존재하고 따라서 주소를 변경해 주어야 합니다.

 

페이징을 사용하기 위해서 커널은 미리 페이지 디렉토리와 페이지 테이블을 만들어 놓아야 합니다.

1) 페이지 디렉토리

페이지 디렉토리는 1024개의 디렉토리 엔트리로 구성된 데이터 입니다. 시스템에 하나만 존재합니다. 각각의 디렉토리 엔트리에는 페이지 테이블의 첫 주소, 페이지 테이블의 포인터를 가지고 있습니다. 따라서 시스템에는 1024개의 페이지 테이블이 존재한다고 볼수 있습니다.

 

2) 페이지 테이블

각각의 페이지 테이블은 또 1024개의 페이지 테이블 엔트리를 가지고 있고 디렉토리 엔트리와 비슷한 구조를 가지고 있습니다. 페이지 테이블의 엔트리는 4KB의 물리 주소, 즉 페이지의 포인터를 가지고 있습니다.

 

페이지 디렉토리와 페이지 테이블은 다음과 같은 C언어 구문으로 나타낼 수 있습니다.

DWORD page[1024][1024];

 

전체 페이지 엔트리 수는 1024*1024 = 1MB 이고, 각각 4KB의 크기를 가지고 있어, 1mb*4kb = 4GB가 됩니다. 따라서 페이징을 사용하여 4GB 영역의 주소를 지정할 수 있습니다.

4GB영역을 모두 지정하기 위해서는 1024*4+1024*4= 16MB(각 엔트리가 32비트)의 영역이 메모리에 할당되고 유지되어야 합니다. 낭비가 될 수 있으므로, 페이지 테이블의 수를 조절합니다.

 

2. 선형주소를 물리주소로

세그먼트를 이용해 산출된 선형 주소는 1)최상위 10비트는 페이지 디렉토리에서 몇 번째 엔트리를 사용할지 결정하고, 2)그 다음 10비트는 페이지 테이블에서 몇 번재 엔트리를 사용할지 나타내며, 3)하위 12비트는 4KB 물리 페이지상에서 오프셋을 나타냅니다.

CR3 레지스터에는 페이지 디렉토리의 포인터가 저장되어 있습니다. 페이지 디렉토리의 포인터는 처음 페이지를 설정할 때 CR3 레지스터에 설정해 줍니다.

 

CPU는 프로그램에서 주소 지정을 하면 자동으로 세그먼테이션과 페이징을 수행하여 물리 주소를 계산 합니다. 위의 그림을 참고해주세요.

(1) 물리주소 계산

1) CR3를 참조하여 페이지 디렉토리 찾기

2) 주어진 선형 주소의 최상위 10비트를 참조하여 페이지 디렉토리의 엔트리 번호를 구해, 이 수에 4를 곱한 후 페이지 디렉토리의 포인터와 더해 페이지 디렉토리 내의 엔트리를 찾기

디렉토리 엔트리의 주소 = 선형주소의 최상위 10비트*4 + 페이지 디렉토리의 포인터

3) 찾아낸 엔트리를 참조하여 페이지 테이블의 물리 주소 찾기

4) 선형 주소의 중간 10비트를 이용하여 페이지 테이블 내의 엔트리 번호 찾기

5) 페이지 테이블에서 물리 페이지의 주소를 구하기

6) 선형 주소의 하위 12비트로 물리 페이지 내에서의 오프셋을 구하여 물리 주소에 접근

 

페이지 디렉토리 엔트리

31

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

12

11

 

9

8

7

6

5

4

3

2

1

0

페이지 테이블의 포인터

커널이 사용

G

P

S

0

A

P

C

D

P

W

T

U

/

S

R

/

W

P

 

페이지 테이블 엔트리

31

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

12

11

 

9

8

7

6

5

4

3

2

1

0

4KB  물리 페이지의 포인터

커널이 사용

G

P

A

T

D

A

P

C

D

P

W

T

U

/

S

R

/

W

P

(1) 포인터

페이지 테이블의 포인터와 4KB 물리 페이지의 포인터는 각각 해당하는 물리주소의 4kb를 나눈 값이 들어갑니다. 페이지는 4kb 단위를 기본으로 동작하기 때문입니다. 따라서 페이지 디렉토리나 페이지 테이블, 4KB 물리 페이지는 0x1000단위, 즉 하위 12비트를 0으로 하는 주소에 위치해야 합니다.

(2) A 비트

커널 프로그램이 모든 엔트리의 A 비트를 조사하여 최근에 접근한 페이지나 접근되지 않은 페이지를 찾아냅니다. 페이지에 접근되면 CPU가 자동적으로 해당 엔트리의 A 비트를 1로 세트합니다(클리어는 커널 프로그램의 몫입니다.)

(3) U/S 비트

0으로 클리어 되어 있으면 페이지 테이블도, 4KB 물리 페이지도 커널만 사용할 수 있다는 뜻입니다.

(4) R/W

0으로 클리어 되어 있으면 페이지 테이블이나 4KB 물리 페이지는 읽기만 가능하다는 의미입니다.

(5)P비트

0으로 클리어 되어 있으면 물리 주소상에 페이지가 위치하고 있지 않다는 의미이고, 1이면 물리 주소에 위치하고 있다는 의미입니다.

 

3. 예시

(1) 선형 주소 0x10065

0x10065를 32비트 이진수로 나타내면 다음과 같습니다.

00000000000000010000000001100101

파란색 부분은 페이지 디렉토리 엔트리 번호이며,

붉은 색 부분은 페이지 테이블의 물리 페이지 엔트리 번호이고,

초록색 부분은 페이지에서의 오프셋입니다.

 

각각을 16진수로 나누면 다음과 같습니다.

0x0

0x10

0x65

 

1) CR3에서 페이지 디렉토리의 시작 주소를 구합니다.

2) 페이지 디렉토리의 주소에 페이지 디렉토리 엔트리 번호(선형주소의 상위 10비트)*4한 값을 더해줍니다. 0이므로 첫번째 엔트리가 될 것입니다.

3) 페이지 디렉토리에서 0번째 엔트리에 포함된 페이지 테이블의 주소를 참조하여 페이지 테이블을 찾습니다.

4) 페이지 테이블의 첫 주소에 0x10*4=64를 더하여 페이지 테이블 엔트리를 찾습니다. 0x10은 선형 주소의 가운데 10비트로, 페이지 테이블의 물리 페이지 엔트리 번호입니다.

5) 찾은 페이지 테이블 엔트리에서 4kb 물리 페이지의 첫 주소를 찾습니다.

6) 4KB 물리 페이지의 첫 주소에 0x65를 더하여 실제 물리 주소를 구합니다.

 

페이지 디렉토리와 페이지 테이블을 10비트로 접근 하는 이유는 1024*4=4069=4KB이기 때문입니다. 반면 페이지의 오프셋을 12비트를 이용하여 접근 하는 이유는 1byte 단위로 접근해야 하기 때문입니다. 따라서 다른 경우 (페이지 디렉토리 엔트리의 페이지 테이블 포인터와 페이지 테이블 엔트리의 4KB 물리 페이지 포인터)와 다르게 4도 곱하지 않습니다.

 

4. 페이지 폴트

RAM 상에 프로그램이 데이터를 쓸 장소가 없는 경우, 페이지 폴트가 발생됩니다. 이때 14번 인터럽트 핸들러가 실행되고, 커널은 페이지 교체 알고리즘에 따라 디스크의 특정 공간에 저장하고, IRET 명령으로 핸들러를 마친뒤 페이지 폴트가 발생했든 프로그램에게 돌아갑니다.

마찬가지로 RAM상에 프로그램이 찾는 페이지가 없을 경우 (4KB 물리 페이지 엔트리의 p 비트가 0일 때) 페이지 폴트를 발생시키고, 14번 인터럽트를 실행 시켜 디스크의 스왑 영역에서 해당 페이지를 찾아 RAM 상에 옮긴뒤, 다시 해당 프로그램으로 돌아갑니다.

A20 혹은 addressing line 20는 x86 컴퓨터 시스템에서 전자적인 시스템 버스로 이루어져있습니다.
8086에는 어드레스 라인이 20개 뿐이여서 0xFFFF:FFFF에서 최상위 1비트를 표현할 수 없었습니다.( 0xFFFF:FFFF, 즉 0x10FFEF가 100001111111111101111로 21자리이기 때문에)

따라서 8086에서 1MB(0x100000) 초과 범위의 주소는 0번지부터 다시 시작되어야 하고 주소가 겹치게 됩니다.

80286때부터 CPU에 Protected Mode가 도입되고, 어드레스 라인이 늘어나게 됩니다. 8086과 호환하게 위해, REAL 모드를 만들고, 20번을 8042(8086 당시의 키보드 컨트롤러)와 AND 게이트로 접속시킵니다.

 

 

A20과 연결된 키보드 컨트롤러의 핀이 0이 라면 항상 0이 되어, 주소가 제대로 전달되지 않을 것이며, 홀수 값인 주소(단위 MB)는 제대로 전달 되지 않아 사용할 수 없을 것입니다.

예) 키보드 컨트롤러의 A20 GATE가 항상 0일 때

0x100000 -> 100000000000000000000(2) -> 000000000000000000000(2) -> 0x0

0x200000 -> 1000000000000000000000(2) -> 1000000000000000000000(2) -> 0x200000

주소값의 20번째 비트를 0으로 바꿔보면 다음과 같이 값이 변경되는 것을 볼 수 있습니다. 짝수 MB 단위의 주소의 경우 20번째 비트가 항상 0이므로 변화가 없습니다.

 

따라서 32비트 커널에서 A20 게이트를 켜 주어야, 제대로 주소 전달을 할 수 있습니다.

 

A20 게이트를 켜 주기 위하여, 먼저 1)int 0x15를 사용하여 BIOS 함수를 호출하거나 2)8042 키보드 컨트롤러를 제어하거나 3) I/O 포트를 제어하는 방법이 있습니다.

1. 스택 영역을 사용한 태스크 스위칭

(1) 인터럽트를 위한 스택

1) 인터럽트가 시작되었을 ,  커널 스택 영역은 다음과 같이 데이터를 쌓습니다.

 

SS0

 

 

ESP0

 

SS

ESP

 

EFLAGS

 

 

CS

 

EIP

 

 

2) 유저 데이터 세그먼트 셀렉터 값을 스택에 저장

DS, ES, FS, GS 유저 데이터 세그먼트 셀렉터 값이 존재하여, 값을 커널 데이터 세그먼트 셀렉터 값으로 바꾸어야 인터럽트 핸들러에서 데이터를 사용할 있습니다. 유저 데이터 세그먼트 셀렉터 값을 스택에 저장 한후, 커널 데이터 세그먼트 셀렉터 값으로 바꾸어 줍니다.

 

SS0

 

 

ESP0

 

SS

ESP

 

EFLAGS

 

 

CS

 

EIP

 

 

GS

 

 

FS

 

 

ES

 

 

DS

ESP

 

 

 

 

 

 

 

3) PIC 초기화

4) 인터럽트 루틴 수행

여기서는 .This is the timer interrupt 라는 문자열을 표시하고, 첫번째 문자의 값을 1 증가 시킵니다.

여기 까지의 스택 모습은 다음과 같습니다

 

SS0

 

 

ESP0

 

SS

ESP

 

EFLAGS

 

 

CS

 

EIP

 

 

GS

 

 

FS

 

 

ES

 

 

DS

 

EAX

EBX

 

ECX

 

EDX

 

EBX

 

EBP

 

ESI

 

EDI

ESP

 

 

5) 인터럽트를 마치고 ret_from_int 함수로

Ret_from_int 함수에서는 인터럽트 핸들러가 유저 영역 실행 중에 발생 했는지 커널 영역 실행 중에 발생 했는지 알아보는 루틴으로 되어 있습니다.

                  mov eax, [esp+52]

                  and eax, 0x00000003

ESP+52 (ESP+4*13) 인터럽트 실행 전의 CS 값을 참조합니다. 값에 and 3 하면 CS RPL 값을 있습니다.

                  mov bx, cs

                  and ebx, 0x00000003

                  cmp eax, ebx

마찬가지로 인터럽트 핸들러가 실행하고 뒤의 CS 값의 RPL 구합니다.

값을 구하여 비교하고, 만약 스택에서 뽑아낸 CS 값이 크다면 이전에 실행되던 태스크는 유저모드 태스크 이므로 태스크 스위칭을 위한 루틴 scheduler: 점프하고작거나 같다면 커널 모드 루틴 실행 중에 인터럽트이므로 스택에 저장해 두었던 범용 레지스터, 데이터 세그먼트 셀렉터 모두를 POP하여 스택에서 꺼내고, IRET 통해 EIP, Cs, Eflag 스택에서 꺼낸다음, 인터럽트가 발생한 시점으로 돌아갑니다.

 

(2) 유저 모드 태스크를 위한 스택

유저 모드 태스크에 각각 실행하던 레지스터 값을 저장하기 위해 RAM 상에 스택을 가지고 있습니다. 스택은 커널 모드 스택이며, 유저 모드에서 실행되는 루틴은 실행되선 안됩니다.

1) 유저 프로그램을 위한 레지스터 저장 영역 예시

times 63 dd 0

User1Stack:

User1regs:

                  dd 0, 0, 0, 0, 0, 0, 0, 0

                  dw UserDataSelector, 0

                  dw UserDataSelector, 0

                  dw UserDataSelector, 0

                  dw UserDataSelector, 0

                  dd user_process1

                  dw UserCodeSelector, 0

                  dd 0x200

                  dd User1Stack

                  dw UserDataSelector, 0

 

Times 63 dd 0 유저 태스크의 스택을 만들어 주는 영역입니다. 다음과 같이 스택이 생성됩니다.

0

UserDataSelector(SS)

User1Stack(ESP)

0x200(EFLAGS)

UserCodeSelector(CS)

0

User_process1(EIP)

0

UserDataSelector(GS)

0

UserDataSelector(FS)

0

UserDataSelector(ES)

0

UserDataSelector(DS)

0(EAX)

0(EBX)

0(ECX)

0(EDX)

0(EBX)

0(EBP)

0(ESI)

0(EDI)

 

라벨이 아래쪽에 지정되어 있는 것은 스택이여서, PUSH 될때 메모리의 작은 번지수를 향하여 진행되기 때문입니다스택의 아래부분에는 범용 레지스터들이 저장되는 공간이 있으며, POPAD 명령을 사용하여 메모리의 번지수방향으로 내려가며 복구하게 됩니다.

데이터 세그먼트 셀렉터 부분은 순서대로 pop 하며 복구합니다.

위의 EIP, CS, EFLGAS, ESP, SS iret명령을 통해 한번에 CPU 복구되게 됩니다.

소스에는 초기값으로 EIP 부분에 user_process1 지정되어 있으며, 프로그램이 실행되면서 EIP 영역의 값이 계속 변할 것입니다. 영역은 태스크스위칭이 일어났을 처음으로 실행 해야하는 명령어가 저장되어 있는 부분입니다.

2) 유저 프로세스

다음 코드는 유저 프로세스의 예시입니다.

user_process1:

                  mov eax, 80*2*2+2*5

                  lea ebx, [msg_user_process1_1]

                  int 0x80

                  mov eax, 80*2*3+2*5

                  lea ebx, [msg_user_process1_2]

                  int 0x80

                  inc byte [msg_user_process1_2]

                  jmp user_process1

 

msg_user_process1_1 db "User Process1", 0

msg_user_process1_2 db ".I'm running now", 0

 

Int 0x80 IDT 0x80번째 있는 트랩 게이트를 이용하겠다는 의미입니다.

                  push eax

                  mov ax, SysDataSelector

                  mov ds, ax

                  mov es, ax

                  mov fs, ax

                  mov gs, ax

                  pop eax

 

                  mov edi, eax

                  lea esi, [ebx]

                  call printf

 

인터럽트 서비스 루틴의 코드의 일부입니다. 세그먼트 셀렉터 레지스터를 커널의 데이터 세그먼트 셀렉터로 설정하였습니다. 과정에서 eax 사용하기 때문에, 스택에 eax 옮겨놓고 작업이 끝나면 다시 꺼내서 eax 저장합니다.

 

3) 태스크 스위칭

1) 태스크 스위칭 준비

CurrentTask dd 0 ; 현재 실행 중인 태스크 번호

NumTask dd 20    ; 모든 태스크의

TaskList :

                  times 5 dd 0  ; 태스크 저장 영역의 포인터 배역

 

다음 코드는 태스크 스위칭을 위해서 정의한 변수 선언입니다. 다음의 C언어 코드와 유사합니다.

int CurrentTask = 0;

int NumTask = 20;

int TaskList[5] = {0,};

 

TaskList 유저 영역 태스크가 사용하는 레지스터 저장 영역의 포인터를 계속해서 넣어줍니다. (4byte 포인터이므로 4 더하면서 넣습니다)

                  mov eax, [CurrentTask]

                  add eax, TaskList

                  lea edx, [User1regs]

                  mov [eax], edx

                  add eax, 4

                  lea edx, [User2regs]

                  mov [eax], edx

                  add eax, 4

 

2) 태스크 스위칭

scheduler:

                  lea esi, [esp]

 

                  xor eax, eax

                  mov eax, [CurrentTask]

                  add eax, TaskList

 

                  mov edi, [eax]

 

                  mov ecx, 17

                  rep movsd

                  add esp, 68

 

현재 커널 영역에 저장된 모든 레지스터 값을 유저 모드 태스크의 스택 위에 있는 레지스터 저장 공간으로 복사를 합니다.  이를 구현하기 위해서 ESP (스택 포인터) ESI 넣고 레지스터 저장 공간의 주소 포인터를 EDI 넣은뒤 rep movsd 값을 복사합니다.

이때, 커널 스택 영역에 저장된 레지스터 값은 이제 필요가 없어, ESP+68, 스택 포인터를 옮겨 원래대로 되돌립니다. 나중에 값은 덮어 씌여져 없어질 것입니다.

 

                  add dword [CurrentTask], 4

                  mov eax, [NumTask]

                  mov ebx, [CurrentTask]

                  cmp eax, ebx

                  jne yet

                  mov byte [CurrentTask], 0

 

yet:

                  xor eax, eax

                  mov eax, [CurrentTask]

                  add eax, TaskList

                  mov ebx, [eax]

 

CurrentTask 변수는 포인터로 사용되는데, 값에 4 ,  NumTask 비교하여, 같으면 모든 태스크를 번씩 수행한 것으로 보아, CurrentTask 변수를 0으로 만들어 다시 처음부터 수행합니다. 다음 다음에 실행할 태스크의 레지스터영역의 주소를 ebx 넣어줍니다.

 

3)TSS

TSS 영역을 하나만 사용하여, 인터럽트로 커널로 진입했을 , ESP 있는 포인터로 스택을 사용합니다. TSS 영역의 ESP0 현재 ESP 값을 넣습니다.

ESP 값은 스택을 이용하여 태스크 스위칭을 하고 레지스터 값을 복원하기 위해 사용됩니다.

이와 같이 스택에 레지스터를 저장하고 복원하는 것을 반복하면서 태스크 스위칭이 이루어 집니다.

(POPAD, POP, iret 등등등)

 

3. 태스크 실행

                  mov eax, [CurrentTask]

                  add eax, TaskList

                  mov ebx, [eax]

                  jmp sched

 

CurrentTask 변수를 이용하여 레지스터 저장 영역의 포인터를 꺼내, EBX 넣고 sched 번지로 점프하여 레지스터 값을 복원하여 IRET 명령을 통해 태스크를 실행합니다.

 하여 레지스터 값을 복원하여 IRET 명령을 통해 첫 태스크를 실행합니다.

1. 유저 모드 세그먼트

다음 코드는 유저 모드 세그먼트영역을 지정하는 디스크립터입니다.

                  dd 0x0000FFFF, 0x00FCFA00

                  dd 0x0000FFFF, 0x00FCF200

 

유저 모드의 코드 세그먼트

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

11

1

1010

0x00

0x00

1

1

1

1

0xC

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

유저모드의 데이터 세그먼트

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

11

1

0010

0x00

0x00

1

1

1

1

0xC

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

디스크립터의 셀렉터 번호는 다음과 같이 나타냅니다.

UserCodeSelector equ 0x28+3

UserDataSelector equ 0x30+3

 

3 더하는 것은 세그먼트 셀렉터의 RPL 부분에 유저 모드를 뜻하는 권한 레벨 3 넣어주겠다는 의미입니다.

15

 

 

 

 

 

 

 

 

 

 

 

3

2

1

0

 

 

 

 

 

 

 

 

 

 

 1

0

1

0

1

1

 

15

 

 

 

 

 

 

 

 

 

 

 

3

2

1

0

 

 

 

 

 

 

 

 

 

 

 1

1

0

0

1

1

 

세그먼트 디스크립터는5, 6번째 위치에 있고, TI 모두 0이므로 GDT 존재함을 있습니다.

 

2. 콜게이트 설정

GDT 게이트 디스크립터를 만들었습니다.

descriptor7:

                  dw 0

                  dw SysCodeSelector

                  db 0x02

                  db 0xEC

                  db 0

                  db 0

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x00

SysCodeSelector (ox08)

1

11

0

1

1

0

0

 

사용안함

0x2

0x00

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

커널 루틴의 오프셋 부분이 0으로 설정되어 있고, 부분은 Protected Mode 들어오기 전에 다시 채워 넣을 것입니다. 미리 인수의 개수를 2 지정하여 인수를 2 사용합니다.

Printf 오프셋은 0x00AD 입니다. (채워질 주소 값은 0x10000+0x00AD = 0x100AD)

                  xor eax, eax

                  lea eax, [printf]

                  add eax, 0x10000

                  mov [descriptor7], ax

                  shr eax, 16

                  mov [descriptor7+6], al

                  mov [descriptor7+7], ah

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x00AD

SysCodeSelector (ox08)

1

11

0

1

1

0

0

 

사용 안함

0x2

0x0001

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Protected 모드로 들어온 , 임의의 값을 tts_esp0 넣어, 커널이 사용할 스택으로 지정합니다. 여기서는 일단 PM_Start 주소를 지정 합니다. 스택은 거꾸로 자람으로 메모리의 주소 마이너스 방향으로 자라납니다 . PM_Start 위에는 이상 사용하지 않는 루틴이 존재하므로 사용해도 좋습니다.

                  lea esp, [PM_Start]

 

                  mov ax, TSSSelector

                  ltr ax

 

                  mov [tss_esp0], esp

                  lea eax, [PM_Start-256]

                  mov [tss_esp], eax

 

 

3. 유저 모드로의 태스크 스위칭

유저 모드의 태스크를 실행시키겠습니다. Protected Mode 들어와 TR 레지스터, TSS영역을 세팅 , 유저모드 처럼 설정하여 IRET 명령을 통해 유저 모드의 태스크를 실행시킵니다.

(1) 세그먼트 셋팅 유저 데이터 영역 세그먼트로

                  mov ax, UserDataSelector

                  mov ds, ax

                  mov es, ax

                  mov fs, ax

                  mov gs, ax

 

데이터 관련 세그먼트에 UserDataSelector 값을 넣어줍니다.

현재 ESP 레지스터에는 커널 모드의 스택의 시작 주소를 지정하는 PM_Start 저장되어 있으므로 유저 모드 스택으로 바꾸어 주기 위해 PM_Start-256 값을 지정합니다.

Lea esp, [PM_Start-256]

 

(2) 인터럽트에 걸린 처럼 스택을 채우기

인터럽트 루틴에서 유저 모드의 레지스터를 스택에 저장해놓았다가 수행을 마치고, 유저 모드로 돌아갈 다시 복귀 시킵니다. 인터럽트 루틴을 수행한 것은 아니지만, 그런 상황을 다음과 같이 재현하여, 유저 태스크를 실행시킬 것입니다.

UserDataSelector(SS)

Esp(ESP)

0x200(EPLAGS)

UserCodeSelector(CS)

User_process(EIP)

 

 

Push 명령어로 다음과 같이 스택을 꾸며줍니다.

                  push dword UserDataSelector

                  push esp

                  push dword 0x200

                  push dword UserCodeSelector

                  lea eax, [user_process]

                  push eax

                  iretd

 

IRET으로 돌아갈 유저 모드에서 사용하는 SS 복구되지만 DS, ES, Fs, GS 셀렉터들은 값이 복구 되지 않아 미리 값을 채웠습니다.

EIP user_process 함수의 주소(오프셋) 지정되어 있기 때문에 user_process 실행합니다.


4. 콜게이트를 사용한 시스템

커널 모드의 함수인 printf 실행하기 위해 게이트를 이용하여 시스템 콜을 합니다. 다음과 같이 스택이 만들어 집니다.

 

(1) 특권 레벨 3 스택 : 인자2

80*2*7

 

Parameter1

ESP

 

 

(2) 특권 레벨1 스택 :

 

SS

 

ESP

 

80*2*7

EBP+8

Parameter1

 

Cs

EBP

EIP

 

ES

ESP

EAX

 

printf:

                  mov ebp, esp

                  push es

                  push eax

                  mov ax, VideoSelector

                  mov es, ax

                  mov esi, [ebp+8]

                  mov edi, [ebp+12]

 

Printf 함수에서는 먼저 esp(tss esp0) 값을 ebp 지정을 합니다. 프로그램 진행 중에 바뀔 esp 값을 복원하기 위해 백업해 것입니다.

EBP+8 하여 첫번째 인수인 문자열을 접근 있고, 두번째 인수는 EBP+12 사용해 접근 있습니다.

CPU의 보호 레벨은 0~ 3, 4개로 실제로 쓰이는 것은 0과, 3 2개입니다. (숫자가 아닌 보안 수준이)낮은 레벨의 태스크가 높은 레벨의 데이터를 참조(3 레벨의 태스크가 0 레벨 태스크) 하면 일반 보호 에러(#GP)가 발생하게 됩니다.

1. CPU 보호 레벨

 

1) CPL(Current Priviliege level)

현재 실행되고 있는 태스크의 특권 레벨로, CS, SS 셀렉터 레지스터의 0, 1번째 비트에 있는 수 입니다. 프로그램이 다른 특권 레벨의 코드 세그먼트로 제어가 바뀌면 CPU는 CPL을 변경하게 됩니다.

 

2) DPL(Description Privilege Level)

디스크립터를 통해서 세그먼트 영역으로 접근할 때, 항상 CPL과 DPL을 비교하여 접근이 가능한지 불가능한지 판단이 이루어 지게 됩니다.

 

3) RPL(Requested Privilege Level)

콜게이트(낮은 특권 레벨에서 높은 특권 레벨의 루틴을 사용할 수 있게 하는 관문)을 통해 레벨 3인 프로세스가 특권 레벨 0에 해당하는 루틴을 실행할 때, 일시적으로 특권 레벨 0에 해당하는 데이터 영역에 접근할 수 있습니다. 이러한 현상을 사용하여 악용하는 것을 막기 위해 OS는 RPL 값을 사용합니다.

어떤 레벨의 프로세스가 루틴을 불러들였는지 기록하여, 낮은 특권 레벨이 루틴을 불렀을 때 높은 특권 레벨의 데이터 영역을 보호합니다.

 

2. 콜 게이트

콜게이트는 인터럽트, 예외와 마찬가지로 낮은 특권 레벨의 프로그램이 높은 특권 레벨로 변경되는 수단으로 사용됩니다. 하드웨어 인터럽트와 예외는 응용 프로그램과 상관 없이 특권 레벨이 변경되지만, 소프트웨어 인터럽트와 콜게이트는 낮은 특권 레벨의 프로그램에 따라 높은 특권 레벨의 루틴을 사용할 수 있습니다.

컴퓨터의 RAM, 하드디스크에 들어 있는 자료, 그리고 하드웨어 입출력 장치등은 커널에 의해서 관리되고 유저 프로그램은 커널의 루틴의 도움을 받아 이러한 자료들을 잠시 일부분 사용할 수 있습니다.

콜게이트도 GDT 테이블의 디스크립터로 처리되며, 낮은 특권 레벨의 프로그램이 높은 특권 레벨의 프로그램의 일부분을 사용하기 위한 창구입니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

커널 루틴의 오프셋 15 - 0비트

핸들러의 코드 세그먼트 셀렉터

P

DPL

0

1

1

0

0

 

사용안함

인수의 개수

커널 루틴의 오프셋 31 - 16

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

이 디스크립터를 GDT에 지정하고, 유저 프로그램은 코드 셀렉터와 마찬가지로 디스크립터를 셀렉터로 선택하여 JMP나 CALL 명열을 내릴 수 있습니다.

 

3. 코드와 데이터의 특권 레벨 관계

CALL 명령어는 낮은 특권 레벨에서 높은 특권 레벨로, RET 명령은 높은 특권 레벨에서 낮은 특권 레벨로 이동하는 명령어 이기 때문에 이 명령어를 사용합니다. JMP 는 특권 레벨 간의 이동이 불가능 하며 JMP를 이용하여 특권 레벨의 이동을 사용해야 한다면 콜게이트 사용하여야 합니다.

 

(1) CALL 과 RET의 특권 레벨 변화

Q : CALL 명령어로 특권 레벨 0의 루틴을 불러 낼 때

A : CS 셀렉터의 0, 1 비트에 00이 들어가고 CPL은 0이 됩니다.

Q : RET 명령어로 다시 특권 레벨 3으로 돌아갈 때

A : CS 셀렉터의 0, 1비트에 11이 들어가고 CPL은 1이 됩니다.

 

코드 세그먼트 영역의 특권 레벨 DPL과 CPL은 어떤 상황에서 든 기본적으로 값이 같습니다.

예외 >> Conforming 세그먼트

특권 레벨 0의 루틴을 불러내도 CPL의 값은 그대로 3인 채 실행되었다 IRET으로 돌아옵니다. (Call gate와 관련)

 

(2) 특권 레벨과 접근 권한

특권 레벨 0에서는 모든 특권 레벨의 데이터 세그먼트 영역에 접근이 가능하지만 낮은 특권 레벨의 프로세스에서는 높은 특권 레벨의 데이터에 접근이 금지됩니다.

 

4. 특권 레벨 변동 시의 스택의 변화

 

스택 스위칭은 높은 특권 레벨의 루틴이 스택 공간의 부족 때문에 크래시 되지 않게 하기 위해서, 그리고 낮은 특권 레벨의 루틴이 스택을 통해 높은 특권 레벨의 루틴에 간섭하지 못하게 하기 위해 사용 됩니다.

핸들러나 커널 루틴으로 들어가기 전에 스택에 돌아올 주소 등을 넣어 두었다가 핸들러나 커널 루틴이 끝나고 다시 유저 모드 태스크로 돌아오기 위해 스택에 넣어 두었던 값들을 사용합니다.

 

(1) CALL 명령 – 유저 태스크 가 콜 게이트를 이용하여 커널 모드의 루틴

1) 호출 구문에 인수가 없는 경우

이 태스크 TSS 영역의 SS0, ESP0의 값을 참조하여 커널 모드의 스택에 현재 유저 태스크가 사용하고 있는 SS, ESP, 그리고 유저 태스크가 현재 진행중인 루틴의 주소, CS, EIP를 PUSH ( 스택방향 )

SS

 

ESP

CS

 

EIP

 

콜게이트에 지정된 커널 모드의 루틴 주소로 점프하고 실행

루틴을 마치고 RET 명령을 실행하여 커널 모드의 스택에 저장된 SS, ESP, CS, EIP를 POP(CS 를 먼저 검사한 후, 현재 커널 모드 보다 낮은 특권 레벨이면(콜게이트를 통한 특권레벨 이동일 경우???) SS와 ESP를 POP 합니다.)

2) 호출 구문에 인수가 있는 경우

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

커널 루틴의 오프셋 15 - 0비트

핸들러의 코드 세그먼트 셀렉터

P

DPL

0

1

1

0

0

 

사용안함

인수의 개수

커널 루틴의 오프셋 31 - 16

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

콜게이트를 통한 커널 루틴에서 사용할 인수의 개수를 미리 기입함

유저 레벨의 SS, ESP의 주소에 인수들을 PUSH 하고 콜게이트를 통하여 커널 레벨(특권 레벨 0) 루틴으로 들어가도 커널 레벨의 루틴에서는 스택 주소가 SS0, ESP0로 변경되어 pop 하여도 관련 없는 값을 참조하게 될 것입니다.

 

GDT 디스크립터의 인수의 개수 항목에 2가 들어있다면 유저 레벨의 스택에서 2개를 커널 모드의 스택에 복사합니다.

(스택의 방향은 원래 위에서 아래로 자라지만 여기서는 작성자의 착각으로;;; 반대로 표시했습니다. 반대로 뒤집으시면 거꿀로 자라는 스택이 됩니다)

Param1

스택 진행 방향

 

Param2

 

ESP

 

←SS

 
  

 

Param1과 Param2를 복사합니다.

 

SS

 

스택 진행 방향

ESP

Param1

Param2

 

CS

  
 

EIP

 

ESP0

 

←SS0

 
  

 

(2) 인터럽트 예외가 발생하였을 때의 스택

EFLAGS가 추가된 것을 제외하면 콜 게이트와 흡사합니다

1) 커널 모드에서 인터럽트가 걸렸을 때

특권 레벨 0에서 진행 되므로 SS, ESP 그대로 값을 사용하고, 스택에 EFLAGS, CS, EIP를 PUSH 합니다.

 

EFLAGS

↑스택 진행 방향

 

CS

  
 

EIP

 

ESP

 

←SS

 
  

 

2) 유저 레벨 에서 인터럽트가 발생했을 때

유저 레벨 태스크의 TSS 영역에서 SS0, ESP0를 CPU의 SS, ESP 레지스터에 복사하여, 콜게이트 경우에는 유저 모드의 태스크가 사용하던 SS, ESP, CS, EIP등의 레지스터의 값을 PUSH하여 저장합니다. IRET으로 유저 모드로 돌아갈때는 스택에 있는 값을 POP하여 CPU의 각각의 레지스터에 복원합니다.

 

스택 진행 방향

 
 
 
  
 
  

ESP

 

←SS

 
  

 

SS

 

스택 진행 방향

ESP

EFLAGS

 

CS

  
 

EIP

 

ESP0

 

←SS0

 
  

1. LIMIT 체크

(1) 세그먼트 디스크립터의 Limit G 비트

세그먼트 디스크립터의 LIMIT 프로그램 또는 프로세스가 세그먼트 외의 메모리 영역을 접근하지 못하게 합니다. 세그먼트 디스크립터의 lmit G 비트에 영향을 받고, G 0 , lmit 세그먼트에 명시된 값과 같으며(1byte 단위), G 비트가 1 경우 명시된 값의 0xFFF 입니다.

 

(2) 보호 예외(#GP)

세그먼트 영역에서 유효 LIMIT 전체 세그먼트 영역의 크기에서 1바이트 같습니다. (주소가 0 부터 시작 하기 때문입니다.)

다음과 같은 경우에는 #GP 일으킵니다.

Byte > limit-1(오프셋이 유효 Limit 보다 바이트 )

Word>limit-2(오프셋이 유효 Limit -1 보다 워드)

Dword>limit-4(오프셋이 유효 Limit-3 바이트 보다 더블 워드)

Qword>limit-8(오프셋이 유효 Limit-7 바이트보다 쿼드 워드)

 

(3) Limit 효과

무효한 포인터 계산등의 프로그래밍 에러 검출

다른 세그먼트에 있는 코드나 데이터 보호

 

(4) GDT, IDT, TSS

CPU GDT IDT 같은 테이블과 TSS 영역의 크기를 체크합니다.

GDT -> GDTR 2바이트에 해당하는 Limit 이용

IDT -> IDTR 2바이트에 해당하는 Limit 이용

TSS -> TR 레지스터에 2 바이트에 해당하는 Limit 이용

 

2. Type 체크

(1) S 비트와 Type 필드

S 비트가 0이면 Type 필드가 시스템 Type이라는 것이고, 1이면 코드나 데이터 세그먼트의 Type이라는 뜻입니다.

S 0일때의 타입입니다.

Type

게이트 종류

0x0

예약됨

0x1

16 비트

0x2

LDT

0x3

Busy 16비트 TSS

0x4

16 비트 콜게이트

0x5

태스크 게이트

0x6

16비트 인터럽트 게이트

0x7

16비트 트랩 게이트

0x8

예약됨

0x9

32비트 TSS

0xA

예약됨

0xB

Busy 32비트 TSS

0xC

32비트 콜게이트

0xD

예약됨

0xE

32비트 인터럽트 게이트

0xF

32비트 트랩 게이트

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 인터럽트 게이트 -> 0x8E

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0xE

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 TTS-> 0x89

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0x9

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

Ex) P비트가 1, DPL 00 S 0이고 32비트 TTS,B비트 1-> 0x8B

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

1

00

0

0xB

Base Address 23 – 16 비트

Base Address 31 – 24 비트

G

D

0

AVL

Lmit 19 - 16비트

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

CPU 다음과 같은 경우 type 정보를 조사합니다.

세그먼트 셀렉터가 세그먼트 레지스터에 로드

디스크립터가 세그먼트 레지스터에 이미 로드되어 있는 세그먼트에 명령이 액세스 

CALL JMP 명령의 오퍼랜드에 셀렉터가 있을 셀렉터에 대한 디스크립터의 Type 필드

 

80386 chip 에서 현재 태스크에서 다른 태스크로 스위칭하는 방법은 4가지가 있습니다.

1. TSS descriptor를 참조하여 JMP 혹은 CALL 실행

2. task gate 를 참조하여 JMP 혹은 CALL 실행

3. IDT의 task gate로 인터럽트나 예외 벡터 사용

4. NT flag가 SET 되어 있을 때, IRET 실행

 

이 포스팅에서는 1번의 경우의 태스크 스위칭에 대해서 알아볼 것입니다.

 

1. jmp를 이용한 태스크 스위칭

태스크 스위칭이 일어나 Process2 루틴이 실행되게 해 봅시다.

Tss2의 영역에 있는 EIP에 process2 루틴의 첫 번지를 넣어 process2: 부터 실행 되도록 합니다.

(원래 tss의 EIP영역에는 유저 영역의 스택 주소가 저장되지만, 설명의 편의를 위해 커널 모드의 스택 주소를 넣어 설명하겠습니다.)

 

먼저 다음과 같이 tss 세그먼트 디스크립터가 준비가 되어 있다고 가정합니다.

descriptor 4(TSS 세그먼트 디스크립터) – tss1을 지정, TSS 세그먼트 셀렉터는 TSS1Selector(0x20)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x013A

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 Descriptor 5 – tss2 를 지정, TSS 세그먼트 셀렉터는 TSS2Selector(0x28)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x68

0x01A2

1

00

0

1001

0x01

0x00

0

0

0

0

0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

그리고 LTR 명령으로 TSS1이 지정이 되었습니다.

Limit

0x68

Base Address

0x1013A

속성

 

이제 다음 명령을 통해 태스크 스위칭을 시도 합니다.

jmp TSS2Selector:0

 

TR 레지스터를 참조하여, GDT의 Tss!Selector(0x20)에 해당하는 디스크립터를 찾아, tss1영역을 찾은 뒤, 모든 레지스터 값을 저장합니다.

 

Offset은 아무 숫자나 사용해도 태스크 스위칭을 하여 저장되어 있던 EIP 레지스터를 CPU의 EIP 레지스터에 옮겨 전에 실행되던 주소부터 재개하게 때문에 아무 숫자나 사용해도 괜찮으며, 사실 의미가 없습니다.

 

다음과 같이 TSS의 셀렉터를 참조하여 다른 세그먼트 레지스터를 채운것과 같이 GDT에서 해당 세그먼트 디스크립터를 찾은 뒤 TS의 Hidden Part에 복사하여, TSS를 찾습니다.

 

Limit

0x68

Base Address

0x101A2

속성

 

찾은 tss2 영역에 저장되어 있는 레지스터의 값들을 현재 레지스터에 복원시킵니다.

복원 후, EIP 레지스터에 주소가 있다면, 여기서부터 프로그램이 시작되게 됩니다. 처음에 process2: 의 주소를 넣었으므로, process2가 실행 될 것입니다.

 

이 후에 태스크 스위칭이 일어나면 TR에 지정된 TSS 디스크립터에 현재 레지스터의 상태가 저장이 됩니다.

CPU는 TR 레지스터에서TSS 의 base address를 찾고, 여기에 레지스터의 상태를 저장합니다 (EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, ES, CS, SS, DS, FS, GS, flag register). 이때 TSS의 EIP 필드는 다음에 실행될 명령어를 가리키고 있습니다(EIP 필드가 가리키는 명령어의 전에 실행된 명령어가 task switch를 일으켰다고 볼 수 있습니다).

 

2. CALL 명령에 의한 Task Switching

(1) Task Switching 시점

CALL 명령과 IRET 명령에서 Task Switching이 일어납니다.

(2) EFLAG의 NT 비트와 디스크립터의 B 비트 변화

0 

0 

0 

0 

0 

0 

0 

0 

0 

0 

ID 

VIP 

VIF 

AC 

VM 

RF 

0 

NT

IOPL 

OF 

DF 

IF

TF 

SF 

ZF 

0 

AF 

0 

PF 

1 

CF 

 

EFLAGS 레지스터의 14 비트는 NT 비트로 사용됩니다. Nt는 nested의 약자로, IRET 명령어가 인터럽트 핸들러에서 사용되었는지, 태스크 스위칭에서 사용되었는지 구별할 때 사용합니다.

 

LTR 명령은 해당 TSS 디스크립터의 B 비트를 1로 세트 하며, CPU는 B 비트가 1로 세트되어 있는 디스크립터를 현재 실행되고 있는 태스크로 인식합니다.

CALL 명령이 실행되면, 스위칭 될 태스크의 EFLAGS의 NT 비트와 디스크립터의 B비트가 1로 세트 됩니다.

CALL 명령이 실행될 시점에는 반드시 B 비트가 0이어야 합니다.

IRET명령이 실행될 때, 스위칭 될 태스크의 B 비트가 무조건 1이어야 IRET이 실행이 가능합니다.

IRET을 이용하여 태스크 스위칭 될 때, 현재 태스크의 NT 비트와 디스크립터의 B 비트가 0으로 클리어 됩니다.

 

이러한 B 비트 관리 규약을 사용하는 이유는 이전 태스크로 돌아가지 못하게 하기 위해서 입니다.

 

(3) 이전 주소 저장

CALL 명령이 실행 되면, 새로 태스크 스위칭 될 태스크의 TSS 영역 이전 태스크로의 백링크에 현재 태스크의 TSS 디스크립터 셀렉터를 저장합니다.

IRET 명령이 실행이 되면 현재 태스크의 TSS 영역의 이전 태스크로의 백링크를 사용하여, 이전 태스크를 찾습니다.

 

3. CALL과 JMP

CALL과 IRET 을 이용하여 태스크 스위칭을 하는 경우, 하나의 체인으로 묶여, 그 이전의 태스크는 새로운 태스크의 실행이 끝날 때 까지 다시 실행될 수 없어, 비선점 프로세스 관리에 해당됩니다.

JMP를 이용하여 태스크 스위칭을 하는 경우, 태스크 끼리의 구속성이 없어 자유롭습니다.

+ Recent posts