커널로 올리면 무조건 다 빠를까요?

by gg582 · 2026-09-06 09:34:34 · 33 views

이 글은 개인적인 연구소 개발 경험 및 취미 개발의 경험을 거칠게 쓴 것입니다. 기술적 사실관계를 이 잡듯 뒤진 글은 아닙니다. AI를 이용하면 고품질 레포트가 1분만에 나오지만 여기서는 그러지 않았습니다.

먼저, 통념으로는 커널에 소프트웨어를 적재해서 실행하면 빠르다고 배웠을 것입니다. 이론적으로는 하나의 추상화 계층만큼 내려가서 실행하니 그럴싸합니다.

이 때 대학교에 다니면서 가장 많이 생각해 보고, 또 실제로 작성해 보며 "꼭 그렇지만도 않구나" 하고 판단하게 되는 근거가 섭니다.

아마추어 C 개발을 할 때 입문 단계에서 접하게 되는 두 가지 함수가 있습니다. copy_from_usercopy_to_user입니다.

copy_from_user는 유저 메모리에 상주한 정보를 커널 스페이스로 복사해 옵니다. copy_to_user는 커널 메모리에 상주한 정보를 도로 유저 스페이스에 복사해 주죠. 그리고 Zero-Copy라던가, 시스템 콜 오버헤드와 같은 이야기를 수업 시간이나 교수님의 강의 시간에 수도 없이 들었다면 '아하, 여기서 복사 오버헤드가 들겠군'이라는 생각을 하게 됩니다.

기본적으로 리눅스의 경우는 mmap 같은 메모리 매핑 메커니즘을 쓰지 않는 일반적인 I/O 경로에서는 커널 스페이스와 유저 스페이스가 동일한 메모리 영역을 공유하지 못하게 합니다. 즉 당신의 커널 모듈에서 할당받은 메모리 블럭을 유저 스페이스에서 바로 접근하지 못합니다. 게다가 mmap은 복잡한 동작을 거쳐 메모리 영역을 바로 매핑해 주는 특수한 함수이기 때문에 엄밀히는 할당, 복사같은 작업도 아닙니다. 매핑 설정에, 페이지까지 다 고려해서 열심히 매핑해 줘서 빠른 편에 속하지도 않죠. 즉 mmap으로 메모리를 자주 매핑하면 되겠다는 판단은 도매상을 거치지 않고 튤립을 키우면 싸겠다고 판단하는 것입니다.

마치 커널 스페이스와 유저 스페이스가 뇌혈관 장벽으로 막힌 듯 둘 간의 소통은 '장벽을 통한 안전한 전달'처럼 느껴집니다. 물론 '복사'라는 꽤 중요한 작업이 손쉽게 이뤄진다는 점에서는 확실히 다르지만요.

예시를 통해 실체를 봅니다

그렇다면, 당신이 CoAP 노드에서 정보를 받아 와서, 정보를 가공 및 필터링한 후 정보를 다른 대역폭으로 전달한다고 합시다. 이 때 당신의 리눅스/유닉스 노드는 어디까지를 커널 스페이스에서 처리하는 것이 좋을까요?

먼저 CoAP 노드에서 받아온 정보를 파싱하고, 규격을 변환하는 작업을 커널 모듈로 내리지만, 전송은 여전히 전통적인 유닉스 프로그래밍의 방식을 따른다고 해 봅시다.

우리는 전송을 할 때 유저 스페이스에서 정보를 가지고 놉니다. 그리고 이제 당신은 파싱 및 규격 변환을 커널 모듈에서 하려고 합니다.

이 때 우리는 몇 가지 한계에 부딪힙니다.

첫째로, 커널 스페이스에서 커널에 주어진 공간은 유저 스페이스의 광활한 공간에 비하면 한없이 작습니다. 당신이 아주 많은 노드들을 순차적으로 파싱 및 가공하기 위한 작업을 수행하기 위해 커스텀 링 버퍼 디바이스를 둔다고 해도 버퍼의 크기가 조금만 불어나도 새 노드를 할당하지 못합니다.

둘째로, 복사 흐름과 시스템 콜이 불필요하게 늘어납니다. 네트워크 인터페이스 카드로 들어온 패킷을 커널이 수신하고, 이를 유저 스페이스의 파싱 프로그램으로 넘길 때 copy_to_user, 유저 프로그램이 가공을 위해 커스텀 커널 모듈로 데이터를 전달할 때 copy_from_user, 가공을 마친 모듈이 최종 전송을 위해 다시 유저 스페이스로 결과를 돌려줄 때 copy_to_user, 마지막으로 유저 스페이스가 이를 외부 네트워크로 전송하기 위해 소켓 시스템 콜을 호출할 때 copy_from_user까지 겹치며 번잡한 왕복 오버헤드가 쌓입니다.

첫째에서 파생되어, 할당기가 당신에게 할당 가능한 공간을 찾아 주지 못했고, NULL을 반환했을 때 OS의 나머지 영역에서도 할당이 필요한 부분은 있을 것입니다. 이 과정에서 어떤 것이 할당 실패하고 OS에서 어떻게 장애가 발생 가능한지 예측도 힘들며, 안정성을 찾아보기 힘들 것입니다.

그렇다고 버퍼의 크기를 작게 하면 미친 듯이 밀려 들어오는 정보를 드롭 없이 다 받아낼 수 있을지 확실하지 않죠.

이 두 가지 문제 외에도 다양한 문제가 겹쳐 우리는 정보가 전달될 수 있다는 신뢰성을 잃게 됩니다.

만약 잠재적으로 위험한 패킷을 단순한 필터링을 통해 걸러 내는 정도만 한다면 어떻게 될까요?

이것은 eBPF 모듈을 작성하여서 네트워크 송수신 시점에 그물망을 걸듯이 1회 걸어 주면 됩니다.

유저 스페이스로 올라온 정보를 파싱하고, 가공하고, 유효한 데이터인지 검증하고, io_uring이나 kqueue같은 데 다 때려넣든, 그냥 전통적으로 단순한 쓰기를 하든 네트워크 드라이버 레벨에서 1번 체를 쳐 주고, 통과하는 것만 보내면 됩니다. 이렇게 단순히 체를 쳐서 보내는 것을 유저 레벨에서 하기에는, 분식집을 할 때 궁중요리용 마미체로 쳐서 보내는 격이 될 수도 있습니다.

그리고 이럴 때는, 가볍고 투박한 커널 영역에서의 체 치기가 '주방용 스테인리스 체'처럼 괜찮을 수 있습니다.

이 과정은 너무도 정석적인 방법 앞에 한 개의 필터만 더해져서 느려지지도 위험해지지도 않습니다.

종종 우리는 무거운 작업을 "커널로 올리면" 마법처럼 해결될 것이라고 믿습니다. 하지만 커널에서 사용 가능한 메모리 영역도 제한적이고, 복사 오버헤드가 들며, 커널에서의 사소한 누수나 위험한 참조는 시스템의 패닉을 불러 옵니다. 이러한 것이 우리와 같은 아마추어에게 어떤 교훈을 주는지 짧게 요약 가능합니다.

내 집에서 말을 타고 달릴 수 없으며, 초원에서 소반을 펴고 밥을 먹을 수 없다.

Back

Comments

No comments yet.