본문 바로가기

CS/유닉스프로그래밍

2-2. 유닉스 파일 시스템콜 : open, creat, close, read, write, lseek , dup, fcntl

open(2)

괄호 안에 (2)라고 있는 건 시스템콜을 의미함

1은 command, 2는 system call, 3은 library를 의미함

 

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

			Returns: file descriptor if OK, -1 on error 

 

open 시스템콜을 사용하기 위해서는 /usr/include/fcntl.h 헤더파일을 반드시 include 해야함

첫번째 인자로는 relative 또는 absolute pathname이 들어감 char*는 문자열임

세번째 인자는 옵션임

 

두번째 인자 flag는 read only, wrtie only, read write 이런 내용이 들어감

 

보통 바이너리 값으로 되어 있음 

read only는 00, write only는 01, read write는 10 이렇게 되어 있어서

O_RDONLY || O_WRONLY 이렇게 하면 bitwise OR라서 01이 나오니 O_WRONLY가 됨

이 거 외에도 다른 flag들이 있음

 

O_APPEND는 파일  끝에 APPEND 하라는 말

O_CREAT는 없는 파일을 만듦

O_EXCL은 O_CREAT를 했는데 파일이 이미 존재한다면 ERROR메세지를 보내기

O_TRUNC는 파일이 이미 존재한다면 내용을 다 없애라는 말임

NONBLOCK은 사용하지 않음

이 flag들이 모두 read와 write와 함께 사용됨, 여러가지 조합으로 사용될 수 있음

 

O_WRONLY는 혼자 쓰일 때, 파일이 있으면 true, 없으면 false가 됨

true라면 file open을 수행하고, false면 file open error가 됨

파일이 있어서 열면 return 3이 됨, 파일디스크립터를 반환

파일이 없는데 열면 return -1이 됨

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 | O_CREAT : 파일이 없다면 error가 아니고 create를 함

O_WRONLY | O_CREAT | O_EXCL : 파일이 있으면 open 하지 않고, 없으면 create해서 open 함

O_WRONLY | O_CREAT | O_TRUNC : 기존 데이터를 empty로 만들고 open함 

 

#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);
}

fd = open(workfile, O_RDWR)

이 때 파일이 없으면 -1이고 couldn't open을 출력함

 

 

 

 

permission은 9개 비트임 read, write, execute에 대한 권한으로 user, group, others 마다 있음

permission을 볼 때 ls -al 로 봄

chmod로 permission을 바꿔줄 수 있음 

chmod g-r test 라고 하면 test 파일에 대한 group의 read 권한을 제거하는 거임

chmod o+w test라고 하면 test 파일에 대한 others의 write 권한을 추가하는 거임

이걸 8진수 세자리로 나타낼 수도 있음

 

그래서 create 할 때 0644라고 하면 110100100라서

user에게는 read, write 권한이 있고 group과 others에게는 read 권한만 있는 거임

 


creat(2)

새로운 파일이 만들러지면 그 디렉토리에 이름이 하나가 추가됨

추가되는 디렉토리의 이름이 parent 디렉토리에 write된다는 말임

새로운 파일을 만드려면 current working directory에 있 대한 write permission이 있어야함

current 뿐만 아니라 parent, root 까지 모두 write permission이 있어야 함

 

#include <fcntl.h> 

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


			Returns: file descriptor if OK, -1 on error 

 

fd = creat(“/tmp/newfile”, 0644);

==

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

 

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

process를 만들 때 이 파일의 오너는 effective user id가 됨

group id도 real과 effective로 나뉨

 


close(2)

#include <unistd.h>

fd = open(“/tmp/newfile”, O_RDONLY);
	.
	.
	.
fd = close(fd);

                           Returns: 0 if OK, -1 on error 

open 한 파일을 닫음


read(2)

#include <unistd.h>

ssize_t read(int filedes, void *buffer, size_t n);


       Returns: number of bytes read, 0 if end of file, -1 on error

인자에 파일디스크립터, buffer가 들어감

buffer의 type이 void인 이유는 가변성울 두기 위해서임

파일디스크립터가 가리키는 파일에서 buffer가 가리키는 메모리의 위치에서부터 n사이즈 만큼 읽어서 그 뒤로 넣음

 

여기서 size_t도 primitive system data type임

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

 

파일디스크립터가 가리키는 파일의 맨 마지막까지 읽고 EOF에 도착하게 되면 그 다음을 읽을 수 없음

하나도 못 읽는다는 것은 읽은 데이터 바이트가 0이라는 말임

그래서 current file postion이 이미 파일의 EOF라면 return 값이 0임

 

current file position은 파일을 open할 때 정해짐

file position 또는 offset이라고 하는데, 보통은 맨 앞을 가리킴

O_APPEND로 open하면 맨 뒤를 가리키게 됨

 

파일을 읽고 나면 file position이 다 읽은 위치 n 으로 이동함

다음에 읽을 때는 또 이동한 file position부터 읽음

 

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라는 파일을 read only로 open함

