Computer Science/시스템 프로그래밍

[시스템 프로그래밍] Chapter 6

seungwon9201 2024. 5. 9. 16:25

Pipe(이름 없는 파이프)

파이프라는 것은 같은 컴퓨터 안에서 돌고 있는 프로세스들 사이에 데이터를 주고받기 위해서 간단하게 사용할 수 있는 IPC 메커니즘이다.

파이프를 사용하기 위해선 먼저 파이프 객체를 만들어야 한다. 파이프 객체를 만들기위해서 사용할 수 있는 시스템 콜 함수가 pipe함수이다. 파일디스크립터가 2개가 리턴이 된다. fd[0]은 읽기 전용이고 fd[1]은 쓰기 전용이다. 파일 디스크립터를 잘못 지정할 경우에 오류가 발생한다. 즉, 파이프함수는 파이프 객체를 만들고 해당파일을 오픈도해주는 함수이다. 또한 데이터를 읽고 쓸때 전용 파일 디스크립터를 지정해야한다. 또한 FIFO구조이다. 성공시 0을 리턴 실패시 -1을 리턴한다.

Pipe의 특징

  •  파이프는 이름이 없다. ls를 해도 아무것도 안 뜸 (네임프 파이프는 ls를 하면 파일이 있다.)
  • 파이프는 일시적인 객체이다(temporary). 파이프를 사용하는 프로세스가 모두 종료를 하게 되면 사용했던 파이프 객체도 자동으로 소멸이 된다. (네임드 파이프는 프로세스가 모두 종료가 되더라도 삭제가 되는 것이 아니라 계속 남아있음) 파이프의 파일 디스크립터를 사용하는 프로세스가 없다면 파이프는 자동으로 삭제된다.)
  • 두 개의 파일 디스크립터를 알고 있어야만 파이프를 access할 수 있다.(아무 프로세스나 파이프를 사용할수있는것은 아님, 파이프의 특성상 부모자식관게의 프로세스들이 사용할 수 있다. 부모자식관계가 아니라면 사용할 수 없다. 왜냐하면 이름도 없고 전혀상관없는 프로세스가 파일의 디스크립터를 알아낼수있는 방법이 없기떄문이다. fork()를 해서 프로세스가 자식을 만들고 자식은 부모의 파일디스크립터를 그대로 상속받는다. 부모가 pipe함수를 써서 두개의 파일디스크럽터가 오픈된다, 디스크립터 테이블 안에도 두개의 엔트리가 추가가 된다. 그다음에 부모프로세스가 fork를 하게되면 두개의 파일 디스크립터 정보가 자식프로세스에게 파일 디스크립터 테이블 엔트리가 그대로 상속이 된다. 그래서 자식프로세스도 부모가 오픈한 파이프의 파일 디스크립터 정보를 상속받게 된다. 이러한 이유 때문에 파이프를 엑세스할 수 있음)
  • 두개의 파일 디스크립터 fd [0], fd [1]을 읽기 전용, 쓰기 전용으로 사용을 해야 한다. 혹시나 바꿔서 사용을 하면 오류가 발생할 것이다. POSIX에서는 잘못 사용했을 경우에 대한 것을 규정하지 않았기 때문에 무슨 일이 발생할지 모른다. 
  • 파이프를 통해서 read를 할 경우에 파이프의 상태에 따라서 달라진다(3가지 경우). fd [0]을 통해서 read함수를 호출을 하면 파이프로부터 데이터를 읽겠다는 것인데 이랬을 때 read함수가 즉각적으로 리턴을 하는 경우는 파이프가 비어있지 않을 경우(누군가가 파이프에 데이터를 써둔 것)이다.  read함수는 파이프에 있는 데이터를 읽어서 바로 실제 읽은 바이트의 수를 리턴을 해준다(1번째). 그럼 만약 파이프가 비어있는 상태로 read함수를 호출할 경우에 어떻게 될까? 파이프가 비어있는 경우는 두 가지가 있다 먼저 파이프가 비어있지만 후에 프로세스가 파이프에 데이터를 써주는 경우이다. 혹은 아예 쓸 데이터가 없어서 앞으로도 데이터가 쓰일 일이 없을떄이다. 이 두가지 경우에 따라서 read함수가 다르게 작동한다. 파이프가 비어있고 어떤 프로세스가 파이프의 쓰기전용으로 오픈을 해놨다면(즉, 어떤 프로세스의 파일디스크립터 테이블안에 쓰기용 디시크립터를 가지고 있는 프로세스가 있으면(fd[1]을 의미) OS는 이 프로세스가 언제든지 write함수를 써가지고 파이프에 데이터를 쓸수 있겠구나 라고 판단을 한다. 그러면 read함수는 바로 리턴이 되는것이 아니고 block이 된다.(2번째) 마지막으로 read함수를 호출했을때 파이프가 비어있는데 이 파이프에 쓰기용으로 오픈한 프로세스가 아예없는 경우에는 OS는 기다려봤자 데이터가 쓰여질 일이 없다고 판단한다. 그래서 이러한 경우에는 read함수는 바로 0을 리턴을 한다.(read함수에서 0을 리턴한다는 얘기는 end of file을 의미) 파이프에서 파일의 끝을 의미한다는 것은 이 파이프에 쓸 프로세스가 지금도 없고 앞으로도 없다는 것을 의미한다.  
  •  기본적으로 파이프는 blocking I/O를 사용한다. 읽을 게 없으면 읽을거리가 있을 때까지 blocking이 된다. 있으면 그냥 읽음. 

 

하나의 프로세스가 파이프를 만들어서 이 프로세스가 파이프에 쓰고 쓴 내용을 다시 이 프로세스가 읽어 가는 예제이다.
파이프 함수를 호출을해서 두개의 파일 디스크립터가 리턴이된다. fd[1]을 이용해서 쓰고 fd[0]을 이용해서 읽는다.end of file(CTRL+D)에 가게되면 파일의 끝이라는것을 파이프를 통해서 알려준다. end of file케릭터를 만날때까지 파이프에 있는 데이터를 쭉 읽어가게 될것이다.
하지만 이러한 예제처럼 사용하는 방식은 쓸모가 없다.


부모와 자식프로세스가 파이프를 통해서 어떻게 실제로 데이터를 주고받는지 알아보자. 왼쪽이 부모프로세스고 파이프함수를 호출해서 파이프 객체를 만들었다. 그래서 두개의 파일디스크립터가 오픈이 되었다. 그렇기 때문에 부모프로세스의 파일 디스크립터 테이블 안에 두개의 엔트리가 추가가 되었다.
이 상황에서 부모프로새스가 fork를 수행을 한다. 그러면 자식 프로세스가 생성이 되고 자식프로세스는 부모 프로세스의 파일 디스크립터 내용을 그대로 상속을 받기 떄문에 자식 프로세스도 파이프의 파일 디스크립터 정보를 알게 된다. 그러면 부모프로세스와 자식프로세스는 이 파이프 객체를 공유하는 상태가 된다. 그럼 어느쪽이든 파이프의 데이터를 쓸 수 있고 읽어갈 수 있다.

 

위의 예시를 한번 보자.
자식은 항상 파이프로부터 스트링 값 전체를 다 읽을까? read함수자체가 항상 내가 요청한 바이트 만큼 다 읽으라는것이 보장이 되지 않는다. 부모의 bufin은 항상 empty값을 가지고 있지만 자식의 buffin은 부모가 보내준 메세지를 전부다 읽게되면 hello로 다 덮어주는것이 정상적인 상황이다. 그러나 read함수가 항상 모두다 읽는다는 보장이 없기 떄문에 read를 하고 난 뒤에 확인을 해야한다.원래 버퍼의 내용이 empty였는데 처음 3byte만 읽게 되면 buffin에 처음 3바이트만 덮어 씌어지게 되고 helty처럼 될 수 있다. 그렇다고해서 read함수가 task를 수행하지 못한것은 아니다. read함수는 한 바이트라도 읽으면 자신의 task를 수행한 것이다. 이것을 염두해서 사용하자.

그렇다면 부모가 파이프에 hello를 쓰기 전에 자식프로세스가 먼저 read 하면 어떻게 될까? 

자식프로세스가 비어있는 파이프에 대해서 read를 하게 되면 read함수는 block이 될 것이다. 그래서 부모가 hello를 써줄때까지 기다릴 것이다. 왜냐하면 파이프에 write용으로 오픈한 프로세스가 있기 때문에(부모자식 모두 fd[1] 보유중) 비어있는 파이프에대해서 read를 하게되면 block이 될것이다. 그렇기 때문에 누가먼저 실행이 되는지에 따라서 걱정할 필요가 없다. 


FIFO(이름이 있는 파이프)

이름 없는 파이프는 temporary 하기 때문에 모든 프로세스들이 종료를 하게 되면 사용했던 파이프도 OS에 의해서 같이 삭제된다는 특징이 있었다.

그런데 일반 파일처럼 사용할 수 있는 파이프도 제공이 되었는데 이것을 FIFO or named pipe라고 한다.

FIFO는 일반파일을 만들 때 사용했던 것처럼 권한정보(rwx권한)를 지정을 해야 한다. FIFO는 이름 또한 지정해야 한다. 이름이 있기 때문에 permanent 하다. 그래서 ls를 하면 보임. 그렇기 때문에 FIFO의 이름을 알고 권한이 있다면 아무 프로세스와 통신이 가능하다.(pipe보다 사용범위가 넓음)

FIFO객체를 만들기 위해서 mkfifo함수를 이용하자. 첫번째 파라미터는 내가 만들려고 하는 파일의 경로명, 두번째 파라미터는 권한을 지정한다. mkfifo를 터미널에서 명령어로 사용할 수 있고 mkfifo함수를 통해서도 만들 수 있다. mkfifo함수를 호출하면 새로운 FIFO객체만 생성이 된다.(pipe는 객체를 만들고 오픈도 해줌) 그렇기 때문에 읽기든 쓰기든 하려면 open먼저 해야함. FIFO를 삭제하려면 rm 명령어를 사용하든가 unlink함수를 사용하자.

 

644권한, 성공시 0 실패시 -1 리턴
myfifo 객체를 삭제하려고 한다면 unlink함수를 써서  일반 파일 삭제하듯이 fifo의 이름 경로를 지정을 해주면 된다.

 

두개의 프로세스가 FIFO를 통해서 데이터를 주고받는 예제이다. 먼저 부모프로세스가 FIFO를 생성하고 fork를해서 자식프로세스를 만든다. 그다음 자식프로세스가 FIFO에 메세지를 쓰고 부모가 메세지를 FIFO로부터 읽어갈 것이다.(pipe는 부모가 자식에게 전달해줬다면 FIFO는 자식이 부모에게 메세지를 전달하는 예제이다.)
프로세스들이 모두 종료되고 난 뒤에 named pipe는 어떻게 될까? named pipe 이기때문에 프로세스가 모두 종료되더라도 남아있다. unlink함수를 호출해서 삭제를 한것이 아니기때문에 디렉토리안에 그대로 남아있다.