[C] TIL 4. 입출력 이론


  1. 커널에 대해서 설명하라.
  2. 버퍼에 대하여 소켓에대한 개념을 포함해서 설명하라.


boostcourse모두를 위한 컴퓨터 과학 (CS50 2019) 강의를 듣고 정리한 필기입니다. 😀


1 HCI

title

웹개발을 하다보면 UX(User Ecperience)가 HCI(Human Computer Interaction)의 보편적인 개념으로 자리했다. HCI는 Human(개인), Computer(컴퓨터), Interaction(상호작용)이라는 세가지 요소료 정의된다. 이런 HCI는 한편의 사용자와 컴퓨터 시스템 간의 주고받는 상호작용에 대해 연구하고 설계하는 분야이다. 특히, 디지털 사회로 도래하면서 CLI( Command-line Interface)환경에서 GUI(Graphic User Interface)를 거쳐 UX(User Ecperience)까지 개념이 진화했다.

title

위는 윈도우 cmd 이다. (CLI의 예이기도 하다.) HCI의 보편적 개념이 UX까지 진화한 지금 CLI를 논하는 것은 시대에 뒤떨어지는 것이 아니냐 하면, 또 그것도 아니다. 왜냐하면 위 그림처럼 CLI는 GUI, UX, UI 모든 개념의 근간이기 때문이다. CLI는 가상 터미널 또는 텍스트 터미널을 통해 사용자와 컴퓨터가 상호작용하는 방식을 말한다. 정보라는 것은 모두 문자열 형태로 존재한다. 특히나 요즘 웹 환경이 일반화 되었지만,웹을 이루고 있는 중요한 요소인 HTML, XML, HTTP 등이 정보를 이룰 때 문자열로 한다는 것이다.



2. 입력과 출력(Input and Output, I/O)

모든 HCI는 입력(Input)과 출력(output)에 기반한다. 즉, 컴퓨터 분야의 다양한 상황에서 쓰인다는 것이다. 그중 우리가 사용하는 키보드가 입력장치, 모니터를 출력장치라고 할 수 있다. 특히 C 언어의 기본 라이브러리에는 콘솔(consloe, 표준 출력 장치)과 키보드(표준입력창지)를 쉽게 다룰 수 있는 함수들이 있다. 이번 장에서는 getchar()putchar()같은 입출력함수를 배우겠지만, 그전에 알고 넘어가야 할 개념이 있다.

getchar()의 char는 캐릭터 즉, 문자 한 글자를 의미한다. 한 글자를 get, 가지고 오라는 뜻의 함수이다. 이런 함수를 보고 있노라면 ¹어디서 가지고 오는가? 그리고 ²어떻게 가지고 오는가? 에 대한 것들이 궁금할 것이다.

이러한 것들을 알기 위해서는 컴퓨터 내부에 대서 조금 언급할 필요가 있다. 컴퓨터라는 기계는 하드웨어와 소프트웨어로 이루어져 있다. 소프트웨어는 또 시스템 소프트웨어와 응용 소프트웨어로 나뉜다. window와 같은 운영체제가 시스템 소프트웨어에 해당한다. 그리고 이러한 컴퓨터는 여러 개의 층으로 표현할 수 있다.

title

가장 아래층에 있는 것이 하드웨어로, CPU, RAM, 키보드, 웹캡, 전원 공급장치 등을 말한다. 두 번째 층은 kernel 이라는 것으로써 운영체제 OS를 이루고 있는 핵심 코어 모델이다. 그리고 가장 상위층은 user 모드가 된다. user 층에는 네모 박스가 두 개가 있는데, 이 박스가 Process가 되는 것이고, 그 안에 있는 것들을 Tread 라고 이해하면 된다. 하나의 OS를 기반으로 여러 개의 Process가 돌아가서 멀티 테스킹(multitasking)이라고 한다. 그리고 한 Process 에서 여러 개의 Tread 가 작동하기 때문에 이건 멀티 쓰레드(multi thread)가 된다. 이와 같은 구성요소가 있다는 걸 상식적으로 알고 있어야 한다. user 에서 하드웨어까지, 심지어 펌웨어 부분도 C언어로 구현할 수 있기 때문이다.(user 부분이 목적화가 된다.)

