얼마 전에 Black Hat USA 2021 학회에서 가상 머신 탈출 탐지 및 방어 관련 연구(Alcatraz)를 발표하게 되었다고 말씀을 드렸는데요, 해당 연구가 QEMU와 아마존(Amazon)에서 만든 MicroVM 기술(Firecracker)을 대상으로 하고 있습니다. MicroVM 기술은 전체 시스템을 가상화하여 가상 머신(Virtual Machine, VM)을 생성하는 QEMU와 달리 컨테이너 구동을 위한 최소한의 기능만 구현한 경량(micro) 가상 머신입니다. 그래서 이름도 MicroVM이죠. QEMU보다는 최근에 나온 기술이라 많이 생소하실 텐데요, QEMU보다 공격면이 작고 좀 더 안전하기 때문에 앞으로 주목받을 것 같습니다. ^^;;;

Firecracker는 현재 https://github.com/firecracker-microvm/firecracker에서 프로젝트가 진행 중입니다. 문서가 비교적 잘 정리되어 있어서 따라 하는 것이 크게 어렵지 않은데요, 좀 더 쉽게 사용할 수 있는 방법이 있어서 정리하는 셈 치고 남겨봅니다. ^^;;

1. 우분투 환경에서 도커(Docker) 설치하기

Firecracker는 MicroVM 내부에 도커(Docker)를 구동하기 때문에 먼저 설치해 줍니다. 우분투 환경에서 도커는 아래와 같이 설치할 수 있습니다.

# 도커 패키지 설치
$> sudo apt install docker.io

# 현재 유저 계정을 도커 그룹에 추가, 그룹 추가가 완료되면 로그아웃 및 로그인 수행 필요
$> sudo useradd -aG docker ${USER}

2. 아마존(Amazon) Firecracker 다운로드 및 빌드

Firecracker의 소스코드는 이미 공개되어 있으므로, 아래와 같이 입력하여 소스코드를 받아 빌드합니다.

# 소스코드 다운로드
$> git clone https://github.com/firecracker-microvm/firecracker

# 빌드 및 링크 생성
$> cd firecracker
$> tools/devtool build
$> ln -s build/cargo_target/x86_64-unknown-linux-musl/debug/firecracker firecracker

3. Firecracker 구동용 스크립트 생성하기

Firecracker는 웹 API를 제공합니다. 그래서 curl을 이용해서 직접 커맨드를 실행해도 되지만, 너무 불필요한 작업이 많아서 아래처럼 스크립트를 만들었습니다. 이 글을 쓰게 된 이유이기도 하죠. ^-^)/

#!/bin/bash
# 00_get_kernel_and_rootfs.sh로 저장 후 chmod +x로 실행권한 부여

arch=`uname -m`
dest_kernel="hello-vmlinux.bin"
dest_rootfs="hello-rootfs.ext4"
image_bucket_url="https://s3.amazonaws.com/spec.ccfc.min/img"

if [ ${arch} = "x86_64" ]; then
    kernel="${image_bucket_url}/quickstart_guide/x86_64/kernels/vmlinux.bin"
    rootfs="${image_bucket_url}/hello/fsfiles/hello-rootfs.ext4"
elif [ ${arch} = "aarch64" ]; then
    kernel="${image_bucket_url}/quickstart_guide/aarch64/kernels/vmlinux.bin"
    rootfs="${image_bucket_url}/aarch64/ubuntu_with_ssh/fsfiles/xenial.rootfs.ext4"
else
    echo "Cannot run firecracker on $arch architecture!"
    exit 1
fi

echo "Downloading $kernel..."
curl -fsSL -o $dest_kernel $kernel

echo "Downloading $rootfs..."
curl -fsSL -o $dest_rootfs $rootfs

echo "Saved kernel file to $dest_kernel and root block device to $dest_rootfs."
#!/bin/bash
# 01_start_firecracker.sh로 저장 후 chmod +x로 실행권한 부여

echo "Remove existing socker."
rm -f /tmp/firecracker.socket

echo ""
echo "Start firecracker."
./firecracker --api-sock /tmp/firecracker.socket
#!/bin/bash
# 02_set_guest_kernel.sh로 저장 후 chmod +x로 실행권한 부여

arch=`uname -m`
kernel_path=$(pwd)"/hello-vmlinux.bin"

if [ ${arch} = "x86_64" ]; then
    curl --unix-socket /tmp/firecracker.socket -i \
      -X PUT 'http://localhost/boot-source'   \
      -H 'Accept: application/json'           \
      -H 'Content-Type: application/json'     \
      -d "{
            \"kernel_image_path\": \"${kernel_path}\",
            \"boot_args\": \"console=ttyS0 reboot=k panic=1 pci=off\"
       }"
elif [ ${arch} = "aarch64" ]; then
    curl --unix-socket /tmp/firecracker.socket -i \
      -X PUT 'http://localhost/boot-source'   \
      -H 'Accept: application/json'           \
      -H 'Content-Type: application/json'     \
      -d "{
            \"kernel_image_path\": \"${kernel_path}\",
            \"boot_args\": \"keep_bootcon console=ttyS0 reboot=k panic=1 pci=off\"
       }"
else
    echo "Cannot run firecracker on $arch architecture!"
    exit 1
fi
#!/bin/bash
# 03_set_rootfs.sh로 저장 후 chmod +x로 실행권한 부여

rootfs_path=$(pwd)"/hello-rootfs.ext4"
curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/drives/rootfs' \
  -H 'Accept: application/json'           \
  -H 'Content-Type: application/json'     \
  -d "{
        \"drive_id\": \"rootfs\",
        \"path_on_host\": \"${rootfs_path}\",
        \"is_root_device\": true,
        \"is_read_only\": false
   }"
#!/bin/bash
# 04_start_microvm.sh로 저장 후 chmod +x로 실행권한 부여

curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/actions'       \
  -H  'Accept: application/json'          \
  -H  'Content-Type: application/json'    \
  -d '{
      "action_type": "InstanceStart"
   }'

4. Firecracker 실행

최초 실행 시에는 커널(Kernel) 파일과 루트파일시스템(rootfs)가 없으므로, 00_get_kernel_and_rootfs.sh를 실행해서 이미지를 다운로드할 필요가 있습니다. 한 번 다운로드한 뒤에는 이미지를 계속 활용하면 되니 01_start_firecracker.sh부터 실행하면 됩니다. 여기서 주의할 점이 있는데요, 01_start_firecracker.sh 스크립트를 실행하면 터미널이 계속 대기상태로 있다는 겁니다. MicroVM이 시작되기를 기다리는 건데요, 이후에 다른 터미널을 열어서 스크립트를 실행하면 대기상태가 풀리고 로그인 쉘이 표시되기 때문에 걱정 안 하셔도 됩니다. 스크립트를 통해 Firecracker를 실행하는 과정은 아래와 같습니다.

# 커널 및 루트파일시스템 이미지를 다운로드하는 단계이므로, 최초 1회만 실행
$> ./00_get_kernel_and_rootfs.sh

# Firecracker 실행, 이때 터미널이 대기상태로 빠지므로 다른 터미널을 열어서 다음 스크립트를 실행
$> ./01_start_firecracker.sh

# MicroVM용 커널 설정
$> ./02_set_guest_kernel.sh

# MicroVM용 루트파일시스템 설정
$> ./03_set_rootfs.sh

# MicroVM 실행
$> ./04_start_microvm.sh

위의 스크립트를 차례로 실행하면, 01_start_firecracker.sh를 실행하여 대기중이던 터미널에서 아래와 같이 부팅이 진행되고 로그인 쉘이 표시됩니다. 기본 로그인 정보는 ID/PWD가 root/root로 설정되어 있습니다. 로그를 보니 Alpine 리눅스를 기반으로 MicroVM 이미지를 생성한 것 같네요.

[    0.000000] Linux version 4.14.225 (ubuntu@ip-172-31-11-16) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #1 SMP Fri Mar 12 16:47:14 UTC 2021
[    0.000000] Command line: console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw virtio_mmio.device=4K@0xd0000000:5
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'
[    0.000000] x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256
[    0.000000] x86/fpu: xstate_offset[3]:  832, xstate_sizes[3]:   64
[    0.000000] x86/fpu: xstate_offset[4]:  896, xstate_sizes[4]:   64
[    0.000000] x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
[    0.000000] e820: BIOS-provided physical RAM map:

... omitted ...

Starting default runlevel

Welcome to Alpine Linux 3.8
Kernel 4.14.225 on an x86_64 (ttyS0)

localhost login:

그럼 즐거운 저녁 되세요 >ㅁ<)-b

여러 가지 이유로 Intel 가상화 기술(Virtualization Technology)을 직접 활용한 시스템 소프트웨어를 구현한 이야기를 예전에 잠시 했었습니다. Shadow-box라고 부르고 있는데... 그 녀석(?!)으로 인해 영혼까지 탈탈 털리고 있는 중입니다. ^^;;; 2015년에 처음 개발을 시작했으니까 올해가 5년 차인데, 뮤택스와 스핀락 관련 이야기(https://kkamagui.tistory.com/925)에서 잠깐 소개를 하기도 했죠.

경량 가상화 기술을 이용한 영역(World) 분리

처음 가상화 기술로 영역을 분리하고 나니 잘 되는 듯 하지만 실제로는 잘 안 되는(?!) 상황이 많이 발생했는데요, 이러한 일들이 커널이 변경되어 새로운 기능이 추가되거나 단말이 바뀔 때마다 나와서... 정말 탈탈 털리고 있다는 표현이 맞는 것 같네요. 정말 멜트다운(Meltdown) 때문에 PTI(Page Table Isolation)이 커널에 들어갔을 때는 죽을 맛이었는데... 어쨌거나 큰 산을 넘고 또 넘어서 지금은 많이 안정화가 되었습니다. 물론... 새로운 문제가 나오기 전까지 안정적인 거지만요. ^^;;;

이번에 단말을 바꾸면서 여러가지 시험을 진행하고 있었는데요, 특정 단말에서만 한 20분을 못 넘기고 멈추는 문제가 발생했습니다. 이 문제 때문에 며칠 밤을 새웠는데요, 딱히 의심되는 부분이 없어서 계속 반복 실행하면서 로그를 확인했는데 별다른 로그가 없이 시스템이 정지했습니다. 사실 Shadow-box에 문제가 있거나 페이지 테이블 맵핑에 문제가 있으면 이런 현상이 생겼던 경험이 있어서 페이지 테이블 쪽을 의심하고 있던 찰나... 인터럽트 발생 횟수를 아래처럼 살펴보다 보니 NMI(Non-Maskable Interrupt)가 꽤 많이 발생한 것이 보이더라구요.  

$ cat /proc/interrupts
            CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       CPU8       CPU9       CPU10      CPU11      CPU12      CPU13      CPU14      CPU15      
 NMI:         51        179        778         16        241         33         77         90         47        127        408        136        268         62        192        219   Non-maskable interrupts

NMI는 마스킹, 즉 비활성화가 불가능해서 언제 어디서건 발생할 수 있기 때문에 핸들러가 반드시 있어야 합니다. 당연히 게스트(Guest), 즉 리눅스가 동작하는 일반 영역에서 발생하면 커널의 핸들러가 불려서 처리가 되니 문제가 없을 거라 생각했는데... 문득 호스트(Host), 즉 커널 보호 기술이 동작하고 있는 영역에서 발생하면 문제가 생길 것 같은 느낌이 들었습니다. 아니나 다를까, 실제로 인터럽트 핸들러를 생성해서 로그를 찍어보니 아주 간간히 NMI 인터럽트가 호스트에서도 발생하고 있었습니다. 그리고 그때마다 게스트의 NMI 핸들러를 불러서 처리를 하고 있더라구요. 아아... 이러면 안 되는데... ㅠ^ㅠ

호스트는 리눅스 커널의 기능을 최소로 활용하기 때문에 NMI 핸들러를 실행할 만큼 자원이나 호출 가능한 함수가 충분치 않거든요. 그래서 이번 기회에 인터럽트 핸들러를 호스트용 하나 더 만들고 게스트와 분리시켜줬습니다. 호스트의 인터럽트 핸들러는 아무것도 할 필요가 없기 때문에 로그만 찍는 더미(Dummy) 핸들로로 모두 연결했죠. 그랬더니 새로운 단말에서도 다시 안정적으로 동작을 하기 시작했습니다. >ㅁ<)/~ 로그를 보니 거의 몇 분 단위로 NMI 인터럽트가 발생하고 있더라구요.

$> sudo journalctl -f | grep shadow-box
 8월 03 00:22:11 machine kernel: shadow-box: VM [3] INT NMI callback
 8월 03 00:24:18 machine kernel: shadow-box: VM [5] INT NMI callback
 8월 03 00:26:02 machine kernel: shadow-box: VM [8] INT NMI callback
 8월 03 00:26:32 machine kernel: shadow-box: VM [0] INT NMI callback
 8월 03 00:27:52 machine kernel: shadow-box: VM [13] INT NMI callback
 8월 03 00:29:45 machine kernel: shadow-box: VM [6] INT NMI callback
 8월 03 00:31:21 machine kernel: shadow-box: VM [7] INT NMI callback
 8월 03 00:36:26 machine kernel: shadow-box: VM [9] INT NMI callback
 8월 03 00:37:36 machine kernel: shadow-box: VM [11] INT NMI callback
 8월 03 00:42:54 machine kernel: shadow-box: VM [1] INT NMI callback
 8월 03 00:44:30 machine kernel: shadow-box: VM [14] INT NMI callback
 8월 03 00:48:18 machine kernel: shadow-box: VM [15] INT NMI callback
 8월 03 00:51:03 machine kernel: shadow-box: VM [4] INT NMI callback
 8월 03 00:54:26 machine kernel: shadow-box: VM [12] INT NMI callback
 8월 03 01:12:17 machine kernel: shadow-box: VM [10] INT NMI callback
 8월 03 01:59:58 machine kernel: shadow-box: VM [2] INT NMI callback

어휴... 진땀을 뺐네요. 자칫 잘못하면 엄청 길어질 뻔했는데, 운이 좋아서 다행입니다.

그럼 좋은 밤 보내세요. ^^

