본문 바로가기

카테고리 없음

2. 유닉스 : 파일

유닉스는 70년대에 나온 OS, 그 전에는 IBM의 메인프레임이 주류였음

메인프레임은 쓰기가 불편

유닉스는 파일의 기능을 심플하게 함

 

FILE

파일은 BYTE의 sequence

파일에는 어떠한 포멧도 정해지지 않음

(메인프레임은 ISAM, RANDOM, VSAM FILE 등 파일의 종류가 많고, 액세스하는 API가 모두 달랐음

파일의 타입과 각각의 READ, WRITE하는 API를 OS가 모두 정의해서 OS가 무거웠음)

모든 byte는 디스크 파일에서 주소를 각각 지정함. - direct random file이 됨

파일에 관한 인터페이스를 다 통일시킴

외부 디바이스, 디스크 파일 등 모두 파일로 간주

모든 디바이스의 인터페이스를 통일함 read, write

 

FILE SYSTEM

파일과 데이터를 organize하고 hierarchical하게 함

체계적으로 만들고 인터페이스를 통일하여 파일을 찾기 쉽게 만듦

데이터 스토리지 디바이스를 사용할 수 있음

하드디스크, CD ROM, 마그네틱 테이프, USB 등 적어도 외형적으로는 통일된 파일 시스템일 들어가도록 함

 

 

 

왼쪽 상자에 있는 것들이 시스템콜

파일을 액세스하기 위한 시스템콜은 기본적으로 이렇게 9개

시스템콜이 발생할 때마다 소프트웨어 인터럽트(트랩)이 발생

시스템콜 인페이스가 있고 그 밑에 최종적으로 하드웨어가 있음

유저가 시스템콜로 파일을 액세스하기 위한 시스템콜을 부르면 file system management가 있고

hardware controller로 하드웨어를 제어함

 

디스크 등은 block device이고

스크린, 키보드, 프린터는 character device임

character device의 단위는 1 byte라서 중간에 버퍼가 필요없음

block device는 IO단위가 1 block(보통 4KB = 4096byte), buffer cache가 있음

 

lseek는 디스크에서만 쓰는 random access가 되는 block device인 디스크에서만 사용하는 시스템콜

디스크에 저장된 파일의 바이트 단위 주소를 집어내어 위치를 이동시키는 시스템콜

 

ANSI C.(American national standard institute) 국제표준이 아닌 미국 표준임

이 함수들은 unbuffered IO, 라이브러리 안에 있는 버퍼가 없다는 말임, 곧바로 커널에 접속

read하면 커널에 접속하지만 fread하면 라이브러리를 통해 접근하는데 그 사이에 버퍼가 있음

 

파일디스크립터

파일을 오픈할 때 넘어오는 포인터가 있음

파일을 지정하는 번호가 있는데 이를 파일디스크립터라고 함

음수가 아닌 정수로 주어짐

파일을 새로 만들어서 오픈하거나 기존의 파일을 오픈할 때 파일디스크립터로 read , write 함

기본적으로 세 개의 파일디스크립터가 이미 할당됨

1번과 2번은 스크린으로 똑같은데 output과 error를 구분해야할 때가 있어서 구분함

open("filename-relative or absolute pathname-",)이러면 3번으로 파일디스크립터 할당됨

사용되지 않은 정수 중 가장 낮은 숫자 반환

 

/* a rudimentary example program */

/* these header files are discussed below */
#include <fcntl.h>
#include <unistd.h>

main()
{
    int fd;
    ssize_t nread;
    char buf[1024];

    /* open file “data” for reading */
    fd = open(“data”, O_RDONLY); 

    /* read in the data */
    nread = read(fd, buf, 1024);

    /* close the file */
    close(fd);
}

 

#include <fctnl.h> : open 시스템콜을 쓸 때 필요로 함

#include <unistd.h> : ssize_t 타입을 사용하기 위해서 필요로 함

fd : 파일디스크립터, 정수

fd = open("data", O_RDONLY) : realative pathname, fd는 3이 됨

read(fd, buf, 1024) : fd는 파일디스크립터, buf는 버퍼 사이즈, 1024는 실제로 읽을 바이트 수

ssize_t 

sys/types.h에 저장되어 있음

