File I/O - open, read, write, lseek, close, dup, fcntl, ioctl

sesame·2021년 12월 13일
0

교육

목록 보기
1/46

5개 함수
open, read, write, lseek, close

다중 프로세스
dup, fcntl, ioctl

1. File Descriptors

파일 열거나 새로 생성시 커널은 file descriptor을 프로세스에 리턴한다
파일 읽거나 작성할 때 파일 읽거나 생성시 리턴된 file descriptor을 사용해서 식별한다.

<unistd.h>

  • standard input 0: STDIN_FILENO
  • standard output 1: STDOUT_FILENO
  • standard error 2: STDERR_FILENO

2. open

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

int open(const char *pathname, int oflag, .../*, mode_t mode */);

returns ok는 file descriptor, error는 -1
여기서 세번째 변수인 ...은 새 파일 생성시에만 사용
pathname: 열거나 생성할 파일 이름
oflag: 는 다음 상수 중 하나 이상을 함께 사용

  • O_RDONLY(0) : 읽기모드
  • O_WRONLY(1) : 쓰기모드
  • O_RDWR(2) : 읽기/쓰기

다음은 이 상수들에 따라 붙는 옵션

  • O_APPEND: 파일 작성시 맨 마지막에 추가할 때

  • O_CREAT: 존재하지 않는 파일 새로 생성시

  • O_EXCL: 파일이 이미 존재할 때 error, O_CREAT와 함께 사용

  • O_TRUNC: 만약 파일 이미 존재할 때 읽기 전용/쓰기 전용으로 파일 성공적으로 열면 길이를 0으로 자름(파일 내용을 지움)

  • O_NOCTTY: pathname에 해당하는 파일이 터미널 장치인 경우, 해당 장치를 현재 프로세스의 컨트롤링 터미널로 할당하지 않는다.

  • O_NONBLOCK

    blocking/non-blocking mode는 read/write 시에 동작을 다르게 합니다.

    • blocking mode로 open한 파일 디스크립터에 read()를 한다고 했을 때, 읽을 데이터가 있으면 바로 읽어가고, 읽을 데이터가 없는 경우 읽을 데이터가 있을 때까지 blocking 하여 기다립니다.
    • 반면 non-blocking mode로 open한 파일 디스크립터에 read()를 한다고 했을 때, 읽을 데이터가 있으면 바로 읽어가고, 읽을 데이터가 없는 경우에는 바로 -1을 리턴합니다. 이 경우 errno가 EAGAIN으로 설정됩니다.
  • O_SYNC: write() 시스템 호출을 하면 디스크에 물리적으로 저장한 후 반환

Filename and Pathname Truncation
만약 NAME_MAX가 14인데 파일 생성시 filename을 15character보다 많이 작성하면?
14글자 미만으로 자른다
이 반응은 단지 파일 생성시에만 생기는 반응이 아니다
NAME_MAX가 14이고 이미 이름이 14인게 있을 때 pathname을 하는 함수도 같은 요구를 담당해야한다
POSIX.1 의 _POSIX_NO_TRUNC 긴 filename그리고 긴 pathname을 자를지 error을 반환할지 결정한다
FIPS 151-1 은 error return을 필요로 한다
SVR4는 기존 시스템 V filesystem에서는 error을 생성하지 않는다
4.3+BSD는 항상 error를 return한다

_POSIX_NO_TRUNC가 유효할 때, ENAMETOOLONG이 return된다. pathname이 path_max 초과하거나 filename이 name_max를 초과하거나 할 때

3. creat

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

int creat(const char *pathname, mode_t mode);

returns 읽기 전용에서 열 때 ok는 file descriptor, error는 -1
생성된 파일의 사용권한은 mode로 정한다.

open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);

이전 버전에서는 존재하지 않는 파일을 열 수가 없었다.
그러므로 system call을 나눌 때 creat는 새 파일 생성, O_CREAT와 O_TRUNC 옵션이 열 때 공급되면, 분리된 creat 함수가 더이상 필요없어졌다

생성시 부족한 한가지는 파일이 작성으로만 열린다는 것이다
열린 새 버전이 이전에 공급되었다면, 만약 우리가 일시적 파일을 생성할 때 우리가 작성하고, 읽고 싶을 때 creat, close 그리고 다음에 open을 호출해야한다
더 나은 방법은 새로운 열기 함수인

open(pathname, O_RDWR|O_CREAT|O_TRUNC, mode);

이것을 사용하는 것이다

4. close

#include<unistd.h>