ps) CPU의 인터럽트 핸들러나 실제 핸들링 과정이 궁금하신 분은 64비트 멀티코어 OS 원리와 구조*(http://www.yes24.com/Product/Goods/65061299)를 참고하시면 좋을 것 같습니다.

 

소싯적에 뜻한 바가 있어서 인텔 가상화 기술(Intel Virtualization Technology, VT-x/VT-d)를 직접 이용하여 리눅스 커널을 보호하는 소프트웨어를 만들었습니다. ^^;;; 이름은 Shadow-box라고 지었는데요, 설계와 구현물은 오픈소스로 공개되어 있고 github.com/kkamagui/shadow-box-for-x86에서 보실 수 있습니다. 최근에 커널의 메모리 관련 디버깅 옵션과 연계하는데 문제가 있어서 업그레이드를 힘겹게 진행했는데요, 시험 중에 재미있는 현상을 발견해서 한자 남겨봅니다.

Shadow-box는 가상화 기술을 사용해서 실제 보호 기능을 수행하는 호스트(Host) 영역과 호스트에 의해 보호받는 일반 영역인 게스트(Guest) 영역으로 구분합니다. 여기서 재미있는 부분은 호스트와 게스트가 커널을 공유한다는 점인데요, 공유함과 동시에 호스트에서 게스트 커널의 주요 영역이 변조되었는지를 이벤트 기반(Event-driven) 혹은 주기적 감시(Periodic monitoring)를 통해 확인한다는 겁니다.

Shadow-box의 구조

 비록 호스트는 게스트의 리눅스 커널을 공유하지만 모든 기능을 사용할 수 는 없습니다. 호스트는 적어도 변조가 되지 않은 리눅스 커널 영역에서 구동되어야 하고 혹여 태스크 스위칭이 되어 호스트에서 게스트 프로세스가 실행되거나 하면 안 되기 때문이죠. 그래서 호스트에서는 철저하게 이를 막고 있는데요,  호스트에서 인터럽트 비활성화(Interrupt Disable)와 선점 불가(Preemption Disable)를 사용합니다. ^^;;;

인터럽트 비활성화의 경우는 VT-x가 직접 처리해주는 부분이라 Shadow-box에서 별도의 처리는 하지 않고, 선점 불가의 경우는 리눅스 커널의 preemption_disable(), preemption_enable() 함수를 사용해서 처리하고 있습니다. 그런데 예상치도 못한 변수를 만났습니다. 바로 뮤텍스(Mutex)와 스핀락(Spinlock) 때문이죠.

뮤텍스(Mutex)와 스핀락(Spinlock)은 커널에서 흔히 그리고 전통적으로 사용되었던 동기화 기재(Synchronization)인데요, 뮤텍스는 대기(Sleep)를 전제로 하고 있고 스핀락은 바쁜 대기(Busy-waiting)를 전제로 하고 있습니다. 좀 더 자세히 설명하자면, 뮤텍스의 경우는 이미 누군가가 뮤텍스를 사용(Lock)하고 있다면 다른 사람은 해당 뮤텍스의 대기 큐에 자신을 등록하고 차례가 올 때까지 대기(Sleep) 합니다. 그리고 앞서 뮤텍스를 잡은 사람이 이를 해제(Unlock)하면 대기 큐에 있는 프로세스를 깨우는 것이죠. 그리고 즉시 해당 프로세스가 실행되는 특징이 있습니다. 스핀락의 경우는 누군가가 잡고(Lock) 사용 중이면 다른 사람은 계속 루프를 돌면서 해제(Unlock) 되었는지를 확인합니다. 그리고 앞서 스핀락을 잡은 사람이 이른 해제(Unlock)하면 루프를 돌면서 확인하던 다른 사람은 이를 감지하고 다시 잡은 후 작업을 시작하게 되는 것이죠. 이미 눈치를 채셨겠지만, 대기 큐가 없고 순서도 없는 야생인 것이죠. ^^;;;

실제 리눅스 커널의 뮤텍스의 Unlock 코드는 아래처럼 되어 있습니다. 대기 큐(Wait Queue)를 돌면서 태스크를 깨우는 것을 알 수 있습니다.

static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
{
	struct task_struct *next = NULL;
	DEFINE_WAKE_Q(wake_q);
	unsigned long owner;

	mutex_release(&lock->dep_map, ip);

	/*
	 * Release the lock before (potentially) taking the spinlock such that
	 * other contenders can get on with things ASAP.
	 *
	 * Except when HANDOFF, in that case we must not clear the owner field,
	 * but instead set it to the top waiter.
	 */
	owner = atomic_long_read(&lock->owner);
	for (;;) {
		unsigned long old;

#ifdef CONFIG_DEBUG_MUTEXES
		DEBUG_LOCKS_WARN_ON(__owner_task(owner) != current);
		DEBUG_LOCKS_WARN_ON(owner & MUTEX_FLAG_PICKUP);
#endif

		if (owner & MUTEX_FLAG_HANDOFF)
			break;

		old = atomic_long_cmpxchg_release(&lock->owner, owner,
						  __owner_flags(owner));
		if (old == owner) {
			if (owner & MUTEX_FLAG_WAITERS)
				break;

			return;
		}

		owner = old;
	}

	spin_lock(&lock->wait_lock);
	debug_mutex_unlock(lock);
	if (!list_empty(&lock->wait_list)) {
		/* get the first entry from the wait-list: */
		struct mutex_waiter *waiter =
			list_first_entry(&lock->wait_list,
					 struct mutex_waiter, list);

		next = waiter->task;

		debug_mutex_wake_waiter(lock, waiter);
		wake_q_add(&wake_q, next);
	}

	if (owner & MUTEX_FLAG_HANDOFF)
		__mutex_handoff(lock, next);

	spin_unlock(&lock->wait_lock);

	wake_up_q(&wake_q);    
}


void wake_up_q(struct wake_q_head *head)
{
	struct wake_q_node *node = head->first;

	while (node != WAKE_Q_TAIL) {
		struct task_struct *task;

		task = container_of(node, struct task_struct, wake_q);
		BUG_ON(!task);
		/* Task can safely be re-inserted now: */
		node = node->next;
		task->wake_q.next = NULL;

		/*
		 * wake_up_process() executes a full barrier, which pairs with
		 * the queueing in wake_q_add() so as not to miss wakeups.
		 */
		wake_up_process(task);
		put_task_struct(task);
	}
}

스핀락의 Unlock은 아래와 같은데요, 이 역시 Unlock과 함께 스케줄러를 호출하는 것을 알 수 있습니다. 단, preemptible 일 때만 그렇게 하는데, 인터럽트가 활성화되어 있고 preempt_enable()인 상태여야 합니다.

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
	spin_release(&lock->dep_map, _RET_IP_);
	do_raw_spin_unlock(lock);
	preempt_enable();
}

#define preempt_enable() \
do { \
	barrier(); \
	if (unlikely(preempt_count_dec_and_test())) \
		__preempt_schedule(); \
} while (0)

asmlinkage __visible void __sched notrace preempt_schedule(void)
{
	/*
	 * If there is a non-zero preempt_count or interrupts are disabled,
	 * we do not want to preempt the current task. Just return..
	 */
	if (likely(!preemptible()))
		return;

	preempt_schedule_common();
}
NOKPROBE_SYMBOL(preempt_schedule);
EXPORT_SYMBOL(preempt_schedule);

#define preemptible()	(preempt_count() == 0 && !irqs_disabled())

Shadow-box는 태스크 목록을 관찰할 때 스핀락을 사용하며, 커널 모듈 목록을 관찰할 때 뮤텍스를 어쩔 수 없이 사용합니다. 이 두 자료구조는 리스트 형태로 되어 있는데, 리스트를 순회하는 동안 리스트가 바뀌어 엉뚱한 곳에 데이터를 읽어오거나 접근하거나 하면 안 되기 때문이죠. ^^;;;

결국 해결책을 찾아야 했는데요, 스핀락 같은 경우는 인터럽트가 비활성화되어 있으면 스케줄러를 호출하지 않기 때문에 별 문제가 없는 것으로 확인되었습니다. 다만, 뮤텍스 같은 경우는 잘못하면 호스트에서 게스트의 다른 프로세스를 실행할 수 있는 잠재적인 문제가 있어서 결국 제거하는 방법을 택했습니다. 이로 인해 일부 탐지 기능이 지연되는 문제가 있지만... 안정성이 먼저니까요. ^^;;;

그럼 좋은 밤 되세요 ^^)/

페도라(Fedora)나 우분투(Ubuntu)와 같은 배포판들은 롤링 릴리즈(Rolling Release) 방식을 사용하는 아치(Arch)와 달리 배포판에 버전이 부여되고 버전 간에는 통째로 시스템을 업그레이드하는 방식을 사용합니다. 롤링 릴리즈는 이와 달리 변경사항이 매일 매일 릴리즈되고 이를 설치하면 항상 최신 버전이 되는 방식입니다. 버전을 부여하는 방식은 해당 버전을 충분히 테스트해서 릴리즈가 되기 때문에 안정성이 보장되는 것이 장점인 반면, 업데이트가 늦다는 단점이 존재합니다. 물론 롤링 릴리즈는 이와 반대의 장단점이 존재하지요. ^^;;;

저는 여러가지 배포판을 사용하고 있는데요, 그동안 너무 구 버전의 페도라를 사용하고 있어서 업그레이드를 진행했습니다. 사실 배포판 사이에 업그레이드는 잘되는 경우보다 문제가 생기는 경우가 더 많은데요, 걱정 반 기대 반으로 시작했습니다. ^0^)-b

페도라 업그레이드는 공식 페도라 위키에서 볼 수 있는데요, 방법은 아래와 같습니다.

# 배포판을 업그레이드할 준비를 합니다.
$> sudo dnf upgrade --refresh
$> sudo dnf install dnf-plugin-system-upgrade

# releasever는 업그레이드할 버전을 정해주는 것이 좋은데요, 현재 버전보다 하나 더 높은 버전을 설정해주는 것이 좋습니다. 시간이 많이 걸리니 차라도 한 잔 하시는 것이... 쿨럭..;;;
$> sudo dnf system-upgrade download --refresh --releasever=28

# 마지막으로 시스템을 리부팅하여 변경사항을 반영합니다.
$> sudo dnf system-upgrade reboot

위와 같이 하면 리부팅과 함께 업그레이드가 진행되는데요, 간혹 업그레이드가 실패하고 이전 버전으로 부팅되는 경우가 있습니다. 이런 경우, 아래와 같이 입력하여 강제로 업그레이드를 진행할 수 있습니다.

$> dnf --releasever=22 distro-sync

저는 위와 같이 해서... 폭망했습니다. ㅠㅠ 결국 저는 리부팅 시 서비스 오류가 너무 많이 발생해서 그냥 최신 버전으로 다시 설치했네요. ㅠㅠ

그럼 좋은... 쿨럭..;;;

여러분의 꾸준한 관심에 감사드립니다!

많은 분들이 구매해주신 덕분에 "64비트 멀티코어 OS 원리와 구조"가 3쇄까지 올 수 있었습니다.


현재 yes24를 비롯한 주요 서점에 일시 품절로 나와있는 것으로 보아, 열심히 찍고 있는 듯 하고 아직 서점에는 풀리지는 않은 것 같습니다.  ^^;;; 조금만 기다려 주시면 그동안에 제보해주신 오탈자가 모두 반영된 버전을 만나실 수 있을 듯 합니다.


예전에 한동훈 편집자님께서 1쇄만 다 팔려도 괜찮은 거라고 말씀하신게 엊그제 같은 데 벌써 6년이나 지났네요. 6년이 지나는 동안 많은 부분이 바뀌어서 버전업이 필요한 부분도 많은데, 마음 같아서는 개정판에 도전하고 싶지만... 제 마음대로 할 수 있는 부분이 아니기에 언제쯤 가능할런지 시기만 보고 있습니다. ^^;;;;


한동훈 편집자님 뒤를 이어 한없이 부족한 책을 보살펴 주고 계신 송성근 팀장님과 최현우 편집자님께도 심심한 감사의 말씀을 전합니다. 두 분이서 도와주신 덕분에 꾸준히 팔릴 수 있었던 것 같습니다.


책을 쓰기로 마음을 먹은 시점이 지금처럼 시원한 바람이 부는 초가을이었습니다. 항상 이맘때가 되면 자신에게 부끄럼이 없는지 되돌아 보게 되는데요, 오늘의 나는 어제의 나보다 한 발자국 더 나아가야 한다는 좌우명 때문인 것 같습니다.


사실 올 해 초부터 혼란한 시기가 이어지고 있어서 고민이 많았는데요, 어제의 나보다 더 나아가기 위해서 새로운 도전을 해볼까 생각 중입니다. 육아 때문에 힘들긴 하겠지만 문득 지금이 적기라는 생각이 들었거든요. 어느정도 완성이 되면 공개할 날이 올 것 같습니다. ^^;;; 육아 고행에서 좀 자유로워지면 탄력이 붙을 것 같긴한데... 당분간은 시간이 부족해서 거북이처럼 진행해야 할 것 같네요.


늦은 밤, 이제야 겨우 여유가 생겨 감사의 글을 남깁니다. 아마 근래에 쓴 글 중에 가장 긴 글이 아닌가 싶네요. 부족한 책이지만 구매해 주셔서 다시 한 번 감사드립니다. ^^)/

작년에 거의 1년 정도를 투자해서 만든 가상화 기술 기반 보안 기술을 만들었습니다. 처음에는 프로토타입 수준이었지만 이제 결실을 맺어 릴리즈를 앞두고 있는데요, 항상 그렇듯이 릴리즈를 앞두고 요상한 문제를 만나 디버깅을 하고 있습니다. 

지금 3 주차에 접어 들었는데요, 버그의 현상은 시스템을 종료할 때 가끔 정상적인 종료가 안되고 멈추는 것입니다.

현상

실험 데이터를 바탕으로 종합해보면, 2 코어 4스레드인 CPU가 장착된 장비에서는 약 1/1000의 확률로 종료된 후에 다시 리부팅이 안되고 텍스트 모드 화면에서 커서만 깜빡입니다. 13코어 26스레드인 CPU 2개가 장착된 장비에서는 약 1/20의 확률로 종료가 정상적으로 종료가 안되고 같은 현상으로 멈춥니다. 

원래 systemd가 종료 시에 모든 프로세스를 죽인다는 메시지가 출력되고 종료가 시작되어야하는데, 이 부분 전에 멈춰서 종료가 더 이상 진행이 안되더라구요. ㅠㅠ

원인 분석을 위한 실험, 그리고 해결책...

사실 정확한 원인을 찾지 못했어요. ㅠㅠ 여러 실험을 반복한 결과, 가상화 기술 중 Extended Page Tagle(EPT)를 활성화하고 호스트의 일부 메모리를 매핑하지 않았을 때 문제가 발생한다는 것까지 파악했습니다만... 이 맵핑 안된 메모리 때문에 간혹 종료가 안되는 현상은 이해하기가 어렵더라구요. 

시스템 종료 시에 어떤 이유에서인지 해당 물리 메모리에 접근하는 부분이 있는 것 같은데... 정확하게 찾지 못해서 결국은 메모리를 매핑하지 않은 부분을 읽기 전용으로 매핑해서 우회했습니다. ㅠㅠ 

 기존의 KVM이나 Xen을 안쓰고 처음부터 만들었더니만, 역시 이런 저런 문제가 많이 생기네요. ㅠㅠ 

 다음에는 기존에 있는 걸 최대한 활용해야겠습니다. 그럼 좋은 하루 되세요. ;)


* Cygwin이 자주 업데이트 되는 바람에 가상머신을 사용해서 개발하실 수 있도록 준비했습니다. 계속해서 빌드에 오류가 생기신다면 http://jsandroidapp.cafe24.com/xe/8920 에 가셔서 가상머신을 사용하시기 바랍니다. ^^

* 2014년 4월 13일자 Cygwin을 기준으로 내용이 업데이트 되었습니다 ^^

* 빌드에 문제가 생기거나 정상적으로 동작하지 않는다면, MINT64 OS 개발용 가상머신을 이용하세요 ^^

** 가상머신 이미지 다운로드: http://jsandroidapp.cafe24.com/xe/8920**

* 관련 내용은 64비트 멀티코어 OS 원리와 구조 QnA 사이트(www.mint64os.pe.kr)에서 계속 업데이트 되고 있습니다. ^^


제 책 "64비트 멀티코어 OS 원리와 구조" 가 나온지도 벌써 3년이 지났습니다. ^^;;; 책이 출간됨과 동시에 가장 많이 질문을 받는 것이 OS 제작을 위한 환경 구축 방법인데요...

얼마전부터 환경 구축에 어려움을 겪는 분들이 많아지셔서 Cygwin 최신 버전을 다운 받아서 빌드를 해봤습니다. ^^;;; 실제로 빌드를 진행하다보니 몇 가지 변경점이 보이더군요. ㅠㅠ 그래서 그 내용을 여기다가 정리해놓습니다. ㅠㅠ

<출처 - yes24.com>

ps1) 혹시 아래대로 수행했는데 빌드에 문제가 생기거나 정상적으로 동작하지 않는다면, 곧 업로드될 MINT64 OS 개발용 가상머신을 이용하세요 ^^ ps2) 안타깝게도 Cygwin에 덮어쓰는 방법은 이제 더이상 동작하지 않습니다.


