pipe 구현 - hochan222/Everything-in-JavaScript GitHub Wiki

Implementation of multiple pipes in C
Having trouble with fork(), pipe(), dup2() and exec() in C
Multiple pipes in C (Not complete, only for concept purpose)

리눅스 프로그래머를 위한 가이드 C 프로그래밍 언어로 파이프라인을 만드는 것은 간단한 쉘 예제보다는 많은 것을 포함한다. C로 간단한 파이프를 만들기 위해 pipe() 시스템 호출을 사용한다. 이것은 두 정수의 배열인 한개의 아규먼트(argument)를 가지며, 성공하면 배열은 파이프 라인을 위해 사용되는 새로운 두개의 파일 식별자를 갖는다. 파이프를 만들고 난 후, 프로세스는 전통적으로 새로운 프로세스를 낳는다. (자식은 열려진 파일 식별자를 상속받는다는 것을 기억하라)

SYSTEM CALL: pipe();

PROTOTYPE: int pipe(int fd[2]);
  RETURNS: 성공시 0
           -1 on error: errno = EMFILE (자유로운 식별자가 없다)
                                EMFILE (시스템 파일 테이블이 다 찼다)
                                EFAULT (fd 배열이 유효하지 않다)
NOTES: fd[0]는 읽기를 위해 준비되고, fd[1]은 쓰기를 위해 준비된다.

배열의 첫번째 정수(element 0)는 읽기를 위해 준비되고 열려지는 반면, 두번째 정수(element 1)는 쓰기를 위해 준비되고 열려진다. 시각적으로 말하면, fd1의 출력은 fd0를 위한 입력이 된다. 파이프를 통해 이동하는 모든 자료는 커널을 통해 이동함을 다시 말해둔다.

	#include <stdio.h>
	#include <unistd.h>
	#include <sys/types.h>

	main()
	{

		int fd[2];

		pipe(fd);
		.
		.
	}

C에서 배열의 이름은 첫번째 멤버의 포인터임을 기억하라. 위에서, fd는 &fd[0]와 같다. 파이프 라인을 만들고 난 후, 새로운 자식 프로세스를 생성(fork)한다.:

	#include <stdio.h>
	#include <unistd.h>
	#include <sys/types.h>

	main()
	{
		int fd[2];

		pipe(fd);

		if((childpid = fork()) == -1)
		{
			perror("fork");
			exit(1);
		}
		.
		.
	}

부모가 자식으로 부터 자료를 조회하기를 원한다면, fd1를 닫아야 하고 자식은 fd0를 닫아야 한다. 부모가 자식에게 자료를 보내고자 한다면, fd0를 닫아야 하고 자식은 fd1을 닫아야 한다. 부모와 자식간에 식별자를 공유하고 있으므로, 관계하고 있지 않는 파이프의 끝은 항상 닫혀져야만 한다. 기술적인 주의사항은 불필요한 파이프의 끝을 명백하게 닫지 않으면 EOF는 영원히 돌아오지 않는다는 것이다.

	#include <stdio.h>
	#include <unistd.h>
	#include <sys/types.h>

	main()
	{
		int	fd[2];
		pid_t	childpid;

		pipe(fd);

		if((childpid = fork()) == -1)
		{
			perror("fork");
			exit(1);
		}

		if(childpid == 0)
		{
			/*자식 프로세스는 파이프의 입력 쪽을 닫는다*/
			close(fd[0]);
		}
		else
		{
			/*부모 프로세스는 파이프의 출력 쪽을 닫는다*/
			close(fd[1]);
		}
		.
		.
	}

앞에서 말했던 것처럼, 파이프라인을 만든 후에 파일 식별자는 일반 파일의 식별자처럼 취급된다.

/*****************************************************************************
 리눅스 프로그래머를 위한 가이드 - 6장 에서 발췌
 (C)opyright 1994-1995, Scott Burkett
 ***************************************************************************** 
 MODULE: pipe.c
 *****************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
        int     fd[2], nbytes;
        pid_t   childpid;
        char    string[] = "Hello, world!\n";
        char    readbuffer[80];

        pipe(fd);
        
        if((childpid = fork()) == -1)
        {
                perror("fork");
                exit(1);
        }

        if(childpid == 0)
        {
		/*자식 프로세스는 파이프의 입력 쪽을 닫는다*/
                close(fd[0]);

		/*파이프의 출력 쪽으로 "string"을 보낸다*/
                write(fd[1], string, strlen(string));
                exit(0);
        }
        else
        {
		/*부모 프로세스는 파이프의 출력 쪽을 닫는다*/
                close(fd[1]);

		/*파이프로 부터 문자열을 읽는다*/
                nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
                printf("Received string: %s", readbuffer);
        }
        
        return(0);
}