반환 값이 -1이라는 것은 open에 실패했다는 것임. 파일 권한이 없는 등의 이유가 있음

파일이 open 되면 파일디스크립터가 3이 됨

 

foo 파일의 총 데이터가 700 byte 만큼 있다고 하면

처음 511을 읽고 file position은 512를 가리키게 될 것임

두번째 read할 때는 188byte밖에 안남아서 n2는 188이 될 것임

그럼 세번째 read할 때 file position은 EOF를 가리키고 있을 거임 그래서 0을 반환함

 

파일 테이블에 있는 count는 해당 파일 테이블을 가리키는 파일디스크립터의 갯수임

O_RDONLY는 mode를 의미하고 그 다음 있는게 file position = file offset 임


write(2)

#include <unistd.h>

ssize_t write(int filedes, const void *buffer, size_t n);

       		Returns: number of bytes written if OK, -1 on error

 인자는 파일디스크립터, buffer, n 으로 read와 같음

시작 위치부터 n 바이트 만큼 파일 디스크립터가 가리키는 파일에 buffer의 내용을 쓰는 것임

실제 write된 데이터 바이트 수가 return 되고 EOF는 없으니 0 은 반환 안됨

write에 실패하면 -1을 return 함

 

read와 마찬가지로 file offset에서부터 n바이트 만큼 write함

그러면 file offset이 n만큼 이동한 상태로 update됨

 

이미 있는 파일을 write로 open하면 이미 있는 파일이라면 overwrite함

APPEND를 넣으면 file position이 EOF로 이동해서 열림

 

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하는 내용임

 

open(name2, FMODE, PERM)

FMODE는 O_WRONLY | O_CREAT | O_TRUNC 임 

이렇게 열면 파일이 있으면 truncate하고 열고, 없으면 PERM 권한을 주고 새로 만들어서 열어버림

 

BUFSIZE는 512로 설정되어 있음

read 반환 값이 0보다 크면 어떤 데이터를 하여튼 읽었다는 말임

그럼 읽은 만큼 outfile에 write를 함

write의 반환 값이 nread보다 작다는 건 write에서 에러가 발생했다는 말임

name1에 EOF를 만나면 끝나고 close함


read와 write의 성능

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

 

user time은 실제 프로세스가 실행된 시간을 의미함

real time은 시작했을 때부터 끝날 때까지의 시간을 의미함 - OS와 다른 프로세스까지 감안

system time도 비슷한 거임 오에스를 실행한 시간을 의미함

 

디스크에서 IO할 때 block size 4K byte 만큼 하는 것이 가장 성능이 좋음

그래서 bufsize를 증가시킨다고 무작정 좋아지는 것이 아님

 

buffer 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);

	                  Returns: new file offset if OK, -1 on error

lseek는 unistd.h가 include 되어야 함. 이 시스템콜은 regular file 에서만 사용 가능함

off_t는 int나 long 정도 될 거임

유닉스에서 디스크 파일은 각 바이트 마다 addressable하다고 했음

중간에 있는 바이트부터 읽거나 쓰고 싶다면 거기로 file offset을 이동시키는 시스템콜임

 

그래서 file offset은 디스크에 있는 랜덤엑세스가 가능한 디스크 파일인 regular file에서만 사용 가능한 것임

flag는 세 가지가 있음

SEEK_SET은 파일의 시작 위치를 기준으로 함. 맨 위부터 offset만큼 이동하라는 말임

SEEK_CUR은 현재 offset을 기준으로 이동하라는 말임

SEEK_END는 현재 값과 관계없이 무조건 EOF에서부터 offset만큼 이동하라는 말임

offset은 +-값을 갖는데 SET에서 마이너스는 할 수 없음

END에서는 + 값 가능함

 

off_t nepos;

newpos = lseek(fd, (off_t)-16, SEEK_END);

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

off_t filesize;
int filedes;

filesize = lseek(fd, (off_t)0, SEEK_END); /* filesize is the size of file */

 

 

newpos = lseek(fd, (off_t)-16, SEEK_END);

SEEK_END니까 EOF로 이동해서 그 위치에서 -16이니까 앞쪽으로 16바이트 만큼 이동

그 위치, 새로운 position이 return되어 newpos에 저장됨

 

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);

 

SEEK_END로 flag를 설정하고 offset을 0으로 두고 write를 하는 것과 APPEND flag를 주는 것과 같음


 

FILE SHARE

파일을 엑세스할 때 공유하면서 할 수 있음

자세한 내용을 다루기 전에 커널 안에서 프로세스와 파일 엑세스가 어떻게 다루어지는지 데이터 구조를 이해해야 함

유저가 프로세스를 생성하려면 커널 아래 프로세스 테이블이라는 data structure가 있음

새로운 프로세스가 만들어지면 거기에 새로운 엔트리가 하나 추가로 생성됨

프로세스 테이블에 대응되는 엔트리 안엥 그 프로세스의 각종 정보가 있음

 