Cross Compiler를 만들려면 먼저 Cygwin 사이트(http://www.cygwin.com/install.html)로 이동해서 32비트 Cygwin Installer를 다운로드 받습니다. Setup.exe 파일을 다운하면 됩니다. ^^;;;

다운로드 후 파일을 실행하면 어떻게 설치할 것이고 패키지 소스는 어디에서 다운 받을 것인지 선택하는 부분이 나오는데, 책에 나와있는 카이스트 사이트(ftp://ftp.kaist.ac.kr)가 더이상 유효하지 않으니 다른 사이트를 선택해야합니다.

우리나라 근처에 있는게 아무래도... 속도도 빠르니 가까운 일본 주소인 ftp://ftp.jaist.ac.jp를 선택하면 됩니다. ^^;;; 패키지 소스를 다운 받을 주소를 선택하고 난 뒤에는 "Select Package" 화면이 나올 때까지 적당히 Next를 선택해주면 됩니다. ^^;;;

"Select Package" 화면이 나오면 아래의 항목들을 설치해줍니다. 책을 쓸 때는 C++ 관련 항목이 없어도 정상적으로 빌드가 되었지만, 지금은 C++ 컴파일러를 설치하지 않으니 configure 중에 오류가 발생하더군요. ㅠㅠ

+Devel
    + binutils - 2.24.51-2 버전, 소스 및 바이너리 모두 설치
    + bison - 2.7.1-1 버전, 바이너리만 설치
    + flex - 2.5.35-1 버전, 바이너리만 설치
    + gcc-core - 4.8.2-2 버전, 소스 및 바이너리 모두 설치
    + gcc-g++ - 4.8.2-2 버전, 바이너리만 설치
    + libiconv - 1.14-2 버전, 바이너리만 설치
    + libtool - 2.4.1 버전, 바이너리만 설치
    + make - 4.0-2 버전, 바이너리만 설치
    + patchutils - 0.3.2-1 버전, 바이너리만 설치
+Libs
    + libgmp-devel - 6.0.0a-1 버전, 바이너리만 설치
    + libmpfr-devel -3.1.2-1 버전, 바이너리만 설치
    + libmpc-devel - 1.0.2-1 버전, 바이너리만 설치
    + libncurses-devel - 5.7-18 버전, 바이너리만 설치

위의 항목이 설치되고 나면 binutils부터 차례대로 빌드를 시작하면 됩니다.

binutils를 빌드하려면 /usr/src 디렉터리 아래에 있는 binutils 디렉터리로 이동해서 다음과 같은 순서로 커맨드를 입력하면 빌드와 설치가 끝납니다.

// 디렉터리 이동 후 아래 작업 수행
$> cd /usr/src/binutils-2.24.51-2   <== 디렉터리 이름은 binutils 버전에 따라 다를 수 있음    
$> export TARGET=x86_64-pc-linux
$> export PREFIX=/usr/cross
$> ./configure --target=$TARGET --prefix=$PREFIX --enable-64-bit-bfd --disable-shared --disable-nls
$> make configure-host
$> make LDFLAGS="-static"
$> make install

아래는 테스트를 위한 부분입니다. 아래처럼 커맨드를 실행했을 때 x86_64 관련 항목이 보이면 제대로 된 것입니다. ^^;;;;

$> /usr/cross/bin/x86_64-pc-linux-ld --help | grep "supported "
/usr/cross/bin/x86_64-pc-linux-ld: supported targets: elf64-x86-64 elf32-i386 a.
out-i386-linux efi-app-ia32 efi-bsdrv-ia32 efi-rtdrv-ia32 efi-app-x86_64 efi-bsd
rv-x86_64 efi-rtdrv-x86_64 elf64-little elf64-big elf32-little elf32-big srec sy
mbolsrec tekhex binary ihex
/usr/cross/bin/x86_64-pc-linux-ld: supported emulations: elf_x86_64 elf_i386 i38
6linux

binutils 빌드 및 설치가 끝났다면 gcc를 cross compile할 차례입니다. gcc는 아래와 같은 순서로 입력하면 빌드와 설치가 완료됩니다. ^^;;;

압축 해제, 파일명은 cygwin에서 다운받은 GCC 버전에 따라서 다를 수 있으니 실제 디렉터리를 확인하여 입력해야 합니다. ^^;;; 아래 패치 파일도 모두 마찬가지 입니다.

$> cd /usr/src
$> tar -xvf gcc-4.8.2.tar.bz2
$> export TARGET=x86_64-pc-linux
$> export PREFIX=/usr/cross
$> export PATH=$PREFIX/bin:$PATH

압축 해제가 끝났으니 소스가 있는 디렉터리 이동 후 나머지 작업 수행합니다.

$> cd /usr/src/gcc-4.8.2    <== 디렉터리 이름은 GCC 버전에 따라 다를 수 있음
$> ./configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers --disable-shared --enable-multilib
$> make configure-host
$> make all-gcc
$> make install-gcc

빌드가 완료된 후 테스트용입니다. 아래와 같이 입력했을 때 m64가 보이면 정상적으로 설치된 것입니다.

$> /usr/cross/bin/x86_64-pc-linux-gcc -dumpspecs | grep -A1 multilib_options
*multilib_options:
m64/m32

빌드한 크로스 컴파일러는 MINT64 OS를 개발하는데 필요한 최소한의 환경만 구성되어 있습니다. 64비트 관련 gcc 라이브러리 등은 빌드되지 않은 상태이므로, 빌드된 컴파일러로 test.c 파일을 빌드했을 때 발생하는 오류는 무시하셔도 됩니다. ^^

자, 이제 64비트 바이너리를 생성할 수 있는 gcc가 생성되었으니, 즐거운 OS 프로그래밍하세요 ;)

64비트 멀티코어 원리와 구조가 나온지 약 3년이 지났습니다. 그동안 QnA 사이트를 운영하면서 가장 많이 받았던 질문이 개발환경 설정에 대한 글이었는데요, 아무래도 cygwin 버전이 올라가다보니 책에 있는 빌드방법으로는 제대로 빌드가 되지 않더라구요(지금도 개발환경에 대한 질문이 계속 올라온다는...). ㅠㅠ 그래도 많은 분들이 도와주셔서 어찌어찌 성공하신 분들도 있지만... 사실 책임은 저한테 있는지라 언젠가는 해결해야 겠다고 생각했습니다.

<64비트 멀티코어 원리와 구조 - 출처 yes24>

그래서~!!! 오늘 애기를 빡시게 놀게한다음 일찍 재우고 다시 개발환경을 만드는 중입니다. 그래서 내용도 업데이트하고 이번에는 가상머신으로 만들어서 다운받아 바로 쓸 수 있도록 만들 생각입니다. @0@)-b 이렇게 하면 빌드에 문제가 생겨도 가상머신을 다운받아 실행하면 되니 문제가 없을 것 같더군요. ^^;;;

크로스 컴파일러를 만드는게 시간이 많이 걸리는지라... 지금 시작하면 언제 끝날지 잘 모르겠네요. ㅠㅠ 그래도 이번에 확실히 해서 큰 포부를 가지고 OS 개발을 시작하셨다가... 개발 환경 설정 때문에 좌절하시는 일이 없도록 잘 해보겠습니다. @0@)/~

그럼 좋은 밤 되세요 >ㅁ<)-b

ps1) 아침에는 개발 환경 설정을 업데이트 할 수 있으면 좋겠네요 ㅠㅠ

ps2) Yes24 사이트에서는 책이 품절로 나오는군요. ' ')a... 교보문고에 가시면 아직 구할 수 있습니다. ^^

0. 들어가기 전에...

이 글은 64비트 멀티코어 OS 구조와 원리의 QnA 사이트(http://mint64os.pe.kr)에 등록된 RUMO님의 질문과 그에 대한 답변을 요약한 것입니다. 자세한 내용은 C언어로 커널을 개발중입니다를 참고하시기 바랍니다. ^^


1. 질문 내용

안녕하세요; 커널을 혼자 공부중인 학생입니다...

아주 기초부터 썩어빠진 놈이 커널짠답니다.

2개월째 낑낑 대는데 아주 기초적인 부분부터 막막합니다. 자비를 베풀어 도움을 주십사 합니다 ㅠ

먼저 boot.asm 을 만들었습니다....부트로더이지요. 책을 참고하여 만들었습니다.

또 어셈블리어로 kernel.bin을 만들었습니다. boot.asm이 kernel.bin으로 점프하지요. 맞습니다 여기까지 잘됩니다.

헌데 커널을 C언어로 작성한뒤 병합하니 이상하게도 boot.asm만 실행된다는 것입니다.

일단 개발환경부터 말씀드리자면 VM웨어에 우분투를 설치하여 작업중이며 테스트 또한 VM에 테스트중입니다.

원래 boot.asm과 kernel.asm은 아래와 같이 컴파일 하였습니다.

nasm -f bin -o boot.bin boot.asm nasm -f bin -o kernel.bin kernel.asm cat boot.bin kernel.bin > kernel.img

커널이 어셈으로 짜여졌을때는 매우 잘 동작 하였습니다.

헌데 C언어로 짠 커널 (ckernel.c라고 하겠습니다.)...

nasm -f bin -o boot.bin boot.asm gcc -c ckernel.c ld -e ckernel -Ttext 0x00 -e textout ckernel.c objcopy -R .comment -R .note -S -O binary ckernel.c ckerenl.bin cat boot.bin ckernel.bin > ckernel.img

이렇게 컴파일 하니 부트로더만 동작한다는 것입니다...

그러던중 알게된것이 엔트리 포인트입니다만 이것또한 저에겐 너무 와닿지 않는 개념인지라;;;

당췌 어찌 해야될지 모르겠습니다. 컴파일도 제대로 하고 있는게 맞는지; 엔트리 포인트를 제대로 찍은건지;

초보자라 설명도 중구난방입니다...첨부 파일을 보시면 바로 이해가 되실겁니다 -_-;;

몇주째 개고생중입니다. 제발 도와주십시오 ㅠ


2.답변 내용

안녕하세요, 커널을 만드시느라 고군분투하고 계시는군요. ^^

어셈블리어로 작성된 부트로더에서 C 언어로 작성된 32비트 커널로 점프하기 위해서는 크게 2가지가 필요합니다.

첫 번째는 스택입니다. 스택에 관련된 레지스터는 ESP와 EBP가 있는데, 1Mbyte 이하 영역중에서 마음에 드는 곳을 지정해주면 됩니다. 한가지 주의할 점은 스택은 아래로 자라기 때문에 레지스터에 설정하는 값이 스택 영역의 끝부분을 가리키고 있어야겠지요. boot.asm을 ESP는 초기화하여 스택은 만들어 놓으셨더군요. 그런데 EBP는 초기화 되어있지 않던데 EBP 레지스터도 ESP와 같은 값으로 초기화하면 좋을 것 같습니다. ^^;;;

두 번째는 엔트리 포인트입니다. 엔트리 포인트는 C 코드가 제어를 넘겨받았을 때 제일 처음 코드 수행을 시작해야 하는 부분을 나타냅니다. 만일 빌드 결과물이 리눅스의 ELF 파일 포맷이나 윈도우의 PE 파일 포맷 형식이라면 링커(LD)에 -e 옵션을 주어 엔트리 포인트를 지정해주는 것이 의미가 있습니다. 하지만, 지금은 커널을 만들고 있고 실제로 C 코드의 출력 결과물은 메타 정보 없이 코드/데이터만 들어있는 바이너리(Binary) 형식을 사용하고 있으니, 링커에서 지정해준 엔트리 포인트는 의미가 없습니다.

따라서 강제로 엔트리 포인트인 kernel_main() 함수를 부트로더가 점프하는 어드레스인 0x10000로 지정해줘야 합니다(kernel.c 파일을 보고 엔트리 포인트 함수가 저게 아닐까 생각했는데, 원하시는 함수가 있으시면 그걸로 선택하시면 됩니다 ^^). 저도 예전에 이 문제로 고민을 많이 했는데요, 해결방법은 아주 간단합니다. 컴파일러가 코드를 컴파일할 때 실제로 C 파일에 나열된 함수의 순서를 그대로 따릅니다. 그러니 엔트리 포인트로 사용할 함수를 C 파일에 가장 위쪽으로 옮겨주면 되겠지요. ^^

두 부분을 제외한 나머지는 제가 봤을 때 충분한 것 같네요. ^^ 부트로더에서 C 언어 커널로 점프하는 부분에 대한 더 자세한 내용이 궁금하시다면, 64비트 멀티코어 OS 원리와 구조 에서 7장을 참고하시면 됩니다. 엔트리 포인트에 대해서도 자세히 설명해 놓았으니 도움이 되실겁니다. ^^

그럼 파이팅입니다. ^^

ps) OS를 빌드하실 때 배치 파일이나 makefile을 이용하시면 좀더 편리하게 작업하실 수 있습니다. ;)

64비트 멀티코어 OS 원리와 구조 세트가 출간된지 3년이 다 되어갑니다. 책에 대한 QnA 사이트가 필요해서 책 출간과 동시에 mint64os.pe.kr 사이트도 운영 중인데요, 지금까지 약 300개의 QnA가 달렸습니다. @0@)/~ (그중에 오탈자에 대한 문의도 다수 있다는 건 함정... 쿨럭..;;;)

<출처 - yes24.com>

질문에 대해서 답변을 달다보면 가끔 통찰력이 느껴지는 질문도 있는데요, 이런 질문들은 혼자보기가 아깝더라구요. ^^;;; 그래서 정리해서 공유하는게 어떨까하는 생각을 했습니다. 시간이 나는대로 틈틈이 정리해서 올려볼 예정이니 기대해주세요 >ㅁ<)/~

그럼 좋은 하루 되세요 ^^

