내부적으로 배열을 사용하여 조회,삽입,삭제 모두 O(1)안에 수행하기 위한 특별한 자료구조다. 배열의 인덱스를 유일하게(혹은 그에 가깝게) 지정하기 위해서 데이터와 연관된 고유한 숫자를 만들어낸 후 그것을 인덱스로 사용한다.

또 일반적으로 순서를 보장하지 않기 때문에, 순서, 관계가 있는 목적에는 적합하지 않다.

Hash function

데이터에 연관된 고유한 값을 만들기 위해서 해시 함수를 사용한다. 이 해시 함수를 통해서 나온 결과값을 해시 값(혹은 해쉬 코드,해쉬)라고 하고 이것을 이용해 데이터에 대한 접근 연산을 수행한다.

가장 많이 쓰이는 해시 함수는 나머지 연산(modulo)를 이용한다. 키 k 를 어떤 정해진 수 D 로 나눈 나머지를 k 를 저장하는 버킷의 인덱스로 사용하는 것이다.
h(k)=k

일반적으로 D 는 적절히 큰 소수(prime number)를 사용하는데 이유는 다음과 같다.

만약 D를 소수가 아닌 값이라 하면, D의 모든 약수는 자신의 배수가 곧 키값이 된다. 해시충돌이 많이 일어나는것이다.

만약 이 해시 함수가 엄밀하지 못해서 여러개의 객체가 서로 같은 값을 가지게 된다면 이것을 해시 충돌(hash collision)이라고 한다.

일반적인 경우에서 가능한 키들의 집합을 U라고 하고, 버킷들의 개수를 m이라고 할때 U>>m인 경우가 대부분이므로 충돌은 필연적으로 발생한다. 이것을 해결하기 위해서 버킷의 사이즈를 단순히 키우는것은 좋은 해결책이 아니다. 메모리 사용량에서 치명적이다.

좋은 해시 함수를 고안해도, 여전히 해시 충돌은 불가피하다. 해시충돌이 늘어나게되면 O(1)의 시간복잡도 장점을 잃어버리고 O(n)에 가깝게 되니, 적절한 해결책을 세워야 한다.

Open Addressing

개방주소법(Open Addressing)은 간단히 말해서 해시충돌이 발생하면(계산된 인덱스로 접근한 버킷이 이미 사용중이면) 단순히 다른 인덱스에 데이터를 저장하는 방식이다.

개방주소법 안에서도 여러개로 나뉜다.

  • Linear Probing
    • 계산된 해시값에서 해시충돌이 발생한다면, 고정폭만큼 건너뛰어 비어있는 해시에 저장하는 방법이다. 만약 그 자리에도 차있다면, 그 다음 고정폭의 위치를 탐색한다.
    • 이 방법은 단순해서 계산을 하기 쉽지만, 최악의 경우 탐색을 시작한 위치까지 돌아오게 되어 종료할 수 있다. O(n)이 걸리는 것이다.
    • 또 primary clustering이라는 특정 해쉬 값 슬롯 근처에 값들이 뭉치게 되는 문제도 생길 수 있다. x 라는 해쉬 값을 공유하는 객체들이 x+1,x+2,x+3 등으로 모이기 때문이다.
    • 클러스터의 크기가 커질수록, 비슷한 해쉬값들이 적절히 배치되지 못하고 다음을 probing하니 클러스터가 더 빠르게 자라나고*, 이는 성능적으로 이슈를 불러일으킨다.
    • 다만 값들이 클러스터링 되어있기 때문에 cache hit 적인 측면에서는 유리하다. 처음 키에 대해서 접근을 하면 다음 키도 캐쉬에 올라와 있기 때문이다.
  • Quadratic Probing
    • Linear Probing과 비슷하게 , 해시충돌이 발생한다면 다음 슬롯을 찾는다. 다른 점은 idx=(hash(key)+i^2) mod m 의 꼴을 취하는 것이다.
    • 이 방법도 primary clustering보다는 덜 하지만 성능에 영향을 주는 secondary clustering 문제를 일으킨다.
    • 초기 hash 포지션이 아닌 좀 더 광범위하게 퍼져있는 것이다.
  • Double hashing
    • 이름 그대로 해시 충돌이 생기면, 2차 해시함수를 이용해서 다시 해시를 하는 방법.
    • 값이 퍼지게 되어서 캐쉬의 측면에서는 비효율적이고 연산량이 많이 들지만, 클러스터링에는 큰 영향을 받지 않는다.

