IT 그리고 정보보안/Knowledge base

리눅스 파일 입출력 관련 시스템콜 함수

plummmm 2021. 4. 14. 13:10
반응형

먼저 open함수에 대해서 알아보자. 이름그대로 파일을 여는 시스템콜 함수이다.

파일을 read하든, write하든 일단 파일을 열어야 할 것이 아닌가? 하여튼 파일 입출력에서 가장 기본이 되는 함수이다.

일단 기본 함수 레퍼런스와 각 인자들이 뜻하는 바를 알아보자.

#include <fcntl.h>
int open(cosnt char *pathname, int oflag, ... /* mode_t mode */);

fcntl.h 헤더파일을 사용해야 하고

반환 값은

성공한다면 파일 디스크립터값을 리턴하고,

실패한다면 -1을 리턴한다.

 

char *pathname

파일의 경로를 포함한 이름을 나타내는 문자열 포인터

 

int oflags

어떤 방식으로 파일을 오픈 할 것인가에 대한 내용. 여러가지 값들이 있다.

mode_t mode

2번째 인자에서 파일을 생성할 시(O_CREAT) 에 사용된다. 생성될 파일의 권한에 관련된 인자이다.

(mode_t는 파일 접근권한을 부여하기 위한 플레그값을 가진 구조체이다.)

 

 

다음은 위의 값들을 fcntl.h 헤더파일에서 찾아봤다. (mode_t 구조체)

95 # define S_IRUSR        __S_IREAD       /* Read by owner.  */
96 # define S_IWUSR        __S_IWRITE      /* Write by owner.  */
97 # define S_IXUSR        __S_IEXEC       /* Execute by owner.  */
98 /* Read, write, and execute by owner.  */
99 # define S_IRWXU        (__S_IREAD|__S_IWRITE|__S_IEXEC)
100 
101 # define S_IRGRP        (S_IRUSR >> 3)  /* Read by group.  */
102 # define S_IWGRP        (S_IWUSR >> 3)  /* Write by group.  */
103 # define S_IXGRP        (S_IXUSR >> 3)  /* Execute by group.  */
104 /* Read, write, and execute by group.  */
105 # define S_IRWXG        (S_IRWXU >> 3)
106 
107 # define S_IROTH        (S_IRGRP >> 3)  /* Read by others.  */
108 # define S_IWOTH        (S_IWGRP >> 3)  /* Write by others.  */
109 # define S_IXOTH        (S_IXGRP >> 3)  /* Execute by others.  */
110 /* Read, write, and execute by others.  */

그럼 이제 이 open 함수를 어떤 식으로 사용하는지?

예제 코드를 보겠다.

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(){
	int fd;
	mode_t mode;
    mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
    fd=open("unix.txt",O_CREAT,mode);
    if(fd=-1){
		perror("create");
		exit(1);
	}
close(fd);
return 0;
}

 

그냥 간단히 unix.txt 라는 파일을 여는 것이다. 없으면 만들고.. 권한은 위에 보는것 같이 