2.1 Debugger

하드웨어, kernel, user, Process, Tread 등 이러한 것들은 각자 고유한 영역을 갖는다. 이러한 영역은 침범하면 안 되는, 우리가 지금 밥이 없다고 해서 옆집의 밥을 훔쳐 오면 안되는 것과 비슷한 맥락이다. 하지만, 각자의 영역을 침범해 각 프로세스의 메모리를 일일이 뒤질 수 있는 특권을 가진 소프트웨어가 있다. 이 소프트 웨어를 Debugger라고 한다. Debugger 라는 도구로 각자의 영역에 침범하면 해킹이 되지만, 이러한 해킹과 소프트웨어의 결함 문제를 해결하는 도구도 Debugger 라고 한다. 누가 Debugger를 사용하느냐에 따라 다르다는 것이다. 이런 Debugger를 프리지워드 소프트웨어라고 부른다.

2.2 입출력의 주체

가끔 사람들은 C언어의 함수를 프로그램에 Call해서 사용해서, 자기 자신이 입출력을 했다고 착각한다. 혹은 자신이 작성한 프로그램이 했다고 생각하기도 한다. 하지만, 소위 입출력(I/O)라고 하는 주체는 user나, user 모드 소프트웨어가 아니라, kernel이다.

2.2.1 쉘(shall)

title

하이드웨드에 무슨 장치가 있든, 이 장치들은 디바이스드라는 kernel 소프트웨어에 의해서 움직인다. 즉, kernel이 해야 할 일은 컴퓨터의 하드웨어를 제어하는 일일 것이고, 특정 하드웨어를 제어하는 명세서는 프로그래머가 작성한다. 그렇다면 프로그래머가 더욱 유연하게 하드웨어를 제어하도록 인터페이스를 제공할 필요가 있다. 이 역할을 하는 것을 device driver라고 한다. 이러한 장치 드라이버는 커널의 일부분으로써 작동하기 때문에 device driver 영역에서 작동한다고도 말한다.

title

사용 유저는 애플리케이션으로 시스템의 하드웨어를 제어해야 하는데, 이 애플리케이션이 있는 부분을 user space 라고 한다. 하지만 사용자는 프로그래머처럼 직접적으로 제어할 수 없기 때문에 kernel 함수를 통해 요청하는 식으로 제어를 할 수 있다. 문제는 kernel이 User space에 진입할 수 없다는 것이다. 이렇게 되면 kernel과 user가 단절되는 사태가 발생하여 사용자가 하드웨어를 제어할 수 없게 된다. 그래서 이 둘이 상호소통을 할 수 있도록 디바이스 드라이버는 인터페이스 가능한 특수한 파일을 유저에게 제공한다. 이때, 이 파일을 쉘(shall)이라고 한다.

쉘은 유닉스 계열의 시스템에서 사용하는 대화형 인터페이스이다. 사용자는 쉘에 명령을 입력할 수 있다. 사용자의 커널 사이에서 사용자의 입력을 받아서 명령을 해석하여 커널에 전달하고 결과를 사용자에게 반환한다. 쉘은 크게 네 가지 기능을 수행할 수 있다. ¹어플리케이션 실행/정지/Restart, ²환경변수의 관리, ³커맨드 히스토리 관리, ⁴커맨드 실행 결과 표시 및 파일 출력이 이에 해당한다.

어찌 되었건, 쉘을 통해 쓰고, 수정한 것이 디바이스드 드라이브에 전달되고, 그것이 하드웨어까지 닿으면 유저가 원하는 데로 작동을 할 것이다. 그리고 이 전달되는 정보에는 고유의 규칙 또한, 존재할 것이다. 보통 이 규칙을 프로토콜이라고 한다. (네트워크 프로토콜, 로컬 디바이스 프로토콜 등등), 이 프로토콜이 리모트로 가든, 머신 내에서 이루어진 모두 프로토콜에 해당한다. 이러한 프로토콜은 매우 다양할 것이다. 그렇다고 해서 사용자가 매번 다른 규칙에 맞춰서 명령하는 것은 힐 들 것이다. 그래서 이러한 것을 하나의 함수 형태로 만든다. 그렇게 만들어진 대표적인 함수가 getchar()putchar()가 되는 것이다.

