Kernel - Math Emulation

리눅스2025. 01. 24.
게시일
Jan 25, 2025
카테고리
시리즈
커널에 Math Emulation관련 코드들이 뭐하는 기능인지 궁금해서 찾아봤다. 결론부터 말하면 FPU(부동소수점연산장치)가 없는 CPU에서 부동소수점 연산을 정수연산명령어들을 이용해 에뮬레이션 하는 기능이다. 현재는 FPU가 없는 일부 임베디드, 라우터용 프로세서를 제외하면 쓰이지 않는 기술이지만 내용이 흥미로워서 조금 정리해봤다.

Coprocessor

coprocessor란, 프로세서의 기능을 보조하는 독립된 연산 칩들을 의미한다. 특정 명령어(대부분 부동소수점)가 실행이 되었을 때 프로세서는 해당 연산을 실행하는 외부 칩이 명령어를 실행하도록 아웃소싱한다. Coprocessor를 지원하기 위해서는 ISA에서 Coprocessor를 위한 명령어세트와 인터페이스를 가지고 있어야 한다. 대표적인 coprocessor로는 부동소수점 연산 장치인 fpu가 있다.
현대에는 이런 대부분의 coprocessor들이 CPU 안에 통합되었다. 예를 들어 FPU는 본래 독립적인 외부 칩이었지만 최근에는 x86, arm계열 CPU 거의 전부가 FPU를 내장한 채로 출시된다. 그렇지만 근래의 cpu들도 여전히 레거시 기능으로 Coprocessor 인터페이스를 지원하고 있으며, 코드만 봤을 때는 FPU가 CPU 외부에 있든 내부에 내장되어있든 상관없이 동일한 매커니즘으로 동작한다.
현대의 GPU도 프로세서의 기능을 보조(대규모 병렬 부동소수점 연산과 그래픽 연산 수행)하므로 Coprocessor의 일종이라고 볼 수 있다.

Coprocessor Emulation

ISA에서 Coprocessor 인터페이스를 지원한다고 해서 꼭 Coprocessor를 시스템에 포함해야한다는 법은 없다. 특히 과거에는 저렴한 PC에 coprocessor까지 탑재하기에는 비용이 많이 부담될 수 있기에 누락하는 경우가 많았다고 한다. 이런 경우를 대비해 ISA에는 부동소수점 연산을 CPU의 정수연산으로 에뮬레이션 할 수 있는 옵션이 있다. 동작과정은 다음과 같다.

Coprocessor가 있는 시스템의 경우

  1. 유저 프로그램이 부동소수점 연산을 실행한다.
  1. CPU는 시스템에 Coprocessor가 있는지 확인한다. (x86같은 경우에는 CR0 레지스터의 EM 플래그가 이 역할을 수행)
  1. Coprocessor가 있는 경우 CPU는 해당 Coprocessor에 연산을 맡긴다.
  1. 연산이 완료되면 Coprocessor는 CPU에 인터럽트를 건다.

Coprocessor가 없는 시스템의 경우

  1. 유저 프로그램이 부동소수점 연산을 실행한다.
  1. CPU는 Coprocessor가 있는지 확인한다.
  1. Coprocessor가 없는 경우, CPU는 운영체제의 Coprocessor 핸들러로 Trap한다.
  1. 운영체제의 핸들러는 정수연산을 이용해 부동소수점 연산을 Emulation한다.
즉, 유저프로세스에서 부동소수점 연산을 실행할 경우, 운영체제가 에뮬레이션 할 수 있도록 자동적으로 trap을 하도록 cpu가 설계되어있다

커널에서

당연히 math emulation과 같은 코드는 ISA마다 차이가 나기 때문에 ISA 의존코드로 작성된다. 헤더파일은 include/math-emu/double.h에 존재한다. 최근에는 쓰이는 곳이 많지 않지만, 라우터나 임베디드용 프로세서에 FPU가 존재하지 않는 경우 간혹 쓰이곤 한다고 한다. math emulation 옵션을 켤 경우 부동소수점 연산 명령어가 수행될 때 에뮬레이션 되도록 설정할 수 있다.
FPU 에뮬레이션의 가장 큰 단점은 역시 오버헤드이다. 일단 OS의 핸들러를 불러야 하니 protection boundary를 넘어야 한다. 즉 실수연산 한번 할때마다 시스템콜을 부르는 셈이나 마찬가지이다. 그래서 성능이 필요하다면 FPU 에뮬레이션 보다는 유저 라이브러리를 이용해 유저 영역에서 끝내는 것이 좋다. 다만 기존 시스템과의 호환성을 위해서 예방적으로 FPU 에뮬레이션 옵션을 켜둔다고 한다.
덤으로 이거 조사하다가 알게된 사실인데, 커널에서는 FPU를 사용하지 않는 옵션도 있다고 한다. FPU를 유지할 경우 context switch에 드는 비용이 상당히 커지고, 어차피 커널 내부에서는 실수 연산을 할 일이 거의 없기 때문에 그냥 fpu자체를 비활성화한다고 한다. 그리고 유저프로세스에서도 FPU 연산을 사용하는 프로세스의 경우에만 FPU 관련 정보를 context에 저장하는 식으로 최적화를 한다.
참고 :
 
 
 

 
notion image
Minseok Kim / Semteul
블로그 겸 노트에 오신 것을 환영합니다.
 
블로그 홈
🚀
Minseok’s 노트