[pipex](2)pipex 함수 파악하기



1️⃣ 목표

  • pipex의 서브젝트를 보면 알 수 있듯이 이번 파트는 쉘 커맨드에 관한 파트입니다.
  • 처음의 과제를 접했을 때 피펙스라고 읽었는데 사용가능한 함수중 pipe()함수가 있는 것을 보고 파이프엑스로 읽어야함을 깨달았습니다. 그만큼 pipe()함수는 아직 저에게 매우 낯선함수입니다.
  • 목표에 대해서 서브젝트에 예시가 잘나와있지만 어떤식으로 구현할지 아직 막연한 것이 사실입니다. 다행히 힌트(?)로 주어진듯한 사용가능한 함수 목록이 있는데, 거기에 나온 함수를 하나하나 파악해가면 어느정도 해결책이 생길 것 같습니다.
  • 함수 목록중 처음보거나 애매한 함수는 다음과 같고 이번에 파악해볼 예정입니다. 1. pipe 2. execve 3. fork 4. access 5. unlink 6. waitpid 7. wait 8. dup 9. dup2 10. perror 11. strerror


2️⃣ pipe() 함수

#include <unistd.h>
int  pipe(int fd[2]);


  • 이번 프로젝트의 이름이 pipex인 만큼 이번 과제에서 가장 중요한 함수이지 않을까 생각이 듭니다.
  • pipe()함수는 말그대로 파이프를 생성해준다고 생각하면 될 것같습니다. 이 파이프데이터를 넣고 뺄 수 있습니다.
  • 단, FIFO (First In First Out, 선입선출)의 방식으로 데이터를 주고받을 수 있습니다.
  • 인자fd(파일디스크립터)변수를 넣어주어야합니다. 변수명은 마음대로지만 int형의 크기가 2인 배열을 넣어주어야합니다. pipe()함수는 이 fd[0]입력fdfd[1]출력fd로 만들어 줍니다.
int main(void)
{
    int fd1[2];
    int fd2[2];
    int fd3[2];

    pipe(fd1);
    pipe(fd2);
    pipe(fd3);
    printf("%d %d\n", fd1[0], fd1[1]);
    printf("%d %d\n", fd2[0], fd2[1]);
    printf("%d %d\n", fd3[0], fd3[1]);
}
/* 출력 */
3 4
5 7
8 9
  • 위의 예시는 파이프3개만들어준 뒤 각각의 fd요소를 확인해 보는 코드입니다.
  • 출력값을 보면 알 수 있듯이 3부터 겹치지 않게 배정되어 졌음을 알 수 있습니다.
  • 기본적으로 fd(파일디스크립터)에서 0, 1, 2는 다음으로 배정되어 있습니다.
    • 0: 표준입력
    • 1: 표준출력(콘솔출력)
    • 2: 에러출력
  • open()함수를 이용했을때도 fd를 배정받는데 사용한 후에 close()함수를 이용하여 닫아줬습니다. pipe()함수 역시 fd를 배정받기에 사용 후 혹은 사용하지않는 fd(파일디스크립터)close()함수를 이용하여 닫아주어야 합니다.
  • pipe()함수가 실패했을 경우 -1을 반환합니다.


3️⃣ fork() 함수

#include <unistd.h>
pid_t  fork(void);


  • fork()함수는 프로세스를 복사해주는 함수입니다. 이렇게 복사된 프로세스를 "자식 프로세스" 기존의 프로세스를 "부모 프로세스"라고 부릅니다.
  • 반환값으로 자식프로세스의 pid값을 반환합니다. 여기서 pidProcess IDentifier의 약자로, 프로세스의 고유 ID입니다. 자료형타입으로 pid_t를 사용하는데 int형으로 선언해도 잘 동작합니다. (자세한 이유는 모르겠지만 pid_t는 0 ~ 32767의 범위를 갖는다고 합니다. -1 == 32768경우도 포함)
  • fork()함수가 실패할 경우 -1을 반환합니다.
  • 자식프로세스fork()함수가 호출된 이후부터 진행합니다. 그렇기 때문에 자식프로세스에서는 fork()반환값을 저장한 변수의 값이 0입니다. 그렇기 때문에 다음의 코드예시와 같이 fork()함수가 반환한 자식함수pid를 이용하여 부모프로세스 혹은 자식프로세스에서만 동작하는 함수를 만들 수 있습니다.
