시스템 콜과 자바에서의 시스템 콜 사용례

참고 링크

https://brewagebear.github.io/java-syscall-and-io/

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sheep_horse&logNo=221271778167

1. 시스템 콜이란?

  • 시스템 콜은 사용자 프로세스가 커널 프로세스에게 어떠한 문맥을 요청하면서 발생하는 것이다.

  • 이를 알기 전에 약간의 운영체제 지식이 필요하니 운영체제에 대해서 조금 훑고 지나가자.

1-1. 운영체제와 시스템 콜

  • 운영 체제의 목적은 다음과 같다.

    • 사용자가 편리하게 컴퓨터 시스템을 사용할 수 있는 환경을 제공

    • 컴퓨터 시스템 안의 하드웨어를 효율적으로 관리하기 위함

  • 그렇다면 어떻게 편리한 사용 환경을 만들고, 하드웨어를 효율적으로 관리할까?

  • 오늘날 대부분의 운영체제는 시분할 시스템이다. 컴퓨터 내에서 돌아가는 수 많은 프로세스들은 운영체제 스케줄링에 의해서 자원을 할당받아 실행된다.

    • 스케줄링을 통해 할당 받는 시간 단위가 매우 짧기 때문에, 여러 프로그램을 실행해도, 동시에 실행되는 것 처럼 느껴진다.

    • 즉, 프로세스는 운영체제 위에서 실행 중인 프로그램이라고 볼 수 있다. 당연히 이러한 환경이다보니 어떤 프로세스의 자원 처리나 하드웨어 작업 등의 처리가 나날이 복잡해졌다.

  • 우리가 C 언어를 사용할 때, 어떠한 메모리를 할당받았으면 반드시 프로그래머는 해당 구문이 필요가 없어지면 free() 를 사용해 자원을 반납해주어야 했다.

  • 하지만, Java 와 같은 언어들은 GC 를 지원하면서 자원 반납에 대한 프로그래머의 부담을 해소시켜주었다.

  • 운영체제도 우리에게 Java 와 같은 편리함을 제공해준다고 생각하면 될 것 같다. (우리는 컴퓨터를 사용하면서 프로세스 메모리에 대한 고민을 하지 않는다..)

  • 여기서 하나의 궁금증이 생길 수 있는데, C 언어에서 메모리 할당을 받는 것처럼 운영체제도 그와 같은 기능을 하지 않을까?

  • 운영체제는 크게 2가지 모드로 프로세스를 동작시킨다. (더 세분화된 모드들도 있다)

    • 사용자 모드 (User mode)

      • 사용자 모드가 사용하는 대부분의 프로그램들이 동작하는 모드

    • 커널 모드(Kernel mode)

      • 커널 모드는 운영체제 내부의 커널이 관리하는 프로세스 모드

      • 커널 모드를 통해 외부의 접근을 최소화해야하는 영역(보안)을 지정해두고 보안성을 높였다.

  • 사용자 모드가 커널 영역에 접근하는 것이 아니라, 운영체제에게 요청을 하면 해당 처리를 운영체제에 위임해 처리하도록 하는데, 이 명령을 바로 시스템 콜이라고 한다.

    • 우리가 C 를 사용하면서 malloc() 같은 명령어를 수행하면 내부적으로 시스템 콜이 발생해서 프로세스는 커널 모드로 변경되고 운영체제에게 이 요청을 위임한다.

    • 운영체제는 해당 명령어를 해석하고 할당해서 완료가 되면 프로세스에게 알려주고 다시 프로세스는 사용자 모드로 변경된다.

  • 중요한 부분은 단순하게 메모리 용량 할당 뿐 아니라, I/O 작업이나 네트워크 작업 등 커널 영역이 필요한 모든 곳에서는 시스템 콜이 필요하다.

  • 즉, 우리가 사용하는 프로세스는 수 없이 많이 사용자모드와 커널모드를 왔다갔다 하면서 작업을 수행하는 것이다.

  • 그렇다면 자바에서 시스템 콜이 가장 자주 일어나는 I/O 에 대해서 알아보자.

