입력과 출력
개요
- 컴퓨터 프로그래밍에서의 입력과 출력 시스템에 대한 정리
입출력
- 입출력은 프로그래밍을 배울 때 제일 첫 번째로 배우는 시스템
- 모든 개발자들은 처음 배울 때
hello, world!!를 출력하는 법을 배우게 됨 - 입력은 학습 과정에 따라 조금 늦게 배울 수는 있음.(변수에 대해서 배워야 하기에)
하지만 어떻게 본다면, 대부분 출력보다도 더 중요하다고 여기게 되는 기능(빠른 입출력 때문에)
포스트 목적
- 나름 익숙해져서 별 것 아니라 생각할 수도 있는 입력과 출력 시스템에 대한 개괄적인 개념 정리
- 입출력에 의한 보안 문제
- 좋은 프로그램을 만들기 위해 입출력을 어떻게 다뤄야 하는지에 대한 주장
입출력의 종류
- 콘솔 입출력 Console I/O
- 파일 입출력 File I/O
- 네트워크 입출력 Network I/O
- 장치 입출력 Device I/O
- 메모리 입출력 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 내부 동작
- 프로그램이 OS에게 IO 요청을 보냄. 특정 메모리 공간에 데이터를 넣어주세요 / 특정 메모리 공간의 데이터를 가져가 주세요.
- 인터럽트 발생, 프로세스는 sleep 상태 돌입. 유저모드 -> 커널모드로의 전환. 컨텍스트 스위칭 비용이 발생.
- OS가 유저 메모리에 있는 데이터를 커널 메모리로 복사, 이후 커널 메모리에서 IO 장치로 복사. (두 번의 복사가 발생)(입력의 경우는 반대)
- 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나 그래픽스 같은 요소들을 직접적으로 다루지 않음
- 소켓 프로그래밍이나 그래픽스를 학습해 직접 자체 엔진(프레임워크)을 만드는 것을 생각하는 사람이라면,
상용 프레임워크들처럼 이 입출력 계층화를 잘 구현하는 것또한 주요 과제 - 또한, 이미 잘 개발된 프레임워크를 사용하는 개발자들도 외부 입력에 따른 동작과
내부적인 프로그램 동작 상의 변화를 구분지어 본다면 좀 더 안정적인 프로그램을 만들 수 있다고 생각
결론
- 입출력은 프로그램을 이루는 전부이면서도 일부에 지나지 않은 부수요소
- 게층화를 통해 입력을 잘 통제하고, 출력을 자연스럽게 만들어 애플리케이션 개발시에 크게 신경쓰지 않아도 되도록 추상화를 잘 하자