int main(void)
{
    pid_t pid;

    pid = fork();

    if (pid == -1)
    {
        printf("fork() error");
        exit(1);
    }
    if (pid == 0)
    {
        printf("\n****자식프로세스****\n");
        printf("변수pid값: %d\n", pid);
        printf("자식피드: %d\n", getpid());
    }
    else
    {
        printf("\n****부모프로세스****\n");
        printf("변수pid값: %d\n", pid);
        printf("부모피드: %d\n", getpid());
    }
    return (0);
}
/* 출력 */

****부모프로세스****
변수pid값: 6239
부모피드: 6238

****자식프로세스****
변수pid값: 0
자식피드: 6239
  • getpid()함수를 이용하면 현재 프로세스의 pid값을 얻을 수 있습니다.


4️⃣ wait(), waitpid() 함수

(1) wait() 함수

#include <sys/wait.h>
pid_t  wait(int *statloc);


  • 형식은 pid_t wait(int *status)감시한 자식 프로세스pid값을 반환함과 동시에 인자값을 통해 자식 프로세스 종료 상태 정보를 알려줍니다.
  • 만약 어떠한 자식프로세스도 종료가 되는데 지연이된다면 무한 대기 상태에 빠질 수 있습니다.
  • 다음은 wait()함수를 쓰기 전 코드예시입니다.

/* 코드 생략 */

if (pid == 0)
{
    printf("\n****자식프로세스****\n");
    sleep(2);
    printf("자식프로세스 종료\n");
}
else
{
    printf("\n****부모프로세스****\n");
    sleep(1);
    printf("부모프로세스 종료\n");
}
/* 출력 */

****부모프로세스****

****자식프로세스****
부모프로세스 종료
$> 자식프로세스 종료
  • sleep()함수는 인자의 값만큼 딜레이를 가지게합니다. 즉, 위의코드에서 sleep()의 인자값이 더 큰 자식프로세스가 더 늦게 종료됩니다.

/* 코드 생략 */

if (pid == 0)
{
    printf("\n****자식프로세스****\n");
    sleep(2);
    printf("자식프로세스 종료\n");
}
else
{
    wait_res = wait(&status);
    printf("\n****부모프로세스****\n");
    sleep(1);
    printf("부모프로세스 종료\n");
    printf("wait인자값: %d\nwait반환값: %d\n", status, wait_res);
    printf("자식pid: %d\n", pid);
}
/* 출력 */
****자식프로세스****
자식프로세스 종료

****부모프로세스****
부모프로세스 종료

wait인자값: 0
wait반환값: 7891
자식pid: 7891

  • wait()함수가 호출된 시점에서 자식프로세스가 종료될떄까지 기다리고 코드가 진행됨을 알 수 있습니다.
  • wait()함수를 반대로 자식프로세스에서 사용해봤지만 아무일도 일어나지않았습니다 즉, 부모프로세스에서만 사용이 가능합니다.
  • 사실 wait()함수는 자식프로세스좀비 프로세스상태로 되는 것을 막기위해 사용하는 것입니다. 자식프로세스가 종료됐을지라도 부모프로세스가 진행중이라면 자식프로세스반환값을 지닌채 좀비 프로세스상태로 대기하고 있습니다. 그 때 wait()함수의 인자를 통해 자식프로세스의 반환값과 데이터등을 받아오면 자식프로세스는 데이터를 넘겨주고 좀비 프로세스 상태에서 벗어날 수 있습니다.

(2) waitpid() 함수

#include <sys/wait.h>
pid_t  waitpid(pid_t pid, int *statloc, int options);


  • waitpid()함수의 동작은 wait()함수의 매커니즘과 비슷합니다.
  • 자세한 사용법 및 인자waitpid 함수 사용하기 - 개발여행기를 참고하면 될 것같습니다.
  • waitpid()함수는 wait()함수보다 더 다양한 일을 합니다. 특징적으로 무한대기 상태가 될 수 있는 wait()함수를 보완한 함수입니다.
if (pid == 0)
{
    printf("\n****자식프로세스****\n");
    sleep(4);
    printf("자식프로세스 종료\n");
}
else
{
    wait_res = waitpid(pid, &status, WNOHANG);
    printf("\n****부모프로세스****\n");
    sleep(1);
    printf("부모프로세스 종료\n");
    printf("\nwaitpid인자값: %d\nwaitpid반환값: %d\n", status, wait_res);
}

return (0);
/* 출력값 */

****자식프로세스****
자식프로세스 종료

****부모프로세스****
부모프로세스 종료

