

벌써 pintos 3주차가 되었다.
1주차: thread
2~3주차: userprog
현재는 2~3주차 userprog를 진행중이다.
어떻게 기록해야 할지?
무엇을 기록해야 할지?
고민만 하다가, pintos 하면서 정리를 많이 하지 못했다.

pintos에는 꽤나 많은 Makefile이 존재한다.
test case들을 진행하면서
일일이 하나씩 체크하는 것

make check 명령어를 사용해 모든 케이스를 체크하는 것

이렇게 크게 나눠볼 수 있을 것이다.
이게 꽤나 시간을 잡아 먹어서 "어떻게 이걸 빠르게 할 수 없을까?"에서 시작되었다.
그러니까 정확히 내가 원하는 테스트들만 해보고 싶은 것이다.
# =================================================================
# 테스트 그룹 정의
# =================================================================
# CREATE 테스트들
CREATE_TESTS = create-normal create-empty create-null create-bad-ptr create-long create-exists create-bound
# OPEN 테스트들
OPEN_TESTS = open-normal open-null open-twice open-missing open-boundary open-bad-ptr open-empty
# WRITE 테스트들
WRITE_TESTS = write-normal write-bad-ptr write-boundary write-zero write-stdin write-bad-fd
# READ 테스트들
READ_TESTS = read-normal read-bad-ptr read-boundary read-zero read-stdout read-bad-fd
# CLOSE 테스트들
CLOSE_TESTS = close-normal close-twice close-stdin close-stdout close-bad-fd
# EXEC 테스트들
EXEC_TESTS = exec-once exec-arg exec-boundary exec-missing exec-bad-ptr
# 모든 테스트 그룹 목록
TEST_GROUPS = create open write read close exec
# 특별한 파일이 필요한 테스트들 (sample.txt 등)
TESTS_NEED_SAMPLE = open-normal open-twice write-normal read-normal
# =================================================================
# 메인 테스트 실행 함수 (내부 함수)
# =================================================================
define run_test_group
@echo "=== Running $(1) Tests ==="
@success=0; total=0; \
for test in $($(shell echo $(1) | tr '[:lower:]' '[:upper:]')_TESTS); do \
echo "Running $$test..."; \
total=$$((total + 1)); \
if $(MAKE) run-single-test TEST=$$test; then \
success=$$((success + 1)); \
fi; \
done; \
echo "=== $(1) Tests Complete: $$success/$$total passed ==="
endef
define run_test_quick
@echo "=== Running $(1) Tests (Quick) ==="
@for test in $($(shell echo $(1) | tr '[:lower:]' '[:upper:]')_TESTS); do \
echo "Running $$test (quick)..."; \
$(MAKE) run-single-test-quick TEST=$$test; \
done
@echo "=== $(1) Tests Complete (Quick) ==="
endef
# =================================================================
# 단일 테스트 실행 (내부 함수)
# =================================================================
run-single-test: os.dsk
@sample_file=""; \
if echo "$(TESTS_NEED_SAMPLE)" | grep -q "$(TEST)"; then \
sample_file="-p ../../tests/userprog/sample.txt:sample.txt"; \
fi; \
pintos -v -k -T 60 -m 20 --fs-disk=10 \
-p tests/userprog/$(TEST):$(TEST) \
$$sample_file \
-- -q -f run $(TEST) < /dev/null \
2> tests/userprog/$(TEST).errors > tests/userprog/$(TEST).output; \
if perl -I../.. ../../tests/userprog/$(TEST).ck tests/userprog/$(TEST) tests/userprog/$(TEST).result 2>/dev/null; then \
echo "$(TEST): PASS"; \
exit 0; \
else \
echo "$(TEST): FAIL"; \
exit 1; \
fi
run-single-test-quick: os.dsk
@sample_file=""; \
if echo "$(TESTS_NEED_SAMPLE)" | grep -q "$(TEST)"; then \
sample_file="-p ../../tests/userprog/sample.txt:sample.txt"; \
fi; \
pintos -v -k -T 20 -m 20 --fs-disk=10 \
-p tests/userprog/$(TEST):$(TEST) \
$$sample_file \
-- -q -f run $(TEST) < /dev/null \
2> tests/userprog/$(TEST).errors > tests/userprog/$(TEST).output; \
perl -I../.. ../../tests/userprog/$(TEST).ck tests/userprog/$(TEST) tests/userprog/$(TEST).result 2>/dev/null
# =================================================================
# 공통 타겟들
# =================================================================
# 특정 테스트 그룹 실행
test-%: os.dsk
$(call run_test_group,$*)
# 빠른 테스트 그룹 실행
test-%-quick: os.dsk
$(call run_test_quick,$*)
# 단일 테스트 실행 (사용자용)
test-single:
@if [ -z "$(TEST)" ]; then \
echo "Usage: make test-single TEST=test_name"; \
echo "Example: make test-single TEST=create-normal"; \
exit 1; \
fi
$(MAKE) run-single-test TEST=$(TEST)
# 커스텀 테스트 그룹 실행
test-custom:
@if [ -z "$(TESTS)" ]; then \
echo "Usage: make test-custom TESTS=\"test1 test2 test3\""; \
echo "Example: make test-custom TESTS=\"create-normal open-normal write-normal\""; \
exit 1; \
fi
@echo "=== Running Custom Tests ==="
@for test in $(TESTS); do \
echo "Running $$test..."; \
$(MAKE) run-single-test TEST=$$test; \
done
@echo "=== Custom Tests Complete ==="
# =================================================================
# 결과 확인 및 관리
# =================================================================
# 특정 그룹 결과 확인
check-%-results:
@echo "=== $* Test Results ==="
@group_tests=$$(echo $($(shell echo $* | tr '[:lower:]' '[:upper:]')_TESTS)); \
for test in $$group_tests; do \
if [ -f tests/userprog/$$test.result ]; then \
echo -n "$$test: "; \
if grep -q "PASS" tests/userprog/$$test.result 2>/dev/null; then \
echo "PASS"; \
elif grep -q "FAIL" tests/userprog/$$test.result 2>/dev/null; then \
echo "FAIL"; \
else \
echo "UNKNOWN"; \
fi; \
else \
echo "$$test: NOT RUN"; \
fi; \
done
# 모든 테스트 결과 확인
check-all-results:
@for group in $(TEST_GROUPS); do \
$(MAKE) check-$$group-results; \
echo ""; \
done
# 특정 그룹 정리
clean-%-tests:
@echo "Cleaning $* test files..."
@group_tests=$$(echo $($(shell echo $* | tr '[:lower:]' '[:upper:]')_TESTS)); \
for test in $$group_tests; do \
rm -f tests/userprog/$$test.output tests/userprog/$$test.errors tests/userprog/$$test.result; \
done
# 모든 테스트 파일 정리
clean-all-tests:
@echo "Cleaning all test files..."
@for group in $(TEST_GROUPS); do \
$(MAKE) clean-$$group-tests; \
done
# =================================================================
# 편의 기능들
# =================================================================
# 테스트 그룹 정보 표시
list-tests:
@echo "Available test groups:"
@for group in $(TEST_GROUPS); do \
echo " $$group: $$(echo $($(shell echo $$group | tr '[:lower:]' '[:upper:]')_TESTS))"; \
done
# 실패한 테스트들만 재실행
rerun-failed:
@echo "=== Re-running Failed Tests ==="
@failed_tests=""; \
for group in $(TEST_GROUPS); do \
group_tests=$$(echo $($(shell echo $$group | tr '[:lower:]' '[:upper:]')_TESTS)); \
for test in $$group_tests; do \
if [ -f tests/userprog/$$test.result ] && grep -q "FAIL" tests/userprog/$$test.result 2>/dev/null; then \
failed_tests="$$failed_tests $$test"; \
fi; \
done; \
done; \
if [ -n "$$failed_tests" ]; then \
$(MAKE) test-custom TESTS="$$failed_tests"; \
else \
echo "No failed tests found."; \
fi
# 도움말
help-tests:
@echo "Test System Usage:"
@echo ""
@echo "Test Groups:"
@echo " test-create - Run all create tests"
@echo " test-open - Run all open tests"
@echo " test-write - Run all write tests"
@echo " test-read - Run all read tests"
@echo " test-close - Run all close tests"
@echo " test-exec - Run all exec tests"
@echo ""
@echo "Quick Tests (shorter timeout):"
@echo " test-create-quick - Run create tests quickly"
@echo " test-open-quick - Run open tests quickly"
@echo " test-write-quick - Run write tests quickly"
@echo " (etc...)"
@echo ""
@echo "Individual Tests:"
@echo " test-single TEST=test_name - Run specific test"
@echo " test-custom TESTS=\"t1 t2 t3\" - Run custom test list"
@echo ""
@echo "Results & Management:"
@echo " check-create-results - Check create test results"
@echo " check-all-results - Check all test results"
@echo " clean-create-tests - Clean create test files"
@echo " clean-all-tests - Clean all test files"
@echo " list-tests - List all available tests"
@echo " rerun-failed - Re-run only failed tests"
꽤 길다.
정확히는 Makefile을 수정하면 test case를 만들 수 있겠다 생각하여,
AI를 이용해 Makefile을 수정하면 됬다.
/workspaces/pintos-userprog/pintos/userprog/build
Makefile 맨 밑에 위 코드를 추가 해주고
make test-open
make test-read
make test-create
이런 식으로 테스트 진행하면 원하는 테스트 셋만 빠르게 확인된다.