int close(int filedes);

파일 닫을 때 또한 프로세스에 파일이 있으면 레코드 잠금 풀어준다.
프로세스 끝날 때, 열린 모든 파일이 자동으로 커널에서 닫힌다.
많은 프로그램이 이사실에 이점을 가지고 명시적으로 파일을 닫지 않는다.

5. lseek

이것은 파일의 시작지점부터 byte크기를 잰 음이 아닌 정수이다.
O_APPEND 옵션이 지정되어있지 않을 때, 파일 열때 default로는 0으로 초기화되어있다.

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int filedes, off_t offset, int whence);

returns OK 성공하면 현재 위치 offset 반환, error -1

filedes: 현재 파일의 디스크립터
offset: 기준에서 이동할 크기
whence: 파일 위치 포인터의 기준

offset의 해석은 whence 변수 값에 관련
whence 변수

  • SEEK_SET: 파일의 처음
  • SEEK_CUR: 파일 내 현위치
  • SEEK_END: 파일의 마지막
    lseek의 성곡적인 호출은 새로운 file offset을 리턴한다
    현재 오프셋을 결정하기 위해 현재 위치에서 0바이트를 찾을 수 있습니다.
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);

file descriptor가 pipe, FIFO이면 lseek는 -1 return하고 EPIPE에 errno를 설정한다

#include <sys/types.h>
#includ "ourhdr.h"

int main (void){
	if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
    	printf("cannot seek\n");
    else
    	printf("seek OK\n");
    exit(0);
}

일반적으로 파일의 현재 offset은 음이 아닌 정수여야 합니다.
그러나 특정 장치에서 음수 offset을 허용할 수 있습니다.
그러나 일반 파일의 경우 offset은 음수가 아니어야 합니다.
음수 offset이 가능하기 때문에 lseek의 반환 값이 -1과 같거나 같지 않은지 비교하고 0보다 작은지 테스트하지 않도록 주의해야 합니다.

  • SVR4의 /dev/kmem 디바이스 80386은 음수 offset을 지원한다
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "ourhdr.h"

char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";

int main(void){
	int fd;
    if((fd=creat("file.hole", FILE_MODE)) < 0)
    	err_sys("create error");
    if(write(fd, buf1, 10) != 10)
    	err_sys("buf1 write error");
       /*offset now = 10*/
    if(lseek(fd, 40, SEEK_SET) == -1)
    	err_sys("buf1 write error");
        /*offset now = 40*/
    if(write(fd, buf2, 10) != 10)
    	err_sys("buf2 write error");
        /*offset now = 50*/
    exit(0);
}

6. read

#include<unistd.h>

//filedes가 나타내는 파일에서 nbytes 만큼의 데이터를 읽어 읽어온 데이터를 buff에 저장
ssize_t read(int filedes, void *buff, size_t nbytes);

성공: 읽은 바이트 수 반환
파일 끝: 0
에러: -1

예외: 요청 수 보다 적게 바이트 수 읽어오는 경우

  • 100바이트 읽으려고 했을 때, 30바이트밖에 없으면 30 리턴
  • 터미널 디바이스 읽을 때,
  • 터미널 디바이스 한줄씩 읽음
  • 네트워크에서 네트워크의 버퍼링때문에 요청수보다 적을 수 있다
  • 기록 지향 장치, 마그네틱 테이프와 같은, 한개의 레코드 한번에

읽기 작업이 파일의 현재 offset 부터 시작한다.
성공을 리턴하기 전에 offset은 실제로 읽을 바이트 수보다 증가된다

기존 함수 프로토타입

int read(int filedes, char *buff, unsigned nbytes);
  • char을 void: void*는 제네릭 포인터 사용된다.
  • return value: 양수 바이트 수

7. write

#include <unistd.h>

ssize_t write(int filedes, const void *buff, size_t nbytes);

return: 성공시 실제 쓰여진 byte 반환, error -1

8. I/O Efficiency

buffsize 정하는 법
계산 결과 BUFFSIZE가 8192일 때 가장 적은 시스템 시간이 소모되었다. 버퍼 사이즈를 더 늘리는 것은 아무 효과가 없었다.

9. file sharing

유닉스는 서로 다른 프로세스 사이에 열린파일 공유
우선 커널에의해 모든 I/O에서 사용되는 자료구조를 알아보자.

