5개 함수
open, read, write, lseek, close
다중 프로세스
dup, fcntl, ioctl
파일 열거나 새로 생성시 커널은 file descriptor을 프로세스에 리턴한다
파일 읽거나 작성할 때 파일 읽거나 생성시 리턴된 file descriptor을 사용해서 식별한다.
<unistd.h>
#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_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를 초과하거나 할 때
#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);
이것을 사용하는 것이다
#include<unistd.h>
int close(int filedes);
파일 닫을 때 또한 프로세스에 파일이 있으면 레코드 잠금 풀어준다.
프로세스 끝날 때, 열린 모든 파일이 자동으로 커널에서 닫힌다.
많은 프로그램이 이사실에 이점을 가지고 명시적으로 파일을 닫지 않는다.
이것은 파일의 시작지점부터 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 변수
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);
}
#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);
#include <unistd.h>
ssize_t write(int filedes, const void *buff, size_t nbytes);
return: 성공시 실제 쓰여진 byte 반환, error -1
buffsize 정하는 법
계산 결과 BUFFSIZE가 8192일 때 가장 적은 시스템 시간이 소모되었다. 버퍼 사이즈를 더 늘리는 것은 아무 효과가 없었다.
유닉스는 서로 다른 프로세스 사이에 열린파일 공유
우선 커널에의해 모든 I/O에서 사용되는 자료구조를 알아보자.
다음 세가지 자료구조가 커널에 의해 사용되고, 그것들간의 상호작용이 한 프로세스가 다른 프로세스의 파일을 공유하려고 할 때 생겨나는 효과를 결정짓는다.
file descriptor flag
파일 테이블 엔트리로의 포인터
로 구성된다.
파일 상태 플래그(read, write, append, sync, nonblocking, 등등)
현재 파일의 옵셋
파일의 v-노드 테이블 엔트리를 가리키는 포인터
#include <unistd.h>
//filedes에 대한 복제본인 새로운 파일 디스크립터를 생성하여 반환
int dup(int filedes);
//filedes를 filedes2에 복제하고 복제된 새로운 파일 디스크립터 반환
int dup2(int filedes, int filedes2);
//하나의 파일을 공유
return: ok file descriptor, error -1
이미 열린 파일만 변경 가능
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int filedes, int cmd, .../* int arg */);
return: ok, error -1
하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 함수
read(), write()를 이용해서 데이터를 읽고 쓰는 등의 기능은 가능하지만 하드웨어를 제어하거나 상태정보를 확인하려면 ioctl() 사용해야함
#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int filedes, int request, ...);
첫번째 인자: open한 fd값
두번째 인자: #define 값, 디바이스에 전달할 명령
나머지 인자는 일반적으로 1개 추가: 변수나 구조체의 포인터
/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과 같은 것이어도 정상 작동