일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 프로그래머스
- 스프링
- 셀레니움
- 개발자 디자인
- nurigo
- 인턴
- 문자 인증
- DNS동작원리
- excalidraw
- Executors
- 위키 꾸미기
- 사이드 프로젝트
- NAVER D2
- 스프링 회원가입 인증
- 스트림
- 현장실습 IT
- 정렬 기준
- 숭실대
- Spring
- aws ec2
- 현장실습
- 잘하고싶다..
- 위키 탭
- 값 객체
- OpenAI
- Java
- 스터디 관리
- 자바
- ssh
- REST
- Today
- Total
뭐요
예제로 알아보는 xv6에서 시스템 호출 구현하기 (1) 본문
예제로 알아보는 xv6에서 시스템 호출 구현하기
시스템 호출 구현
xv6에서 시스템 호출을 구현해보자. 직접 시스템 호출을 통해 호출한 프로세스의 메모리 사용량을 출력하는 memsize()를 구현해보려고 한다.
우리의 목표
- memsize() 시스템 콜 추가 및 호출하는 쉘 프로그램 구현
들어가기 전에
기본 개념
새로운 시스템 호출을 구현하기 위해서, 기존에 있는 시스템 호출이 어떻게 구현되어 있는 지 확인하고 그대로 따라할 예정이다.
시스템 호출 종류는 다음과 같다.
- 인자가 없는 시스템 호출 EX) sysproc.c 내 구현된 uptime()
- 문자열 및 정수 인자가 있는 시스템 호출 EX) sysfile.c 내 구현된 open()
- 구조체 인자가 있는 시스템 호출 EX) fstat()
새롭게 만들 시스템 호출 memsize()와 trace는 각각 인자가 없는 시스템 호출, 문자열 및 정수 인자가 있는 시스템 호출이다. 시스템 호출을 하는 데 있어서, 인수를 전달하기 위해 일반적인 방식으로는 불가능하다. 이는 User-level function에서 Kernel-level function으로 바로 argument를 전달할 수 없기 때문인데 자세한 사항은 아래 결과 설명하면서 구체적으로 설명할 예정이다.
xv6 커널 이해
xv6 커널에 대해 이해해보자. 새로운 시스템 호출을 추가하기 위해서는 “proc.c”, “proc.h”, “syscall.c”, “syscall.h”, “sysproc.c”, “user.h”, “usys.h”에 대해서 수정이 필요하다.
“user.h”
xv6의 시스템 호출 정의
“usys.S”
xv6의 시스템 호출 리스트
“syscall.h”
시스템 호출 번호 매핑 → 새 시스템 호출을 위해 새로운 매핑 추가해야 함
“syscall.c”
시스템 호출 인수를 구문 분석하는 함수 및 실제 시스템 호출 구현에 대한 포인터
“sysproc.c"
프로세스 관련 시스템 호출 구현 → 시스템 호출 코드 추가해야 함
“sysproc.c"
struct proc 구조 정의되어 있음 → 프로세스에 대한 추가 정보를 추적을 위해 구조 변경
“proc.c”
프로세스 간의 스케줄링 및 컨텍스트 전환을 수행하는 함수
memsize() 시스템호출 구현
호출한 프로세스의 메모리 사용량을 출력하는 memsize() 시스템 호출을 구현해보자.
1) user.h에서 memsize() 추가
struct stat;
struct rtcdate;
// system calls
int fork(void);
int exit(void) __attribute__((noreturn));
int wait(void);
int memsize(void); // 새로운 시스템 호출 추가
2) usys.S에서 memsize() 추가
SYSCALL(getpid)
SYSCALL(sbrk)
SYSCALL(sleep)
SYSCALL(uptime)
SYSCALL(memsize) // 새로운 시스템 호출 추가
3) syscall.h에서 memsize() 추가
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
'
'
'
#define SYS_close 21
#define SYS_memsize 22 // 새로운 시스템 호출 번호 추가
4) syscall.c에서 memsize() 추가
extern int sys_wait(void);
extern int sys_write(void);
extern int sys_uptime(void);
extern int sys_memsize(void); // 새로운 시스템 호출 추가
'
'
'
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_memsize] sys_memsize, // 새로운 시스템 호출 추가
5) sysproc.c에서 memsize 시스템 콜 구현
// return memory size
int
sys_memsize(void)
{
uint size;
size = myproc()->sz;
return size;
}
해당 코드를 추가한다.
proc 구조체는 다음과 같이 정의 되어있다.
// Per-process state
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
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]; // Process name (debugging)
};
위 과정을 거치면서 memsize() 시스템 호출을 새롭게 구현했다. 이제 memsize()가 정상적으로 동작하는 지 쉘 프로그램을 구현함으로써 확인해본다.
6) memsizetest 쉘 프로그램 구현
memsizetest.c 파일을 만든다.
#include "types.h"
#include "stat.h"
#include "user.h"
#define SIZE 2048
int main(){
int msize = memsize();
printf(1, "The process is using %dB\\n", msize);
char *tmp = (char *)malloc(SIZE * sizeof(char));
printf(1, "Allocating more memory\\n");
msize = memsize();
printf(1, "The process is using %dB\\n", msize);
free(tmp);
printf(1, "Freeing memory\\n");
msize = memsize();
printf(1, "The process is using %dB\\n", msize);
exit();
}
Makefile을 수정하여 memsizetest.c 파일도 make 시 컴파일 되도록 변경한다.
⇒ Makefile에서 UPROGS, EXTRA 부분 수정
7) memsizetest 실행 결과 테스트
$ memsizetest
The process is using 12288B
Allocating more memory
The process is using 45056B
Freeing memory
The process is using 45056B
8) 실행 결과