장점과 단점

이처럼 개방주소법 내에서도 여러가지 충돌 처리방식이 있다. 일반적으로 개방주소법은 적은 양의 데이터에는 효과를 보이고 메모리 효율도 분리연결법에 비해 상대적으로 좋고, 메모리 할당에 대한 오버헤드도 없는 편이다.

또 일반적으로 연결리스트를 사용하는 분리연결법에 비하여 캐쉬 효율이 좋기 때문에 (특히 Linear Probing) Python에서 hashtable을 구현할때 사용된다.

하지만 데이터의 삭제에서 좋지 않은 퍼포먼스를 보인다.
예를 들어 A,B,C 가 연속적으로 있을때(linear probing) A 를 삭제한다고 해보자. 그럼 NULL,B,C 라고 변경될텐데, 이때 C 에 대해서 조회를 한다면, NULL 을 만나게 된다. 이것을 원래부터 비어있는 공간인지 혹은 삭제되어서 비어있는 공간인지 알 수 없기 때문에 C 를 조회하지 못하고 탐색이 종료된다.
이를 극복하기 위해서 삭제된 공간은 삭제되었음을 표시해주는 DEL 같은 표기자를 사용해 다음 index를 조회할수 있게끔 해야한다.
물론 이러한 DEL 표시자가 늘어난다면, 조회할 데이터가 없어도 계속적인 탐색을 수행해줘야 하니 표시자의 개수에 따라 해시테이블 전체에 대해서 rehashing을 해줘야 한다.

load factor를 l이라고 하였을때 삽입과 조회, 삭제 모두 O(\frac{1}{1-l})의 성능을 보여준다.

Seperate Chaining

분리연결법(Separate Chaining)은 일반적인 상황에서 개방주소법보다는 빠른데, 개방주소법의 경우 load factor가 커질수록 최악의 경우( O(n))의 발생 빈도가 높아지기 때문이다.

분리연결법은 해시충돌이 잘 발생하지 않게끔 하기 위해서 보조 해시 함수를 이용해 최악의 경우로 가는 상황을 줄이려고 한다.

분리연결법에도 두가지 방법이 존재한다.

  • Linked List
    • 각각의 버킷들을 연결리스트로 두어 충돌이 발생하면 해당 버킷의 리스트에 추가하는 방식.
    • 단, 연결리스트의 단점을 그대로 가지고 있다. 메모리 주소 상에서 연속적이지 않기 때문에 캐시의 효율이 나쁘고, 아주 적은 데이터가 있을때의 메모리 오버헤드가 있다.(개방주소법과 비교해서)
    • 또 Traverse를 할 때 최악의 경우에는 O(n)의 시간복잡도를 보인다.
  • Tree
    • 연결리스트의 단점을 개선하기 위해 나온 것으로 연결리스트가 아닌 Tree 구조를 이용해 데이터를 저장한다.
    • 단, Tree에서도 데이터가 편향되게 들어오면 O(n)의 시간복잡도를 가질 수 있으니 Red-black Tree와 같은 Balanced Binary Tree를 사용함으로써 O(logn)의 연산을 보장시킨다.
    • 하지만 적은 데이터 수에서 RB Tree를 유지하는데 드는 메모리 사용량이 연결리스트보다 크니, 적은 수의 데이터보다는 어느정도 데이터가 들어왔을때 연결리스트에서 트리로 전환한다.
    • Java 8에서부터는 데이터가 8개가 넘어가면 트리로 전환하고, 6개가 되면 다시 연결리스트로 전환한다. 두개의 차이가 2가 나는 이유는 데이터의 잦은 삽입,삭제로 1개단위로 전환하게 되면 오버헤드가 더 크기 때문에 일정 수준을 유지하는것이다.
    • AVL 트리도 균형이진트리인데 사용하지 않는 이유는, 일반적으로 hashtable 같은 경우 데이터의 조회만 intensive하게 일어나지 않기 때문에, AVL 트리를 사용하면 rotation과 같은 balance를 유지하는데 드는 오버헤드가 너무 크다.
    • 이에 반해 RB 트리는 조금 더 느슨하게 균형을 유지함으로써 조회,삽입,삭제에 평균적으로 좋은 퍼포먼스를 보여주기 때문에 hashtable의 내부 자료구조로 사용되는 것이다.

장점과 단점

