pipex

김지수·2023년 9월 2일
0

42

목록 보기
4/4

과제 해석

다음과 같이 프로그램이 실행된다.

 ./pipex file1 cmd1 cmd2 file2

반드시 4개의 인자를 지닐텐데
file1과 file2는 파일명이며, cmd1과 cmd2는 쉘 명령어 이다.
위의 실행 예시의 결과는 다음과 같아야 한다.

<file1 cmd1 | cmd2 > file2

예를 들어

$> ./pipex infile "ls -l" "wc -l" outfile
$> ./pipex infile "grep a1" "wc -w" outfile

< infile ls -l | wc -l > outfile
< infile grep a1 | wc -w > outfile

와 같다.

과제 풀이 내용

프로세스

프로세스란 프로그램이 실행되는 상태를 말한다. 프로그램이 실행된다는 것은 RAM에 프로그램 코드가 적재돼 동작한다는 것이다.
프로세스는 운영체제로부터 '스레드'라는 시스템 자원을 할당 받는 단위를 사용하며 메모리, 주소 공간을 할당 받는다. 이때 스레드는 할당 받은 자원들을 스레드끼리 공유하며 실행된다.

IPC(Inter Process Communication)

파이프는 두 프로세스가 통신할 수 있게 하는 정보 전달자다. 즉, 정보 생산자와 소비자 프로세스 간 통신을 의미한다. 이러한 IPC 방법에는 공유 메모리, 소켓, 파이프 등있다.

함수

access

int access(const char *path, int mode);

파일의 권한을 체크하는 함수

path : 파일 명

mode : mask 값(비트연산을 이용해서 여러 개 확인 가능)

R_OK : 파일 존재 여부, 읽기 권한 여부
W_OK : 파일 존재 여부, 쓰기 권한 여부
X_OK : 파일 존재 여부, 실행/검색 권한 여부
F_OK : 파일 존재 여부
성공시 0, 실패 시 -1

fork

pid_t fork(void);

자식 프로세스를 생성하는 함수

성공시 pid, 실패시 -1

자식 프로세스는 부모 프로세스의 메모리 상태를 그대로 복사해서 생성하기 때문에 부모 프로세스의 반환 값은 자식 프로세스의 pid지만 자식프로세스에서의 반환값은 0이다.(즉. -1이냐 0이냐 다른 숫자냐를 가지고 분기 가능)

waitpid

pid_t waitpid(pid_t pid, int *status, int options);

특정 pid를 지닌 자식 프로세스의 상태를 획득하고 필요시 메모리를 회수한다

pid : 프로세스의 id

pid = -1 : 임의의 자식 프로세스를 기다림
pid > 0 : 프로세스의 아이디가 pid와 같은 자식 프로세스를 기다림
pid == 0 : waitpid를 호출한 프로세스 그룹 pid와 같은 그룹 id를 지닌 프로세스를 기다림
pid < 0 : 프로세스 그룹 id가 pid 의 절댓값과 같은 자식 프로세스를 기다림
ststus : 프로세스 종료시의 상태를 보관할 변수의 주소

options

WNOHANG : 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수할 수 없는 상황에서 호출자는 차단되지 않고 반환 값으로 0을 받음

dup2

int dup2(int fd, int fd2);

파일 디스크립터를 복제하는 함수. fd를 복제해서 fd2 로 지정한다. fd2가 사용 중이라면 기존 fd2를 닫은 후 복제를 한다.

fd : 복제할 원본 파일 디스크립터

fd2 : 복제한 fd를 담을 fd

반환값은 성공 시 새로운 fd(fd2), 실패 시 -1

pipe

int pipe(int fd[2]);

프로세스 간 통신을 위해 fd 쌍을 생성하는 함수

fd[2] : 파일 디스크립터 배열, fd[0]은 파이프의 출구로 데이터를 입력받는 fd 가 담기고 fd[1]에는 파이프의 입구로 데이터를 출력할 수 있는 fd가 담긴다.

반환값은 성공 시 0, 실패 시 -1

execve

int execve(const char *file, char * const *argv, char * const *envp);

파일을 실행하는 함수

exec 계열 함수들은 기본적으로 파일의 경로를 첫번째 인자로 받아와서 실행하는 함수이다.

v 는 vector, e는 environment의 매개변수를 의미한다.