mode = S_IRUSR | (사용자 계정에 read권한

            S_IWUSR | ( 사용자 계정에 write권한

            S_IRGRP | ( 그룹 READ권한

            S_IROTH | ( OTHER READ권한

 

or 연산자로 구분을 짓는다.

 

open함수의 두번째 인자에 O_CREAT를 주고 파일을 생성할 수도 있지만

그냥 creat() 라는 함수를 써서 파일을 생성하는 것도 가능하다.

 

creat() 함수

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

리턴값은 역시 마찬가지로​ 성공하면 파일 디스크립터 값, 실패하면 -1을 리턴한다.

 

솔직히 creat() 함수는 쓸 필요가 없다.

원래는 open() 함수에서 파일 생성 기능이 제공되지 않아서

creat로 만들고 close로 닫고 다시 open으로 열고 이 귀찮은 짓을 했어야 했다..

하지만 open에서 이제 파일 생성이 되니 굳이 creat를 쓸필요가 없다.

 

더군다나 creat함수의 큰 특징으로 파일을 항상 쓰기 전용 모드로 연다는 것.

그럼 이번엔 close함수가 나왔으니 이것도 한번 알아보자. 

 

close() 함수

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

 

열린 파일을 닫을 때 사용하는 녀석이다​.  파일을 닫는다.. 외에 뭘 설명할게 없다.

다만 파일을 열었으면 반드시 닫아줘야 한다는거~

 

 

이번에 알아볼 함수는 lseek()함수이다. 모든 열린 파일에는 파일 offset이 존재한다.

 

파일을 읽거나 쓰기 연산을 할 때 어디까지 수행되었는 지에 대한 정보가 있어야할 것이다.

시작 부분부터 얼마나 떨어져 있는지 (현재 수행된 작업이) 알기 위해 offset값이 존재하는 것임.

 

파일을 처음 열면 offset값은 0이다.

아까 언급한 open() 함수의 두번째 인자에 O_APPEND 를 줬다면 파일을 열어도 바로

offset은 파일의 마지막을 가르키고 있겠지? 즉 파일 크기 만큼.

 

read나 write 함수를 사용하면 이 offset이 지 알아서 왔다리 갔다리 할 텐데,

굳이 또 이걸 설정하고 싶으시다면 lseek 함수를 사용하면 된다.

 

lseek() 함수

#include <unistd.h>
off_t lseek(int fileds, off_t offset, int whencase);

lseek() 

리턴값은 성공하면 새로운 파일 offset, 실패하면 역시나 -1을 반환한다.

각 인자들의 내용을 보겠다.

int fileds

offset을 설정할 파일의 파일 디스크립터

 

off_t offset

설정할 offset값

 

int whencase

어떻게 offset값을 적용할 것인지에 대한 인자. whencase 필드에도 여러가지 값이 있다.

 

예제 코드

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
 if(lseek(STDIN_FILENO,0,SEEK_CUR)==-1)
  printf("error\n");
 else
  printf("ok\n");
 exit(0);
}

 

이건 lseek의 동작에 관련된 코드라고 딱 말하긴 그렇지만.. fd가 stdin 으로 되어 있으니 표준 입력이 들어가지 않으면 error가 나오는 코드이다.

 

다른 예제도 한번 보겠음.

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
char buf1[] ="abcdefghij";
char buf2[] ="ABCDEFGHIJ";
int main(){
int fd;
       if((fd=creat("file.hole",S_IRWXU))<0)
               puts("creat error");
       if(write(fd,buf1,10) !=10)
               puts("buf1 write fail");
       if(lseek(fd,16384,SEEK_SET) ==-1)
               puts("lseek error");
       if(write(fd,buf2,10)!=10)
               puts("buf2 write fail");
       exit(0);
       return 0;
)

 

file.hole 이라는 파일을 일단 생성해. 그리고 buf1에 있는 문자열을 write 하고, lseek함수를 이용하여 파일의 끝 -10 위치로 offset을 옮긴다. 그리고 거기에 다시 buf2에 있는 내용을 10바이트 만큼 write 하여 중간에 빵꾸?를 만드는 코드다.

파일의 끝은 아래에서 알 수 있다. 파일크기 만큼이 파일의 끝이다. (밑에 보면 16394 있음)

 

woodong@ubuntu:~/systemprogr$ gcc -o lseekfnc3 lseekfnc2.c
woodong@ubuntu:~/systemprogr$ vi lseekfnc2.c
woodong@ubuntu:~/systemprogr$ ./lseekfnc3
woodong@ubuntu:~/systemprogr$ ls -l file.hole
-rwx------ 1 woodong woodong 16394 Sep 12 04:16 file.hole

woodong@ubuntu:~/systemprogr$ od -c file.hole
0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0040000   A   B   C   D   E   F   G   H   I   J
0040012

여튼 lseek 함수는 이렇게 offset을 옮기는데 사용된다.

 

다음, 자료를 읽어오는 read 함수를 알아보자. 상기 lseek 예제에서도 나왔지요.

 

read() 함수

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);

리턴값은 성공했을 시 읽은 바이트수, 파일 끝을 읽을 경우 0,  실패했을 시 -1를 반환한다.

int fd : 파일 디스크립터

void *buf : 파일을 읽어 들일 버퍼

size_t nbytes : 몇 바이트를 읽어들일 것인가에 대한 내용

 

바로 write 함수에 대해 알아보자.

열린 파일에 자료를 기록할 때 사용하는 시스템콜 함수이다.

 

write() 함수

#include <unistd.h>
ssize_t write(int fd, void *buf, size_t nbytes);

 

리턴값은 성공했을 시 기록된 바이트수, 실패했을 시 -1를 반환한다.

int fd : 파일 디스크립터

void *buf : 파일에 기록할 내용을 담은 버퍼

size_t nbytes : 몇 바이트를 기록할 것인가에 대한 값

 

write가 실패할 경우가 있는데, 디스크에 남은 용량이 없거나 주어진 프로세스에 대한 파일 크기 상한선을 넘는 경우다.

 

read와 write 예제를 한번봅시다.

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#define BUFSIZE 8192
int main(){
        int n;
        char buf[BUFSIZE];
         while((n=read(STDIN_FILENO,buf,BUFSIZE))>0)
                 if(write(STDOUT_FILENO,buf,n)!=n)
                         printf("write error");
                 if(n<0)
                         printf("read error");
                 exit(0);
         }

 

표준 입력 받은 것을 read 버퍼에 저장하여 그녀석을 다시 표준 출력으로 write 하는 코드이다.

 

woodong@ubuntu:~/systemprogr$ cat unix.txt
adsfasdfsadf
adfsadf
woodong@ubuntu:~/systemprogr$ vi rwfunc.c
woodong@ubuntu:~/systemprogr$ cat unix.txt
adsfasdfsadf
adfsadf
woodong@ubuntu:~/systemprogr$ ./rwfunc <unix.txt >nex.txt
woodong@ubuntu:~/systemprogr$ cat nex.txt
adsfasdfsadf
adfsadf

unix.txt 를 stdin 으로 주고 stdout을 nex.txt 라는 파일로 줬다.

 

다음 파일 디스크립터를 복제하는 함수인 dup와 dup2를 알아보자.

 

dup(), dup2() 함수

#include <unistd.h>
int dup(int fd); 
int dup2(int fd1, int fd2);

리턴값은 둘다 성공 시에 새로운 파일 디스크립터값,

실패했을 시 -1을 반환한다.

 

dup함수는 복사하는 파일디스크립터 값을 지정할 수 있는 가장 작은 숫자부터 할당하여 복사한다.

dup2 같은 경우는 사용자가 fd2로 지정된 값이 새 파일 디스크립터 값이 되고,

fd2가 이미 열려있는 경우 그걸 닫고 복제한다.

 

파일 디스크립터 값으로 장난치는 함수

 

다음은 동기화 관련된 함수들이다. 

 

리눅스의 대부분 I/O 연산은 커널 안의 캐시를 거친다. 무슨말이냐면 프로세스가 파일에 자료를 입력하면 바로 디스크에 써지는게 아니라 커널에서 쓰는 일정한 버퍼에 저장해뒀다가 적당한 시점이 되면 디스크에 쓰기를 한다.

 

그래서 그걸 동기화(synchronization) 하는 함수들이 있다. sync, fsync, fdatasync 이다.

각자 미묘한 차이가 있지만 대동소이한 녀석들이다. 한번 알아보겠음

 

sync함수

인자가 void다. 딱봐도 파일을 지정하여 동기화 시키는게 아니라, 그냥 블록 버퍼에 있는 모든 내용을

동기화 시키겠다는 의지가 보인다. 대신 sync함수가 실행되었다고 바로 동기화가 끝이나는 것은 아니다.

기록이 끝날 때까지 기다리지 않음.

 

fsync함수

파일 디스크립터에 해당하는 파일에만 동기화가 적용된다. 이 함수는 쓰기가 완료될 때까지 기다렸다가 반환된다.

아마도 파일 하나니까 시간이 오래 안걸려서 그런 모양.

DB같이 변동 사항 기록이 중요한 프로그램을 위해서 구현했지 싶다.

 

fdatasync함수

fsync와 비슷한데, 파일에서 '자료 부분' 만 동기화 시킨다.

fsync는 파일의 속성까지 동기화 시키는 것과 반대로. 솔직히 별로 쓸모없다.

FreeBSD 5.2.1 과 Mac OS에선 지원 하지 않는다.

 

마지막으로...

 

fcntl 함수

이미 열려있는 파일의 특성을 변경할 수 있는 함수이다.

 

위와 같음. 여러가지 커맨드들을 가지고.. 명령에 따라 다른 리턴값을 갖는다.

F_DUPFD 만 설명을 하자면

fcntl(file_des, F_DUPFD, 0); 와 dup(file_des); 는 동일하다고 볼 수 있다.

실패하면 여전히 -1을 반환한다.

아래는 fcntl()에 관한 예제코드이다.

읽기 전용으로 test.sh를 open하고 

현재 파일 상태 플레그를 조회하여 표준출력하는 예제이다.

실행하면 아래와 같이 나온다.

반응형