커널 모드가 아닌 사용자 모드에서 실행되는 응용 프로그램 및 프로세스
시스템 리소스에 대한 액세스가 제한되어 있으며 하드웨어 또는 특권 명령에 직접 액세스할 수 없습니다. 이는 운영 체제가 보안, 안정성 및 리소스 관리를 유지하는 데 필수적인 기능입니다.
프로젝트 1에서는 운영체제의 커널에서 모든 작업을 진행했다면, 이번 주 User Programs에서는 프로그램이 시스템 콜을 통해서 OS와 상호작용할 수 있도록 만들어야 합니다.
- init.c/main(): 커널 메인 스레드가 시작되어 명령줄을 읽고 구문 분석하고 run_actions 함수를 호출합니다.
- init.c/run_actions(argv): 해당 함수를 호출하여 argv[]에 지정된 작업을 실행합니다.
- init.c/run_task(argv): process_wait(process_create_initd(task))를 호출하여 지정된 작업 이름(file_name)으로 새 프로세스를 만들고 기다립니다.
- process.c/process_create_initd(file_name): 새로운 커널 스레드를 생성하여 새 프로그램을 실행하고 initd() 함수를 호출합니다.
- process.c/initd(file_name): 프로세스를 초기화하고 process_exec() 함수를 호출합니다.
- process.c/process_exec(file_name): 현재 실행 중인 사용자 프로세스를 새 실행 파일의 프로세스로 전환합니다.
- process.c/load(): 실행 파일을 ELF 파일 형식에 따라 메모리에 마운트하고, User Pool에 스택, 데이터, 코드를 생성 및 초기화하고, CPU에서 다음 명령어의 주소를 설정합니다. 프로그램의 시작 주소(_start() 함수)로. 또한 입력 매개변수를 사용자 스택에 올바르게 쌓습니다.
- process.c/process_activate(): 프로세스에 대한 커널 가상 메모리 주소(RSP0)의 시작점을 설정하여 사용자 프로세스가 커널 모드로 전환될 때 커널 스택 메모리에서 데이터를 빌드할 위치를 나타냅니다.
=> Pintos에서 새로운 User Program을 실행하는 것은 프로그램을 메모리에 로드하고, 사용자 및 커널 스택을 설정하고, 프로세스를 초기화하고, 운영 체제의 컨텍스트 내에서 실행되도록 하는 순서로 동작합니다.
User Program이 실행되기 전에, 커널은 무조건 해당 함수의 인자들을 스택에 저장
실행중인 프로세스의 레지스터 정보, 스택 포인터, Instruction Count를 저장하는 자료구조
인터럽트나 시스템 콜 호출 시 사용
esp는 높은 메모리 주소에서 낮은 메모리 주소로 이동하면서 인자를 스택에 삽입
parse: 프로그램 이름과 인자가 저장되어 있는 메모리 공간 (char parse)
count: 인자의 개수
esp: 스택 포인터를 가리키는 주소 값 (void esp)
User Process가 커널에게 시스템 콜을 이용해 어떤 동작을 요청할 때, 포인터도 같이 전달
커널은 사용자 프로세스가 전달한 포인터를 맹목적으로 신뢰해서는 안 되고, 포인터의 유효성을 검사하고 유효한 사용자 메모리 영역을 참조하는지 확인해야 합니다.
4GB+----------------------------------+
| Kernal Virtual |
| Memory |
| |
USER_STACK +----------------------------------+ PHYS_BASE
| user stack |
| | |
| | |
| V |
| grows downward |
| |
| grows upward |
| ^ |
| | |
| | |
| heap |
+----------------------------------+
| uninitialized data segment (BSS) |
+----------------------------------+
| initialized data segment |
+----------------------------------+
| code segment |
0x400000 +----------------------------------+ 128MB
| |
| |
0 +----------------------------------+
인자로 받은 주소 값이
KERN_BASE
보다 높은 값의 주소값을 가지는 경우인지를 체크한 후, 만약 위의 두 경우에 해당된다면 프로세스를 종료해야 합니다.
userprog/syscall.c/check_address()
void check_address(void *addr){
if (!is_user_vaddr(addr) || pml4_get_page(thread_current->plm4, addr) == NULL)
exit(1);
}
PintOS에서 시스템 호출은 Linux에서 사용되는 것과 유사한 시스템 호출 인터페이스를 사용하여 구현됩니다. System Call을 구현하려면 System Call 인터페이스 정의, System Call 테이블에 System Call 등록 및 사용자 수준 라이브러리 기능을 구현해야 합니다.
System Call은 모든 운영 체제의 기본적인 부분입니다. 사용자 수준 응용 프로그램이 운영 체제 커널과 상호 작용할 수 있는 방법을 제공합니다. System Call은 사용자 수준 응용 프로그램과 커널 간의 인터페이스를 제공합니다. PintOS에서 System Call은 Linux에서 사용되는 것과 유사한 System Call 인터페이스를 사용하여 구현됩니다.
System Call은 사용자 수준 프로그램이 시스템 수준 리소스에 안전하고 효율적으로 액세스할 수 있는 방법을 제공합니다. 프로그램은 입력 및 출력 작업, 프로세스 관리, 메모리 관리, 파일 관리 및 동기화와 같은 서비스를 운영 체제에 요청할 수 있습니다.
사용자 프로그램이 시스템 콜을 호출
syscall.c 에서 해당 시스템 콜의 넘버, 인자들, 해당 프로그램의 인터럽트 프레임을 정해진 순서대로 레지스터에 채워서 syscall 명령을 내리고 소프트웨어 인터럽트가 발생되면서 CPU가 커널 모드로 전환
RIP
를 통해 인터럽트 후 다시 되돌아가야 하는 인스트럭션의 주소를 알 수 있다. 또한 RSP
는 커널 스레드의 스택 최상단을 가리킨다
=> 인터럽트 시 인터럽트 당한 프로세스는 인터럽트 당하기 직전 CPU 레지스터에 담긴 정보들을 자신의 커널 스레드 스택에 저장해둔다