(make test-open)
하지만 문제가 있다.

FAIL 임에도 불구하고 PASS라는 문구가 뜬다.
물론, 이걸 해결하면 좋겠으나 일단 확인이 가능하기에, 시간의 여유가 좀 더 생겼을 때 수정하자...
모든 테스트 케이스들을 확인하며 확인해야 했던 케이스들을 내가 원하는 테스트 셋으로 확인하는 방법으로 수정.
SYS_CLOSE
void close(int fd) {
/*
종료될 때 생각해야 할 것
1. fd의 값이 유효한 값인가?
2. 중복 close가 될 수 있기 때문에 status가 이미 닫힌 상태인가?
3. bad_fd를 방지하기 위해서는 fd값이 유효하지 않다는 것인데
*/
struct thread *curr = thread_current();
if (fd < FILE_START || fd > FILE_END )
return;
struct file *close_file = curr->fdt[fd];
if (close_file == NULL)
return;
lock_acquire(&file_lock);
file_close(close_file);
curr->fdt[fd] = NULL; /* 사용 후 NULL로 값 변경 */
lock_release(&file_lock);
}
이런식으로 어떤 방어 코드를 작성 해야할지
생각해보고 적고, 코드로 구현하고 안되면 생각해보고 찾아보고, 반복하고 있다.
SYS_READ
int read(int fd, void *buffer, unsigned size) {
/* lock이 필요하지 않을 것 같음 */
/*
처리해야 할게 무엇일까?
인자값들이 유효한가?
1. fd가 유효한가?
2. buffer가 null인가?
3. buffer의 주소가 유효한가?
4. 불러온 file이 null인가?
*/
int result;
check_bad_ptr(buffer); // bad_ptr일 경우 exit 종료
if (fd == 0) {
*(char *)buffer = input_getc();
result = strlen(buffer);
}
else if (fd < FILE_START || fd > FILE_END) {
return ERROR_NUM;
}
else {
struct file *curr_fd = thread_current()->fdt[fd];
if (curr_fd == NULL)
return ERROR_NUM;
lock_acquire(&file_lock);
result = file_read(fd, buffer, size);
lock_release(&file_lock);
}
return result;
}
read 또한 방어 코드를 생각해보고 적어보고, 테스트를 실행 해보는데
아니?
read-normal.c
void
test_main (void)
{
check_file ("sample.txt", sample, sizeof sample - 1);
}
test 케이스가 어떻게 구현되었는지 확인
lib.c
void
check_file (const char *file_name, const void *buf, size_t size)
{
int fd;
msg("this is error point? in check file\n");
CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification",
file_name);
msg("passed CHECK in check_file\n");
check_file_handle (fd, file_name, buf, size);
msg ("close \"%s\"", file_name);
close (fd);
}
check_file로 들어와 보았더니 open을 먼저 실행하는구나.

분명 sample.txt이라는 파일로 잘 들어와 filesys_open을 잘 실행하는데 왜 open_file이 null일까?
도대체 왜?
filesys_open은 성공 시 파일 반환, 실패 시 NULL 값을 반환한다.
팀원 중에 이걸 구현한 형님이 "check_file -> check_file_handle 때문에 그럴거야." 라고 하여

syscall.c에 filesize가 구현이 안되어 있다면 실행이 안될 것이다.
SYS_FILESIZE
int filesize(int fd) {
return get_filesize(fd);
}
static int get_filesize(int fd) {
struct file *curr_fd = thread_current()->fdt[fd];
if (fd < FILE_START
|| fd > FILE_END
|| curr_fd == NULL)
return ERROR_NUM;
return file_length(curr_fd);
}
이미 구현을 해놨는데 그리고 방어 코드도 틀린게 없는거 같은데...
자꾸 되지 않는다.
이것 때문에 하루를 통째로 날렸지만 아직도 해결하지 못하고 있다.
분명 무언가가 잘못 되었는데 무엇이 잘못 되었는지 아직 찾지 못해 참....
부디 내일은 반드시 해결한다.