종종 자식의 식별자는 표준 입력 또는 출력과 중첩된다. 자식은 표준 stream을 상속하는 다른 프로그램을 exec() 할 수 있다. dup() 시스템 호출을 살펴보자.:

 SYSTEM CALL: dup();                                                           

  PROTOTYPE: int dup( int oldfd );                                              
    RETURNS: 성공시 새로운 식별자 
             -1 on error: errno = EBADF (oldfd가 유효한 식별자가 아니다)       
                                  EBADF (newfd가 범위를 벗어났다)                 
                                  EMFILE (프로세스에 대해 식별자가 너무 많다) 

  NOTES: 과거의 식별자는 닫혀지지 않는다. 두개가 상호 교환되어질 수 있다.

과거의 식별자와 새로 만들어진 식별자가 상호 교환되어질 수 있더라도, 전통적으로 표준 stream의 한쪽을 닫는다. dup() 시스템 호출은 새로운 식별자에게 아직 사용되지 않은 가장 작은 번호를 부여한다.

주의해서 보자:

        .
        .
        childpid = fork();
        
        if(childpid == 0)
        {
		/*자식의 표준 입력을 닫는다*/
                close(0);
                
		/*파이프의 입력 쪽을 표준입력으로 한다*/
                dup(fd[0]);
                execlp("sort", "sort", NULL);
                .
        }

파일 식별자 0이 닫힌 후, dup()를 호출하여 파이프의 입력 식별자(fd0)를 표준 입력으로 복사한다. 정렬(sort) 프로그램과 자식의 텍스트 세그먼트(text segment)를 중첩시키기 위해 execlp()의 호출을 사용한다. 새롭게 exec된 프로그램들은 그들의 부모로 부터 표준 stream을 상속받으므로, 실제로 파이프의 입력 끝을 표준 입력처럼 상속받는다. 원래의 부모 프로세스가 파이프로 보내려는 것은 sort로 가게 된다.

dup2()라는 또 다른 시스템 호출을 사용할 수 있다. 이러한 특별한 호출은 유닉스 버전 7로 부터 시작되었고, BSD 버전에서도 수행되며 POSIX 표준에서도 필요하다.

  SYSTEM CALL: dup2();                                                          

  PROTOTYPE: int dup2( int oldfd, int newfd );                                  
    RETURNS: 성공시 새 식별자
             -1 on error: errno = EBADF (oldfd가 유효한 식별자가 아니다)       
                                  EBADF (newfd가 범위를 벗어났다)                 
                                  EMFILE (프로세스에 대해 식별자가 너무 많다) 

  NOTES: 과거의 식별자는 dup2()에 의해 닫힌다!

이런 특별한 호출을 사용하여 한개의 시스템 호출로 포장되어 있는 close 작업과 실제 식별자 복사 작업을 한다. 게다가, 작업의 원자화(atomic)가 허용되는데 이는 기본적으로 신호의 도착에 의해 작업이 중단되지 않음을 의미한다. 전체 동작은 신호의 발송을 위해 커널에게 통제권을 돌려주기 전에 일어난다. 프로그래머는 호출하기 전에 원래의 dup() 시스템 호출을 가지고 close() 작업을 수행해야만 한다. 그것은 그들 사이에서 경과되는 작은 시간의 양에 대한 다소의 약점을 가진 두개의 시스템 호출로 끝난다. 그 짧은 사이에 신호가 도착한다면, 시별자의 복사는 실패한다. 물론, dup2()는 이러한 문제를 해결했다. 살펴보자:

        .
        .
        childpid = fork();
        
        if(childpid == 0)
        {
                /* Close stdin, duplicate the input side of pipe to stdin */
                dup2(0, fd[0]);
                execlp("sort", "sort", NULL);
                .
                .
        }

/**
 * Pipe example
 *
 * Runs the command: ls -la / | wc
 *
 * Try running the above command on the command line to see if the
 * output matches.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(void)
{
    int fd[2];

    // Make the pipe for communication
    pipe(fd);

    // Fork a child process
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        // Child process

        // Hook up standard input to the "read" end of the pipe
        dup2(fd[0], 0);

        // Close the "write" end of the pipe for the child.
        // Parent still has it open; child doesn't need it.
        close(fd[1]);

        // Run "wc"
        execlp("wc", "wc", NULL);

        // We only get here if exec() fails
        perror("exec wc");
        exit(1);
    } else {
        // Parent process

        // Hook up standard output to the "write" end of the pipe
        dup2(fd[1], 1);

        // Close the "read" end of the pipe for the parent.
        // Child still has it open; parent doesn't need it.
        close(fd[0]);

        // Run "ls -la /"
        execlp("ls", "ls", "-la", "/", NULL);

        // We only get here if exec() fails
        perror("exec ls");
        exit(1);
    }

    return 0;
}
⚠️ **GitHub.com Fallback** ⚠️