[OS] xv6 사용법 정리
- [ CS기초 ]/운영체제
- 2022. 9. 7.
0. 쉘 명령어(커널명령어) 추가
Makefile의 UPROG와 EXTRA부분에 다음과 같이 추가해준다.
1)
UPROG=\
...
_new\
2)
EXTRA=\
umalloc.c new.c\
1. 파일 추가
README file in Xv6 is a generic file too. Searching for occurrences of README in the MakeFile and adding your required file will be sufficient.
Suppose new.txt is the file you want to add.
Parts of the MakeFile to be changed are:
1)
fs.img: mkfs README new.txt $(UPROGS)
./mkfs fs.img README new.txt $(UPROGS)
2)
PRINT = runoff.list runoff.spec README new.txt toc.hdr toc.ftr $(FILES)
3)
EXTRA=\
mkfs.c ulib.c user.h cat.c echo.c forktest.c grep.c kill.c\
ln.c ls.c mkdir.c rm.c stressfs.c usertests.c wc.c zombie.c\
printf.c umalloc.c\
README new.txt dot-bochsrc *.pl toc.* runoff runoff1 runoff.list\
.gdbinit.tmpl gdbutil\
https://stackoverflow.com/questions/47250441/add-a-generic-file-in-xv6-makefile
2. xv6 커널 이해
- user.h : xv6의 시스템 콜 정의
- usys.S : xv6의 시스템 콜 리스트
- syscall.h : 시스템 콜 번호 매핑 -> 새 시스템 호출을 위해 새로운 매핑 추가
- syscall.c : 시스템 호출 인수를 구문 분석하는 함수 및 실제 시스템 호출 구현에 대한 포인터
- sysproc.c : 프로세스 관련 시스템 호출 구현 -> 여기에 시스템 호출 코드 추가
- proc.h : struct proc 구조 정의 -> 프로세스에 대한 추가 정보 추적을 위해서 구조 변경
- proc.c : 프로세스 간의 스케줄링 및 컨텍스트 전환을 수행하는 함수
3. xv6에 시스템 콜 추가하기
시스템 콜이란, 프로그램이 OS의 도움을 필요로 할 때 사용하는 api이다. OS에서 UI와 서비스 사이에 위치하여 라이브러리 등을 사용할 때 내부적으로 호출된다. 리눅스의 경우 시스템 콜 호출시 interrupt가 발생하고, 시스템 콜 테이블에서 어떤 시스템 함수를 실행할지 매칭되어 실행된다.
<pdf 사진>
(1) sysproc.c에 새로운 시스템 콜 구현부(ex-int newcall() {...})를 추가한다.
(2) syscall.h에 SYS_newcall을 define해주고, syscall.c에 sys_newcall을 등록해준다. (extern~ 부분, [] ~, 부분)
- syscall.h에서는 시스템 콜 테이블에 시스템 호출 번호를 매핑한다 (새 시스템 호출을 위해서 새로운 매핑을 추가한다)
- syscall.c에 정의된 syscall() 함수는 매핑된 함수 포인터를 호출한다.
(3) user.h에 newcall을 선언해준다.
(4) usys.S에 newcall을 등록해준다.
- SYSCALL(newcall) 매크로를 통해서 newcall 시스템 콜을 정의한다. SYSCALL 매크로는 syscall.h에 정의되어 있는 SYS_name에 해당하는 번호를 레지스터에 저장한다.
+ Makefile에도 추가해주어야 하지 않나?
+ proc.c에서는 프로세스 간의 스케줄링 및 컨텍스트 전환을 수행한다. proc.h.는 struct proc 구조를 정의한다.
4. 시스템 콜에 argument 전달
유저레벨 함수의 인자로 넘겨준 변수를 커널레벨에서 받아서 사용할 수 없다.
int
sys_kill(void)
{
int pid;
if(argint(0, &pid) < 0)
return -1;
return kill(pid);
}
sysproc.c에 정의되어 있는 sys_kill 함수이다. kill 시스템콜은 일반적으로 kill 다음에 pid를 붙여서 특정 프로세스에 시그널을 보내는데 사용한다.
그런데 위에 있는 sys_kill함수를 보면 매개변수로 아무것도 넘어오지 않음(void)을 알 수 있다. 대신 argint/argptr/argstr이라는 xv6의 built-in function을 사용한다. 위와 같이 argint(0, &pid)라고 쓰면 0번째 argument를 &pid로 전달한다는 것.
프로그램이 실행을 위해서 메모리에 올라가면 프로세스
proc.c는 프로세스를 관리하는 자료구조 관련 정의(process control block)
프로세스마다 code-data-heap-stack-kernel의 구조를 가지는데, 각 프로세스는 각각 user모드와 커널모드 공간의 일부를 조금씩 차지한다. 이 두공간을 왔다갓다하면서 관리해주는 역할을 os가 하는데, 이때 시스템콜을 이용하여 커널공간에 약속된 요청을 함.
5. proc 구조체의 초기화
자바랑 다르게 c에서는 초기화를 하지 않으면 메모리에 쓰레기값이 들어간다. 따라서 proc구조체의 mask값이 trace 여부를 판단하는 값으로 의미를 가지려면 mask값이 초기화가 이루어져야 한다. 그런데 c는 구조체의 초기값을 설정해주는 방법이 따로 없기에, 구조체 선언시마다 초기화를 해주거나, memset을 이용하여 메모리를 초기화해주어야 한다. 그런데 막상 커널을 돌리면서 mask값을 출력해보면 모두 0으로 나온다. 여기서 두가지 사실을 추측해볼 수 있다.
1. proc구조체는 어떤 방법으로든 초기화가 이루어진다.
2. 내가 임의로 추가한 mask값도 초기화되는 것을 보니 memset()을 사용할 것이다.
이 두가지에 기반하여 코드를 좀 뒤져보았다.
먼저 proc.c의 userinit()은 신규 생성되는 child 프로세스를 컨트롤하는 부분인데, 이 함수를 살펴보면 allocproc()을 통해서 process table에서 unused proc이 있다면 그걸 할당받고 kernal stack을 구성한다.
그런데 allocproc()과 userinit() 모두 memset()을 사용하여 각각 proc구조체의 context, tf부분만을 초기화하고 있었다. 그러면 내가 임의로 추가한 mask는 대체 어디서 초기화된 것일까?
참고 1. https://groups.google.com/g/minix3/c/jwxyrm-bcuo
참고 2. http://www.iamroot.org/xe/index.php?mid=Kernel&document_srl=176813
6. malloc()의 메모리 할당
umalloc.c를 살펴보면 malloc(), morecore(), free()라는 세 함수가 있다. 지금 내가 궁금한건 왜 16384바이트나 되는 메모리를 malloc()을 통해 할당해도 4096밖에 되지 않는 메모리가 할당되는지가 궁금하다.
(메모리가 더 할당되는 것은 malloc자체에 메모리가 필요하다와 같이 추측이라도 가능하지만, 덜 할당되는것은 도저히 이해가 안갔다)
morecore()에 보면 if(nu<4096) nu = 4096라는 코드가 있는데, nu는 malloc()에서 morecore()를 호출하면서 인자로 넘어온다. 이 넘어온 인자는 nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;로 정의되어 있다. (nbytes는 malloc에 넘겨준 인자(byte를 의미))
sizeof(Header)가 관건이므로 출력해보도록 하자.
8이다. 따라서 대충 계산해보면 malloc할당 요청한 바이트수를 N이라고 하면, (N+8/8)+1이 morecore의 인자로 넘어가는 것을 알 수 있다. N이 대충 크다면 대충 N/8정도가 인자로 넘어가는 것.
따라서 if(nu<4096) nu = 4096 이걸 해석해보면 N이 4096*8 = 32768정도 이하일때는 nu가 N값에 상관없이 4096이 되어 sbrk()로 4096*8이 할당되고, 약 32768 이상이라면 nu가 그대로의 값을 가져 sbrk()에 의해 nu*8만큼, 즉 N만큼이 할당된다.
근데 왜 4096바이트가 할당되는거지? xv6 sbrk에 대해 찾아보면(https://www.cs.ucr.edu/~heng/teaching/cs179f-winter21/lab2.html)sbrk(n)은 데이터 세그먼트의 크기를 조절하여 사용가능한 메모리를 n"바이트"만큼 증가시킨다고 되어있다. 그러면 4096*8=32768 바이트가 고정적으로 할당되어야 되는것 아닌가? 그런데 pdf에는 proc의 sz 단위가 바이트가 아닌 비트라고 했고, 실제로 늘어난 크기는 4096바이트이다. 뭐지
-> 과제 명세가 틀렸음. 내가 생각한게 맞는듯
솔직히 세부적인건 더 이해가 안간다. freep는 뭐고 base는 뭐고 morecore는 왜 freep를 반환하는거고, 왜 굳이 그대로 안옮겨주고 (N+8/8)+1로 넘겨줬다가 다시 8을 곱해서 (N/8)*8과 같이 할당하는거고..
7. xv6 스케줄링
xv6커널은 모든 프로세스를 proc구조체를 통하여 관리하는데, proc구조체는 ptable이라는 proc구조체의 배열로 관리된다.
struct {
struct spinlock lock;
struct proc proc[NPROC];
} ptable;
ptable은 위와 같은 구조로 되어있는데, 5번에서 이야기했듯 userinit()이 allocproc()함수를 통해서 할당할 수 있는 proc구조체가 있는지 확인하고 할당받는다. 할당받았다면 pid와 kernel stack을 같이 할당받는데, 이를 통해 해당 프로세스는 스케줄링될 때 실행될 함수와 레지스터를 미리 준비해놓는다.
// Per-process state
struct proc {
uint sz; // 프로세스 크기(byte)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // 프로세스 상태
int pid; // pid
struct proc *parent; // 부모 프로세스
struct trapframe *tf; // 커널영역에서 유저영역으로 돌아올 수 있게 레지스터를 저장
struct context *context; // 스케줄링에 필요한 레지스터를 저장
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // 프로세스명
};
scheduler함수가 호출되면, scheduler는 무한루프를 돌면서 runnable한 process를 찾아나간다. runnable 프로세스를 찾으면 현재 struct cpu의 proc멤버를 해당 프로세스의 포인터로 대체하고, scheduler 멤버를 old context로 swtch함수를 호출한다. swtch함수는 old context로 입력 들어온 scheduler 멤버를 현재 kernel stack의 esp로 덮어쓰고, new context로 cpu register를 변조한다. 마지막으로 swtch의 retn을 통해 해당 프로세스의 next instruction을 실행해 나간다.
현재 time_slice값은 10,000,000ns(10ms)이므로, 10ms 이후 time interrupt가 발생하면 trap함수는 현재 프로세스가 running stae일때 yield함수를 호출하여 struct cpu의 scheduler 멤버로 context switch, cpu의 권한이 다시 kernel의 scheduler 함수로 넘어간다.
이렇게 xv6는 10ms timer interrupt가 발생할 때마다 context switch를 호출하고 있었고, 이를 round-robin방식이라고 한다.
'[ CS기초 ] > 운영체제' 카테고리의 다른 글
[OS] 멀티쓰레드와 멀티프로세스, 멀티코어 (0) | 2024.06.30 |
---|---|
[OS] 쉘과 init.c, fork-exec (0) | 2022.09.08 |