Operating System/UNIX

[UNIX] 입출력 및 리다이렉션 (I/O Redirection)

  • -

유닉스 계열 운영체제에서 파일은 연속된 n개의 바이트다.

네트워크, 디스크, 터미널 같은 모든 I/O 디바이스들은 파일로 모델링되며,

모든 입력과 출력은 해당 파일을 읽거나 쓰는 형식으로 수행된다.

 

파일 열기

#include <sys/types.h>	// mode_t type
#include <sys/stat.h>	// mode masks
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

 

응용 프로세스는 해당 파일을 여는 것을 커널에 요청한다.

성공시 커널은 파일 디스크립터를 반환하고, 실패시 -1을 반환한다.

이후 응용 프로세스는 해당 파일 디스크립터(이하 fd) 를 이용해 해당 파일에 접근할 수 있다.

 

파일 디스크립터와 표준 스트림에 대한 더 자세한 정보는 아래의 링크에서 확인 가능하다.

 

[UNIX] 파일 디스크립터 (File Descriptor)

개념 유닉스 및 유닉스 계열 운영 체제에서 파일 또는 파이프나 네트워크 소켓과 같은 기타 입출력 리소스에 대한 프로세스 고유 식별자이자 핸들이다. 일반적으로 음수가 아닌 정수 값이며, 음

sikpang.tistory.com

 

