1. PIC ( Programmable Interrupt Controller )

PC의 모든 외부로부터의 하드웨어 인터럽트는 8259A라는 칩을 통해서 입력을 받습니다. PIC를 제어 하기 위해, OS에는 PIC을 초기화하고 연결, 인터럽트를 받아들이는 방법, 받아 들인 인터럽트를 CPU에 전달하는 방법이 정의되어 있어야 합니다.

 

   

PIC master –slave 구조로 구성되고, 마스터 PIC IRQ 8 (0 – 7) 슬래이브 PIC IRQ 8 (8 – 15) 핀에 연결된 하드 웨어 장치의 인터럽트를 받아, CPU에게 알리고, CPU 인터럽트의 정보를 PIC으로부터 받아 해당 인터럽트 루틴을 실행합니다.

 

마스터 PIC 동작은 다음과 같습니다.

연결된 장치 , 인터럽트가 발생하면,

(1) INT 핀에 신호를 실어 CPU INT 핀에 신호를

 - CPU Eflags IE 비트를 1 세트하고, 인터럽트를 받을수 있는 상황이면 /INTA 핀으로 신호를 보냄

(2) CPU로부터 /INTA핀으로 신호가 오면, 번째 IRQ 연결된 장치에서 인터럽트가 발생했는지 데이터 버스를 통에 CPU 전달

- CPU PIC 보내온 데이터를 참조하여, Protected Mode 실행 중이면 번호에 맞는 디스크립터를 찾아 인터럽트 핸들러를 실행

슬레이브 PIC 동작은 다음과 같습니다.

(1) 자신의 INT 핀에 신호를 실어 마스터 PIC IRQ 2 핀에 인터럽트 신호를 보냄

- 마스터 PIC 자신의 IRQ 핀에서 인터럽트가 발생하여, CPU에게 INT핀으로 신호 보냄

(2) CPU /INTA 신호를 주면 데이터버스에 숫자를 실어  번째 장치에서 인터럽트가 발생했는지 알려줌

 

마스터 PIC 슬레이브 PIC 사용하기 위해서는 초기화를 해주어야 하며, 동작 등을 프로그래밍 주어야 합니다.

 

PIC 제어 하기 위해 4개의 프로그램을 정의 하겠습니다.

ICW1, ICW2, ICW3, ICW4

 

PIC 사용하는 I/O port 다음과 같은 주소를 사용합니다.

 

Master PIC Command : 0x20

Master PIC Data : 0x21

Slave PIC Command : 0xA0

Slave PIC Data : 0xA1

 

PIC 초기화 – ICW1

7

6

5

4

3

2

1

0

0

0

0

1

L

T

I

M

0

S

N

G

L

I

C

4

 

LTIM – 엣지 트리거링(0), 레벨 트리거링(1)

SNGL – 마스터 슬레이브 구조(0), 마스터만 사용(1)

마스터 PIC 초기화 하기 위해서 다음과 같이 세트 줍니다.

7

6

5

4

3

2

1

0

0

0

0

1

0

0

0

1

16진수로 계산해 보면 0x11 입니다.

 

I/O 명령어인 out 이용하여, 마스터 PIC I/O 주소 0x20 프로그램 합니다.

 

mov al, 0x11

out 0x20, al

 

딜레이를 주기 위해 다음 명령어를 2  실행시킵니다.

jmp $+2

nop

nop

 

명령들은 헥사코드로 0x00eb입니다. 따라서 이렇게 표시합니다

 

dw 0x00eb, 0x00eb

 

슬레이어 PIC I/O주소 0xA0 마찬가지로 세트 하고, 마찬가지로 딜레이를 줍니다.

7

6

5

4

3

2

1

0

0

0

0

1

0

0

0

1

 

 

out 0xA0, al

dw 0x00eb 0x00eb

 

PIC IQR 시작점 설정(IRQ 리맵핑): ICW2                       

7

6

5

4

3

2

1

0

Off7

off6

Off5

Off4

Off3

0

0

0

 

PIC 인터럽트를 받았을 IRQ 번호에 얼마를 해서 CPU 알려줄지 정합니다. 0 – 2 비트가 0 것은 숫자를 8 단위로 기재해야 된다는 뜻입니다.

마스터 PIC 다음과 같이 세트합니다.

7

6

5

4

3

2

1

0

0

0

1

0

0

0

0

0

 

mov al, 0x20

out 0x21, al

dw 0x00eb, 0x00eb

 

슬레이브 PIC 다음과 같이 세트합니다. IRQ 8 부터 시작 함으로, 앞서 마스터 PIC 지정한 것에서 8 더해서 슬레이브 PIC 0x28 세트 합니다.

7

6

5

4

3

2

1

0

0

0

1

0

1

0

0

0

 

mov al, 0x28

out 0xA1, al

dw 0x00eb, 0x00eb

 

만약 IRQ0 연결된 하드웨어에서 인터럽트가 발생한다면, ICW2 명령어에서 0x20 설정했기 때문에 IRQ 번호는 0x20 됩니다.

슬레이브 PIC IRQ0 (전체적으로 보면 IRQ8) 연결된 하드웨어 에서 인터럽트가 발생한다면,  0x28 됩니다.

 

마스터 PIC 슬레이브 PIC 구분 – ICW3

IRQ2 번에 슬레이브 PIC 연결되어 있다는 것을 마스터 PIC에게 알려줍니다.

7

6

5

4

3

2

1

0

S7

S6

S5

S4

S3

S2

S2

S0

 

S0 – S7 IRQ 선에 해당됩니다. 0 들어가면, 하드웨어 장치에 연결이 되어 있다는 것을 나타내고, 1 연결하면 슬레이브 PIC 연결되어 있음을 의미합니다.

7

6

5

4

3

2

1

