[OS] I/O에서 데이터 전송 방식과 Timers, Protected Instruction

2024. 4. 1. 21:49CS/Operating System

DMA

 I/O 컨트롤러 내부에 조그마한 버퍼 메모리가 있다. 그 버퍼 메모리에서 메인 메모리쪽으로 복사사키는걸 input, 반대를 output이라 한다. 이러한 과정, I/O에서 데이터를 전송하는 방식은 Programmed I/O와 DMA 방식이 있다.

 

Programmed I/O

  • 데이터를 옮기는 작업을 CPU가 관여한다. 특별한 I/O instruction(protected instruction) 방법이 있고 memory-mapped 방법도 있다.
  • I/O 작업때문에 CPU가 묶여있기에 비효율적일 수 있다.
  • Programmed I/O 방식은 전송 데이터 양이 적을때 사용한다.  키보드에서 1byte만 옮기는 상황이라면 CPU가 빨리 복사해서 옮기는게 더 낫다.

DMA

  • I/O 장치가 직접 옮긴다.
  • DMA란 Direct Memory Access의 약자로 직접 메모리에 접근하는 것이다. 
  • DMA인터럽트가 오면 프로세스를 멈추고 현재 context를 보존하고 인터럽트를 커널 코드로 바꿔서 테이블을 뒤져보고 핸들러를 호출한다.
  • DMA는 보통 high-speed I/O를 요구하거나 전송 데이터 양이 많을 때 사용한다. DMA는 인터럽트를 처리하는 등의 과정이 많이 필요한데 전송 데이터 양이 많다면 이 과정들을 감안하고도 더 빠르기 때문이다.
  •  DMA는 보통 그래픽카드처럼 빠른 장치가 옮기는데 CPU는 I/O 장치에게 4GB만큼 메모리에 채워줘~ 이런 하나의 명령만 한다. 작업이 종료되면 CPU에게 인터럽트를 건다. CPU는 데이터를 옮기라는 명령만 주고 데이터를 옮기는 동작에는 전혀 관여하지 않는 것이다.

DMA, Programmed I/O를 잘 구분해야 한다!


Timers

CPU는 time-sharing 방식으로 a.exe, b.exe, c.exe의 instruction을 한줄씩 돌면서 실행한다. CPU는 a.exe → b.exe 처럼 현재 실행하는 명령어를 언제 바꿀 수 있을까?

CPU가 a.exe를 실행시키고 있으면 운영체제가 중간에 멈출 수 없다.

따라서 hardware timer가 필요하다.

 

timer는 메인보드에 들어있고 일정 주기마다 인터럽트를 쏴준다. 인터럽트를 받으면 잠시 CPU가 멈추므로 그때 운영체제 코드가 CPU를 돌면서 a.exe가 시간을 많이 썼으므로 b.exe로 변경하는 방식으로 실행중인 프로세스를 변경한다. 이를 context-switch라 한다. 타이머를 통해 어떤 프로그램을 돌릴지 정할 수 있는 것이다. 타이머 주기는 CPU 스케줄링에 굉장히 중요하다.

 

타이머를 건드리는 일은, 타이머를 늦춘다거나 타이머 인터럽트를 어떻게 한다던가 하는 일은 운영체제 코드만 가능하다. 즉 권한이 필요하다.


Protected Instructions & OS Protection

CPU 설계자는 명령어를 만들때 protected와 일반 명령어로 설계하고 protected instructions는 운영체제만 사용 가능하게 해놓는다. 어플리케이션은 사용할 수 없다. 참고로 앞에서 말한 타이머를 컨트롤하는 명령어는 protected(privileged) instructions만 존재한다.

  • Direct I/O access
    • 권한이 필요한 명령어나 memory-mapping을 사용한다.
  • Memory state management
    • Page table update, TLB load 

