정글/Pintos

[pintos] 2주차 - 키워드 정리

nkdev 2025. 5. 24. 17:06

User mode vs Kernel mode

일반 응용 프로그램은 하드웨어를 제어할 수 없게 만들기 위해서 유저모드와 커널모드를 분리시켰다.

예를 들어 디스크에 저장된 정보들은 보호되어야 한다. 아무 응용 프로그램이나 읽을 수 있게 만들면 안 된다. 따라서 ‘시스템 콜’이라는 특정 절차를 거쳐 커널 모드로 진입해야 파일 시스템을 통해 디스크의 값을 읽고 쓸 수 있다.프로세스가 유저 모드에서 커널 모드로 진입하는 유일한 방법은 인터럽트, 오류, 트랩 시스템 콜 같은 예외를 통해서이다. 예외가 발생하여 제어가 예외 핸들러로 넘어가면 프로세서는 유저 모드에서 커널 모드로 전환한다. 핸들러는 커널 모드에서 돌아간다. 제어가 응용 코드로 돌아오면 프로세서는 모드를 커널 모드에서 다시 유저 모드로 변환한다.

  • User mode
    • 일반 응용 프로그램을 실행하는 경우, 하드웨어 접근이 제한된 상태
  • Kernel mode
    • 하드웨어 특권 수준이 격상되어 하드웨어에 대한 제어가 가능한 상태
    • 하드웨어 명령어 trap으로 시스템 콜이 호출됨 → 미리 등록되어 있는 trap handler 함수에게 제어권을 넘김 → 특권 수준이 커널 모드로 격상됨 → OS의 작업 수행 → 작업이 완료되면 return-from-trap 명령어로 제어권을 다시 사용자에게 넘김
    • 일반 응용 프로그램은 유저 모드에서 동작하며 사용할 수 있는 인스트럭션이 제한적이다. 이 프로세스에게 특권을 주면 커널 모드로 동작하게 되고, 모든 인스트럭션이 실행 가능하며 메모리의 모든 영역에 접근 가능하게 된다.
    • 일반 응용 프로그램은 하드웨어를 제어할 수 없게 만들기 위해서 유저모드와 커널모드를 분리시켰다.

 

User Stack

Process virtual adress space Linux x86-64 run-time memory image
CSAPP 책 9.26 The virtual memory of a Linux process

  • 유저 가상 주소 공간의 가장 위쪽
  • 컴파일러가 함수 호출할 때 사용됨
  • 유저 스택은 런타임에 동적으로 공간을 넓힌다.
  • 함수 호출할 때마다 스택이 자라고, 함수가 리턴되면 줄어든다.
  • 사용자 모드에서 프로세스, 스레드에 의해 사용됨
  • 함수 호출 시 해당 함수의 매개변수, 지역변수, 복귀 주소가 저장되는 곳
  • 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

  • 프로세서 외부에 있는 입출력 디바이스가 프로세서에게 시그널을 보내면 발생
  • 특정 인스트럭션을 실행해서 발생하는 것이 아니라 비동기적으로 발생함
  • 비동기 예외 : 프로세서 외부에 있는 입출력 디바이스 내 이벤트의 결과로 발생
  • 동기 예외 : 인스트럭션을 실행한 결과로 발생

👉🏼 인터럽트 발생 과정?

  1. 입출력 디바이스(네트워크 어댑터, 디스크 컨트롤러, 타이머 칩 등)가 프로세서 칩의 핀에 시그널을 보내서 인터럽트를 발생시킴
  2. 인터럽트를 발생시킨 디바이스를 식별하는 예외번호를 시스템 버스에 보냄
  3. 현재의 인스트럭션이 실행 완료된 후에, 프로세서는 인터럽트 핀이 high로 올라갔다는 것을 발견하고 시스템 버스에서 예외 번호를 읽어서 적절한 인터럽트 핸들러를 호출
  4. 핸들러가 리턴할 때, 제어를 다음 인스트럭션으로 돌려줌 (즉, 제어흐름에서 인터럽트가 발생하지 않았다면 현재 인스트럭션 다음에 왔을 인스트럭션)
  5. 나머지 예외 종류들(트랩, 오류, 중단)은 지금의 인스트럭션을 실행한 결과로 동기적으로 일어난다. 우리는 이것을 오류 인스트럭션이라고 부른다.

 

(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한가?