0.도입
pipex 과제는 쉘 프롬프트에서 명령어 사이를 연결에 해주는 | 파이프를 직접 구현해 보는 과제이다. 예를 들어
ls -al | wc -l
위와 같은 명령어가 있다. ls -al 명령어는 현재 위치의 파일, 디렉토리를 표준출력에 출력해주는 명령어이다. 하지만 파이프가 있다면 표준출력으로 보내지 않고 wc -l 의 입력으로 그 값을 넘겨주게 된다.
그래서 wc -l 명령어는 그 값을 읽고 줄 수를 세서 표준 출력에 출력하게 된다. 이게 파이프의 기능이다.
우리는 이번 과제에서 파이프 기능을 직접 만들어보는 시간을 가질 것이고 그 포맷은 이러하다.
<입력 파일> <명령어> | <명령어> <출력 파일>
< infile cat | wc -l > outfile
./pipex infile 'cat' | 'wc -l' outfile
pipex 과제는 프로세스를 다뤄볼 수 있는 과제이다. 프로세스란 실행 중인 프로그램을 말한다.
프로세스가 무엇인지 이 블로그에 들어가서 한번 쭉 읽어보길 바란다. ( 링크 )
그럼 우리가 pipex 과제를 완료한다면 무엇을 얻을 수 있을까?
첫째, 프로세스의 동작과정을 이해할 수 있다.
둘째, 프로세스를 만들고 지우는 과정에서 프로세스를 관리하는 방법을 알게된다.
1.함수 정리
일단 pipex과제는 함수를 제대로 아는 것이 중요하다. 아래 정리해 놓은 함수를 잘 읽어보길 바란다. 근데 정리 못한 개념이 너무 많아서 직접 하나씩 사용해봄으로써 기능을 잘 익히길 바란다.
access
- 헤더 : unistd.h
- 매개변수 :
- const char pathname : 체크하고자 할 디렉토리 또는 파일명
- int mode : 마스크 값을 통해 파일 존재 여부 및 권한 여부 확인
- R_OK : 파일 존재여부, 읽기 권한 여부
- W_OK: 파일 존재여부, 쓰기 권한 여부
- X_OK : 파일 존재여부, 실행 권한여부
- F_OK : 파일 존재여부
- 반환값 : 성공시 0, 실패시 -1
access 함수의 pathname 인자는 명령어의 루트까지 표시를 해줘야한다. 예를 들어 우리가 아무 생각없이 사용했던 ls 명령어는 사실 /bin 파일에 들어있다. 쉘에서 쳤을때는 알아서 찾아서 실행을 시키지만 access 함수를 사용할 때는 envp(환경변수 목록)에서 PATH에서 명령어의 루트를 찾아서 붙여서 pathname에 넣어줘야 한다.
dup2
- 헤더 : unistd.h
- 매개변수 :
- int fd : fd로 전달받은 파일디스크립트를 복제하여 반환
- int fd2 : 새 디스크립트의 값을 fd2로 지정하고, 만약 fd2가 열려있다면, 닫은 후 복제가 됨.
- 반환값 : 성공 시 새 파일 디스크립트, 실패 시 -1
- fd2가 열려있다는 의미는 어떤 파일을 가리키고 있다는 뜻과 같다.
infile = open("file1", O_RDWR);
dup2(infile, STDIN_FILENO);
✅ 프로세서의 파일 디스크립터가 가르키는 파일 테이블을 바꿔줌으로 최종적으로는 가르키는 파일(inode)를 바꿔줄 수 있는 것이다. 이후 pipe함수와 같이 사용하면서 프로세서 끼리의 통신을 도와주는 역할을 한다.
execve
int execve(const char *filename, char *const argv[], char *const envp[]);
- 헤더 : unistd.h
- 매개변수 :
- const char *path : 전체 파일 명
- char *const arg : 인수 목록
- char *const envp[] : 환경설정목록
- 반환값 : 실패 시 -1
- 설명:첫번째는 명령어를 넣는다. 다만 앞에 /bin/@@형식의 path가 있어야 하는 것 같다.두번째는 인자를 넣는다. main에서 넘어온 인수 배열에서 앞(실행파일)에만 빼고 넣으면 될거 같다. 끝에 NULL 넣기. 세번째는 main에서 받아온 envp 그대
- const char*, char const *, char *const 의 차이점
- const char*, char const * : 가르키는 곳의 데이터를 바꾸려고 하면 에러가 발생한다.
- char *const : 가르키는 곳을 바꾸려면 에러가 발생한다.
#include <unistd.h>
#include <stdio.h>
int main(int argc, char * const *argv, char **envp)
{
char *arr[] = {"ls", "-al", NULL};
int returnv = execve("/bin/ls", arr, envp);
printf("value = %d\\n", returnv);
}
fork
- 헤더 : unistd.h
- 매개변수 :
- int wstate : 종료상태를 알 수 있고, NULL 전달 가능
- 반환값 : 부모 프로세스는 자식프로세스의 PID, 자식 프로세스에겐 0 반환, 실패시 -1 반환
- 설명:해당 c언어의 프로세스가 두개 생성된다 볼 수 있음.
프로세스의 메모리 구조
Code영역: 프로그램을 실행시키는 실행 파일 내의 명령어들이 올라갑니다.
(쉽게 말하면 소스코드가 올라간다고 생각하면 됨)
Data영역 : 전역변수, static 변수의 할당.
Heap영역 : 동적할당을 위한 메모리 영역.
- C언어 : malloc & free // C++ : new & delete // JAVA : new & (java에서 메모리 해제는..가비지컬렉터가 알아서해주는거로 앎..아니면 객체 null)
Stack 영역 : 지역변수, 함수 호출시 전달되는 인자(파라미터)를 위한 메모리 영역.
pipe
- 헤더 : unistd.h
- int pipe(int pipefd[2]);
인자
- pipefd: 파이프의 파일 디스크립터를 저장할 배열. 크기가 2인 배열이어야 한다. pipefd[0]는 파이프의 읽기(Read) 파일 디스크립터를 가리키고, pipefd[1]는 파이프의 쓰기(Write) 파일 디스크립터를 가리킨다.
함수 기능 설명:
- pipe 함수는 파이프를 생성하고, 생성된 파일 디스크립터를 pipefd 배열에 저장한다.
- pipefd[0]은 파이프의 읽기 채널을 나타내며, pipefd[1]은 파이프의 쓰기 채널을 나타낸다.
- 한 프로세스에서 파이프의 쓰기 채널로 데이터를 전송하면, 다른 프로세스에서 파이프의 읽기 채널로 해당 데이터를 수신할 수 있다.
- 파이프는 단방향이므로, 양방향 통신을 위해서는 두 개의 파이프를 사용해야 한다.
int pipe(int fd[2]);
fd[0] -> pipe_out
fd[1] -> pipe_in
프로세스 간 통신을 위해 fd 쌍을 생성하는 함수
fd[2] : 파일 디스크립터 배열, fd[0]은 파이프의 출구로 데이터를 입력받는 fd 가 담기고 fd[1]에는 파이프의 입구로 데이터를 출력할 수 있는 fd가 담긴다.
반환값은 성공 시 0, 실패 시 -1
- fd[0] : 다른 프로세서의 입력 내용 받는 곳. → pipeout (파이프 안에 기록된 것을 가져온다.)
- fd[1] : 자식 프로세서가 첫번째 명령어를 실행 시키고 나온 값을 입력해주는 곳. →pipein (파이프 안에 기록을 한다.)
int main(int ac, char **av)
{
pid_t pid;
int fd[2];
char *str;
if (pipe(fd) == -1)
throw_error("Pipe Error", 1);
pid = fork();
if (pid == -1)
throw_error("Fork Error!", 1);
if (pid == 0)
{
close(data.fd[0]);
str = ft_strdup("hello world!");
write(data.fd[1], str, 13);
free(str);
}
else
{
close(data.fd[1]);
str = malloc(50);
read(data.fd[0], str, 13);
write(1, str, 13);
close
}
return (0);
}
사용하지 않는 fd를 닫는 이유
pipe는 기본적으로 단방향 통신을 지원한다. 즉 한쪽에서는 쓰기만, 한쪽에서는 읽기만 가능하다는 뜻이다.
만약 사용하지 않는 fd를 닫지 않으면 어떻게 될까?
예를 들어 부모프로세스에서 A파이프에 쓰고 자식 프로세스에서 A파이프를 읽어서 cat 명령을 수행하는 프로그램이 있다고 해보자.
이때 부모프로세스에서 읽기용 fd 를 닫지 않은채로 자식프로세스에서 cat프로그램을 실행을 하면 부모프로세스에서 양쪽 fd에 여전히 접근 할 수 있기때문에 cat명령어가 input fd로 read를 할때 eof가 나타나지 않는다. 따라서 cat 프로세스는 죽지않고 계속 살아있게 된다.
이러한 이유 때문에 부모 프로세스와 자식 프로세스 에서는 자기쪽에서 안쓰는 fd를 닫아주어야한다.
unlink
- 헤더: <unistd.h>
- int unlink(const char *pathname);
- char *pathname : 삭제할 파일이름
설명
- 파일을 삭제하는 system call함수이다.
- unlink() 는 hard link의 이름을 삭제하고 hard link가 참조하는 count를 1 감소시킨다.
- hard link의 참조 count가 0이 되면, 실제 파일의 내용이 저장되어 있는 disk space를 free하여 OS가 다른 파일을 위해 사용 할 수 있도록 한다.
- 따라서 hard link를 생성하지 하지 않은 파일은 바로 disk space를 해제해서 파일을 삭제한다.
반환값
- 0 : 성공
- -1 : 에러
waitpid
- 헤더 : <sys/wait.h>
- pid_t waitpid(pid_t pid, int *statloc , int options);
반환값
- 성공 : 프로세스 ID 반환
- 오류 : -1
waitpid 함수 사용하기(wait함수와 비교)
waitpid 함수는 wait 함수처럼 자식 프로세스를 기다릴때 사용하는 함수입니다. 즉, 자식 프로세스의 종료상태를 회수할 때 사용합니다. 하지만 waitpid 함수는 자식 프로세스가 종료될 때 까지 차단
codetravel.tistory.com
'42seoul' 카테고리의 다른 글
[42Seoul] Web Server 프로젝트 회고 (0) | 2024.01.29 |
---|---|
[42Seoul] ft_transcendence 퐁 게임 프로젝트 회고 (1) | 2023.12.17 |
[42Seoul] Net_practice 문제풀이 (0) | 2023.07.13 |
[42Seoul] Born2beroot A-Z (0) | 2023.07.01 |
[42Seoul] ft_printf (0) | 2023.07.01 |