이를 primitive data type이라고 함

유닉스에 많은 버전이 있어서 return 값을 short, long 등 다양하게 만듦 

이를 표준화하기 위해서 새로운 이름으로 통일 함

버전마다 다른 속성을 ssize_t같은 이름 안에 숨기는 거임

 

open(2)

open(1)은 command, open(2)는 system call, open(3)은 library를 나타냄

open system call을 사용하기 위해서 /usr/include/fcntl.h해더파일을 include해야 함

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

open의 argument에는 3개가 있음

첫번째에는 파일명을 넣음, relative나 absolute로 pathname을 넣음. char*는 문자열임

두번째에는 flag. read only로 할지, write only로 할지, read write를 할지를 결정

세번째에는 옵션임

 

flag는 보통 바이너리 값이 주어져 있음

 

O_RDONLY || O_WRONLY는 00과 01을 bitwise OR연산을 하는 거라서 O_WRONLY가 됨

 

 

O_EXCL은 O_CREAT했지만 파일이 존재한다면 error메세지를 보내는 거

O_TRUNC는 파일이 존재한다면 내용을 모두 없애는 거

O_NONBLOCK은 안씀

이 옵션들이 read, write하고 같이 쓰임

 

fd = open(“/tmp/newfile”, O_WRONLY|O_CREAT, 0644);
/* if isExist(file) “file open” else “file create & open” */ 

fd = open(“/tmp/newfile”, O_WRONLY|O_CREAT|O_EXCL, 0644);
/* if isExist(file) “open error” else “file create & open” */

fd = open(“/tmp/newfile”, O_WRONLY|O_CREAT|O_TRUNC, 0644);
/* if isExist(file) “file truncate & open ” else “file create & open” */

O_WRONLY : 파일이 있으면 true라서 return 3, 없으면 false라서 없으면 file open error로 return -1

파일디스크립터는 non negative라서 -1이 되면 시스템콜 실행 실패를 나타냄

O_WRONLY | O_CREAT : 파일을 여는 것은 위와 같은데, 파일이 없다면 error가 아니라 create를 함

이 때 0644라는 permission을 주고 create file 

O_WRONLY | O_CREAT | O_EXC : 파일이 있으면 open하지 말고, 없으면 create해서 open 하라는 말

O_WRONLY | O_CREAT | O_TRUNC : open할 때 truncate하고 open, O_TRUNC가 없으면 그냥 overwrite

기존의 데이터를 모두 지우고 length를 0으로 만듦

 

APPEND는 파일 맨 끝에 포인터를 두고 맨 뒤에서부터 write하는 거

WRONLY는 맨 앞에부터 overwrite하는 차이점이 있음

파일이 없다면 error나느 건 똑같음

 

#include <stdlib.h>	/* exit() */
#include <fcntl.h>	/* open() */

char *workfile = “junk”;

main()
{
    int fd;
    if( (fd = open(workfile, O_RDWR)) == -1)
    {
        printf(“Couldn’t open %s\n”, workfile);
        exit(1);
    }

    exit(0);
}

#include <fcntl.h> : open을 쓰기 위해서 필요

#include <stdlib.h> : exit을 쓰기 위해서 필요

junk라는 문자열 file name pointer가 workfile임

relative pathname을 RDWR로 OPEN함 

이 파일이 없으면 -1이 반환

그 때 couldn't open이라고 출력

 

FILE PERMISSION

유닉스는 multi user system, 

user, group, others로 나눠서 permission을 각각 줌

r(read), w(write), x(execute)로 각각 3비트씩 총 9비트임

file permission의 symbolic names가 있는데 숫자가 더 편해서 보통 숫자로 사용함

그래서 이건 딱히 몰라도 됨

 

CREAT(2)

#include <fcntl.h> 
int creat(const char *pathname, mode_t mode);

pathname을 받아서 그 파일을 permission을 주고 만드는 시스템콜

mode_t로 마찬가지로 primitive data type이고 보통 unsigned integer

return 값은 파일디스크립터가 되고, create 실패하면 -1이 return되어 error

fd = creat(“/tmp/newfile”, 0644);
fd = open(“/tmp/newfile”, O_WRONLY|O_CREAT|O_TRUNC, 0644);

 creat와 open ( wronly | creat | trunc )와 같음

