입력과 출력

개요

  • 컴퓨터 프로그래밍에서의 입력과 출력 시스템에 대한 정리

입출력

  • 입출력은 프로그래밍을 배울 때 제일 첫 번째로 배우는 시스템
  • 모든 개발자들은 처음 배울 때 hello, world!!를 출력하는 법을 배우게 됨
  • 입력은 학습 과정에 따라 조금 늦게 배울 수는 있음.(변수에 대해서 배워야 하기에)
    하지만 어떻게 본다면, 대부분 출력보다도 더 중요하다고 여기게 되는 기능(빠른 입출력 때문에)

포스트 목적

  • 나름 익숙해져서 별 것 아니라 생각할 수도 있는 입력과 출력 시스템에 대한 개괄적인 개념 정리
  • 입출력에 의한 보안 문제
  • 좋은 프로그램을 만들기 위해 입출력을 어떻게 다뤄야 하는지에 대한 주장

입출력의 종류

  1. 콘솔 입출력 Console I/O
  2. 파일 입출력 File I/O
  3. 네트워크 입출력 Network I/O
  4. 장치 입출력 Device I/O
  5. 메모리 입출력 Memory load/store

콘솔 입출력

  • 용도) 대다수의 개발자들이 알고리즘 문제를 풀기 위해 사용, 혹은 간단한 CLI(Command Line Interface) 환경 프로그램
  • 특징) 휘발적인 입출력. 영구저장 용도가 아닌 임시적인(즉발적인) 입출력
  • 데이터 종류) 주로 텍스트(ASCII, 유니코드). 간혹 바이너리(이진) 데이터를 다루기도 함.
  • 프로그래밍 처음 입문시에 배우는 기초적인 입출력. printf, scanf를 시작으로 프로그래밍을 배움
  • 키보드 입력을 기반으로 동작하는 프로그램을 작성할 수 있음.

파일 입출력

  • 용도) 2차 저장공간(HDD, SSD)에 데이터를 저장하기 위한 입출력.
  • 특징) 영구적인 입출력. 프로그램이 종료되어도 데이터가 남아있음.
  • 데이터 종류) 텍스트 데이터, 바이너리 데이터 모두 다룸. (json, txt, csv, exe, mp4, m4a...)
  • 대용량 데이터를 영구적으로 저장하고 불러오는 프로그램 작성 가능.

네트워크 입출력

  • 용도) 서로 다른 프로세스 간의 데이터 교환을 위한 입출력. 서로 다른 컴퓨터간의 프로세스끼리도 교환 가능
  • 특징) 네트워크 환경에 영향을 받음. 불안정한 부분이 존재할 수 있음. (네트워크 지연, 패킷 손실 등)
  • 데이터 종류) 텍스트 데이터, 바이너리 데이터 모두 다룸. (json, txt, csv, exe, mp4, m4a...)
  • 소켓프로그래밍을 통해 네트워크 입출력 구현.
  • 로컬 컴퓨터의 범위에서 벗어나 멀리있는 컴퓨터와의 데이터 통신이 가능해짐.

장치 입출력

  • 용도) 컴퓨터에 연결된 하드웨어 장치와의 데이터 교환을 위한 입출력. (예: 프린터, 스캐너, USB 장치 등)
  • 특징) 하드웨어와의 직접적인 상호작용이 필요할 수 있음. 드라이버나 API를 통해 접근
  • 데이터 종류) 대체로 바이너리 데이터를 다룸.
  • 마우스(포인터), 키보드(키), 패드(키), 프린터, usb, 모니터(그래픽), 스피커(사운드) 등 다양한 장치와의 입출력 존재
  • gui 프로그램 개발시에 주로 다루게 됨.(게임) 보통 OS 레벨에서 제공하는 API를 직접적으로 호출할 줄 알아야 함.

