거북이의 쉼터

(2022.03.23 ~ 03.24) COW 구현 (4) 본문

코딩 삽질/KAIST PINTOS (CS330)

(2022.03.23 ~ 03.24) COW 구현 (4)

onlim 2022. 3. 24. 16:28

1. 서론 및 필요 내용 설명

질문에 대한 답변이 왔다.

일단 mmap에 관한 부분에서는 꼭 fork가 아니더라도 단순히 다른 프로세스가 같은 파일을 mmap 하는 경우를 생각해볼 수 있습니다. 그래서 프로세스 A, B가 같은 파일을 mmap해서 막 다루는 상황을 가정하였을때, A가 B보다 먼저 munmap을 한다고 생각해보겠습니다.

그러면 말씀하신 부분은 그냥 lab3 설명에 있는 When a mapping is unmapped, whether implicitly or explicitly, all pages written to by the process are written back to the file, and pages not written must not be. 이 부분을 그대로 생각해서 그대로 구현하시면 될 것 같습니다.

- A가 B가 둘다 어떤 page를 수정을 안하였다면 write back을 할 필요가 없고
- A가 어떤 page를 수정을 하고 B가 그 page를 수정을 안하였다면 A가 수정한 부분이 파일에 반영이 되고 B는 그 부분에 대해서는 write back을 안하기 때문에 그대로 A가 수정한 부분이 반영이 될것이고
- A는 어떤 page를 수정하지 않고 B만 그 page를 수정한다면 늦게 끝나는 B가 수정한 부분이 파일에 반영이 될 것입니다.
- 마지막으로 A가 어떤 page를 수정하였고 B도 그 page를 수정하였다면, A가 수정한 내용이 일단 파일이 반영에 되겠지만 더 늦게 munmap을 한 B 또한 수정을 진행하였기 때문에 B가 수정한 내용이 파일에 최종적으로 반영이 되겠습니다.

 

친절한 조교님... 그저 빛... 이러면 고칠게 없다. 휴....

 

그럼 다시 테스트 케이스로 복귀를 하면 되는데 이건 내일, 그러니까 24일에 짜도록 하자.

 


2. 구현해야 하는 것

  • COW를 테스트하기 위한 테스트 케이스

 


3. 구현 과정

지난 포스팅에 테스트를 끼워 넣는 건 어떻게 할 수 있는지를 다루었으니, 이번에는 실제 테케를 만들어보자. 생각해 볼 수 있는 기본적인 테스트 케이스는 아래와 같은 것들이다.

 

  • file-backed page에 대한 COW 검사
  • 프로세스를 여러번 fork했을 때 모두 같은 프레임을 공유하는지 검사
  • 여러번 fork한 상황에서 하나가 write에 의해 분리되더라도 나머지의 공유는 유지되는지 검사
  • lazy load 되었을 때, 또는 swap-out 후 swap-in 될 때 공유가 유지되는지 검사
  • 스택 공간의 공유

등등... 각 경우를 검사하면 될 것 같다. 

3-1. file-backed page에 대한 COW 검사

다음과 같이 cow-simple과 비슷하게 구현하였다.

#include <string.h>
#include <syscall.h>
#include <stdio.h>
#include <stdint.h>
#include "tests/lib.h"
#include "tests/main.h"
#include "tests/vm/sample.inc"

void
test_main (void)
{
	int handle;
	int slen = strlen (sample);
	pid_t child;
	void *pa_parent, *pa_child;
	void *map;
	char *actual = (char *) 0x10000000;

	CHECK (create ("sample.txt", strlen (sample)), "create \"sample.txt\"");
	CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
	CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\"");
	close (handle);

	CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\"");
	CHECK ((map = mmap (actual, 4096, 1, handle, 0)) != MAP_FAILED, "mmap \"sample.txt\"");

	CHECK (memcmp (actual, sample, slen) == 0, "check data consistency");

	pa_parent = get_phys_addr((void*)actual);

	child = fork ("child");
	if (child == 0) {
		CHECK (memcmp (actual, sample, slen) == 0, "check data consistency");

		pa_child = get_phys_addr((void*)actual);
		CHECK (pa_parent == pa_child, "two phys addrs should be the same.");

		actual[0] = '@';
		CHECK (memcmp (actual, sample, slen) != 0, "check data change");

		pa_child = get_phys_addr((void*)actual);
		CHECK (pa_parent != pa_child, "two phys addrs should not be the same.");

		munmap(map);
		return;
	}

	wait (child);
	CHECK (pa_parent == get_phys_addr((void*)actual), "two phys addrs should be the same.");
	CHECK (memcmp (actual, sample, strlen (sample)) == 0, "check data consistency"); // the parent data should not be contaminated
	
	munmap(map);
	return;
}

3-2. 스택 공간의 COW 검사

첫 번째 스택은 별다른 행동을 하지 않아도 syscall에서 반환되면서 각 프로세스에서 변경 요청을 하기 때문에 바로 공유가 해제되면서 공유 상태에 있는 것을 확인할 수가 없다. 때문에 두 번째 스택에서부터 실험을 할 수 있다.

