커널에 들어갔다 나오는 값
Esc

    커널에 들어갔다 나오는 값

    –OS

    게임 코드는 파일을 직접 읽지 않습니다. 메모리를 직접 배정받지도 않고, 다음에 어느 스레드가 돌지도 정하지 않습니다. 그 일들은 전부 커널이 합니다. 우리 코드는 부탁만 합니다.

    부탁할 때마다 사용자 모드에서 커널 모드로 넘어갔다 돌아옵니다. 이 왕복에 값이 붙습니다. 그리고 이 문은 생각보다 자주 열립니다.

    사용자 모드와 커널 모드

    CPU 에는 지금 어느 모드로 도는지를 나타내는 상태가 있습니다. 모드에 따라 할 수 있는 일이 다릅니다.

    사용자 모드 커널 모드
    일반 연산 가능 가능
    페이지 테이블 손대기 불가 가능
    장치에 직접 명령 불가 가능
    남의 프로세스 메모리 불가 가능

    주소를 번역하는 표를 프로세스가 못 고치는 이유가 이것입니다. 표를 고치려면 커널 모드여야 하고, 우리 코드는 사용자 모드로 돕니다.

    그래서 메모리를 더 달라거나 파일을 열어 달라는 일은 전부 부탁이 됩니다.

    문을 두드리는 세 가지 방법

    이 문을 지나는 길은 셋뿐입니다. 셋 다 결과는 같습니다. 커널 코드가 돌고, 끝나면 돌아옵니다.

    무엇 누가 부르나 예
    시스템 콜 우리 코드가 스스로 파일 읽기, 메모리 요청, 시간 묻기
    예외 CPU 가 처리를 못 해서 페이지 폴트, 0 으로 나누기
    인터럽트 장치가 일이 끝나서 저장 장치 읽기 완료, 타이머, 입력

    페이지 폴트가 두 번째 줄입니다. 프로그램이 부른 적이 없는데 커널 코드가 도는 이유가 이것입니다. CPU 가 번역을 못 해서 대신 문을 두드린 것입니다. 그쪽에서 커널이 무슨 일을 하는지는 페이지 폴트는 오류가 아니지만 프레임은 흔든다에 적었습니다.

    셋을 따로 배우면 셋이지만, 비용으로 보면 하나입니다. 전부 같은 문을 지납니다.

    문을 넘는 값

    1번과 3번, 그러니까 문을 넘는 것 자체는 수백 나노초 규모입니다. 아무 일도 안 하는 가장 단순한 시스템 콜을 재도 그렇습니다.

    그런데 이 값이 몇 년 사이에 올랐습니다. 투기적 실행을 악용하는 취약점이 알려진 뒤로 커널에 드나들 때 추가로 하는 일이 생겼기 때문입니다. 같은 기계에서 완화 조치를 켜고 끄고 재면 두세 배까지 차이가 납니다.

    그중 하나는 앞의 이야기와 직접 닿아 있습니다. 커널의 주소 공간을 사용자 주소 공간에서 떼어 놓는 방식인데, 문을 넘을 때마다 번역의 기준이 되는 표를 바꾸는 셈이라 TLB 에 담아 둔 번역이 흔들립니다. 시스템 콜 하나가 작은 주소 공간 전환처럼 되어 버린 것입니다.

    돌아온 뒤가 더 비쌀 수 있다

    4번이 눈에 안 보이는 값입니다.

    커널 코드도 코드라서 캐시를 씁니다. 우리 데이터가 있던 자리를 밀어내고 자기 것을 올립니다. 돌아왔을 때 우리 코드는 자기가 쓰던 캐시를 다시 채워야 합니다.

    그래서 시스템 콜 하나의 값은 “문을 넘는 시간” 으로 끝나지 않습니다. 돌아온 뒤 한동안 느려진 시간까지 합해야 실제 값이 됩니다. 호출 하나하나는 싸 보이는데 합쳐 놓으면 커지는 이유가 이것입니다.

    게임에서 자주 두드리는 문

    한 프레임에 만 번은 과장 같지만, 모르고 쓰면 금방 갑니다.

    무엇 왜 문을 두드리나
    로그 출력 한 줄마다 쓰기 요청입니다. 루프 안에서 찍으면 그 루프가 통째로 느려집니다
    파일 읽기 한 번에 조금씩 여러 번 읽으면 횟수만큼 두드립니다
    시간 재기 자주 부르는 자리에서 매번 현재 시각을 물으면 쌓입니다
    락 경합 경합이 없으면 사용자 모드에서 끝나지만, 기다려야 하면 커널이 스레드를 재웁니다
    네트워크 패킷 하나씩 보내면 하나마다 두드립니다

    재워진 스레드가 다시 올라올 때 붙는 값은 프로세스와 스레드, 그리고 갈아 끼우는 값에 적었습니다.

    대응은 셋 다 같은 모양입니다.

    • 모아서 한 번에. 여러 번 쓸 것을 버퍼에 쌓았다가 한 번에 내보냅니다
    • 프레임 밖으로. 로딩 구간이나 별도 스레드로 옮깁니다
    • 아예 안 부르기. 프레임 시작에 한 번 받아 둔 값을 그 프레임 동안 재사용합니다

    시간 재기는 요즘 기계에서 문을 안 넘고 끝나기도 합니다. 커널이 시간 값을 사용자 쪽에서 읽을 수 있는 자리에 올려 두는 방식이 있어서 그렇습니다. 그런 예외가 있다는 것 자체가 이 문이 얼마나 비싼지를 보여줍니다.

    정리

    • 우리 코드는 사용자 모드로 돕니다. 페이지 테이블도 장치도 직접 못 건드려서 커널에 부탁해야 합니다
    • 그 문을 지나는 길은 셋입니다. 우리가 부르는 시스템 콜, CPU 가 부르는 예외, 장치가 부르는 인터럽트
    • 페이지 폴트는 두 번째입니다. 프로그램이 부른 적 없는데 커널이 도는 이유가 그것입니다
    • 문을 넘는 값은 수백 나노초 규모이고, 투기적 실행 취약점 완화가 들어간 뒤로 두세 배까지 올랐습니다
    • 돌아온 뒤가 더 비쌀 수 있습니다. 커널이 캐시를 차지하고 나가서 우리 코드가 식은 채로 다시 시작합니다
    • 그래서 대응은 하나로 모입니다. 덜 두드리기. 모아서 한 번에 보내고, 프레임 밖으로 옮기고, 받아 둔 값을 재사용합니다