메모리 입출력

  • 용도) 프로그램 내부에서 상태의 저장을 위한 입출력. (예: 변수에 값 저장, 배열 요소 접근 등)
  • 특징) 보통, 메모리에 데이터를 저장하고 읽는 것은 입출력으로 따지지 않음. 하지만, store, load 동작은 cpu 레지스터 - 메모리 간의 입출력(데이터 이동)이라고 볼 수 있음.
  • 일반적인 환경에서 메모리 입출력이 문제가 되는 경우는 없음. 메모리는 온전히 해당 프로세스의 소유로 외부에서의 접근이 불가능. 단, 멀티스레딩 프로그래밍 시에 문제가 발생하게 됨.
  • 스레드라는 흐름 단위가 등장하면서, 여러 스레드가 하나의 메모리를 공유하는 상황 발생. 이 때, 단순 입출력보다도 더 심각한 문제가 발생하게 됨. (경쟁 상태)
  • 메모리 읽기, 쓰기 또한 일종의 IO라고 할 수 있음. (커널모드 교체 비용은 발생하지 않는다는 특수성이 있음)

운영체제와 입출력의 관계

  • 입출력은 외부에 있는 값을, 프로세스 내부 메모리로 끌고오는 혹은 밖으로 끌고가는 행위.
  • 이 때, 프로세스가 직접 데이터를 조작하는게 아닌, 운영체제에게 시스템 콜 요청을 통해 운영체제가 직접 데이터 복사를 수행하게 됨.
  • 보통 print 문을 통해 언어단위에서 추상화되어 있지만, 내부적으론 OS가 제공하는 API를 통해 입출력이 이뤄짐.

IO 내부 동작

  1. 프로그램이 OS에게 IO 요청을 보냄. 특정 메모리 공간에 데이터를 넣어주세요 / 특정 메모리 공간의 데이터를 가져가 주세요.
  2. 인터럽트 발생, 프로세스는 sleep 상태 돌입. 유저모드 -> 커널모드로의 전환. 컨텍스트 스위칭 비용이 발생.
  3. OS가 유저 메모리에 있는 데이터를 커널 메모리로 복사, 이후 커널 메모리에서 IO 장치로 복사. (두 번의 복사가 발생)(입력의 경우는 반대)
  4. IO 완료 후, OS가 프로세스를 깨움(컨텍스트 스위칭) -> 프로세스가 다시 runㅜable 상태로 돌아옴.

정리

  • 입출력은 프로그램이 실질적인 의미를 가지게하는 상호작용 시스템.
  • console, file, network... 등 다양한 종류의 입출력 시스템이 존재.
  • 표준 라이브러리에 의해 입출력 동작들이 추상화되어 있지만, 직접적으로 입출력을 다루기 위해서는 OS가 제공하는 APi를 이해할 필요가 있음.

입출력의 위험성

외부 세계와의 상호작용

  • 입출력은 자신 프로그램 외의 데이터를 다루는 행위. 즉, 외부 세계와의 상호작용이 발생하는 지점
  • 닫힌 계가 열린 계로 전환되면서, 취약점 발생 가능성이 생기게 됨.
  • 내부 시스템이 다운되거나, 데이터 조작 혹은 유출과 같은 보안 사고들이 발생할 수 있는 지점

1. 형식과 다른 입력(원하지 않는 입력 데이터)

  • 데이터는 기본적으로 바이트 단위의 스트림 구조를 가짐.
  • 이를 유의미한 데이터로 다루기 위해선 파싱을 해야만 함. 특정한 형식으로 해석(파싱)하는 것은 프로그램의 몫. (예: 텍스트, json, csv, exe...)
  • 원하는 형식으로의 파싱이 실패한 경우, 시스템이 다운되거나 최악의 경우 잘못 파싱된 데이터를 정상적으로 처리하려는 과정에서 시스템 결함이 크게 발생할 수 있음.
  • 이를 대처하기 위해, 파싱을 잘못됐을 경우에 대한 처리를 해주는 것이 중요.

