1. 프로세스간의 커뮤니케이션
UNIX pipes는 IPC(InterProcess Communication, 프로세스간의 협동을 위함)의 형태!
POSIX 시스템에서의 IPC폼의 예시 : Pipes, Sockets, Shared memory, Signals, Process return values, Enviroment variables, etc...
이 중에서 특히 UNIX Pipes를 살펴보자.
2. Pipes
A. Pipes란?
한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조
file-like abstraction for IPC, Reading과 writing엔 UNIX I/O 함수들을 사용한다. 파일 디스크립터로 표현된다.
처음에는 이 Pipe가 뭐 어떤 프로세스든 연결해주는 소켓 같은 것이라고 생각했는데 그렇지 않았다. 기본적으로 "Pipes는 프로세스 계층구조에 따라 자식 프로세스와 부모 프로세스를 연결해주는 것"이라고 이해하면 될 것 같다. 이에 대한 예시는 Pipes의 역할을 먼저 살펴본 후에 확인해보자.
B. Pipes의 역할
2개의 파이프로 쌍방향 통신도 가능하지만 기본적으로 파이프는 일방향 데이터 전송이다. 하나는 read-only(pipe[0])이고 다른 하나는 write-only(pipe[1])이다.
이 파이프는 앞서 서술한 대로 파일 디스크립터로 표현되고 어떠한 파일 디스크립터와도 연결될 수 있다 (STDIO라고 하더라도 말이다) 특히, STDIO를 통한 파이프가 가장 유용한데, 이를 통해 프로세스 P2에서의 out을 활용한 프로세스 P1의 입력을 연결할 수도, 파일 자체에 내용을 쓸 수도 있다.
C. Pipes의 메커니즘?
앞서 말한대로 파이프는 기본적으로 일방향 통신이고, 하나가 read-only, 다른 하나가 write-only이다.
하나의 파이프는 하나의 버퍼를 갖는데, 이 버퍼를 통해서 우리는 데이터를 저장(쓰기), 로드(읽기)할 수 있는 것이다.
그렇다면 이 버퍼에서 read를 할 수 있는 위치를 가리키는 포인터가 필요하고, write를 할 수 있는 위치를 가리키는 포인터가 필요하다. (마치 dequeue처럼 말이다.)
하지만 이 버퍼는 무한하지 않고 만약 read_pointer가 write_pointer보다 큰 위치를 가리킨다면 deadlock상태가 될 것이다. 그렇다면 프로세스간의 통신에 이것은 문제가 되지 않을까? 과거의 folk를 통해 자식 프로세서를 만들고 통신하던 프로그램에서 "어떤 프로세스의 코드가 먼저 실행되는지 장담할 수 없다"라고 명시되어 있었는데 만약 이 불확실성 때문에 read가 write보다 먼저 된다면 어떨까?
정답은 "어떤코드가 먼저 실행되더라도 상관없다." 이다. 왜나햐면 프로세스 간의 파이프는 deadlock을 고려하지 않고 설령 read가 먼저 되더라도 write를 기다렸다가 데이터를 받으면 그 이후에 실행이 되기 때문이다.
D. I/O Redirection
지금까지 pipe는 file descriptor를 통해 다른 프로세스 간에 데이터를 read, write 할 수 있다는 것까지 알아보았다.
만약에 이 파이프를 프로세스의 input으로 받는 것은 불가능할까? 파일 디스크립터는 복사가 가능하기 때문에 가능하다! stdin을 파이프의 read-descriptor로 변경(close(stdin) + desc[0] = pipe_read_ptr)한다면 마치 프로세스가 준 데이터를 stdin으로 받은 것과 같은, I/O Redirection이 되는 것이다.
E. Pipes의 예시
우선 상황부터 정의하고 가자. 우리는 pipe를 통해 부모 프로세서에서 자식 프로세서로 데이터를 보낼 것이다.
부모 프로세서에서는 단어를 자식 프로세서에게 전달할 것이고 자식 프로세서는 그 단어를 정렬하여 표준출력에 출력할 것이다.
-1. 부모 프로세스의 생성 및 디폴트 스트림의 설정
-2. pipe()호출을 통한 pipe 파일 디스크립터 2개 생성, 연결
-3. 자식 프로세스 형성 (fork, dup 등등), 같지만 독립된 프로세스 테이블
-4. 부모 프로세서에서 사용하지 않을 read file descriptor를 close하고, 자식 프로세서는 stdin인 0번을 닫고 read file descriptor를 0에 복사한다.
-5. 부모 프로세서가 write pointer로 데이터를 쓴다. 자식 프로세서는 더 이상 필요없는 pipe file descriptor를 닫는다.
-6. 부모 프로세스가 데이터를 다 쓴 이후 write descriptor를 닫는다. 이 경우 EOF가 전달되고 자식 프로세스는 모든 데이터가 전송되었음을 안다. 이후 부모 프로세스는 자식 프로세스를 기다린다
-7. 앞서 정의된 상황에 맞게 자식 프로세스는 부모 프로세스로 부터 전달 받은 데이터를 정렬한다.
-8. 연산이 종료되면 자식 프로세스가 stdout에 데이터를 출력하고 역할이 끝났으므로 종료되면서 부모프로세스에게 신호(signal)을 전송한다.
-9. 자식 프로세스가 종료되면서 자신의 디폴트 파일 디스크립터 0,1,2를 모두 닫고 부모 프로세스 또한 종료되면서 모든 파일 디스크립터를 닫는다.
이 과정에서 그렇다면 pipe 생성 시 open table은 몇개가 생기는 것일까?
정답은 2개가 생성된다. 왜냐하면 하나는 read-only만을 위해서, 또 하나는 write-only 만을 위해서 여는 것이다.
이렇게 2가지의 파일 디스크립터가 2가지의 open table을 가리키고, folk 혹은 dup을 통해 복사된 파일디스크립터 총 4개를 이용하여 하나의 파일을 통해(사실은 메모리지만 취급하기로는) 두 프로세스가 통신하는것과 같이 되는 것이다.
read든 write든 같은 파일을 이용하여 통신하는 것이므로 1개의 open table을 갖는게 아니냐? 라고 할 수 있지만, open table은 같은 파일이더라도 open시마다 생성되는 것이므로 그렇지 않다.
'학교 공부 > 시스템 프로그래밍' 카테고리의 다른 글
ELF 파일 분석 명령 (objdump, objcopy, nm) + ELF 데이터 치환 (0) | 2021.04.09 |
---|---|
시프 헤더파일 및 ELF 분석 (0) | 2021.04.06 |