파일 데이터의 할당


리눅스의 파일 시스템은 크게 4가지로 구분 할 수 있다.



부트 블록(Boot Block) 


슈퍼 블록(Super Block) 

아이노드 블록(inode Blocks) 

데이터 블록(Data Blocks) 


  • 부트 블록 : 운영체제를 부팅시키기 위한 코드가 저장되어 있다.
  • 슈퍼 블록 : 파일 시스템과 관련된 정보를 저장하고 있다.
  • 아이노드 블록 : 파일에 대한 정보를 저장하고 있으며, 모든 파일은 반드시 하나의 아이노드 블록을 가지고 있다.
  • 데이터 블록 : 파일이 보관해야하는 데이터가 저장되어 있으며, 보관하는 데이터의 크기에 따라 여러 개가 있을 수 있다.


파일의 제거


#include <unistd.h>


int unlink(const char *pathname);


#include <stdio.h>


int remove(const char *pathname);


파일을 제거하는 데에는 두 가지 방법이 있다.

두 호출 모두 제거할 파일의 이름(스트링)을 인수로 받는다.


unlink("/tmp/myfile1");


remove("/tmp/myfile2");


위와 같이 사용할 수 있다.


두 호출 모두 파일의 제거에 성공하면 0을 반환하고, 실패시 -1을 반환한다.


빈 디렉토리를 제거히기 위해서는 remove(path)는 rmdir(path)와 동일하다.

디렉토리에 대해서는 unlink 대신 항상 remove 시스템 호출을 사용해야 한다.

추후 자세히 살펴보자.

lseek 시스템 호출


#include <sys/types.h>

#include <unistd.h>


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


앞에서 파일 포인터에 대해 배웠다.

lseek 시스템 호출은 이 파일 포인터를 임의의 위치로 변경할 수 있게 해준다.


첫 번째 인수는 파일 기술자이고, 두 번째 인수는 파일 포인터의 새 위치를 결정하는데, 세 번째 인수에서 더해질 바이트 수를 지정한다.

세 번째 인수는 두 번째 인수가 어느 위치를 기준으로 하여 더해질지 위치의 기준으로 다음과 같다.

  • SEEK_SET : offset을 파일의 시작위치부터 계산한다.
  • SEEK_CUR : offset을 파일 포인터의 현재 위치부터 계산한다.
  • SEEK_END : offset을 파일의 끝부터 계산한다.
두 번째 인수는 (off_t)로 캐스팅해주는 것에 유의해야한다.
lseek 시스템 호출의 반환값은 파일 안의 새로운 위치이거나 오류 발생시 -1을 반환한다.
off_t 타입은 <sys/types.h>에 정의되어 있으므로, 이 헤더파일은 include해준다.

다음의 예를 보자.

off_t newpos;
..
...
newpos = lseek(fd, (off_t) -8, SEEK_END);


오류가 발생하지 않는다면 newpos는 새로운 위치 값을 반환받을 것이다.

위에서 말한대로 두 번째 인수는 (off_t)로 캐스팅해주었다.

여기서 두 번째 인수 offset의 값은 음수가 가능하다. 즉, 파일 포인터에서 거꾸로 이동하는 것이 가능하다는 것이다.

만약 파일의 시작점보다 더 앞으로 움직일 때에는 오류가 발생할 것이다!

또한 파일의 끝보다 더 뒤의 위치로 이동을 할 수가 있는데, 이 때에는 읽기를 위한 자료를 존재하지 않지만 파일이 확장하게 된다.

이 경우 실제로 물리적으로 할당은 되지 않지만, ASCII null 문자로 채워진다.


※ 파일 오픈 후 파일 포인터 맨 끝에 위치시키기

fd = open("myfile", O_RDWR);

lseek(fd, (off_t) 0, SEEK_END);


fd = open("myfile", O_RDWR | APPEND);

 

위의 예시 둘 다 파일 포인터를 맨 끝에 위치시킨다!

write 시스템 호출


#include <unistd.h>


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


write 시스템 호출은 read 시스템 호출의 반대이다.

이는 내 프로그램 내의 버퍼의 내용을 외부 파일로 복사한다.


첫 번째 인수는 파일 기술자이다.

두 번째 인수는 쓰여질 내용에 대한 포인터이다.

세 번째 인수는 쓰여질 바이트의 수이다.