지난번에 Workspace님께서 MINT64 OS를 Mac OS X에서 개발하는 방법(http://osdevlog.tistory.com/1)을 올려주셨는데요, 

이번에 Jungsub K님께서도 내용을 조금 보강해서 올려주셨습니다. 감사합니다. Jungsub K님 ^^


해당 내용은 여기(http://nayuta.net/64비트_멀티코어_OS_원리와_구조/OS_X에서_개발환경_구축하기)에서 보실 수 있습니다.


각 OS에서 빌드하는 방법을 올려주시니... 왠지 MINT64 OS가 관심받고 있는 것 같아 마음이 훈훈하네요. ㅠㅠ


나중에 활동 많이 해주시는 분들을 모아서 식사라도 한 끼 대접해야할 듯 싶습니다. ㅠㅠ


다들 감사드리고, 앞으로도 많은 관심 부탁드립니다. (_ _)



리눅스 커널 부팅과정(Linux Kernel Booting Sequence) 분석

윈도우에 이어 리눅스도 부팅과정을 분석하고 있는데요, 소스코드가 있으니 윈도우보다는 그나마 조금 낫네요. ^^;;;; 여기저기서 자료를 좀 모았는데, 나름 정리해봅니다.

1.GRUB

윈도우에는 BOOTMGR이 있듯이 리눅스에는 GRUB이 있습니다. 이 GRUB의 역할은 Disk의 MBR 영역에 있다가 PC 부팅할 때 리눅스 커널(Linux Kernel)을 메모리에 올려주는 것입니다. GRUB은 Stage 1과 Stage 2로 구성되어 있는데요, Stage 1은 우리가 이야기하는 MBR 영역에 있는 1 Sector를 말하고 Stage 2는 Disk에 접근해서 커널을 로딩하는 파트를 말합니다.

  • Stage 1에서는 간단히 몇가지 BIOS 관련 Parameter를 설정하고 Stage 2로 넘어감
  • Stage 2에서는 리눅스 커널 파일(vmlinuz)을 분석해서 리눅스 커널의 부트로더(Bootloader)를 0x90000 ~ 에 로딩하고, 압축된 커널과 커널의 압축을 푸는 부분을 0x100000(1MB) ~ 에 로딩
  • Stage 2 후에는 커널 부트로더를 뛰어 넘어 0x90200 위치로 Jump(?)

사실 마지막 부분에 대한 내용은 제가 제대로 확인을 못해서 좀 애매하긴한데, 한빛미디어에서 나온 리눅스 커널의 이해를 보면 LILO 시절부터 0x90200 어드레스로 Jump를 하고 있습니다. 사실 GRUB 역시 크게 다르지 않을 것이라 생각합니다만... 좀 더 시간을 갖고 확인할 필요가 있을 것 같네요. ^^;;;;

GRUB에 대한 자세한 내용은 The GRUB MBR을 참고하세요 ;)

2. 리눅스 커널 부트로더

리눅스 커널 부트로더는 arch/x86/boot/header.S에 있는데요, 크기가 2 Sector입니다. ^^;;; 따라서 앞부분의 512Byte는 MBR 역할을 하고 그 뒤에 512Byte는 커널 부트로더와 커널을 이어주는 역할을 합니다.

그런데... 예전에는 바로 부팅할 수 있도록 했나 본데, 지금은 LILO나 GRUB의 등장으로 지원하지 않는 것 같습니다. 아래는 header.S에 있는 코드의 일부분인데요, 보시면 플로피를 통한 직접부팅을 지원 안하니까 다시 디스켓 빼라는 메시지와 함께 재부팅하는 코드만 달랑 있습니다. ^^;;;; 사실 이 부분의 코드가 GRUB에 의해 0x90000에 로딩되는 부분입니다.

 18 #include <asm/segment.h>
 19 #include <linux/utsrelease.h>
 20 #include <asm/boot.h>
 21 #include <asm/e820.h>
 22 #include <asm/page_types.h>
 23 #include <asm/setup.h>
 24 #include "boot.h"
 25 #include "offsets.h"
 26 
 27 BOOTSEG         = 0x07C0                /* original address of boot-sector */
 28 SYSSEG          = 0x1000                /* historical load address >> 4 */
 29 
 30 #ifndef SVGA_MODE
 31 #define SVGA_MODE ASK_VGA
 32 #endif
 33 
 34 #ifndef RAMDISK
 35 #define RAMDISK 0
 36 #endif
 37 
 38 #ifndef ROOT_RDONLY
 39 #define ROOT_RDONLY 1
 40 #endif
 41 
 42         .code16
 43         .section ".bstext", "ax"
 44 
 45         .global bootsect_start
 46 bootsect_start:
 47 
 48         # Normalize the start address
 49         ljmp    $BOOTSEG, $start2
 50 
 51 start2:
 52         movw    %cs, %ax
 53         movw    %ax, %ds
 54         movw    %ax, %es
 55         movw    %ax, %ss
 56         xorw    %sp, %sp
 57         sti
 58         cld
 59 
 60         movw    $bugger_off_msg, %si
 61 
 62 msg_loop:
 63         lodsb
 64         andb    %al, %al
 65         jz      bs_die             // <<= 무조건 Jump하면 메시지 출력후 bs_die로 이동~!!
 66         movb    $0xe, %ah
 67         movw    $7, %bx
 68         int     $0x10
 69         jmp     msg_loop
 70 
 71 bs_die:
 72         # Allow the user to press a key, then reboot
 73         xorw    %ax, %ax
 74         int     $0x16
 75         int     $0x19
 76 
 77         # int 0x19 should never return.  In case it does anyway,
 78         # invoke the BIOS reset code...
 79         ljmp    $0xf000,$0xfff0
 80 
 81         .section ".bsdata", "a"
 82 bugger_off_msg:
 83         .ascii  "Direct booting from floppy is no longer supported.\r\n"
 84         .ascii  "Please use a boot loader program instead.\r\n"
 85         .ascii  "\n"
 86         .ascii  "Remove disk and press any key to reboot . . .\r\n"
 87         .byte   0
 88 
 89 
 90         # Kernel attributes; used by setup.  This is part 1 of the
 91         # header, from the old boot sector.
 92 
 93         .section ".header", "a"
 94         .globl  hdr

이 바로 뒤는 당연히 0x90200에 위치하는데요, _start로 시작하며 커널의 bss 영역을 초기화하고 main 함수로 Jump하는 부분이 있습니다. 해당 부분의 코드는 arch/x86/boot/header.S를 참고하세요 ;)

104         # offset 512, entry point
105 
106         .globl  _start
107 _start:
108                 # Explicitly enter this as bytes, or the assembler
109                 # tries to generate a 3-byte jump here, which causes
110                 # everything else to push off to the wrong offset.
111                 .byte   0xeb            # short (2-byte) jump
112                 .byte   start_of_setup-1f
113 1:
114 
115         # Part 2 of the header, from the old setup.S

                             ... 생략 ...

224         .section ".inittext", "ax"
225 start_of_setup:
226 #ifdef SAFE_RESET_DISK_CONTROLLER
227 # Reset the disk controller.
228         movw    $0x0000, %ax            # Reset disk controller
229         movb    $0x80, %dl              # All disks
230         int     $0x13
231 #endif
232 
                             ... 생략 ...
271 
272 # Check signature at end of setup
273         cmpl    $0x5a5aaa55, setup_sig
274         jne     setup_bad
275 
276 # Zero the bss
277         movw    $__bss_start, %di
278         movw    $_end+3, %cx
279         xorl    %eax, %eax
280         subw    %di, %cx
281         shrw    $2, %cx
282         rep; stosl
283 
284 # Jump to C code (should not return)
285         calll   main       // <== 여기가 다음 스텝인 리눅스 커널 로더로 jump하는 부분

3. 리눅스 커널 로더

리눅스 커널 로더는 arch/x86/boot/main.c에 있습니다. 제가 이 코드에 리눅스 "리눅스 커널로더"라는 명칭을 사용해도 될지 모르겠지만, 일단 이 시점에서 32bit 모드로 변환하는 코드가 들어 있어서 커널로더라고 했습니다. ^^;;; 이 코드의 역할은 32bit 커널이 실행될 준비를 하는 건데요, BIOS를 통해 메모리 크기를 확인하고 키보드를 초기화한다음 32bit 커널로 Jump합니다. 이때 Jump하는 어드레스는 32bit 커널이 로딩되어 있는 0x100000(1MB)입니다. ^^;;; 해당 위치에 GRUB이 이미 올려놓았거든요 ;)

아래는 arch/x86/boot/main.c 파일의 일부분입니다. 좀더 자세한 코드는 arch/x86/boot/main.c를 통해서 확인할 수 있습니다. ^^;;;

125 void main(void)
126 {
127         /* First, copy the boot header into the "zeropage" */
128         copy_boot_params();
129 
130         /* End of heap check */
131         init_heap();
132 
133         /* Make sure we have all the proper CPU support */
134         if (validate_cpu()) {
135                 puts("Unable to boot - please use a kernel appropriate "
136                      "for your CPU.\n");
137                 die();
138         }
139 
140         /* Tell the BIOS what CPU mode we intend to run in. */
141         set_bios_mode();
142 
143         /* Detect memory layout */
144         detect_memory();
145 
146         /* Set keyboard repeat rate (why?) */
147         keyboard_set_repeat();
148 
149         /* Query MCA information */
150         query_mca();
151 
152         /* Query Intel SpeedStep (IST) information */
153         query_ist();
154 
155         /* Query APM information */
156 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
157         query_apm_bios();
158 #endif
159 
160         /* Query EDD information */
161 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
162         query_edd();
163 #endif
164 
165         /* Set the video mode */
166         set_video();
167 
168         /* Parse command line for 'quiet' and pass it to decompressor. */
169         if (cmdline_find_option_bool("quiet"))
170                 boot_params.hdr.loadflags |= QUIET_FLAG;
171 
172         /* Do the last things and invoke protected mode */
173         go_to_protected_mode();    // <== 여기가 32bit 커널로 Jump하는 부분이에요 :)
174 }

4. 리눅스 커널 - 압축된 커널

커널 로더에 의해서 32bit 커널로 jmp(0x100000)하면 이제 본격적으로 커널이 시작되는데요, 이때 수행되는 함수는 arch/x86/boot/compressed/head_32.S에 있는 가장 첫 번째 함수, startup_32입니다. 파일의 가장 앞부분에 있는 함수기 때문에 0x100000 어드레스로 Jump하는 것만으로 바로 저 함수에 도달하는 것이지요. ^^;;;

startup_32() 함수는 여러가지 일을 하는데요, 가장 중요한 역할은 자신에게 붙어있는 압축된 커널을 안전한 버퍼로 옮긴다음 압축을 푸는 겁니다. >ㅁ<)-b 그리고 압축을 풀고나면 이를 다시 0x400000 어드레스로 옮기고 jump하는 것이지요. 안전한 버퍼라는 말이 조금 애매하긴한데 대략 0x400000 어드레스 부근인 것 같습니다(정확하게는 _end 오프셋 뒷쪽인데요, 이 값은 빌드 타임에 정해지는 값이라 compressed 디렉터리의 코드량에 따라서 좀 달라질 수도 있을 것 같습니다. ^^;;; (정확하지는 않아요 ㅠㅠ)

다음은 압축된 커널을 안전한 곳으로 복사하고 압축을 푼 뒤에 압축이 해제된 곳으로 jump하는 코드입니다. 전체 내용은 arch/x86/boot/compressed/head_32.S를 참조하세요.


 26 #include <linux/linkage.h>
 27 #include <asm/segment.h>
 28 #include <asm/page_types.h>
 29 #include <asm/boot.h>
 30 #include <asm/asm-offsets.h>
 31 
 32 .section ".text.head","ax",@progbits
 33 ENTRY(startup_32)
 34         cld
 35         /* test KEEP_SEGMENTS flag to see if the bootloader is asking
 36          * us to not reload segments */
 37         testb $(1<<6), BP_loadflags(%esi)
 38         jnz 1f
 39 
 40         cli
 41         movl $(__BOOT_DS),%eax
 42         movl %eax,%ds
 43         movl %eax,%es
 44         movl %eax,%fs
 45         movl %eax,%gs
 46         movl %eax,%ss
 47 1:
                     ... 생략 ...

 87 /* Copy the compressed kernel to the end of our buffer
 88  * where decompression in place becomes safe.
 89  */
 90         pushl %esi
 91         leal _end(%ebp), %esi
 92         leal _end(%ebx), %edi
 93         movl $(_end - startup_32), %ecx
 94         std
 95         rep
 96         movsb
 97         cld
 98         popl %esi
 99 
100 /* Compute the kernel start address.
101  */
102 #ifdef CONFIG_RELOCATABLE
103         addl    $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
104         andl    $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
105 #else
106         movl    $LOAD_PHYSICAL_ADDR, %ebp
107 #endif
108 
109 /*
110  * Jump to the relocated address.
111  */
112         leal relocated(%ebx), %eax
113         jmp *%eax
114 ENDPROC(startup_32)
115 
116 .section ".text"
117 relocated:
118 
119 /*
120  * Clear BSS
121  */
122         xorl %eax,%eax
123         leal _edata(%ebx),%edi
124         leal _end(%ebx), %ecx
125         subl %edi,%ecx
126         cld
127         rep
128         stosb
129 
130 /*
131  * Setup the stack for the decompressor
132  */
133         leal boot_stack_end(%ebx), %esp
134 
135 /*
136  * Do the decompression, and jump to the new kernel..
137  */
138         movl output_len(%ebx), %eax
139         pushl %eax
140                         # push arguments for decompress_kernel:
141         pushl %ebp      # output address
142         movl input_len(%ebx), %eax
143         pushl %eax      # input_len
144         leal input_data(%ebx), %eax
145         pushl %eax      # input_data
146         leal boot_heap(%ebx), %eax
147         pushl %eax      # heap area
148         pushl %esi      # real mode pointer
149         call decompress_kernel       <<= 요기가 실제로 압축 해제를 담당하는 부분
150         addl $20, %esp
151         popl %ecx
152 
153 #if CONFIG_RELOCATABLE
154 /* Find the address of the relocations.
155  */
156         movl %ebp, %edi
157         addl %ecx, %edi
158 
159 /* Calculate the delta between where vmlinux was compiled to run
160  * and where it was actually loaded.
161  */
162         movl %ebp, %ebx
163         subl $LOAD_PHYSICAL_ADDR, %ebx
164         jz   2f         /* Nothing to be done if loaded at compiled addr. */
165 /*
166  * Process relocations.
167  */
168 
169 1:      subl $4, %edi
170         movl 0(%edi), %ecx
171         testl %ecx, %ecx
172         jz 2f
173         addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
174         jmp 1b
175 2:
176 #endif
177 
178 /*
179  * Jump to the decompressed kernel.
180  */
181         xorl %ebx,%ebx
182         jmp *%ebp                   // <<= 압축을 푼 어드레스로 jump하는 부분

실제로 압축 해제는 decompress_kernel() 함수를 호출해서 처리하는데, 이 함수는 arch/x86/boot/compressed/misc.c 파일에 있습니다. decompress_kernel() 함수의 원형은 아래와 같습니다.

304 asmlinkage void decompress_kernel(void *rmode, memptr heap,
305                                   unsigned char *input_data,
306                                   unsigned long input_len,
307                                   unsigned char *output)

위의 함수 원형을 보면 결국 우리가 넘겨주는 output buffer에 차곡차곡 압축을 풀어줌을 알 수 있는데요, 저 output 버퍼의 위치는 decompress_kernel()을 호출하는 곳에서 LOAD_PHYSICAL_ADDR로 대략 정의하고 있는데요, 정확하게 말하기는 힘들지만 대략 0x400000(4MB) 어드레스 부근이라고 생각됩니다.

리눅스 커널의 이해를 보면 압축을 해제한 뒤에 다시 1MB 영역의 어드레스로 덮어쓴다는 이야기가 있던데, 실제 코드에서는 바로 jump하는 부분만 확인했을 뿐 데이터를 이동하는 부분은 찾지 못했습니다. ㅠㅠ 그렇다는 이야기는 이제는 더이상 안옮기거나 제가 못찾았다는 이야기겠지요. 혹시 찾으신 분 있으시면 덧글로 제보 부탁드릴께요 ㅠㅠ

5. 리눅스 커널 - 실제 커널

이제 압축까지 다 해제된 실제 커널로 왔습니다. 커널의 실제 엔트리 포인트는 startup_32() 함수이구요, arch/x86/kernel/head_32.S에 있습니다. 이 함수를 보면 페이지 테이블을 임시로 설정하고 start_kernel() 함수를 호출하는 것을 볼 수 있습니다. start_kernel()은 32bit 커널에서 C 코드가 시작되는 부분이지요. 여기까지만 오면 사실 부팅은 거의 끝난 겁니다. ^^;;;;

다음은 startup_32() 함수의 코드입니다. 전체 코드는 arch/x86/kernel/head_32.S 파일을 참조하세요.

269 #ifdef CONFIG_SMP
270 ENTRY(startup_32_smp)
271         cld
272         movl $(__BOOT_DS),%eax
273         movl %eax,%ds
274         movl %eax,%es
275         movl %eax,%fs
276         movl %eax,%gs
277 #endif /* CONFIG_SMP */

             ... 생략 ...

451         movl $(__KERNEL_STACK_CANARY),%eax
452         movl %eax,%gs
453 
454         xorl %eax,%eax                  # Clear LDT
455         lldt %ax
456 
457         cld                     # gcc2 wants the direction flag cleared at all times
458         pushl $0                # fake return address for unwinder
459 #ifdef CONFIG_SMP
460         movb ready, %cl
461         movb $1, ready
462         cmpb $0,%cl             # the first CPU calls start_kernel
463         je   1f
464         movl (stack_start), %esp
465 1:
466 #endif /* CONFIG_SMP */
467         jmp *(initial_code)     // <<= start_kernel() 함수의 어드레스가 들어 있음
468 

6. 리눅스 커널 - C 함수 시작

start_kernel() 함수는 init/main.c 파일에 있으며, 각종 커널 관련 자료구조(페이지 테이블 포함)를 다시 한번 초기화하고 init 프로세스를 생성하는 역할을 합니다. start_kernel() 함수의 가장 마지막은 rest_init() 함수인데요, 이 함수는 kernel_thread() 함수를 호출해서 별도의 스레드를 만든 다음 kernel_init 함수를 수행하게 만듭니다. 그리고 자기 자신은 계속 코드를 수행하면서 cpu_idle() 함수를 수행하여 idle process가 됩니다.

다음은 start_kernel() 함수의 일부분과 rest_init() 함수의 일부분을 나타낸 것입니다.

535 
536 asmlinkage void __init start_kernel(void)
537 {
538         char * command_line;
539         extern struct kernel_param __start___param[], __stop___param[];
540 
541         smp_setup_processor_id();
542 
543         /*
544          * Need to run as early as possible, to initialize the
545          * lockdep hash:
546          */
547         lockdep_init();
548         debug_objects_early_init();
549 
550         /*
551          * Set up the the initial canary ASAP:
552          */
553         boot_init_stack_canary();
554 
555         cgroup_init_early();
556 
557         local_irq_disable();
558         early_boot_irqs_off();
559         early_init_irq_lock_class();
560 
561 /*
562  * Interrupts are still disabled. Do necessary setups, then
563  * enable them
564  */
565         lock_kernel();
566         tick_init();
567         boot_cpu_init();
568         page_address_init();
569         printk(KERN_NOTICE "%s", linux_banner);
570         setup_arch(&command_line);
571         mm_init_owner(&init_mm, &init_task);
572         setup_command_line(command_line);
573         setup_per_cpu_areas();
574         setup_nr_cpu_ids();
575         smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
576 
577         /*
578          * Set up the scheduler prior starting any interrupts (such as the
579          * timer interrupt). Full topology setup happens at smp_init()
580          * time - but meanwhile we still have a functioning scheduler.
581          */
582         sched_init();
583         /*
584          * Disable preemption - early bootup scheduling is extremely
585          * fragile until we cpu_idle() for the first time.
586          */
587         preempt_disable();
588         build_all_zonelists();
589         page_alloc_init();
590         printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
591         parse_early_param();
592         parse_args("Booting kernel", static_command_line, __start___param,
593                    __stop___param - __start___param,
594                    &unknown_bootoption);
595         if (!irqs_disabled()) {
596                 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
597                                 "enabled *very* early, fixing it\n");
598                 local_irq_disable();
599         }
600         sort_main_extable();
601         trap_init();
602         rcu_init();
603         /* init some links before init_ISA_irqs() */
604         early_irq_init();
605         init_IRQ();
606         pidhash_init();
607         init_timers();
608         hrtimers_init();
609         softirq_init();
610         timekeeping_init();
611         time_init();
612         sched_clock_init();
613         profile_init();
614         if (!irqs_disabled())
615                 printk(KERN_CRIT "start_kernel(): bug: interrupts were "
616                                  "enabled early\n");
617         early_boot_irqs_on();
618         local_irq_enable();
619 
620         /*
621          * HACK ALERT! This is early. We're enabling the console before
622          * we've done PCI setups etc, and console_init() must be aware of
623          * this. But we do want output early, in case something goes wrong.
624          */
625         console_init();
626         if (panic_later)
627                 panic(panic_later, panic_param);
628 
629         lockdep_info();
630 
631         /*
632          * Need to run this when irqs are enabled, because it wants
633          * to self-test [hard/soft]-irqs on/off lock inversion bugs
634          * too:
635          */
636         locking_selftest();
637 
638 #ifdef CONFIG_BLK_DEV_INITRD
639         if (initrd_start && !initrd_below_start_ok &&
640             page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
641                 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
642                     "disabling it.\n",
643                     page_to_pfn(virt_to_page((void *)initrd_start)),
644                     min_low_pfn);
645                 initrd_start = 0;
646         }
647 #endif
648         vmalloc_init();
649         vfs_caches_init_early();
650         cpuset_init_early();
651         page_cgroup_init();
652         mem_init();
653         enable_debug_pagealloc();
654         cpu_hotplug_init();
655         kmem_cache_init();
656         kmemtrace_init();
657         debug_objects_mem_init();
658         idr_init_cache();
659         setup_per_cpu_pageset();
660         numa_policy_init();
661         if (late_time_init)
662                 late_time_init();
663         calibrate_delay();
664         pidmap_init();
665         pgtable_cache_init();
666         prio_tree_init();
667         anon_vma_init();
668 #ifdef CONFIG_X86
669         if (efi_enabled)
670                 efi_enter_virtual_mode();
671 #endif
672         thread_info_cache_init();
673         cred_init();
674         fork_init(num_physpages);
675         proc_caches_init();
676         buffer_init();
677         key_init();
678         security_init();
679         vfs_caches_init(num_physpages);
680         radix_tree_init();
681         signals_init();
682         /* rootfs populating might need page-writeback */
683         page_writeback_init();
684 #ifdef CONFIG_PROC_FS
685         proc_root_init();
686 #endif
687         cgroup_init();
688         cpuset_init();
689         taskstats_init_early();
690         delayacct_init();
691 
692         check_bugs();
693 
694         acpi_early_init(); /* before LAPIC and SMP init */
695 
696         ftrace_init();
697 
698         /* Do the rest non-__init'ed, we're now alive */
699         rest_init();               // <<= 요기가 실제 init 프로세스 생성으로 가는 부분
700 }

