크래프톤 정글 TIL : 0903

lazyArtisan·2024년 9월 3일
0

정글 TIL

목록 보기
65/147

🖥️ PintOS

🔷 User Programs

1. 깃북 읽기


https://www.notion.so/yjohdev/PROJECT-2-USER-PROGRAM-b019874b02f645d7813c554bd7377770

📌 Introduction

또한 프로젝트2에서 여분의 과제가 있습니다. 이걸 구현하는 건 선택입니다. 이 여분의 과제에 대해서는 테스트 케이스를 제외하고는 기본 코드가 제공되지 않아요. 어떤 구조로 만들어갈지는 여러분들 마음입니다. 이 여분의 과제를 제출하고 테스트 하고 싶다면 userprog/Make.vars 파일을 수정해야 합니다.

extra 통과하려면 make 파일 수정해라

Background

지금까지 여러분이 pintos 에서 실행한 모든 코드는 OS 커널의 한 부분이었습니다. 예를 들어 지난 과제에서 제출한 코드들은 커널의 일부로서 시스템에 중요한 부분에 접근할 수 있는 특권을 가지고 실행되었던 것입니다. 우리가 OS위에서 작동하는 유저 프로그램을 실행하기 시작하면 더 이상 그런 특권은 없을 것입니다. 이번 프로젝트는 이런 상황을 다뤄야 합니다.

시스템 콜 쓰라는 뜻

우리는 한번에 하나 이상의 프로그램이 실행될 수 있도록 할 겁니다. 각각의 프로세스들은 하나의 쓰레드를 가집니다. (멀티쓰레드 프로세스는 지원하지 않아요) 유저 프로그램들은 자신들이 컴퓨터 전체를 소유한다는 환상 하에 쓰여집니다. 이는 여러분들이 한 번에 여러 프로세스들을 로드하고 실행할 때, 여러분은 메모리, 스케쥴링, 그 외 다른 상태들이 프로세스가 가진 이 환상을 만족시키는 방향으로 관리해야 한다는 것을 의미합니다.

가상 메모리랑 대충 뭐 그런거

이전 프로젝트에서, 우리는 테스트 코드를 커널에 바로 컴파일했기에, 커널안에서 상호작용하는 특정 함수들을 필요로 했었습니다. 지금부터 우리는 유저프로그램을 실행함으로서 여러분의 운영체제를 테스트할 것입니다. 여러분은 유저프로그램 인터페이스가 이 문서에 적힌 규칙(스펙)을 만족시키도록 해야 합니다. 이 규칙만 지킨다면 원하는대로 커널 코드의 구조를 바꾸거나 다시 짜도 됩니다. 여러분의 코드들은 절대로 #ifdef VM로 감싸진 코드 블록내에 위치해서는 안됩니다. 이 코드 블록은 가상메모리의 하부 시스템을 가능하게 한 (여러분이 프로젝트 3에서 구현할 부분) 이후에 포함될 것이기 때문입니다. 또한 코드가 #ifndef VM로 감싸져있다면 이 코드들은 프로젝트 3에서는 빠지게 될 것입니다.

시스템 콜을 만들어야 된다는 건가?

Source Files

