[Pipex]

......·2023년 6월 13일

42서울

목록 보기
3/7

전체적인 목표

  • argument로 4개의 인자를 받음(file1 cmd1 cmd2 file2)
  • 해당 argument의 동작방식이 쉘에서 ./pipex < file1 cmd1 | cmd2 > file2 와 동일하게 동작
  • 의미는 pipex프로그램에서 file1이 프로그램의 표준입력이 되어서 cmd1을 적용을 시킨 것을 파이프(|)로 이어받아서 cmd2를 적용시킨 후, file2에 저장을 하면 되는 흐름
  • 주요내용
    -> PIPE란?
    -> 프로세스(부모, 자식, 고아)에 대한 이해
    -> fork함수
    -> pipe함수
    -> execve함수
    -> 전체적인 작성 개요

PIPE란?

  • 두 개의 프로세스가 서로 통신할 수 있게 해주는 버퍼(IPC통신)
  • 한 개의 프로세스는 읽기 기능, 한 개의 프로세스는 쓰기 기능만 가능
  • 만약 서로 읽기,쓰기를 실행하고 싶으면 두 개의 파이프를 준비해야함
  • 이와 같은 동작을 하며 FIFO형태를 띄는 큐라고 생각을 해도 됨
  • 파이프의 종류에는 Named-PIPE / Anonymous_PIPE 가 있음

Named-PIPE

  • 반이중통신(Half-Duplex)으로, 프로세스에서 하나의 파일을 공유해서 사용하기 때문에 한 번에 한가지의 동작(읽기 또는 쓰기)만 가능
  • PIPE타입의 파일을 생성해서 해당 파일을 통해 여러개의 프로세스에서 통신을 하는 방식
  • mkfifo() 함수를 통해서 파이프를 생성

Anonymous_PIPE

  • 단방향통신(Simplelx)으로, 여러개의 프로세스에서 하나의 프로세스는 쓰기, 하나의 프로세스는 읽기만 가능한 통신방법
  • pipe() 함수를 통해서 파이프를 생성
  • 사용 시, 파일디스크립터(fd)를 2개를 사용하는데, 1개의 fd는 읽기, 1개의 fd는 쓰기를 담당하며, 하나의 fd가 동작할 경우, 다른쪽 fd는 close해줘야함

fork()함수

  • #include <unistd.h> 헤더파일 내에 상속되어 있는 함수
  • fork함수를 사용하면 프로세스가 2개가 되며, 하나의 부모프로세스의 메모리를 그대로 복사해서 새로운 프로세스에 할당을 해준 후에, 서로 두개의 프로세스는 다른 메모리 값을 가지게 됨
함수 원형
pid_t fork(void)
성공 시 -> 부모 프로세스에서는 자식 프로세스의 PID값을 반환
	  -> 자식 프로세스에서는 0 값을 반환
실패 시 -> 음수 값(-1) 반환
  • 함수 사용 예시
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid;
    int x;
    x = 0;

    pid = fork();    
    if(pid > 0) 
    {  // 부모 코드
        x = 1;
        printf("부모 PID : %ld,  x : %d , pid : %d\n",(long)getpid(), x, pid);
    }
    else if(pid == 0)
    {  // 자식 코드
        x = 2;
        printf("자식 PID : %ld,  x : %d\n",(long)getpid(), x);
    }
    else 
    {  // fork 실패
        printf("fork Fail! \n");
        return -1;
    }
    return 0
}
  • 위의 사용 예시의 실행 결과

pipe()함수

  • #include <unistd.h>에 상속되어 있음
  • fd[1]에서 쓰기한 내용이 fd[0]으로 읽었을 때, 쓰기로 생성한 내용이 읽혀진 것을 확인가능, 즉 부모프로세스에서 작성한 내용을 자식프로세스에서 읽은것을 확인
함수 원형
int pipe(int fd[2])
-> fd[1]: 읽기, fd[0]: 쓰기
-> fd[2]가 할당되어 초기화 되어있지 않더라도 pipe함수에서 자동으로 할당 및 초기화가 이루어짐
성공 시 -> 0반환
실패 시 -> -1반환
  • 함수 사용 예시
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
        int fd[2];
        pid_t pid;
        char buf[MAX_BUF];

        if(pipe(fd) < 0){
                printf("pipe error\n");
                exit(1);
        }
        if((pid=fork())<0){
                printf("fork error\n");
                exit(1);
        }

        printf("\n");
        if(pid>0){ //parent process
                close(fd[READ]);
                strcpy(buf,"message from parent\n");
                write(fd[WRITE],buf,strlen(buf));
        }else{  //child process
                close(fd[WRITE]);
                read(fd[READ],buf,MAX_BUF);
                printf("child got message : %s\n",buf);
        }
        exit(0);
}
  • 예시코드 실행 결과