반환되는 값은 write에 의해 쓰여진 문자의 수 또는 오류 발생시 -1이 있다.


int fd;

ssize_t w1, w2;

char wbuf1[512], wbuf2[1024];

.

.

if( (fd = open("myfile", O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1 )

return (-1);


w1 = write(fd, wbuf1, 512);

w2 = write(fd, wbuf2, 1024);


myfile에 wbuf의 내용과 wbuf2의 내용을 연달아 적는다.

위의 open flags들은 '쓰기 전용으로 오픈하되, 파일이 존재하지 않으면 생성하고, 존재한다면 -1을 반환하라' 이다.


만약 O_EXCL을 제외한다면, 파일이 존재하면 그냥 쓰기 전용으로 오픈이 될텐데, 이렇게 오픈하고나서 write 시스템 호출을 하면?!

파일 포인터가 맨 앞에 위치하여 기존 파일의 내용이 처음부터 덮어써지게 될 것이다.

이 경우를 방지하고, 존재할 경우 파일 포인터를 맨 뒤에 위치하도록 해서 열고 싶다면 O_APPEND flag를 이용하면 되겠군!!




☞ read와 write 시스템 호출을 이용하여 파일 복사하기!

..

...

while( (nread = read(infile, buf, BUFSIZ)) > 0 ) {

while( write(outfile, buf, nread) < nread ) {

close(infile);

close(outfile);

return (-1);

}

}

.,,,


첫 번째 while문은 infle을 BUFSIZ만큼 읽어서 파일 끝까지 읽도록 반복한다.

두 번째 while문은 한 번 읽을 때마다 outfile에 읽은 만큼 write하라는 말이다.


여기서.. 만약 infile을 끝까지 read했다면 0을 반환한다. 그럼 첫 번째 while을 벗어난다.

그렇지 않다면 두 번째 while문으로 간다.

write는 쓰여진 바이트 수를 반환하는데, 정상적으로 쓰여졌다면 nread와 거의 항상 같을 것이다. <- 계속 반복하게 만드는 것

하지만 오류가 발생했거나, 잘못된 경우 nread보다 작은 값을 반환하면, 다 쓰여졌다는 것을 의미하는 조건이 참이 되어 파일을 close한다.

read 시스템 호출


#include <unistd.h>


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


read 시스템 호출은 파일 안에 있는 문자나 바이트를 내 프로그램 안의 버퍼로 복사하기 위해 사용된다.

버퍼는 void형이므로, 어느 타입의 데이터라도 저장할 수 있다.


첫 번째 인수 filedes는 이전의 open이나 creat 호출로부터 얻은 파일 기술자이다.

두 번째 인수는 자료가 복사될 문자 배열에 대한 포인터이다.

세 번째 인수는 파일로부터 읽혀질 바이트의 수를 나타내는 양의 정수이다.

read 시스템 호출은 실제로 읽힌 바이트수는 반환한다.

보통 세 번째 인수의 값인 요청된 문자의 수이지만, 항상 그런 것은 아니고, 더 작은 값을 가질 수 있다.

또한 오류가 발생하면 -1을 반환한다.


int fd;

ssize_t nread;

char buffer[SOMEVALUE];


// open을 통하여 fd를 얻었음

.

.

nread = read(fd, buffer, SOMEVALUE);



☞ 읽기-쓰기 포인터 (파일 포인터)에 대하여 알아보자.

시스템은 읽기-쓰기 포인터 또는 파일 포인터라 불리는 것을 사용하여 파일 안에서의 프로세스의 위치를 관리한다.


만약 myfile이 최소한 1024개의 문자를 가지고 있다고 가정하자.

아래의 프로그램은 myfile의 처음 512 문자들을 buf1에 저장하고, 두 번째 512 문자들을 buf2에 저장한다.


int fd;

ssize_t n1, n2;

char buf1[512], buf2[512];

.

.

.

if( (fd = open("myfile", O_RDONLY)) == -1 )

return (-1);


n1 = read(fd, buf1, 512);

n2 = read(fd, buf2, 512);


read의 경우, 시스템은 각 호출 후에 읽혀진 바이트 수만큼 파일 포인터를 전진시킨다.

파일 오픈 후 파일 포인터가 맨 처음에 위치하게 되는데, n1 = read(...); 를 수행한 후에는 파일 포인터가 512 다음에 위치하게 되고, n2 = read(...)를 수행한 후에는 그 위치에서 512를 더한만큼의 위치에 파일 포인터가 있을 것이다.


그렇다면, 내가 읽으려고 하는 파일의 크기를 모를 때 다 읽었는지 어떻게 알 수 있을까?

이 때에는, read의 반환 값을 이용하여 알 수 있다!

read 호출에 의해 요청된 문자의 수 > 파일에 남아 있는 문자의 수

↑ 이 경우 버퍼에는 파일 안에 남아 있는 문자들만 전달하고, 반환되는 값은 요청된 문자의 수만큼 읽지 못했으니 요청된 문자의 수가 아니라 바로 0이다.

즉, 반환된 값이 0이라면 더 읽을 수 있는 남아있는 문자가 하나도 없다는 말이다!


.

.

.

while( (nread = read(fd, buf, BUFSIZ)) > 0 )

total += nread;

.

.

.


위와 같은 방법으로 파일을 BUFSIZ만큼 읽어가면서 파일 내 문자 수를 셀 수가 있다.

close 시스템 호출


#include <unistd.h>


int close(int filedes);


close 시스템 호출은 open 호출의 반대로, 프로세스가 파일의 사용을 끝냈음을 알리는 것이다!

현재 수행 중인 프로그램이 동시에 개방할 수 있는 파일의 수가 제한되어 있기 때문에, close를 통하여 사용 종료를 알린다.


close 시스템 호출은 파일 기술자를 파라미터로 받는다.

이 파일 기술자는 close 사용 이전에 open이나 creat를 통해서 반환받아서 저장되어 있을 것이다.


int filedes;


filedes = open("file", O_RDONLY);

.

.

.

close(filedes);


close 시스템 호출은 성공하면 0을 반환하고, 오류 발생시 -1을 반환한다.


기본적으로, 프로그램 수행이 끝나면 모든 개방된 파일은 자동으로 닫힌다 :>

open 시스템 호출


#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>


int open(const char *pathname, int flags, [mode_t mode]);


open 호출이 성공하고 파일이 성공적으로 개방된다면, 양의 정수 값인 파일 기술자(file descriptor)를 반환한다.

만약 오류가 발생한다면, -1을 반환한다. (error checking!)


flags에는 다음과 같은 것들이 있다. (이것들은 fcntl.h에 정의되어있음!)

  • O_RDONLY : 읽기 전용으로 개방할래요.
  • O_WRONLY : 쓰기 전용으로 개방할래요.
  • O_RDWR : 읽기, 쓰기 전용으로 개방할래요.
open 시스템 호출을 이용하여 파일을 생성할 수 있는데, 이 때에는 flags의 결합으로!
  • O_WRONLY | O_CREAT : 개방하려는 pathname이 존재하지 않으면, 쓰기 전용으로 새 파일을 생성하여 개방하라!
여기서 세 번째 인수 mode가 사용되는데, 파일 접근 허가를 나타내는 숫자를 적는다. (추후에 자세히 알아보자)
만약 위의 결합에서 개발하려는 pathname이 존재한다면, O_CREAT와 mode가 없는 것 처럼 단지 쓰기 전용으로 개방된다.

하지만, 파일이 존재한다면 open을 실패하고 싶다면?
이 때에는 다음과 같은 flags를 결합하면 된다.
  • O_WRONLY | O_CREAT | O_EXCL : 개방하려는 pathname이 존재하지 않으면, 쓰기 전용으로 새 파일을 생성하여 개방하지만, 존재한다면 실패하고 -1을 반환하라!

또, 개방하려는 파일이 존재할 때 파일을 개방하는데, 그 안의 내용을 다 지우고 (0바이트로 만들기) 개방하고 싶을 때에는?
  • O_WRONLY | O_CREAT | O_TRUNC : 파일이 존재하고, 접근 허가가 허락할 경우 그 파일을 0바이트로 자르라!
즉, 과거에 수행했던 자료를 모두 지운다.


파일 포인터와 관련해서 다음과 같은 flag도 있다.

  • O_WRONLY | O_APPEND : 파일 포인터가 마지막 바이트 바로 뒤에 위치하도록 쓰기 전용으로 열어라!

보통 open을 하면 파일 포인터가 맨 처음에 위치하게 되지만, 위의 flag를 사용하여 open하면 파일 포인터가 맨 뒤에 위치하게 열린다.

+ Recent posts