가장 쉽게 앞으로 해야할 프로그래밍 작업의 개요를 보려면, 앞으로 작업할 부분들을 훑어보세요. userprog 디렉토리에는 파일이 많지는 않지만(9개 있군요) 여기서 많은 일을 해야 할겁니다.

  • process.c, process.h : ELF 바이너리(=ELF 실행파일)들을 로드하고 프로세스를 실행합니다 (ELF: ELF는 많은 운영체제에서 목적 파일, 공유 라이브러리, 그리고 실행 파일들을 위해 사용되는 파일 포맷)

  • syscall.c, syscall.h : 유저 프로세스가 일부 커널 기능에 접근하려고 할때마다 시스템 콜이 호출됩니다. 이게 시스템 콜 핸들러의 기본 구조입니다. 현재 상태에서는 이때 단지 메세지를 출력하고 유저 프로세스를 종료시키게 되어있습니다. 이번 프로젝트의 part2에서 시스템 콜이 필요로 하는 다른 일을 수행하는 코드를 수행하게 될 겁니다.

  • syscall-entry.S : 시스템 콜 핸들러를 부트스트랩하는 어셈블리 코드입니다. 이 코드를 이해하실 필요는 없어요. (부트스트랩 프로그램: 전원을 켜거나 재부팅을 할 때 적재되는 프로그램)

  • exception.c, exception.h : 유저 프로그램이 특별한 접근 권한을 필요로 하거나 금지된 연산을 수행할 때, 이는 exception 또는 fault로 커널 내로 트랩합니다. exception.c, exception.h 파일들은 예외사항을 처리합니다. 현재 모든 예외사항들은 단지 메세지를 출력하고 프로세스를 끝내고 있습니다. 프로젝트2에 대한 일부 해결책은 이 파일 내에 있는 page_fault()를 수정하는 것입니다. (응용프로그램이 시스템 콜을 호출하면 하드웨어는 ‘트랩 핸들러’를 실행하여 하드웨어 특권 수준을 커널모드로 격상시킴. 커널모드에서 운영체제는 시스템 하드웨어를 자유롭게 접근할 수 있음)

  • gdt.c, gdt.h : x86-64는 segmented 아키텍쳐입니다. Global Descriptor Table (GDT)는 사용중인 세그먼트들을 알려주는 표입니다. gdt.c, gdt.h 파일들은 GDT를 셋업합니다. 앞으로의 어떤 프로젝트에서도 이 파일들을 수정할 필요는 없습니다. GDT가 어떻게 작동하는지에 대해 궁금하다면 읽어보시면 됩니다.

  • tss.c, tss.h : Task-State Segment (TSS)는 x86 아키텍쳐의 문맥교환에 사용됩니다. 하지만 x86-64에서 문맥교환(context switching = task switching)은 지원이 중단된 기능입니다. 그래도 TSS는 여전히 ring switching 동안 스택 포인터를 찾아내기 위해 사용되고 있습니다. 이는 유저 프로세스가 인터럽트 핸들러에 진입할 때, 하드웨어는 tss에게 커널의 스택 포인터를 찾아달라고 요청한다는 의미입니다. 앞으로의 어떤 프로젝트에서도 이 파일들을 수정할 필요는 없지만 어떻게 TSS가 동작하는지 궁금하다면 읽어보시면 됩니다.

이게 뭔 소리임 context switching이 없어졌다니

질문하신 내용에서 언급된 context switching이 없어졌다는 말은 약간의 오해가 있을 수 있습니다.

x86 아키텍처에서는 Task-State Segment (TSS)를 이용해 CPU 레벨에서 자동으로 태스크 간 전환을 수행할 수 있는 하드웨어 기능을 지원했습니다. 이 기능은 태스크의 실행 상태를 저장하고, 다음 태스크로 전환하는 작업을 CPU가 직접 수행할 수 있게 했습니다.

그러나 이 기능은 x86-64 아키텍처에서 성능이나 복잡성 문제로 인해 더 이상 사용되지 않습니다. 이 말은 하드웨어가 자동으로 수행하는 태스크 스위칭 기능이 더 이상 제공되지 않는다는 것입니다.

Context Switching은 운영체제 수준에서 프로세스나 스레드의 실행 상태를 저장하고, 다른 프로세스나 스레드를 실행시키는 일반적인 과정입니다. 이는 여전히 x86-64 아키텍처에서도 소프트웨어적으로 구현되어 사용됩니다.

x86-64 아키텍처에서 TSS는 주로 인터럽트 처리 시에 사용됩니다. 예를 들어, CPU가 유저 모드에서 커널 모드로 전환될 때, TSS는 커널 스택 포인터를 제공합니다.

