개요
pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어와 똑같이 동작하도록 구현해야 되는 과제이다.
즉 infile 파일을 읽고 명령어 2개를 실행한 결과를 outfile에 저장해야 된다. 여기서 이 작대기 |가 파이프다.
우리는 이 과제를 통해 프로세스와 프로세스 간 통신을 할 때 사용하는 pipe에 대해 공부해야 한다.
프로세스(process)란?
프로그램은 컴퓨터에서 실행될 때 특정 작업을 수행하는 일련의 명령어들의 집합이다. 이 프로그램의 명령어와
정적 데이터가 메모리에 적재되면 프로세스가 된다. 간단히 말하면 프로세스는 실행 중인 프로그램을 의미한다.
우리가 작업 관리자를 켜면 잔뜩 나오는 것들이 프로세스다.
함수 정리
1. fork
#include <unistd.h>
pid_t fork(void);
fork 함수는 프로세스를 복사해준다. 기존의 프로세스를 부모 프로세스, 복사된 프로세스를 자식 프로세스라고 부른다.
fork 함수는 프로세스의 고유 id인 pid를 반환한다. fork 함수 실행 실패 시 -1을 반환하고 자식 프로세스를 생성하지 않는다.
성공 시에는 부모 프로세스에는 자식 프로세스 피드 값을, 자식 프로세스에는 0이 반환한다.
예시 1)
코드만 보면 if, else문이 다 돌아가는 게 말이 안 되는 것 같지만 부모, 자식 프로세스에서 각각 main문이 돌아가기 때문에 2번 출력된다.
int main(void)
{
pid_t pid = fork();
if (pid == 0)
{
printf("Hello from Child\n");
printf("%d %d\n", pid, getpid());
}
else
{
printf("Hello from Parent\n");
printf("%d %d\n", pid, getpid());
}
}
/*
실행 결과
Hello from Parent
48280 48279
Hello from Child
0 48280
*/
예시 2) 자식과 부모 프로세스는 데이터와 상태가 다르기에 변수의 변화가 영향을 끼치지 않는다.
int main(void)
{
int x = 1;
if (fork() == 0)
printf("Child has x = %d\n", ++x);
else
printf("Parent has x = %d\n", --x);
}
/*
실행 결과
Parent has x = 0
Child has x = 2
*/
2. pipe
#include <unistd.h>
int pipe(int fd[2]);
pipe 함수는 파이프를 생성하고 2개의 디스크립터(읽기 전용, 쓰기 전용)를 생성해준다. 실패했을 경우에는 -1을 반환한다.
생성한 2개의 디스크립터와 파이프를 사용해 아래와 같은 방식으로 부모 프로세스를 자식 프로세스를 통신시킬 수 있다.
open 함수를 사용할 때와 마찬가지로 디스크립터를 사용하기에 pipe 함수 역시 사용하지 않는 디스크립터는 close 함수로 닫아줘야 한다.
부모에서 자식으로 일반통행 방식이기 때문에 부모 프로세스는 읽는 쪽을, 자식 프로세스는 쓰는 쪽을 닫아주면 된다.
예시 1) pipe 생성
int main(void)
{
int fd[2];
pipe(fd);
printf("fd[0]: %d fd[1]: %d\n", fd[0], fd[1]);
}
/*
실행 결과
fd[0]: 3 fd[1]: 4
*/
예시 2) pipe를 사용한 부모와 자식 프로세스 간 통신
#define MAX_BUF 1024
int main()
{
int fd[2];
pid_t pid;
char buf[MAX_BUF];
if (pipe(fd) < 0)
printf("pipe error\n");
pid = fork();
if (pid < 0)
{
printf("fork error\n");
exit(1);
}
if (pid == 0)
{
close(fd[1]);
read(fd[0], buf, MAX_BUF);
printf("Child got message : %s\n", buf);
}
else
{
close(fd[0]);
ft_strcpy(buf, "Massage from Parent");
write(fd[1], buf, ft_strlen(buf));
}
}
/*
실행 결과
Child got message : Massage from Parent
*/
3. wait
#include <sys/wait.h>
pid_t wait(int *statloc);
wait 함수는 자식 프로세스가 종료되었을 때 자식 프로세스의 pid값을 반환하고, 오류가 났을 때는 -1을 반환합니다.
wait 함수를 사용하면 부모 프로세스를 자식 프로세스가 종료될 때까지 기다리게 할 수 있습니다.
만약 자식 프로세스가 종료되었는데 부모 프로세스가 계속 돌아가고 있다면 자식 프로세스는 좀비 프로세스 상태가 되는데
wait 함수를 사용해 이를 방지할 수도 있다.
예시 1) 부모 프로세스가 자식 프로세스가 끝난 후 다시 돌아가는 것을 확인할 수 있다.
int main()
{
int stat, stat_res;
if ((fork()) == 0)
printf("Child Hi\n");
else
{
stat_res = wait(&stat);
printf("Parent Hi\n");
printf("wait 인자값: %d\nwait 반환값: %d\n", stat, stat_res);
wait(NULL);
printf("Child Bye\n");
}
printf("Process Bye\n");
return 0;
}
/*
실행 결과
Child Hi
Process Bye
Parent Hi
wait 인자값: 0
wait 반환값: 57724
Child Bye
Process Bye
*/
아래 WIF 매크로를 사용해서 stat의 정보를 확인할 수 있다.
WIFEXITED(status): 자식 프로세스가 정상적으로 종료했으면 참을 반환한다.
WEXITSTATUS(status): 자식 프로세스의 종료 상태를 반환한다. 이 매크로는 WIFEXITED가 참을 반환했을 때만 써야 한다.
WIFSIGNALED(status): 자식 프로세스가 시그널로 종료되었으면 참을 반환한다.
WIFSTOPPED(status): 자식 프로세스가 정지된 상태라면 참을 반환한다
예시 2) 자식 프로세스가 정상적으로 종료됐으므로 WIFEXITED의 조건문이 실행된다.
int main()
{
int pid, status;
printf("Parent: %d\n", getpid());
pid = fork();
if (pid == 0)
{
printf("Child %d\n", getpid());
sleep(2);
return (0);
}
waitpid(pid, &status, 0);
if (WIFSIGNALED(status))
printf("Error\n");
else if (WEXITSTATUS(status))
printf("Exited Normally\n");
else if (WIFEXITED(status))
printf("Parent: %d\n", getpid());
return 0;
}
4. waitpid
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
waitpid 함수는 wait 함수와 거의 똑같은데, 옵션을 추가해 동작 방식을 변경할 수 있다.
아래는 옵션들(3번째 인자 종류)이다. 인자가 0일 경우에는 wait 함수와 동일하게 작동한다.
WNOHANG: 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수할 수 없는 상황일 때 0을 반환
WUNTRACED: 중단된 자식 프로세스의 상태를 반환
WCONTINUED: 중단되었다가 재개된 자식 프로세스의 상태를 반환
에시 1) WNOHANG 사용
#include <time.h>
int main()
{
pid_t pid;
int status;
time_t t;
pid = fork();
if (pid < 0)
perror("fork error");
else if (pid == 0)
{
sleep(3);
exit(1);
}
else do
{
pid = waitpid(pid, &status, WNOHANG);
if (pid == -1)
perror("wait error");
else if (pid == 0)
{
time(&t);
printf("child is still running at %s", ctime(&t));
sleep(1);
}
else
{
if (WIFEXITED(status))
printf("child exited with status of %d\n", WEXITSTATUS(status));
else
puts("child did not exit successfully");
}
} while (pid == 0);
}
/*
child is still running at Wed Sep 7 12:27:32 2022
child is still running at Wed Sep 7 12:27:33 2022
child is still running at Wed Sep 7 12:27:34 2022
child exited with status of 1
*/
사실 이 과제에서 WNOHANG을 사용했다는 것은 좋은 징조가 아니다. dup2 후 파이프를 전부 다 닫아주면
WNOHANG을 사용하지 않아도 잘 넘어가진다. 파이프를 잘 안 닫았을 경우 yes | head -1이나
/dev/urandom | head -1 같은 경우에 head 한 결과가 출력도 안되고 프로그램이 끝나지도 않는다.
'42 SEOUL > pipex' 카테고리의 다른 글
42 서울 pipex Mandatory (0) | 2022.12.19 |
---|---|
42 서울 pipex 정리 2 (access, dup2, execve, perror, strerror) (2) | 2022.09.09 |