int fd = open("test.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IXUSR);

 

flags 인자에는 프로세스가 파일에 접근할 때 부여받는 권한과 추가적인 명령을 명시해준다.

 

Flag Description
O_RDONLY 읽기 전용
O_WRONLY 쓰기 전용
O_RDWR 읽기 + 쓰기 전용
O_CREAT 파일이 존재하지 않으면 새 파일을 생성
O_TRUNC 파일이 이미 존재하면 파일의 내용 비우기
O_APPEND 파일의 위치(offset)를 파일의 맨 마지막으로 변경

 

| 연산으로 비트를 합침으로써 같이 사용 가능하다.

 

mode는 파일을 새로 만들었을 때 해당 파일의 접근 권한 비트를 명시해준다.

 

Mask Description
S_IRUSR owner의 읽기 권한
S_IWUSR owner의 쓰기 권한
S_IXUSR owner의 실행 권한
S_IRGRP group member의 읽기 권한
S_IWGRP group member의 쓰기 권한
S_IXGRP group member의 실행  권한
S_IROTH other의 읽기 권한
S_IWOTH other의 쓰기 권한
S_IXOTH other의 실행  권한

 

마찬가지로 | 연산으로 같이 사용이 가능하다.

 

 

읽기

#include <unistd.h>

ssize_t read(int fd, void* buf, size_t n);

 

read() fd에 기록된 파일의 offset에서 최대 n byte를 buf 로 복사한다.

이후 파일의 offset을 최대 n만큼 이동시킨다.

성공시 읽기에 성공한 byte 수를 반환하고, 실패시 -1을 반환한다.

 

ssize_t는 부호 있는 size_t 이다. 오류를 감지하여 음수로 내보내기 위해 사용된다.

 

fd에 표준 스트림을 넣으면 표준 입력을 받게 되며,

Enter로 입력 시 개행 문자가 마지막에 삽입된다. (방지하려면 EOF (Ctrl-Z) 입력)

 

read() 호출 시 호출한 프로세스는 입력을 다 받을 때까지 block 되어있는다.

 

예제

#include <unistd.h>
#include <stdio.h>
#define BUFFER_SIZE 10

int main()
{
	char buf[BUFFER_SIZE+1] = {};

	ssize_t res = read(STDIN_FILENO, buf, BUFFER_SIZE);

	if (res == -1)
		printf("\nError\n");
	else
		printf("\nres : %s\n", buf);
}

 

buf NULL 문자까지 고려하여 n보다 1 크게 할당하고, 마지막에 NULL 문자를 배치해주어야 버퍼 오버플로우를 방지할 수 있다.

 

 

 

쓰기

#include <unistd.h>

ssize_t write(int fd, const void* buf, size_t n);

 

write() buf에서 최대 n byte를 fd에 기록된 파일의 offset으로 복사한다.

이후 파일의 offset을 최대 n만큼 이동시킨다.

성공시 쓰기에 성공한 byte 수를 반환하고, 실패시 -1을 반환한다.

 

fd에 표준 스트림을 넣으면 표준 출력을 하게 된다.

 

예제

#include <unistd.h>
#include <stdio.h>
#define BUFFER_SIZE 10

int main()
{
	char buf[BUFFER_SIZE+1] = {'H', 'e', 'l', 'l', 'o', '\n', '\0'};

	ssize_t res = write(STDOUT_FILENO, buf, BUFFER_SIZE);

	if (res == -1)
		write(STDERR_FILENO, "Error\n", 6);
}

 

write() 호출 시 buf 의 크기보다 n이 더 크면 버퍼 오버플로우가 발생하므로 주의하자.

 

 

 

파일 닫기

#include <unistd.h>

int close(int fd);

 

프로세스는 연 파일을 close() 를 통해 닫는다. 실패시 -1을 반환한다.

 

해당 fd 숫자는 더이상 사용하지 않으므로 재사용이 가능하다.

 

해당 fd가 가리키고 있는 파일의 본체가 더이상 다른 프로세스에서도 사용되지 않으면,

파일 open에 관련된 자원 할당을 해제한다.

 

 

입출력 리다이렉션

fd가 가리키는 파일을 다른 파일로 리다이렉션 시킬 수 있다.

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

 

두 함수 모두 성공시 할당된 fd를 반환하고, 실패시 -1을 반환한다.

 

dup2

oldfd에 바꾸려는 fd를 넣고, 바꿔질 fd 번호를 newfd에 넣는다.

 

newfd가 이전에 열려 있었다면 재사용되기 전에 자동으로 닫힌다.

oldfd가 유효한 fd가 아닌 경우 호출이 실패하며, newfd가 닫히지 않는다.
oldfd가 유효한 fd이고 newfd와 같다면, 아무 작업도 수행하지 않고 newfd를 반환한다.

 

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define BUFFER_SIZE 14

int main()
{
	char* buf = "Hello World!\n";
	int file_fd = open("./test", O_WRONLY);

	dup2(file_fd, STDOUT_FILENO);
	write(STDOUT_FILENO, buf, BUFFER_SIZE);
}

 

write()표준 출력에 수행했지만 표준 출력은 리다이렉션되어 test 파일에 출력되었다.

 

 

 

dup

fd를 인자로 넣으면 새 fd가 할당되며 반환된다.

새로운 fd 번호는 사용되지 않은 fd 중 가장 낮은 번호의 fd 번호가 보장된다.

 

두 fd가 참조하는 파일은 동일한 fd를 참조하므로 파일 오프셋과 파일 상태 플래그를 공유한다.

 

위의 dup2 예제에서 표준 스트림을 리다이렉션 시키면, 다시 표준 출력으로 바꾸는 데에 어려움이 있다.

따라서 해당 fd를 미리 복사하여 보존해둘 수 있다.

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define BUFFER_SIZE 14

int main()
{
	char* buf = "Hello World!\n";
	int file_fd = open("./test", O_WRONLY);

	int new_stdout = dup(STDOUT_FILENO);
	dup2(file_fd, STDOUT_FILENO);

	write(new_stdout, buf, BUFFER_SIZE);
}

 

dup2()로 표준 출력을 리다이렉션 시켰지만, 미리 dup()으로 보존해두어 표준 출력이 가능해졌다.

 

 

 

입출력 리다이렉션은 이미 표준 입출력으로 구현되어있는 프로그램들에 대해 프로세스간 통신에 자주 사용된다.

 

 

참고

https://man7.org/linux/man-pages/man2/open.2.html
https://man7.org/linux/man-pages/man2/close.2.html
https://man7.org/linux/man-pages/man2/dup.2.html

 

 

개인 공부용 포스팅인 점을 참고하시고, 잘못된 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.