아래는 rest_init() 함수의 내용입니다. 

451 static noinline void __init_refok rest_init(void)
452         __releases(kernel_lock)
453 {
454         int pid;
455 
            // init 프로세스를 생성하는 kernel_init 스레드를 만드는 부분
456         kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
457         numa_default_policy();
458         pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
459         kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
460         unlock_kernel();
461 
462         /*
463          * The boot idle thread must execute schedule()
464          * at least once to get things moving:
465          */
466         init_idle_bootup_task(current);
467         rcu_scheduler_starting();
468         preempt_enable_no_resched();
469         schedule();
470         preempt_disable();
471 
472         /* Call into cpu_idle with preempt disabled */
473         cpu_idle();              // <<= Idle Process가 되는 부분
474 }

kernel_thread()는 아래 코드에서 볼 수 있듯이 마지막에 init_post() 함수를 호출하는데 이 함수가 최종적으로 init process를 생성합니다. 다음은 kernel_thread() 함수와 init_post() 함수의 코드입니다. 리눅스가 실행될 때 일반적으로 initrd와 같은 RAMDISK 이미지를 사용하기 때문에, init_post() 함수 안의 ramdisk_execute_command 변수에는 "/init" 이 들어가 있으며, 따라서 initrd의 루트 디렉터리의 init 프로세스를 실행합니다.

initrd에 있는 init은 사실 스크립트로 기본적인 드라이버를 로딩하고 루트 디스크를 마운트한 뒤, 실제 init process로 제어를 넘깁니다. 그러면 본격적으로 커널이 실행되는 것이죠. ^^;;;;

847 static int __init kernel_init(void * unused)
848 {
849         lock_kernel();
850         /*
851          * init can run on any cpu.
852          */
853         set_cpus_allowed_ptr(current, cpu_all_mask);
854         /*
855          * Tell the world that we're going to be the grim
856          * reaper of innocent orphaned children.
857          *
858          * We don't want people to have to make incorrect
859          * assumptions about where in the task array this
860          * can be found.
861          */
862         init_pid_ns.child_reaper = current;
863 
864         cad_pid = task_pid(current);
865 
866         smp_prepare_cpus(setup_max_cpus);
867 
868         do_pre_smp_initcalls();
869         start_boot_trace();
870 
871         smp_init();
872         sched_init_smp();
873 
874         do_basic_setup();
875 
876         /*
877          * check if there is an early userspace init.  If yes, let it do all
878          * the work
879          */
880 
881         if (!ramdisk_execute_command)
882                 ramdisk_execute_command = "/init";
883 
884         if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
885                 ramdisk_execute_command = NULL;
886                 prepare_namespace();
887         }
888 
889         /*
890          * Ok, we have completed the initial bootup, and
891          * we're essentially up and running. Get rid of the
892          * initmem segments and start the user-mode stuff..
893          */
894 
895         init_post();       // <<= init process 생성으로 가는 부분
896         return 0;
897 }

803 static noinline int init_post(void)
804         __releases(kernel_lock)
805 {
806         /* need to finish all async __init code before freeing the memory */
807         async_synchronize_full();
808         free_initmem();
809         unlock_kernel();
810         mark_rodata_ro();
811         system_state = SYSTEM_RUNNING;
812         numa_default_policy();
813 
814         if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
815                 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
816 
817         (void) sys_dup(0);
818         (void) sys_dup(0);
819 
820         current->signal->flags |= SIGNAL_UNKILLABLE;
821 
822         if (ramdisk_execute_command) {   // <<= initrd와 같이 RAMDISK 이미지를 사용하는 경우 이 코드로 진입하고, 이때 ramdisk_execute_command는 /init이 들어 있음
823                 run_init_process(ramdisk_execute_command);
824                 printk(KERN_WARNING "Failed to execute %s\n",
825                                 ramdisk_execute_command);
826         }
827 
828         /*
829          * We try each of these until one succeeds.
830          *
831          * The Bourne shell can be used instead of init if we are
832          * trying to recover a really broken machine.
833          */
834         if (execute_command) {
835                 run_init_process(execute_command);
836                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
837                                         "defaults...\n", execute_command);
838         }
839         run_init_process("/sbin/init");
840         run_init_process("/etc/init");
841         run_init_process("/bin/init");
842         run_init_process("/bin/sh");
843 
844         panic("No init found.  Try passing init= option to kernel.");
845 }

start_kernel() 함수 이하에 있는 함수들의 전체 코드는 init/main.c를 참고하기 바랍니다. ^^

약간 보기 불편한 점은 있지만 그래도 한번 정리해놓으니 좋군요.
그럼 다들 좋은 밤 되세요 ;)

참고 자료

"64비트 멀티코어 OS 원리와 구조"가 출간된지 벌써 1년이 다되었습니다. ^^)-b 그동안 MINT64 OS 개발과 관련해서 많은 질답이 오고 갔는데요, 그중에서 빼 놓을 수 없는 것이 바로 "개발 환경 설정"과 관련된 내용입니다. ㅠㅠ 실제로 개발 환경 설정때문에 많은 분들이 고생을 하고 계신데요, 이 문제로 고민하시던 Ethobis님께서 해결책을 내놓으셨습니다(Ethobis님 감사합니다. ㅠㅠ).


Ethobis님께서 사용하신 방법은 Cygwin을 기본으로 설치하고 난 뒤 부록 CD에 있는 개발환경을 그대로 복사하는 방법인데요, Cross Compiler를 만드는 복잡한 단계를 거치지 않아도 되기 때문에 괜찮은 것 같습니다. ^^


부록 CD를 다운 받는 곳과 Ethobis님의 글은 아래에서 보실 수 있습니다. ;)


    부록 CD : http://jsandroidapp.cafe24.com/xe/220

    Ethobis님의 글 : http://ethobis.tistory.com/63


그럼 즐거운 OS 프로그래밍하세요 :)


ps) 좋은 팁을 알려주신 Ethobis님께 감사의 말씀을 전합니다. ^^

"64비트 멀티코어 OS 원리와 구조"가 2012년 대한민국 우수학술도서로 선정된지 얼마되지 않아서 또 기쁜소식이 들려왔습니다. Workspace님께서 MINT64 OS를 Mac OS X 10.7.4에서 빌드하는 방법을 올려주셨거든요 ;)


아래 글 링크 나갑니다.


    OS X 10.7.4에서 개발 환경 구축하기: http://osdevlog.tistory.com/1

    Chapter 7 C Kernel 실행까지~: http://osdevlog.tistory.com/2


Workspace님 감사합니다. :)

즐거운 OS 개발 하시길 바래요 ^^)-b

근 한달동안 모든 코딩을 어셈블리어로만 하고 있다보니 호출 규약(Calling Convention)을 다시 살펴보고 있습니다. C로 프로그래밍하던 시절에는 호출 규약은 별로 신경쓰지 않았는데, 어셈블리어로 함수를 호출하려니 안 볼수가 없더군요. ㅠㅠ


MINT64 OS를 만들 때 어셈블리어로 코딩했으면서 왠 엄살이냐고 생각하실텐데... 리눅스에서 사용하는 호출 규약(Calling Convention)과 윈도우에서 사용하는 호출 규약은 상당한 차이가 있더라구요. ㅠㅠ 그래서 적응하는데 살짝 힘들었습니다. 사실 적응한다기 보다는 차이를 아는데 시간이 좀 걸렸지요. ㅠㅠ

64비트 리눅스의 호출 규약

리눅스는 64비트 모드에서 파라미터를 전달할 때 윈도우보다 레지스터를 더 많이 사용합니다. 정수 타입의 파라미터를 전달할 때는 순서대로 RDI, RSI, RDX, RCX, R8, R9까지 6개의 레지스터를 사용하고 7개 이상이면 스택을 통해 전달합니다. 실수 타입의 파라미터의 경우는 XMM0 ~ XMM7까지 8개를 순서대로 사용하고 그 이상이면 스택으로 전달하지요. ;)


반환값은 정수일 때 RAX(하위 64비트), RDX(상위 64비트)를 사용하고, 실수일때는 XMM0(하위 128비트), XMM1(상위 128비트)를 사용합니다. 아래는 64비트 멀티코어 OS 원리와 구조의 11.2.2 장에서 추출한 호출 규약 그림입니다. ;)




64비트 윈도우의 호출 규약

반면, 윈도우는 64비트 모드에서 파라미터를 전달할 때 레지스터 4개만 사용합니다. 정수 타입의 경우는 순서대로 RCX, RDX, R8, R9를 사용하고 나머지는 스택으로 전달합니다. 실수의 경우는 XMM0 ~ XMM3까지 4개를 순서대로 사용하며 나머지는 스택으로 전달합니다. 여기까지 보면 파라미터로 사용하는 레지스터의 종류와 개수만 차이가 나는 것 같습니다만.... 실제로 보면 스택을 사용하는 방법도 차이가 있습니다.


리눅스의 경우 파라미터를 전달할 때 스택을 꽉꽉 채워서 사용하는 반면, 윈도우의 경우는 4개의 레지스터가 들어갈 공간만큼을 띄워서 사용합니다. 즉, 파라미터 4개가 RCX, RDX, R8, R9를 통해 전달되지만, 스택에 이 4개를 위한 공간을 할당해놓는 것이죠. 아래 그림을 보시면 좀 더 이해하시기 편할 겁니다. ^^;;;





이 차이 때문에 한참을 헤맸네요. ㅠㅠ 스택에 저 공간을 안 할당해놓으면 파라미터가 잘못 전될되서 보기좋게 함수 호출이 실패를... 쿨럭..;;; 그리고 주의할 점은 비록 파라미터가 4개 미만이더라도 함수 호출 시 저 영역은 무조건 할당해야 한다는 겁니다. 에궁... 진짜 알고나면 별 것 아닌데... 코드를 몇 번이나 갈아 엎었는지 모르겠네요. ㅠㅠ


C로 코딩을 했으면 아예 신경을 안써도 되는 부분인데... 어셈블리어로 코딩하다보니 삽질을 하고 말았군요. ㅎㅎ 윈도우 호출 규약에 대한 보다 자세한 내용이 궁금하시다면 MSDN 사이트를 참고하시기 바랍니다. ^^


그나저나 PE32 파일 포멧을 읽어 메모리에 로딩하여 실행하는 가볍게 어셈블리어로 작성하는 괴수(?)를 어떻게해야 따라잡을 수 있을까요? 아우... 따라가려니 죽을 것만 같네요. ㅠㅠ


그럼 좋은 밤 되세요 ;)

<64비트 멀티코어 OS 원리와 구조>


제가 오랜시간을 걸쳐 정리한 책, 64비트 멀티코어 OS 원리와 구조가 "2012년 대한민국학술원 우수학술도서"에 선정되었습니다. 이런 영광이... 제게도 이런 날이 오는군요. ㅠㅠ)-b 우수 학술 도서 목록은 http://www.nas.go.kr/info/notice/view.jsp?NP_Code=10000043&NP_DataCode=20000014&NGB_Code=10001430에서 살펴보실 수 있습니다. ^^