즉, TSS는 더 이상 태스크 스위칭을 위해 사용되지 않고, 오직 특수한 상황(예: 인터럽트 처리)을 위해 사용됩니다.


원문 보니까 context switching이라는 말 없고 task switching이라고만 돼있었음. 번역이 편하긴 한데 오역 당하니까 무섭네.

Using the File System

이번 프로젝트에서는 파일 시스템 코드와 인터페이스해야 합니다. 왜냐하면 유저 프로그램이 파일 시스템으로부터 로드되기도 하고, 여러분들이 구현해야 할 시스템 콜들이 파일 시스템을 다루기 때문입니다. 하지만 이번 프로젝트의 중점 과제는 파일 시스템은 아니므로, 저희가 간단하지만 완전한 파일 시스템을 filesys 디렉토리에 제공하고 있습니다. 파일 시스템을 사용하는 방법과 파일 시스템의 수많은 제약 사항들을 이해하고 싶다면 filesys.h 과 file.h 인터페이스를 살펴보세요.

이번 프로젝트에서 이 파일 시스템 코드를 수정할 필요도 없고, 수정하지 않기를 권합니다. 파일 시스템에 정신을 뺏기지 마시고 이번 프로젝트의 중점 과제에 집중하세요.

쓰긴 하는데 수정하지 말라는 뜻

파일 시스템 루틴을 적절히 사용하는 것은 파일 시스템 구현을 향상시켜야 하는 프로젝트 4에서의 여러분들의 삶을 훨씬 쉽게 만들어줄 겁니다. 그 전까지는 아래의 제한 사항들을 꼭 지켜주셔야 합니다.

  • 내부 동기화를 하지마세요. 동시 접근은 서로를 방해할 겁니다. 동기화는 한번에 하나의 프로세스만이 파일 시스템 코드를 실행한다는 걸 보장하기 위해서만 사용하세요.
  • 파일 사이즈는 파일이 생성될 때 고정됩니다. 루트 디렉토리가 파일이기 때문에 생성될 수 있는 파일의 갯수들도 제한되게 됩니다.
  • 파일의 데이터는 single extent로 할당됩니다. 즉, 한 파일의 데이터는 디스크 섹터를 연속적으로 차지해야 합니다. 그래서 파일시스템이 사용되고 시간이 흐르면서 외부 단편화가 큰 문제가 될 수 있습니다.
  • 하위 디렉토리들을 만들지 마세요.
  • 파일 이름은 14자로 제한됩니다.
  • 연산 중에 (디스크에 읽고 쓰는 중에) 시스템 crash가 발생하면 자동으로 복구 되지 못하는 방식으로 디스크를 망쳐버릴 수 있습니다. 파일 시스템 복구 툴 같은 것은 어차피 없습니다.

모든 테스트 프로그램이 커널 이미지로 존재했던 프로젝트1과 달리, 당신은 반드시 테스트 프로그램(유저 공간에서 돌고있는)을 핀토스 가상 공간에 넣어줘야 합니다. 과제를 조금 쉽게 하기 위해 , make check 와 같은 테스팅 스크립트들이 자동적으로 위 과정을 다루며, 대부분 경우에 당신은 이 과정을 이해할 필요는 없습니다. 그러나, 이 과정을 이해한다면 각 테스트 케이스를 돌려볼 때 도움이 될 것입니다.

pintos 가상 머신에 파일을 넣기 위해서 당신은 먼저 파일 시스템 파티션이 있는 모의 디스크(simulated disk)를 만들 수 있어야 합니다. pintos-mkdisk 프로그램은 이 기능을 제공합니다. userprog/build 디렉토리에서 pintos-mkdisk filesys.dsk 2를 실행합니다. 이 명령어는 2MB 크기의 pintos 파일 시스템 파티션 하나를 포함하는 filesys.dsk라는 모의 디스크(simulated disk)를 만듭니다. 그리고 pintos 명령어 뒤에 —-fs-disk filesys.dsk를 적어줌으로써 어떤 디스크인지 지정합니다. —fs-disk는 모의 커널(simulated kernel)이 아니라 pintos script를 위한 옵션이므로 --를 써줘야 합니다. (가령,  pintos --fs-disk filesys.disk -- KERNEL_COMMANDS...)그 이후에 커맨드 라인에 -f -q 옵션을 적어줘서 파일 시스템 파티션을 포맷팅합니다. (가령, pintos SCRIPT_COMMANDS -- -f -q). 파일 시스템이 포맷팅하기 위해서 -f 옵션을 쓰고, -q 옵션은 핀토스가 포맷팅이 끝나면 바로 끝나도록 합니다.