다음 세가지 자료구조가 커널에 의해 사용되고, 그것들간의 상호작용이 한 프로세스가 다른 프로세스의 파일을 공유하려고 할 때 생겨나는 효과를 결정짓는다.

  1. 모든 프로세스는 프로세스 테이블 내의 엔트리를 갖는다. 각각의 프로세스 테이블 내에는 열려진 file descriptor 테이블이 존재한다. 이것은 아마 벡터로 되어 있을 것이다. 각각의 file descriptor
  • file descriptor flag

  • 파일 테이블 엔트리로의 포인터

로 구성된다.

  1. 커널은 모든 열려진 파일의 테이블을 관리한다. 각각의 테이블 엔트리는 다음을 포함한다.
  • 파일 상태 플래그(read, write, append, sync, nonblocking, 등등)

  • 현재 파일의 옵셋

  • 파일의 v-노드 테이블 엔트리를 가리키는 포인터

  1. 모든 열려진 파일(이나 디바이스)은 v-노드 구조체를 가진다.
  • v-노드에는 파일의 타입과 파일에서 수행하는 함수들의 포인터가 포함
  • 대부분의 파일에서, v-노드는 그 파일에 대한 I-노드 또한 포함
    이 정보는 파일이 열려질 때 디스크로부터 읽혀지고, 파일에 대한 모든 적절한 정보는 제공될 준비가 된다. 예를 들어 I-노드는 파일의 소유자나 크기나 파일이 존재하고 있는 장치나, 실제 데이터 블록이 존재하고 있는 곳의 포인터 등등이 된다.

10. atomic operations

  • creating a File

11. dup, dup2

#include <unistd.h>
//filedes에 대한 복제본인 새로운 파일 디스크립터를 생성하여 반환
int dup(int filedes);
//filedes를 filedes2에 복제하고 복제된 새로운 파일 디스크립터 반환
int dup2(int filedes, int filedes2);
//하나의 파일을 공유

return: ok file descriptor, error -1

12.fcntl

이미 열린 파일만 변경 가능

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

int fcntl(int filedes, int cmd, .../* int arg */);

return: ok, error -1

  • F_DUPED: 파일 descriptor 복사(dup2는 복사될 파일 지정자 사용자가 지시, 이거는 arg와 같은 크기의 파일지정자를 되돌려주거나, 이미 사용되어지고 있다면 arg보다 큰 할당 가능한 파일지정번호 중 가장 작은 번호 return)
  • F_GETED: filedes에 대한 flag값 return, 현재는 FD_CLOEXEC 플래그만 정의
  • F_SETED: FD_CLOEXEC(close-on-exec)의 값을 지정된 비트값으로 설정한다.
  • F_GETFL: 파일지정자에 대한 플래그값 -open(2) 호출시 지정한 플래그를 되돌려줌
  • F_SETFL: arg에 지정된 값으로 파일지정자 fd의 플래그를 재설정
  • F_GETOWN: 비동기 입출력과 관련, SIGIO와 SIGURG 신호를 받는 프로세스 아이디를 얻기 위해서 사용
  • F_SETOWN: 비동기 입출력과 관련, SIGIO, SIGURG 시그널을 수신하는 프로세스 아이디 설정위해 사용

13. ioctl

하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 함수
read(), write()를 이용해서 데이터를 읽고 쓰는 등의 기능은 가능하지만 하드웨어를 제어하거나 상태정보를 확인하려면 ioctl() 사용해야함

#include <unistd.h>
#include <sys/ioctl.h>

int ioctl(int filedes, int request, ...);

첫번째 인자: open한 fd값
두번째 인자: #define 값, 디바이스에 전달할 명령
나머지 인자는 일반적으로 1개 추가: 변수나 구조체의 포인터

/dev/fd

/dev/fd/n 파일을 여는 것은 descriptor을 복제하는 것과 같다
fd = open("/dev/fd/0", mode);
가 호출되면, 대부분의 시스템은 mode 지정자를 무시한다. 하지만 일부 시스템이 열려있는 경우(STDIN_FILENO) 파일의 mode의 부분집합이기를 요구한다.

fd = dup(0);
descriptor 0과 fd는 동일한 파일 테이블 엔트리를 공유
예를 들어 descriptor 0은 읽기 전용으로 열렸으면 fd만 읽을 수 있다. 시스템은 open 모드를 무시
fd = open("/dev/fd/0", O_RDWR);
호출에 성공하더라도 fd를 작성할 수는 없다.
creat에 지정하거나 O_CREAT와 함께 open 할 수 있다. 만약에 경로명이 /dev/fd/1과 같은 것이어도 정상 작동

0개의 댓글