0

0

0

0

0

0

ID2

ID2

ID0

 

번째 IRQ 핀에 연결되어 있는지 마스터 PIC 해당 비트를 1

3~7비트는 0으로 하여, ID0 – ID2 3비트를 사용하여 슬레이브 PIC 마스터 PIC IRQ 핀에 연결되어 있는지 세트합니다.

 

마스터 PIC IRQ 2번에 슬레이브 PIC 연결합니다.

마스터 PIC 이렇게 세트 합니다.

7

6

5

4

3

2

1

0

0

0

0

0

0

1

0

0

 

슬레이브 PIC 다음과 같이 세트 합니다.

7

6

5

4

3

2

1

0

 

 

 

 

 

 

1

0

 

mov al, 0x04

out 0x21, al

dw 0x00eb, 0x00eb

mov al, 0x02

out 0xA1, al

dw 0x00eb, 0x00eb

 

추가 명령어 : ICW4

7

6

5

4

3

2

1

0

0

0

0

S

F

N

M

B

U

F

M

/

S

A

E

O

I

U

P

M

 

SFNM BUF, M/S 기능은 우리가 사용하는 PC에서 구현되어 있지 않기 때문에 항상 0으로 설정합니다.

AEOI – PIC Reset 자동으로 할지, 수동으로 할지 나타냄

리셋을 자동으로 수행하도록 한다면 CPU IRQ 번호를 알린 바로 리셋 하며, 수동으로 수행하도록 한다면 CPU에서 IRQ 번호를 받아들이고, 인터럽트 루틴을 처리 다음, PIC 명령을 주어 리셋해야 합니다.

uPM - MCS-80/85 모드(0) , 8086(1)

 

IRQ 자동으로 리셋하고, 8086 모드로 실행되게 합니다.

다음과 같이 마스터 PIC 설정합니다.

7

6

5

4

3

2

1

0

0

0

0

0

0

0

0

1

 

다음과 같이 슬레이브 PIC 설정합니다.

7

6

5

4

3

2

1

0

0

0

0

0

0

0

0

1

 

mov al, 0x01

out 0x21, al

dw 0x00eb, 0x00eb

out 0xA1, al

dw 0x00eb, 0x00eb

 

인터럽트 막기

 

Protected Mode 넘어가면서 IDT 설정, 하드웨어 인터럽트 작업을 하는 동안 모든 인터럽트를 불가능으로 전환합니다.

 

마스터 PIC IRQ핀에서 슬레이브 PIC 연결된 IRQ2 핀을 제외하고 인터럽트를 막고, 슬레이브 PIC 모든 인터럽트를 막습니다.

 

슬레이브 PIC 다음과 같이 설정합니다.

7

6

5

4

3

2

1

0

1

 

1

1

1

1

1

1

1

 

마스터 PIC 다음과 같이 설정합니다.

7

6

5

4

3

2

1

0

1

1

1

1

1

0

1

1

 

mov al, 0xFF

out 0xA1, al

dw 0x00eb, 0x00eb

mov al, 0xFB

out 0x21, al

 

 

'Operating Systems > OS 커널 제작' 카테고리의 다른 글

4. Interrupt 와 Exception (4) - 예외  (0) 2015.01.20
4. Interrupt 와 Exception (3) – 인터럽트 핸들러  (0) 2015.01.19
3. Protected Mode (3)  (0) 2015.01.17
3. Protected Mode (2)  (0) 2015.01.15
3. Protected Mode (1)  (0) 2015.01.14

5. 16비트 Real Mode에서 32비트 Protected Mode

 

(1) CR0 레지스터 설정

GDT를 준비하였으면, CPU에게 이제부터 Protected Mode로 넘어간다는 것을 알려야 합니다.

이런 역할을 하는 것이 CR0의 마지막 비트입니다.

다음 그림은 CR0레지스터 입니다.

CR0 레지스터는

31 bit (PG:Paging) – 만약 이 비트가 1일 때, 페이징과 CR3 레지스터를 활성화 하고 0일 때 페이징을 비활성합니다.

30 bit (CD : Cache disable) – 전역적으로 메모리 캐시를 활성화하거나 비활성화 합니다.

29 bit(NW:Not-write through) – 전역적으로 캐시를 통해 쓰는 것을 활성화 하거나 비활성화 합니다.

18 bit(AM:Alignment mask) – AM이 세트 되어 있으면 alignment 체크를 활성화하고, AC 플래그(EFLAGS에 있습니다.)가 세트 되어 있으면 권한이 level3 임을 의미합니다.

16 bit(WP:Write protect) – CPU가 페이지에 쓸 수 있는지 결정하여, 권한 레벨이 0일 때 read-only로 표시합니다

5 bit(NE:Numeric error) : 세트되어 있을 때 내부 x87 부동소수점 에러 리포팅을 활성화합니다. 0일 때, 내부 PC x87 에러를 검사합니다.

4 bit(ET:Extension type) : 386에서 , 외부 수리 보조처리기가 80287 인지 80387인지 구별합니다.

3 bit(TS:Task Switced): x87 명령이 사용된 이후에만 태스크 스위치 시에 x87 context를 저장합니다.

2 bit(EM:Emulation) : 세트되어 있을 때, x87 부동소수점 유닛이 존재하지 않고, 클리어 되어 있을 때, x87 FPU가 존재합니다.

1bit(MP:Monitor coprocessor) : WAIT/FWAIT 명령의 작용을 TS 플래그와 함께 제어합니다.

0bit(PE:Protected Mode Enable):세트 되었을 때 시스템은 protected mode 이며, 해제되었을때, real mode 입니다.

 

0비트를 set 해주면 Protected Mode가 활성화 됩니다.

 

mov eax, cr0

or eax, 0x00000001

mov cr0, eax

 

