Jungle

[TIL] pintos : 유저 프로세스 관리 - Troble Shooting

손가든 2023. 12. 13. 20:19

핀토스 2주차 과제를 끝마치며 '유저 프로세스 관리' 해주는 운영체제까지 구현할 수 있었다.

 

 

유저 프로세스는 디스크에 있는 프로그램을 읽어 가상 메모리를 만들어야 하고,

 

권한이 없는 자원에 대한 요청(시스템콜)을 받아내어

 

요청을 처리해주는 작업(시스템 콜 핸들러)을 수행하도록 코드를 구현했다.

 

 

오늘은 유저 프로그램 관리 코드를 작성하면서

 

알 수 없었던 의문의 오류들이 발생시키는 이유에 대해 고민해보고

 

이를 해결했던 트러블 슈팅 사례들에 대해 다뤄보겠다.

 


 

문제 1 : 'process_exec' 함수 -> 'parsing' 중에 timer_interrupt가 터지면 발생하는 ERROR

 

문제 상황은 다음과 같았다.

 

test 중 'multi-recurse'를 테스트하는 중에 간헐적으로 timer_interrupt가 터지면 Error를 출력했다.

 

출력 오류는 이러하였다.

 

 

콜스택을 확인해 보면 parsing_file_input이 콜스택에 찍히는데,

 

이는 process_exec 함수에서만 사용하는 파싱하는 함수이다.

 

프로그램 파일을 로딩하는 process_exec() 함수

 

이 pasing_file_input() 함수는 'load()' 함수가 실행되고 나서

 

command로 전달받은 인자들을 파싱하여 유저 프로세스의 가상 메모리 런타임 스택을 채우는 역할을 한다.

 

 

parsing_file_input에서 스트링 복사하여 파싱 작업을 수행

 

parsing 함수는 내부에서 comand_line을 파싱할 때, race 상황을 방지하기 위해서 palloc으로 스트링을 복사 한다.

 

이 함수는 process_create_initd 에서 사용한 방식과 똑같이 사용하여 palloc_get_page 인자로 '0'을 전달했다.

 

처음엔 이 인자가 어떤 의미인지 몰랐다.

 

 

'palloc_get_page(0)'는 콜스택 흐름대로 실행하다가 내부에서 bitmap 함수를 실행할 즈음 timer_interrupt가 발생한다.

 

timer_interrupt 실행 flow 중에 thread_tick에서 사용하는 thread_current() 함수에서

 

'이 함수를 호출한 스레드가 Running_thread인지 확인'하는 assert 문에서 fail 되었다.

 

 

 

 

따라서 이때, 현재 parsing 함수를 호출하는 running thread의 흐름인 process_exec()의 context와는

 

다른 흐름으로 이 palloc() 함수로 진입하면서 문제가 발생했다고 유추했다.

 

 

그럼 왜 context가 바뀌는 걸까?

 

load를 통해 유저 프로그램으로 컨텍스트를 스위치하기위해

 

현재 프로세스의 페이지를 지우는 과정이

 

running thread의 컨텍스트를 버리고 있기 때문이었다.

 

 

 

따라서 process_exec()안의 

 

 

load에서 process_activate를 실행함으로써 새로운 context인 pml4를 할당받고 유저 프로세스의 context로 탈바꿈할 준비를 한다.

 

 

따라서 이 load함수 밑에서 수행하는 parsing_file_input() 함수에서는 thread()가 유저 프로세스의 스레드인 것이라고 생각했다.

 

 

 

그래서 생각한 해결책은

 

현재 스레드가 유저 영역에서 수행되고 있으니까 palloc도 유저 영역에 할당하면 문제가 사라지지 않을까?

 

 

이 해결책은 문제 해결에 적중했다.

 

 

페이지를 유저 pool에 할당하도록 flag를 전달한 후에 timer_interrupt에서 thread_current() 문제가 절대 발생하지 않았던 것.

 

 

이 문제에 대해서 내가 예상한 문제 상황은

 

load이후 유저 프로세스의 가상 메모리를 할당 받으면서 현재 스레드가 유저 영역에 존재하고 있기 때문에

 

커널 영역에서 할당하려고 하는 thread가 실행중인 스레드와 일치하지 않아서 발생하는 문제라고 생각했고,

 

 

그에 대한 해결책은 커널 영역이 아닌 유저 영역에 palloc을 함으로써 문제를 해결하려고 했다.

 

 

이에 대한 내 의도가 문제 해결에 정확히 맞아 떨어지는지는 아직까지 확실하지 않지만

 

확실히 이해해야지만 그럴듯한 해결책을 제시할 수 있다는 교훈을 얻을 수 있었다.

 

 

 

 


 

 

문제 2 : syn-read 에서의 간헐적인 fail (feat : 불규칙적인 결과 )

 

이 문제는 최종 보스인 multi-oom 이후에도 여전히 발생한 문제였다.

 

규칙적으로 고장난다면 해결 방안을 생각해 낼 수 있겠지만

 

3번에 1번꼴로 fail.

 

그것도 실패하는 방식도 제멋대로이다.

 

fail한 결과 log를 보면 다음과 같았다.

 

 

보통 발생하던 결과 창이다.

이유를 알 수 없는 Exit(-1)

 

매번 정상적이지 않은 exit(-1)은 fork를 10번하면 몇번째 자식이던 간에 상관없이 매 test마다 다르게 나타났다.

 

근데 또 다른 fail 결과창을 확인해 보니

 

 

load 시에 파일을 open할 때 fail하는 것이 10번에 한번 꼴로 발생하는 것을 관찰했다.

 

(약 130번정도 돌려보고 깨닫게 됨)

 

 

이 때,

 

아! 설마 빠르게 동일한 파일을 fork하는 중에 동시에 open하면서 오류가 발생한 것은 아닐까?

 

라는 생각을 하게 되었다.

 

load 안에서 filesys_open을 통한 파일 열기는 synce를 보존하는 시스템콜을 거치지 않기 때문이었다.

 

 

그래서 이 load안에서 lock을 사용하여 synce를 수행해보자고 생각했다.

 

 

file을 오픈하기 전에 lock으로 동기화를 보장했고,

 

 

어떤 예외 상황이든 거치는 done 시에 lock을 해제하도록 했다.

 

 

결과는 적중했다.

 

 

이후 syn-read에서 fail하는 문제는 발생하지 않았다.