분리연결법은 load factor에 크게 민감하게 반응하지 않아도 된다. 일반적으로 개방주소법에서 load factor가 커지면 성능이 기하급수적으로 나빠지는것에 비해서
분리연결법은 조금 linear한 나쁜 성능을 보여준다.

또 개방주소법에서는 hash table의 resize가 필연적으로 일어나게 되는데, 이것은 O(m) , (m은 key의 개수)의 시간복잡도를 요구하니 꽤 치명적이다.
하지만 분리연결법에서는 하나의 버킷에 대해 지속적으로 사용하기 때문에 테이블의 확장이 개방주소법보다는 더디게 일어나는 편이다.

다만 일반적으로 개방주소법에서 버킷들의 캐시 효율이 좋은 반면 분리연결법은 링크드리스트나 트리를 이용하기에 캐시의 효율이 좋지 않다.

해시 테이블 자체의 단점

데이터가 pseudo-random 위치에 저장되기 때문에, 데이터를 정렬된 순서로 접근하는 것에 있어서 엄청난 비용이 발생한다. Self-balancing binary tree와 같은 자료구조에서는 O(logn)의 조회를 보장해 조금 느리고 구현이 더 복잡하지만 데이터는 정렬되어 있다.

또 데이터를 loop하면서 traverse하는 능력도 떨어지는데, 데이터가 흩뿌려질(산재된) 확률이 높은 해쉬테이블의 특성상 빈 슬롯도 모조리 체크해가면서 순회해야 하기 때문이다.

일반적으로 해시 테이블은 지역참조성에 취약한데, 해시 테이블의 조회 자체가 버킷들을 건너띄면서 확인하는 방식이기 때문이다. 그렇기에 프로세스가 계속해서 캐시 미스를 발생시키고 이는 오버헤드로 이어진다. 데이터가 적고 type이 간단한(Integer...) 경우에는 배열을 이용한 자료구조가 더 나은 성능을 보일 수 있다.

'자료구조' 카테고리의 다른 글

B 트리  (0) 2022.09.18
Stack과 Queue  (0) 2022.09.18
Array와 Linked List  (0) 2022.09.18
Hash Table에 대해서 완전 자세하게 알아보자.  (0) 2022.09.18

운영체제 과제 4(double indirect inode)


테스트 환경

  • OS : Ubuntu 16.04
  • gcc : gcc 5.4.0

개요

운영체제 네번째 과제인 double indirect inode에 대한 내용입니다.

크게 fs.c안의 bmap , itrunc함수를 수정하고, fs.hfile.h, param.h의 값을 조금 수정함으로써 double indirect inode를 구현합니다.


과제 명세

먼저 xv6의 기본적인 inode의 구조에 대한 간단한 구조입니다.
image

dinode 구조체에서 direct block pointer는 12개가 존재하고, 1개의 indirect block pointer가 존재합니다.

그런 구조를 아래와 같이 수정해야합니다.
image

dinode 구조체에서 direct block pointer가 11개로 바뀌고, 1개의 single indirect block pointer, 그리고 1개의 dobule indirect block pointer가 존재합니다.

구현하기 위해서는 bmapitrunc 함수를 수정해야 합니다.
image


fs.h 수정 사항

fs.h에서 NDIRECT의 값을 1만큼 줄여주고 (12->11),

NDOUBLEINDIRECT 의 값을 NINDIRECT의 제곱으로 정의합니다.

addrs 배열의 크기를 NDIRECT의 값과 매치되게 수정합니다.

( addrs[NDIRECT+1]->addrs[NDIRECT+2])

image

이렇게 작성하면 direct block pointer의 개수는 11개로 줄어들고,
single(addrs[11]), dobule indirect block pointer(addrs[12]) 가 만들어집니다.

file.h, param.h 수정 사항

inode의 구조체 안에 있는 addrs 배열도 dinode에서 addrs 배열을 수정한것과 같이 수정해줍니다.

( addrs[NDIRECT+1]->addrs[NDIRECT+2])

param.h 안의 FSSIZE 값도 20000이상인 30000으로 수정해줍니다.


fs.c 수정 사항

먼저 bmap 함수를 수정했습니다.
기존의 bmap 함수를 최대한 참고하여 , double indirect block pointer의 역할을 수행할 수 있게 수행했습니다.

image

그 후에는 itrunc 함수를 수정했습니다.
기존의 iturnc 함수를 참조하고, double indirect이기 때문에 두번의 for문이 필요한 구조입니다.
image