뭔 소린지 모르겠. 직접 해봐야 알듯.

How User Programs Work

핀토스는 메모리에 적합하고 당신이 구현한 시스템 콜만 사용하는 보통의 C 프로그램을 실행시킬 수 있습니다. 이 프로젝트에서 쓰이는 어느 시스템 콜도 메모리 할당을 허락하지 않기 때문에 malloc()은 구현될 수 없습니다. 핀토스는 또한 부동소수점 연산을 사용하는 프로그램을 실행시킬 수 없습니다. 왜냐하면 커널은 문맥 교환이 일어날 때 프로세서의 부동 소수점을 저장하고 복구하지 않기 때문입니다.

malloc()이랑 부동소수점 연산 쓰지 말라고 함

핀토스는 userprog/process.c에 제공된 로더로 ELF 실행 파일을 로드할 수 있습니다. ELF는 linux, solarix 등의 운영체제에서 목적 파일, 공유 라이브러리, 그리고 실행 파일들을 위해 사용되는 파일 포맷입니다.

process.c로 실행 파일 로드하라고 함

Virtual Memory Layout

핀토스의 가상메모리는 2개의 영역으로 나눌 수 있습니다. 1) 유저 가상 메모리 2) 커널 가상 메모리. 유저 가상 메모리는 가상 주소 0부터 KERN_BASE까지의 범위를 가집니다. KERN_BASE는 include/threads/vaddr.h에 정의되어 있고 기본적으로 0x8004000000 입니다. 커널 가상 메모리는 가상 주소 공간의 나머지를 차지합니다.

유저 스택 커널 스택?

유저 프로그램은 자신의 유저 가상 메모리에만 접근할 수 있습니다. 유저 프로그램이 커널 가상 메모리에 접근하려는 시도는 page fault를 야기하고 프로세스는 종료됩니다. page fault는 userprog/exception.c에 있는 page_fault()라는 함수에 의해 이루어집니다. 한편, 커널 스레드들은 커널 가상 메모리에 접근 가능하고 만일 유저 프로세스가 running 상태라면 이 유저 프로세스의 유저 가상 메모리에도 접근할 수 있습니다. However, even in the kernel, an attempt to access memory at an unmapped user virtual address will cause a page fault.

유저 프로그램은 커널 가상 메모리에 접근 못하고
커널 스레드들은 커널 가상 메모리와 유저 가상 메모리에 둘 다 접근 가능

Accessing User Memory

시스템 콜의 일부로서, 커널은 유저 프로그램이 제공한 포인터들을 통해 메모리에 자주 접근해야 합니다. 유저가 null 포인터나 매핑되지 않은 가상 메모리를 가리키는 포인터, 혹은 커널 가상 메모리 공간(KERN_BASE 위에 있는 공간)을 가리키는 포인터를 전달할 수 있기 때문에 커널은 매우 신중해야 합니다. 잘못된 포인터들은 커널과 다른 running 프로세스들에게 아무 악영향을 미치지 않고 거부되어져야 하고, 이를 위해서 문제가 되는 프로세스를 종료시키고 그 프로세스의 자원들을 해제해야 합니다.

포인터가 이상한 메모리 주소 가리키면 안됨