2.2.2 소켓(Socket)

getchar(), putchar() 등과 같은 함수들은 입출력을 요구, 요청하는 함수이다. 직접 입출력하는 것이 아니라 “요구”, “요청” 함수이다. 왜냐하면 getchar(), putchar() 와 같은 함수들은 커널이 아닌 user space에서 쓰는 것이기 때문이다. 다시 한번 말하면 user space에서는 쉘을 통해서 쓰기 혹은, 읽기를 수행하는데, 쉘이 이 수행들을 커널에 요청하게 되는 것이다.

title

예를 들어서 하드웨어 층에 NIC(Network Interface Controller, LAN카드)라는 것이 있다. 그럼 그것을 움직이는 드라이버는 커널에 있고, 그 드라이버 위에 넷트워크 인터페이스라는 프로토콜 스텍(TCP/IP)이 존재할 것이다. 이제, TCP/IP를 user space에 접근할 수 있도록 추상화된 인터페이스가 존재하게 된다. 이 프로토콜을 추상화한 파일을 Socket(소켓)이라고 부른다. 이 소켓은 근본적으로 파일이다.

하지만, 요청은 우리가 하면 된다지만, 요청에 대한 응답은 우리가 결정하는 것이 아니다. 즉, 네트워크에서 정보가 언제 들어올지 모른다는 것이 문제다. 요청이 들어올 때까지 사용자를 기다리게 할 수 없으니, 이것을 해결하기 위해 비동기I/O라는 것을 하게 된다. 비동기는 입출력 요청을 먼저 하고 요청한 것에 대한 응답이 오면 빠르게 수행하는 식으로 행동한다.

2.3 버퍼(buffer)

title

소켓은 근본적으로 파일이다. 그리고 이 파일은 두 가지 버퍼를 가지고 있다. 즉, 메모리를 가지고 있다는 것이다. 컴퓨터 내부에 대해서 자세히 배울 것이 아니기 때문에 하나는 일기용(Read buffer)이라고 하고, 하나는 쓰기용(Write buffer)이라고 규정할 것이다.

2.3.1 Write buffer

title

putchar(‘A’)를 하게 될 경우 Write buffer안에 A라는 문자가 들어가게된다. 이런식으로 버퍼를 채우는 것을 버퍼링이라고 한다. 그리고 이러한 버퍼링은 한다 안한다의 기준 없이, 바로 커널에 있는 장치에게 전달되고 종래에는 콘솔에 A라는 문자가 출력되게 된다.

2.3.2 Read buffer

title

getchar() 함수는 Read buffer에 있는 한 글자를 퍼 올린다. 여기서 중요한 것은 버퍼 안에 6글자가 들어가 있어도 오로지 한 글자만 가지고 퍼 올린다는 것이다. 그리고 이렇게 퍼 올리고 나면 버퍼 속에는 6글자가 아니라 5글자만 남는다. getchar() 는 입출력을 요청하는 그 행동의 주체는 커널이고, 그 커널의 추상화된 인터페이스(소켓)는 파일마다 고유의 입출력 버퍼를 갖게 된다. 이때, 일기 전용이 I가 될 것이고, 쓰기는 O가 될 것이다. 이 I/O에 딸려는 버터가 있다. 이 버퍼로부터 글자 한 개를 퍼 올리는 함수가 getchar() 이다.

반복적으로 언급되는 동사 “퍼 올린다”는 그냥 가지고 오는 것을 비유한 단어가 아니다. 송수신을 마친 커널이 그걸 복사해서 퍼 올리는 것이 getchar()putchar()이다. 그래서 얘네는 입출력할 때 버퍼가 있는 것이다. 그리고 이 I/O를 buffered I/O를 한다고 이야기한다.

