뇌 마음 반반저장소

[42_GNL] get_next_line 개념 이해하기 (open, read, 버퍼사이즈) 본문

42/get_next_line

[42_GNL] get_next_line 개념 이해하기 (open, read, 버퍼사이즈)

맹진저 2022. 12. 19. 20:00
728x90

나는 처음에 이 프로젝트를 시작하면서 이 get_next_line이라는 개념이 정말 이해가 안 되었다. 일단 \n을 기준으로 줄 바꿈을 해서 내보내는 것은 이해가 되었지만 그 안에서 어떻게 저장이 되고 어떻게 돌아가는지가 이해가... 나는 내가 바보인 줄 알았다. (바보 맞음)

 

Open, Read, Close

일단 사용 가능한 함수 read를 한번 짚고 넘어가 보자.

우리는 파일을 읽으려면 열고 -> 읽고 -> 닫고의 과정을 거친다. 함수도 이렇게 세 가지를 함께 사용한다. 

Open 함수의 헤더는 <fcntl.h>이고 read와 close의 함수는 <unistd.h>에 포함되어있다!

open() 함수

open함수는 파일과 fd사이의 연결을 설정해서 파일을 여는 함수이다. 그렇기 때문에 열린 파일을 품어줄 수 있는 fd를 생성해야만 한다. (fd = open(....)으로 함수가 만들어지는 이유!)

함수 원형을 보면 조금 더 이해가 쉽다!

#include <fcntl.h>
int open(const char *pathname, int flags);

open함수는 int형으로 반환이 되네? 그 이유는 파일 열기에 성공하면 양수의 값이 반환되고 실패하면 음수의 값이 전달되기 때문이다.

첫 번째 매개변수는 파일 이름이 들어가고, 두 번째 매개변수는 파일열기 옵션 [접근권한]이 들어간다. 그러면 두번째 매개변수에 들어가는 파일 옵션들을 살펴보자.

O_RDONLY Open for reading only. 읽기 전용으로 엽니다.
O_WRONLY Open for writing only. 쓰기 전용으로 엽니다.
O_RDWR Open for reading and writing. The result is undefined if this flag is applied to a FIFO.
읽기와 쓰기를 위해 엽니다. 이 플래그가 FIFO에 적용되면 결과는 정의되지 않습니다.
Any combination of the following may be used:
다음을 조합하여 사용할 수 있다. : 
O_APPEND 파일이 추가모드로 열린다. 파일의 위치는 파일의 끝이된다
O_CREAT 만약 pathname 파일이 존재하지 않을경우 파일을 생성한다.
O_EXCL O_CREAT 를 이용해서 파일을 생성하고자 할때, 이미 파일이 존재한다면, 에러를 되돌려주며 파일을 생성하는데 실패한다. 이러한 특성때문에 때때로 잠금:::파일(:12)을 만들기 위해 사용되기도 한다.
O_NOCTTY 열기 대상이 터미널일 경우, open ()은 터미널 장치가 프로세스의 제어 터미널이 되지 않도록 합니다.
O_NONBLOCK 읽을 내용이 없을 때에는 읽을 내용이 있을 때까지 기다리지 않고 바로 복귀한다.
O_SYNC 입출력:::동기화(:12) 모드로 열린다. 모든 write 는 데이타가 물리적인 하드웨어에 기록될때까지 호출 프로세스를 블록시킨다.
O_TRUNC 파일이 존재하며 일반 파일에 파일이 O_RDWR 또는 O_WRONLY로 성공적으로 열리면, 길이가 0으로 잘리고 모드와 소유자는 변경되지 않는다.

출처👇

 

이렇게 글로만 보면 볼수록 헷갈리니 예를 직접 보자.

1. mytext라는 파일을 만들고 (만약에 동명의 파일이 있다면 건너뛰시오) 읽고 쓸 수 있다.

int	fd;

fd = open("./mytext.txt", O_WRONLY | O_CREAT | OEXCL);