file_test 결과

file_test는 T1,T2,T3로 구성되어 있습니다.

T1은 파일에 8MB정도의 크기를 쓰고, T2는 8MB의 크기를 읽고 T3는 T1과 T2를 10회 정도 반복해서 전체적인 구조에서 문제가 생기지 않는지를 체크합니다.

image

위의 그림과 같이 큰 문제 없이 T1과 T2,T3를 통과합니다. 여러번의 file_test를 시도해도

테스트는 성공적으로 종료됩니다.


추가 구현점과 문제점

눈에 보이는 큰 문제점은 없습니다. 추가 구현점이라면 ,

Project 3(LWP)에서 만들었던 Thread의 개념을 응용해 multi-thread 환경에서 파일을 읽고 쓰는 기능을 만들수 있을것 같습니다.

코드

Operating-System-xv6

운영체제 과제 3(LWP)


테스트 환경

  • OS : Ubuntu 16.04
  • gcc : gcc 5.4.0

개요

운영체제 세번째 과제인 Light-weight ProcessThread에 대한 내용입니다.

크게 thread_create, thread_exit, thread_join을 통해 구현됩니다.


thread 구현을 위한 proc 구조체 변경사항

image

int isThread,int numOfThread,int nextThreadId,thread_t tid,void * retval, struct proc*p creator 등을 추가했습니다.
그중에서 creator 멤버는 기존 proc 구조체parent와 비슷한 역할을 수행합니다.

이번 설계에서 process와 thread_create를 통해 생성된 thread는 parent-child 관계가 아니고 pid도 다르기 때문에 creator라는 포인터를 가짐으로써 최소한의 연결 관계를 유지해줍니다.

이 방식은 아래에서 다시 설명합니다.

(단 프로세스의 경우에는 creator와 parent가 같다고 생각합니다.)

기본적인 Thread 기능 명세

먼저 Thread의 기본 명세는 다음과 같습니다.

image

  1. LWP는 Process와는 다르게 주소 공간을 공유합니다.
  2. LWP는 본인을 생성한 Process와 다른 pid 값을 가집니다.
  3. LWP는 본인을 생성한 Process를 pointer로 creator에 저장함으로써 최소한의 연결관계를 유지합니다.
  4. LWP가 fork를 만나면 정상적으로 수행해야 합니다. 단, fork된 process는 LWP의 creator에는 접근이 불가합니다.
  5. LWP가 exec를 만나면 정상적으로 수행해야 합니다. exec를 수행할때 , LWP의 creator의 다른 LWP들은 종료되어야 합니다.
  6. LWP가 exit를 만나면 다른 모든 LWP가 종료되어야 합니다.
  7. LWP의 creator가 kill의 대상이 되면 craetor의 다른 모든 LWP역시 종료되어야 합니다.

선언하고 구현한 시스템콜

image

  1. thread_create : thread를 생성합니다. 기존의 시스템콜인 fork와 유사합니다.
  2. thread_exit : thread를 종료합니다. 기존 시스템콜인 exit와 유사합니다. 이때 결과물을 proc 인자로 받은 retval 안에 저장합니다.
  3. thread_join : 특정 thread를 기다립니다. 기존 시스템콜인 wait과 유사합니다. 다른 점은 인자로 받은 double-pointer에 return 값을 저장합니다.
  4. (thread_exit_target) : 명세에는 표시되지 않았지만, 특정 쓰레드를 종료시키기 위해서 직접 구현했습니다.

Thread 세부 설명

Thread의 생성 : thread_create

첫번째로 thread_create 함수에 대해서 설명과 예시입니다.

먼저 함수의 인자에 대해서 간략히 설명합니다.

  1. 첫번째 인자인 thread_t * thread는 thread 생성이 끝난 후 추적을 위해 Thread의 id를 저장하는 용도로 사용했습니다.
  2. 두번째 인자인 함수 포인터 start_routine은 생성된 Thread가 해야하는 함수의 진입점으로 사용됩니다.
  3. 세번째 인자인 void * arg는 두번째 인자인 함수를 수행하는데 사용되는 것으로 ustack에 저장됩니다.

전체적인 동작사항은 fork 시스콜과 다르지 않지만

image


위의 부분에서 allocproc를 통해 반환받은 proc 구조체를

thread 처럼 동작하게끔 하기 위해 isThread 값을 1로 설정해주고 , tid값을 현재 proc의 nextThreadId값을 가져와서 설정해줍니다.