위 코드는 GDTR에 gdt를 등록하고 난 뒤에, CPU에게 이제부터 Protected Mode로 바꾸는 것을 알리기 위해

CR0의 최 하위 비트를 세트 해주는 코드입니다.

CR0의 다른 비트에 영향을 끼치지 않게 하기 위해여 OR 연산을 하였습니다.

 

(2) CPU 파이프라인 유닛 비우기

Protected Mode로 변경되었으나 CPU에는 파이프라이닝으로 인해 16비트 명령어 코드가 남아있을 수 있습니다. 따라서 jmp 명령어를 이용하여 CPU의 16비트 코드를 깨끗하게 지웁니다.

jmp $+2

nop

nop

 

 

(3) 세그먼트 레지스터 값 변경

세그먼트 레지스터가 16비트의 값을 포함하지 않도록 바꿔주어야 Protected Mode로 넘어갈 수 있습니다.

Db 0x66

Db 0x67

Db 0xEA

Dd PM_Start

Dw SysCodeSelector

 

Far JMP를 하기 위해서 CS레지스터에 새로운 세그먼트 값이 들어갑니다. CS에는 SysCodeSelector가, EIP 레지스터에는 PM_Start가 들어갑니다.

0x66 은 16비트에서 오퍼랜드를 32비트로 쓰겠다는 의미이고, 0x67은 16비트 모드에서 주소 값을 32비트로 쓰겠다는 의미입니다. 이 prefix를 붙이는 이유는 아직 명령어가 16비트 부분이기 때문입니다. 따라서 이를 어셈블리어로 옮기면

jmp DWORD SysCodeSelector:PM_Start

가 됩니다.

 

부트로더에서 Kernel로 진입하기 위해 데이터 세그먼트들을 CS와 같이 0x1000으로 할당하였습니다.

(커널을 0x10000 위치에 로드 했습니다.)

아직 그 값이 남아 있으므로 그 다음 세그먼트 레지스터를 Protected Mode에서 사용할 수 있도록, 다음과 같이 세그먼트 레지스터(셀렉터)에 데이터 세그먼트를 넣어 초기화 합니다.

 

[bits 32]

 

PM_Start:

mov bx, SysDataSelector

mov ds, bx

mov es, bx

mov fs, bx

mov gs, bx

mov ss, bx

 

4. Protected Mode의 주소 지정 방법

 

 

Real Mode에서는 16비트의 세그먼트 레지스터를 사용한 반면, Protected Mode에서는 각 세그먼트 레지스터가 셀렉터 레지스터와 디스크립터 레지스터로 나누어 집니다.

오른 쪽에 있는 디스크립터 레지스터는 다음과 같은 형태를 취하고 있습니다.

 

상위 13비트는 디스크립터를 찾기 위한 인덱스가 저장되고, 두번째 비트에는 TI 값, 0, 1 비트에는RPL 값이 들어갑니다.

실제로 프로그래머가, 세그먼트 레지스터에서 다룰 수 있는 것은 이 세그먼트 셀렉터입니다.

 

세그먼트 디스크립터 찾기

이 세그먼트 셀렉터를 이용하여 세그먼트 디스크립터를 찾는 방법을 설명하겠습니다.

본격적으로 찾기 전에 앞서 다음의 코드가 실행이 이미 되어 있어, gdtr 레지스터에 gdt가 등록이 되어 있다고 가정 하겠습니다.

lgdt[gdtr]

 

gdtr:

dw gdt_end – gdt – 1

dd gdt+0x10000

 

 

47

 Base address = 0x00010000

16

15

Limit  = 0x1F

0

00

00

00

00

00

01

00

00

00

00

00

1F

Gdtr 레지스터

 

예시로 세그먼트 셀렉터에 지난 포스팅에서 만든 데이터세그먼트 디스크립터를 나타내는 값을 넣어 보겠습니다.

 

SysCodeSelector equ 0x08

dw 0xFFFF              ; limit 0 - 15

dw 0x0000              ; base 0 - 15

dd 0x01                    ; base 16 - 23

db 0x9A                   ; 10011010(2)

db 0xCF                   ; 11001111(2)

db 0x00                  ; base 24 - 63

 

mov bx, SysCodeSelector

mov cs, bx


CS 레지스터에 SysCodeSelector equ 0x08 값을 넣어 봅니다.

15

 

 

 

 

 

 

 

 

 

 

 

3

2

1

0

 

 

 

 

 

 

 

 

 

 

 

0

1

0

0

0

들어간 값은 다음과 같이 해석할 수 있습니다.

3 - 15 (인덱스) : 1

2 (TI) : 0

0 – 1 (RPL) : 0

 

먼저 GDTR레지스터에 등록되어 있는 GDT의 Base Address를 가져와 GDT의 첫번째 디스크립터의 물리 주소 0x10000를 구합니다.

다음 그림은 지난 포스트에서 만든 4개의 디스크립터가 메모리에 저장된 모습입니다. (Little Endian)

각 세그먼트의 경계는 각기 다른 색으로 표시 되어 있습니다.

   

(hex)

00

01

10

11

 

10000

00

00

00

00

gdt:

10004

00

00

00

00

10008

FF

FF

00

00

SysCodeSelector :

1000C

01

9A

CF

00

10010

FF

FF

00

00

SysDataSelector :

10014

01

92

CF

00

10018

FF

FF

00

80

SysVideoSelector :

1001C

0B

92

40

00

10020

 

 

 

 

 


 

세그먼트 셀렉터에 주어진 인덱스를 가져와 디스크립터의 크기(8byte) 만큼 곱하고, 구한 Base Register 만큼 더합니다.

1 * 8 + 0x10000 = 0x10008

물리주소 0x10008을 찾아가 본 결과, 코드 세그먼트 디스크립터를 찾을 수 있었습니다.

