9. 파일의 권한과 모드
리눅스는 다중 사용자 운영체제이며, 때문에 모든 파일에는 권한이 부여된다. 리눅스 상에서는 모든 것이 파일로 표현되기 때문에, 파일에 권한을 부여한다는 얘기는 운영체제와 컴퓨터의 모든 것에 대한 권한이 부여될 수 있다는 것과 마찬가지가 된다.
예컨데 권한이라 함은 '이 파일은 내 것이며 나만 읽을 수 있다' 라든지 '스터디 그룹에 포함된 사람들은 읽을 수 있지만, 다른 사람들은 읽을 수 없다' 등과 같은 접근 권한을 말한다. 여기에 접근 권한 외에도 읽기와 쓰기 가능에 대한 권한(접근 범위 - 모드)까지 세부적으로 분류할 수 있다. 현실에서와 마찬가지다. 현실에서도 직위나 직책, 부서에 따라서 문서에 대한 접근 권한이 정해져 있으며, 읽기와 쓰기에 대한 행위도 정의된다. 리눅스 운영체제의 파일은 읽기와 쓰기 외에도 실행에 대한 모드를 가지고 있다는게 현실세계에서의 문서시스템과 다른점이라 할 수 있을 것이다.
파일에 대한 권한은 소유자, 그룹, other 세 부분으로 나뉜다. 소유자는 개인이라고 생각할 수 있다. 파일에 대한 모드는 위에서 언급했듯이 읽기, 쓰기, 실행 3개로 세분화 될 수 있다. 이들의 조합으로 파일의 권한과 모드가 정의된다.
우리는 ls 를 통해서 파일의 권한과 모드를 확인할 수 있다.
# ls -al -rw-r--r-- 1 yundream yundream 4806656 2006-07-28 14:00 My_sweet_darlin.mp3 drwxr-xr-x 5 yundream yundream 4096 2007-07-29 01:26 PicasaDocuments -rwxr-xr-x 1 yundream yundream 7402 2007-11-26 00:01 UserInfoRead -rw-r--r-- 1 yundream yundream 751 2007-11-26 00:02 UserInfoRead.c -rwxr-xr-x 1 yundream yundream 7433 2007-11-25 23:33 UserInfoWrite -rw-r--r-- 1 yundream yundream 1087 2007-11-25 23:56 UserInfoWrite.c drwxr-xr-x 2 yundream yundream 4096 2007-07-31 23:51 backup
10. 파일의 종류와 권한, 모드 알아내기
모든 것이 파일로 표현될 수 있다는 점과 다중 사용자 운영체제라는 리눅스 운영체제의 특성상 파일의 종류와 권한,모드를 알아내는 것은 매우 중요하다. 파일을 다루는 프로그램을 작성할 경우 가장 먼저 하는 일이 접근 가능한 파일 인지를 확인하는 일이다. 리눅스는 파일에 대한 정보를 얻어올 수 있는 stat 라는 함수를 제공한다.
#include <sys/stat.h>
int stat(const char *file_name, struct stat *buf);
파일 이름 file_name 를 인자로 주면, 그에 대한 정보를 stat 구조체에 담아서 되돌려준다. stat 에는 다음과 같은 파일 정보들이 담겨져 있다.
#include <sys/stat.h>
int stat(const char *file_name, struct stat *buf);
파일 이름 file_name 를 인자로 주면, 그에 대한 정보를 stat 구조체에 담아서 되돌려준다. stat 에는 다음과 같은 파일 정보들이 담겨져 있다.
struct stat { dev_t st_dev; /* device */ ino_t st_ino; /* inode */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device type (if inode device) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last change */ };
주석을 보는 정도로 각 멤버 변수가 의미하는 바를 쉽게 이해할 수 있을 것이다. 그러니 몇 가지 생소한 멤버 변수들만 설명하도록 하겠다.
- st_ino : 파일의 일련번호다. 이 번호는 하나의 장치에서 유일하게 존재하며, 파일과 파일을 구분하게 해준다. 하나의 장치에서만 유일하다는 것에 주의하기 바란다.
- st_dev : 파일이 속한 장치의 식별번호다. st_ino 와 st_dev 의 쌍은 전체 시스템에서 유일하다.
- st_nlink : 파일의 hard link(이하 하드링크)의 갯수를 알려준다. 하드링크에 대한 내용은 따로 자세히 다루도록 하겠다.
- st_mode : 파일의 형식을 알려준다. 이 값을 이용해서, 파일이 디렉토리인지, 링크인지, 장치 파일인지 등을 알아낼 수 있다. 이 값을 분석하기 위한 다음과 같은 메크로를 제공한다. 각 메크로는 검사하고자 하는 내용이 참이면 0이 아닌 값을 리턴한다.
- S_ISDIR(st_mode) : 파일이 디렉토리인지 검사한다.
- S_ISCHR(st_mode) : 파일이 문자 장치 파일인지 검사한다.
- S_ISREG(st_mode) : 일반파일인지 검사한다.
- S_ISFIFO(st_mode) : FIFO 혹은 pipe 파일인지 검사한다.
- S_ISLNK(st_mode) : symbolic 링크 인지 검사한다.
- S_ISSOCK(st_mode) : 소켓 파일인지 검사한다.
다음은 파일의 각종 정보를 읽어오는 프로그램이다. 프로그램의 이름은 stat.c 로 하겠다.
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <pwd.h> #include <grp.h> int main(int argc, char **argv) { int return_stat; char *file_name; struct stat file_info; mode_t file_mode; if (argc != 2) { printf("Usage : ./file_info [file name]\n"); exit(0); }
file_name = argv[1]; if ((return_stat = stat(file_name, &file_info)) == -1) { perror("Error : "); exit(0); } file_mode = file_info.st_mode;
printf("파일이름 : %s\n", file_name); printf("=======================================\n"); printf("파일 타입 : "); if (S_ISREG(file_mode)) { printf("정규파일\n"); } else if (S_ISLNK(file_mode)) { printf("심볼릭 링크\n"); } else if (S_ISDIR(file_mode)) { printf("디렉토리\n"); } else if (S_ISCHR(file_mode)) { printf("문자 디바이스\n"); } else if (S_ISBLK(file_mode)) { printf("블럭 디바이스\n"); } else if (S_ISFIFO(file_mode)) { printf("FIFO\n"); } else if (S_ISSOCK(file_mode)) { printf("소켓\n"); } printf("OWNER : %d\n", file_info.st_uid); printf("GROUP : %d\n", file_info.st_gid); printf("dev : %d\n", file_info.st_dev); printf("inode : %d\n", file_info.st_ino); printf("FILE SIZE IS : %d\n", file_info.st_size); printf("마지막 읽은 시간 : %d\n", file_info.st_atime); printf("마지막 수정 시간 : %d\n", file_info.st_mtime); printf("하드링크 된 파일수 : %d\n", file_info.st_nlink); }
테스트 삼아서 stat.c 에 대한 조사를 해보자.
$ ./stat stat.c 파일이름 : stat.c ======================================= 파일 타입 : 정규파일 OWNER : 1000 GROUP : 1000 dev : 2051 inode : 6603353 FILE SIZE IS : 1692 마지막 읽은 시간 : 1196074251 마지막 수정 시간 : 1196074249 하드링크 된 파일수 : 2
10.1 hard link와 symbolic link
바로 위에서 link(이하 링크)가 몇 번 언급 되었었다. 이 링크에 대해서 자세히 알아보도록 하겠다. 링크는 파일을 가리키는 일종의 별칭으로 주로 관리의 목적으로 사용한다. 예를 들어 오픈 오피스의 문서 프로그램을 실행시키기 위해서 /usr/local/openoffice/bin/openoffice_swrite 를 실행시켜야 한다고 가정해보자. 이 것을 기억해서 실행하는 일은 보통 곤욕스러운 일이 아닐 것이다. 이 경우 링크를 이용해서 간단하게 문제를 해결할 수 있다.
# ln -s /usr/local/openoffice/bin/openoffice_swrite /usr/bin/swrite
/usr/bin 은 환경 변수 PATH 에 등록이 되어있을 것이기 때문에, 간단하게 swrite 만 입력하는 정도로 /usr/local/openoffice/bin/openoffice_swrite 를 실행할 수 있게 된다. 이제 ls를 이용해서 /usr/bin/swrite 의 정보를 확인해 보도록 하자.
$ ls -al swrite lrwxrwxrwx 1 root root 43 2007-11-26 23:44 swrite -> /usr/local/openoffice/bin/openoffice_swrite
swrite 가 원본 openoffice_swrite 를 링크하고 있는 것을 확인할 수 있을 것이다. 직관적으로 이해할 수 있을 것이다. 추가적으로 link 는 심볼릭 링크와 하드 링크로 나뉘는데 바로 다음에서 이 둘의 차이점에 대해서 알아보도록 하겠다.
10.2 hard link
앞서 파일은 장치 내에서 식별되기 위해서 inode(하나의 장치에서만 유일한 파일의 일련번호) 를 가진다는 것을 언급했었다. 여기에 inode 가 1234 인 파일 myfile 이 있다고 가정해보자. 이것을 다른 Directory 에 복사하기 위한 가장 일반적인 방법은 파일을 copy 하는 것으로 이 경우 새로운 inode 를 가지는 파일이 생길 것이다. 그럼 cp 를 이용해서 파일을 복사해보도록 하자.
# mkdir testdir # cp myfile testdir/myfile2이제 두 개 파일의 inode 를 확인해보자. stat 함수를 이용해서 프로그램을 만들 필요는 없다. ls 의 -i 옵션을 사용하면 간단하게 파일의 inode 값을 알아낼 수 있다.
# ls -i myfile 1131883 myfile # ls -i testdir/myfile2 1163816 testdir/myfile2내용은 동일하지만 완전히 다른 파일이 생성되었음을 알 수 있다.
이 방법은 대부분의 경우 유용하게 사용할 수 있겠지만 하나의 파일을 여러 개의 디렉토리에 공유할 목적으로 사용하고자 할 경우 문제가 발생한다. 예를 들어 주소록 파일인 /home/yundream/mydata.txt 가 있다고 가정해보자. 이 파일을 /home/dragona 에 공유하길 원한다. 만약 mydata.txt 에 새로운 내용이 추가되거나 삭제되면 /home/dragona 에도 그대로 적용되어야 한다. 단순히 copy 할 경우에는 한쪽에서 변경하면, 다른 한쪽에는 반영되지 않을 것이다. 링크를 사용하면 이 문제를 간단하게 해결할 수 있다.
# ln mydata.txt /home/dragona
이제 한쪽에서 파일을 수정해보자. 다른 쪽도 그대로 수정된 내용이 반영되어 있음을 확인할 수 있을 것이다. ls -i 로 확인해보면 두 개의 파일이 동일한 inode 를 가지고 있음을 확인할 수 있을 것이다. 이것을 링크라고 하며, 위에서와 같이 inode 를 공유해서 사용하는 것을 하드 링크 라고 한다. 이해하기 쉽게 그림으로 나타내보면 다음과 같다.
이러한 하드 링크로 얻을 수 있는 장점은 데이터를 공유할 수 있다는 것 외에, 디스크를 아낄 수 있다는 장점도 가진다. 데이터를 직접 복사하는 것이 아니기 때문이다. 원본은 하나이고 inode 만 공유할 뿐이다. 하드 링크를 하나 생성하면 inode 공유 카운터가 1증가할 뿐이다. ls -al 로 mydata.txt 원본 파일의 정보를 확인해 보자
# ls -al mydata.txt -rw-r----- 2 yundream yundream 192 2007-11-26 23:57 mydata.txt
하드 링크 카운터가 하나 증가해서 2가 되어 있는 것을 확인할 수 있을 것이다. 파일을 하나 지우고 나서 ls 결과를 보면 카운터가 하나 줄어서 1이 되는 것을 확인할 수 있을 것이다.
하드 링크를 사용할 때 주의해야 할 점이 있다. 하드 링크는 inode 를 가리킨다. 이 때, inode 는 하나의 장치에서만 유일하므로 다른 장치로의 하드링크는 불가능 하다는 점이다. 왜냐하면 다른 장치에서 유일하다는 것을 보장할 수 없기 때문이다. 이런 경우에는 심볼릭 링크를 사용해야 할 것이다.
11. 심볼릭 링크
하드 링크와 달리 심볼릭 링크는 별도의 inode 를 가지는 파일로 원본 파일에 대한 inode 와 함께 장치 정보까지 가지고 있다.(원본 파일의 포인터) 어떤 파일에 대한 inode 와 장치 정보를 알고 있다면, 전 시스템에서 유일한 파일을 가리킬 수 있기 때문에 장치에 관계없이 링크를 걸 수 있게 된다.
그럼 mydata.txt 를 원본 파일로 하는 심볼릭 링크 mydata2.txt 를 만들어 보도록 하자. ln 명령에 -s 옵션을 주면 심볼릭 링크를 생성할 수 있다.
이제 -i 옵션을 이용해서 두 개 파일의 inode 를 비교해 보면 서로 다른 별개의 inode 를 유지하고 있음을 알 수 있을 것이다. ls -l 을 이용해서 심볼릭 링크가 가리키는 원본 파일의 이름을 얻어올 수 있다.
# ln -s mydata.txt mydataln.txt
이제 -i 옵션을 이용해서 두 개 파일의 inode 를 비교해 보면 서로 다른 별개의 inode 를 유지하고 있음을 알 수 있을 것이다. ls -l 을 이용해서 심볼릭 링크가 가리키는 원본 파일의 이름을 얻어올 수 있다.
# ls -l mydataln.txt lrwxrwxrwx 1 yundream yundream 10 2007-11-28 01:49 mydataln.txt -> mydata.txt
12. 표준입력, 표준출력, 표준에러
프로그램은 어떤 값을 입력 받아서 처리하고 그 결과를 출력하는 일을 한다. 입력은 보통 키보드를 통해서 이루어지고 출력은 모니터를 통해서 이루어진다. 대부분의 프로그램이 이러한 입/출력 방식을 사용한다. 따라서 프로그램이 실행될 때에는 기본적으로 키보드 장치와 모니터 장치를 열어서 입/출력이 가능하게 해놓았다. 이렇게 키보드 장치를 통한 입력을 표준입력이라하고, 모니터를 통해 출력하는 것을 표준출력이라고 한다.
키보드를 통한 입력을 표준입력이라고 정의 하는 것은 문제가 없다. 그러나 모니터를 통한 표준출력에는 약간의 문제가 있다. 프로그램이 모니터에 출력하는 정보에는 입력 데이터를 정상적으로 처리해서 나오는 결과값 외에 잘못 처리되어서 출력되는 결과값이 있기 때문이다. 덧셈 프로그램을 만들었는데, 피연산자에 숫자 대신 알파벳 문자 등을 넣었다면, 프로그램은 에러 메시지를 출력할 것이다. 그런데 똑같이 모니터에 출력이 되어버리면, 결과값이 에러인지 아닌지 구분할 수 없을 것이다.
이렇게 출력값이 정상인지 에러인지를 구분하기 위해서 표준출력 외에 표준에러를 제공한다. 결과적으로 프로그램은 최초 실행시 다음과 같은 3개의 입/출력 장치를 open 하게 된다.
우리는 리눅스가 모든 것을 파일로 처리한다는 것을 배워서 알고 있다. 표준입력, 출력, 에러 역시 파일로 처리된다. 더불어 리눅스에서 파일을 다룰때에는 파일 이름이 아닌, 파일 지정 번호를 이용한다는 것도 알고 있다. 리눅스는 이들 3개의 파일에 대해서는 파일 지정 번호를 고정시켜 놓았다.
다음은 표준입력, 표준출력, 표준에러를 이용해서 만든 간단한 나눗셈 프로그램이다. 표준입력을 통해서 2개의 수를 입력받아서 나눈 결과를 표준출력을 통해서 출력한다. 이 프로그램의 이름은 stdio.c 로 하겠다.
표준출력과 표준입력을 어떻게 사용하는지 이해하는 것은 어렵지 않을 것이다. 그러나 표준에러를 어떻게 사용해야 할지는 감이오지 않을 것이다. 걱정할 필요 없다. 아래의 재지향을 공부하다 보면, 자연스럽게 감이 오게 될 것이다.
- 표준입력 : 키보드를 통한 입력
- 표준출력 : 모니터로 출력되는 정상 메시지
- 표준에러 : 모니터로 출력되는 에러 메시지
우리는 리눅스가 모든 것을 파일로 처리한다는 것을 배워서 알고 있다. 표준입력, 출력, 에러 역시 파일로 처리된다. 더불어 리눅스에서 파일을 다룰때에는 파일 이름이 아닌, 파일 지정 번호를 이용한다는 것도 알고 있다. 리눅스는 이들 3개의 파일에 대해서는 파일 지정 번호를 고정시켜 놓았다.
- 표준입력 : 0
- 표준출력 : 1
- 표준에러 : 2
다음은 표준입력, 표준출력, 표준에러를 이용해서 만든 간단한 나눗셈 프로그램이다. 표준입력을 통해서 2개의 수를 입력받아서 나눈 결과를 표준출력을 통해서 출력한다. 이 프로그램의 이름은 stdio.c 로 하겠다.
#include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #define STDIN 0 #define STDOUT 1 #define STDERR 2 #define ERRMSG "Devide Not Zero\n" int main() { int a; int b; char buf[80]; read(STDIN, buf, 80); a = atoi(buf); read(STDIN, buf, 80); b = atoi(buf); if (b == 0) { write(STDERR, ERRMSG, strlen(ERRMSG)); return 1; } sprintf(buf, "%d / %d = %d\n", a, b, (int)(a/b)); write(STDOUT, buf, strlen(buf)); return 0; }
- 18 표준입력으로 분자를 입력받는다. 키보드로 부터 입력받는 값은 문자열로 atoi(3) 함수를 이용해서 int형 값으로 변경했다.
- 21 표준입력으로 분모를 입력받는다.
- 23 ~ 27 나눗셈에서는 분모가 0이 되는걸 허용하지 않고 있다. 그러나 실수로 0을 입력할 수 있으므로, 입력값을 검사해서 0이면 에러메시지를 출력하고 종료하도록 하고 있다. 이때 결과값은 에러메시지 이므로 표준에러를 통해서 출력했다.
- 31 나눈 결과값을 표준출력을 이용해서 모니터에 출력한다.
표준출력과 표준입력을 어떻게 사용하는지 이해하는 것은 어렵지 않을 것이다. 그러나 표준에러를 어떻게 사용해야 할지는 감이오지 않을 것이다. 걱정할 필요 없다. 아래의 재지향을 공부하다 보면, 자연스럽게 감이 오게 될 것이다.
13. 입출력 재지향
입/출력 재지향 혹은 I/O Redirection 에 대해서 알아보자. 일단 재지향의 의미에 대해서 알고 넘어갈 필요가 있을 것 같다. 재지향의 사전적 의미는 '다른 방향으로 보낸다' 이다. 여기에 맞추어 입/출력 재지향을 사전적 의미 그대로 해석하자면, 입력과 출력을 다른 방향으로 보낸다는 의미가 될 것이다. 실제 입/출력 재지향은 사전적의미 그대로 이해하면 된다.
리눅스에서 모든 것은 파일로 다루어진다고 했다. 이는 입력과 출력에도 예외없이 적용이 되므로, 입력과 출력을 다른 방향으로 보낸다는 것은 입력과 출력을 다른 파일로 보낸다는 것과 같은 의미이다. 예컨데, 키보드로부터 입력 받은 표준입력 데이터를 일반 파일로 보내거나 프린터(파일)로 보내는 등의 일이 가능하다는 얘기가 된다. 일반 파일을 프린터로 보내거나, 표준출력을 표준에러로 보내는 등의 일 역시 가능하다. 모든 것이 파일이기 때문에 모든 방향으로의 재지향이 가능하다.
stdio.c 파일을 예로 들어서 설명해보도록 하겠다. stdio.c 프로그램은 입력을 검사해서 분모가 0 이 되면, 에러 메시지를 출력하도록 했다. 이 에러 메시지는 표준에러 형태로 모니터에 출력된다. 입력이 제대로 이루어져서 결과값이 나올 경우에는 표준출력 형태로 모니터에 출력이 된다. 이를 파일로 재지향 해보도록 하자.
쉘에서는 꺽쇠를 이용해서 재지향을 이용할 수 있다. 표준출력을 result.txt 파일로 재지향하고 결과를 확인해 보도록 하자.
# ./stdio > result.txt 1234 2 # cat result.txt 1234 / 2 = 617
프로그램을 만들어서 테스트 할 경우 디버깅 등의 목적으로 에러 메시지를 파일로 따로 저장해둬야 할 필요가 생긴다. 그렇다면 stdio 의 표준에러를 파일로 재지향 시키면 될 것이다.
표준에러를 표준출력으로 재지향 시킬 수도 있다.
이제 표준에러도 표준출력 형태로 모니터에 뿌려지게 된다. 표준에러를 표준출력으로 재지향 시키는 예는 grep 등을 이용해서 결과를 모니터링 하는 스크립트를 만들기 위해서 자주 이용된다. 아래의 경우를 보도록 하자.
위의 스크립트는 stdio 의 실행결과 중 분모가 0인 경우를 err.log 로 남기기 위한 목적으로 작성되었다. 그렇지만 예상과는 다르게 분모가 0인 경우도 err.log 에 남겨지지 않을 것이다. 왜냐하면 파이프 | 는 표준출력 결과만을 grep 로 넘기는데 Device Not Zero는 표준에러이므로 파이프를 통해서 grep로 넘어가지 않기 때문이다.
# ./stdio 2> err.txt 1000 0 # cat err.txt Devide Not Zero
표준에러를 표준출력으로 재지향 시킬 수도 있다.
# ./stdio 2>1&
이제 표준에러도 표준출력 형태로 모니터에 뿌려지게 된다. 표준에러를 표준출력으로 재지향 시키는 예는 grep 등을 이용해서 결과를 모니터링 하는 스크립트를 만들기 위해서 자주 이용된다. 아래의 경우를 보도록 하자.
# ./stdio | grep Not > err.log
위의 스크립트는 stdio 의 실행결과 중 분모가 0인 경우를 err.log 로 남기기 위한 목적으로 작성되었다. 그렇지만 예상과는 다르게 분모가 0인 경우도 err.log 에 남겨지지 않을 것이다. 왜냐하면 파이프 | 는 표준출력 결과만을 grep 로 넘기는데 Device Not Zero는 표준에러이므로 파이프를 통해서 grep로 넘어가지 않기 때문이다.
이때 표준에러를 표준출력으로 재지향 시키는 방법으로 문제를 해결할 수 있다. 위의 스크립트를 아래와 같이 수정한다음에 테스트해보도록 하자.
표준출력결과가 파일로 저장된걸 확인할 수 있을 것이다.
# ./stdio 2>&1 | grep Not > err.log 1000 0 # cat err.log Devide Not Zero
표준출력결과가 파일로 저장된걸 확인할 수 있을 것이다.
이론적으로는 파일에 저장된 내용을 각 장치에 재지향 시키는 것 만으로도 해당 장치를 이용할 수 있다. 예를 들자면 wav 파일을 읽어서 사운드 카드를 가리키는 장치 파일에 재지향 시켜서 wav 파일을 플레이 하는 것이다.
# cat sound.wav > /dev/audio
재지향의 개념에 대해서 알아보았는데, 정작 중요한 것은 재지향이 시스템 프로그래밍의 관점에서 어떻게 구현이 되는가 하는 것이다. 간단히 생각해 보자면, 두 개의 파일을 연 다음에 하나의 파일의 내용을 읽어서 다른 파일로 복사하면 된다. 표준출력을 파일로 재지향한다면, 표준출력과 파일을 연 다음에 표준출력의 내용을 읽어서 파일에 그대로 쓰는 형식이다.
그러나 이 방식은 매우 복잡하다. 이보다는 dup2 함수를 이용해서 좀 더 간단하게 재지향을 구현할 수 있다. 아래의 코드를 컴파일 한 다음 실행시켜 보도록 하자.
이 정도면 재지향의 구현 개념에 대해서 정리가 되었으리라 생각된다. dup 와 dup2 는 프로세스간 입/출력을 공유하기 위한 용도로 나중에 자세히 언급이 될 것이다. 우선은 이런 함수가 있다는 정도만 이해하고 넘어가도록 하자.
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #define STDIN 0 #define STDOUT 1 #define STDERR 2 int main(int argc, char **argv) { int fd; fd = open("test.log", O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); fd = dup2(fd, STDOUT); printf("Hello World %d\n", fd); }
- 15 : 재지향할 파일로 test.log 를 오픈했다. fd 는 아마도 3일 것이다.
- 16 : dup2 함수를 이용해서 fd를 STDOUT 로 복사했다.
- 17 : printf 함수를 이용해서 표준출력 했지만 STDOUT 는 test.log 의 파일 지정자로 복사가 되었기 때문에, 모니터로 출력이 되는 것이 아니고 파일로 출력이 된다.
이 정도면 재지향의 구현 개념에 대해서 정리가 되었으리라 생각된다. dup 와 dup2 는 프로세스간 입/출력을 공유하기 위한 용도로 나중에 자세히 언급이 될 것이다. 우선은 이런 함수가 있다는 정도만 이해하고 넘어가도록 하자.
14. 입출력 버퍼 비우기
일반적으로 버퍼는 성능을 높이기 위한 목적으로 사용한다. 예컨데, 1byte 씩 2048번 쓰는 것 보다는 , 1024byte씩 2번 쓰는게 훨씬 효율적일 것이다. 일반 응용 차원에서 뿐만 아니라, 운영체제 차원에서도 이러한 규칙은 동일하게 적용된다. 이에 따라 입력과 출력에 대해서도 별도의 버퍼를 유지하게 된다. 예를 들어 write 를 이용해서 화면에 데이터를 출력한다고 하면, 1byte 씩 쓸 때마다 출력되는 것이 아니고, 버퍼에 쌓아두고 있다가 버퍼가 가득 찼을 때 출력을 하게 된다.
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { int i = 1; while(1) { printf("%d",i); usleep(100); } }
위 코드를 실행시켜 보기 바란다. 버퍼가 가득차기 전까지는 화면에 출력시키지 않는 것을 확인할 수 있을 것이다. 대개의 경우 버퍼의 크기는 1024이므로, 1024개의 문자가 기록되었을 때, 한번에 화면에 출력되는 것을 확인할 수 있을 것이다.
그러나 때때로, 곧바로 버퍼가 채워지기 전에 버퍼의 내용을 파일에 쓰고, 버퍼를 비울 필요가 있을 것이다. 만약 버퍼가 다 채워지지 않아서 파일에 쓰지 않은 상태에서 프로그램이 종료되어 버린다면, 버퍼에 있는 내용은 날아가 버릴 것이다. 위의 프로그램을 중간에 Ctrl+C 를 눌러서 종료시켜 보기 바란다. 버퍼의 내용이 버려짐을 확인할 수 있을 것이다. 이때 쓰는 함수가 fflush() 로, 이 함수를 호출하게 되면, 버퍼의 내용을 즉시 파일에 쓰게 된다.
위 코드를 아래와 같이 수정해보자.
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { int i = 1; while(1) { printf("%d",i); fflush(stdout); usleep(100); } }
이제 매번 버퍼에 있는 내용을 파일(여기에서는 모니터)에 쓰는 것을 확인할 수 있을 것이다.
15. 사용한 함수들 정리
- open(2) : 파일을 연다.
- write(2) : 파일을 쓴다.
- read(2) : 파일의 내용을 읽는다.
- close(2) : 열린 파일을 닫는다.
- printf(3) : 문자열을 화면에 표준출력 한다.
- dup2(2) : 파일지정번호를 복사한다.
- perror(3) : 에러메시지를 표준에러로 출력한다.