단 이 tid는 연속적이나, 실행되고 있는 Thread들의 tid는 연속적임을 보장하지 않습니다. Ex) tid : 1 2 5 6

creator를 현재 Process로 설정해줍니다. 이를 통해 thread와 Process간의 Parent-Child 관계는 아니지만 (np->parent=curproc->parent 이기 때문에)
어느 정도의 관계를 가지고 있게끔 구현했습니다.
또 시작지점을 설정하는 부분에서는 기존 시스템콜인 exec을 참고하였습니다.

mage

ustack을 만들어주고 copyout을 통해서 복사해줍니다.

np->tf->eip=(uint)start_routine
eip값을 인자로 들어온 start_routine으로 설정해주어서 thread 가 수행할
함수의 진입점을 올바르게 설정해줍니다.

또 LWP끼리는 주소공간을 공유해야 하기 때문에

acquire(&ptable.lock);
struct proc* p;
~~~
for(p=ptable.proc;p<&ptable.proc[NPROC];p++){
             if(p->parent->pid==np->parent->pid){
                     p->sz=np->sz;
               }
}
~~~
release(&ptable.lock);

위와 같은 코드를 통해서 sz값을 공유시켜줍니다. 이를 통해 Memory Illegal Access 문제를 방지 할 수 있습니다.

fork 시스템콜에서는 생성된 Process의 pid값을 반환했지만, thread_create에서는 인자로 전달받은
thread_t * thread에 thread의 tid를 저장해줍니다.

Thread의 종료 : thread_exit

두번째로 thread_exit 함수에 대해서 설명과 예시입니다.

반환형은 void이고 함수의 인자에 대해서 설명합니다.

  1. 첫번째 인자인 void * retval 입니다. thread가 종료될때 이 인자에 결과값을 저장합니다.

전체적인 동작은 exit 시스템콜과 크게 다르지 않습니다.

image

한가지 다른점은

curproc->creator->numOfThread--;

을 통해 자신이 속해있는 Process의 쓰레드 개수를 줄이는 기록을 합니다.

Thread가 끝나기를 기다림 : thread_join

세번째로 thread_join 함수의 설명입니다.

반환형은 int로, join이 성공적이면 0을 .그렇지 않으면 다른 값을 반환합니다.

함수의 인자는 두개 입니다.

  1. 첫번째 인자인 thread_t thread입니다. join의 대상 thread를 지칭하는데 사용됩니다.
  2. 두번째 인자는 void retval() 입니다. thread_exit을 통해서 반환된 값을 저장해줍니다.

thread_join도 기존의 wait시스템콜과 유사합니다.

image

다른 점이 몇가지 있는데

  1. freevm(p->pgdir)을 사용하지 않습니다. 주소공간을 공유하기 때문에 이 함수를 join에서는 사용하지 않습니다.
  2. *retval=p->retval이라는 새로운 코드를 추가합니다. 인자로 전달받은 double-pointer retval에 저장합니다. 이를 통해 완료된 값들을 받아갈 수 있습니다.

또 한가지 중요한점은

sleep (curproc,&ptable.lock);

을 사용한다는 점입니다(기존의 wait에도 존재합니다.) 이를 통해서 모든 자식들이 끝나기를 기다립니다.

xv6와의 상호작용

  • 일부기능을 제외하고는 프로세스와 같이 움직여야 하며 , 내장 스케쥴러인 RR을 사용합니다 *
  1. Fork 의 경우에는 LWP의 대부분의 기능이 Process와 비슷하게 동작하기 때문에 큰 수정 없이 정상작동합니다.
  2. Exec의 경우에도 큰 수정 없이 정상작동합니다. 단 , thread가 exec를 만났을때,자신을 제외한 같은 프로세스에 속해 있는 다른 thread을 종료해야 하도록 구현했습니다.
  3. sbrk의 경우에는 growproc 시스템콜의 수정이 필요합니다. 스레드의 주소공간이 늘어났다면, 같은 프로세스 군에 속해있는 모든 thread가 이 변경된 sz의 값을 알아야합니다. 그렇기에 아래처럼 같은 군에 속해있거나 특정 프로세스(프로세스 군의 main)의 sz를 변경해줍니다. 그렇지 않으면 page fault가 나는것을 확인했습니다.
  4. growproc에서 sz을 접근할때, 다른 스레드가 sz를 수정중일 수 있으니 growproc 전체적으로 ptable lock을 잡아야합니다.
  5. kill의 경우에는 thread_kill test 에서 다시 언급하겠지만, Process가 kill을 당하면 그 프로세스에 속해있는 모든 프로세스를 종료해줍니다. 기존의 kill 시스템콜에서 조금의 추가가 있습니다.
  6. Exit의 경우에 thread가 exit을 만나면 같은 프로세스에 속해있는 다른 thread와 자기 자신을 종료하도록 구현했습니다.