디스크립터를 찾았으면 DS 세그먼트 셀렉터의 RPL과 코드 세그먼트 디스크립터의 DPL을 대조해 봅니다.

CS 세그먼트 셀렉터의 RPL은 00이며, 코드 세그먼트 디스크립터의 DPL은 00 임으로 일치합니다.

값이 일치한다면 CS 세그먼트 디스크립터 레지스터에 코드 세그먼트 디스크립터의 내용을 복사합니다.

 

세그먼트와 오프셋을 이용해 주소 지정

 

Lea esi, [ds:msgPMode]

 

위 코드에서 ds:msgPMode의 선형주소를 구해봅시다.

 

Ds의 세그먼트 디스크립터 레지스터입니다.

Limit

0xFFFFFFFF

Base Address

0x10000

속성

msgPMode의 주소를 구하니 0x65라는 것을 알 수 있습니다.(little endian를 고려합니다)

26

27 ;------------------------------------------------------------;

28 ;*********** 여기부터 Protected Mode입니다. *****************;

29 ;------------------------------------------------------------;

30

31 [bits 32]

32

33 PM_Start:

34 00000025 66BB1000     mov bx, SysDataSelector

35 00000029 8EDB     mov ds, bx

36 0000002B 8EC3     mov es, bx

37 0000002D 8EE3     mov fs, bx

38 0000002F 8EEB     mov gs, bx

39 00000031 8EEB     mov gs, bx

40 00000033 8ED3     mov ss, bx

41

42 00000035 31C0     xor eax, eax

43 00000037 66B81800     mov ax, VideoSelector

44 0000003B 8EC0     mov es, ax

45 0000003D BF54060000     mov edi, 80*2*10+2*10

46 00000042 3E8D35[65000000]     lea esi, [ds:msgPMode]

47 00000049 E802000000     call printf

48

49 0000004E EBFE     jmp $

50

51 ;------------------------------------------------------------;

52 ;*************** Sub Routines *******************************;

53 ;------------------------------------------------------------;

54

 

이제 세그먼트와 오프셋을 이용해 물리 주소를 찾아 봅시다. 먼저

(1) 오프셋의 주소를 lmit와 비교하여 작은지 검사합니다.

 

DS 세그먼트의 디스크립터 레지스터의 limit는 0xFFFFFFFF 로 오프셋 주소가 더 작음을 알 수 있습니다.

0xFFFFFFFF > 0x65

 

(2)lmit보다 작다면 세그먼트의 Base Address와 더해줍니다.

 

0x10000 + 0x65 = 0x10065

 

위 과정을 거치면 선형주소 , 즉 물리 주소가 완성됩니다.

 

1. 16비트 Real Mode

Real Mode는 PC를 부팅할 때 맨 처음 동작하는 CPU모드로, 16비트 명령어로 동작하며 16비트 형식의 레지스터를 사용합니다.

Real Mode의 특징

(1) 범용 레지스터

 

16비트 모드에서의 레지스터들은 16비트, WORD로 되어 있고 0에서 0xFFFF까지 저장할 수 있습니다.

(2) 주소지정

주소지정은 세그먼트 레지스터와 오프셋 레지스터를 사용하여 할 수 있습니다.

이 두 레지스터는 16비트 모드에서 각각 16비트 값만 취할 수 있습니다.

만약 물리 번지 0x30004F로 점프(예 – jmp)를 하려고 시도를 한다면 IP 레지스터 또한 16비트의 값만 취급 하므로 점프를 할 수 없습니다.

그러므로 IP 레지스터(오프셋으로 사용)가 현재 지정한 세그먼트 영역 안에서 0x0000 ~ 0xFFFF의 범위 밖으로 나갈 수 없어 점프가 불가능 한 경우,

Far jmp를 사용하여 세그먼트와 오프셋을 함께 바꾸어야 합니다.

Jmp operation

 

Opcode

Mnemonic

Description

EB cb

JMP rel8

Jump short, relative, displacement relative to next instruction.

E9 cw

JMP rel16

Jump near, relative, displacement relative to next instruction.

E9 cd

JMP rel32

Jump near, relative, displacement relative to next instruction.

FF /4

JMP r/m16

Jump near, absolute indirect, address given in r/m16.

FF /4

JMP r/m32

Jump near, absolute indirect, address given in r/m32.

EA cd

JMP ptr16:16

Jump far, absolute, address given in operand.

EA cp

JMP ptr16:32

Jump far, absolute, address given in operand.

FF /5

JMP m16:16

Jump far, absolute indirect, address given in m16:16.

FF /5

JMP m16:32

Jump far, absolute indirect, address given in m16:32.

 

X86 어셈블리언어에서 jmp 명령은 무조건 분기를 수행합니다. 내부적으로는 IP 레지스터를 변경하는 것으로 수행됩니다. CPU real mode인지, protected 모드인지에 따라, 명령의 오퍼랜드가 16비트인지, 32비트인지, 혹은 세그먼트:오프셋 형식을 가지는지, 오버라이드 명령어가 사용되는지에 따라서 각각 다른 opcode 맵핑 되어 있습니다. 점프에는 근접 점프, 조건 점프, 레지스터의 간접 점프, 즉치값을 이용한 점프 등이 있습니다.

1. 16비트 포인터를 사용한 간접 점프

2. 세그먼트 안에서 32비트 포인터를 이용한 long jmp

3. eax 레지스터를 이용한 register-indirect absolute jump // 프로텍트 모드에서만 가능

 

 

(3) 세그먼트

Real mode에서의 세그먼트 접근의 위험성

Real 모드에서는 세그먼트 내에서는 어떤 메모리 영역에도 접근이 가능하여 ,

