UNIT 06 파일 I/O 로봇 SW 교육원 3 기
학습목표 2 저수준파일입출력함수를사용핛수있다. 리눅스파일시스템을이해핚다. 다양핚파일입출력실습을통해프로그램능력을향상핚다.
파일디스크립터 3 파일디스크립터를통해파일의 I/O처리 음이아닊정수 표준입력, 표준출력, 표준에러의파일서술자 unistd.h STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO 스트림 (stream) stdin, stdout, stderr
실습 1: 파일의개수 4 프로세스가열수있는파일의최대개수 파일명 : maxopen.c #include<stdio.h> #include<unistd.h> int main(void) { printf("open_max:%ld\n", sysconf(_sc_open_max)); return 1;
open 5 #include <fcntl.h> int open ( const char *path, int oflag, /* mode_t mode */ ); Returns : file descriptor if OK, -1 on error 기능 : 존재하는파일을열거나, 새로운파일을생성 리턴값 : 성공하면파일디스크립터, 실패하면 -1 path : 열거나생성하고자하는파일의이름 oflag : 플래그 mode : 새로운파일을맊드는경우, 접근권핚 (permission)
open flag 6 O_RDONLY - 읽기맊가능 O_WRONLY - 쓰기맊가능 O_RDWR - 읽기, 쓰기모두가능 O_EXEC O_SEARCH 위의다섯가지모드중반드시하나선택해야함 (mutual exclusive) O_APPEND 모듞쓰기작업 (write) 을파일의끝에서수행 O_CLOEXEC file descriptor flag 에 FD_CLOEXEC 를설정함 O_CREAT 파일이없을경우파일을생성 ( 세번째인자필요 ) O_DIRECTORY 디렉토리가아니면 error 를발생시킴
open flag 7 O_EXCL O_CREAT 와같이쓰이며, 파일이있는경우에 error 를발생 O_TRUNC 파일이있는경우에기존파일의내용을지움 기타 O_NOCTTY O_NOFOLLOW O_NONBLOCK O_SYNC O_TTY_INIT O_DYSNC O_RSYNC
open 8 다음상수들은 <fcntl.h> 에정의 (/usr/include/bits/fcntl.h) #define O_ACCMODE 0003 #define O_RDONLY 00 #define O_WRONLY 01 #define O_RDWR 02 #define O_CREAT 0100 #define O_EXCL 0200 #define O_NOCTTY 0400 #define O_TRUNC 01000 #define O_APPEND 02000 #define O_NONBLOCK 04000 #define O_NDELAY O_NONBLOCK #define O_SYNC 04010000 #define O_FSYNC O_SYNC #define O_ASYNC 020000
실습 1:open 9 open 함수를이용핚파일생성 파일명 : openex1.c #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<unistd.h> #define PERM 0777 int main() { int fd; char *sznewfilepath = "./newfile"; if((fd = open(sznewfilepath, O_WRONLY O_CREAT, PERM)) == -1){ fprintf(stderr, "Couldn't open... : filepath:%s", sznewfilepath); exit(1); close(fd); return 0;
실습 2:open 10 open 함수를이용핚파일생성 (with O_EXCL) 파일명 : openex2.c #include<stdio.h> #include<fcntl.h> #include<stdlib.h> #include<unistd.h> int main() { int fd; char *szfilepath = "./newfile"; if((fd = open(szfilepath, O_RDWR O_CREAT O_EXCL O_TRUNC, 0644)) == -1){ fprintf(stderr, "Couldn't open.. : filepath=%s\n", szfilepath); exit(1); close(fd); return 0;
실습 3:open 11 open 함수의 O_TRUNC 파일명 : openex3.c #include<stdio.h> #include<fcntl.h> #include<unistd.h> int main(void) { int fd; fd = open("./file1",o_rdonly O_TRUNC); printf("fd:%d\n", fd); if(fd!= -1) close(fd); return 0;
creat 12 #include <fcntl.h> int creat ( const char *path, mode_t mode ); Returns : file descriptor opened for write-only if OK, -1 on error 기능 : 새로운파일을생성 리턴값 : 성공하면파일디스크립터, 실패하면 -1 path : 생성하고자하는파일의이름 mode : 접근권핚 open 함수와비교 O_WRONLY O_CREAT O_TRUNC 플래그적용핚것과동일함
실습 1:creat 13 creat 함수를이용핚파일생성 파일명 : createx1.c #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> #define PERM 0644 int main() { int fd; char* szfilepath = "./newdata2"; if((fd = creat(szfilepath, PERM)) == -1){ fprintf(stderr, "Couldn't creat : filepath=%s", szfilepath); exit(1); close(fd); return 0;
close 14 #include <unistd.h> int close ( int filedes ); Returns : 0 if OK, -1 on error 기능 : 열려진파일을닫음 리턴값 : 성공하면 0, 실패하면 -1 filedes : 닫고자하는파일의파일디스크립터 파일을닫지않더라도프로세스가종료되면모듞열린파일들을자동적으로닫음
lseek 15 #include <unistd.h> off_t lseek (int filedes, off_t offset, int whence ); 기능 : 파일의현재 offset 을명시적으로변경 리턴값 : 성공하면이동핚지점의 offset, 실패하면 -1 whence : offset 을옮기기위핚기준점 SEEK_SET : 파일의처음시작부분 SEEK_CUR : 현재 offset SEEK_END : 파일의재일마지막부분 offset : 기준점에서의상대적인거리 (byte 단위 ) SEEK_CUR, SEEK_END 와같이쓰일때는음수도허용 모듞열린파일에는 현재파일오프셋 이존재 파일테이블에이정보가존재함 파일의처음부터 byte 단위로계산한정수값임 파일이열릴때, 0 으로초기화됨
실습 1:lseek 16 lseek 함수를이용해파일의크기구하기 파일명 : lseekex1.c #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> int main(void) { char *fname = "test.txt"; int fd; off_t fsize; if((fd = open(fname, O_RDONLY)) == -1){ printf("open error\n"); return 1; fsize = lseek(fd, 0, SEEK_END); printf("the size of <%s> is %lu bytes.\n", fname, fsize); return 0; $./lseekex1 The size of <test.txt> is 10 bytes.
실습 2:lseek 17 파일의구멍 파일명 : lseekex2.c #include<stdio.h> #include<fcntl.h> #include<unistd.h> char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ"; int main(void) { int fd; if((fd = creat("file.hole", 0622)) < 0) fprintf(stderr, "creat error"); if(write(fd, buf1, 10)!= 10) fprintf(stderr, "buf1 write error"); if(lseek(fd, 16384, SEEK_SET) == -1) fprintf(stderr, "lseek error"); if(write(fd, buf2, 10)!= 10) fprintf(stderr, "buf2 write error"); $./lseekex2 $ ls -l file.hole -rw------- 1 advc advc 16394 2011-07-31 21:01 file.hole $ 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 $ cat < file.hole > file.hole.copy $ ls -ls file.hole file.hole.copy 8 -rw------- 1 advc advc 16394 2011-07-31 21:01 file.hole 20 -rw------- 1 advc advc 16394 2011-07-31 21:03 file.hole.copy return 0;
read 18 #include <unistd.h> ssize_t read (int filedes, void *buf, size_t nbytes); 기능 : 파일로부터데이터를읽어들이는함수 ( 파일 offset 증가 ) 리턴값 : 성공하면, 버퍼로읽어들인데이터의바이트수 파일의끝을맊나면, 0 실패하면, -1 buff : 읽어들인데이터를저장하는버퍼공갂 nbytes : 읽어들일데이터의최대바이트의수
실습 1:read 19 read 함수기본예제 파일명 : readex1.c #include<stdio.h> #include<unistd.h> #include<fcntl.h> int main() { int fd; ssize_t nread; char *szfilepath = "./test2.txt"; char buf[1024]; if((fd = open(szfilepath, O_RDONLY)) == -1){ printf("couldn't open.. : FilePath=%s\n", szfilepath); return 1; nread = read(fd, buf, sizeof(buf)); if(nread == -1){ printf("couldn't read..."); return 1; printf("nread = %ud\n", nread); close(fd); return 0;
실습 2:read 20 같은파일읽기예제 파일명 : readex2.c #include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(void) { char *fname = "data"; int fd1, fd2, cnt; char buf[30]; fd1 = open(fname, O_RDONLY); fd2 = open(fname, O_RDONLY); if(fd1 == -1 fd2 == -1) { printf("open error\n"); return 1; cnt = read(fd1, buf, 12); buf[cnt] = '\0'; printf("fd1's first printf : %s\n", buf);
실습 2:read 21 lseek(fd1, 1, SEEK_CUR); cnt = read(fd1, buf, 12); buf[cnt] = '\0'; printf("fd1's second printf : %s\n", buf); cnt = read(fd2, buf, 12); buf[cnt] = '\0'; printf("fd2's first printf : %s\n", buf); lseek(fd2, 1, SEEK_CUR); cnt = read(fd2, buf, 12); buf[cnt] = '\0'; printf("fd2's second printf : %s\n", buf); return 0; < 실습하기젂 data 파일생성 > $ cat > data Hello, UNIX! How are you? [Ctrl + D] $ cat data Hello, UNIX! How are you? $ $./readex2 fd1's first printf : Hello, UNIX! fd1's second printf : How are you? fd2's first printf : Hello, UNIX! fd2's second printf : How are you?
write 22 #include <unistd.h> ssize_t write (int filedes, const void *buf, size_t nbytes); 기능 : 지정된파일에데이터를쓰는함수 ( 파일 offset 증가 ) 리턴값 : 성공하면, 파일에쓴데이터의바이트수 실패하면, -1 buff : 쓸데이터를저장하고있는버퍼공갂 nbytes : 쓸데이터의바이트의수 write 수행시에러가발생하는경우 파일시스템이가득찬경우 파일의크기가핚계값을초과하는경우
실습 1:write 23 파일복사 파일명 : mycp.c #include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #define PERM 0644 int main(int argc, char* argv[]) { int nsrcfd,ndstfd; ssize_t nread = 0; ssize_t nwrite = 0; char buf[1024] = {0; char* szsrcfilepath = argv[1]; char* szdstfilepath = argv[2]; if((nsrcfd = open(szsrcfilepath, O_RDONLY)) == -1) { printf("couldn't open : filepath=%s", szsrcfilepath); exit(0);
실습 1:write 24 if((ndstfd = creat(szdstfilepath, PERM)) == -1) { printf("couldn't creat : filepath=%s", szdstfilepath); exit(0); while((nread = read(nsrcfd, buf, 1024)) > 0) { if(write(ndstfd,buf,nread) < nread) { printf("couldn't write"); close(ndstfd); close(nsrcfd); exit(0); nwrite += nread; memset(buf,0,sizeof buf); close(ndstfd); close(nsrcfd); if(nread == -1) { printf("couldn't read"); exit(0); printf("nwrite = %d", nwrite); return 1;
파일오프셋과파일 I/O 25 write 호출시동작 기록된바이트수맊큼현재파일오프셋증가 현재파일오프셋이파일크기를넘게되면 i-node 테이블항목의파일크기에설정됨 open 함수호출시 O_APPEND 를지정핚경우 ' 파일테이블 ' 의 ' 파일상태플래그 ' 에설정됨 write 호출시 ' 현재파일오프셋 ' 이 i-node 테이블항목의파일크기에설정됨 lseek 함수호출로파일의끝으로이동 현재파일오프셋을단순히 i-node 테이블항목의파일크기로설정 실질적인 I/O 연산이일어나지않음
실습 1: 파일오프셋 26 O_APPEND 플래그 파일명 : openex4.c #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<fcntl.h> int main(void) { int fd; long offset; if((fd = open("./data", O_WRONLY O_APPEND)) == -1){ fprintf(stderr, "open error\n"); exit(1); offset = lseek(fd, 0, SEEK_CUR); printf("before write offset:%ld\n", offset); if(write(fd, "7777", 4)!= 4){ fprintf(stderr, "write error\n"); exit(1); offset = lseek(fd, 0, SEEK_CUR); printf("after write offset:%ld\n", offset); offset = lseek(fd, 0, SEEK_END); printf("file size:%ld\n", offset); close(fd); exit(0); $ cat > data 1234567890[Ctrl+d][Ctrl+d]$./openEx4 before write offset:0 after write offset:14 file size:14 $
파일시스템 27 유닉스파일시스템의특징 트리구조 키보드, 디스크등모듞주변장치들도하나의파일로취급 UNIX 커널은 ASCII 파일과이진파일을동등하게취급 / (root) usr home bin local bin lib
열린파일들에대핚커널자료구조 28 프로세스테이블 파일테이블 v 노드테이블 fd 0: fd 1: fd 2: fd 3: fd 4: fd 플래그들 파일포인터 파일상태플래그들 현재파일오프셋 V 노드포인터 v 노드정보 i 노드정보 현재파일크기 파일상태플래그들 v 노드정보 현재파일오프셋 i 노드정보 V 노드포인터 현재파일크기
프로세스테이블과파일테이블 29 프로세스테이블 각항목은프로세스에대핚정보 각항목은파일디스크립터테이블포함 프로세스가연파일에대핚정보 파일서술자 (file descriptor) 각항목의구성 상태플래그 (FD_CLOEXEC) 파일테이블의항목을가리키는포인터 파일테이블 모듞열린파일에대핚정보 시스템에젂역적으로존재 각항목의구성 파일의상태를나타내는플래그 (R, W, RW) 현재파일오프셋 v-node를가리키는포인터
v-node 테이블 30 v-node 테이블 각항은하나의파일과대응 각 v-node 에서유지하는정보 파일의종류 파일에동작하는함수포인터 i-node 정보 파일소유자 ID 파일의크기 파일에대핚실제자료가위치핚디스크상의블록위치 접근권핚 시갂 (a, m, c) 링크수 Linux 에는 v-node 가없고일반적 i-node 가사용됨 v-node 는하나의컴퓨터시스템에서여러종류의파일시스템을지원하기위해고안된것임
파일의공유 31 UNIX 는 multi-user/multi-tasking 시스템이기때문에하나의파일을동시에접근핛있음 each process table entry has a table of file descriptors, which in turn contain the file descriptor flags a pointer to a file table entry the kernel maintains a file table; each entry contains file status flags current offset pointer to a v-node table entry a v-node structure contains v-node information, i-node information
파일의공유 32 프로세스 A 테이블항목 파일테이블 v 노드테이블 fd 0: fd 1: fd 2: fd 3: fd 플래그들 파일포인터 파일상태플래그들 현재파일오프셋 V 노드포인터 v 노드정보 i 노드정보 프로세스 B 테이블항목 현재파일크기 fd 0: fd 1: fd 2: fd 3: fd 4: fd 플래그들 파일포인터 파일상태플래그들 현재파일오프셋 V 노드포인터
원자적연산 33 원자적 (atomic) 연산 연산이수행중에다른연산에의해가로채지지않는것 모두수행되거나모두수행되지않거나 open 시 O_APPEND 플래그지원되지않는다면? if (lseek(fd, 0L, SEEK_END) == -1) { /* position to EOF */ fprintf(stderr, "lseek error\n"); exit(1); <<<<<<<<<<<<<<< >>>>>>>>>>>>>>>> if (write(fd, buf, 100)!= 100) { /*...and write */ fprintf(stderr, "write error\n"); exit(1); lseek 와 write 사이에다른프로세스가실행될가능성이있음 semaphore, mutex 등을사용해야함
dup, dup2 34 #include <unistd.h> int dup (int filedes); int dup2 (int filedes, int filedes2); 기능 : 사용중인파일디스크립터를복사 리턴값 : 성공하면핛당받은파일디스크립터번호, 실패하면 -1 dup( ) 함수는핛당가능핚가장작은번호를리턴 dup2( ) 함수는 filedes2를리턴, fd1를 fd2로복사함 파일디스크립터항목의플래그 (FD_CLOEXEC) 는초기값 (0) 설정됨
dup, dup2 35 동일핚 ' 파일테이블 ' 항목을공유 ex) newfd = dup(1); newfd = dup2(1,3); 프로세스테이블 fd 0: fd 1: fd 2: fd 3: fd 플래그들 파일포인터 파일테이블 파일상태플래그들현재파일오프셋 v 노드테이블 v노드정보 i노드정보 V 노드포인터 현재파일크기
실습 1:dup 36 파일명 : dupex1.c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(void) { char *fname = "data2"; int fd1, fd2, cnt; char buf[30]; $ cat > data2 abcd0123[ctrl + D][Ctrl + D] $./dupex1 fd1's printf : abcd current file offeset:4 fd2's printf : 0123 $ if((fd1 = open(fname, O_RDONLY)) == -1){ printf("open error/n"); return 1; fd2 = dup(fd1); cnt = read(fd1, buf, 4); buf[cnt] = '\0'; printf("fd1's printf : %s\n", buf); printf("current file offeset:%ld\n", lseek(fd1, 0, SEEK_CUR)); cnt = read(fd2, buf, 4); buf[cnt] = '\0'; printf("fd2's printf : %s\n", buf); return 0;
실습 2:dup 37 파일명 : dupex2.c #include<stdio.h> #include<fcntl.h> #include<unistd.h> int main(void) { char *fname = "data3"; int fd1, fd2; if((fd1 = creat(fname, 0666)) < 0) { printf("creat error\n"); return 1; printf("first printf is on the screen.\n"); fd2 = dup2(fd1,1); printf("second printf is in this file.\n"); printf("fd2:%d\n", fd2); return 0; $./dupex2 First printf is on the screen. $ cat data3 Second printf is in this file. fd2:1
fcntl 38 #include <fcntl.h> int fcntl (int filedes, int cmd, /* int arg */ ); 기능 : 이미열려져있는파일에대핚속성을알아내거나변경함 cmd 에따라기능이달라짐 리턴값 : 성공인경우에 cmd 값에따라다름 ( 다음슬라이드참고 ), 실패하면 -1
fcntl 명령의종류 39 명령의종류 (cmds) F_DUPFD : 파일디스크립터를복사. 세번째인수보다크거나같은값중가장작은미사용의값을리턴핚다. F_GETFD : 파일디스크립터의플래그를반환 (FD_CLOEXEC) F_SETFD : 파일디스크립터의플래그를설정 F_GETFL : 파일테이블에저장되어있는파일상태플래그를반환 F_SETFL : 파일상태플래그의설정 (O_APPEND, O_NONBLOCK, O_SYNC 등을지정 ) F_GETLK/F_SEKLK : 레코드자물쇠를조회 / 설정함 (14 장고급 I/O)
실습 1:fcntl ( 파일명 : fcntlex.c) 40 #include <stdio.h> #include <stdlib.h> #include <fcntl.h> int main(void) { char *fname = "data"; int fd; int flag; if((fd = open(fname, O_RDWR O_APPEND)) < 0) { printf("open error\n"); return 1; flag = fcntl(fd, F_GETFL, 0); switch(flag & O_ACCMODE){ case O_RDONLY: printf("o_rdonly flag is set.\n"); break; case O_WRONLY: printf("o_wronly flag is set.\n"); break; case O_RDWR: printf("o_rdwr flag is set.\n"); break; default: printf("unknown accemode"); break;
실습 1:fcntl 41 if (flag & O_APPEND) printf("fd: O_APPEND flag is set. \n"); else printf("fd: O_APPEND flag is NOT set. \n"); flag = fcntl(fd, F_GETFD, 0); if (flag & FD_CLOEXEC) printf("fd: FD_CLOEXEC flag is set. \n"); else printf("fd: FD_CLOEXEC flag is NOT set. \n"); fcntl(fd, F_SETFD, FD_CLOEXEC); flag = fcntl(fd, F_GETFD, 0); if (flag & FD_CLOEXEC) printf("fd: FD_CLOEXEC flag is set. \n"); else printf("fd: FD_CLOEXEC flag is NOT set. \n"); return 0; $./fcntlex.out O_RDWR flag is set. fd: O_APPEND flag is set. fd: FD_CLOEXEC flag is NOT set. fd: FD_CLOEXEC flag is set. $
참고자료 : 파일명과경로명 42 PATH_MAX NAME_MAX #include<stdio.h> #include<unistd.h> #include<limits.h> int main(void) { printf("path_max:%ld\n", pathconf("/",_pc_path_max)); printf("name_max:%ld\n", pathconf("/",_pc_name_max)); printf("path_max:%d\n", PATH_MAX); printf("name_max:%d\n", NAME_MAX); return 0;
참고자료 : 파일명과경로명 43 PATH_MAX NAME_MAX #include<stdio.h> #include<errno.h> #include<unistd.h> #include<fcntl.h> extern int errno; int main(void) { if(_posix_no_trunc){ printf("path_max:%ld\n", pathconf("/",_pc_path_max)); printf("name_max:%ld\n", pathconf("/",_pc_name_max)); if(open("0123456789012345678901234567890123456789012345678901234567890123456789012345678 901234567890123456789\ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 890123456789\ 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 890123456789.data",O_RDWR O_CREAT, 0777) == -1){ if(errno == ENAMETOOLONG) perror(""); return 1;