유저가 전달한 잘못된 포인터에 문제 없이 잘 대응하기 위해서는 적어도 2개의 합리적인 방법이 있습니다. 첫 번째 방법은 유저가 전달한 포인터에 문제가 없는지 검사한 후에 역참조하는 것입니다. 만일 당신이 이 방법을 선택한다면, thread/mmu.c 와 include/threads/vaddr.h에 있는 함수들을 살펴보세요. 이 방법이 유저 메모리 접근을 가장 쉽게 처리할 수 있는 방법입니다.

포인터에 문제 없는지 검사

두 번째 방법은 유저가 전달한 포인터가 KERN_BASE보다 아래 부분을 가리키고 있는지 검사한 후에 역참조하는 것입니다. 잘못된 유저 포인터는 당신이 userprog/exception.c의 page_fault()함수 코드를 수정함으로써 당신이 page fault를 발생시킵니다. 유효하지 않은 유저 포인터는 페이지 폴트를 발생시킬 있고, 여러분이 userprog/exception.c 파일에 있는 page_fault() 함수의 코드를 수정함으로써 페이지 폴트를 다룰 수 있습니다. 이 방법은 프로세서의 MMU를 활용하기 때문에 보통은 더 빠르고, 리눅스를 포함한 실제 커널에서도 많이 사용됩니다.

KERN_BASE보다 아래인지 확인

두 가지 방법 모두에서 당신은 리소스가 “누수되지” 않도록 해야 합니다.

예를 들어 시스템 콜이 락을 얻었거나, malloc()으로 메모리를 할당받았다고 가정해봅시다. 만약 올바르지 못한 유저 포인터를 맞닥뜨리게 된다 해도, 여전히 락을 풀 수 있어야 하고 메모리 페이지는 free 되어야 합니다.

유저 포인터가 가리키는 데이터를 역참조하기 전에 포인터를 검증하는 방법을 택했다면, 좀 수월할 겁니다.

하지만 두번째 방법은 다루기가 조금 더 까다로운데, 잘못된 포인터가 페이지 폴트를 발생시키게 되면, 메모리 접근으로 에러코드를 리턴하는 것이 불가능하기 때문에 그렇습니다. 그러므로 후자(두 번째)의 방법을 택하기로 했다면, 저희가 약간 도움이 될만한 코드를 제공해 드리겠습니다 :

코드는 나중에 필요하면 보면 될듯.

📌 Argument Passing

process_exec() 함수에 있는 “유저 프로그램”을 위한 인자를 세팅하기

x86-64 Calling Convention (x86-64 시스템에서의 호출 규약)

호출 규약은 다음과 같습니다 :

  1. 유저-레벨 어플리케이션은 %rdi%rsi%rdx%rcx%r8%r9 시퀀스들을 전달하기 위해 정수 레지스터를 사용합니다.
  2. 호출자는 다음 인스트럭션의 주소(리턴 어드레스)를 스택에 푸시하고, 피호출자의 첫번째 인스트럭션으로 점프합니다. CALL 이라는 x86-64 인스트럭션 하나가 이 두 가지를 모두 수행합니다.
  3. 피호출자가 실행됩니다.
  4. 만약 피호출자가 리턴 값을 가지고 있다면, 리턴 값은 레지스터 RAX에 저장됩니다.
  5. 피호출자는 x86-64 인스트럭션인 RET (리턴)를 사용해서, 스택에 받았던 리턴 어드레스를 pop하고 그 주소가 가리키는 곳으로 점프함으로써 리턴됩니다.

어셈블리 관련이라 내가 건드리는 건 아닌 거 같은데

Program Startup Details (프로그램 시작과 관련된 디테일들)

유저 프로그램을 위한 Pintos C 라이브러리는 _start() 함수를 유저 프로그램의 시작 포인트로 지정합니다. _start() 는 lib/user/entry.c 에 있습니다.