flag에 대한 정보와 offset과 vnode(inode) 정보가 있음

inode table과 파일은 1대1 대응됨 

inode table이 실제 파일을 가리킴

특정 파일에 엑세스하는 파일 테이블은 여러개가 있을 수 있음

 

$ ./a.out	/* fd = open(“test”, O_RDONLY); */

$ ./b.out	/* fd = open(“test”, O_RDONLY); */

서로 다른 프로세스가 같은 파일을 공유할 수도 있음

a.out과 b.out이라는 프로세스가 있는데, 각자 파일디스크립터가 3번인 파일이 같음

test라는 디스크 파일이 있는데 이 파일은 vnode table과 1대1대응 함

a.out과 b.out이 가리키는 file table은 상이한데, 그 테이블이 가리키는 test파일의 inode table은 같음

 

int fd3, fd4;   char buf[20];

fd3 = open(“file”, O_RDWR);
fd4 = open(“file”, O_RDONLY);

read(fd3, buf, 20);	printf(“fd3: %s\n”, buf);
read(fd4, buf, 30);	printf(“fd4: %s\n”, buf);

close(fd3);
close(fd4);

같은 file 파일에 대해서 fd3은 RDWR로 open하고 fd4는 RDONLY로 open함

fd3이 가리키는 file table이 만들어지고, 이 file이라는 디스크파일과 1대1로 대응되는 vnode table을 가리킴

이 파일을 처음 열었을 때는 offset은 0으로 open됨

 

fd4가 RDONLY로 열고, 해당 file table은 fd3이 가리키는 vnode table과 같은 vnode table을 가리킴

fd3이 파일디스크립터에서 20byte만큼 읽었으면 fd3의 offset이 20으로 update됨

fd4번에서 30byte만큼 읽으면 해당 file table에 저장된 offset이 30으로 update되는데 fd3의 offset은 그대로 20임


dup(2) , dup2(2)

dup2가 dup보다 더 중요하고 많이 사용됨

파일디스크립터를 복제하는 시스템콜임

#include <unistd.h>

int dup(int filedes);
int dup2(int filedes, int filedes2);

               Both return: new file descriptor if OK, -1 on error

filedes를 filedes2에 복제하면 복제한 파일 디스크립터가 반환됨

file table에 있는 count가 2가 됨

해당 테이블을 가리키고 있던 파일디스크립터가 그대로 다른 파일디스크립터에 복제되기 때문임

 

newfd = dup(1) /* STDOUT_FILENO #1 */
	==
newfd = fcntl(1, F_DUPFD, 0);
	!=
newfd = 1

 

F_DUPFD는 command임

1번을 0번으로  카피하는게 아니라, 0보다 큰 번호중 lowest unuesd integer를 말하는 거임

0,1,2는 원래 사용되니까 3이 됨

새로만들어진 newfd 두개가 같고, 파일디스크립터 1번은 스크린임

 

fd4 = open(“test”, O_RDONLY); /* fd4 == 4  */

dup2(3, fd4);	/* test’s file descriptor closed */

==
close(fd4);
fcntl(3, F_DUPFD, fd4)

사전에 이미 파일디스크립터 3번이 open 되어 있다고 가정

3번이 어떤 파일을 가리키게 되면서 count가 1이 됨

4번 파일디스크립터가 test를 O_RDONLY로 open 함

근데 dup으로 3번을 파일디스크립터 4번에 복제하면 3번이 가리키는 file table을 가리키게 됨


 

fcntl(2)

file control을 줄여서 fntl임

 

#include <fcntl.h>

int fcntl(int filedes, int cmd, ...);

	    Returns: depends on cmd if OK (see following), , -1 on error

 

open한 파일디스크립터가 첫번째 인자로, 두번째부터는 명령의 종류가 들어감

실패할 경우 -1을 반환함

return 값이 명령에 따라 종류가 달라짐

#include <fcntl.h>

int filestatus(int filedes)
{
  int arg1;

  if (( arg1 = fcntl (filedes, F_GETFL)) == -1)
  {
    printf ("filestatus failed\n"); 
    return (-1);
  }

  printf ("File descriptor %d: ",filedes);

  /* 개방시의 플래그를 테스트한다. */
  switch ( arg1 & O_ACCMODE){
    case O_WRONLY: printf ("write-only"); break;
    case O_RDWR:   printf ("read-write"); break;
    case O_RDONLY: printf ("read-only");  break;
    default: printf("No such mode");
  }

  if (arg1 & O_APPEND)
    printf (" -append flag set");

  printf ("\n");
  return (0);
}

 어떤 파일디스크립터의 mode가 뭔지 확인하는 코드임

파일디스크립터가 가리키는 file table의 첫번째에 mode가 있음

파일 디스크립터 F_GETFL command를 주면 return 값이 나오는데, 그 값에 정보가 있음

실패하면 -1을 반환함

arg1에 mode 정보가 있음