User mode vs Kernel mode
일반 응용 프로그램은 하드웨어를 제어할 수 없게 만들기 위해서 유저모드와 커널모드를 분리시켰다.
예를 들어 디스크에 저장된 정보들은 보호되어야 한다. 아무 응용 프로그램이나 읽을 수 있게 만들면 안 된다. 따라서 ‘시스템 콜’이라는 특정 절차를 거쳐 커널 모드로 진입해야 파일 시스템을 통해 디스크의 값을 읽고 쓸 수 있다.프로세스가 유저 모드에서 커널 모드로 진입하는 유일한 방법은 인터럽트, 오류, 트랩 시스템 콜 같은 예외를 통해서이다. 예외가 발생하여 제어가 예외 핸들러로 넘어가면 프로세서는 유저 모드에서 커널 모드로 전환한다. 핸들러는 커널 모드에서 돌아간다. 제어가 응용 코드로 돌아오면 프로세서는 모드를 커널 모드에서 다시 유저 모드로 변환한다.
- User mode
- 일반 응용 프로그램을 실행하는 경우, 하드웨어 접근이 제한된 상태
- Kernel mode
- 하드웨어 특권 수준이 격상되어 하드웨어에 대한 제어가 가능한 상태
- 하드웨어 명령어 trap으로 시스템 콜이 호출됨 → 미리 등록되어 있는 trap handler 함수에게 제어권을 넘김 → 특권 수준이 커널 모드로 격상됨 → OS의 작업 수행 → 작업이 완료되면 return-from-trap 명령어로 제어권을 다시 사용자에게 넘김
- 일반 응용 프로그램은 유저 모드에서 동작하며 사용할 수 있는 인스트럭션이 제한적이다. 이 프로세스에게 특권을 주면 커널 모드로 동작하게 되고, 모든 인스트럭션이 실행 가능하며 메모리의 모든 영역에 접근 가능하게 된다.
- 일반 응용 프로그램은 하드웨어를 제어할 수 없게 만들기 위해서 유저모드와 커널모드를 분리시켰다.
User Stack