file : 디렉터리 포함 전체 파일 이름

argv : 인수 목록

envp : 환경설정 목록

실패 시 -1 성공 시에는 return을 받을 수 없음

perror

void perror(const char *s);

시스템 에러 메세지 출력 함수

s : 출력할 문구

s를 표준 에러로 출력하게 되는데, s 뒤에 에러와 errno를 함께 출력한다

구현

계획

교수님께 어떻게든 끝낸다고 말씀드렸기 때문에 3일안에 무조건!!

소스 코드

pipex.c

#include "pipex.h"

void	process(char **argv, char **envp, int *fd, int mode)
{
	int	file;

	if (mode == 1)
	{
		file = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0777);
		if (file == -1)
			error();
		dup2(fd[0], STDIN_FILENO);
		dup2(file, STDOUT_FILENO);
		close(fd[1]);
		execute(argv[3], envp);
	}
	else
	{
		file = open(argv[1], O_RDONLY, 0777);
		if (file == -1)
			error();
		dup2(file, STDIN_FILENO);
		dup2(fd[1], STDOUT_FILENO);
		close(fd[0]);
		execute(argv[2], envp);
	}
	close(file);
}

int	main(int argc, char **argv, char **envp)
{
	int		fd[2];
	pid_t	pid;

	if (argc != 5)
		ft_putstr_fd("Bad format", 1);
	else
	{
		if (pipe(fd) == -1)
			error();
		pid = fork();
		if (pid == -1)
			error();
		else if (pid == 0)
			process(argv, envp, fd, 0);
		waitpid(pid, NULL, WNOHANG);
		process(argv, envp, fd, 1);
	}
	return (0);
}
utils.c

#include "pipex.h"

char	*find_path(char *cmd, char **envp)
{
	char	**paths;
	char	*path;
	char	*part_path;
	int		i;

	i = 0;
	while (ft_strnstr(envp[i], "PATH", 4) == 0)
		i++;
	paths = ft_split(envp[i] + 5, ':');
	i = -1;
	while (paths[++i])
	{
		part_path = ft_strjoin(paths[i], "/");
		path = ft_strjoin(part_path, cmd);
		free(part_path);
		if (access(path, F_OK) == 0)
			return (path);
		free(path);
	}
	i = -1;
	while (paths[++i])
		free(paths[i]);
	free(paths);
	return (0);
}

void	error(void)
{
	perror("error");
	exit(EXIT_FAILURE);
}

void	execute(char *argv, char **envp)
{
	char	**cmd;
	char	*path;
	int		i;
	int		result;

	cmd = ft_split(argv, ' ');
	path = find_path(cmd[0], envp);
	if (path)
	{
		result = execve(path, cmd, envp);
		free(path);
	}
	i = -1;
	while (cmd[++i])
		free(cmd[i]);
	free(cmd);
	if ((!path) || result == -1)
		error();
}

삽질 모음

pipe 함수가 필요한 이유

여기서 문제가 생긴다.
이게 바로 pipe함수가 필요한 이유다. 기본적으로 모두 inputfile로 따지기 때문에 outputfile로 바꾸는 것이 허용되지 않는다.

fork 함수가 필요한 이유

fork가 없다면 make는 되지만 file2.txt가 생성되지 않는다.
왜지? c언어는 절차지향으로 한줄 씩 실행되니 당연히 되야하는 것 아닌가?
왜냐!! 어찌보면 당연한 것인데 첫 process 함수에서 종료되기 때문이다.(실제로 첫번째와 두번째 process 사이에 write 함수를 넣으면 실행이 되지 않는다.)

첫번째 함수 중에서도

위의 dup2에서 끝난다.

/dev/urandom

정말 이 부분이 너무 짜증 났는데.

cat /dev/urandom

을 실행하면 무언가 계속해서 나온다. 즉 이 때문에 자식 프로세스가 끝나지 않아 다음 단계로 넘어가지 않는다. 이를 해겨하기 위해

waitpid(pid, NULL, WNOHANG);

로 3번째 인자를 바꿨다.

추가 궁금증

파일 디스크립터가 정상적으로 모두 종료?

lsof | grep 'PIPE' | wc -l

해당 명령어를 통해 파이프의 남은 수가 증가하는지 찾아볼 수 있다.

profile
노는게 제일 좋아

0개의 댓글