2. 자바와 시스템 콜

  • 자바는 기본적으로 JVM 이라는 가상화 머신을 사용해 동작한다. 그렇기 때문에, C 프로세스보다 한 단계를 더 거쳐 운영체제에 접근하게 된다.

  • 핵심은 C 프로세스의 경우 시스템 콜을 직접 사용할 수 있지만, 자바의 경우에는 간접적으로 사용할 수 있다는 것이다.

    • 만약 C 로 I/O 를 한다면 아래와 같은 흐름으로 시스템 콜이 발생할 것이다. C 프로세스 -> 시스템 콜 -> 커널 -> 디스크 컨트롤러 -> 데이터 복사

    • 자바는 아래와 같은 흐름으로 시스템 콜이 발생한다. JVM -> 시스템 콜 -> 커널 -> 디스크 컨트롤러 -> 커널 버퍼 복사 -> JVM 버퍼 복사

  • 때문에, C 프로세스에 비해서 Java 프로세스의 IO 처리 속도가 느릴 수 밖에 없다..

3. I/O 속도 향상을 위한 운영체제 수준의 기술

  • 자바 혹은 다른 언어를 사용하더라도, 결국은 시스템 콜을 사용하는 I/O 는 느릴 수 밖에 없다. 그래서 운영체제는 I/O 속도 향상을 위한 기술들을 제공하는데 다음과 같다.

    • 버퍼 (Buffer)

    • Scatter/Gather

    • 가상메모리 (Virtual Memory)

    • 메모리 맵 파일

    • 파일 락

3-1. 버퍼 (Buffer)

  • 버퍼를 설명하기 전에 앞서 시스템 콜 영역을 좀 더 세부적으로 그려보면 다음과 같다.

  • 유저 영역과 커널 영역에서 버퍼를 사용하는 모습을 볼 수 있다. DMA, Disk Controller 는 운영체제 내요이니 패스~

  • 버퍼는 무엇이고, 왜 사용해야 할까?

    • 여러번 반복적으로 전달하는 것 보다. 중간에 버퍼에 값을 쌓아두었다가 일정량이 모이면 한번에 전달하는 것이 효율적이다.

    • I/O 비용은 비싸다..

  • 버퍼는 효율적으로 데이터를 전달하는 객체이다. 따라서 데이터를 전송하는 곳에서 대부분 버퍼를 사용하는데 운영체제도 예외적인 아니다.

  • 버퍼를 사용하고 안하고의 속도 차이를 보고 싶으면 다음 글을 참고하자. 차이가 상당하다;; I/O 기본1

3-2 Scatter / Gather

  • 만약 내가 버퍼를 N 개를 만들어서 사용하는데, 동시에 I/O 작업이 이뤄진다고 가정해보자. 그렇다면 N 번의 시스템 콜이 일어날 수 있다고 추론할 수 있다.

    • 시스템 콜은 컨텍스트 스위칭과 비교해서 상대적으로 낮지만 여전히 성능에 영향을 줄 수 있다.

  • 이렇게 N 번의 시스템 콜을 요청하는 경우 당연히 비효율적이라고 볼 수 있다. 이러한 문제 때문에 운영체제는 Scatter 와 Gather 를 제공해준다.

  • Scatter 와 Gather 의 흐름은 아래 그림과 같다. 기존과 달리 버퍼에 대한 메타데이터(주소, 크기 등) 를 포함하는 구조체를 사용하여, 시스템 콜(시스템 콜이 1번만 발생한다) 시 이 정보를 함께 전달한다.

    • 메타데이터를 통해서 주어진 버퍼들을 순차적으로 읽거나 쓴다.

  • 자바에서는 이런 기능을 이용하기 위해서 java.nio.channel 패키지에 ScatteringByteChannelGatheringByteChannel 을 제공해준다.

3-3. 가상메모리

  • I/O 관점에서 가상메모리를 사용함으로 얻는 장점은 다음과 같다.

    • 실제 물리 메모리 크기보다 큰 가상 메모리 공간 사용 가능

    • 여러 개의 가상 주소가 하나의 물리적 메모리 주소를 참조함으로써 메모리를 유연하게 사용 가능

  • 가상 메모리를 사용하면 2개의 버퍼를 사용하더라도 뒤에서 볼 메모리 맵 파일을 통해서 동일한 영역에 접근이 가능해진다.

  • 따라서, 커널 영역 -> 유저 영역으로 데이터를 복사하지 않아도 된다.

