정글에서의 8-9주차, project 2 user program을 마주하며...
이번 주의 user program은 주로 운영체제 세 가지 이야기 서적을 읽고, 손으로 공부했다. 종이에 지식을 정리하고, 지금까지 읽어온 CSAPP을 다시 찾아보고 추가로 정리하니 어지럽게 분산된 지식들이 조금씩 제자리를 찾아가는 것 같았다. 특히 권영진 교수님의 OS 강의가 재미있었다! 막연하기만 했던 pintOS나, OS의 개념들이 조금 더 눈에 잘 들어오게 되었다. OS가 왜 CS의 꽃이라 말하는지 짐작이 되었다. 프로그래밍에서 중요한 개념들을 집대성한 결과물이라서 그랬던 거였다.
Keywords
- User mode VS Kernel Mode
- Register VS Memory
- User Stack
- System Call
- File Descriptor
- Cache
- Atomic Operation
- rax register
- 32 bit OS VS 64 bit OS
- Interrupt
- Segmentation Fault - closer Project 3
추가 공부 키워드
- Process
Process Environment block (PEB)
Process identifier (PID) - x86_64 calling convention
- argument vector
- Executable Linkable Format (ELF) & loader
- System Call
- filesys related
- open, close, create, read, write, seek, tell, ...
- process related
- halt, exit, exec, fork, wait ..
- file descriptor
- file descriptor table
- dup2 syscall
Process
프로세스는 개별적으로 주소 공간을 가진다. user stack, memory-mapped, run-time heap, sement...
Process Environment block (PEB)
유저 레벨에서 프로세스에 대한 환경 정보를 담고 있는 구조체.
Process identifier (PID)
프로세스를 식별하는 식별
User Stack
프로그램이 최초 실행될 때 run time에 생성되며, 프로세스의 %rsp 레지스터가 stack pointer를 가리킨다.
User mode vs Kerner mode
유저 프로그램 등으로부터 OS를 보호하기 위하여 OS는 user mode와 kernel mode를 구분하여 권한을 제한한다. 모든 유저 프로그램은 user mode에서 작동하며 전권을 행사하는 kernel mode과는 달리 기능이 다수 제한된다. 특히 하드웨어 자원에 대한 접근 권한이 유저 모드와 커널 모드를 가르는 기준이다.
그렇다면 유저의 프로그램은 커널에서만 사용할 수 있는 특권 명령어를 사용하지 못하는 걸까? 그것은 아니다. OS는 kernel mode에서 사용 가능한 기능들을, system call 이라는 표준화된 인터페이스를 user program에게 제공한다.
Exception
Memory | Cache
Cache
캐시는 메인 메모리에서 자주 사용되는 데이터의 복사본을 저장하는 작고 빠른 메모리이다
CPU가 매번 메모리에 접근해서 데이터를 읽고 쓰면 많은 시간이 소모된다. 이런 프로세서(cpu)와 메모리 사이의 gap을 처리하기 위해 케시 메모리라는 더 작고 빠른 저장 장치를 CPU 내부에 만들어 두고, 메인 메모리 로부터 자주 사용하는 정보를 저장해두어 처리 속도를 향상시킨다. CPU는 데이터를 처리할 때 cache부터 검사하고 데이터가 존재한다면 빠르게 가져올 수 있게 된다.
메모리 계층구조(memory hierarchy)
메모리 계층구조에서 아래로 내려갈수록 더 많은 정보를 저장할 수 있으나 속도는 느려진다.
Cache Locality
캐시
프로그램이 인근 지역에 접근하는 경향성을, 즉 Access가 균일하지 않고 한순간에 특정 부분을 집중적으로 참조하는 특성을 지역성(locality)라고 한다. 지역성은 시간적 지역성과 공간적 지역성이 나뉜다.
- 시간적 지역성 Temporal Locality : 최근 참조된 주소의 내용은 근시일에 또다시 참조될 수 있다.
- 공간적 지역성 Spatial Locality: 인접한 데이터들이 연속적으로 엑세스 될 가능성이 높다. 이를 살려 캐시는 특정 메모리 주소가 포함된 블록을 전부 캐시에 저장한다.
Cache Hit | Cahce Miss | Cache Hit rate | Miss Rate
- 캐시 적중 : CPU가 엑세스하려는 데이터가 이미 캐시에 있을 때
- 캐시 미스 : 데이터가 캐시에 없어 메인메모리에 접근해야 할 때 (속도 느려짐)
- 캐시 적중률: CPU가 원하는 데이터가 캐시에 있을 확률(적중률/전체 기엊강체 엑세스 횟수)
- 미스율: CPU가 엑세스하려는 데이터가 캐시에 존재하지 않을 확률
캐시 개념은 OS에서도 사용되지만 다른 프로그래밍에서도 사용된다.
[!] 캐시 일관성 문제
single process with multi thread에서 발생하는 문제와 유사하다: 하나의 시스템에 여러 프로세서가 존재하고 하나의 공유 메인 메모리가 있을 때 동기화 문제가 발생할 수 있다.
Register vs Memory
레지스터 | 메모리 | |
위치 | CPU 칩 내부 (L0) | CPU와 별도, Address line & Data line으로연결됨 (L4) |
종류 | SRAM( static random access memory) 으로 구성 |
DRAM으로 구성 |
용량 | 적은 용량 | 대용량 |
보유중인 데이터 | CPU가 현재 처리중인 명령어와 데이터 (입출력 데이터) |
실행에 필요한 프로그램 명령어와 데이터 |
Register는 CPU 칩 내부에 존재하며, 문맥 교환시 등에 쓰이는 입출력 값을 저장한다. 메모리 계층구조에서는 L0 위치로 본다. 레지스터는 CPU의 데이터 처리 속도 높이기 위해 사용된다. CPU에게 가까이 있을 수록 Memory access time이 줄어드는데, register는 CPU 내부에 존재하므로 데이터 처리 속도가 빠르다. 메모리(RAM)은 CPU와는 분리되어 있으며, cpu에서 메모리의 주소를 참조하여 데이터를 읽고 쓴다. 비유하자면 레지스터는 내가 들 수 있는 (칸이 나뉘어진) 상자이고, 메모리는 창고인 것 같다.
rax register
함수의 반환 값(return 하는 값)을 주로 저장한다. 그 외에도 산술 연산 시에도 명령어/결과값을 저장한다.
또한 system call의 번호를 가리키는 레지스터이기도 하다.
rax register in PintOS
system call handler가 인자로 받은 인터럽트 프레임에서, 이 안의 범용 레지스터 R 안에 담긴 rax 내부로부터 system call number를 가져와 분기 처리를 한다.
/* The main system call interface */
void
syscall_handler (struct intr_frame *f) {
//printf("[syscall number] %d\n", f->R.rax);
#ifdef VM
/* When switched into kernel mode,
save stack pointer in thread'*/
thread_current()->intr_rsp = f->rsp;
//printf("[syscall checking] curr intr_rsp %p,\n",thread_current()->intr_rsp);
#endif
switch (f->R.rax) {
case SYS_HALT:
halt();
break;
case SYS_EXIT:
exit(f->R.rdi);
break;
case SYS_FORK:
f->R.rax = fork(f->R.rdi, f);
break;
case SYS_EXEC:
if (exec(f->R.rdi) < 0) {
exit(-1);
}
break;
case SYS_WAIT:
f->R.rax = wait(f->R.rdi);
break;
case SYS_CREATE:
f->R.rax = create(f->R.rdi, f->R.rsi);
//printf("[syscall] created file:%p \n", f->R.rax);
break;
case SYS_REMOVE:
f->R.rax = remove(f->R.rdi);
break;
case SYS_OPEN:
f->R.rax = open(f->R.rdi);
//printf("[syscall switch] fd?:%d\n", f->R.rax);
break;
case SYS_FILESIZE:
f->R.rax = filesize(f->R.rdi);
break;
case SYS_READ:
f->R.rax = read(f->R.rdi, f->R.rsi, f->R.rdx);
break;
case SYS_WRITE:
f->R.rax = write(f->R.rdi, f->R.rsi, f->R.rdx);
break;
case SYS_SEEK:
seek(f->R.rdi, f->R.rsi);
break;
case SYS_TELL:
f->R.rax = tell(f->R.rdi);
break;
case SYS_CLOSE:
close(f->R.rdi);
break;
/* project 3 : mmap and unmmap */
case SYS_MMAP:
{f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
break;}
case SYS_MUNMAP:
{void* res = f->R.rdi;
munmap(f->R.rdi);
break;}
default:
exit(-1);
}
//printf("[syscall] end \n");
}
System Call
user mode에서 사용되는 user program들이 kener mode에서만 실행 가능한 권한들(파일 읽고 쓰기 및 거의 모든 작업)을 실행하기 위해 process에게 제공하는 기능이다.
- trap이 시스템 콜을 호출
- 하드웨어가 트랩 핸들러에게 제어권을 넘기고, 특권 수준을 커널 모드로 격상 시스템 콜 시작
- 커널 모드로 진입한 OS는 HW 자원을 자유롭게 접근하여 쓰고 읽을 수 있다.(입출력/메모리 할당 등)
- 서비스 완료시 return-from-trap 명령어로 제어권을 user에게 넘김
- program에게 다시 제어권 넘김.
- 사용자 모드로 전환.
- 시스템 콜은 %rax에 각 시스템 콜 number가 저장되고, 개별 함수에 필요한 인자들은 %rdi, %rsi, %rdx ...순으로 이어지는 레지스터들에 담겨 전달 된다.
- Linux 에서 사용하는 system call table 은 이 링크에서 확인할 수 있다.
분류
filesys related : open, close, create, read, write, seek, tell, ...
process related : halt, exit, exec, fork, wait ..
System Call in PintOS
PintOS, project 2에서 확인 가능한 시스템 콜은 다음과 같다.
추억
'공부기록 > OS' 카테고리의 다른 글
[WIL / PintOS] [Project 2] System Call | others (0) | 2023.12.16 |
---|---|
[WIL / PintOS] [Project 2] Argument Passing | User Memory (0) | 2023.12.15 |
[TIL / WIL] [Project 1] MLFQS + Advanced Scheduler in PintOS (0) | 2023.12.04 |
[TIL][Project 1] Priority Donation (0) | 2023.12.04 |
[TIL][Project 1] Priority Scheduling , Synchronization + pintOS (0) | 2023.11.27 |