전공/시스템 프로그래밍(운영체제)

3. System Call: read, write

문정훈 2021. 10. 16. 14:41

1-1. read 시스템 콜

read라는 시스템 call의 함수 역할은 파일을 읽는 역할을 한다.

따라서 read전에 open을 먼저해줘야하고 open으로 리턴 받은 파일의 파일 디스크립트 값을 read 의 첫 매개변수로 준다. 그러면 read 파일 디스크립터는 파일의 맨 처음을 가리켜 읽을 수 있게 된다.

buf는 파일로부터 데이터를 읽어서 저장할 공간을 buf에 지정한다. 즉 문자열의 주소의 주소 값을 전달한다. bufvoid 형태는 무슨 형태(문자열인지, 숫자인지, 2진수인지 모름) 의 데이터 형태인진 모르겠으나 offset부터 len 길 만큼에 파일에서 읽어 저장하는 인자이다.

 

read함수는 파일 디스크립터가 처음 호출될때는 처음을 가리키고 len만큼 read한 만큼 파일 디스크립터가 가리키는 파일의 offset 값이 이동한다.

 

read는 읽은 값을 buf 변수에 저장하고 read 함수의 리턴 값은 실제 읽은 수 이다.

만약 리턴 값이 1이라면 읽기에 실패했다는 것이다.

 

또한 파일 offset 임의로 이동시킬 수 있다.

 

만약 read의 반환 값이 len보단 작고 0보단 크다는 것은 실제 파일 offset이 가리키는 곳 부 터 전체 길이가 len보다 작다는 것을 의미한다.

 

만약 read 값이 0이라면 파일의 끝에 도달했다는 EOF의 의미이다.

또한 read 함수는 non-blocking으로 동작한다.

또한 한번 읽기 시작하면 다 읽을 때까지 중지못한다.

 

 

1-2. read 시스템 콜 사용 예시

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
	char *str=null;
    
    int fd;
    fd = open("file.txt", O_RDONLY);
    
    if(fd == -1) {
        fprintf(stderr, "Failed to open file.\n");
        exit(0);
    }
    else printf("Successfully opened file.\n");
    
    int size;
    size = read(fd, str, 5);
    if(size == -1) {
            fprintf(stderr, "Failed to read file.\n");
            exit(0);
    }
    else printf("Successed reading file.\n");
}

파일을 read하기 위해서는 open 선행 작업이 이루어져야한다. read 시스템 콜은 첫 매개변수로 open으로 연 파일의 파일 디스크립터를 지정한다. 그리고 세 번째 인자만큼 파일에서 값을 읽어온다. 위 예제는 5byte를 읽는 것이고 read 시스템 콜이 리턴하는 값은 실제 읽은 값을 리턴하는 것이다. 

만약  -1을 반환하면 에러가 발생한 것인데 stderr를 출력하면서 exit한다.
에러의 종류에 대해 아래에서 정리한다.

 

 

1-3. read 시스템 콜 에러 정리

read 시스템 콜의 리턴 값이 -1이면 에러가 발생한 것이다. 시스템 콜이 호출되었다가 문제가 발생하면  errno에 특정 값을 할당하여 그 이유를 알린다.

errno 에 저장된 error number를 확인하여 어떤 에러가 발생하였는지 알 수 있다. 아래 표로 errno에 저장되는 error number 상수 값을 정리하였다. 

 

<기본적인 errno 값>

EINTR

파일을 다 읽기 전에 interrupt 신호가 와서 에러가 난 경우이다. 
이 경우는 다시 읽어주면 되서 continue;를 호출해주면 된다. 
EAGAIN

O_NONBLOCK 옵션은 파일 디스크립터가 논블록 모드로 열린 상태에서 쓰기 작업이 블록될 경우, write() 시스템 콜은 -1을 반환한다. 이때 반환되는 errno는 EAGAIN으로 설정된다.
이 경우 나중에 쓰기 요청을 다시 해야 한다.
일반 파일에서는 대체로 일어나지 않는 상황이다.

 