dup2()함수

함수 원형
int dup2(int old_fd, int new_fd)
-> old_fd에 new_fd를 복사하는것으로, new_fd를 닫고, 해당 new_fd를 불러내면 old_fd가 불러와지는 기능
-> 성공 시 new_fd반환
-> 실패 시 -1반환
  • 함수 사용 예시
#include <unistd.h>

int main()
{
	int fd1 = 5;
	int fd2 = 1;

	write(fd1, "error\n", 6);
	printf("%d\n", fd1);
	dup2(fd2, fd1);
	write(fd1, "hello\n", 6);
	printf("%d\n", fd1);
}
  • 예시코드 실행 결과

    -> fd1이 처음에 잘못되었기 때문에 출력이 안되었으나, dup2를 통해서 fd1을 fd2로 복사를 진행한 후, 확인해보면 hello가 출력된것을 확인가능
    -> 즉, dup2(fd2, fd1)을 통해서 fd2에 fd1이 같이 할당되어서 fd2 = 5, 1 이란 느낌

execve()함수

int execve(const char *pathname, char *const argv[], char *const envp[]);
-> 성공 시 pathname에 해당하는 프로그램을 실행, 해당 프로세스 종료
-> 실패 시, -1반환
-> pathname: 실행할 프로그램의 경로와 이름
-> argv: 프로그램에 전달할 커맨드 라인 인수들을 담고 있는 문자열 배열
-> envp: 프로그램에 전달할 환경 변수들을 담고 있는 문자열 배열
  • 함수 사용 예시
#include <stdio.h>
#include <unistd.h>
int main() {
    char *args[] = {"/bin/echo", "hello_world", NULL };
    char *env[] = {"$USER"};
   
    execve("/bin/echo", args, env);
    // execve 함수가 성공하면 이 아래 코드는 실행되지 않습니다.
    printf("This line will not be printed.\n");
    return 0;
}
  • 예시 코드 실행결과

    -> 리눅스에서는 /bin폴더에 실행될 프로그램들이 존재(예시. ls, echo, cat 등등)
    -> 따라서 execve함수를 통해서 /bin폴더의 echo프로그램을 실행시킨 것이며 args를 통해 hello world를 출력

전체적인 Mandetory파트 작성개요

  1. 처리해야하는 문제가 Shell에서 < input cmd | cmd2 > output 형식이므로 인자가 4개로 고정!
  2. inputfile의 fd를 STDIN에 연결시켜야함 -> 이를 위해 inputfile에 STDIN을 연결
  3. outputfile의 fd를 STDOUT에 연결시켜야함 -> 이를 위해 outputfile에 STDOUT을 연결
  4. 여기서 허용함수들만 보아도 프로세스간의 통신을 통해서 처리를 하는 것이기 때문에 fork함수를 통해서 분기
  5. fork함수에 따라오는 pipe함수를 통해서 부모-자식 프로세스간에 통신 준비
  6. 분기된 부모-자식프로세스에서 처음 cmd를 처리하기 위해서 부모는 STDIN를 가지고, 자식은 STDOUT을 통해서 부모프로세스로 exec함수를 통한 결과를 전달
  7. 해당 전달된 내용을 가진 부모프로세스에서 다시한번 fork함수로 분기를 통해서 자식프로세스에서 execve함수로 cmd2를 실행시키고 나온 결과인 STDOUT을 쓰기작업을 통해 outputfile의 fd에 작성이 됨

Bonus파트 작성 개요

  1. 보너스의 경우 처리할 케이스가 다중 cmd 및 Shell의 heredoc기능

  2. 다중 cmd의 경우, 처음에는 Mandetory파트와 동일하지만, 2번째 cmd부터 부모로부터 읽어와서 다시 exec함수를 통해 cmd를 반복해서 처리

    2-1. 마지막은 Mandetory파트와 동일하게 STDOUT에 있는 내용을 outputfile에 쓰기작업 실행

  3. Shell의 heredoc기능은 LIMITER로 지정된 문자열이 STDIN에 읽히기 전까지 STDIN에서 내용을 계속 읽어옴
    3-1. cmd << LIMITER | cmd2 >> outputfile 의 형식을 구현해야함
    3-2. STDIN이 read로 열려있는 경우, 대기상태로 입력을 계속 받아 이를 임시파일에 저장
    3-3. Mandetory파트의 경우를 사용해서 임시파일을 저장 및 읽기를 통해서 cmd2를 실행시킨 결과를 outputfile에 전송
    3-4. 생성한 임시파일을 unlink()함수를 통해서 임시파일을 삭제하면 heredoc기능 종료

0개의 댓글