2. 읽기만 할 수 있는 파일이 열린다.

int	fd;

fd = open("./text.txt", O_RDONLY);

그러면 이 파일을 연 문자열들은 어디에 갔을까? 사실 open함수는 그 파일을 가리키고 있는 포인터 같은 역할이라고 보면된다. read함수를 통해서 불러와 내가 만든 문자열을 사용해서 불러와야만한다. 혹시 내용을 쓰고싶다면 write함수를 통에 그 안에 적으면 된다는 것!

read 함수

read 함수는 fd에서 buf가 가리키는 곳으로 가서 nbyte길이까지 읽어주는 친구이다. 함수 원형은 아래와 같다.

ssize_t	read(int fildes, void *buf, size_t nbyte);

자.. 잠깐만! ssize_t는 무엇이란 말인가?

size_t는 unsigned int, 부호 없는 정수형을 뜻하고

ssize_t는 signed int를 뜻해서 int 형 반환 값으로 해당 함수의 실패 여부를 알려준다. 

아~ 그러면 read함수도 성공 여부에 따라서 int형 값이 반환되는구나! 0보다 크면 성공 0보다 작으면 실패.

 

성공시 반환 값 : 읽은 바이트 수
파일 끝인 경우 반환 값 : 0
오류시 반환 값 : -1

 

예제를 완성하기 위해서 close함수까지 알아보고 가겠다.

close함수

close함수는 새로 만든 fd 파일을 닫고 연결된 리소스도 해제된다. free 같은 친구! close는 성공하면 0을 반환하고 실패하면 -1이 반환된다.

버퍼 사이즈

위의 함수를 가지고 코드를 만드는데 read의 버퍼 사이즈가 발목을 잡았다. 알고 넘어가 보자.

사실 버퍼 사이즈라고 해서 지레 겁을 먹었는데 생각보다 별게 아니어서 깜짝 놀랐다.. 

컴퓨팅에서 버퍼(buffer, 문화어: 완충 기억기)는 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역이다.

1. 버퍼는 쉬워요 : 개념 설명

버퍼(Buffer)는 임시 기억공간이다. 입력한 데이터가 우리눈으로 보이기까지 버퍼라는 곳에 임시 저장을 거쳐 프로그램에서 사용된다. 

 

요즘엔 많이 쓰지 않지만 옛날엔 버퍼링(buffering) 걸렸다는 말을 자주 썼다. 하지만 이 부정적인(?) 사용과는 다르게 버퍼링은 효율적으로 데이터를 전송하기 위해 한꺼번에 옮겨주는 역할을 한다. 

예시 1. 사과를 하나 따서 집으로 하나 옮기고, 하나 따서 집으로 하나 옮기는 것보다는, 바구니에 한꺼번에 담아서 옮기는 게 효율적이겠죠? 여기서 버퍼는 사과이고 사과 바구니는 버퍼링이다.
예시 2. 넷플릭스를 볼 때 한 버퍼씩 받아서 출력하는 것보다 우리가 영상을 보고 있을 때 다음 1분의 버퍼를 이미 받아놓고 끊기지 않게 계속 이어서 보는 게 좋겠죠? 우리가 유튭이나 넷플릭스를 볼 때 하단 영상 시간 바를 보면 회색으로 조금씩 채워져 있는 게 버퍼링이 열일을 하고 있는 것이다.
예시 3. 이 게임은 컴퓨터 사양이 요만큼 안되면 못합니다~라는 문구는 실제로 버퍼링을 많이 담을 수 있는 사양이 되면, 빠르게 변하는 그래픽을 커다란 버퍼링 바구니에 담아서 왕왕 전송할 수 있어야 한다는 의미이기도 하다.

 

이렇게 버퍼의 개념을 공부하다 보니 갑자기 캐시가 생각났다.

캐시(cache)와 레지스터(resister)도 버퍼(buffer)처럼 데이터를 임시로 저장하지만 다른 개념이다. 

 

