허용 함수를 다 안다는 전제로 글을 작성했습니다. 모르면 아래 글 보기.
https://jinho-study.tistory.com/1138
전체적으로 봤을 때 로직은 아래와 같이 생각보다 간단하다. 실제로 파일 3개면 구현 가능하다.
int main(int ac, char **av, char **envp)
{
int i;
if (ac != 5)
에러 출력 후 종료
i = -1;
while (++i < 2)
{
if (i == 0)
명령어 파싱 -> 첫 번째 프로세스 실행(infile을 stdin으로 받고 명령어 실행)
else if (i == 1)
명령어 파싱 -> 두 번째 프로세스 실행(첫 번째 프로세스 실행 결과를 stdin으로
받고 명령어 실행 결과를 outfile에 저장)
}
포크된 프로세스 만큼 wait를 해준다. 멘덴토리의 경우에는 2번
i = -1;
while (++i < 2)
wait(NULL);
}
다만 여기서 알고 넘어가야 될게 2가지가 있다. minishell 내용 같긴 한데 pipex 하면서 알고 간다면
minishell 할 때 아주 큰 도움이 된다.
1 ) 쉘에서 명령어들은 병렬로 돌아간다.
예를 들어 sleep 3 | sleep 3은 6초를 기다릴 것 같지만, 실제로는 병렬로 돌기에 3초를 기다린다.
병렬이더라도 cat | cat 같은 경우에는 애초에 앞에서 입력이 들어와야 넘어가기 때문에 프로그램이 끝나지 않는다.
2) 파이프 개수만큼 fork 되는 것이 아니라 파이프 개수 + 1 만큼 fork 된다.
실행되는 명령어가 2개(ls | cat)라서 pipe가 한 개라고 fork를 한번 하지 않는다.
실제로 bash에서 그냥 exit은 당연히 꺼지지만 exit | exit 은 bash가 꺼지지 않는다.
그냥 exit은 fork가 안되고 바로 실행돼서 bash가 꺼지지만, exit | exit의 경우에는 fork가 2번 실행되어서
프로세스가 3개라 bash가 꺼지지 않는다. 이러한 구조를 가지는 것은 자식 프로세스에서 명령어를 수행하고
부모(main) 프로세스에서 모든 자식 프로세스를 기다리게 하기 위함이 아닐까?라고 작성자는 이해했다.
각 프로세스 별로 구현해야 되는 것들을 순서대로 쭉 나열해 보면
1) 첫 번째 프로세스
1.1) infile 읽고 pipe 세팅
자식 프로레스)
infile을 stdin으로 dup2, fd[1]을 stdout으로 dup2 해준다.
여기서 fd[0]만 사용 안 하니까 fd[0]만 close 하는 경우가 많은데, 둘 다 close 해줘야 된다.
fd[0]만 close 하면 yes | head -1 같은 테스트의 경우 프로그램이 끝나지 않는다.
부모 프로레스)
fd[0]을 stdin으로 dup2 해준다. 여기서 fd[0]은 자식 프로세스에서의 명령어 실행 결과가 된다.
결국 첫 번째 명령어의 실행 결과가 stdin으로 들어와 있는 상태라고 생각하면 된다.
여기서도 마찬가지로 fd[0], fd[1] 둘 다 close 해준다.
static void dup_child_1(char **av, int *fd)
{
int infile;
infile = open(av[1], O_RDONLY);
if (infile == -1)
perror_exit("infile error");
if (dup2(infile, STDIN_FILENO) == -1)
perror_exit("dup2 error");
if (dup2(fd[1], STDOUT_FILENO) == -1)
perror_exit("dup2 error");
close(fd[0]);
close(fd[1]);
}
static void child_process_1(char **av, char **envp)
{
int fd[2];
pid_t pid;
if (pipe(fd) == -1)
perror_exit("pipe error");
pid = fork();
if (pid == -1)
perror_exit("fork error");
if (pid == 0)
{
dup_child_1(av, fd);
execute(av[2], envp);
}
if (dup2(fd[0], STDIN_FILENO) == -1)
perror_exit("dup2 error");
close(fd[0]);
close(fd[1]);
}
1.2) 명령어 파싱 후 실행
메인에서 인자로 받는 envp 안에는 이런 식으로 환경변수들이 저장되어 있다.
여기서 우리가 필요한 것은 PATH=/Users/jinhokim/.brew/bin:/Users/jinhokim/.brew/bin:/usr/local/bin:/usr/bin:/
bin:/usr/sbin:/sbin:/usr/local/munki이다. PATH= 뒤의 문자열을 : 기준으로 split 하게 되면
["/bin", "/usr/bin", ...] 같은 결과가 되는데 총 6개의 경로가 나오게 된다. 이 경로들 안에 명령어 파일들이 들어있다.
예를 들어 ls는 /bin/ 안에 들어있는데 그렇기에 쉘에서 /bin/ls를 실행하면 ls가 실행된다.
이제 저 6개의 경로와 첫 번째 명령어를 ft_strjoin한 결과를 access 함수를 통해 실행 가능한 파일인지 확인하고
없는 명령어의 경우에는 에러 출력, 있는 명령어의 경우에는 그냥 execve를 실행해주면 된다.
// path -> ["/bin", "/usr/bin", ...]
while (path[i])
{
// joined_cmd = "/ls"
// ret_cmd = "/bin/ls"
ret_cmd = ft_strjoin(path[i++], joined_cmd);
if (access(ret_cmd, X_OK) != -1)
{
free(joined_cmd);
return (ret_cmd);
}
free(ret_cmd);
}
free(joined_cmd);
return (NULL);
}
2) 두 번째 프로세스
2.1) outfile에 실행 결과 저장
자식 프로레스)
outfile을 생성하고 stdout으로 dup2 해준다. 그러고 위에 설명했던 명령어 파싱 후 실행을 반복하면 된다.
stdout이 outfile에 연결되어 있기 때문에 이 프로세스에서 표준 출력되는 내용들은 모두 outfile 안에 저장된다.
부모 프로레스)
아무것도 할 필요가 없다.
static void child_process_2(char **av, char **envp)
{
pid_t pid;
int outfile;
pid = fork();
if (pid == -1)
perror_exit("fork error");
if (pid == 0)
{
outfile = open(av[4], O_RDWR | O_CREAT | O_TRUNC, 0644);
if (outfile == -1)
perror_exit("outfile error");
if (dup2(outfile, STDOUT_FILENO) == -1)
perror_exit("dup2 error");
execute(av[3], envp);
}
}
'42 SEOUL > pipex' 카테고리의 다른 글
42 서울 pipex 정리 2 (access, dup2, execve, perror, strerror) (2) | 2022.09.09 |
---|---|
42 서울 pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid) (0) | 2022.09.06 |