// The first stack page always gets faulted by syscall procedure
// Use the second stack page

#include <string.h>
#include <syscall.h>
#include <stdio.h>
#include <stdint.h>
#include "tests/lib.h"
#include "tests/main.h"
#include "tests/vm/sample.inc"

void *pa_parent;
void *pa_child;
pid_t child;

#define CHUNK_SIZE 4096 * 2

void
test_main (void)
{
	char BIG_STACK[CHUNK_SIZE] = {0, };

	pa_parent = get_phys_addr((void*)BIG_STACK + 0x1000);
	
	child = fork ("child");
	if (child == 0) {
		pa_child = get_phys_addr((void*)BIG_STACK + 0x1000);

		CHECK (pa_parent == pa_child, "two phys addrs should be the same.");
		BIG_STACK[0x1000] = 'A';
		msg ("modified stack value.");
		pa_child = get_phys_addr((void*)BIG_STACK + 0x1000);
		CHECK (pa_parent != pa_child, "two phys addrs should not be the same.");
		return;
	}
	wait (child);
	CHECK (pa_parent == get_phys_addr((void*)BIG_STACK + 0x1000), "two phys addrs should be the same.");
	return;
}

3-3. multi-fork COW 검사

fork를 했을 때 spt가 유지가 되어야 하기 때문에 따로 child를 만들어 exec 시킬 수는 없고, fork 반환 값으로 루틴을 구분해 아래처럼 같은 프레임 공유와 연결 해제 부분을 검사하도록 한다.

/* Checks if fork is implemented properly with copy-on-write */

#include <string.h>
#include <syscall.h>
#include <stdio.h>
#include <stdint.h>
#include "tests/lib.h"
#include "tests/main.h"
#include "tests/vm/large.inc"

#define CHUNK_SIZE (128 * 1024)
#define CHILD_CNT 20

void
test_main (void)
{
	size_t i;
	pid_t children[CHILD_CNT];

	void *pa_parent;
	void *pa_child;
	char *buf = "Lorem ipsum";

	CHECK (memcmp (buf, large, strlen (buf)) == 0, "check data consistency");
	pa_parent = get_phys_addr((void*)large);

	for (i = 0; i < CHILD_CNT; i++) {
		children[i] = fork ("child-linear");
		if (children[i] == 0) {
			if (memcmp (buf, large, strlen (buf)) != 0)
				fail ("check data consistency");

			pa_child = get_phys_addr((void*)large);
			if (pa_parent != pa_child)
				fail ("two phys addrs should be the same.");

			large[0] = '@';
			if (memcmp (buf, large, strlen (buf)) == 0)
				fail ("check data change");

			pa_child = get_phys_addr((void*)large);
			if (pa_parent == pa_child)
				fail ("two phys addrs should not be the same.");

			exit(0x42);
		}
	}

	for (i = 0; i < CHILD_CNT; i++) {
		CHECK (wait (children[i]) == 0x42, "wait for child %d", i);
	}

	CHECK (pa_parent == get_phys_addr((void*)large), "two phys addrs should be the same.");
	CHECK (memcmp (buf, large, strlen (buf)) == 0, "check data consistency");
	return;
}

3-4. lazy loading 되기 전 fork, 후 접근

해당 경우에는 semaphore를 통해 통제하려고 했으나, 생각해보니 모든 것이 복제되는 특성상 semaphore로 통제한다는 것이 어불성설이었다. 이에 timer_sleep()를 통해서 child의 실행 시점을 늦추려고 했으나, 해당 함수에 대한 참조가 이루어지지 않았을 뿐더러, 생각해보니 어떻게 통제를 하더라도 복사 이후의 주소를 다른 프로세스로 넘기는 과정이 유저 입장에서는 몹시 어렵다는 것을 깨달았다. 따라서 해당 케이스는 폐기하였다.

3-5. swap-out 후 fork, 후 접근

해당 경우도 마찬가지다. 사실 이걸 보고 싶었는데, 너무 복잡한 것 같다.

 


4. 디버깅

이제 테스트 케이스 구현이 끝났으니, 실행을 해 보자. 

Functionality of copy-on-write (tests/vm/cow/Rubric):
        - Basic functionality for copy-on-write.
             1/ 1 tests/vm/cow/cow-simple
             1/ 1 tests/vm/cow/cow-stack
             1/ 1 tests/vm/cow/cow-file
             1/ 1 tests/vm/cow/cow-multifork

        - Section summary.
              4/  4 tests passed
              4/  4 points subtotal

다행히 모두 잘 돌아간다. 물론 모든 경우를 다 다룬 것은 아니지만 이 정도면 할 만큼 했다.

 

아이 행복해라~

 


5. 후기

이로서 프로젝트 3의 진엔딩도 보았다. ㅈ망겜... 이제 대망의 프로젝트 4만 남아있다. 다른 애들은 이제 프로젝트 2 들어가서 fork 갖다가 씨름하고 있는데 난 여기까지 와서 뭐하는걸까 하.....

뭔가 이렇게까지 해야 하는가라는 자괴감이 든다...

Comments