형식과 다른 입력 예시)

  • a, b = map(int, input().split()) python 코드
  • scanf("%d %d", &a, &b) c 코드 => 입력값이 정수형이 아닌 겨우, 바로 에러 터지면서 시스템 다운됨.

2. 조작된 데이터 혹은 유효하지 않은 입력

  • 입력 데이터의 형식이 개발자가 의도한 올바른 형식이지만, 악의적으로 조작된 데이터일 경우
  • 특히나 문자열을 받을 경우에 더욱 취약해짐. 데이터를 검증하는 과정이 필요. (예: 입력값의 길이, 범위, 형식 등을 체크)
  • 예) SQL 인젝션, XSS 공격, 버퍼 오버플로우 공격 등

3. 입출력 요청의 비용

  • 입출력은 OS에게 시스템콜을 통해 제어를 넘기는 행위. 컨텍스트 스위칭과 데이터 복사로 인한 비용이 발생하게 됨.
  • 또한 동기적으로 입출력을 수행하게 되므로 입력의 경우 프로세스가 입출력이 완료될 때까지 대기해야 하는 상황이 발생할 수 있음.(블로킹)
  • 이 때문에, 입출력 요청이 과도하게 발생하는 경우 시스템의 성능 저하가 발생할 수 있음. (예: 대량의 파일 입출력, 네트워크 입출력 등)
  • 이에 대처하기 위한 입출력 최적화가 요구됨(예: 버퍼링, 비동기 입출력, 배치 처리 등)

입출력 계층화

  • 입출력에 대한 대응을 효율적으로 하기 위해, 입출력과 프로그램 내부 로직을 계층화하여 격리시키는 것이 중요하다고 생각
  • 프로그램 내부 동작을 입력단 -> 내부 로직 -> 출력단으로 계층화하여, 입출력과 내부 로직을 분리
  • 내부 로직은 정상적인 입력만 들어온다는 가정 하에 효율적으로 개발할 수 있고, 입출력 계층에서는 입력 검증과 출력 형식화에 집중해 책임 분리를 할 수 있음.

마무리

  • 통상적으로 부르는 입출력은 프로그램의 가치에 큰 영향을 끼침. (cli보다 gui 프로그램이 일반적으로 더 낫다고 여겨짐)
  • 이 때문에, 과도하면 입출력 자체에만 크게 몰두하게 될 수 있지만, 프로그램 내부에서의 데이터 처리 방식과 같은 내부 로직 또한 매우 중요한 요소임
  • 프로그램 제작 시에, 이 입출력과 내부 로직을 계층화하여 격리시켜야 효율적인 개발이 가능

''

  • 이미 수많은 프레임워크들이 입출력을 추상화 시켜둠. 네트워크 라이브러리, 그래픽 프레임워크를 충분히 제공.
  • 웹개발이나 게임개발과 같은 응용 프로그램 개발시에, 보통의 개발자들은 네트워크 IO나 그래픽스 같은 요소들을 직접적으로 다루지 않음
  • 소켓 프로그래밍이나 그래픽스를 학습해 직접 자체 엔진(프레임워크)을 만드는 것을 생각하는 사람이라면,
    상용 프레임워크들처럼 이 입출력 계층화를 잘 구현하는 것또한 주요 과제
  • 또한, 이미 잘 개발된 프레임워크를 사용하는 개발자들도 외부 입력에 따른 동작과
    내부적인 프로그램 동작 상의 변화를 구분지어 본다면 좀 더 안정적인 프로그램을 만들 수 있다고 생각

결론

  • 입출력은 프로그램을 이루는 전부이면서도 일부에 지나지 않은 부수요소
  • 게층화를 통해 입력을 잘 통제하고, 출력을 자연스럽게 만들어 애플리케이션 개발시에 크게 신경쓰지 않아도 되도록 추상화를 잘 하자