여기서 _start()는 main() 함수를 감싸고 있는 함수입니다. main() 은 리턴되면서 exit()을 호출하게 됩니다.

  1. 명령어를 단어로 분리하기:
    명령어 /bin/ls -l foo bar를 실행할 때, 이 명령어를 개별적인 단어로 나눕니다. 결과적으로 /bin/ls, l, foo, bar라는 네 개의 단어로 나눠집니다.

  2. 스택의 맨 위에 단어를 배치하기:
    이 네 개의 단어를 메모리의 스택 영역에 배치합니다. 여기서 순서는 중요하지 않으며, 나중에 포인터(메모리 주소)를 통해 접근하게 됩니다.

  3. 각 단어의 주소와 null 포인터를 스택에 저장하기:
    각 단어의 메모리 주소를 스택에 저장합니다. 단어들의 주소는 오른쪽에서 왼쪽 순서로 저장됩니다. 마지막에는 null 포인터를 넣어줍니다. 이 null 포인터는 C 언어의 표준에 따라 argv[argc]가 null이 되도록 보장해 줍니다.
    이렇게 하면 argv[0]이 가장 낮은 메모리 주소에 위치하게 됩니다. 그리고 메모리 접근 성능을 최적화하기 위해 스택 포인터를 8의 배수로 정렬합니다.

  4. %rsi와 %rdi 레지스터 설정:
    %rsi 레지스터에 argv (즉, argv[0]의 주소)를 가리키도록 설정합니다.
    %rdi 레지스터에는 argc (인자의 개수)를 설정합니다.

  5. 가짜 "리턴 주소" 푸시:
    마지막으로, 스택에 가짜 "리턴 주소"를 푸시합니다. 이 리턴 주소는 실제로 사용되지 않지만, 함수 호출 시 스택 프레임의 구조를 맞추기 위해 필요합니다. 이렇게 함으로써, 시스템이 이 함수를 호출할 때 다른 함수와 동일한 스택 구조를 가질 수 있게 됩니다.

Implement the argument passing. (인자 전달의 구현)

현재, process_exec() 함수는 새로운 프로세스들에 인자를 전달하는 것을 지원하지 않습니다.

process_exec() 함수를 확장 구현해서, 지금처럼 단순히 프로그램 파일 이름만을 인자로 받아오게 하는 대신 공백을 기준으로 여러 단어로 나누어지게 만드세요.

첫 번째 단어는 프로그램 이름이고, 두 번째 단어는 첫 번째 인자이며, 그런 식으로 계속 이어지게 만들면 됩니다.

따라서, 함수 process_exec("grep foo bar") 는 두 개의 인자 foo와 bar을 받아서 grep 프로그램을 실행시켜야 합니다.

커맨드라인에서, 여러개의 공백은 하나의 공백과 같게 처리해야 합니다. 그러므로 process_exec("grep foo bar")는 위의 원본 예시와 동일하게 동작해야 합니다.

또한 당신은 납득할만한 수준에서 커맨드라인 인자들의 길이 제한을 강요할 수 있습니다. 예를 들면, 인자들이 한 페이지 크기(4kB) 안에 들어가게끔 제한하는 것입니다. (그리고 직접적인 관계는 없지만, pintos 유틸리티가 커널에 전달할 수 있는 커맨드라인 인자 크기는 128바이트로 제한되어 있습니다.)

인자로 들어온 문자열은 당신이 원하는대로 파싱하면 됩니다.

만약 어떻게 해야 할 지 모르겠다면, include/lib/string.h에 있는 strtok_r()함수와, lib/string.c 에 구현된 것들을 참고해보세요. 근데 이제 면밀한 주석을 곁들인…

man 페이지를 보면 더 많은 정보를 볼 수 있습니다. (프롬프트 창에 man strtok_r 을 실행하면 man 페이지를 볼 수 있어요)

위도 봐야겠지만 일단 구현은 이게 제일 중요한듯

2. 일단 시작