create와 read only는 함께 사용할 수 없음. 방금 만들어서 읽을 게 없기 때문임

 

새로운 파일을 open이나 o_wronly | o_creat로 열면 그 디렉토리에 이름이 하나 추가됨

그 디렉토리 parent에 있는 디렉토리 파일에 새로운 이름이 write된다는 말임

current working directory뿐만 아니라 root까지 permission이 모두 있어야함

 

user id는 real user id와 effective user id가 있음

그 파일의 실제 owner는 effective user id가 됨

group id도 마찬가지로 real group id와 effective user id가 있음

나중에 설명

 

close(2)

#include <unistd.h>
int close(int filedes);

open해서 file name과 flag를 설정하고 파일디스크립터가 생김

그 파일디스크립터를 close에 넣으면 그 파일을 닫는 거임

프로그램이 종료될 때 close를 안하더라도 자동을 ㅗ닫힘

 

read(2)

#include <unistd.h>
ssize_t read(int filedes, void *buffer, size_t n);

argument 세개

첫번째는 파일디스크립터

두번째는 char buffer, void라는 것은 미리 type을 정해두지 않는다는 것, 가변성

세번째 n 만큼 바이트를 읽음. buffer에서 가리키는 위치부터 n만큼 읽은 거

size_t도 마찬가지로 primitive system data type임. 이거도 int라고 생각하면 됨

read에 실패하면 -1을 반환함

 

파일디스크립터가 가리키는 파일의 맨 마지막까지 읽은 후

EOF에 도착하면 그 이후를 읽을 수 없음. 데이터 바이트 갯수가 0개 이때는 0을 반환

리턴 값이 0이라는 것은 파일이 이미 EOF라는 것

 

파일을 오픈할 때 파일의 포지션이 정해짐

파일 맨 앞부분을 file position 또는 offset이라고 함. 보통 맨 앞을 가리킴

append로 파일을 열면 맨 뒤를 가리킴

file offset은 현재 읽을 위치를 말하는 거

파일 포지션은 read나 write할 때 사용

buffer는 시작 위치를 가리키는 변수, n은 읽을 파일의 크기임

 

int fd;
ssize_t n1, n2, n3;
char buf1[512], buf2[512], buf3[512];

if( (fd = open(“foo”, O_RDONLY)) == -1)
    return -1;

			/* f_offset : 0 */
n1 = read(fd, buf1, 512);	/* n1 : 512, f_offset : 512 */
n2 = read(fd, buf2, 512);	/* n2 : 188, f_offset : 700 */
n3 = read(fd, buf3, 512);	/* n3 : 0, when read EOF */

"foo"라는 relative pathname으로 넣은 파일을 read only로 읽음

만약 open의 return 값이 -2이면 permission이 없는 거임

 

파일이 오픈됐다면 fd는 3이 됐을 거임 

foo파일에 700byte가 있었다고 가정

첫 read 수행할 때에는 file offset이 맨 위를 가리킬 거임 거기서부터 512만큼 읽음

두번째 read를 수행할 때 file offset은 512에 있을 거임. 남은 file은 188dla

512만큼 읽지 못하고 188만큼 읽고 file offset은 700을 가리킴

세번째 read할 때는 file offset이 700을 가리키며 거기는 EOF임 그래서 return 0을 할 거임

 

파일디스크립터 0,1,2는 이미 할당되어 있고 open에 성공하면 파일디스크립터가 3이 됨

그 파일디스크립터에는 "foo"파일을 바로 가리키지 않고, 그 파일을 나타내는 file table을 가리킴

file table에는 count(해당 파일을 가리키는 파일디스크립터 개수), flag, offset이 있음

 

write(2)

#include <unistd.h>
ssize_t write(int filedes, const void *buffer, size_t n);

write도 unistd.h가 include 되어야 함

argument에는 파일디스크립터, buffer, size_t가 있음 

buffer는 메모리이고 그 시작부터 n바이트만큼 파일디스크립터가 가리키는 파일에 쓰라는 말임

 

실제 write된 데이터 바이트의 수가 return되고 

EOF는 없으니까 0 은 출력안됨

write에 실패하면 -1을 반환

 