그동안 64비트 멀티코어 OS 원리와 구조를 구매해주신 많은 분들께 감사드리며, 우수학술도서로 선정될 수 있도록 힘써주신 한빛미디어의 한동훈님께 감사드립니다. 그리고 책이 나올 수 있도록 도와주시고 응원해주신 많은 분들께도 감사의 말씀을 드립니다. 앞으로 책이 많이 나가서 얼른 2쇄 찍고 부끄러운 오탈자를 정리할 수 있으면 좋겠네요. ㅠㅠ)-b


앞으로 더욱 열심히 하는 kkamagui가 되겠습니다. ^^)-b

그럼 좋은 밤(?) 되세요 ;)


후배의 소개로 내일... 이 아니라 오늘이군요. 오늘 "아키텍트를 꿈꾸는 사람들" 스터디 그룹에서 간단히 발표를 하게 되었습니다. ^^;;; 제가 말 주변이 좋지 않아서 발표를 잘 할 수 있을지 의문이지만... 일단 초대 받았으니 가서 열심히 하고 오겠습니다. ㅎㅎ

강남역 토즈 2호점에서 한다던데... 서울 사람들은 참 멋진 것 같아요 ;) 주말에도 열심히 공부하고 토론하는 것 보면... 정말 부럽다는 생각도 들고 대단하다는 생각도 듭니다. 스터디 그룹에 계시는 분들이 다들 대단하셔서 제가 가서 정보를 전달해 드리는 건 크게 의미가 없을 것 같아서 그냥 개발 스토리 위주로 PPT를 만들었는데... 잘한 짓인지는 모르겠네요. ㅠㅠ

일단 다녀와서 후기를 남기겠습니다~ ㅎㅎ
그럼 다들 좋은 밤 되세요 ;) 



굵게 표시된 부분이 오타가 수정된 부분입니다.


==== 1권의 오탈자 내용 ====

*. 79 Page, 아래에서 5 번째 줄

보호 모드는 IA-32e 모드로 전환하려면 공식적으로 거쳐야 하는 모드로, 32비트 윈도우나 리눅스 OS가 동작하는 기본 모드입니다.


*. 80 Page, 위에서 6 번째 IA-3e 모드를 IA-32e 모드로 수정

IA-32e 모드


*. 80 Page, 아래에서 5 번째줄

리얼 모드에서 전환할 수 있는 모드는 공식적으로 보호 모드뿐 이며, 보호 모드에서는 가상 8086 모드나 IA-32e 모드, 다시 리얼 모드로 전환할 수 있습니다. 시스템 관리 모드는 모든 모드에서 진입할 수 있고, 처리가 끝나 이전의 운영 모드로 복귀하거나 리셋을 통해 리얼 모드로 진입할 수 있습니다.


*. 80 Page, 아래에서 3 번째 줄

화살표가 연결되지 않은 리얼 모드에서 IA-32e 모드로 전환하는 것은 불가능하며, 무리하게 시도하면 리셋이나 예외가 발생할 수있습니다(실제 편법을 사용하면 가능하긴 하지만 여기서는 다루지 않습니다).


*. 83 Page, [그림 3-3]에서 128비트 XMM 레지스터(16개), 64비트 RIP, 64비트 RFLAGS, CR8 레지스터 부분 수정됨

[그림 3-3].PNG


*. 85 Page의 "여기서 잠깐"의 시작에서 6번째 줄

곱셈 명령은 AX와 오퍼랜드를 곱한 후, 그 결과를 DX:AX나 혹은 AX에 저장하도록 설계되었습니다.


*. 87 Page 아래에서 5번째 줄부터...

그렇다면 어떻게 해야 할까요? 프로세서 제조사에서는 이런 경우를 대비하여 무조건 분기 명령어(jmp)에 예외를 두었습니다.

즉, 무조건 분기 명령어의 오퍼랜드의 크기는 기본 64비트로 하여 전체 어드레스 범위에서 이동이 가능하도록 한 것입니다.

[그림 3-5]는 RIP 상대 어드레스를 사용하여 표현 가능한 어드레스 영역과 무조건 분기 명령어를 사용해서 접근할 수 있는 영역에 대해

나타낸 것입니다.


*. 88 Page [그림 3-5]

           [그림 3-5].png


*. 113 Page 아래에서 8번째 줄

그 위쪽에서 두 파일은 a.c와 b.c로부터 생성되는 것을 알 수 있습니다.


*. 141 Page 위에서 6번째 줄, 154 Page 아래서 5번째 줄, 2861 Page 위에서 7번째 줄, 2872 Page 맨 밑줄

xor byte [ HEADNUMBER ], 0x01 ; 헤드 번호를 0x01과 XOR 하여 토글(0->1, 1->0)


*. 142 Page, 아래서 3번째줄

x86 프로세서의 스택은 [그림 5-3]과 같이 데이터가 삽입될 때마다 스택의 상위(Top)를 나타내는 스택 포인터 레지스터(SP)가 낮은 어드레스(0x00에 가까운 어드레스)로 이동합니다.


*. 143 Page, 밑에서 10째줄

또한 스택은 넉넉한 것이 좋으므로 스택 포인터 레지스터(SP)와 베이스 포인터 레지스터(BP)를 0xFFFF로 설정하여, 스택 영역의 크기를 세그먼트의 최대 크기로 지정하겠습니다.


*.149 Page, 아래에서 13번째 줄

        ret 12                   ; 호출한 함수로 복귀한 후, 스택에 삽입된 파라미터 3개를 제거(3*4) "ret" "add esp, 12"와 같은 역할

        ^^^^^^

            ret 수행 후 스택 포인터(SP)를 12만큼 더함. 보호모드 코드이므로

            레지스터의 기본 크기 및 스택의 기본 크기가 32비트(4바이트)이기 때문에 4*3 = 12만큼 더함


*.151 Page, 위에서 15번째 줄

        ret 4                     ; 호출한 함수로 복위한 후, 스택에 삽입된 파라미터 1개를 제거(1*4) "ret" "add esp, 4"와 같은 역할

        ^^^^^^^

             ret 수행 후 스택 포인터(SP)를 4만큼 더함. 보호 모드 코드이므로

             레지스터의 기본 크기와 스택의 기본 크기가 32비트(4바이트)이기 때문에 4*1 = 4만큼 더함


*. 161 Page, 아래서 5번째줄 Code

; 512 - ( $ - $$ ) % 512 : 현재부터 어드레스 512까지


*. 213 Page, 가장 아래번째 줄

보호 모드 엔트리 포인트(EntryPoint.s) 코드에서 최초로 실행되는 C 코드입니다.


*. 251 Page, [그림 9-2] 페이지 디렉터리 엔트리 부분에서 PS1을 PS로 수정

[그림 9-2].PNG


*. 261 Page, 위에서 4번째 줄

kSetPageEntryData( &( pstPML4TEntry[ 0 ] ), 0x00, 0x101000, PAGE_FLAGS_DEFAULT, 0 );


*. 263 Page, [그림 9-5]의 XMSE를 SMXE로 수정


[그림 9-5].PNG


*. 266 Page, 소스 코드에서 위에서 10째줄

// 1 비트 P, RW, US, PWT, PCD, A, D, PS, G, 3 비트 Avail, 1 비트 PAT, 8 비트 Reserved,


*. 283 Page, [표 10-2] IA32_EFER 레지스터의 비트 구성에서 7번째 줄 LMA 항목

LMA 읽기 전용 -1로 설정되면 프로세서 모드가 IA-32e 모드(호환 모드 또는 64비트 모드)임을 나타내며, 0으로 설정되면 프로세서 모드가 기타 모드임을 나타냄


*.283 Page, 밑에서 10째줄

[표 10-2]에서 보는 것과 같이 IA-32e 모드 활성화 여부는 비트 8에 위치하는 LME 비트와 관계가 있고 이 비트를 1로 설정하면 IA-32e 모드를 활성화할 수 있습니다.


*.288 Page [예제 10-2] 위에서 1째줄

#ifndef __MODESWITCH_H__

#define __MODESWITCH_H__


*. 333 Page 아래쪽과 351 Page 가운데 부분, 482 Page 윗부분

BOOL kChangeKeyboardLED( BOOL bCapsLockOn, BOOL bNumLockOn, BOOL bScrollLockOn )

