지금 여러분이 핀토스의 내부동작을 가진 어떤 유사한 것을 가지고 있을지 모르겠습니다. 여러분의 OS는 적당한 동기화를 하는 여러 스레드의 실행을 정확하게 다룰 수 있고, 한 번에 여러 사용자 프로그램들을 로드할 수 있습니다. 그러나 실행 할수 있는 프로그램의 갯수와 크기는 머신의 주기억메모리 크기에 한정되어 있습니다. 이러한 한정에 대해, 여러분은 그러한 제한을 사라지게 할것 입니다. 마지막 것 위에 이 임무를 작성하게 될 것입니다. Project2에서의 테스트 프로그램은 Project 3에도 또한 작동하게 될 것입니다.
여러분이 Project 3의 작업을 시작하기 전에 Project 2에서의 서브 미션에 의한 어떤 버그들을 고쳐야 하는것에 주의해야 할지 모릅니다. 왜냐하면 그 버그들은 project3에서의 몇가지 문제들의 원인이 되는 것들과 거의 유사할 것이기 때문입니다. 여러분은 이전에 할당해서 했던 것들과 같은 방법으로 핀토스 디스크들과 파일시스템들을 계속 다루게 될 것입니다.
4.1 Background
4.1.1 Source Files
여러분은 이 프로젝트를 vm 디렉토리 안에서 작업하게 될것입니다.vm디렉토리는 Makefile만 포함하고 있습니다.userprog에서 유일한 변화는 이 새로운 Makefile을 -DVM으로 새롭게 바꾸는 것입니다여러분이 작성한 모든 코드는 새로운 파일안에 있거나 더 먼저 번의 프로젝트들에 소게된 파일안에 있게 될 것입니다,
여러분은 아마도 초반에는 단지 적은 파일들만을 마주치게 될것입니다.물리 디스크로의 접근을 제공하고 조금은 두려운 IDE 인터페이스를 따로 추출합니다.여러분은 스왑 디스크 접근의 이 인터페이스를 사용하게 될것입니다.
4.1.2 Memory Terminology
신중한 정의들은 가상메모리에 대한논의가 혼란스럽게 되지 않게 하기 위해서 필요합니다.그래서 기억하고 저장하기 위해서 어떠한 용어를 표현하는 것에서 부터 시작합니다.이러한 조건의 몇 개인가는, 프로젝트 2로부터 유사하게 되게 해야할 지 모릅니다.4.1.2.1 Pages
페이지(때때로 가상페이지로 불리는)는, 길이로 4096바이트(페이지 크기)인 연속된 지역입니다. 페이지는 페이지를 일렬로 늘어놓아야 합니다.즉, 페이지 크기에 의해서 균일하게 나누어지는 가상주소로 시작해야 합니다. 이와 같이, 32비트의 가상 주소는 20-bit의 페이지 번호와 12bit의 페이지 offset으로 로 나누어질 수 있습니다.
각 프로세스는 독립된 사용자(가상)페이지들의 집합을 가지고 있으며, 그 페이지 들은 가상주소 PHYS_BASE(일반적으로 0xc0000000(3Gb)) 아래에 존재 합니다. 가상의 커널 페이지 집합은 다른 말로 전역입니다, 어떤 스레드나 프로세스가 실행중인지는 상관없이 남아있는 것입니다. 커널은 사용자와 커널페이지를 둘다 접근할수 있지만 사용자 프로세스는 오직 자신의 페이지만 접근할 수 있습니다. 자세한 내용은 3.1.3절의 Virtual Memory참조
프레임(때때로 물리 프레임 또는 페이지 프레임을 불림)은 물리적인 메모리에 연속적인 공간입니다. 페이지처럼 프레임은 페이지사이즈 이고 페이지처럼 정렬되어야 합니다 이와같이, 32-bit 물리주소는 20bit 프레임 번호와 12-bit 프레임 오프셋으로 나뉘어질수 있습니다. 80x86는, 물리 주소의 메모리직접접근방식으로 나뉘어지지 않습니다. 핀토스는, 커널 가상메모리의 맵핑을 통해 물리적인 메모리에 직접 접근할 수 있습니다. 커널 가상메모리의 첫번째 페이지는 물리적 메모리의 첫 번째 프레임으로 매핑되어 있으며, 두 번째 페이지엔 두번째 프레임, 등등 이와같이 프레임들은 커널가상메모리를 통해 접근될수 있습니다. 핀토스는 물리주소와 커널 가상주소사이에 변환을 해주는 함수를 제공합니다 자세한내용은 Section A.6(가상주소 75페이지 참조)
4.1.2.3 Page Tables
핀토스에서 페이지 테이블은 CPU가 사용하는 가상주소를 물리주소로 변환하는 것을 사용하는데 쓰이는 데이터 스트럭쳐 입니다. 그것은 또한 페이지에서 프레임으로 변환하는. 페이지테이블 포멧은 80x86 아키텍쳐에 의해 이야기 됩니다. 핀토스는 pagedir.c에 페이지테이블 관리관련 코드를 제공합니다. 아래와 같은 그림은 페이지와 프레임의 관계를 이야기합니다.
왼쪽에서 가상주소는 페이지번호와 오프셋으로 구성됩니다. 페이지테이블은 프레임넘버에 페이지넘버로 전환되고 오른쪽에서물리주소를 포함하는 오프셋은 수정 없이 합쳐집니다.
- Table of file mappings : 프로세스는 프로세스의 가상 메모리 공간에 파일들을 맵핑할지 모릅니다. 여러분은 어느페이지들에 파일이 맵핑되는지 추적하기 위해 테이블이 필요합니다. 여러분이, 반드시 4개의 완전히 다른 데이터 스트럭쳐를 구현할 필요가 있는것은 아닙니다.: 그것은 완 전히 또는 부분적으로 통합된 데이터 스트럭쳐안의 관련된 자원들을 합치는데에 유용할지 모릅니다.
각각의 데이터 스트럭처를 위해, 여러분은 각각의 요소가 어떤정보를 포함하는지 결정할 필요가있습니다.
여러분도 데이터 스트럭쳐의 범위에서 지역적(프로세스안에서) 또는 전역(전체시스템안에서 허용되는)을 결정할 필요가 있습니다, 또한 얼마나 많은 인스턴스들이 그 범위안에서 요구되는지.
여러분의 디자인을 심플하게 하기 위해서, 여러분은 페이지의 메모리공인 이외에 이 데이터 스트럭쳐들을 저장해야 할지 모릅니다.
즉, 여러분은 그것들 사이의 포인터들이 유효할 것인지 확신할 수 있습니다. 데이터 스트럭쳐들의 가능한 선택은 배열, 리스트, 비트맵, 그리고 해쉬테이블을 이야기 합니다. 배열은 종종 가장 간단한 접근이지만 드문드문 비어있는 배열은 메모리를 낭비합니다 리스트 또한 간단하지만 특정 위치를 찾아내기 위해 긴 리스트를 탐색하는것은 시간을 낭비합니다. 배열과 리스트는 크기가 변경될수 있으나 리스트는 중앙에서 보다 효과적으로 삽입/삭제를 지원합니다. 핀토스는 비트맵 데이터 스트럭쳐를 'lib/kernel/bitmap.c'와 'lib/kernel/bitmap.h'에 포합하고 있습니다, 비트맵은 디트들의 배열이며 각각은 true,또는 false가 될수 있습니다. 비트맵은 일반적으로 (한쌍의)자원들의 집합안에서 사용을 추적하는데 사용됩니다. 자원 n이 사용중이라면, 비트맵의 비트 n은 true입니다. 핀토스 비트맥들은 size로 고정되어있는데, 여러분은 리사이징을 지원하는 그것들의 구현을 확장할수 있었습니다. 핀토스도, 해쉬테이블의 자료구조를 포함합니다.(섹션 A.8(해쉬테이블) 81페이지참조) 핀토스 해쉬테이블은 테이블 사이즈의 광역에 걸쳐서 삽입 삭제를 효과적으로 지원합니다. 비록 더 복잡한 자료구조들이 성능이나 이득을 가져올지는 모르겠지만, 그것들은 또한 여러분의 구현도 불필요하게 어렵게 만들지 모릅니다. 이와같이, 우리는 여러분의 디자인의 부분으로서 조금 추천되는 자료구조의 구현을 추천하지 않습니다.
4.1.4 Managing the Supplemental Page Table
supplemental page tatable은 각각의 페이지에대해 새로운 데이터로 페이지 테이블을 보충합니다.
그것은 페이지 테이블의 포멧에 의해 한계가 제한되기 때문에 필요합니다.
그러한 데이터 스트럭쳐는 종종 "page table"이라고 풀립니다.
우리는 혼란을 줄이기 위해 "supplemental"이란 말을 추가하였습니다.
supplemental page table은 적어도 두가지 목적을 위해 사용됩니다.
가장 중요한 것으로 페이지폴트에서 커널은 어떤 데이터가 있는지 확인하기 위해서 supplemental page table에서 폴트된 가상페이지를 찾습니다.
여러분은 여러분의 바램에 의해서 supplemental page table을 조직할지 모릅니다.
그 조직에 관한 적어도 두가지의 기본적인 접근이있습니다. segment에 관해 또는 page들에 관해서 임의로 다신은 페이지테이블의 멤버들을 추적하는 인덱스로서 페이지테이블 자체를 사용할지 모릅니다.
여러분은 그렇게 하기 위해서 'pagedir.c안에 구현된 핀토스 페이지 테이블을 수정해야 할것입니다.
우리는 이 접근을 대학생에게 추천합니다.
자세한내용은 섹션 A.7.4.2(Entry Format 80페이지)참조
supplemental page table의 가장 중요한 사용자는 페이지폴트 핸들러입니다
프로젝트2에서 페이지 폴트는 언제나 커널또는 유저프로그램의 버그를 가리키고 있었습니다.
프로젝트3에서 이것은 더이상 true가 아니었습니다. 현재, 페이지 폴트는 페이지가 파일또는 스왑으로부터 반입되지 않으면 안되는것을 나타낼때만 가리키고 있을지 모릅니다. 여러분은 이러한 경우들을 위해 더욱 세련된 페이지 폴트 핸들러를 구현해야만 할것입니다.
'threads/exception.c'안의 page_fault()를 수정함으로써 구현될지도 모르는 여러분의 페이지 폴트 핸들러는 개략적으로 아래사항을 하기위해 필요합니다.
1. 추가 페이지 테이블의 fault된 페이지를 위치 시킵니다. 만약 메모리 참조가 valid 하면, 페이지의 데이터가 위치한 suppliemental page table entry를 사용하고 그것을 파일 시스템, 스왑공간 또는 제로 페이지에 단순히 위치시킬 것입니다. 만약 여러분이 공유를 구현한다면, 페이지의 데이터는 페이지 테이블이 아닌 이미 페이지 프레임에 있을 것입니다. 만약 페이지가 연관사상 되어있지 않다면, 그것은 그곳에 없는 데이터이거나 커널 가상메모리 상의 잘못된 페이지 또는 읽기 전용으로 시도되어 접근된 것입니다. 그때에는 접근은 정당하지 않습니다. 어떠한 정당하지 못한 접근들은 그 리소스의 모든 것들을 그것들의 주위로 놓으며 종료 시킵니다.
2. 우선 페이지가 저장된 프레임을 찾아냅니다. 4.15 Section [Managing the Frame Table] 의 40페이지를 자세하게 찾아보십시오.만약 여러분이 sharing을 구현하면, 여러분이 필요한 data는 이미 프레임에 있을 것이고 그 경우 여러분은 반드시 가능한한 그 프레임에 반드시 위치 시켜야 할 것입니다.
3. 프레임에 데이터를 Fetch하고 파일시스템이나 스왑 등으로 부터 읽어 들입니다.만약 여러분이 sharing을 구현하면 여러분이 구현한 페이지는 이미 프레이에 있을 것이고, 그경우 이단계에서 필요로한 아무액션도 취하지 않습니다.
4. 물리 페이지에서 faulting virutal address의 페이지 테이블 엔트리를 표시합니다. 여러분은 'userprog/pagedir.c'의 함수들을 사용할 수 있습니다.
4.1.5 Managing the Frame Table
프레임 테이블은 유저 페이지를 가지고 있는 개개의 프레임마다 하나의 엔트리를 가지고 있습니다. 각각의 엔트리는 그 페이지에 해당하는 포인터를 가지고 있으며, 데이터가 있다면 그것을 점유하거나 작성자가 지정한 다른 데이터를 점유할 수 있습니다. 핀토스는 프레임 테이블을 이용해서 효과적으로 축출법(eviction policy)을 실행할 수 있으며 이는 그 모든 프레임이 자유롭지 않을 때 페이지를 선택함으로서 축출을 하게 됩니다.
유저 페이지를 위해서 쓰이는 프레임들은 반드시 "유저 풀"에서 가져와야 하며 palloc_get_page(PAL_USER)를 이용해서 호출합니다. 작성자는 반드시 PAL_USER를 사용하여 "커널 풀"로부터 프레임을 배정받는 일을 피해야합니다. 그렇지 않을 경우 몇몇 시험 사례에서 예기치 않은 오류를 일으킬 수 있습니다. ([Why PAL USER?] 항목을 보십시오) 만약 작성자가 테이블 실행의 일환으로서 'palloc.c'를 수정 하면 반드시 그 두 개의 풀의 차이를 숙지하시기 바랍니다.
프레임 테이블에서 제일 중요한 실행은 미사용 프레임을 획득하는 것입니다. 하나 이상의 프레임이 자유로울 경우 이 과정은 쉽게 처리됩니다. 하지만 모든 프레임이 묶여있을 경우 원래 프레임의 몇몇 페이지를 축출함으로서 프레임을 획득해야합니다.
만약 교체 슬롯(Swap Slot)조차 없이 프레임이 하나도 축출되지 않을 경우, 혹은 모든 교체 슬롯이 포화 상태인 경우 커널이 경색될 가능성이 있습니다. 실용 운영 체제의 경우 매우 폭넓은 범위에 걸쳐서 이러한 경직 상태를 피하거나 혹은 빈 프레임을 얻는 수단을 강구하고 있습니다. 하지만 이러한 방법은 지금 다루는 프로젝트의 범위 밖입니다.
축출 과정은 개략적으로 다음 과정을 통해서 이루어집니다.
1. 축출할 프레임을 고릅니다.
이 과정에서 작성자가 정한 교체 알고리즘이 사용됩니다. 페이지 테이블 내의 "조회된 Accessed" 혹은 '난잡한 dirty" 비트들은-밑에서 설명하겠지만- 이 과정을 처리하는데 있어 유용합니다.
2. 위에서 언급된 요소를 대조하여 프레임 내의 페이지들을 참조합니다. 작성자가 실행 공유(Implemented Sharing)를 하고 있지 않는한, 한 순간에는 오직 하나의 페이지만의 한 프레임에 대해서만 참조합니다.
3. 필요하다면, 그 페이지를 파일 시스템에 옮기거나 교체(Swap)합니다. 축출된 프레임은 이제 다른 페이지를 저장하기 위해서 사용될 수 있습니다.
4.1.5.1 Managing the Frame Table
80x86 하드웨어는 몇 가지 페이지 교체 알고리즘의 구현을 도와주는 것을 제공합니다. 페이지에서 어떤 것을 읽거나 쓰면, CPU는 페이지의 PTE를 1로 설정시키고, 쓰기를 할때에 CPU는 dirty bit를 1로 설정합니다. CPU는 결코 이런 것들을 0으로 리셋하지 않고 OS가 그러한 것들을 합니다.
여러분은 여기서 같은 프레임에서 두 개 또는 그 이상의 페이지간에 aliases을 알아 챌 필요가 있습니다. 그 액세스 된 것과 다른 aliases의 dirty bits는 업데이트 되지 않습니다.
핀토스에서 모든 user virtual memory는 그것의 kernel virtual memory로 alias 됩니다. 여러분은 반드시 어떠한 식으로든 이러한 alias 들을 관리해야만 합니다.
예를 들어서, 여러분의 코드는 accessed와 dirty bit가 체크될 수 있고 업데이트 될 수 있습니다. 대신에 커널은 user virtual address를 통해서 user data가 accessing 될 때에만 그 문제를 피할 수 있습니다.
다른 aliases는 다만 만약 여러분이 기타 credit을 공유하는 것을 구현 했을 때에만 일어납니다.
A.7.3 섹션참조
4.1.6 Managing thw Swap Table
스왑 테이블에서는 빈 스왑 슬롯을 추적합니다. 그것은 스왑디스크의 프레임에서 사용되지 않은 스왑을 골라냅니다. 그것은 반드시 어떤 페이지가 종료되어서 스왑공간으로 읽고 되돌아 오면 빈스왑 슬롯으로 풀어줄 수 있도록 허용해야 합니다.
여러분은 devices/disk.h에 있는 디스크 인터페이스 원형을 사용하여 스왑디스크로 hd1:1 인터페이스의 디스크를 사용할 것입니다. 'vm/build' 디렉토리로 부터 핀토스-mkdisk swap.dsk n 명령을 사용하여 'swap.dsk'라는 이름의 n MB 스왑 디스크를 만든다. 그후 'swap.dsk' 핀토스를 실행시키면 'swap.dsk'는 자동적으로 hd1:1로써 부착됩니다. 대안으로는 핀토스에서 임시적인 n MB 스왑 디스크를 사용하기위해 '--swap-disk=n'을 명령할 수 있습니다.
스왑 슬롯은 늦게 할당되는데 이것은 사실상 방출에 의해 요청될 때에만 수행됩니다. 실행가능한 데이터 페이지를 읽는 것과 그것을 스왑에 기록하는 것은 프로세스가 시작될때엔 늦지 않는다. 스왑 슬롯은 특정 페이지 저장을 위해 할당되지(reserved) 말아야 합니다.
스왑슬롯은 그것의 내용이 프레임으로 되읽혀질때 비워진다.
4.1.7 Managing Memory Mapped Files
파일시스템은 가장 자주 read와 write 시스템콜에 접근합니다. 두번째 인터페이스는 mmap 시스템 콜을 사용하는 파일에서 가상 페이지로의 'map' 이다. 프로그램은 파일 데이터를 직접적으로 지시하는 메모리를 사용할 수 있습니다.'foo' 파일이 0x10000 바이트 길이라고 가정해봅시다. 만약 'foo'가 메모리의
시작주소 0x5000에 맵핑 되어있다면 0x5000 ... 0x5fff 위치의 메모리 접근은 'foo'에 대응하는 바이트에 접근하게 될 것입니다. 여기에 mmap을 사용하여 콘솔에 파일을 출력하는 프로그램이 있습니다. 명령줄에 쓰인 파일을 열고 가상 주소 0x10000000 에 그것을 맵핑하고 맵핑된 데이터를 콘솔에 출력한뒤 파일을 언맵핑합니다.
#include <stdio.h> #include <syscall.h> int main (int argc UNUSED, char *argv[]) { void *data = (void *) 0x10000000; /* Address at which to map. */ int fd = open (argv[1]); /* Open file. */ mapid_t map = mmap (fd, data); /* Map file. */ write (1, data, filesize (fd)); /* Write file to console. */ munmap (map); /* Unmap file (optional). */ return 0; }
모든 에러 핸들링을 하는 이와 유사한 프로그램이 'examples'디렉토리 안에 'mcat.c'가 포함되어 있고 mmap의 두번째 예제로 'mcp.c'가 포함되어 있습니다.
여러분의 submission은 메모리에 맵핑된 파일 파일에 의해 메모리의 추적을 할 수 있어야 합니다. 이것은 맵핑된 지역의 페이지 폴트를 적절히 처리하고 맵핑된 파일은 프로세스의 다른 세그먼트들에 중복되지 않아야 함을 책임지기 위해 필요하다.
4.2 Suggested Order of Implementation
우리는 다음과 같은 초기 구현순서를 제안합니다.
프레임 테이블(4.1.5 [프레임 테이블의 맵핑] 부분, 페이지 40을 참고). 여러분의 프레임 테이블 할당자를 사용하기 위해 'process.c'를 수정해야 합니다. 아직 스왑핑은 구연하지 말라. 만약 여러분이 프레임 밖에서 실행합니다면 할당에 실패 하거나 커널패닉에 걸리게 됩니다. 이 단계 후에 여러분의 커널은 프로젝트 2의 모든 테스트를 통과해야만 합니다.
페이지 테이블과 페이지 폴트 핸들러의 보충(4.1.4 [페이지 테이블의 보충] 부분, 페이지 40을 참고). 실행할수 있도록 로딩과 스텍에 셋업할 때 보충 페이지 테이블에 필요한 정보를 기록하기 위해 'process.c'를 수정합니다.
코드와 데이터 세그먼트의 로딩은 페이지 폴트 핸들러에 구현합니다. 지금은 타당한 접속에 관해서만 고려해봅시다. 이 단계 후에 여러분의 커널은 프로젝트 2의 모든 기능적 테스트 경우를 통과해야하지만 확고한 테스트 몇개에 대해 생각해봅시다.
여기에서 여러분은 스택의 증가, 맵핑된 파일들의 구현과 병행적인 프로세스 종료에서의 페이지 교정을 할 수 있습니다.
다음 단계는 추방(eviction)의 구현이다(4.1.5 [프레임 테이블 관리] 부분, 페이지 40을 참고). 초기에 여러분은 임의적으로 추방할 페이지를 선택했습니다
현 시점에서 여러분은 커널과 유저페이지의 들쭉날쭉한 접근과 dirty비트를 어떻게 관리할 것인지 고민해야할 필요가 있습니다. 추방 프로세스 중에 프로세스B의 프레임인 페이지에서 프로세스 A가 폴트가 났다면 이것을 어떻게 다룰 것인가 또한 동기화와 관련이있습니다. 마지막으로 클락 알고리즘과 같은 추방정책을 구현합니다.
4.3 Requirements 요구사항
이 과제는 디자인에 제한이 없는 문제 이다. 우리는 어떻게 할 것인지에 대해 조금이라도 가능성이 있는것에 대해 말할 것입니다. 우리는 기능적인 면 보다는 여러분의 OS가 지원할 것을 요구하는데 초점을 둘 것입니다.. 여러분은 페이지 폴트를 어떻게 처리할 것인지, 어떻게 스왑 디스크를 구성할 것인지, 페이징을 어떻게 구현 할 것인지 등을 자유롭게 선택해야 합니다.
4.3.1 Design Document 디자인 문서
여러분의 프로젝트에 임하기 앞서 여러분은 프로젝트 3 디자인 문서 틀을 반드시 '핀토스/src/vm/DESIGNDOC'폴더 아래 복사해 두어야 합니다. 우리는 여러분이 프로젝트 작업을 시작하기 전에 디자인틀을 읽어둘 것을 추천합니다
가공 프로젝트 예제 디자인 문서인 부록 D [프로젝트 문서], 페이지 96을 읽기 바랍니다.
4.3.2 Paging 페이징
실행가능하도록 로드된 세그먼트를 위해 페이징을 구현합니다. 커널의 가로채기로 인한 페이지 폴트로 인해 이러한 페이지들은 대부분 늦게 로드됩니다.
추방(eviction)이 일어나면 페이지는 로드된 후에 (dirty bit로 인지하여) 스왑공간에 쓰여지도록 수정되야 합니다. 실행가능한 프로그램으로부터 항상 블럭을 읽을수 있도록 하기위해, 오직 읽기만한 페이지를 포함하여 수정되지 않은 페이지는 절대로 스왑공간에 쓰여져서는 안됀다.
LRU에 가까운 페이지 교체 알고리즘을 구현합니다. 여러분의 알고리즘은 적어도 'second chance' 또는 'clock' 알고리즘 만큼 수행할 수 있어야 합니다.
여러분의 디자인은 병행성을 보장해야 합니다. 만약 프로세스가 폴트가 없는 수행이 계속되거나 I/O수행이 없는 다른 페이지 폴트 동안에 페이지 폴트로 I/O를 요구 합니다면 수행이 완료 되어야 합니다. 이것은 동기화에 대한 노력을 요구합니다.
여러분은 'userprog/process.c' 안에있는 load_segment()의 루프문인 로더 프로그램의 핵심부를 수정한 필요가 있을 것입니다.. 루프문에서 page_read_bytes는 실행가능한 파일로부터 읽어들은 바이트수를 받고 page_zero_bytes는 뒤이어 읽은 바이트를 0으로 초기화한 바이트의 수를 받습니다. 이 두수의 합은 PGSIZE(4,096)을 항상 가진다. 페이지의 처리는 이런 변수들의 값을 요구하게 됩니다.
만약 page_read_bytes가 PGSIZE와 같다면 페이지는 디스크로의 그것의 처음 접속으로부터 페이지를 요구하게됩니다. 만약 page_zero_bytes가 PGSIZE와 같다면 0으로 채워져있기 때문에 페이지는 디스크로부터 읽어들일 필요가 없습니다.. 다르게 둘다 PGSIZE와 같지 않다면, 페이지의 처음부분은 디스크로부터 읽고 나머지는 0으로 채워야 합니다.
4.3.3 Stack Growth 스택의 성장
스택의 성장구현. 프로젝트 2에서 스택은 유져 가상 주소공간의 꼭대기에 단일페이지였고 프로그램들은 스택의 크기만큼 제한적이었다. 지금 과거보다 현재 크기만큼 스택이 성장합다면 필요에 따라서 페이지들을 추가로 할당해야합니다.
추가적인 페이지의 할당이 있을때에만 스택 접속에 표시되어야 합니다. 스택에 접근하려는 다른 접속 시도로부터 구별하는 방법을 고안해야 합니다.
유저 프로그램들이 스택포인터 아래에 썼다면 버그가 많다. 왜냐하면 전형적인 OS들은 스택에 데이터를 넣는 시그널을 보내서 어떤 상황이던간에 인터럽트 걸기 때문이다. 그러나 80x86 PUSH 명령은 그것을 스택포인터에 적용하기 전에 접근권한을 체크하도록 되어있기 때문에 아마도 스택포인터 아래로 4 바이트 페이지 폴트를 야기할 것입니다. 유사하게 PUSHA 명령은 한번에 32바이트를 push하기에 스택포인터 아래로 32바이트 폴트가 난다.
여러분은 유져 프로그램 스택 포인터의 현재 값을 얻을 필요가 있을것입니다.
유저 프로그램에 의해 생성된 시스템 콜 또는 페이지 폴트 범위 안에서 struct intr_frame의 맴버변수 esp로부터 syscall_handler() 또는 page_fault() 각각을 여러분은 정정할 수 있습니다. 스택에 접근하기 전의 유저 포인터를 검증할수 있다면(3.1.5 [유져 메모리 접근] 부분, 페이지 26 참고), 이것들은 처리할 필요가 있는 유일한 경우이다. 반면에 잘못된 메모리 접근을 감지한 페이지 폴트라면, 여러분은 커널에서 발생한 페이지 폴트의 다른 경우의 처리를 필요로 하게 될 것입니다. 프로세서는 유져에서 커널 모드로 전환할때 야기되는 예외상황에서 struct intr_frame 범위밖의 esp를 읽고 유져 스택 포인터가 아닌 엉뚱한 값을 산출해 낼때 스택포인터를 저장할 뿐이다. 여러분은 유져에서 커널 모드로 초기 변환시에 struct thread에 esp를 저장하는것 처럼 다른방법으로 정리할 필요가 있을 것입니다.
여러분은 대부분은 OS들 처럼 스택 사이즈에 절대적 제한을 부과해야합니다.
Unix system 기반의 ulimit 명령으로 유져가 조정가능한 제한선을 가진 OS도 있습니다. 대부분의 GNU/Linux system 에서 일반적으로 8MB 제한선을 가진다
먼저 스텍 페이지는 스택 페이지는 늦게 할당되어서는 안됄 필요가 있습니다. 여러분은 오류 발생을 기다릴 필요가 없이 커멘드 라인의 인수들을 로드하는 시간에 초기화와 할당을 할수있습니다. 모든 스택 페이지는 추방(eviction)의 후보가 될 수 있습니다. 추방된 스택 페이지는 스왑공간에 쓰여져야만 합니다.
fd로 열린 파일은 프로세스의 가상 주소 공간에 맵핑됩니다. 전체파일은 addr로부터 연속적인 가상 페이지에 맵핑됩니다.
여러분의 VM 시스템은 mmap 영역에 늦게 로드되고 mmap된 파일 자체로 맵핑을 저장하는데 사용합니다. mmap에 의해 맵핑된 페이지의 추방은 그것을 맵핑했던 파일에 쓰는것입니다.
만 약 파일의 길이가 PGSIZE의 복수개가 아니라면 최종적으로 맵핑된 페이지'stick out'을 넘어 파일끝에 몇바이트이다. 디스크로부터 오류가 발생한 페이지 일때 이런 바이트들을 0으로 셋팅하고 디스크로 써진 페이지일 경우에는 이를 무시합니다.
만약 성공적이라면 프로세스에서 맵핑된 유일한 식별자로 'mapping ID'를 반환하는 기능이다. 유효한 맵핑 아이디가 아니거나 프로세스의 맵핑이 수정되지 않은것과 같이 실패한경우 이것은 반드시 '-1'을 반환하게 됩니다.
mmap 은 만약 0바이트의 길이를 가진 파일을 fd로 열었을 경우 실패하게 됩니다. addr 이 페이지 정렬되어 있지 않거나, 실행가능한 프로그램 로드 시간에 맵핑된 페이지 또는 스택을 포함하는 현존하는 맵핑된 페이지에 덮어씌워지는 페이지의 영역이 있다면 이것은 반드시 실패합니다. 그리고 addr이 0
이라면 반드시 실패하도록 되어있습니다. 왜냐하면 핀토스 코드는 가상 페이지 0은 맵핑되지 않도록 되어있기 때문이다. 마지막으로 파일 디스크립터 0 과 1은 콘솔의 입력과 출력을 표현하므로 맵핑되지 않는다.
void munmap (mapid_t mapping) // [system call]
아직 unmapped되지 않은 프로세스에 의해 이전 mmap의 호출로 맵핑 아이디를 반환하는 맵핑을 Unmap합니다.
모 든 맵핑은 암묵적으로 프로세스가 종료될때 exit 과정 또는 다른 방법으로 unmapped 됩니다. 맵핑이 암묵적으로 또는 명시적으로 unmapped될 때 모든 페이지는 프로세스가 파일에 다시 쓰는것으로 인해 unmapped됩니다. 그리고 페이지는 페이지는 다시 쓰여지지 않는다. 페이지들은 가상 페이지의 프로세스 리스트들로부터 제거됩니다.
파 일의 제거 또는 닫음은 그것의 mapping으로부터 unmapped하지 않는다. 생성되었을때 맵핑은 munmap이 호출될때까지 또는 Unix정책에 따른 프로세가 종료될때 까지 유효하다. [열린 파일의 제거] 부분, 페이지 34를 참고하면 더 많은 정보를 볼 수 있습니다..
핀토스 설치에 대한 포스팅이 본의아니게 너무 과열되어서 ㅎㅎ 이어지는 P2 P3는 선뜻 올리지 못하고 그냥 흐지부지 넘어갔는데 (그냥 넘어간 듯 말하지만, 사실 아는게 별로 없다) P4는 다시한번 조심스럽게 포스팅에 도전해보려 한다. 변역된 글을 살짝 읽어봤는데. 음 과연. 이번 과제는 설치만큼 간단한 과제는 아닌듯 싶다. 사실 성공할지 조차도 의심스럽다..ㅠ 번역해놔도 뭔말인지 이해가 안간다. 이 미친 괴물. 이래놓고 실패하면 뭔망신이겠냐만은 ㅋ 일단 저질러놓고 수습하면 된다.
맨날 지겹다 힘들다 지친다 떠들어놓고도 이러고 있는걸 보면 축하해. 드디어 미친게 분명하다 ㅋㅋ
태어나서 처음으로 Pintos라는 것에 대해 들어보았다. 사실 들어본것도 아니다. A4용지 한쪽 귀퉁이에 쓰여진 "Pintos 프로젝트를 수행하라"는 짤막한 한마디. 이너넷을 대충 뒤져보니, Stanford 대학에서 개발한 학습용 OS인 것 같았다.
나중에 프로젝트에 대한 추가 설명이 있겠지만, 주말이고 할 일도 없고 해서 그냥 설치만 미리 해보기로 했다. 근데 이렇게 어려울 줄이야.. 주말이 그냥 지나갔다;;
우분투 7.10 커널 2.6.22.14 기준에서 작성. 정확한 순서는 아니다. 내가 작업하면서 시행착오를 겪은 순서대로 썼다. 아마 거의 대부분의 오류는 여기 다 있을듯. 내용이 길기 때문에, 필요한 부분만 찾아서 보는게 좋겠지요.
1. bochs 설치
Pintos를 구동하려면 이게 필요하단다. (Box와 같은 발음으로 읽는다. 박스.) 이너넷을 통해 구할 수 있는 공개 프로그램(http://bochs.sourceforge.net/)이며 "오픈소스 ia32 에뮬레이션 프로젝트"라는 것을 보니, 뭔가 가상머신의 일종으로 짐작된다.
(1) 설치과정
우분투에서는 bochs가 내장되어있다. 그래서 시스템 - 관리 - 시냅틱 꾸러미 관리자 - 검색 - 'bochs'입력 - 'bochs'체크하여 설치표시 - 적용 요렇게 순서대로 클릭해주면 알아서 설치한다. 쉽군. 별거 아니네. 그러나 이 방법을 통해서 설치하게되면, 2006년도 버전인 bochs-2.2 버전이 설치된다. 뭔가 찜찜하기에, 최신버전(2007년 2.3.6)을 설치하기 위해 위 사이트에서 직접 다운로드하여 설치했다. (지금 생각해보면 이때부터가 실수였을수도. 다음에는 내장 패키지로 한번 해봐야지...;;)
다운받은 파일을 압축을 풀어주고, 터미널을 실행하여 해당 폴더로 간 후,
$ ./configure --enable-gdb-stub $ make $ sudo make install
순서대로 요렇게 해주면 설치가 끝난다. 위의 것들이 한 번에 됐다면 바로 Pintos를 설치해주면 된다.
(2) 첫번째 오류 - error: C compiler cannot create executables
역시,,,,, 한번에 될 리가 없지 않은가. 처음부터 막힌다. $ ./configure --enable-gdb-stub 를 실행해주면,
checking build system type... i686-pc-linux-gnulibc1 checking host system type... i686-pc-linux-gnulibc1 checking target system type... i686-pc-linux-gnulibc1 checking if you are configuring for another platform... no checking for standard CFLAGS on this platform... checking for gcc... gcc checking for C compiler default output file name... configure: error: C compiler cannot create excutables See `config.log' for more details.
컴파일러가 실행파일을 생성하지 못하겠다는 이야기다. (컴파일러가 안하면 누가 하라고;;;;;) 문제의 원인은, C컴파일러는 우분투를 설치하면서 자동으로 설치되나, C++컴파일러 역할을 하는 g++패키지는 직접 설치해주어야 한다는 것.(by 구글) 아래의 방법으로 해결해주었다. 원인을 쉽게 파악해서 그런지, 해결도 쉬웠다.
설치가 끝나면 $ ./configure --enable-gdb-stub 를 다시 실행해준다. 기쁜마음으로.
(3) 두번째 오류 - X windows libraries were not found
그러면 1분만에 두 번째 문제에 봉착하게 된다.
checking whether to build docbook documentation... no checking for wx-config... not_found checking for wxWidgets configuration script... not_found checking for wxWidgets library version... checking for default gui on this platform... x11 ERROR: X windows gui was selected, but X windows libraries were not found.
X윈도우 라이브러리가 없으니 이를 설치하라는 것 같았다. (그게 뭐지... ) 혼자 시냅틱 패키지 관리자를 방황하다가 구글에 그대로 쳐넣으니 의외로 쉽게 답이 나왔다. 나 같은 고생을 하는 사람이 또 있었다. 터미널을 열어 다음과 같이 입력해주면 해결~!
$ sudo apt-get install xorg-dev →이게 아마도 X윈도우 라이브러리...??
그 다음부터는 일사천리로 진행된다.
$ ./configure --enable-gdb-stub $ make $ sudo make install
여기서 sudo 라는 명령어는 시스템으로부터 SuperUser(관리자)권한을 얻겠다는 명령어이다. 저걸 안 넣어주면 가끔 Permission denied라는 오류가 나면서 어쩌고저쩌고 떼를 쓰므로, 시스템을 수정할때는 sudo 를 넣어주어야 한다. 간혹 관리자 비밀번호를 입력하라고 할 때도 있다. 넣어주자.
운좋게도 두 개의 오류만으로 설치가 끝났다. 다행이다. ㅋㅋ 하지만 이것이 다가 아니었다........;;
다운로드후 원하는 곳에다가 압축을 풀어준다. 터미널을 열어서 압축 푼 pintos 디렉토리까지 찾아간 후
$ cd /src/threads $ make
를 차례로 실행해준다. 뭐라고 막 괴물같은 글자들을 뱉어내면서 설치가 되고... 다 된 다음엔 /src/threads 밑에 새로 build라는 디렉토리가 생긴다.
여기까지만 해주면 설치 완료이다. 상대경로를 직접 입력하여 pintos를 실행할 수 있지만, 좀더 편하게 실행하기 위해서 환경변수를 설정해주면 좋다. 터미널에 다음을 입력.
$ sudo gedit ~/.bashrc
관리자 비밀번호 넣으라고 나오고, 넣으면 편집창이 하나 뜬다. 당황하지 말고 맨 아랫줄에 다음을 그대로 입력해주고 저장하고 빠져나온다.
export PATH="$PATH:/home/user/pintos/src/utils" ↑pintos를 설치한 디렉토리경로를 적어준다.
환경변수 설정도 끝. $ bash로 환경변수를 다시 읽어오고 이제 pintos를 테스트하기 위한 최종 명령만이 남아있다.
$ cd build (build 디렉토리로 이동) $ pintos -- run alarm-multiple (이 명령으로 pintos를 테스트할 수 있다. 띄어쓰기 유의)
만약 난 특별하기 때문에 위에서 환경변수 설정을 안했다면, 다음과 같은 방법으로 실행해줄 수 있을 것이다.
$ cd build (build 디렉토리로 이동) $ ../../utils/pintos -- run alarm-multiple (상대경로 직접 찾아가서 실행. 역시 띄어쓰기 유의)
여기까지가 간단하게 요약한 Pintos의 설치과정이다. bochs에 이어서 이것까지 단번에 성공했다면, 당신은 올 한해 운수가 격하게 좋은 것이다.
(2) 첫 번째 오류 - __stack_chk_fail
올 한해 운수는 별로 좋지 않은것 같다. $ make명령 실행의 결과다.
/home/user/pintos/src/threads/build/../../lib/stdio.c:547: undefined reference to `__stack_chk_fail' tests/threads/alarm-wait.o: In function `test_sleep': /home/user/pintos/src/threads/build/../../tests/threads/alarm-wait.c:134: undefined reference to `__stack_chk_fail' tests/threads/alarm-simultaneous.o: In function `test_alarm_simultaneous': /home/user/pintos/src/threads/build/../../tests/threads/alarm-simultaneous.c:19: undefined reference to `__stack_chk_fail' tests/threads/alarm-priority.o: In function `test_alarm_priority': /home/user/pintos/src/threads/build/../../tests/threads/alarm-priority.c:39: undefined reference to `__stack_chk_fail' tests/threads/priority-fifo.o: In function `test_priority_fifo': /home/user/pintos/src/threads/build/../../tests/threads/priority-fifo.c:84: undefined reference to `__stack_chk_fail' tests/threads/priority-sema.o:/home/user/pintos/src/threads/build/../../tests/threads/priority-sema.c:38: more undefined references to `__stack_chk_fail' follow make[1]: *** [kernel.o] 오류 1 make[1]: Leaving directory `/home/user/pintos/src/threads/build' make: *** [all] 오류 2
`__stack_chk_fail'이라는 오류들이 무더기로 발생한 것을 볼 수 있었다. 젠장. 점점 미쳐가고 있었다. 분명이 하라는 대로 잘 하고 있는데 왜 나만 이래 ㅋㅋ 정신을 가다듬고 다시 구글 구글 구글... 우분투 OS에서만 발생하는 오류인 것 같았다. make 도중 stack을 체크하는 과정에서 발생하는 오류..정도? 구글에는 나 말고도 많은 사람들이 이것 때문에 고민한 흔적이 있었다. 유명한 놈이군. 거기서 시키는 대로, /pintos/src 내의 Make.config파일을 찾아서 옵션을 추가해주는 것으로 해결을 보았다.
CFLAGS = -g -msoft-float -O -fno-stack-protector ↑이 부분을 찾아서 ↑요렇게 뒤에 덧붙여준다. no-stack-protector
파일 수정 저장후 빠져나와서, 다시 처음부터 실행. $ make clean으로 이전에 build된 것들을 제거해주어야 $ make 가 정상적으로 수행된다. 결과는? 성공. 환경변수도 이상없이 설정되었다.......만..
(3) 두 번째 오류 - System BIOS must end at 0xfffff
$ pintos -- run alarm-multiple 명령. 지금까지의 과정을 최종 확인하는 순간이다. 그러나 역시 마지막 단계를 넘기 직전, 최후의 보스는 어김없이 나온다. 굉장히 강한 놈이었다. 고민에 고민을 거듭해도 모르겠고, 구글링도 완벽한 답을 주지는 못했다..
Writing command line to /tmp/mcnK5SXSJY.dsk... warning: can't find squish-pty, so terminal input will fail bochs -q ======================================================================== Bochs x86 Emulator 2.3.6 Build from CVS snapshot, on December 24, 2007 ======================================================================== 00000000000i[ ] reading configuration from bochsrc.txt 00000000000i[ ] installing x module as the Bochs GUI 00000000000i[ ] using log file bochsout.txt ======================================================================== Bochs is exiting with the following message: [MEM0 ] ROM: System BIOS must end at 0xfffff ========================================================================
Bochs라는 프로그램의 구 버전에서는 BIOS의 크기가 64k로 고정되어 있으며, 그 시작점의 주소가 0xf0000 이었다. 그러나 2.2.5버전 이후로는 BIOS가 512k까지 커졌고 시작점의 주소를 지정할 필요가 없어지게 되었다. 지금 상황은 새로운 버전의 BIOS를 구버전의 시작점 주소방식으로 사용하고 있는 것 같아보이므로, romimage파일에서 시작점의 주소 parameter를 직접 찾아서 제거해주면 간단히 해결된다.
뭐야. 별거 아니었군. 간단히 해결된대. . . . . . . . . . . 그래서 어떻게 해줄까. romimage 파일은 무엇이며 시작점의 주소 parameter가 무슨 개소리인가.
여기서부터 추론이 시작된다.
위 최종보스의 파란 글씨 부분을 보면 bochsrc.txt로부터 무언가를 읽어들이고 있었다. 저걸 일단 찾아서 열어보았다. build 디렉토리 안에 있는 놈이었다.
너무 쉽게 찾았군. 빨간 색에서 address 뒷부분을 지워주면 해결되겠구나.ㅋㅋㅋㅋ 당장 지우고 저장한 후 다시 실행명령을 먹였다. 결과는 동일했다. '어? 이상하다. 분명히 지웠는데' 하면서 다시 파일을 열어본 순간.. 정말 경악을 금치 못했다. 지우고 저장했던 부분이 거짓말처럼 되살아나있는 것이었다......
이후로 수십번을 그랬다. 지우고 저장하고 실행하고 안되고, 지우고 저장하고 실행하고 안되고, 지우고 저장하고 실행하고 안되고, ........... 그야말로 좀비가 따로 없었다. 최종보스답군. 미치는 줄 알았다. 이런 괴물이 또 있었나 싶었다.
이성적으로 생각해보았다. 수정하고 저장까지 완벽이 끝낸 파일이 다시 살아날 수는 없는 일. 수정한 부분이 되살아난다는건 어떤 다른 파일로부터 갱신된 정보를 받고 있는거겠지. 지우고 저장하고 다시 열어볼 때는 저장한 그대로 있었으나, 실행하고 안되고 이후에 열어볼 때는 되살아나 있었다. 실행할 때 뭔가 정보를 넘겨받는군. 결론은 실행파일을 뜯어보자!
실행파일은 /pintos/src/utils 디렉토리 안에 있는 pintos라는 놈이다.
$ sudo gedit pintos
드디어 최종보스를 물리치는 순간이었다. 추론이 맞아들어갔다. 다음은 최종보스의 내부구조이다.
# Write bochsrc.txt configuration file. open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; print BOCHSRC <<EOF; romimage: file=\$BXSHARE/BIOS-bochs-latest, address=0xf0000 vgaromimage: file=\$BXSHARE/VGABIOS-lgpl-latest boot: disk cpu: ips=1000000 megs: $mem log: bochsout.txt panic: action=fatal EOF
빨간글씨를 보면 알 수 있다. 놈은 갱신되고 있었다. address=0xf0000을 과감히 지워주고 저장. pintos의 거지와도 같은 설정파일갱신기능이 이렇게 몸고생 맘고생을 시켰다. 젠장.
_____________________
최종보스를 물리친 결과이다. 알람메시지가 출력된 모습.
여기까지가 설치 끝. 이제부터가 시작이구나;;; 지겨워지겨워. 리눅스를 쓰면 쓸수록 드는 생각은 "윈도우는 위대하다~?" 이런 시행착오를 겪으면서 점점 강해지는 것이 아닐까. 강한 최종보스를 물리쳤으니, 경험치 급상승? ㅋㅋㅋㅋ
친구 덕에 시작하게 되었지만 나에게 정말 도움이 많이 되었던 프로젝트. 제대 후 할일없어서 방바닥 긁던 나에게 목표를 설정해 준 프로젝트. 이거 때문에 완전 고생했음에도 치킨 두마리 얻어먹고 방긋 웃었던 프로젝트.ㅋㅋ
이렇게 열심히 해다 줬더니만.... 결국 기말시험을 망쳐서 'B-'를 받았다고 하더라는....^^;;
자 이제 분석을 해보기에 앞서 만에 하나 이 내용이 어느 포털 사이트 검색의 결과물로 나오더라도, 이 코드 그대로 카피해서 제출하는 일이 없기를 간절히 바랍니다. 이 프로젝트의 내용은 모 대학에서 2007년 여름학기에 실제 과제로 제출되었던 내용입니다. 따라서 추후에도 충분히 과제로 다시 나올 가능성이 있다고 봅니다. 이 허접한 프로그램의 저작권은 본 블로그의 주인에게 있습니다. 본인은 순수히 본인 또는 누군가의 학업증진을 위해 소스코드를 올린 것일뿐,, 여러분의 학점향상의 도구 역할을 하고픈 마음은 없습니다. 그래도 베끼고 싶다면 차라리 제게 연락을 하세요. 치킨 두마리면 진지하게 생각해보겠습니다.ㅋㅋㅋㅋㅋㅋ 베끼지 맙시다. 베껴서 도움되는 것은 하나도 없습니다. 당신만 베끼면 다행이지만 누군가 이걸 또 베껴서 낸다면 어떻게 되겠습니까?ㅋㅋ 공부합시다. 기본적인 내용입니다. 공부하면 이런거 금방 만들 수 있지요. =============================================================================================================================================================
이 프로그램의 간단한 구조는 다음과 같다.
main() 텍스트파일 읽기 & 메모리에 저장하기 메모리의 내용을 성적순으로 정렬하기(f_sort()) 성적과 석차에 맞게 학점 매기기 성적순으로 파일에 출력하기 중간고사와 기말고사 데이터만 따로 뽑아서 분포도 출력 f_sort() 배열을 넘겨받아 내림차순으로 정렬
간단하게 눈으로 죽 훑어본다면 "이거 별거 아니군요"라고 할 수도 있을 것이다. 나도 처음엔 '이거 치킨 두마리 벌기 쉽구나' 생각하면서 금방 끝나겠지 하는 마음으로 뛰어들었다가 세세한 부분으로 파고들면서 점점 짜증이 낫던 기억이 있다. 기본적인 구조는 쉽지만 자세히 파고 들다보면 세부적으로 사소한 것들이 문제가 생기는 경우가 있어서 그러한 부분을 하나하나 처리해주는 것이 어렵다...기 보단 좀 귀찮다. 누구야. 이런 플젝을 내주는 사람은. ㅋㅋ
핵심부분 분석.
1. 파일 입출력 * 파일 입출력은 기본내용이므로 생략(소스 참조) strtok()함수를 사용하여 문자열을 " "단위로 끊어서 필요한 문자열만 배열에 저장한다는 거 정도? atoi()함수를 사용하여 문자열로 읽어온 숫자 "23"같은 걸 실제 정수 23의 형태로 변환한다는 내용 정도? 굳이 짚고 넘어간다면 위의 두 내용 정도가 될 듯.(역시 소스 참조)
2. 성적순으로 오름차순 정렬 ** 이 오름차순 정렬 방식은 본인이 알고 있는 유일한 방법이며 사실 이보다 더 효율적인 방법도 없는거 같다....(있긴 있을것이다.) f_sort() 함수 내부를 살펴보면,,,
배열을 처음부터 끝까지 읽어가면서, 현재 값보다 뒤쪽에 있는 값이 더 클 경우 두 값의 자리를 바꿔주어 큰 값이 앞으로 오도록 하는 방식. 배열의 모든 값을 처음거부터 차례로 검사하기 때문에 빠지는 부분 없이 모든 데이터가 순차적으로 정렬된다. 더 효율적인 방법도 분명 있을거라 생각되지만, 나중에 배워야지.ㅋㅋ
3. 학점 매기기(동점자 처리) ***** 프로그램에서 제일 고생했던 부분이다. 학점은 참 민감한 부분이라는 걸 다시 느끼는구나;; 주어진 문제에서는 A 20%, B 30%, C 30%, D 15%, F 5%로 끝났고 더 신경쓸 것이 없어 보이지만, 커트라인 근처에서 동점자가 발생할 경우에는 판정이 골치가 아파진다.
동점자의 경우는 동점이 나온 학생 모두를 묶어서 학점을 주어야 한다. 예를 들어 모든 학생이 공부를 열심히 해서 모두 똑같이 100점이 나왔다면, 실제 우리가 다니는 학교에서는 "어이쿠 님하 잘했어여~"하면서 모두 A를 줄 지도 모르지만,... 적어도 이 프로그램 상에서는.... 전원 F학점이 나와야 한다. A B C D 학점을 줄 수 있는 최대 범위는 모두 합해서 95%인데, 100% 학생전원이 만점을 받았으므로,, 실제로는 들어갈 자리가 F학점 외에는 없게 되는 것. 동점자 처리를 하지 않는다면, 동점자가 발생했을 경우 입력파일에 먼저 있는 사람 순으로 석차가 매겨진다.. 그리하여 시험 점수를 동일하게 받더라도 운이 좋아서(줄을 잘서서 ㅋ) 좋은 학점을 받는 불상사가...
한참을 고민했다. 치킨 두마리가 역시 쉽지 않았다. 하지만 결국 모든 문제는 그 답이 나오기 마련.
크게 두 가지를 생각해야 한다. * 우선 동점자를 어떻게 찾아낼것인가. * 찾아낸 동점자에게 학점을 어떻게 부여할 것인가.
동점자를 찾아내는 것은 어렵지 않다. 바로 앞사람 점수와 비교해서 같으면 동점자이므로.
동점자에게 학점을 부여하는 방법은 다음과 같다. 석차순으로 쭉 나열한 다음, 석차가 높은 사람부터 학점을 준다면, 커트라인에 걸쳐있는 동점자는 높은 학점을 받게 된다 즉 학점의 최대 수용범위를 넘어서게 된다. 그러나 석차가 낮은 사람부터 학점을 준다면 즉 밑에서부터 학점을 매긴다면, 커트라인에 걸쳐있는 동점자는 자연스럽게 낮은 학점을 받게 되므로 학점 수용 범위를 지킬 수 있는것.
이거 글로 쓰려고 하니 어려운데.. 위에서 보았던 전원 100점의 경우로 예를 들어 설명하자. 석차가 높은 사람부터 학점을 주면 맨 처음 학생은 20% 안쪽에 있으므로 A학점을 주고, 그 다음 학생은 동점자이므로 똑같이 A를 주고, 그 다음 학생도 마찬가지로 A를 주고 또 A를 주고 결국 전원 A를 받게 된다. 그러나 석차가 낮은 사람부터 학점을 주면 꼴등한 학생은 95% 보다 바깥쪽에 있으므로 F를 받게 되고 뒤에서 두번째 학생은 동점자이므로 똑같이 F를 주고, 그다음 그다음 그다음 쭉 학점을 주어서 모두 F가 나오게 되는 것.
어렵구나... 라는 생각도 들 수 있지만 사실 별거 아니다.ㅋㅋ 내가 생각해낸거라면 누구나 생각해낼수 있는것. 해당 부분의 코드를 보면 "뭐야 님하 이게 끝이야?"라는 생각이 들 정도로 간단한 부분.
//계산식을 따로 정의. 공식을 수정할 필요가 생길 경우 이 부분을 수정해주면 된다. #define f(i) (hw_1[i]/10)*5+(mid_term[i]/100)*30+(hw_2[i]/10)*10+(final[i]/100)*40+(project[i]/20)*15 #define MAX_STUDENT 100
void f_sort(char name[100][20], double score[], int num);
/*****************중간고사 데이터 출력********************/
if((fp=fopen("midHisto.txt", "w"))==NULL) { printf("\"midHisto.txt\" 파일을 생성하는데 오류가 있습니다. 프로그램을 종료합니다.\n"); exit(1); } printf("\"midHisto.txt\" 파일 생성...");
/*****************기말고사 데이터 출력********************/
if((fp=fopen("finalHisto.txt", "w"))==NULL) { printf("\"finalHisto.txt\" 파일을 생성하는데 오류가 있습니다. 프로그램을 종료합니다.\n"); exit(1); } printf("\"finalHisto.txt\" 파일 생성...");
============================================================================================================================================================= 쓰고보니 글이 참 길다. 읽는 사람이나 있는지 모르겠군.ㅋㅋㅋ