waitpid인자값: 0
waitpid반환값: 2935
  • waitpid()의 3번째 인자에 0을 넣으면 wait()함수와 같이 동작합니다. 하지만 다른점은 여러개의 자식프로세스가 있을 때 wait()함수는 자식프로세스중 하나라도 종료되면 대기상태가 풀립니다. waitpid()함수의 첫번째인자로 -1을 넣게되면 이 마저도 같게 동작합니다.
  • 두번째 인자상태를 저장해주는 용도인 것같은데 자식프로세스의 반환값 이외에 어떤 데이터를 저장해주는지는 아직 잘 모르겠습니다. 이 데이터를 해석해주는 함수가 존재합니다. 과제를 차근차근 진행하면서 알아볼 예정입니다.
  • 정의되어 있는 세번째 인자의 상수값은 다음과 같습니다.
    • WCONTINUED: 0x00000010
    • WNOHANG: 0x00000001
    • WUNTRACED: 0x00000002
  • 세번째 인자를 잘 사용하면 wait()함수에서 일어날 수 있는 “무한대기상태”에서 벗어날 수 있습니다. (WNOHANG: 종료된 프로세스가 없으면 그냥 return한다.) 세번째 인자도 나중에 코드를 구현하면서 필요에 따라서 추가적인 기능을 알아보도록 하겠습니다.
  • 결론적으로 waitpid()함수는 특정 자식프로세스를 감시할 수 있다는 점 입니다.


5️⃣ fork(), pipe() 조합해서 사용해보기

int main(void)
{
    int fd1[2];
    int fd2[2];
    char buffer[BUFSIZE];
    pid_t pid;

    if(pipe(fd1) == -1 || pipe(fd2) == -1)
    {
        printf("pipe error");
        exit(1);
    }

    pid = fork();

    if (pid == -1)
    {
        printf("fork() error");
        exit(1);
    }
    if (pid == 0)
    {
        write(fd1[1], "(자식에서 입력)\n", 25);
        read(fd2[0], buffer, 25);
        printf("\n자식출력: %s\n", buffer);

    }
    else
    {
        write(fd2[1], "(부모에서 입력)", 25);
        read(fd1[0], buffer, BUFSIZE);
        printf("\n부모출력: %s\n", buffer);
    }
    return (0);
}
/* 출력 */

자식출력: (부모에서 입력)

부모출력: (자식에서 입력)

  • 위처럼 fork()함수와 pipe()함수를 조합해서 간단한 예시를 만들어 봤습니다.
  • 파이프FIFO방식으로 동작하기 때문에 2개를 만들어주어 일방통행으로 사용하도록 했습니다. 그렇게 되면 각각의 프로세스에서 안쓰는 fd가 생기게 되는데 적절히 close()함수로 닫아주면 될 것같습니다. (위의 예시에서는 생략)

(1) 동작방식 실험1

  • 위의 예시에서 자식 프로세스부모 프로세스가 독립되어 있지만 출력부분을 보면 입,출력이 꼬이지 않고 동시에 출력되었습니다. 절차지향형으로 프로그래밍이 진행된다는 것을 생각한다면 다소 이해가 되지않았습니다.
/* 코드 생략 */

if (pid == 0)
{
    write(fd1[1], "(자식에서 입력)\n", 25);
    read(fd2[0], buffer, 25);
    printf("\n자식출력: %s\n", buffer);
}
else
{
    //write(fd2[1], "(부모에서 입력)", 25);
    read(fd1[0], buffer, BUFSIZE);
    printf("\n부모출력: %s\n", buffer);
}
/* 출력 */

부모출력: (자식에서 입력)

  • 위와같이 부모 프로세스에서 입력을 하지 않아봤습니다.
  • 자식 프로세스에서 read()함수가 읽을 함수가 읽을fd가 있을 떄까지 대기하고 있음을 알 수 있습니다. 위의 예시에서는 읽을 fd가 없어서 아래의 printf()함수를 호출하지않고 그 지점에서 중단됐음을 알 수 있습니다.

(2) 동작방식 실험2

/* 코드 생략 */

if (pid == 0)
{
    write(fd1[1], "(자식에서 입력)\n", 25);
    read(fd2[0], buffer, 25);
    printf("\n자식출력: %s\n", buffer);

}
else
{
    wait(NULL);  // wait함수 추가
    write(fd2[1], "(부모에서 입력)", 25);
    read(fd1[0], buffer, BUFSIZE);
    printf("\n부모출력: %s\n", buffer);
}
/* 출력 */

  • 이번에는 부모프로세스에서 입력을 하기전에 wait()함수를 이용하여 대기를 시켜줬습니다.
  • 결국 서로 입력값을 못찾아서 아무것도 출력되지 않은 채 종료되었습니다.




© 2021.02. by kirim

Powered by kkrim