다른 프로그램의 실행코드 등을 임의로 변경시키는 등의 위험한 상황이 발생할 수 있습니다.

세그먼트 지정의 제약

Real 모드에서 세그먼트:오프셋 형태로 주소를 지정하게 되면 CPU는 세그먼트 값에 16을 곱한 뒤 오프셋과 더하는 연산을 하여 주소를 구하게 됩니다. 따라서 16비트 모드에서는 세그먼트는 항상 16비트 단위로 지정이 되게 됩니다.

(4) 물리주소

Real Mode에서 최대한 지정할 수 있는 메모리 주소는 0xFFFF:0xFFFF = 0x10FFEF 입니다.

따라서 아무리 램의 용량이 많다고 하여도 접근할 수 있는 주소 영역은 0x000000 에서 0x10FFEF 입니다.

거기에 0xA0000 ~ 0x100000까지는 그래픽 모드 비디오 메모리, 텍스트 모드 비디오 메모리, BIOS 등이 사용하는 영역 등이 정해져 있어 실제로 사용자가 프로그램을 저장할 수 있는 공간은 더 적습니다. 실제로 사용할 수 있는 영역은0x000~0xA0000, 640KB밖에 사용할 수 없습니다.

 

2. Protected Mode

 

 

Protected Mode의 특징

 

(1) 최대 4 GB의 영역을 지정할 수 있음

(2) 커널 시스템 영역과 유저 영역으로 나누어, 커널 시스템 영역을 유저 애플리케이션으로부터 보호하거나 코드/데이터 세그먼트를 분리시켜 코드를 일거나 쓰지 못하도록 막을수 있음

(3) 각 세그먼트의 시작 주소를 물리주소로 지정 할 수 있고, 1byte 단위로 지정할 수 있음

 

3. GDT

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

Lmit 15 - 0비트

Base Address 15 - 0비트

P

DPL

S

Type

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

 

(1) Base Address

이 세그먼트의 시작 주소로, 하위 0 – 15 비트와 상위 16 – 19 비트로 나눠서 저장합니다.

(2) Lmit

이 세그먼트의 한계점(크기)를 나타냅니다. 오프셋은 이 숫자를 넘어갈 수 없고 넘어가게 되면 GP fault(Protected Mode 규약 위반)이 발생합니다.

Base Address처럼 나누어서 하위 0 – 15 비트와 ,16 – 23 비트, 상위 24 - 31비트로 나누어서 저장합니다.

G 비트가 0일 경우 Limit 값의 단위는 그대로 이고, 1일 경우 0xFFF을 곱하여 한계점으로 정합니다.

(3) P bit

이 세그먼트가 메모리상에 존재하는지를 나타내는 값으로, 커널 프로그램의 메모리 관리 루틴에 사용됩니다. 페이지와 관련된 비트입니다.

(4) DPL

이 세그먼트가 커널 레벨인지 유저 레벨인지 나타냅니다.

 

인텔 x86 계열 CPU에는 4가지 레벨의 권한이 있으며, 0이면 커널 레벨이고 3이면 유저 레벨입니다.

 

(5) S bit

시스템 세그먼트(0)인지 코드 혹은 데이터 세그먼트(1)인지를 결정합니다. 항상 1로 지정합니다.

(6) Type


 

Type Field

디스크립터

타입

설명

10진수

11

10

9

8

 

E

W

A

0

0

0

0

0

데이터

읽기 전용

1

0

0

0

1

데이터

읽기 전용, 액세스

2

0

0

1

0

데이터

읽기/쓰기

3

0

0

1

1

데이터

읽기/쓰기, 액세스

4

0

1

0

0

데이터

읽기 전용, EXPAND DOWN

5

0

1

0

1

데이터

읽기전용, EXPAND DOWN, 액세스

6

0

1

1

0

데이터

읽기/쓰기, EXPAND DWON

7

0

1

1

1

데이터

읽기/쓰기, EXPAND DOWN, 액세스

 

 

C

R

A

 

 

8

1

0

0

0

코드

실행 전용

9

1

0

0

1

코드

실행 전용, 액세스

10

1

0

1

0

코드

실행/읽기

11

1

0

1

1

코드

실행/읽기, 액세스

12

1

1

0

0

코드

실행 전용, CONFORMING

13

1

1

0

1

코드

실행 전용, CONFORMING, 액세스

14

1

1

1

0

코드

실행/읽기 전용, CONFORMING

15

1

1

1

1

코드

실행/읽기 전용, CONFORMING, 액세스

  • 최상위 비트 : 코드 세그먼트/데이터 세그먼트 지정
  • 마지막 비트 : 액세스 비트

    어떤 프로그램이 이 세그먼트에 접근했을 때 cpu가 1로 바꿔주고, 클리어는 커널이 함

  • 첫번째 비트가 0일 때

    코드 세그먼트로 사용

    두번째 비트 : Conforming 지원 여부(CPU Protected Mode의 보호 정책)

    세번째 비트 : 세그먼트의 읽기 권한


GDT 첫번재 디스크립터는 NULL 디스크립터를 기재해 주어야 됩니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0x0000

0x0000

0

00

0

0000

0x00

0x00

0

0

0

00

00

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

이것을 코드로 나타내면 다음과 같습니다.

gdt:

dw 0        ; limit 0 - 15

dw 0        ; base 0 - 15

dd 0         ; base 16 - 23

db 0         ; type, s, DPL, P

db 0         ; limit 16 – 19, AVL, 0, D, G

db 0        ; base 24 - 63

 

코드 세그먼트 디스크립터의 예시 입니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

00

1

1010

0x01

0x00

1

1

0

00

0xF

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

위의 디스크립터를 코드로 나타내면 다음과 같습니다.

SysCodeSelector equ 0x08

dw 0xFFFF              ; limit 0 - 15