{

    int i, j;


    // 키보드에 LED 변경 커맨드 전송하고 커맨드가 처리될 때까지 대기

    for( i = 0 ; i < 0xFFFF ; i++ )

    {

        // 입력 버퍼(포트 0x60)가 비었으면 커맨드 전송 가능

        if( kIsInputBufferFull() == FALSE )

        {

            break;

        }

    }

...   생략 ...


*. 334 Page 위에서 3번째 줄

// 입력 버퍼(포트 0x60)로 LED 상태 편경 커맨드(0xED) 전송

kOutPortByte( 0x60, 0xED );

... 생략 ...


*. 341 Page 위에서 15번째 줄, 357 Page 아래에서 5번째 줄

BOOL kIsUseCombinedCode( BYTE bScanCode )


*. 341 Page, 위에서 18번째 줄, 357 Page, 아래에서 2번째 줄

BOOL bUseCombinedKey = FALSE;


*. 343 Page 아래쪽359 Page 가운데 부분

void UpdateCombinationKeyStatusAndLED( BYTE bScanCode )

{

    BOOL bDown;

    BYTE bDownScanCode;

    BOOL bLEDStatusChanged = FALSE;


    // 눌림 또는 떨어짐 상태처리, 최상위 비트(비트 7)가 1이면 키가 떨어졌음을 의미하고

    // 0이면 눌림을 의미함

    if( bScanCode & 0x80 )

    {

        bDown = FALSE;

        bDownScanCode = bScanCode & 0x7F;

    }

    else

    {

        bDown = TRUE;

        bDownScanCode = bScanCode;

    }


*. p.363 페이지 (02.Kernel64/Source/Keyboard.h)의 Down을 On으로 변경
// 키보드의 상태를 관리하는 자료구조
typedef struct kKeyboardManagerStruct
{
    // 조합 키 정보
    BOOL bShiftDown;
    BOOL bCapsLockOn;
    BOOL bNumLockOn;
    BOOL bScrollLockOn;
    
    // 확장 키를 처리하기 위한 정보
    BOOL bExtendedCodeIn;
    int iSkipCountForPause;
} KEYBOARDMANAGER;


*. 365 Page, 아래에서 12번째 줄

BOOL kIsUseCombinedCode( BYTE bScanCode );


*. 365 Page, 아래에서 17번째줄 변수 선언 부분 굵게 처리

void Main( void )

{

    char vcTemp[ 2 ] = {0, };

    BYTE bFlags;

    BYTE bTemp;

    int i = 0;


    ... 생략 ...



*. 383 Page의 [그림 12-7]에서 16~31 비트의 값(크기를 기준 주소로 수정)

그림 12-7.PNG


*. 386 Page, 위에서 21번째 줄 30번째 줄

#define GDT_FLAGS_LOWER_TSS ( GDT_FLAGS_LOWER_DPL0 | GDT_FLAGS_LOWER_P

#define GDT_FLAGS_UPPER_TSS ( GDT_FLAGS_UPPER_G )


*. 428 Page 아래에서 18번째 줄, 436 Page 아래에서 3번째 줄, 600 Page 아래에서 9번째 줄, 606 Page 위에서 7번째 줄

%macro KLOADCONTEXT 0      ; 파라미터를 전달받지 않는 KLOADCONTEXT 매크로 정의


*. 483 Page, 위에서 10번째 줄, 굵게 표시

// 키를 저장하는 큐와 버퍼 정의

static QUEUE gs_stKeyQueue;

static KEYDATA gs_vsKeyQueueBuffer[ KEY_MAXQUEUECOUNT ];


*. 492 Page의 위쪽

사실 14장은 앞 장과 비교해서 변한 것이 거의 없습니다.


*. 498 Page의 "GCC 헤더 파일에 정의된 가변 인자 매크로와 리스트"의 위쪽 문단

각 매크로와 데이터 타입이 어떻게 정의되어 있는지 코드를 직접 확인해 보겠습니다.


*. 609 Page, 첫 번째 줄  굵게 표시

{ "createtask", "Create Task", kCreateTestTask },


*. 620 Page의 [그림 18-3]

그림 18-3.png


*. 627 Page, "TCB 할당 및 해제 함수의 코드"에서 위로 2번째 줄

다음은 앞서 설명한 내용에 따라 구현한 TCB 할당 및 해제 함수의 코드입니다. ID를 처리하는 부분을 제외하고는 평이한 코드이므로

이해하는데 큰 어려움이 없을 것입니다. TCB 자료구조에 추가된 stLink 필드는 18.2.4절에서 살펴보겠습니다.


*. 660 Page의 위에서 4째줄

콘솔 화면의 주변을 돌면서 문자를 출력하는 태스크와 자신의 ID에 해당하는 위치에 바람개비를 출력하는 태스크를 작성했습니다.


*. 741 Page 위에서 7 번째 줄

태스크의 수를 제한하려면 임계 영역 진입 여부를 나타내는 플래그와 잠금 횟수를 나타내는 카운터, 그리고 임계 영역에 진입한 태스크의 ID만 있으면 처리할 수 있습니다.


*. 742 Page 아래에서 8번째 라인

만약 잠근 태스크가 해제를 요청하는 것이고 잠긴 횟수가 2 이상이면 중복으로 잠긴 것이므로 횟수만 1 감소시키고 실제로 해제하는 작업은 수행하지 않습니다.


*. 751 Page 그림 [20-4]

[그림 20-4].PNG


*. 769 Page 맨 윗 라인

콘솔 셸 파일에는 태스크를 생성하고 종료하는 태스트 때문에 killtask 커맨드의 기능이 수정되고 동기화 기능을 테스트하는 testmutex 커맨드가 추가되었습니다.


*. 791 Page의 밑에서 셋째줄

하지만 멀티코어 프로세서 환경일 경우 자식 스레드는 다른 코어에서 실행될 수 있으며, 이런 경우 태스크를 즉시 대기 리스트로 옮길 수 없습니다.


*. 815 Page의 밑에서 8째줄

[그림 21-4]의 위를 보면 콘솔 셸(태스크 ID: 0x100000000)과 유휴 태스크(태스크 ID: 0x200000001)의 태스크 플래그를 보면 최상위 바이트가 각기 0x60과 0x58로 시작하는 것을 볼 수 있습니다.


*. 825 Page [그림 22-2]

[그림 22-2].PNG


* 843 Page, 밑에서 10 째줄

또 FPU 예외 처리가 끝나면 다시 0으로 설정해서 같은 태스크가 FPU를 사용할 때 예외가 발생하지 않도록 해야 합니다.


* 876 Page, 밑에서 두번째 줄

1KB 블록 2개를 생성한 후 1KB 블록 하나를 할당해주면 남은 블록은 [그림 23-3]과 같이 1KB 블록 1개, 2KB 블록 1개, 4KB 블록 1개가 됩니다.


* 879 Page, 위에서 3번째 줄 그림 제목

[그림 23-6] 블록 크기에 맞지 않는 메모리를 할당받았을 때 할당된 메모리의 구조(4.3MB 할당)


* 933 Page 위에서 7 째줄

하지만, LBA 어드레스 모드일 때는 레지스터가 LBA 어드레스로 통합되어 섹터 번호 레지스터는 LBA 어드레스의 0비트~7비트, 실린더 LSB 레지스터는 8비트~15비트, 실린더 MSB 레지스터는 16비트~23비트, 드라이브/헤드 레지스터의 헤드 필드는 24비트~27비트를 저장합니다.


* 1049 Page, 위에서 6번째 줄 자료구조에서 아래의 굵게된 부분 추가

typedef struct kFileHandleStruct
{
    // 파일이 존재하는 디렉터리 엔트리의 오프셋
    int iDirectoryEntryOffset;
    // 파일 크기
    DWORD dwFileSize;
    // 파일의 시작 클러스터 인덱스
    DWORD dwStartClusterIndex;
    // 현재 I/O가 수행중인 클러스터의 인덱스
    DWORD dwCurrentClusterIndex;
    // 현재 클러스터의 바로 이전 클러스터의 인덱스
    DWORD dwPreviousClusterIndex;
    // 파일 포인터의 현재 위치
    DWORD dwCurrentOffset;
} FILEHANDLE;


* 1120 Page 위에서 6 째줄

// 파일 이름 삽입

kMemCpy( vcBuffer, pstentry->d_name, kStrLen( pstEntry->d_name ) );


* 1161 Page, [그림 27-5]의 가장 마지막 부분

[그림 27-5].PNG


*. 1174 Page, 위에서 11 번째 줄

iRealReadCount = MIN( gs_stRDDManager.dwTotalSectorCount -

dwLBA, iSectorCount );


*. 1174 Page, 밑에서 9 번째 줄

iRealWriteCount = MIN( gs_stRDDManager.dwTotalSectorCount -

dwLBA, iSectorCount );


*. 1186 Page, 밑에서 16 번째 줄

iRealReadCount = MIN( gs_stRDDManager.dwTotalSectorCount -

dwLBA, iSectorCount );

*. 1186 Page, 밑에서 2 번째 줄

iRealWriteCount = MIN( gs_stRDDManager.dwTotalSectorCount -

dwLBA, iSectorCount );


*. 1239 Page, 위에서 10째줄과 13째줄

// LSB 제수 래치 레지스터(포트 0x3F8)에 제수의 하위 8비트를 전송

// MSB 제수 래치 레지스터(포트 0x3F9)에 제수의 상위 8비트를 전송


*. 1246 Page, 위에서 2째줄과 5째줄

// LSB 제수 래치 레지스터(포트 0x3F8)에 제수의 하위 8비트를 전송

// MSB 제수 래치 레지스터(포트 0x3F9)에 제수의 상위 8비트를 전송


*. 1251 Page, [예제 28-3]의 소스 부분
#include "FileSystem.h" <-- Normal 글씨체로 수정
#include "SerialPort.h" <-- Bold 글씨체로 수정

*. 1280 Page, 위에서 5번째줄, 맨 앞의 "-"를 "*"로 대체하고 위의 *와 같은 레벨로 들여쓰기 수정
* BIOS의 롬 영역 중에서 0x0F0000~ 0x0FFFFF 범위 내에 존재

* 1291 Page, [그림 29-13]에서 전달할 로컬 APIC INTIN을 전달할 로컬 APIC LINTIN으로 수정, 0x000x04로 수정
[그림 29-13].PNG  
* 1399 Page, [그림 31-1]에서 오른쪽 아래에 있는 "ㅁ: 비활성화 된 부분"의 사각형을 회색으로 변경
[그림 31-1].PNG

==== 2권의 오탈자 내용 ====

*. 1528 Page, 아래에서 11번째 줄

태스크 풀을 관리하는 함수를 수정하는 방법은 33.2.2절에서 살펴보므로, 이번 절에서는 수정된 태스크 풀 자료구조만 살펴보고 넘어가겠습니다.


*. 1539 Page, 위에서 7번째 줄, 1580 Page, 아래에서 12번째 줄

// 나머지 코어에서 현재 태스크와 같은 레벨을 검사


*. 1612 Page, 아래에서 2째줄

하지만, 7부가 지나면 곧 다시 돌아오니 너무 아쉬워 할 필요는 없습니다.


*. 1655 Page, 위에서 5번째 줄

브레슨햄 직선 알고리즘을 사용한 선 그리기 함수의 코드 - 최적화 후


*. 1672 Page, 35.2.1절에서 아래로 8번째줄

하지만, 문자 정보가 비트맵으로 들어 있으므로 확대하거나 축소했을 때 문자가 깨끗하게 표시되지 않고, 굵게하거나 기울일려면 해당 스타일의 비트맵을 모두 생성해야하는 단점이 있습니다.


*.1764 Page, 아래에서 10번째줄, kDrawMouse -> kDrawCursor로 수정

kDrawCursor( &stScreenArea, pstVideoMemory, iX, iY );


*. 1882 Page, 위에서 7번째 줄

이 절에서 작성할 윈도우 매니저는 더 이상 테스트 코드가 아닌 실제 코드로서, 키보드와 마우스 데이터를 이용해 윈도우로 이벤트를 전달하는 기능과 업데이트를 수행하는 기능을 구현합니다.


*. 1946 Page, 위에서 4번째 줄

//----------------------------------------------------------------------------------------------

// 제목 표시줄 그리기

//----------------------------------------------------------------------------------------------

// 제목 표시줄을 채움

        if( bSelectedTitle == TRUE )
        {
            stTitleBarColor = WINDOW_COLOR_TITLEBARACTIVEBACKGROUND;
        }
        else
        {
                stTitleBarColor = WINDOW_COLOR_TITLEBARINACTIVEBACKGROUND;

        }


kInternalDrawRect( &stArea, ... 생략 ...)


*. 1951 Page, 위에서 6번째 줄

39장의 내용을 MINT64 OS에 추가한 뒤 make를 입력하면 Disk.img 파일이 생성됩니다.


*. 1994 Page, 위에서 6번째 줄 제목

41.1.1 Z 순서의 가장 아래에서 위로 그리는 알고리즘의 문제점


*. 1998 Page, 위에서 3번째 줄

윈도우 1은 자신의 영역에 포함된 픽셀 오프셋 5~7 부분을 비디오 메모리로 전송한 뒤 해당 영역의 비트맵을 모두 0으로 표시합니다.


*. 2027 Page, 아래에서 3번째 줄

특히 마우스 데이터를 처리하는 부분과 화면 업데이트 요청을 처리하는 부분은 조금만 수정하면 화면 업데이트 횟수를 많이 줄일 수 있기 때문에 속도에 목마른 우리에겐 아주 중요한 포인트입니다.


*. 2150 Page, 위에서 첫번째 줄

43장에서는 프로세서의 정보와 메모리 정보를 표시하는 시스템 모니터 태스크를 구현해 봤습니다. 


*. 2150 Page, 위에서 6번째 줄

44장에서는 다시 고전으로 돌아가 역사의 뒤안길로 사라졌던 콘솔 셸을 GUI 버전으로 부활시킬 것입니다.


*. 2169 Page, 위에서 7번째 줄 도움말

while( kGetKeyFromGUIKeyQueue( &stData) == FALSE)

        |---------- 그래픽 모드용 키 큐에서 데이터를 꺼내는 함수


*. 2182 Page, "44.3.2 콘솔 파일 수정"에서 아래 첫번째 줄

콘솔 파일은 그래픽 모드 지원을 위해 입출력 함수의 코드를 일부 수정하고, 그래픽 모드용 화면 버퍼와 키 큐를 추가했습니다.


*. 2309 Page 전체를 아래 내용으로 교체2309 페이지의 내용.PNG


*. 2400 Page, 아래에서 4번째 줄

MINT64 OS에는 아직까지 MSR에 읽고 쓰는 함수가 없으므로 MSR에 관련된 어셈블리어 함수부터 만들겠습니다.


*. 2546 Page, "[표 50-2] e_entry 필드의 세부 구성과 의미"를 "[표 50-2] e_ident[16] 필드의 세부 구성과 의미"로 변경


*. 2546 Page, 아래에서 7번째 줄

가장 먼저 할 일은 e_ident 필드의 상위 4바이트가 매직 넘버이므로, 파일의 첫 4바이트가 매직 넘버와 일치하는지를 검사하여 올바른 ELF64 파일인지 확인하는 것입니다.


*. 2548 Page, 아래에서 5번째 줄

현재 섹션의 타입에 따라 필드의 의미가 달라지며 자세한 내용은 [표50-6]을 참조


*. 2616 Page, 위에서 첫번째 줄

[그림 51-2]는 이러한 처리 과정을 나타낸 그림입니다.


*. 2625 Page 위에서 14번째 줄,  2641 Page 아래에서 8번째줄, 2785 Page 밑에서 7번째줄, 

// 현재 라인에서 남은 문자 수와 한 라인에 출력할 수 있는 문자 수 를 비교하여 작은 것을 선택


*. 2692 Page, 위에서 12번째 줄

g_stGameInfo.iBlockX = -1;

g_stGameInfo.iBlockY = -1;


*. 2693 Page, 위에서 10번째 줄

게임판의 블록이 존재하는지 여부는 게임 자료구조의 vvbBoard 필드에 저장되어 있습니다.


*. 2694 Page, 위에서 5번째 줄

게임 자료구조에는 현재 게임판의 정보를 저장하는 vvbBoard 필드가 있으므로 움직이는 블록의 현재 좌표를 그대로 게임판에 저장하면 됩니다.


*. 2720 Page, 아래에서 13번째 줄

g_stGameInfo.iBlockX = -1;

g_stGameInfo.iBlockY = -1;


*. 2766 Page, 위에서 4번째 줄

텍스트 뷰어에 한글 출력 기능을 추가하려면 한글인지를 판단해 2바이트씩 출력하는 코드만 추가하면 되기 때문입니다.


*. 2846 Page, 위에서 1번째 줄

만든 첫 번째 한글 입력 모듈은 오토마타를 사용했습니다.


*. TODO...


OS 제작을 시도해본 분들은 아시겠지만...

OS를 개발하려면 PC 하드웨어부터 OS 관련 지식까지 많은 정보가 필요합니다. ^^;;;


저 역시 책도 많이 보고 웹 사이트도 많이 찾아다녔는데요,

OS를 개발할 때 도움이 되었던 책과 사이트를 정리해둘 필요가 있는 것 같아서 한자 남겨둡니다.


OS를 개발하시는 다른 분들에게 도움이 되었으면 좋겠네요.



책 목록



사이트 목록


그럼 즐거운 OS 프로그래밍하세요 ;)
제가 운영하는 OS 개발 사이트(http://www.mint64os.pe.kr)에 어떤 분께서 Object File을 coff 형식으로 생성할 수 있는 방법이 있느냐고 물어보시더라구요.

사실 제가 gcc의 각종 옵션에 익숙치 않아서....(매번 기본 옵션으로만 쓰다보니.. ^^;;;;) 구글 신님께 여쭤봤는데... 디버그 정보와 함께 오브젝트 파일을 coff 파일로 생성하는 옵션을 발견했습니다.

gcc -gcoff main.c

뭐, 당연한 거지만 gcc가 coff 파일을 지원해야 가능합니다. ^^;;;
보다 자세한 내용은 http://www.hanb.co.kr/network/view.html?bi_id=1382 내용을 참고하세요 ;)

ps) 혹시 디버그 정보 없이 coff 파일 형식으로 오브젝트 파일을 생성하는 옵션 아시는분 있으세요? ㅠㅠ
간만에 재미있는 질문을 받았습니다. 보안 관련 기술 쪽에 ASLR(Address Space Layout Randomization) 기법이 있는데, 이를 어떻게 MNIT64 OS에 적용할 수 있을까를 묻는 내용이었습니다.

저도 보안 쪽은 좀 생소하여 ASLR을 잠시 찾아봤는데, 응용프로그램을 로딩할 때 항상 정해진 어드레스가 아닌 매번 다른 어드레스로 로딩하여 크래커로부터 보호하는 기법이더군요. 사실 어드레스를 매번 변경한다고 해도 찾아서 뚫을려고 마음 먹으면 어쩔 수 없기 때문에 완벽한 방법은 아닙니다만, 나름대로 괜찮은 아이디어라는 생각이 들었습니다.

그래서, 어떻게 하면 MINT64 OS에도 이를 적용할 수 있을까 생각을 해봤는데 의외로 답은 간단했습니다. ^^;;; MINT64 OS는 응용프로그램을 메모리에 로딩할 때 동적 메모리 관리 모듈에서 먼저 메모리를 할당 받은 뒤에 그 메모리에 올려서 응용프로그램을 동작시킵니다. 따라서, 많은 응용프로그램이 실행되고 종료되는 상황이라면, 저절로 어드레스가 랜덤하게 할당되는 것이지요.

물론, 응용프로그램 하나만 계속 생성되었다가 종료되는 상황이라면 여전히 문제가 있습니다. 이 문제도 사실은 간단히 해결할 수 있습니다. 동적 메모리 관리 모듈에서 메모리를 할당받을 때, 메모리 블록을 항상 인덱스 0번부터 검색할 것이 아니라 랜덤한 인덱스부터 검색하도록 하면 됩니다. ^^;;; 나중에 여유가되면 한번 기능을 추가해봐야겠군요. ^^)/~

그럼 즐거운 밤 되세요 ;)
"64비트 멀티코어 OS 원리와 구조" 책의 첫 서평이 올라왔습니다. ㅠㅠ 
(다행히도 서평이 좋네요. ㅠㅠ 감사드립니다. ㅠㅠ)

<64비트 멀티코어 OS 원리와 구조 - 출처 yes24.com>

서평을 써주신  somesing님, charsyam님, niceness님, ageman1님 감사드립니다.  ㅠㅠ

앞으로 더욱 열심히하는 kkamagui가 되겠습니다. (__)

 
ps)  다들 즐거운 추석 연휴 보내세요 ;) 
제 책 "64비트 멀티코어 OS 원리와 구조"가 나온지도 벌써 3달이 다되어 가는군요. ^^ 부족한 책이지만 많은 분들이 구매해주신 덕분에 순조롭게 진행되고 있는 것 같습니다(물론 손익 분기점까지는 아직 멀었지만 말입니다 ㅠㅠ).

<64비트 멀티코어 OS 원리와 구조>



책의 내용 중에서 가장 많이 질문 받는 내용 중에 두 가지가 초반에 환경 구축할 때 크로스 컴파일러를 만드는 과정과 QEMU 0.10.4 버전을 어디서 구하는가 하는 것입니다. ^^;;; 그래서 얼마전에 Cygwin 최신 버전으로 64비트 크로스 컴파일러를 만드는 글(http://kkamagui.tistory.com/762)을 올렸습니다.

이번에는 QEMU 0.10.4 버전을 어디서 다운로드할 수 있는가 하는 것인데... 가장 간단한 방법은 여기에 올려드린 첨부 파일을 다운로드 하는 것입니다. ^^;;; QEMU 0.10.4 버전은 제가 MINT64 OS를 만들기 시작할 때 쓰던 버전이라... 지금은 구하기가 힘들기 때문이지요.


QEMU에 최신 OS를 설치하려면 최신 버전을 다운로드하시는게 아무래도 이득이겠지만, MINT64 OS를 실습하는데는 0.10.4 버전을 사용하시는 것이 정신의 평화와 안정을 유지하는데 도움이 되실겁니다. ^^;;;;

그럼 즐거운 OS 프로그래밍하세요 ;)

제 책 "64비트 멀티코어 OS 원리와 구조"가 나온지도 벌써 2달 이상 지났습니다. ^^;;; 책이 출간됨과 동시에 가장 많이 질문을 받는 것이 OS 제작을 위한 환경 구축 방법인데요... 얼마전부터 환경 구축에 어려움을 겪는 분들이 많아지셔서 Cygwin 최신 버전을 다운 받아서 빌드를 해봤습니다. ^^;;; 실제로 빌드를 진행하다보니 한가지 변경점이 보이더군요. ㅠㅠ 그래서 그 내용을 여기다가 정리해놓습니다. ㅠㅠ


<출처 - yes24.com>


