서울대학교 컴퓨터공학과 김진수 교수님의 "고급 운영체제" 강의를 필기한 내용입니다.

다소 잘못된 내용과 구어적 표현 이 포함되어 있을 수 있습니다.

IPC (Inter Process Communication)

  • shell 에서 pipe (|) 도 IPC 이다
    • shell 이 두 프로세스를 fork 하기 전에 pipe file discriptor 를 만들고 한 프로세스의 stdout 을 나머지 하나의 stdin 으로 보내는 방식으로 작동한다.
    • 이것 말고도 Named pipe (fifo) 를 사용해서 독립된 두개의 프로세스에서 IPC 를 할 수도 있다고 한다.

Protection

CPU protection

  • 실제로는 cpu mode 는 kernel, user 두개만 있지는 않다.
    • x86 의 경우에는 0~3 의 4단계가 있고 0 이 kernel, 3이 user 다.
      • 왜?: 1, 2 는 device driver 처럼 kernel 이긴 하지만 우선순위는 좀 낮은 놈들을 실행시키기 위한 것
      • 즉 device driver 같은 곳에서 문제가 발생하면 ring 0 에서 catch 해서 처리할 수 있게 하기 위함이다. (얘네들이 ring 0 에서 돌게 되면 panic 을 구제하기 힘드니까)
      • device driver 의 경우에는 kernel 보다는 완성도가 떨어지기에 문제가 생길 가능성이 비교적 높은 것도 부가적인 이유임
      • 물론 지금은 잘 안쓴다: normal case 의 경우에는 마치 userspace 인 것 마냥 syscall 을 걸기 때문에 성능 저하가 있기 때문
  • 보안을 위해서 kernel mode 에서만 사용할 수 있는 protected (privileged) instruction 이 있다.
    • register 나 memory 건드는 작업들
  • interrupt controller, APIC
    • 옛날에는 hw 들이 interrupt controller 로 interrupt 를 보내게 되고 요놈이 core 에 전달했는데
    • 요즘에서는 IO APIC 라는게 있어서 이쪽으로 보내면 이놈이 cpu core 마다의 local apic 들에게 분배
    • IPI (Inter Processor Interrupt): 코어끼리 interrupt 는 거는 기능
  • Exception: sw 실행중에 문제가 생기는 것 통칭
    • 아래 세 용어는 x86 의 용어이다. (타 arch 는 다를 수 있음)
    • fault: sw 가 의도하지는 않았지만 복구가능한
      • 예) protection fault: 메모리 사용량을 줄이기 위해 write 전까지는 공유 자원 공유
    • syscall: sw 가 의도한 것 (os 의 기능을 사용하기 위해 요청하는)
    • abort: sw 가 의도하지 않았고 복구하기도 힘든
  • 처리 방법은 exception 이나 interrupt 나 논리적으로는 같다
    • handler vector table 에서 handler 찾아서 kernel mode 에서 실행하고 원래 user space 로 돌아가는 과정

Memory protection

  • os 영역 간섭 방지, process 간 영역 간섭 방지
  • cpu 는 protected inst 로 다 보호를 하는데 memory protection 에서는 kernel level 로 보호를 하지 않는다.
    • 이건 성능때문에
    • memory 접근이 음청 많은데 이걸 다 syscall 로 os 가 처리하게 할 수는 없다.
  • 그럼 어떻게 보호하느냐: memory protection 정보 (memory boundary) 도 kernel 에서 갖고 있는데 이걸 os 를 안거치고 어떻게 할까?
  • os 에서 어딘가에 memory protection 정보를 저장해 놓는 방법으로 해결한다.
    • cpu register (base, limit) 에 process 시작시에 넣어놓고 cpu 에서 접근시에 이 register 에 있는 정보로 검사하는 것
  • 근데 또 이때의 문제는 limit 이 정해져 버린다는 것이다
    • 이 문제를 해결하기 위한 한가지 방법은
      • base, limit 쌍을 여러개 저장해서 확장 가능하게 하고
      • 각 영역의 권한 인 protection 정보까지 같이 저장해서 접근가능한 영역에서 수행할 수 있는작업을 제한
      • 이거를 처리하는 놈을 memory protection unit 으로 hw 처리
      • arm 에서 이런식으로 한다고 한다…
      • 그리고 이때의 유동적인 접근가능 영역을 segmentation 이라고 하는 것
    • 또 다른 한가지는 paging 을 이용하는 것
  • 여기서 핵심은 memory protection information 을 cpu 에 전달해서 os 를 거치지 않게 하는 것이다