정상적으로 작동한다.
지금까지 새로운 시스템 호출인 memsize()를 정의하여 xv6에 추가하고 쉘 프로그램을 작성하여 잘 작동이 되는 지 확인해보았다.
더 나아가기
memsizetest.c 에서 현재 메모리 사용량을 출력하고, malloc을 이용한 동적 메모리 할당 후 메모리 사용량을 한 번 더 출력했다.
#define SIZE 2048
char *tmp = (char *)malloc(SIZE * sizeof(char));
여기서 핵심은 2048 바이트 크기의 동적 메모리 할당을 해주었으므로 malloc 전, 후로는 2048 바이트의 차이가 나야 하지만, 2048이 아닌 4096 바이트 차이가 나온다. ( (45056 - 12288) / 8 = 4096 )
umallo.c의 malloc() 함수를 분석하면서 이유를 알게 되었다.
malloc() 함수는 다음과 같다.
void* malloc(uint nbytes)
{
Header *p, *prevp;
uint nunits;
nunits = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1;
if((prevp = freep) == 0){
base.s.ptr = freep = prevp = &base;
base.s.size = 0;
}
for(p = prevp->s.ptr; ; prevp = p, p = p->s.ptr){
if(p->s.size >= nunits){
if(p->s.size == nunits)
prevp->s.ptr = p->s.ptr;
else {
p->s.size -= nunits;
p += p->s.size;
p->s.size = nunits;
}
freep = prevp;
return (void*)(p + 1);
}
if(p == freep)
if((p = morecore(nunits)) == 0)
return 0;
}
}
해당 코드 등장하는 morecore()가 핵심이다. 해당 함수는 다음과 같다.
morecore(uint nu)
{
char *p;
Header *hp;
if(nu < 4096)
nu = 4096;
p = sbrk(nu * sizeof(Header));
if(p == (char*)-1)
return 0;
hp = (Header*)p;
hp->s.size = nu;
free((void*)(hp + 1));
return freep;
}
malloc() 함수에서 morecore()의 인자로 nunits를 넣어주는데, nunits가 4096이 안되면 4096으로 할당해주기 때문이다! 따라서 우리가 기대하는 2048바이트가 아닌 4096바이트가 나오는 것이고 해당 방식으로 짜여져 있는 이유는 page size 때문이다.
xv6에서 page size는 4096인 걸 알 수 있다. 현재 4096 - 2048 byte만큼의 interner fragmentation이 발생하게 되는데, 무슨 소리인 지 모르겠다면 아래 블로그에서 간단 명료하게 잘 설명이 되어있으니 참고하기 바란다!
내부 단편화
'Operating System > xv6' 카테고리의 다른 글
예제로 알아보는 xv6에서 시스템 호출 구현하기 (2) (0) | 2023.02.28 |
---|---|
xv6에서 로그인 구현 (0) | 2023.02.28 |
xv6에서 hcat 쉘 프로그램 작성 (0) | 2023.02.28 |
xv6에서 helloworld 쉘 프로그램 작성 (0) | 2023.02.28 |
[xv6] writing 1 byte into a region of size 0 에러 (2) | 2022.09.11 |