권한을 구분하기위해 특별한 mode bits를 넣는다. 내가 지금 커널 레벨에서 돌고있는지 유저 레벨에서 돌고있는지 판단하는데, 유저 레벨이면 0으로 mode bits로 설정하고 protected instruction은 사용할 수 없다.

 

CPU를 설계할 때 링이라는 계층으로 설계하는데 최소한 2가지 커널, 유저 모드는 필요하다. 실제로는 4가지 권한 모드(링)를 사용한다. 권한 레벨마다 사용할 수 있는 instruction이 다르다.

실제로는 4가지 모드이므로 mode bits는 2비트가 된다.이를 status bit라 한다.

CS register라는 레지스터는 권한 모드를 구분하는 레지스터이다.

 

OS Protection의 핵심은 커널(운영체제)만 사용할 수 있는 명령어가 존재해야 하고 현재 모드가 커널 모드인지 판단할 수 있어야 한다. 운영체제는 자신과 다른 어플리케이션을 보호할 수 있어야하기 때문이다.

 

유저 프로그램이 권한이 있는 명령을 해야한다면 OS에 부탁해야만 한다. 그게 사실 system calls이다.

system calls를 부르게 되면 Protection boundaries를 넘어가게 된다. 운영체제 내부에는 system call을 어떻게 처리해야하는지 구현되어 있고 애플리케이션이 exception을 발생시키면 커널 모드로 바뀐다. 그리고 커널 핸들러를 호출하고 파라미터와 함께 시스템 콜을 부르게 된다. 시스템 콜이 불리면 어플리케이션이 사용하던 레지스터값 등의 변수 값, 상태 값을 잘 저장한다. 시스템 콜이 끝나면 복원해야하기 때문이다.

 

중요한건 protection boundaries를 넘어가는 일은 CPU 설계부터 OS만 수행할 수 있는 instruction이 있고 유저는 system call을 통해서만 그 명령어를 수행할 수 있다는 것이다. 유저 모드에서는 그 명령어를 사용할 수 있어선 안된다.


Memory Protection

실행 파일을 실행시키면 실제로는 storage에서 a.exe는 얼마만큼 쓰고있고 b.exe는 얼마만큼 쓰고있다. a.exe와 b.exe는 서로의 메모리를 절대 읽고 쓸 수 있어선 안된다. 또 OS는 자기자신을 보호해야 한다. a.exe가 OS가 저장되어있는 storage를 건드릴 수 있으면 절대 안된다. 그래서 OS는 애플리케이션끼리와 자기자신의 메모리를 보호해야 한다.

 

가장 간단한 방법은 base register, limit register를 사용해서 B는 어디부터 어디까지만 쓰라고 설정해주면 된다. B 실행이 끝나고 A 실행이 시작되면 두 레지스터의 값을 바꾸면 된다. 메모리를 엑세스 할때마다 두 레지스터의 값을 확인해서 보호할 수는 있다.

 

그걸 위해서 CPU를 만드는 사람들이 MMU(Memory Management Unit)을 설계해서 만들었다. TLB 역시 MMU안에 포함되어 있다. MMU는 메모리 관리를 하드웨어적 기능으로 좀 더 간단하게 동작하는 것이 가능하게끔 한다.

아래의 항목들이 MMU에 포함된다. MMU를 다루는 것은 당연히 protected instrcutions로 되어있어서 운영체제만 MMU를 다룰 수 있다.

  • Base and limit registers
  • page table pointers, page protection, TLBs
  • virtual memory
  • segmentation

Synchronization

인터럽트는 동기화 문제를 가지고 있다. 인터럽트는 언제든 발생할 수 있어서 운영체제, CPU가 아무리 바빠도 들어올 수 있다. 그런데 아주 가끔 중요한 작업을 하고있으면 그게 영향을 받을 수 있다. 그런 문제를 동기화 문제라 하고 운영체제는 좋은 동기화를 위한 방법을 제공해야 한다. 동기화와 관련된 내용은 다음에 다시 자세히 다룰 예정이다.