dw 0x0000              ; base 0 - 15

dd 0x01                    ; base 16 - 23

db 0x9A                   ; 10011010(2)

db 0xCF                   ; 11001111(2)

db 0x00                  ; base 24 - 63

 

5번째 줄의 바이트 상위 4비트는 0x9 , 4번째 비트가 1이므로 코드 세그먼트라는 것을 있습니다.

마찬가지로 번째 비트와 번째 비트가 00 인데 이것으로 DPL 0이고 따라서 커널 영역으로 사용됨을 나타내는 것을 있습니다.

 

데이터 세그먼트 디스크립터의 예시입니다.

 

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x0000

1

00

1

0010

0x01

0x00

1

1

0

00

0xF

 

SysDataSelector equ 0x10

dw 0xFFFF              ; limit 0 - 15

dw 0x0000              ; base 0 - 15

dd 0x01                    ; base 16 - 23

db 0x92                    ; 10010010(2)

db 0xCF                   ; 11001111(2)

db 0x00                  ; base 24 - 63

 

Type 필드가 0010임으로 위의 type 표를 보면, 세그먼트기 데이터 세그먼트이고, 읽기/쓰기가 가능하다는 것을 있습니다.

 

비디오 메모리를 위한 디스크립터의 예시입니다.

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

0xFFFF

0x8000

1

00

1

1010

0x0B

0x00

1

0

0

00

0x0

63

62

61

60

59

58

57

56

55

54

53

52

51

50

49

48

 

 

VideoSelector equ 0x18

dw 0xFFFF              ; limit 0 - 15

dw 0x8000              ; base 0 - 15

dd 0x0B                   ; base 16 - 23

db 0x92                    ; 10010010(2)

db 0x40                    ; 10000000(2)

db 0x00                  ; base 24 - 63

 

6번째 줄을 보면, 상위 4비트가 4 것을 알수 있으며, G 0 되므로, 세그먼트의 크기가 바이트 단위로 지정이 되었습니다.

 

GDTR 레지스터



 

GDTR GDT 어디에 있는지, 개나 있는지 저장하고 있는 레지스터입니다. 레지스터에 만든 GDT 등록시켜 CPU 사용할 있도록 해야됩니다.

처음 0 – 15 까지의 비트는 사용할 있는 GDT 크기를 저장하고, 16 – 47까지의 비트는 GDT 시작점 주소를 저장합니다.

GDTR 명령어는 GDT 저장하고 있는 주소를 오퍼랜드로 받아, GDT 등록합니다.

Lgdt[gdtr]

 

gdtr:

dw gdt_end – gdt – 1

dd gdt+0x10000

 

Gdt 첫번째 gdt 위치를 가리키며, gdt_end gdt 끝을 나타냅니다. 0부터 시작함으로 1 빼줍니다.

두번째는 커널의 베이스 어드레스가 0x10000이므로 0x10000 더해줍니다.

Gdtr 넣어야 하는 값은 세그먼트:오프셋이 아니라 물리 주소를 넣어야 합니다. 따라서 물리 주소의 값을 만들어서 넣어야 됩니다.



lgdt 명령어 동작

