Pipe란 무엇인가?

Park Choong Ho·2021년 2월 19일
0

Pipe란?

이런 명령어 입력해본적 있으신가요?

❯❯❯ ps aux | grep python

현재 프로세스중 python으로 돌아가는 프로세스를 알기 위한 명령어입니다. 흥미로운 것은 중간에 보이는 |의 존재입니다.

프로세스간의 커뮤니케이션

두 프로세스간 통신을 하기 위해 OS가 제공하는 장치는 많지 않습니다. 대표적으로 소켓이 있고 다른 하나가 오늘 설명하려는 pipe 입니다.(소켓에 대해서는 다음에 다뤄보도록 하겠습니다.) 위 예시를 다시 한번 보도록 하죠.

❯❯❯ ps aux | grep python

위 명령어를 프로세스가 하나라 생각할 수 있지만 사실 2개입니다. ps aux, grep python 이렇게 각기 다른 프로세스를 돌리는 것입니다. 두 프로세스는 pipe | 를 통해서 정보를 주고 받습니다. 파이프를 기준으로 왼쪽 프로세스 ps aux의 결과 값이 오른쪽 프로세스 grep python input 값으로 들어갑니다. 실제 수도관 파이프 처럼 한쪽에서는 물을 넣어주고 다른 한쪽에서는 그 물을 받는 형태입니다. 따라서 소켓과는 다르게 한 방향으로만 통신할 수 있습니다.

pipe는 총 2개의 파일을 생성합니다. 하나는 왼쪽 프로세스가 자신의 결과값을 write하는 파일, 다른 하나는 오른쪽에서 read하는 파일입니다. 아래 예시를 한번 보겠습니다.

import os

read_file, write_file = os.pipe()

pid = os.fork()

if pid == 0:
    #child
    os.close(read_file)
    wd = os.fdopen(write_file, "w")
    wd.write("Hello World")
    wd.close()
else:
    #parent
    os.close(write_file)
    rd = os.fdopen(read_file, "r")
    text = rd.read()
    print(f"I'm parent!: read from child: {text}")
    rd.close()

python 프로그램을 돌리고 pipe를 생성했습니다. 그리고 fork를 떠서 자식 프로세스를 만들었습니다. 자식 프로세스는 자동으로 부모 프로세스 file descriptor를 상속받습니다. file descriptor는 현재 프로세스가 open한 파일 정보를 담은 key라고 생각하시면 됩니다. pipe를 만든 순간 부모 프로세스는 파이프의 read_file과 write_file을 file descriptor에 등록합니다. 부모 프로세스를 fork하면 위에서 말했듯 자식이 부모의 file descriptor를 상속받습니다. 그러면 아래 그림과 같은 상황이 형성됩니다.

이 상태에서 자식이 write하면 부모에서 이를 read하고 출력하는 코드를 작성하고자 합니다. 하지만 위 그림과 같은 상태에서는 방금 말한 작업을 할 수 없습니다. 그 이유는 무엇일까요?

EOF

우리가 어떤 파일을 읽을 때는(OS가 제공하는 API, 즉 read 함수) EOF(End Of File) 까지 읽습니다. EOF를 만족할 중요한 조건이 하나 있는데, 그건 바로 모든 writer가 close되어야 합니다. 위 그림에서 자식 프로세스가 write을 종료했다고 가정해봅시다. 부모 프로세스가 read할 때, 자신이 write 파일을 open해 두었기 때문에 이를 EOF 조건에 충족하지 않았다(어? Writer가 있으니까 계속 먼가를 쓰고 있겠네? 내가 읽기를 멈추면 안되겠군...) 판단하고 계속해서 읽어 내려가게 됩니다. 이렇게 되면 read를 멈추지 않게 되고 결국 프로그램은 원치 않는 방향으로 흘러가게 됩니다. 따라서 부모에서 os.close(write_file)를 통해 write 파일을 닫아주어야 합니다. 그렇다면 자식에서 os.close(read_file) 코드의 역할은 무었일까요?

자식 프로세스에서 read file을 닫는 이유

os.close(write_file) 코드가 없으면 프로그램이 제대로 동작하지 않습니다. 하지만 os.close(read_file) 코드는 없어도 프로그램은 동작합니다.(그러면 안닫아도 되는 거 아닌가???) 그래도 닫는 것이 좋은데 2가지 이유 때문입니다.

하나,프로세스가 불필요한 여러개 파일에 접근하고 있는 것은 좋지 않습니다. file descriptor 갯수에도 제한이 있기에 필요없는 파일은 닫는 것이 좋습니다.

둘째,쓰는 입장에서 여러명이 파일을 읽고 있으면 진짜 reader가 누군지 판단하기 힘듭니다. (쓰고 있을 때는 되도록이면 한명이 읽는게 좋다는 말인가? 이 부분은 차후 학습 후 수정하겠습니다.) 따라서 해당 파일을 닫는다고 합니다.

Conclusion

지금까지 pipe를 공부해 보았습니다. 같은 시스템내 프로세스간 소통에 파이프는 굉장히 유용한 장치입니다. 핵심은
1. 파이프는 생성될때 2개의 파일을 생성한다. (하나는 write, 하나는 read)
2. 수도관처럼 한쪽방향으로만 진행된다. (write 파일에서 쓰면 read 파일에서 읽는다.)
3. fork를 하게되면 자식 프로세스는 부모 프로세스의 파일 디스크립터를 상속받는다.
4. 상속 때문에 두 프로세스 모두 write과 read파일을 open하게 되는데, parent는 반드시 write를 닫아줘야되고 (EOF 조건을 만족해야 read를 멈출 수 있으므로) child는 read를 닫는 것이 권장된다. (이 부분은 좀 더 공부를 해야됨)
이상입니다.

Reference

https://stackoverflow.com/questions/19265191/why-should-you-close-a-pipe-in-linux

profile
백엔드 개발자 디디라고합니다.

0개의 댓글