xv6와의 상호작용 - 기존 시스템콜 변경점

kill

image


kill 시스템에 다른부분은 기존과 동일하지만 사진과 같이

ptable을 돌면서 같은 process 집합에 속해있는 Thread들을 killed=1로 바꿉니다.
이것은 kill의 시스템콜의 기능과 유사합니다. 이것을 통해서 프로세스가 kill 되었을때 속해있는 다른 쓰레드들도 kill을 당하게 합니다.

exit

image

exit도 ptable을 돌면서 자신과 같은 프로세스군에 있지만 자신을 제외한 스레드를 종료하고 같은 집합에 속해있는 Process도 삭제하는
killAllFromThread을 호출하여 exit을 수행합니다.

위의 상황은 thread가 exit을 만낫을때고 Process가 exit을 만낫을때도 자신이 생성한 thread를 모두 죽이는 역할을 수행하는것이
killAllFromThread입니다.

growproc

상기 언급한것과 같이 sz사이즈를 전체적으로 조절합니다.

단 좀더 광범위하게 ptable에 대한 lock을 요청하여 다른 쓰레드가 sz를 수정하는것을 방지합니다.

Test 결과

테스트 결과입니다.

  • 단 앞단에 기재한것과 같이 test 구동에 문제가 있기에 테스트 하나를 끝내고 xv6를 다시 실행해야 합니다.

thread_test 1:create,exit,join

생성하는 thread의 개수를 3개로 늘려서 진행해보았습니다.

image


정상적으로 thread를 3개 생성하고, 3개 exit 한후 join해서 정상적으로 테스트를 수행합니다.

thread_test 2:fork

image


thread 내에서 fork를 진행한후 정상적으로 끝내는 모습을 확인할 수 있습니다.

thread_test 3:sbrk

image


thread 내에서 malloc을 사용해 growproc을 호출하고 , free(dealloc)을 사용해 다시 growproc을 정상적으로 호출하는 것을 볼 수 있습니다. growproc을 수정하지 않으면 remap 패닉이 발생합니다.

thread_test 내 3가지 세부 테스트 모두 성공한 것을 확인 할 수 있습니다.


thread_exec

image


thread가 exec을 만났을때, 자신을 제외한 thread를 모두 종료시키고

정상적으로 Hello, thread를 출력하는것을 확인 할 수 있습니다.

thread_exit

image


thread가 exit을 만났을때, 그 프로세스 내의 모든 thraed를 종료시키는 테스트입니다.
정상작동합니다.

thread_kill

image


thread가 속한 프로세스가 kill 되었을때, 그 프로세스에 속한 thread를 모두 종료시키는 테스트입니다.
정상작동합니다. zombie 프로세스 역시 생기지 않습니다.

트러블슈팅

thread의 기본적인 생성과 종료,그리고 join을 구현하는데 있어서 막막한 감이 있었습니다.

기존 xv6의 fork,exec,allocproc,wait,exit의 코드등을 참고해서 새로운 시스템 콜들을 만들었고

기존의 시스템콜 역시 thread의 상황에 맞게 적절히 수정했습니다.

예를들어 thread_create 부분에서 단순히 fork 시스템콜

switchuvm(np)

라는 부분이 있었는데, 이것을 thread_create단으로 가져오니 알 수 없는 이유의패닉이 자꾸 발생하였고,

switchuvm의 주요 목표가 cr3 레지스터의 값을 변경하는것 이라는걸 알기 위해서

수업시간에 진행했던 lab pdf를 참조 후 그 부분을
다음과 같이 수정했습니다.

// switchuvm-like
pushcli();
lcr3(V2P(np->pgdir));
popcli();

이렇게 수정하고 나니 패닉도 발생하지 않았고, 원하는 대로 함수가 구현되었습니다.

미구현점 혹은 남은 문제사항

하단에 표시된 문제사항 최신 커밋 기준으로 해결되었습니다.

코드

Operating-System-xv6

+ Recent posts