Cross Compiler를 만들려면 먼저 Cygwin 사이트(http://www.cygwin.com/install.html)로 이동해서 Cygwin Installer를 다운로드 받습니다. Setup.exe 파일을 다운하면 됩니다. ^^;;;

다운로드 후 파일을 실행하면 어떻게 설치할 것이고 패키지 소스는 어디에서 다운 받을 것인지 선택하는 부분이 나오는데,

책에 나와있는 카이스트 사이트(ftp://ftp.kaist.ac.kr)가 더이상 유효하지 않으니 다른 사이트를 선택해야합니다.

우리나라 근처에 있는게 아무래도... 속도도 빠르니 가까운 일본 주소인 ftp://ftp.jaist.ac.jp를 선택하면 됩니다. ^^;;;

패키지 소스를 다운 받을 주소를 선택하고 난 뒤에는 "Select Package" 화면이 나올 때까지 적당히 Next를 선택해주면 됩니다. ^^;;;

"Select Package" 화면이 나오면 아래의 항목들을 설치해줍니다. 책을 쓸 때는 C++ 관련 항목이 없어도 정상적으로 빌드가 되었지만, 지금은 C++ 컴파일러를 설치하지 않으니 configure 중에 오류가 발생하더군요. ㅠㅠ 가장 아래쪽에 있는 libmpc-devel이 변경된 부분입니다.

+Devel  
    + binutils - 2.21.53-2 버전, 소스 및 바이너리 모두 설치  
    + bison - 2.4-2-1 버전, 바이너리만 설치  
    + flex - 2.5.35-1 버전, 바이너리만 설치  
    + gcc4-core - 4.3.4-3 버전, 소스 및 바이너리 모두 설치  
    + gcc4-g++ - 4.3.4-4 버전, 바이너리만 설치  
    + libiconv - 1.14-1 버전, 바이너리만 설치  
    + libtool - 2.2.7a-15 버전, 바이너리만 설치  
    + make - 3.81-2 버전, 바이너리만 설치  
    + patchutils - 0.3.1-1 버전, 바이너리만 설치  
+Libs  
    + libgmp-devel - 4.3.2-1 버전, 바이너리만 설치  
    + libmpfr-devel -3.0.1-1 버전, 바이너리만 설치
    + libmpc-devel - 0.8-1 버전, 바이너리만 설치

위의 항목이 설치되고 나면 binutils부터 차례대로 빌드를 시작하면 됩니다.


binutils를 빌드하려면 /usr/src 디렉터리 아래에 있는 binutils 디렉터리로 이동해서 다음과 같은 순서로 커맨드를 입력하면 빌드와 설치가 끝납니다.

// 디렉터리 이동 후 아래 작업 수행

$> cd /usr/src/binutils-2.21.53-2  <== 디렉터리 이름은 binutils 버전에 따라 다를 수 있음
$> export TARGET=x86\_64-pc-linux  
$> export PREFIX=/usr/cross  
$> ./configure --target=$TARGET --prefix=$PREFIX --enable-64-bit-bfd --disable-shared --disable-nls  
$> make configure-host  
$> make LDFLAGS="-all-static"  
$> make install  

// 아래는 테스트를 위한 부분 커맨드를 실행했을 때 x86\_64 관련 항목이 보이면 제대로 된 것입니다. ^^;;;;  
$> /usr/cross/bin/x86\_64-pc-linux-ld --help | grep "supported "  
/usr/cross/bin/x86\_64-pc-linux-ld: supported targets: elf64-x86-64 elf32-i386 a.  
out-i386-linux efi-app-ia32 efi-bsdrv-ia32 efi-rtdrv-ia32 efi-app-x86\_64 efi-bsd  
rv-x86\_64 efi-rtdrv-x86\_64 elf64-little elf64-big elf32-little elf32-big srec sy  
mbolsrec tekhex binary ihex  
/usr/cross/bin/x86\_64-pc-linux-ld: supported emulations: elf\_x86\_64 elf\_i386 i38  
6linux  

binutils 빌드 및 설치가 끝났다면 gcc를 cross compile할 차례입니다. gcc는 아래와 같은 순서로 입력하면 빌드와 설치가 완료됩니다. ^^;;;

// 압축 해제, 파일명은 cygwin에서 다운받은 GCC 버전에 따라서 다를 수 있으니 실제 디렉터리를 확인하여 입력해야 합니다. ^^;;; 아래 패치 파일도 모두 마찬가지 입니다.  
$> cd /usr/src

$> tar -xvf gcc-4.3.4.tar.bz2  
$> patch -p1 < gcc4-4.3.4-3.src.patch  
$> patch -p1 < gcc4-4.3.4-3.cygwin.patch  
$> export TARGET=x86\_64-pc-linux  
$> export PREFIX=/usr/cross  
$> export PATH=$PREFIX/bin:$PATH

// 압축 해제가 끝났으니 소스가 있는 디렉터리 이동 후 나머지 작업 수행  
$>cd /usr/src/gcc-4.3.4 <== 디렉터리 이름은 GCC 버전에 따라 다를 수 있음

$> ./configure --target=$TARGET --prefix=$PREFIX --disable-nls --enable-languages=c --without-headers --disable-shared --enable-multilib  
$> make configure-host  
$> cp /lib/gcc/i686-pc-cygwin/4.3.4/libgcc\_s.dll.a /lib/gcc/i686-pc-cygwin/4.3.4/libgcc\_s.a  
$> cp /lib/libmpfr.dll.a /lib/libmpfr.a  
$> cp /lib/libgmp.dll.a /lib/libgmp.a  
$> make all-gcc  
$> make install-gcc  

// 테스트용입니다. 아래와 같이 입력했을 때 m64가 보이면 정상적으로 설치된 것입니다.  
$> /usr/cross/bin/x86\_64-pc-linux-gcc -dumpspecs | grep -A1 multilib\_options  
    *multilib\_options:  
    m64/m32  

자, 이제 64비트 바이너리를 생성할 수 있는 gcc가 생성되었으니,
즐거운 OS 프로그래밍하세요 ;)

Intel과 AMD에서는 IA-32e Mode, 즉 Long Mode로 전환할 때 Protected Mode(보호 모드)를 거치도록 되어있습니다. 아래 그림은 Intel Manual에서 가져온 Operating Modes Transition Diagram입니다. ^^
 


위의 그림에서 보면 Real Mode(이하 리얼 모드)에서 IA-32e Mode or Long Mode(이하 IA-32e Mode)로 전환할 때 바로 가는 화살표가 없고 Protected Mode(이하 보호 모드)를 거쳐서 IA-32e 모드로 가게 되어 있습니다. 

그.런.데 익명의 제보로 바로 가는 것이 가능하다는 것을 알게 되었습니다. 그래서 구글을 검색해보니 실제로 2005년도에 바로 갈수있는 방법을 찾아내신 분이 있더군요. ^^)-b

OSDev에서 발견했는데, http://forum.osdev.org/viewtopic.php?t=11093에서 그 내용을 보실 수 있습니다. 방법은 비교적 간단합니다. 보호 모드에서 하는 작업을 그대로 리얼 모드에서 한 뒤에 IA-32e 모드로 jump를 하는 것입니다. 아래는 OSDev 링크의 방법을 그대로 옮겨 놓은 것입니다. ^^;;;;

1) Build paging structures (PML4, PDPT, PD and PTs)

2) Enable PAE in CR4

3) Set CR3 so it points to the PML4

4) Enable long mode in the EFER MSR

5) Enable paging and protected mode at the same time (activate long mode)

6) Load a GDT

7) Do a "far jump" to some 64 bit code


때마침 여유가 좀 생겨서 실제로 가능한 예제 코드를 한번 만들어 봤습니다. 기본 뼈대로는 MINT64 OS의 부트 로더 코드와 보호 모드 코드를 사용했고, IA-32e 모드로 전환 가능한지 확인하는 것이 목적이었기 때문에 최소한의 코드만 작성했습니다. 

아래는 실제로 동작하는 화면을 캡쳐한 것입니다. QEMU를 사용해서 확인했습니다. ^^


<실제로 QEMU에서 구동한 화면>


리얼 모드에서 보호 모드를 거치지 않고 IA-32e 모드로 전환하는 방법은 Manual에 의해 보장된 방법이 아니므로, 이런 것도 가능하다는 팁 정도로 보시면 될 것 같네요. ^^;;;

그럼 아래에 실제 코드 나갑니다. ^^
(첨부 파일에 소스 코드와 컴파일된 바이너리도 같이 넣어두었으니 참고하시기 바랍니다)


그럼 좋은 하루 되세요 ;)
 

첨부 파일 :



Intel Architecture Manual을 보면 아래와 같은 Operating Mode Diagram이 있습니다. 각 Mode에 따라서 어떤 Mode로 전환할 수 있으며 이때 필요한 Register 설정은 무엇인지를 나타내는 그림이지요. ;)
 

이 그림을 자세히보면 리얼 모드에서는  IA-32e 모드로 전환할 수 있는 방법이 없습니다. 전환 하려면 보호 모드를 거쳐서 전환해야 하지요. 실제로 Intel Architecture Manual Volume 3A의 9.8.5를 봐도 "The operating system must be in protected mode with paging enabled before attempting to initialize IA-32e mode"로 보호 모드를 거쳐야 한다고 나와있습니다.

그런데~!!! 지나다가님의 제보로 리얼 모드에서 바로 IA-32e 모드로 전환할 수 있는 방법을 알게되었습니다. 물론 제조사 Manual에 나와있지 않은 내용이라, 팁 정도로 봐야겠지만 상당히 흥미로운 내용이라 한번 올려봅니다.

원본의 내용은 http://forum.osdev.org/viewtopic.php?f=1&t=11093&start=0 에서 보실 수 있으며, IA-32e 모드로 변경하려면 실제로 아래와 같이 7가지의 단계를 거쳐야합니다.

    1) Build paging structures (PML4, PDPT, PD and PTs)
    2) Enable PAE in CR4
    3) Set CR3 so it points to the PML4
    4) Enable long mode in the EFER MSR
    5) Enable paging and protected mode at the same time (activate long mode)
    6) Load a GDT
    7) Do a "far jump" to some 64 bit code 
 

위의 단계 중에서 6번은 리얼 모드에서 보호 모드로 전환하는 단계에서 하고, 나머지 6 가지는 보호 모드에서 수행하여 IA-32e 모드의 64비트 서브 모드로 전환합니다. But~!!! 리얼 모드에서 위의 7단계를 모두 수행해서 한번에 IA-32e 모드로 전환하는게 바로 Magic의 정체입니다. @0@)-b

과연 동작할까 싶지만, 실제로 동작하더군요. ㅎㄷㄷ 실제로 동작하는 코드는 http://bcos.hopto.org/temp/init.html에서 보실 수 있으며, 지나가다님이 작성하신 http://jsandroidapp.cafe24.com/xe/1936 에서도 보실 수 있습니다. ^^

정말 깜짝 놀랄 일이군요. ㅎㅎ 어떻게 이런 방법을 찾았는지... ㅎㅎ 세상에는 별별 사람들이 다 있는 것 같습니다. >ㅁ<)-b

안녕하세요, 이벤트가 끝나자마다 다시 이벤트 소식으로 찾아뵙는군요. ^^
 
<진행 중인 이벤트~!! ㅠㅠ 언능 신청한다면 8월 1일까지는 어떻게든 되지 않을까요? ㅠㅠ>


지금 Yes24에서 리뷰어 신청을 하시면 1권을 무료로 증정해주는 리뷰어 이벤트를 진행하고 있습니다. ^^)-b

사실 신청자가 그리 많지 않다는 소식(?)을 들어서,
그동안 책값이 부담되셨던 분들께 도움을 드리고자 이렇게 급하게 올리게되었습니다 .ㅠㅠ
(사실 바로 올렸어야 하는데 주말에 일이 생겨 정신없이 보내다보니... 이제야 올리게됬네요. ㅠㅠ)

월요일(8월 1일)이 당첨자 발표이긴 한데, 
발표전까지만 신청하시면 어떻게든 되지 않을까 하는 희망을 갖고 있습니다.

관심있으신 분들은 지금 바로 신청하시면 될 것 같습니다.
당첨되고 나면 2권만 사서 보시면 되니 괜찮은 이벤트가 아닌가 싶네요. ^^)-b


그럼 언능 신청하시고 책 받으세요 >ㅁ<)-b
회사일 하랴, 질문에 답변 달랴, 오탈자 제보 수정하랴 요즘 정신없이 하루하루를 보내고 있습니다. ^^ 그래도 MINT64 OS의 커뮤니티(http://www.mint64os.pe.kr)에 많은 분들이 관심을 가져 주셔서 몸은 좀 힘들지만 즐거운 나날을 보내고 있습니다. ^^
(감사합니다~ 다 여러분들 덕이에요 ;) )

<MINT64 OS 랍니다. ^^>

질문에 답변을 하다가 문득 든 생각인데, 압축 파일 형태로 제공되는 MINT64 OS가... 마치 생명력을 잃어버린 것 같더군요. 뭐랄까요... 질문과 답변을 통해 커뮤니티는 앞으로 나아가는데 MINT64 OS의 소스는 계속 그 자리에 멈춘듯한 느낌이랄까...

그래서 고민 끝에 MINT64 OS의 최신 코드를 웹상에 공개하기로 했습니다. 지금 생각하는 곳은 소스포지(http://sourceforge.net/)나 구글 코드(http://code.google.com/intl/ko-KR/)인데, 어느 곳이 딱 맞을지는 잘 모르겠네요. ^^;;;

둘 중에 어느 한 곳을 골라서 이번 주에 프로젝트를 개설할까 하는데... 혹시 괜찮은 곳 있으시면 추천 부탁드립니다. ^^
그럼 즐거운 하루 되세요 ;) 

<64비트 멀티코어 OS 원리와 구조>


MINT64 OS의 챕터별 예제 소스는 한빛 미디어 웹사이트(www.hanb.co.kr/exam/1836)로 이동하면 받을 수 있습니다.
해당 링크로 이동하면 source_code.zip 파일cygwin_devenv.zip 파일을 볼 수 있습니다.

source_code.zip 파일: 
   각 장별 소스 코드가 담겨 있으며, MINT64 OS를 실행할 수 있는 환경과 각종 데이터 시트를 모아 놓았습니다.
   (책에서 언급한 부록 CD의 내용은 대부분 여기에 들어있습니다. ^^)

cygwin_devenv.zip 파일:
   제가 개발하던 당시 Cygwin 디렉터리를 압축한 것입니다. 
   일반적인 경우라면 최신 버전의 Cygwin을 내려받아서 환경을 구성합니다. 
   하지만 크로스 컴파일러를 만드는데 계속 실패하거나 알 수 없는 오류가 발생한다면 
   cygwin_devenv.zip 파일의 압축을 풀어 C:\cygwin 디렉터리에 덮어써서 문제를 해결할 수 있습니다. ^^


그럼 즐거운 OS 프로그래밍하시고, 출간 기념 이벤트(http://kkamagui.tistory.com/748)도 많이 참여해주세요 ^^
오늘 드디어 그렇게 기다리던 "64비트 멀티코어 OS 원리와 구조"가 제 손에 들어왔습니다. ㅠㅠ 지금까지 A3 용지에 출력된 것만 보다가, 이렇게 책으로 제본된 것을 보니 왠지 울컥(?) 하더군요. ㅠㅠ 그래서 뭔가 저질러야겠다는 생각이 들어서 출간 기념 이벤트를 하기로 했습니다. ^^)/~ 

<64비트 멀티코어 OS 원리와 구조의 실물 사진>


이벤트 응모 방법은 아주 간단합니다. 오늘부터 배송이 시작되었다고 하니 빠르면 내일부터 책을 받으실 겁니다. 그러면...

책을 받았다는 인증 사진을 찍어 저에게 메일(mint64os@gmail.com)로 보내시거나, 블로그에 올리시고 제가 볼 수 있게 이 글(http://jsandroidapp.cafe24.com/xe/216)의 댓글로 링크를 남겨주시면 됩니다. 

그러면 제가 정확히 7월 31일에 랜덤(?)으로 추첨하여 공정하게 1분을 뽑아 소정의 선물을 드릴까합니다. ^^)/~
(7월 31일 자정에 추첨하겠습니다. ^^;;;)

선물은 책을 보시는데 꼭 필요한 것으로 준비할 예정이니 많은 응모 부탁드립니다. ^^

ps) 실제로 보니 책이 정말 잘 나왔네요. 분량이 많아서 내심 걱정했는데, 내용도 알맞게 잘 편집된 것 같습니다. 아주 마음에 쏙~!! 드는군요. :) 

 

+ Recent posts