if(OperandSize == 16 {

GDT.Limit = Source[0..16];

GDTR.Base = Source[16..47] & 0xFFFFFF;

}

else { //OperandSize == 32

GDTR.Lmit = Source[0..15];

GDTR.Base = Source[16..47];

}

예외상황

Protected Mode

 #UD

source 오퍼랜드가 메모리 영역이 아닐때 

 #GP (0)

현재 권한 레벨이 0이 아닐때

메모리 오퍼랜드의 유효 주소가 Cs, DS, ES, FS, GS 세그먼트의 리미트를 벗어날때

Null  세그먼트 셀렉터를 가지고 있는 DS, ES, FS, GS 레지스터가 메모리에 접근하는데 사용될 때  

 #SS 9)0

메모리 오퍼랜드의 유효 주소가 SS 세그먼트 리미트를 벗어날 때 



1. IDT 만들기

IDT는 Interrupt Descriptor Table의 약자로, 인터럽트를 구현하기 위한 테이블입니다.

메모리 중 어느 곳이든 저장이 가능하며, GDT 디스크립터와 유사한 모양의 256개의 디스크립터로 구성됩니다.

 

다음 그림은 IDT의 구조입니다.

   

15

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

(1)핸들러의 오프셋 15 – 0

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

(3)P

(4) DPL

(5) 0

(6)D

(7)1

(8) 1

(9)0

사용안함

(10) 핸들러의 오프셋 31 – 16

63

 

 

 

 

 

 

 

 

 

 

 

 

 

 

48


 

(1)핸들러의 오프셋 15 – 0

처음 16비트 한 워드에는 인터럽트 핸들러가 자리하고 있는 RAM 상의 물리 주소의 0 ~ 15비트(오프셋)을 기입

 

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

인터럽트 핸들러가 자리하고 있는 코드 세그먼트 셀렉터의 값을 기입합니다.

인터럽트 핸들러는 항상 Protected 모드에서 동작하므로 커널 모드 코드 세그먼트 셀렉터의 값을 기입하면 됩니다.

 

(3)P bit

GDT 디스크립터와 마찬가지로 커널 모드의 코드 세그먼트가 RAM 상에 존재하는지 나타냄. 항상 1로 세트

 

(4)DPL

핸들러가 실행될 특권 레벨을 지정하는 플래그 입니다. 인터럽트 핸들러는 항상 커널 모드에서 동작하기 때문에 0 ~ 3 중 0 레벨을 지정 하여야 함으로 이진수로 00 기입합니다.

 

(5)0

이 디스크립터가 IDT에 위치한 인터럽트 관련 디스크립터라는 것을 CPU에게 알리기 위해 그대로 0 기입합니다.

 

(6)D

현재지정한 코드 세그먼트가 16비트인지 32 비트인지를 나타냅니다.

0이면 16비트를 나타내고 1이면 32비트를 나타내기 때문에 프로텍트 모드에서 동작되는 인터럽트 핸들러는 1로 세트 합니다.

 

(10) 핸들러의 오프셋 3 1 – 16

오프셋의 상위 16비트를 기입합니다.

 

강제사항은 아니지만 사용하고 있는PC의 메인보드의 인터럽트 관련 하드웨어가 256개의 인터럽트를 받아들이도록 디자인 되어 있기 때문에 여기서는 IDT를 메모리상에 256개를 만듭니다.

 

2. RAM 상에 IDT를 만들기

cld

mov ax, SysDataSelector

mov es, ax

xor eax, eax

xor ecx, ecx

mov ax, 256 ; ax 레지스터를 몇 번 복사 할 것인지 알려주는 카운터로 사용

mov edi, 0

 

loop_idt: ; 루프를 돌면서 idt를 복사

lea esi, [idt_ignore] ; idt_ignore은 idt 샘플

mov cx. 8 ; idt가 8byte 이다.

rep movsb ; ds:esi의 내용을 cx의 값(단위는 바이트) 만큼 es:edi 위치에 복사

dec ax

jnz loop_idt 

 

Rep movsb

Rep – 스트링 명령어를 반복

Movsb – move string byte 한 바이트 씩 si 레지스터에 저장된 위치의 데이터를 di 레지스터에 저장된 위치로 복사, ES:DI, DS:SI가 디폴트

Idt_ignore

idt_ignore:

dw isr_ignore ; 실행되어야 할 핸들러 루틴

dw SysCodeSelector

db 0

db 0x8E

dw 0x0001

 

샘플로 작성된 IDT의 내용이 디스크립터의 모양과 같음을 알 수 있습니다.


15

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

Isr_ignore

SysCodeSelector

1

00

0

1

1

 1

0

0

0x0001(커널이 0x10000 번지에 적재되기 때문에 0x000010000 더해저야 )

63

 

 

 

 

 

 

 

 

 

 

 

 

 

 

48

15

              

0


인터럽트 서브루틴의 예시

isr_ignore:

    push gs            ; CPU의 레지스터 값과 FLAG를 스택에 보존

    push fs             ;

    push esi             ;

    push ds             ;

    pushad             ;

    pushfd            ;

 

    mov ax, VideoSelector    ;인터럽트 처리

    mov es, ax        ;

    mov edi, (80*7*2)    ;    

    lea esi, [msg_isr_ignore];

    call printf

 

    popfd            ; CPU의 레지스터 값을 복원

    popad            ;

    pop ds            ;

    pop ds            ;

    pop fs            ;

    pop gs            ;

 

    iret            ; 인터럽트 루틴을 마치고 돌아감

 

3. IDTR

CPU가 인터럽트에 걸렸을 때 IDT를 참조할 수 있도록 IDTR 레지스터에 등록해야 합니다.

Lidt 명령어에 해당 내용을 담고 있는 주소를 operand로 주어 등록할 수 있습니다.

lidt [idtr]

 

idtr:

    dw 256*8-1;(size 이므로 -1 해줘야 함)

    dd 0 

 

Idtr 레지스터구조는 다음과 같습니다.

 

IDT Limit – dw IDT의 크기 (16비트)

IDT Base Address – dd IDT의 시작점 (32 비트)

 

인터럽트가 걸렸을 때 핸들러가 호출되기까지의 과정은 다음과 같습니다.

 

0x20번의 인터럽트가 발생

해당 인터럽트 디스크립터의 위치를 찾는다

1)CPU가 IDTR 참조

2)IDTR의 IDT의 Base Address를 참조하여 IDT의 첫번째 번지 부분으로 이동

3)IDT의 첫 번째 번지에서 0x20번째의 디스크립터를 찾아냄

해당 인터럽트 디스크립터의 물리 주소를 찾는다.(세그먼트의 base address를 구해서 + offset)

1)디스크립터의 세그먼트 셀렉터 값을 가지고 GDT에 해당하는 디스크립터를 찾아냄

2)GDT의 디스크립터를 갖고 RAM 상의 해당 세그먼트의 Base Address를 찾아냄

3)IDT의 0x20번째 디스크립터에 포함된 오프셋 값을 가지고 인터럽트 핸들러 루틴이 세그먼트 범위 안에서 실제로 위치한 것을 찾아냄

인터럽트 루틴을 수행하고 인터럽트를 마친다.

Iret 명령이 내려지면 처음 인터럽트가 걸렸던 프로그램의 인터럽트가 걸렸던 명령문 바로 다음 명령으로 돌아감.

 

 

4. 소프트웨어 인터럽트 걸기


sti    ; Set Interrupts

int 0x77    ; 0x77번 인터럽트를 건다

jmp $    ; while(1){};

 

인터럽트 관련 명령어

  • CLI(Clear Interrupts)
  • STI(SeT Interrupts)
  • POPF(Pop Flags)

 

sti명령으로 인터럽트를 활성화 시킵니다. 이 명령어를 실행하면

EFLAGS의IF 비트가 세트 되어 이후 명령부터 인터럽트를 받아들일 수 있게 됩니다.

0

0

0

0

0

0

0

0

0

0

I

D

V

I

P

V

I

F

A

C

V

M

R

F

0

N

T

I

O

P

L

O

F

D

F

I

F

=

1

T

F

S

F

Z

F

0

A

F

0

P

F

1

C

F


EFLAG 레지스터 - 9번 비트는 Interrupt Enable Flag(IF)로 system Flag 중 하나

'Operating Systems > OS 커널 제작' 카테고리의 다른 글