FAIL tests/userprog/args-none
FAIL tests/userprog/args-single
FAIL tests/userprog/args-multiple
FAIL tests/userprog/args-many
FAIL tests/userprog/args-dbl-space
FAIL tests/userprog/halt
FAIL tests/userprog/exit
FAIL tests/userprog/create-normal
FAIL tests/userprog/create-empty
FAIL tests/userprog/create-null
FAIL tests/userprog/create-bad-ptr
FAIL tests/userprog/create-long
FAIL tests/userprog/create-exists
FAIL tests/userprog/create-bound
FAIL tests/userprog/open-normal
FAIL tests/userprog/open-missing
FAIL tests/userprog/open-boundary
FAIL tests/userprog/open-empty
FAIL tests/userprog/open-null
FAIL tests/userprog/open-bad-ptr
FAIL tests/userprog/open-twice
FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice
FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-normal
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-boundary
FAIL tests/userprog/read-zero
FAIL tests/userprog/read-stdout
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero
FAIL tests/userprog/write-stdin
FAIL tests/userprog/write-bad-fd
FAIL tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
FAIL tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
FAIL tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
FAIL tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
FAIL tests/userprog/wait-bad-pid
FAIL tests/userprog/multi-recurse
FAIL tests/userprog/multi-child-fd
FAIL tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
FAIL tests/userprog/bad-read
FAIL tests/userprog/bad-write
FAIL tests/userprog/bad-read2
FAIL tests/userprog/bad-write2
FAIL tests/userprog/bad-jump
FAIL tests/userprog/bad-jump2
FAIL tests/filesys/base/lg-create
FAIL tests/filesys/base/lg-full
FAIL tests/filesys/base/lg-random
FAIL tests/filesys/base/lg-seq-block
FAIL tests/filesys/base/lg-seq-random
FAIL tests/filesys/base/sm-create
FAIL tests/filesys/base/sm-full
FAIL tests/filesys/base/sm-random
FAIL tests/filesys/base/sm-seq-block
FAIL tests/filesys/base/sm-seq-random
FAIL tests/filesys/base/syn-read
FAIL tests/filesys/base/syn-remove
FAIL tests/filesys/base/syn-write
FAIL tests/userprog/no-vm/multi-oom

우선순위 관련 테스트도 있었는데 이상하게 fail로 뜸.
일단 앞에것들부터 다 해보고 안되면 그때 문제 파악하는걸로?

FAIL tests/userprog/args-none
FAIL tests/userprog/args-single
FAIL tests/userprog/args-multiple
FAIL tests/userprog/args-many
FAIL tests/userprog/args-dbl-space

이것들부터 하면 될 것 같다

tests/args.c 확인

시작부터 테스트 안됨

pintos-mkdisk filesys.dsk 10
pintos --fs-disk filesys.dsk -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

이거 복붙했더니

os.dsk cannot be temporal. 라고 하면서 테스트 안 해줌
깃북에 있는거 그대로 베낀건데. 깃북 다시 보니까

It assumes that you've already built the test cases and that the current directory is userprog/build

라고 돼있음. 뒤에건 봤는데 built the test cases 부분을 놓침. userprog 디렉토리에서 make check 한 뒤에 다시 했더니 돌아는 감.



⚔️ 백준


📌 1005 ACM Craft

from collections import deque
T = int(input())
for _ in range(T):
    N, K = map(int,input().split())
    time = 0
    time_table = list(map(int,input().split()))
    build_table = {i: [] for i in range(N+1)}
    for _ in range(K):
        a, b = map(int,input().split())
        build_table[b].append(a) # 역순으로 탐색
    win = int(input())
    q = deque()
    q.append(win)
    while q:
        b = q.popleft()
        time += time_table[b-1] # 걸린 시간을 더해주고
        for next_b in build_table[b]: # 다음 건물 탐색
            q.append(next_b)
    print(time)

단순히 역순으로 탐색하고 time table에서 각각 더해주면 되는거 아닌가
생각했는데, 필요 조건이 있어서 그러면 안됐음.

만들면 time table 0으로 놓는 것도 생각해냈는데 일단 내일 마저 풀기로 함

0개의 댓글