- 유저 가상 주소 공간의 가장 위쪽
- 컴파일러가 함수 호출할 때 사용됨
- 유저 스택은 런타임에 동적으로 공간을 넓힌다.
- 함수 호출할 때마다 스택이 자라고, 함수가 리턴되면 줄어든다.
- 사용자 모드에서 프로세스, 스레드에 의해 사용됨
- 함수 호출 시 해당 함수의 매개변수, 지역변수, 복귀 주소가 저장되는 곳
- context switching, interrupt를 발생시킨 후 돌아올 때 어떤 상태였는지 저장하는 용도로 사용되는 스택
Kernel Virtual Memory
- 응용 프로그램은 커널 코드에 정의된 함수를 바로 호출하여 이 공간에 저장된 데이터를 읽고 쓸 수 없다. 대신에, 이 연산을 하기 위해 커널을 호출해야 한다.
https://habbn-unitystudy.tistory.com/84
User Stack(사용자 스택)
운영 체제의 관점에서 볼 때, User Stack (사용자 스택)은 매우 중요한 개념이다. 사용자 스택은 프로그램 실행 중 발생하는 다양한 작업들을 관리하기 위한 메모리 구조로, 프로세스의 실행 상태를
habbn-unitystudy.tistory.com
Cache
- 크고 느린 디바이스에 저장된 데이터 객체를 위한 준비 영역으로 사용하는 작고 빠른 저장 장치
- 예) 네트워크 너머의 원격 디스크 → 로컬 디스크 → 메인 메모리 → … → cpu 레지스터 가장 작은 캐시인 cpu 레지스터에 이르기까지 계속 캐싱함
- 각 저장 장치는 block이라는 고정 크기 또는 가변 크기의 연속된 데이터 객체로 이루어져 있음
- 레벨 K의 저장 장치는 레벨 K+1에 있는 블록과 같은 크기인 더 작은 집합의 블록들로 나뉨
- 시간상 아무 때나 레벨 K에 있는 캐시는 레벨 K+1에서 온 블록들의 부분집합의 사본을 가지고 있음
시스템 전체 페이지 중 일부만 메인 메모리에 올릴 수 있다는 관점에서 메인 메모리도 캐시로 취급된다. 디스크로부터 페이지를 가져오는 횟수를 최소화하기 위해 (캐시 히트 최대, 캐시 미스 최소화) 캐시 관리를 어떻게 해야 할까?
https://rob-coding.tistory.com/37
[운영체제] 페이지 교체 알고리즘
패스트캠퍼스 운영체제 강의와 쉽게 배우는 운영체제 교재에 대한 TIL입니다. 저번 포스팅은 메모리 관리 작업 중 메모리 가져오기(Fetch) 정책에 관한 것이었다면, 이번 포스팅은 메모리 재배치(R
rob-coding.tistory.com

페이지 교체 알고리즘에서 고려하는 요소 : Dirty Bit
- 어떤 페이지가 변경되어 dirty 상태가 되었다면 그 페이지를 내보내기 위해서는 디스크에 변경 내용을 기록해야 하기 때문에 비싼 비용을 지불해야 한다. 만약 변경되지 않았다면(clean 상태) 내보낼 때 추가 비용이 없다. 해당 페이지 프레임은 추가적인 i/o 없이 다른 용도로 재사용될 수 있다. 때문에 어떤 vm 시스템들은 더티 페이지 대신 깨끗한 페이지를 내보내는 것을 선호할 수도 있다.
- 이러한 동작을 위해 하드웨어는 modified bit (=dirty bit)를 포함해야 한다. 페이지가 변경될 때마다 이 비트를 1로 설정하고, 페이지 교체 알고리즘에서 이를 고려하여 교체 대상을 선택한다.
디스크에서 읽어올 페이지를 결정할 때 고려하는 요소 : Demanding Paging
- 요구 페이징은 프로세스를 페이지 단위로 나누어 실행에 필요한 부분인지 필요 없는 부분인지 구분하고 이 때 당장 실행에 필요한 페이지만 메모리에 적재하는 기법이다.
- 당장 실행에 필요하지 않은 부분은 Backing Store에 저장해두었다가 필요할 때 메모리에 올린다.
- 요구 페이징 기법 사용 시 필요한 페이지가 메모리에 존재할 수도, Backing Store에 존재할 수도 있는데 이 때 페이지가 메모리에 적재되어있는지 판단할 때 Valid-Invalid Bit을 사용한다. valid(1)은 페이지가 메모리에 존재한다는 의미, invalid(0)은 페이지가 메모리에 없다(Page Fault 발생)는 의미
https://zero-zae.tistory.com/109
[OS] 가상 메모리와 요구 페이징
가상 메모리(Virtual Memory) 메인 메모리의 크기는 한정되어 있다. 따라서 물리적이 메모리 크기보다 크기가 큰 프로세스는 실행시킬 수 없게 되는데 예를 들어 메인 메모리 크기가 100MB일 때 300MB
zero-zae.tistory.com
Atomic Operation
- 외부에서는 하나의 조작으로 보이는 조작의 집합을 의미
- 모든 조작이 완료될 때까지 어떤 프로세스도 변경을 알지 못하도록 비가시적이어야 한다.
- 조작 중 어느 하나라도 실패한다면 조작 전체도 실패하고 시스템 상태를 조작 이전 상태로 복구해야 한다.
- 따라서 Atomic Operation을 실행했을 때는 성공(조작 완료), 실패(변화 없음) 둘 중 하나의 결과만 나와야 한다.
- Atomic Operation의 예시
- 여러 프로세스가 특정 메모리 공간을 공유
- Process A가 공유 메모리 값을 변경시킨 후, 값을 쓰려는 순간 제어권이 넘어감
- Process B가 높은 우선순위로 제어권을 빼앗아 값을 변경하여 쓰고 제어권을 돌려줌
- 제어권을 돌려받은 Process A는 값을 다시 덮어씀
- 이러한 처리 과정을 통해 Process A는 Process B가 자신이 참고해야 하는 값을 바꿨다는 사실을 알지 못한다. → project 1에서 lock을 사용해서 막았음
Interrupt
- 프로세서 외부에 있는 입출력 디바이스가 프로세서에게 시그널을 보내면 발생
- 특정 인스트럭션을 실행해서 발생하는 것이 아니라 비동기적으로 발생함
- 비동기 예외 : 프로세서 외부에 있는 입출력 디바이스 내 이벤트의 결과로 발생
- 동기 예외 : 인스트럭션을 실행한 결과로 발생
👉🏼 인터럽트 발생 과정?
- 입출력 디바이스(네트워크 어댑터, 디스크 컨트롤러, 타이머 칩 등)가 프로세서 칩의 핀에 시그널을 보내서 인터럽트를 발생시킴
- 인터럽트를 발생시킨 디바이스를 식별하는 예외번호를 시스템 버스에 보냄
- 현재의 인스트럭션이 실행 완료된 후에, 프로세서는 인터럽트 핀이 high로 올라갔다는 것을 발견하고 시스템 버스에서 예외 번호를 읽어서 적절한 인터럽트 핸들러를 호출
- 핸들러가 리턴할 때, 제어를 다음 인스트럭션으로 돌려줌 (즉, 제어흐름에서 인터럽트가 발생하지 않았다면 현재 인스트럭션 다음에 왔을 인스트럭션)
- 나머지 예외 종류들(트랩, 오류, 중단)은 지금의 인스트럭션을 실행한 결과로 동기적으로 일어난다. 우리는 이것을 오류 인스트럭션이라고 부른다.
(Trap과) System Call
- 트랩
- 의도적 예외상황
- 인스트럭션 실행 결과로 발생
- 트랩 핸들러 → 다음 인스트럭션을 실행하게 함 (인터럽트 핸들러와 마찬가지로)
- system call이라는 ‘사용자 프로그램-커널’ 사이의 인터페이스를 제공
- 파일 읽기(read), 새로운 프로세스 생성(fork), 새로운 프로그램을 로드(execve), 현재 프로세스 종료 등을 커널에게 요청할 때 사용하는 인스트럭션이 system call
- syscall을 실행하면 트랩이 전달된 인자를 해독, 적절한 커널 루틴을 호출하는 예외 핸들러를 실행함
- 프로그래머 관점에서 system call은 보통의 함수 호출과 동일하게 보이나, 실제 구현 다음과 같음
- 일반적인 함수 호출
- user mode에서 실행되며, 실행할 수 있는 인스트럭션이 제한적임
- 시스템 콜
- kernel mode에서 실행되며, 커널 내에서 정의된 스택에 접근
- 특권을 가진 인스트럭션을 실행할 수 있도록 함
- 일반적인 함수 호출
- Linux x86-64의 시스템 콜
- 리눅스는 파일을 읽고 쓰고 새로운 프로세스를 만들 때 응용 프로그램이 사용할 수 있는 수백개의 system call을 제공함
- C 표준 라이브러리는 대부분이 시스템 콜에 대해서 편한 wrapper 함수를 제공한다. wrapper 함수는 인자들을 패키징하고 커널을 적절한 시스템 콜 인스트럭션으로 트랩을 걸고, 호출하는 프로그램으로 시스템 콜의 리턴 상태를 전달한다.
- 리눅스 시스템 콜에 전달되는 모든 인자들은 스택보다는 범용 레지스터를 통해서 이루어진다.
- rax : 시스템 콜 번호
- rdi, rsi, rdx, r10, r8, r9 : 함수 인자
- rax : 리턴값
Segmentation Fault
| 오류 원인 | 관련 함수/구조체 | 점검 포인트 |
| 1. NULL/미초기화 포인터 | struct inode*, buffer_cache[] | 포인터 NULL 체크 했는가? |
| 2. 오버플로우/스택 부적절 사용 | 지역 배열, 재귀 | 너무 큰 배열 사용 X |
| 3. 경쟁 조건 | lock_acquire() 없이 데이터 접근 | 락 보호했는가? |
| 4. 할당되지 않은 캐시 접근 | cache_entry.data, block_read() | 유효한 sector인가? |
| 5. 사용자 데이터 접근 시 검증 누락 | read_at, write_at, open | 파일 포인터가 valid한가? |
'정글 > Pintos' 카테고리의 다른 글
| [pintos] 프로세스 - PID / fork로 자식 스레드가 생성되는 과정 (0) | 2025.05.26 |
|---|---|
| [pintos] 2주차 - Argument Passing 구현 과정 (0) | 2025.05.24 |
| [pintos] 1주차 - Priority Scheduling 구현 과정 (1) | 2025.05.20 |
| [pintos] 1주차 - Priority Scheduling 문서 해석 / 코드 해석 (1) | 2025.05.20 |
| [pintos] 1주차 - Alarm Clock 구현 과정 (0) | 2025.05.20 |