2.3.3 버퍼링 (buffering)

버퍼란, 기본적으로 메모리이다. 그런데 사전적 정의는 완충기이다. 충격은 무엇이고, 그 충격을 어떻게 완화한다는 것일까?

버퍼링을 설명할 때 가장 흔히 사용하는 예시는 유튜브이다. 유튜브는 전형적인 스트리밍 서비스이다. 즉, 내가 PC로 동영상을 재생하면 유튜브의 TCP에 접속해 내가 요청한 동영상을 틀어달라는 요구를 하게 된다. 그러면 유튜브가 정보를 우리 쪽으로 보내준다.

title

유튜브를 재생하다 보면 위 그림처럼 불투명한 흰색 선위로, 그보다 불투명도가 더 낮은 선이 지나가는 게 보일 것이다. 그리고 노란색은 우리가 동영상을 실행한 시간에 해당한다. 여기서 불투명도가 더 낮은 선이 버퍼에 담겨있는 동영상정보의 양이다.

동영상을 보다 보면 인터넷이 불안정하다는 등의 이유로 동영상 정보가 버퍼에 담기는 속도보다 동영상이 실행되는 속도가 빠른 경우가 있다. 그때 동영상이 잠시 멈출 것이다. 이것을 충격이라고 부른다.

이때 멈추는 현상을 버퍼링이라고 부르는 것도 일단 버퍼에 담긴 정보가 없어서 멈추고 나면, 버퍼에 어느 정도의 정보가 담길 때가 되어서야 다시 동영상이 재생되기 때문이다.

그러니까, 버퍼링은 약간 밑 빠진 독에 물을 뭇는 행위와 같다는 것이다. 물이 빠지는 속도보다 물을 채우는 속도가 빠르면 물이 항상 차있는 것처럼 보이는 것이 비슷하기 때문이다.

2.3.4 DMA

버퍼링이 존재하는 이유는 해당 정보를 소비하고 있는 사람이 넷트웍이 끊기는 등의 충격을 모르고 있기 때문이다.

title

A에 있는 정보가 목적지까지 가기 위해서는 A에서 B에게, B에서 C에게, 그리고 그 사이에 있는 무수한 경로를 타고 가야 최종 목적지에 가 닿을 수 있다. 이 과정에서 정보가 넘어갈 때마다 정보의 복사라 일어나고, 이러한 이유로 속도가 떨어진다. 가급적 한 다리 건너뛰는 것이 빠른데, 이런걸 직접 메모리 접근(Direct Memory Access, DMA)이라고 한다. DMA는 하드웨어 하위 시스템이 CPU와 독립적으로 메인 시스템 메모리에 접근할 수 있게 해주는 컴퓨터 시스템의 기능이다.

buffered I/O와 unbuffered I/O

buffered I/O는 복사해서 정보를 퍼온다고 위에서 설명했다. unbuffered I/O는 반대로 복사를 안 하고 다이렉트로 옮기는 것이다. buffered I/O와 unbuffered I/O는 근본적인 차이가 있다. buffered I/O는 입출력을 하지만, unbuffered I/O는 인터럽트 다이렉트 웨이트에 해당한다. 즉, 버퍼에서 읽어오는 게 아니라 하드웨어 장치가 인터럽트를 발생시킨다.

어떤 콘솔이라는 장치가 있고, 이 장치를 추상화한 소켓이 있다. 그리고 소켓에는 입출력 버퍼가 있을 것이다. 사용자가 타자를 쳐 정보를 입력하면 콘솔에서부터 소켓을 통해 올라가면서 버퍼에 차곡차곡 쌓여갈 것이다.

그 버퍼에서 한 글자씩 꺼내면 getchar(), 한 줄씩 꺼내면 gets(), 거기에 형식문자를 주고 그 규칙에 맞게 꺼내오면 scanf()이다. 이 세 가지 함수는 unbuffered I/O가 아니라 buffered I/O이다. unbuffered I/O를 한다고 하면 getchar()가 아니라, _getch()를 쓰면 된다.




© 2020. by RIVER

Powered by RIVER