3-4. 메모리 맵 파일(Memory-mapped I/O)

  • 위에서 가상메모리를 설명할 때 유저 가상 메모리와 커널 가상 메모리가 매핑되려면 메모리 맵 파일을 사용한다고 했는데, 이번에 메모리 맵 파일에 대해 살펴보자.

  • 우리가 인텔리제이(인텔리제이는 자바를 통해 만들어졌다) 를 통해서 코드를 입력하게 되면 I/O 시스템 콜이 발생할 것이다. 그리고 입력된 값을 다시 버퍼에 옮기는 작업이 이뤄질 것이고, 복사를 한 후에 가비지가 생기고 이를 또 가비지 콜렉터가 처리할 것이다.

  • 가비지 콜렉터가 가비지를 수거하는 것은 상당히 느린 작업이고, 많은 기업들이 GC 튜닝하는데 공을 들이는 이유일 것이다.

  • 이러한 문제점을 해결하기 위해 운영체제에서 지원하는 것이 MMIO(Memory-mapped I/O) 이다.

메모리 맵 파일 동작 방식

  • 파일 매핑

    • 메모리 맵 파일을 사용할 때, 프로그램은 파일을 특정 메모리 주소에 매핑한다. 즉, 파일의 내용을 특정 주소 영역과 연결하여, 파일 데이터를 메모리처럼 접근할 수 있게 만든다.

    • 메모리에 매핑된 파일은 프로그램의 메모리처럼 접근할 수 있어서, 파일을 읽거나 쓰는 동작을 메모리 조작만으로 처리할 수 있다.

  • 가상 메모리와 페이지 관리

    • 메모리 맵 파일은 가상 메모리를 사용하여 페이지 단위로 파일을 매핑한다. 필요할 때만 해당 페이지를 실제 메모리에 로드하는 방식으로 온디맨드 로딩(demand paging) 을 한다.

    • 파일의 특정 부분에 접근할 대만 운영체제가 페이지 단위로 메모리에 올리므로, 모든 파일을 한꺼번에 메모리에 올릴 필요가 없다.

  • 동기화

    • 파일을 변경할 때, 수정된 내용은 메모리에 쓰여지고, 운영체제가 적절한 시점에 디스크로 플러시한다.

    • MappedByteBufferforce() 메서드를 사용해 명시적으로 디스크에 기록할 수도 있지만, 기본적으로는 운영체제가 자동으로 변경된 데이터를 파일에 반영합니다.

속도가 빠른 이유

  • I/O 시스템 콜 감소

    • 메모리 맵 파일을 사용하면 파일을 메모리처럼 직접 조작하기 때문에, 파일을 읽고 쓸 때마다 시스템 콜을 호출할 필요가 없다.

    • 보통 파일 I/O 는 read(), write() 메서드에서 시스템 콜을 반복해서 사용하지만, 메모리 맵 파일은 단 한번의 매핑하는 시스템 콜 만으로 메모리에 파일을 연결하여 효율을 높인다.

  • 페이지 폴트 기반 온디맨드 로딩

    • 메모리 맵 파일은 필요할 때만 페이지 단위로 메모리에 올리는 방식이므로, 필요한 부분만 메모리에 로드됩니다. 특히 대형 파일을 처리할 때 유용하며, 전체 파일을 읽는 대신 특정 부분만 메모리에 올려 효율을 극대화할 수 있습니다.

  • 디스크와 메모리 간 복사 제거

    • 일반적인 파일 I/O는 파일 데이터를 커널 버퍼에서 사용자 버퍼로 복사하는 과정이 필요하지만, 메모리 맵 파일은 파일을 메모리에 매핑하여 복사 과정을 생략합니다.

    • 이로 인해, I/O 작업이 CPU 메모리 접근처럼 이루어지므로 데이터 복사에 드는 시간이 절약됩니다.

3-5. 파일락

  • 원래 자바 1.4 이전에는 파일락 기능을 제공하지 않았다. 이 부분도 운영체제의 기능 중 하나였기 때문이다.

  • 또한 파일락은 프로세스들의 접근 자체를 제한하거나, 접근하는 방법에 제한을 두어야 했어서 JVM 에서 처리가 불가능했다.

  • NIO 패키지에서 이러한 파일 락 기능을 제공하기 시작했다.

    • java.nio.channels.FileChannel.lock()

Last updated