<다른 errno 값>

EBADF

경우1) 
파일의 경로가 잘못되어 파일 디스크립터가 없는경우 발생한다.  다른 경우로
파일 디스크립터가 메인 메모리에 있는 file table에 존재하지 않는데 read를 호출하는 경우이다. 

경우2)
파일에 대한 file descriptor를 반환받는 것 까지는 성공하였으나 이후 I/O 함수를 호출하는 과정에서 동시에 접속한 다른 프로세스에 의해서 close되는 경우 메인 메모리에서 file descriptor가 사라져서 발생하는 오류이다. 

정리=>
파일 디스크립터가 메인 메모리(file table)에 존재하지 않아 발생하는 오류가 된다. 
EFAULT

read 호출시 값의 저장을 위해 주어진 포인터가 Caller 함수내에 존재하지 않을 때 발생한다. 
EINVAL

파일 디스크럽터에는 문제가 없으나 해당 파일이 I/O가 불가능한 경우 발생하는 오류이다. 
파일 디스크립터가 가리키는 대사 파일이 삭제되었을 경우에도 발생한다. 

 

2-1 . System call: write

#include <stdio.h>

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

우선 open이라는 연산을 통해 파일 디스크립터를 받았을 것이다. 그 파일 디스크립터를 write의 첫 매개변수로 지정한다. 그리고 두 번째 매개변수가 가리키는 내용을 count 크기만큼 파일에 write하는 것을 의미함.

보통 count의 값은 sizeof(buf) 또는 strlen(buf)로 지정할 수 있음.

 

파일 디스크립터는 파일에 대한 많은 정보를 가지고 있는데 그 중 position값도 있다.

파일을 열게 되면 position은 파일의 제일 처음을 가리키고 있다

 

write함수의 리턴 값은 실제 파일에 몇 byte를 쎃는지 리턴한다.

 

nr = write(fd, buf, strlen(buf));

위 명령은 buf에 들어있는 문자열을 버퍼의 길이만큼 쓴다는 명령이 된다.

opend을 append모드로 열었다면 파일의 eof부터 쓰고, append모드가 아니라면 파일의 내용을 지우고 쓴다.

 

2-2 . System call: write 에러코드

기본적으로 errno 값은 1-3에서 정리한 에러코드를 포함해 write에서 발생될 errno 값에 대해 정리한 표이다. 

EBADF

경우1) 
파일의 경로가 잘못되어 파일 디스크립터가 없는경우 발생한다.  다른 경우로
파일 디스크립터가 메인 메모리에 있는 file table에 존재하지 않는데 read를 호출하는 경우이다. 

경우2)
파일에 대한 file descriptor를 반환받는 것 까지는 성공하였으나 이후 I/O 함수를 호출하는 과정에서 동시에 접속한 다른 프로세스에 의해서 close되는 경우 메인 메모리에서 file descriptor가 사라져서 발생하는 오류이다. 

정리=>
파일 디스크립터가 메인 메모리(file table)에 존재하지 않아 발생하는 오류가 된다. 
EFAULT

read 호출시 값의 저장을 위해 주어진 포인터가 Caller 함수내에 존재하지 않을 때 발생한다. 
EFBIG

쓰기 작업이 프로세스 단위로 걸려 있는 최대 파일 제약이나 내부 구현 제약보다 더 큰 파일을 만든 경우
EINVAL

파일 디스크럽터에는 문제가 없으나 해당 파일이 I/O가 불가능한 경우 발생하는 오류이다. 
파일 디스크립터가 가리키는 대사 파일이 삭제되었을 경우에도 발생한다. 
EIO

저수준의 입출력 에러이다.
ENOSPC

파일 디스크립터가 들어 있는 파일시스템에 공간 부족
EPIPE

파일 디스크립터가 파이프와 소켓에 연결되어 있지만, 반대쪽 읽기 단이 닫혀버린 상황에서 발생하는 에러코드이다.