캐시(Cache) :  자주 사용되는 데이터나 값을 복사해 놓는 임시 저장소 -> 빠른 처리가 가능하다.

ex) 게임의 배경은 계속 뒤에 깔려있으니 캐시에 저장된다.

 

버퍼(Buffer) : 데이터를 전송하는 동안 일시적으로 보관하는 장소 -> 느리게 처리되지 않게 버퍼링 처리한다.

 

레지스터(Resister) : CPU만 도와주는 데이터 임시저장 공간 -> 데이터, 주소, 값 등을 저장해서 연산을 빠르게 도와준다.

 

2. 버퍼는 쉬워요 : 구조설명

자 다시 버퍼로 돌아와서, read와 write의 함수 구현을 살펴보자.

int	read(int fd, char *buffer, int size);
int	write(int fd, char *buffer, int size);

42에서는 write함수만 원칙적으로 사용하는 게 허용되어있다. 그래서 아마 피신 때부터 write을 많이 썼을 것이다. read도 어렵지 않다.

 

fd = open으로 여는 게 가능한지 아닌지, 양수이면 성공, 음수이면 실패.

buffer = 실제로 쓰일 문자 혹은 문자열.

size = 얼마나 실제로 쓰일지 길이를 제공.

 

파일의 입출력에도 저수준 고수준이 있다고 한다. 더 알아보려면 아래의 블로그를 방문해 보자.

https://12bme.tistory.com/211

 

Exemples

자.. 그러면 드디어 open, read, close를 write과 함께 예를 들어 보자.

1. 파일 새로 쓰기 (open, write, close)

#include <fcntl.h> //open의 헤더파일
#include <unistd.h> // write의 헤더파일
#include <stdio.h>

size_t	ft_strlen(const char *s)
{
	size_t		i;

	i = 0;
	while (s[i])
		i++;
	return (i);
}

void    main()
{
    char        *tmp;
    int         fd;
    int         len;

    tmp = "Hello World\n";
    len = ft_strlen(tmp);
    fd = open("./mytext.txt", O_RDWR | O_CREAT | O_EXCL); //mytext라는 파일을 만들어 tmp를 넣어준다.
    if(fd > 0)
    {
        printf("file opend\n");
        write(fd, tmp, len); //fd 파일에, tmp의 문자열을, len길이만큼 써준다.
    }
    else
        printf("open failed\n");
    close(fd);
}

결과는?

$ ./a.out
file opend
$ cat mytext.txt
Hello World

1. 파일 읽기 (open, read, close)

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

# define BUFFER_SIZE    1024 //실제 GNL구현에서는 사용불가

void    main()
{
    ssize_t     size;
    char        tmp[BUFFER_SIZE];
    int         fd;

    fd = open("./mytext.txt", O_RDONLY); //파일을 읽기전용으로 열고
    read(fd, tmp, BUFFER_SIZE); //버퍼에 mytext의 내용을 읽어준다. (임시저장개념)
    if(fd > 0)
    {
        printf("if it's not negative number, success : %ld\n", read(fd, tmp, BUFFER_SIZE));
        printf("fd : %s", tmp); //tmp에 임시저장된 내용을 실제로 출력해준다.
    }
    else
        printf("open failed\n");
    close(fd);
}

버퍼 사이즈를 1024로 설정한 이유! : https://www.joinc.co.kr/w/Site/system_programing/File/buffer_size_perf

 

결과는?

$ ./a.out
if it's not negative number, success : 0
fd : Hello World

정적변수에 대한 설명은 여기로! 👉https://sudo-me.tistory.com/22

 

도움을 주고 싶으신 내용이나

틀린 내용이 있다면 댓글로 남겨주시고,

도움이 되었다면 공감 한 번씩 눌러주세요👍❤️

728x90

'42 > get_next_line' 카테고리의 다른 글

[42_GNL] 코딩하기  (0) 2022.12.22
[42_GNL] 구조 파헤치기  (0) 2022.12.20
[42_GNL] 시작하며  (0) 2022.12.18
Comments