>

Protected Mode 진입

Study/Kenrel 2015. 9. 23. 13:48

GDT 로드 이후에 CR0의 최하위비트(PE비트) 를 1로 세트시켜야 Protected Mode로 진입하게 된다.

허나 세트하는 명령을 실행한 순간에도 CPU에서 그 다음 명령(16비트)이 해석 중에 있기 때문에 오류가 생길 수 있다. 그래서 이런 파이프라인을 해제하기 위해 jmp 문을 사용한다.

mov eax, cr0

or eax, 0x00000001

mov cr0, eax


jmp $+2

nop

nop


db 0x66

db 0x67

db 0xEA

dd PM_Start

dw SysCodeSelector

jmp 문 이후에 0x66 0x67 등 데이터들이 있는 것을 볼 수 있는데, 0x66 0x67은 prefix로 CPU에게 16비트 명령이 32비트로 바뀌었거나 그 반대의 경우를 알리는데 쓰는 표시이다.

즉 Protected Mode에서 JMP DWORD SysCodeSelector:PM_Start 의 명령을 수행하게 된다.

'Study > Kenrel' 카테고리의 다른 글

PIC 초기화  (0) 2015.09.24
IDT(Interrupt Descriptor Table)  (0) 2015.09.24
GDT(Global Descriptor Table)  (0) 2015.09.23
Segment & Offset  (0) 2015.09.21
Real Mode & Protected Mode  (0) 2015.09.21
Posted by Mungsul
,

GDT는 Protected Mode에서 커널이 메모리를 어떻게 사용하겠다라고 하는 설계서와 흡사하다.


* 그림 출처 : http://manavar.blogspot.kr/2011_05_01_archive.html

GDT의 디스크립터들은 위와 같은 형식으로 지정되는데 크게 Limit(20bit)와 Base Address (32bit) 를 지정한다. 그리고 각각의 Flag들을 지정하여 특권레벨, 세그먼트 권한 Limit 단위 등을 지정한다.

G비트 : 1이면 세그먼트 크기 4KB 단위, 0이면 바이트 단위

P비트 : 페이징과 관련이 있음

S비트 : 시스템 세그먼트일때 0, 코드 혹은 데이터 세그먼트일때 1

DPL : 특권레벨 => 커널(0) 유저(3)

X,C/D,R/W,A 는 Type으로 X가 0일때 데이터, X가 1일때 코드영역을 나타낸다.

A는 액세스 비트로 어떤 프로그램이 이 세그먼트에 접근했을 때 1로 세트된다. 0으로는 클리어해주지 않는다.

데이터 세그먼트일 때 C/D는 EXPAND DOWN, 코드 세그먼트일 때는 CONFORMING 여부

데이터 세그먼트일 때 R/W는 쓰기 권한, 코드 세그먼트일 때는 읽기 권한

데이터는 기본으로 읽기 권한을 갖고 코드는 기본으로 실행권한을 갖는다.


GDTR은 레지스터로 48비트의 크기를 갖는다. 

16비트는 GDT의 크기, 하위 32비트에는 GDT의 시작 주소가 물리 주소로 들어간다.

- lgdt[포인터] 로 부른다.

'Study > Kenrel' 카테고리의 다른 글

PIC 초기화  (0) 2015.09.24
IDT(Interrupt Descriptor Table)  (0) 2015.09.24
Protected Mode 진입  (0) 2015.09.23
Segment & Offset  (0) 2015.09.21
Real Mode & Protected Mode  (0) 2015.09.21
Posted by Mungsul
,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<signal.h>
#include<sys/wait.h>
#include<errno.h>
void sigHandler(int sig)
{
	int pid;
	int status;
	pid = wait(&status);
	printf("pid[%d] terminated\n",pid);
}
int process_init()
{
	int state;
	struct sigaction act;
	act.sa_handler = sigHandler;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	
	state = sigaction(SIGCHLD,&act,0);
	if(state !=0)
	{
		printf("signal error\n");
		exit(1);
	}
}
int sock_init(int port)
{
	struct sockaddr_in server_addr;
	int server_fd;
	int len;
	if((server_fd = socket(AF_INET, SOCK_STREAM,0))==-1)
	{
		printf("socket error\n");
		exit(0);
	}
	memset(&server_addr, 0x00, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(port);
	if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))<0)
	{
		printf("bind error\n");
		exit(0);
	}
	if(listen(server_fd,5)<0)
	{
		printf("listen error\n");
		exit(0);
	}
	return server_fd;
}
int do_write(int fd,char *buf)
{
	return write(fd,buf,strlen(buf));
}
int do_read(int fd, char *buf)
{
	return read(fd,buf,sizeof(buf));
}
int service(int fd)
{
	char buf[120];
	do_write(fd,"Hello Sir?\n");
	do_read(fd,buf);
	exit(1);
}
int main(int argc, char *argv[])
{
	int server_fd,client_fd;
	pid_t pid;
	int len;
	struct sockaddr_in client_addr;
	if(argc !=2)
	{
		printf("usage : %s [port]\n",argv[0]);
		return 0;
	}
	process_init();
	server_fd = sock_init(atoi(argv[1]));
	while(1)
	{
		len = sizeof(client_addr);
		if((client_fd = accept(server_fd ,(struct sockaddr *)&client_addr, &len))<0)
		{
			if(errno == EINTR)
				continue;
			else
				printf("accept error\n");
		}
		if((pid = fork())<0)
		{
			printf("fork error\n");
			exit(0);
		}
		else if(pid > 0)
		{
			close(client_fd);
			continue;
		}
		else if(pid == 0)
		{
			close(server_fd);
			service(client_fd);
		}
	}
}



sock_init 함수는 socket, bind, listen 함수로 서버 설정을 하는 함수이다.

fork를 통해 자식프로세스를 생성한다 => 부모면 그대로 서버소켓 유지, 자식이면 서비스 실행. 

SIGCHLD 시그널을 처리하기 위해 sigaction 함수를 쓴다.

wait 함수는 자식프로세스의 종료 상태를 가져올 때까지 부모를 기다리게 할 수 있다.

accept문을 그냥 쓰면 무한반복되는데 이를 방지하기 위해 errno가 EINTR 일 때에도 처리해준다.

Posted by Mungsul
,