4. Interrupt 와 Exception (3) – 인터럽트 핸들러  (0) 2015.01.19
4. Interrupt 와 Exception (2) - PIC  (0) 2015.01.18
3. Protected Mode (3)  (0) 2015.01.17
3. Protected Mode (2)  (0) 2015.01.15
3. Protected Mode (1)  (0) 2015.01.14

리눅스 시작 프로세스(Linux startup process)는 리눅스를 부팅하는 동안 이루어 지는 여러 단계로 이루어진 초기화 프로세스입니다. 

 

리눅스를 부팅하는 과정은 여러 단계와 소프트웨어 구성요소가 더불어서 이루어집니다. 여기에 펌웨어 초기화, 부트로더 실행, 리눅스 커널 이미지의 적재 및 시작, 그리고 여러가지 시작 스크립트와 데몬이 실행됩니다. 각 단계들과 구성요서들은 각각 다른 변화와 접근을 하게 됩니다. 예를 들어 GRUB, LILO, SYSLINUX, 혹은 Loadlin는 부트로더로서 사용이 됩니다.

 

 

리눅스 시작 프로세스의 구성요소는 다음과 같습니다.

  1. 바이오스 : 기본적인 I/O 시스템으로 MBR을 실행합니다.
  2. MBR -> 마스터 부트 레코드로 GRUB을 실행합니다.
  3. GRUB -> Grand Unified Bootloader 커널을 실행합니다.
  4. Kernel -> /sbin/init을 실행합니다.
  5. Iinit -> runlevel 프로그램을 실행합니다.
  6. runlevel -> /etc/rc.d/rc*.c/ 실행합니다.

 

1. BIOS 

BIOS는 시스템의 (1) 무결성을 검사하고, (2) 부트로더 프로그램를 검색하여, 적재하고, 실행시킵니다.

BIOS 시작 과정에서 MBR이 저장되어 있는 매체의 우선순위 순서를 변경할 수 있습니다.

부트로더 프로그램을 감지 하고 메모리에 적재하면, BIOS는 제어를 적재된 부트로더 프로그램에 넘깁니다.

 

2. MBR

MBR은 마스터 부트 레코드(Master Boot Record)를 의미합니다.

부팅 가능한 디스크에서 첫 번째 섹터에 위치하며, /dev/hda, 혹은 /dev/sda 입니다.

MBR은 GRUB 부트 로더를 실행합니다.

 

MBR의 사이즈는 512바이트 이하이며, 다음과 같이 3개의 구성요소로 이루어져 있습니다.

 

 1) 부트 로더 정보 (처음 445 바이트)

 2) 파티션 테이블 정보 ( 그 다음 64 바이트)

 3) mbr 적합성 검사(2바이트)

   

 

위 그림은 MBR을 도식화 한 것입니다.

 

3. GRUB

 

GRUB은 Grand Unified Boot loader를 의미합니다.

Splash 스크린 표시를 하고, 아무것도 입력하지 않으면 Grub 설정 파일에 지정된 기본 커널 이미지를 로드합니다.

Grub의 설정 파일은 /boot/grub/grub.conf이며, GRUB의 역할은 커널과 initrd 이미지를 실행시키는 것 입니다.

 

4. Kernel

커널은 Grub.conf에서 "root="로 표기된 루트 파일 시스템을 마운트 하고, /sbin/init 프로그램을 실행합니다.

Init은 리눅스 커널에 의해 실행된 첫번째 프로그램으로 PID는 1을 가집니다. .

Initrd는 initial Ram disk를 의미하고, Initrd는 커널이 진짜 루트 파일 시스템을 마운트 할 때까지 일시적인 루트 파일 시스템을 사용하는데, 디스크의 파티션에 접근하거나 다른 하드웨어를 접근 하는데 필요한 드라이버가 컴파일되어 들어있습니다.

 

5. Init

/etc/inittab file 에서 Linux run level을 결정함

아래의 가능한 run levels이 있음

0 – halt

1 – Single user mode

2 – 다중사용자, NFS 제외

3 – 완전 다중사용자 모드

4- 사용되지 않음

5 – x11

6 – 재부팅

Init은 /etc/inittab에서 디폴트 initlevel을 가리키며, 그 것을 이용하여 모든 적합한 프로그램을 메모리에 적재합니다.

보통 디폴트 run lelvel을 3 혹은 5를 설정합니다.

 

6. Runlevel programs

리눅스 시스템이 부팅될 때, 여러가지 서비스들이 시작되는 것을 볼 수 있습니다. 부팅할 때 보여지는 다음과 같은 메시지들이 그에 해당됩니다.

Ex) starting sendmail …. OK

이런 프로그램들은 runlevel 프로그램이며, run level로 정의된 디렉토리에서 실행이 됩니다.

기본 init level 설정에 달려 있으며, 시스템은 다음과 같은 디렉토리 중 하나에서 프로그램을 실행합니다.

  • Run level 0 - /etc/rc.d/rc0.d/
  • Run level 1 - /etc/rc.d/rc1.d/
  • Run level 2 - /etc/rc.d/rc2.d/
  • Run level 3 - /etc/rc.d/rc4.d/
  • Run level 4 - /etc/rc.d/rc5.d/
  • Run level 6 - /etc/rc.d/rc6.d/

/etc 디렉토리에 이러한 경로들의 심볼릭 링크가 존재하고, S로 시작하는 프로그램은 시작(start)에 이용되는 프로그램, K로 시작되는 프로그램은 종료(kill)에 이용되는 프로그램 입니다.

이러한 프로그램의 S 혹은 K 옆에 따라오는 숫자들은 실행 순서가 되겠습니다.

 

 

출처 : http://www.thegeekstuff.com/2011/02/linux-boot-process/

+ Recent posts