read와 마찬가지로 offset에서 시작해서 n 바이트만큼 write를 함

그러면 file offset이 n만큼 뒤로 이동해 있음

이미 있는 file이라면 overwrite함.

 

write only와 append를 bitwise or하면 file position이 EOF로 감

그래서 write하면 맨 마지막에 write를 하게 됨

 

int copyfile ( const char *name1, const char *name2){
    int infile, outfile;
    ssize_t nread;
    char buffer[BUFSIZE];	/* BUFSIZE : 512 */

    if ( (infile = open (name1, O_RDONLY ) )== -1)
	return(-1);

    /* FMODE : O_WRONLY|O_CREAT|O_TRUNC */
    if ( (outfile = open (name2, FMODE, PERM) )== -1){
        close (infile);
        return (-2);
    }
    
    while ( (nread = read (infile, buffer, BUFSIZE) ) > 0){
        if ( write(outfile, buffer, nread) < nread ){
	   close (infile);
	   close (outfile);
	   return (-3); 		/* 쓰기 오류 */
        }
    }

    close (infile);
    close (outfile);

    if ( nread == -1)  return (-4)	/* 마지막 읽기에서 오류 발생 */
    else   return (0); 		/* 만사가 잘 되었음*/
}

name1에 있는 내용을 name2에 copy하라는 내용

FMODE : O_WRONLY|O_CREAT|O_TRUNC

name2가 이미 있는 파일이면 emtpy file로 만들어서 열고, 없으면 permission을 주고 만들어서 엶

 

read 반환값이 0보다 크면 뭔가 하여튼 읽었다는 말임

그럼 읽은만큼 outfile에다가 write함

그게 n보다 작으면 write에서 에러가 발생했다는 말

작지 않으면 아무것도 하지 않고 반복함

 

언젠가 이 file의 EOF를 만나고 close함

 

copyfile(“test.in”, “test.out”);

디스크에서 IO를 할 때, 4KB 단위로 IO를 하는 게 가장 성능이 좋음

그래서 buf size를 증가시키면 좋아짐. 무조건 좋아지는 건 아님

read와 write를 실행하면 context switch도 발생하기 때문에 굉장히 무겁고 오버헤드가 큼

그래서 실행횟수를 가능한 줄이는 게 효율적

 

lseek(2)

#include <unistd.h>
/* the character l in the name lseek means “long integer” */
off_t lseek(int filedes, off_t offset, int start_flag);

off_t는 int 나 long

argument에는 파일디스크립터, start flag, offset가 있음.

disk regular file에서만 사용할 수 있는 시스템콜

 

file offset을 바꿔주는 시스템콜

유닉스의 디스크 파일은 각 바이트마다 addressable함

중간에 있는 바이트부터 읽거나 쓰고 싶다면 거기로 file offset을 이동시키면 되는데 이걸 lseek가 함

그래서 file offset은 디스크에 있는 랜덤 액세스가 가능한 디스크 파일인 레귤러 파일에만 쓸 수 있음

 

flag는 세 가지 값을 가질 수 있음

SEEK_SET : 파일의 시작 위치를 기준으로 시작 위치에서 offset만큼 이동

SEEK_CUR : read, write하다가 중간에 file offset이 바뀌면, 그 offset을 기준으로 뒤로 offset만큼 가라는 말임

SEEK_END : 현재 값과 관계없이 무조건 EOF로 이동, 그 위치에서 offset만큼 +-하라는 말임

offset은 +- 모두 가질 수 있음

seek_set에서는 -값을 가질 수 없음. 맨 앞이니까

off_t nepos;

newpos = lseek(fd, (off_t)-16, SEEK_END);
// EOF에서 -16만큼 이동, 새로운 position이 return 됨

----------------------------------------------------------------------------

fd = open(fname, O_RDWR);
lseek(fd, (off_t)0, SEEK_END);
write(fd, outbuf, OBSIZE);

	==
fd = open(fname, O_WRONLY|O_APPEND);
write(fd, outbuf, OBSIZE);

----------------------------------------------------------------------------

off_t filesize;
int filedes;

filesize = lseek(fd, (off_t)0, SEEK_END); /* filesize is the size of file */
// end에서 offset이 0이니까 EOF를 가리키고 있을 거임