몇가지 추가적인 HW…

  • Timer
    • time sharing system 을 지원하기 위한 것
    • 간단히 말하면 프로세스를 바꾸기 위해서는 kernel code 가 돌아야 하는데 syscall 을 안걸면 kernel code 가 안돌아 해당 프로세스가 monopoly 가 되기 때문에 timer interrupt 를 사용해 주기적으로 kernel mode 로 들어가게 하는 것
    • timer tick: timer 주기마다 한번씩 불리는 interrupt
    • timer slice: process 마다 보장받는 총 실행 시간
      • 당연히 timer tick 의 배수가 된다
  • DMA (Direct Memory Access): 메모리 접근을 cpu 가 직접 하지 않고 별도 hw에 위임한 것
  • Atomic instruction: 자주 사용되는 기능은 atomic 하게 만들어서 성능을 높이는
    • increase/decrease 나
    • 접근 제어 (LL - load locked, sc - store conditional)

SYSCALL 구현

  • function: user space 내에서의 procedure call 을 의미
  • syscall: user space 에서 kernel space 의 지정된 장소로 점프하는 protected procedure call
  • function call 보다 syscall 이 당연히 더 느리다
    • kernel 로 context switch 가 되며 레지스터 값 등을 backup/restore 하는 비용이 있기 때문
    • 또한 address space 가 바뀌기 때문에 TLB 가 깨진다
    • 캐시도 깨진다
    • 요즘은 branch prediction 이 잘되어 있어서 pipeline 이 잘 굴러가는데 이것도 리셋되기 때문에 딜레이가 난다

  • 사실 syscall 은 trap 의 한 부분집합이다
    • user process 가 유발하는 kernel jump 전체가 trap 이고, 그 중에서도 “의도적인” kernel jump 가 syscall 인 것.
    • 반대로 user process 가 아닌 놈이 발생시키는 것을 interrupt 라고 하는것
    • syscall/sysret 이전에 단군할배는
      • int/iret 와 sysenter/sysexit 라는 이름으로도 불렸다고 한다
    • syscall 이 부르는 것, 돌아가는게 sysret (system return)

여기부터는 2024-03-19 강의

System call 호출 과정

#draft PPT 17p ~ 25p

  • AMD 의 x86_64 에 들어간 syscall 은 특별한 레지스터에 trap vector table 의 index 를 넣고 레지스터를 바꿔 syscall 호출
    • 또한 무조건 ring 0 로 뛰게 함
  • msr_lstar 레지스트리에 있는 vector table 로 뛰어서 커널 모드 실행
  • C 언어에서 getpid() 예시
    • eax 레지스터가 intel cpu 에서 syscall number 를 저장
    • 그래서 getpid() 에서는 eax 에 0x27 를 넣고 syscall 호출
  • syscall 에서는 flag register 만 저장하고 syscall 로 뛰기 때문에 syscall 이 처음에 실행될 때는 나머지 register 와 page table 등의 정보를 백업하는 과정이 선행된다
    • 그 다음 rsi 레지스터에 syscall no 를 넣고 관련 syscall C 함수로 가게 된다?

vDSO (Virtual Dynamic Shared Object)

  • Kernel 이 생성하여 메모리에 적재해놓은 라이브러리로, 모든 user memory space 에 mapping 되어 user mode 에서도 (kernel mode 로 바꾸지 않고) 접근할 수 있게 해놓은 놈이다.
  • 이게 왜 필요하냐
    • Read-only syscall (kernel 의 변수값을 읽어오는 등) 의 경우에는 syscall 이긴 하지만 그냥 값을 읽어오는 것이기 때문에 번거롭게 kernel mode 로 갔다 오는 것은 너무 비효율적이기 때문
      • 이러한 RO syscall 은 뭐 getcpu()clock_gettime() 같은 것이 있더라.
    • 그래서 이 기능 덕분에 C 언어에서 RO syscall 을 호출하면 라이브러리 내부적으로 진짜 kernel syscall 을 호출하는 것이 아닌 이 vDSO 공간을 참조해서 값을 읽어오게 된다.
  • /proc/self/maps 에서 확인해 보면

vvar

  • vvar 는 raw data 공간으로, vDSO 의 store 라고 생각하면 된다.
  • 즉, vDSO 는 라이브러리고 실제 데이터는 vvar 에 들어가 있는 것
  • 얘는 항상 vDSO 의 바로 직전 virtual memory address 에 mapping 된다.

vsyscall

  • 현재의 vDSO 는 보안을 위해 고정 주소를 매핑하지 않고 dynamic 하게 주소를 매핑한다.
    • 아마 vDSO 의 physical memory addr 를 부팅시마다? 바꾸고 바뀐 주소를 user memory space 에 매핑시켜주지 않을까
  • vsyscall 은 고정 주소를 매핑하던 옛날 버전

syscall() 함수

  • 대부분은 syscall 이름으로 된 함수가 있는데
  • 이렇게 하는 대신 syscall number 로 syscall 을 호출할 수 있음
    • 그리고 syscall 에 필요한 여러 arg 로 같이 전달
  • syscall 을 직접 구현했을 때 사용해볼 수 있다