프로젝트 일지
목차 프로젝트 일지1일차# Nachos 설치 및 실행# 문제1번결과소스# 문제2KThread.fork()KThread.runThread()KThread.yield()함수 설명 정리KThread.javacreateIdleThreadKthread 생성자selfTestfork설명runThread()설명runNextThread설명KThread.yield()restoreStaterunRunnable
1일차
Nachos 설치 및 실행
문제1번
결과
소스
public void run() {
/* 실행횟수 변경 */
for (int i=0; i<10; i++) {
System.out.println("*** thread " + which + " looped "
+ i + " times");
currentThread.yield();
}
}
문제2
KThread.fork()
/* 이 스레드를 실행시키기 위해 fork() 메소드호출. 이 2 개의스레드는 fork() 메소드로부터 호출되어 반환된 current thread 와 * run() 메소드에의해 target 되어 실행되는 또다른스레드이다.
*/
public void fork() {
/* 아래 두줄중 하나라도 만족하지 못한다면 Error를 발생시킨후 종료한다. */
Lib.assertTrue(status == statusNew); // 현재 상태가 fork가 되지않은 상태인가?
Lib.assertTrue(target != null); // run된 상태가 있는가?
/* 디버그 메시지를 띄워줍니다. */
/* 이 forking될 스레드의 이름과 runnable된 target을 출력 */
Lib.debug(dbgThread,
"Forking thread: " + toString() + " Runnable: " + target);
/* Interrupt 초기상태를 false로 지정합니다. */
/* disable안에 setStatus method에 따라서~ */
/* 기본적으로 Inttrupt클래스에선 enable메서드에 false를 default값으로 저장 */
/* enable에 false저장, oldStatus에 false저장 = intStatus에 false저장 */
boolean intStatus = Machine.interrupt().disable();
/* tcb 안에있는 스레드를 실행시키기 위해 start()를 호출합니다. */
/* PCB(Process Control Block) = TCB(Task Control Block) */
/* 이 TCB가 나타내는 스레드가 실행을 시작하게합니다. */
/* 지정된 target이 thread에서 실행됩니다. */
/* Runnable()의 공간을 할당시키고 run() 을 통해서 실행될 수 있습니다. */
tcb.start(new Runnable() {
public void run() {
runThread();
}
});
/* ready() 함수호출 : 스레드를실행하기위해준비시킴 */
/* 해당 스레드의 status(현재상태)를 ready로 만듭니다. */
/* 해당 스레드를 스케듈러의 ready queue에 추가합니다. */
/* autoGrader에게 현재 상태가 ready상태임을 알립니다. */
ready();
/* 인터럽트를 지정된 상태로 복원합니다. */
/* intstatus 를복구시킴 */
/* restore안에 setStatus method에 따라서~ */
/* status는 true 그리고 oldstatus가 false가 됨.*/
/* 그에따라 새로 생긴 thread의 실행이 이루어진다 */
Machine.interrupt().restore(intStatus);
}
Machine.interrupt().restore(intStatus)
- /* 인터럽트를 지정된 상태로 복원합니다. */
public void restore(boolean status) {
}setStatus(status);
/* 인터럽트가 비활성화되었는지 여부를 테스트합니다. */
public boolean disable() {
}return setStatus(false);
/* 인터럽트가 활성화되었는지 여부를 테스트합니다. */
public void enable() {
}setStatus(true);
- /* 인터럽트를 지정된 상태로 복원합니다. */
TCB
- TCB는 Nachos 스레드를 생성, 컨텍스트 스위치 및 파괴하는데 필요한 저수준 세부 정보를
시뮬레이트합니다. - 여기에 동기화를 사용하지 않을 것입니다.
왜냐하면 이 중 하나가 start ()에 대한 첫 번째 호출이거나, 다른 TCB의 컨텍스트에서 호출되고 있다고 가정하기 때문입니다.
한 번에 하나의 TCB 만 실행할 수 있으므로 동기화가 필요하지 않습니다.
- TCB는 Nachos 스레드를 생성, 컨텍스트 스위치 및 파괴하는데 필요한 저수준 세부 정보를
status
- /* Thread의 상태들입니다. */
private static final int statusNew = 0; //아직 fork되지 않은 상태
private static final int statusReady = 1; //Ready queue에 있으나, 실행은 안된 상태
private static final int statusRunning = 2; // 실행중인 상태
private static final int statusBlocked = 3; // Ready queue에도 없고 실행도 안된 상태
private static final int statusFinished = 4;
- /* Thread의 상태들입니다. */
Target
'run' 메서드가 호출 된 객체.
private void run() {
Lib.assertTrue(Machine.interrupt().disabled()); Machine.yield(); currentThread.saveState(); Lib.debug(dbgThread, "Switching from: " + currentThread.toString() + " to: " + toString()); currentThread = this; tcb.contextSwitch(); currentThread.restoreState();
}
인터럽트(interrupt) 클래스는 로우 레벨 인터럽트 하드웨어를 에뮬레이트합니다.
setStatus method를 통해서 enable disable 상태로 만듭니다.
Machine.interrupt().disabled()
- /* 이 메서드를 사용하는 thread에게 cpu 할당을 disable 합니다. */
public boolean disable() {
}return setStatus(false);
- /* 이 메서드를 사용하는 thread에게 cpu 할당을 disable 합니다. */
setStatus method
public boolean setStatus(boolean status) {
boolean oldStatus = enabled; enabled = status; if (oldStatus == false && status == true) tick(true); return oldStatus;
}
interrupt status를 setStatus의 파라미터가 true냐 false 냐에 따라서 enable과 disable 두 상태로 서로 다르게 세팅합니다.
tick(true);
- 만약에 interrupt status가 disable(비활성)에서 enable(활성)으로 바뀌면 그 시뮬레이션된 시간이 진행됩니다.
Runnable
- 새로운 쓰레드를 생성하려면, 먼저 Runnable interface를 먼저 implement하는 Class를 선언해야 합니다.
Lib.assertTrue() 란?
/**
* Asserts that <i>expression</i> is <tt>true</tt>. If not, then Nachos * exits with an error message. * * @param expression the expression to assert. */ public static void assertTrue(boolean expression) { if (!expression) throw new AssertionFailureError(); }
AssertionFailureError() 란?
/**
Thrown when an assertion fails.
/
class AssertionFailureError extends Error {
AssertionFailureError() {
super();
}AssertionFailureError(String message) {
super(message);
}
}
선언이 실패했을 경우에 오류처리된다.
Error 클래스를 상속받았다.
- 첫째는 java.lang.Error 클래스의 서브클래스들이다. 에러는 시스템에 뭔가 비정상적인 상황이 발생했을 경우에 사용된다. 그래서 주로 자바 VM에서 발생시키는 것이고 어플리케이션 코드에서 잡으려고 하면 안된다.
OutofMemoryError나 ThreadDeath 같은 에러는 catch 블럭으로 잡아도 대응 방법이 없다.
따라서 시스템 레벨에서 특별한 작업을 하는 게 아니라면 어플리케이션에서는 이런 에러에 대한 처리는 신경 쓰지 않아도 된다.
고로 Thread에 대한 에러를 처리하기 위하여 Error 클래스를 상속받아 오버라이딩 하여 사용한다.
- 첫째는 java.lang.Error 클래스의 서브클래스들이다. 에러는 시스템에 뭔가 비정상적인 상황이 발생했을 경우에 사용된다. 그래서 주로 자바 VM에서 발생시키는 것이고 어플리케이션 코드에서 잡으려고 하면 안된다.
KThread.runThread()
/* runThread() 는 private 로선언 ( 현재클래스에서만사용가능 ) */
private void runThread() {
/* 실행하기 위해 해당 스레드를 준비 */
/* 현재 스테이트를 statusRunning으로 세팅 */
/* Autograder에게 지정된 스레드가 현재 실행 중임을 알립니다. */
/* 인터럽트를 가능하게 합니다. */
begin();
target.run(); // target의 run을 통해 스레드 시작
/* 현재 스테이트를 statusFinished으로 세팅 */
/* Autograder에게 지정된 스레드가 현재 종료중임을 알립니다. */
/* (현재상황) 이 메소드는 스레드의 run()값이 반환될 때 자동으로 호출됩니다. */
/* 또한 직접 호출될 수 있습니다. */
finish(); //
}
begin();
- 실행하기 위해 해당 스레드를 준비합니다.
- 인터럽트를 가능하게 합니다.
target.run();
finish();
current thread 가끝나고 안전하게 스레드가 제거되기 위해 schedule합니다.
current thread 의 스택과 다른 실행상태가 여전히 사용중일 수 있기 때문에
current thread 는 즉시 제거될 수 없습니다.
대신 이 스레드는 다음 스레드가 실행됨에 따라 자동으로 제거되고 안전하게 삭제됩니다.이 메소드는 스레드의 run()값이 반환될 때 자동으로 호출됩니다.
또한 직접 호출될 수 있습니다.
public static void finish() {Lib.debug(dbgThread, "Finishing thread: " + currentThread.toString()); Machine.interrupt().disable(); Machine.autoGrader().finishingCurrentThread(); Lib.assertTrue(toBeDestroyed == null); toBeDestroyed = currentThread;
currentThread.status = statusFinished; //currentthread 의상태를 finish 상태로바꿔줌
/* 다른 스레드가 실행하기 위해 ready 상태에 스레드가 있으면 그 스레드에게 CPU 를 양도한다. */
sleep();
}
currentthread 의상태를 finish 상태로바꿔줌
sleep() 함수호출. :
public static void sleep() {
Lib.debug(dbgThread, "Sleeping thread: " + currentThread.toString());
Lib.assertTrue(Machine.interrupt().disabled());
if (currentThread.status != statusFinished)
currentThread.status = statusBlocked;
runNextThread(); // 현재 스레드가 완료되었거나 차단되었으므로 CPU를 양도합니다.
}
- 현재 스레드가 완료되었거나 차단되었으므로 CPU를 양도합니다.
- 이 스레드는 현재 스레드여야 합니다.
- 현재 스레드가 Blocked된 경우(세마포어, Lock, condition) 결국 어떤 쓰레드는 이 쓰레드를 깨우고
다시 대기 큐에 놓아서 다시 스케줄 할 수 있습니다.
그렇지 않으면 finish ()는 다음 스레드가 실행될때 이 스레드를 파괴하도록 예약해야 합니다.
KThread.yield()
public static void yield() {
Lib.debug(dbgThread, "Yielding thread: " + currentThread.toString());
/* 현재 스테이트가 실행된 상태인지 확인합니다. */
Lib.assertTrue(currentThread.status == statusRunning);
boolean intStatus = Machine.interrupt().disable();
/* current thread 는 스스로 ready queue 에 더해지고 다음 thread 와 교환한다. */
currentThread.ready(); //currentthread 는 ready() 함수호출
runNextThread();
Machine.interrupt().restore(intStatus); // 인터럽트 그전상태로 복구
}
- 다른 스레드가 실행하기 위해 ready 상태에 있으면 그 스레드에게 CPU 를 양도한다.
- 만약 레디큐에 현재 스레드를 놓으면 언젠가는 다시 스케줄되어야한다.
- 만약 다른 스레드가 실행할 준비가 되어있지 않다면 즉시 리턴한다.
- 그렇지 않으면 readyqueue.nextthread() 에 의해 다시 선택되어진 current thread 를 리턴한다.
- 인터럽트는 disable 된고, current thread 는 스스로 ready queue 에 더해지고 다음 thread 와 교환한다.
- return 될때 인터럽트는 그전상태로 복구된다.
- yield() 는 인터럽트가 disabled 된 상태로 호출된다.
- 이 때 Machine.interrupt().restore(intStatus)에서 interrupt가 일어나지 않도록 해준다.
이렇게 스레드들이 번갈아가며 실행된다
함수 설명 정리
KThread.java
createIdleThread
private static void createIdleThread() {
Lib.assertTrue(idleThread == null);
idleThread = new KThread(new Runnable() {
public void run() { while (true) yield(); }
});
idleThread.setName("idle");
Machine.autoGrader().setIdleThread(idleThread);
idleThread.fork();
}
- IdeleThread를 생성하는 함수이다.
- 언제생성하는가?
- ready queue에 실행될 thread가 존재하지 않을 때
- runNextThread함수가 호출되었을 때
Kthread 생성자
public KThread() {
/* 스레드가 생성시 앞서서 thread가 생성되어 있었다면, 새로운 TCB를 만들어서 사용합니다. */
if (currentThread != null) { // 현재스레드가 존재하면 True
tcb = new TCB(); // Kthread 전역변수 tcb에 새로운 TCB()객체 생성
}
/* 만약 이번 thread가 처음 생성된 thread라면 */
else {
/*"액세스"라고 말할 수 있는 명확한 스레드가 없는 경우
이 매개 변수는 우선 순위가 전송되지 않아야 함을 나타내는 False 이어야합니다.*/
readyQueue = ThreadedKernel.scheduler.newThreadQueue(false);
/*request()및 nextThread()를 거치지 않고 스레드가 액세스를 받았음을 이 스레드 큐에 알립니다*/
readyQueue.acquire(this);
/* 전역변수에 방금 생성된 스레드가 현재 스레드임을 알립니다. */
currentThread = this;
/* 전역변수에 현재 TCB를 새로 만들어 연결해둡니다. */
tcb = TCB.currentTCB();
/* 그리고 현재 스레드 이름을 Main이라고 정합니다. */
name = "main";
/* thread를 시작상태로 준비합니다.(시작은 아님) */
restoreState();
/* 그 다음 idleThread를 생성합니다. */
createIdleThread();
}
}
Kthread를 생성시 호출되는 Default 생성자이다.
/* 맨 처음 상황에서는 ~ selftest에서 pingtest(1)이 target이라고 보시면 됩니다. */
public KThread(Runnable target) {this(); this.target = target;
}
target은 run method가 호출 된 객체입니다.
- 이때 이 객체는 해당 스레드가 수행해야할 임무를 의미합니다.
이 생성자는 실행된 runnable 객체를 담은 thread가 생성되었을 때 사용됩니다.
- 현재 스레드의 target을 매개변수로 담은 target으로 초기화 합니다.
selfTest
public static void selfTest() {
Lib.debug(dbgThread, "Enter KThread.selfTest");
new KThread(new PingTest(1)).setName("forked thread").fork();
new PingTest(0).run();
}
new KThread(new PingTest(1)).setName("forked thread").fork();
1이라는 이름으로 임무를 수행하는 스레드를 만듭니다.
그 후 fork를 통해서 인터럽가 발생하는것을 막고, 해당 스레드가 실행을 시작하도록합니다.
public void fork() {
Lib.assertTrue(status == statusNew);
Lib.assertTrue(target != null);Lib.debug(dbgThread,
"Forking thread: " + toString() + " Runnable: " + target);
boolean intStatus = Machine.interrupt().disable();
/* 전역변수에 저장되어 있는 tcb임무가 수행되도록 합니다. /
/ 이곳에서 첫번째 TCB가 아니면, 새로운 자바 thread를 생성합니다. /
/ 만약 첫번째 TCB라면 새로운 java thread를 만들필요가 없습니다. 단지 현재 자바 스레드를 이용하면 됩니다. 그 후 target이되는 매개값으로 전달되는 new Runnable의 run이 실행됩니다. */
tcb.start(new Runnable() {public void run() { runThread(); } });
ready();
Machine.interrupt().restore(intStatus);
}private void runThread() {
begin();
/* 여기서 run은 처음 테스트를 할때 만들어졌던 임무(pintest)의 Runnable의 run입니다. /
/ 이곳에서는 해당 임무를 수행하고, yield를 호출합니다. /
/yield는 현재 임무가 수행되던 thread를 ready queue에 넣고, 다음thread를 호출합니다(runNextThread()). 그 후 다음 thread가 (run())에 의해 실행되어집니다. /
/여기서 currentThread 전역변수값이 다음 thread값으로 변경이 이루어집니다. 그리고 tcb의 값도 현재 사용중이었던 tcb에서 다음 수행할 tcb로 변경이 이루어집니다. */
target.run();finish();
}
fork
public void fork() {
Lib.assertTrue(status == statusNew);
Lib.assertTrue(target != null);
Lib.debug(dbgThread,
"Forking thread: " + toString() + " Runnable: " + target);
/*이전에 수행되던 상태를 저장합니다.*/
/*인터럽트를 막아두고, oldstate를 반환하여 intState에 저장합니다.*/
boolean intStatus = Machine.interrupt().disable();
/* 전역변수에 저장되어 있는 tcb임무가 수행되도록 합니다. */
/* 이곳에서 첫번째 TCB가 아니면, 새로운 자바 thread를 생성합니다. */
/* 만약 첫번째 TCB라면 새로운 java thread를 만들필요가 없습니다. 단지 현재 자바 스레드를 이용하면 됩니다. 그 후 target이되는 매개값으로 전달되는 new Runnable의 run이 실행됩니다. */
tcb.start(new Runnable() {
public void run() {
runThread();
}
});
/* 현재 생성한 스레드를 reay queue에 넣어둡니다. */
ready();
/* resotre안에 이전상태정보인 intStatus를 넣어줌으로서 이전 상태로 되돌립니다. */
/* 스레드가 시작될 준비로 만들어주고, toBeDestroyed상태를 체크합니다. */
Machine.interrupt().restore(intStatus);
}
설명
- 새로운 스레드가 생성이 되면, 현재 생성된 스레드가 첫번째냐 아니냐에 따라서
Java Thread를 생성해 매핑시켜줍니다.
그 후 tcb.start 함수에 의해서 new Runnable()의 run()을 실행시켜줍니다.
그 다음에 ready() 함수에 의해서 현재 새로 생성한 스레드를 reay queue에 넣고,
Machine.interrupt().restore(intStatus);에 의해 이전 상태로 되돌립니다.
소스실행
- boolean intStatus = Machine.interrupt().disable()
이전에 수행되던 상태를 저장합니다.
인터럽트를 막아두고, oldstate를 반환하여 intState에 저장합니다. - tcb.start
사용하게 된다면, 첫번째 TCB냐 아니냐에 따라서 아니면 새로운 Java Thread를 생성합니다.
만약 첫번째 TCB라면 새로운 현재 실행되고 있는 thread를 사용하면 되기 때문에
Java Thread를 만들 필요가 없어 생성하지 않습니다.
그 후 매개변수값으로 전달된 new Runnable이 tcb.start에서 target이 되고 이 타겟이 실행이 되어집니다.
즉 new Runnable의 run()메서드안의 runThread();가 실행이 되어집니다. - ready
현재 생성한 스레드를 reay queue에 넣어둡니다. - Machine.interrupt().restore(intStatus);
resotre안에 이전상태정보인 intStatus를 넣어줌으로서 이전 상태로 되돌립니다.
스레드가 시작될 준비로 만들어주고, toBeDestroyed상태를 체크합니다.
runThread()
private void runThread() {
/* 실행하기 위해 해당 스레드를 준비 */
/* 현재 스테이트를 statusRunning으로 세팅 */
/* Autograder에게 지정된 스레드가 현재 실행 중임을 알립니다. */
/* 인터럽트를 가능하게 합니다. */
begin();
/* 여기서 run은 처음 테스트를 할때 만들어졌던 임무(pingtest)의 Runnable의 run입니다. */
/* 이곳에서는 해당 임무를 수행하고, yield를 호출합니다. */
/*yield는 현재 임무가 수행되던 thread를 ready queue에 넣고, 다음thread를 호출합니다(runNextThread()). 그 후 다음 thread가 (run())에 의해 실행되어집니다. */
/*여기서 currentThread 전역변수값이 다음 thread값으로 변경이 이루어집니다. 그리고 tcb의 값도 현재 사용중이었던 tcb에서 다음 수행할 tcb로 변경이 이루어집니다. */
target.run();
/* 인터럽트를 가능하게 불가능하게 합니다. */
/* 현재 스테이트를 statusFinished으로 세팅 */
/* 그 후 sleep()상태로 빠집니다. */
finish();
}
설명
begin(); 을 통해서 실행하기 위해 해당 스레드를 준비합니다. 기와 동시에 인터럽트 가능상태로 전환합니다.
그 후 target.run(); 을 통해 해당 임무를 수행하고, yield를 호출합니다.
(이때, target은 처음 selftest에서 만든 pingtest와 같습니다.)
마지막으로 finish();를 통해서 인터럽트를 불가능상태로 전환하고, 현재 상태를 statusFinished로 전환 후
sleep상태에 빠집니다.public void ready() {
Lib.debug(dbgThread, "Ready thread: " + toString()); Lib.assertTrue(Machine.interrupt().disabled()); Lib.assertTrue(status != statusReady); status = statusReady;
/* 맨 처음 thread가 생성될 때(최초 스레드) KThread생성자에 의해서 idleThread가 생성됩니다. / / 만약 현재 스레드가 idleThread가 아니면, 현재 스레드를 ready queue에 넣어둡니다.*/
if (this != idleThread) readyQueue.waitForAccess(this); Machine.autoGrader().readyThread(this);
}
runNextThread
private static void runNextThread() {
/* 레디큐에서 다음 스레드를 꺼내옵니다. */
KThread nextThread = readyQueue.nextThread();
/* 다음에 꺼내온 스레드가 비어있는 상태 NULL이라면, 다음 스레드로 idleThread로 설정합니다. */
if (nextThread == null)
nextThread = idleThread;
/* 꺼내온 nextThread를 실행시킵니다. */
nextThread.run();
}
설명
- 이 함수가 실행이 되어지면, 다음 수행할 Thread를 Ready Queue에서 꺼내옵니다.
그리고 그 스레드가 존재한다면 nextThread.run(); 함수를 통해 실행시키고
존재하지 않다면 다음 nextThread에 idleThread를 넣어 idelThread를 실행시킵니다.
KThread.yield()
public static void yield() {
Lib.debug(dbgThread, "Yielding thread: " + currentThread.toString());
/* 현재 스테이트가 실행된 상태인지 확인합니다. */
Lib.assertTrue(currentThread.status == statusRunning);
/*이전에 수행되던 상태를 저장합니다.*/
/*인터럽트를 막아두고, oldstate를 반환하여 intState에 저장합니다.*/
boolean intStatus = Machine.interrupt().disable();
/* current thread 는 스스로 ready queue 에 더해지고 다음 thread 와 교환한다. */
currentThread.ready(); //currentthread 는 ready() 함수호출
runNextThread();
/* resotre안에 이전상태정보인 intStatus를 넣어줌으로서 이전 상태로 되돌립니다. */
/* 스레드가 시작될 준비로 만들어주고, toBeDestroyed상태를 체크합니다. */
Machine.interrupt().restore(intStatus); // 인터럽트 그전상태로 복구
}
기본컨셉
- 다른 스레드가 실행하기 위해 ready 상태에 있으면 그 스레드에게 CPU 를 양도한다.
- 만약 레디큐에 현재 스레드를 놓으면 언젠가는 다시 스케줄되어야한다.
- 만약 다른 스레드가 실행할 준비가 되어있지 않다면 즉시 리턴한다.
- 그렇지 않으면 ready()함수의 readyqueue.nextthread() 에 의해
다시 선택되어진 current thread 를 리턴한다.
소스 실행
- boolean intStatus = Machine.interrupt().disable();에 의해
인터럽트를 막아두고, oldstate를 반환하여 intState에 저장합니다. - currentThread.ready(); 에서
현재 스레드를 reay queue에 넣어둡니다. - runNextThread(); 에서
레디큐에서 다음 수행할 스레드를 꺼내오고 해당 스레드가 존재하면 실행시키고,
존재하지 않다면 다음 수행할 스레드로 idelThread로 세팅합니다.
restoreState
protected void restoreState() {
Lib.debug(dbgThread, "Running thread: " + currentThread.toString());
Lib.assertTrue(Machine.interrupt().disabled());
Lib.assertTrue(this == currentThread);
Lib.assertTrue(tcb == TCB.currentTCB());
Machine.autoGrader().runningThread(this);
/* 현재 스레드의 상태를 러닝상태로 초기화 합니다. */
status = statusRunning;
/* toBeDestroyed가 존재한다면 해당 정보를 모두 삭제 후 관련 tcb까지 null로 초기화 합니다. */
if (toBeDestroyed != null) {
toBeDestroyed.tcb.destroy();
toBeDestroyed.tcb = null;
toBeDestroyed = null;
}
}
run
private void run() {
Lib.assertTrue(Machine.interrupt().disabled());
Machine.yield();
currentThread.saveState();
Lib.debug(dbgThread, "Switching from: " + currentThread.toString()
+ " to: " + toString());
currentThread = this;
tcb.contextSwitch();
currentThread.restoreState();
}
Runnable
- 실제로 수행되는 임무를 의미합니다.
- 해당 selftest인 ping test에서는 for문동안 특정 출력문을 출력하는게 임무입니다.
KThread.fork()
KThread.runThread()
KThread.yield()
KThread.runNextThread()