-
시스템 프로그래밍 실습 4주차 : File I/OSystem Programming/Ubuntu Linux 2021. 9. 20. 02:36728x90
File I/O
목차
File & Directory
UNIX I/O
Standard I/O
Error Handling for File I/O
각 I/O 장단점
1) unix io
장점
- 가장 일반적이고 효율적이며 저렴하다.
- 메타데이터를 제공한다.
- async-signal-safe (stack을 사용하여 signal handler 안에서도 안전하다. = reentrant)
단점
- short count 이 발생할 수 있다.
2) std io
장점
- 버퍼링을 효율적으로 사용할 수 있다.
- short count 핸들링이 가능하다.
단점
- 파일 메타데이터를 제공하지 않는다.
- not async-signal-safe (시그널 핸들링에 부적합하다.)
- network socket을 사용할 때 불편한 점이 많이 생긴다.
선택지
std io 쓸 경우 : 디스크나 터미널 파일에 사용한다.
unix io : 효율이 좋기 때문에 시그널 핸들링 코드를 만들 때 사용한다.
rio : 네트워크 소켓 통신에 사용한다.
총정리 -> 6개의 System calls
• open()
• close()
• read()
• write()
• lseek()
• stat() / fstat()File & Directory
File이란? : n 바이트의 연속열로 표현되며, 모든 입출력 장치들뿐만 아니라 커널 또한 파일로 나타낼 수 있다
* 입출력 장치의 경우
/dev/sda1 (hard disk partition)
/dev/tty2 (terminal)
* 커널의 경우
/boot/vmlinux-5.4.0-42-generic (kernel image)
/dev/mem (kernel memory image)
/proc (kernel data structures)
File Types
- 각 파일들은 시스템 내에서 어떤 역할을 맡고 있는지를 가리키는 타입을 가지고 있다
file type의 종류
- Regular file : 임의의 데이터를 가지고 있음
- Directory : 관련된 파일들의 모음을 위한 인덱스 / 문서 파일이다. 파일의 이름과 주소를 담고 있다.
- Socket : 다른 기기와 프로세스 내에서 소통하기 위한
- 그 외에도 FIFOs(프로세스 간 커뮤니케이션 용도), Symbolic links, Character and block devices ( 문자/블록 특수 파일 / 키보드로 입력된 입력값과, 모니터에 나타낼 블록 단위의 값) 등이 있다
1) Regular file
- Application들은 text files와 binary files를 구분한다. 하지만 커널은 둘을 구분하지 않는다.
- text files는 ASCII나 Unicode만을 사용한 파일로, newline char "\n"으로 끝나는 문자의 연속열을 의미한다
- binary files는 모든 것을 의미한다
2) Directory
- Directory는 link들의 배열로 이뤄져 있으며, 각 link들은 파일의 이름을 파일 자체에 mapping해준다
- . 은 link 그 자체를 의미하며, ..는 부모 directory의 link를 의미한다
- 다음과 같은 계층 구조를 가지고 있다
UNIX I/O
"simple interface", "입출력 형식의 균일"
- 커널은 Unix I/O라는 간단한 인터페이스를 export한다
- 모든 input과 output은 "byte stream"이라는 동일한 방법으로 다뤄진다
- 열고 닫기 open() close()
- 읽고 쓰기 read() write()
- 현재 파일 위치를 변경하기 lseek()
1) open()
- 파일을 열 때 커널에 해당 파일에 접근할 준비가 되었다는 것을 알린다.
- 위 예제는 파일 디스크립터를 의미하는 fd가 0 미만의 값을 가질 때, error를 반환하는 코드다. 즉, fd==-1이면 에러가 발생했다는 의미다.
- 기본 형식 : open("path", mode) ex. open("/etc/", RDONLY)
- 리턴 값 : file descriptor (만약 에러 발생 시 -1 리턴)
- std input, std output, std err 파일을 기본적으로 열어줄 수 있다.
- open() 함수에 위와 같은 Flag를 붙이면 모드를 변경할 수 있다
2) close()
- 파일을 닫는 것은 커널에게 해당 파일을 종료할 것을 알려주는 것이다.
- Closing an already closed file is a recipe for disaster in threaded programs (추후에 이유를 알게 된다)
- 항상 return codes를 잘 확인해라!
- 기본 형식 : close(file descriptor)
- 리턴 값 : 음수이면 에러 발생. 그 외엔 이상 없음을 의미
3) read()
- 파일을 읽는 것은 "current file position"에서 "memory"로 bytes들을 복사하여, file position을 업데이트하는 것이다
- fd에서 buf로 읽는 bytes의 수를 return한다
- 만약 nbytes < 0 이면 에러가 발생했음을 의미한다
- Short counts가 가능하며 이는 에러가 아니다! ( nbytes< sizeof(buf) )
- 기본 형식 : read(fd, buf, sizeof(buf))
- 리턴 값 : 읽은 바이트의 크기(부호 있는 정수). 만약 에러 발생 시 음수 리턴
- short counts : 3번째 파라미터인 sizeof(buf) (예상 파일 크기) 보다 작게 리턴된 경우이다. 이 경우 여러 가지 경우가 있는데, 이후에 설명할 것이고 지금은 크게 문제가 되지 않는다고만 알아두자.
4) write()
- 파일을 쓴다는 것은 "memory"에서 "current file position"으로 bytes를 복사한다는 것이며, current file position을 업데이트한다
- read()의 경우와 동일하다
- 기본 형식 : write(fd, buf, sizeof(buf))
- 리턴 값 : write 한 바이트 수. 만약 에러 발생 시 음수 리턴
- short counts 발생 가능
5) File offset - lseek()
* CS에서의 offset이란?
컴퓨터 과학에서 배열이나 자료 구조 오브젝트 내의 오프셋은 일반적으로 동일 오브젝트 안에서 오브젝트 처음부터 주어진 요소나 지점까지의 변위차를 나타내는 정수형이다. 이를테면, 문자 A의 배열이 abcdef를 포함한다면 'c' 문자는 A 시작점에서 2의 오프셋을 지닌다
- 열린 파일에서의 offset은 lseek()나 lseek64()을 호출함으로써 확실하게 설정할 수 있다!
- 파일 fd의 새로운 offset을 return한다
- An offset can be set beyond the end of the file (If data is written at that point, a file "hole" is created)
* Short Counts란?
Short Counts는 다음과 같은 상황에서 발생한다.
- EOF (end-of-file)을 읽는 도중 만난 경우 Encountering (end-of-file) EOF on reads
- text lines 을 터미널로부터 읽어올 때 (예측이 힘듦) Reading text lines from a terminal
- 네트워크 소켓 통신 시 Reading and writing network sockets
Short Counts는 다음과 같은 상황에서 절대 발생하지 않는다.
• Reading from disk files (except for EOF)
• Writing to disk files
=> 디스크 파일을 읽고 쓸 때 (크기가 정해져 있는 상황)
- 다음은 Short Counts를 다루는 방법이다
이제부턴 Robust I/O는 RIO라고 정의하겠다.
이름이 Robust인 이유는 이 I/O 장치의 특성 때문인데, 입출력 장치를 견고하게 만들어준다는 의미를 지니고 있다.
몇몇 불편한 점들을 해결하기 위해 제시된 I/O 가 RIO package다.
RIO는 2가지 형태를 띠고 있다.
1) 2진 데이터로 이루어진 Unbuffered input / output
-> 아래에선 rio_readn, rio_writen 함수로 이를 처리할 것이다.
2) 2진 데이터와 텍스트로 이루어진 Buffered input
-> 아래에선 rio_readlineb, rio_readnb 함수로 설명할 것이다.
* 특히 2번 buffered는 그 전 (unix, standard)에서 하지 못하는 스레드 관리를 해주니 자세히 보도록 하자.
Unbuffered RIO Input and Output
함수 정의
1
2
3
4
5#include "csapp.h"
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
Return: # of bytes transferred if OK,
0 on EOF (rio_readn only), −1 on error설명
- 우선 사용하기 위해 "csapp.h"를 불러온다.
- 함수는 읽기(readn), 쓰기(writen) 2 가지가 있다.
- rio_readn은 eof 파일을 만나게 되면 short count를 리턴 하지만, rio_writen은 short count를 리턴하진 않는다.
- 추가로 interleaved (중간 간섭)을 허용한다.
Buffered I/O
Buffered I/O는 RIO에서 버퍼를 관리한다는 의미를 지닌다.
보통 프로그램에서는 1개의 글자를 읽는데 1번의 시간(cycle)이 걸린다.
예를 들어 hello를 출력하려면, 'h', 'e', 'l', 'l', 'o'를 각각 따로 출력을 해줘야 한다는 의미로 매우 비효율적이다.
이에 대한 해답은 버퍼를 사용하는 읽기 방식이 되겠는데 이게 Buffered read이다.
Buffer image
위 그림은 버퍼를 시각화한 그림인데, 간단하다.
초록색은 이미 읽은 부분, 빨간색은 아직 읽지 않은 부분을 뜻하고
rio_buf는 버퍼가 시작된 지점. rio_bufptr은 현재 읽는 부분을 나타내는 포인터, rio_cnt는 읽을 부분의 수를 나타낸다.
함수 정의
1
2
3
4
5
6#include "csapp.h“
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
Return: num. bytes read if OK,
0 on EOF, −1 on error설명
- rio_readinitb : 초기화 함수이다.
- rio_readlineb : 한 줄 단위로 입력을 받는다. 이때 파라미터로 입력의 최대 바이트 크기를 넣어주어야 한다.
- rio_readnb : 파일 fd에서 n바이트 크기를 읽어온다.
위 코드가 종료되는 조건은 파라미터로 입력된 maxlen 만큼 읽었을 때, 읽는 도중 EOF가 발생한 경우
그리고 한 줄 입력 같은 경우는 newline(개행) 문자를 만난 경우가 되겠다.
https://github-wiki-see.page/m/whdlgp/system_programming_pra/wiki/Robust-I-O
http://www.cs.cmu.edu/afs/cs/academic/class/15213-f02/www/R11/section_c/R11-sectionC-4up.pdf
Accessing Directory
• dirent: structure contains information about a directory entry
• DIR structure contains information about directory while stepping through its entries (see example in the next slide)
• opendir() and closedir()
» Open and close directory stream
• readdir()
» Return a pointer to a dirent structure representing directory entry
» Return NULL on reaching the end of the directory streamFile Metadata
메타 데이터는 실제 파일의 내용은 아니지만, 실제 관리하다 보면 파일의 작성 시간, 수정 시간 등 여러 다른 정보들을 메타 데이터라 부르고 "데이터의 데이터"라고도 한다.
- Per-file metadata maintained by kernel, accessed by users with the stat and fstat functions
Standard I/O
우리가 가장 자주 쓰는 I/O 함수이다.
우리는 항상 #include <stdio.h>를 코드 위에 사용하여 I/O Function을 사용하는데,
이 함수는 fopen fclose, fputs 등의 api를 제공한다.
- C Standard 라이브러리는 더 높은 레벨의 Standard I/O 함수들을 포함하고 있다!
- Standard I/O 함수들의 예시
- Standard I/O models는 파일을 streams으로 연다
• Abstraction for a file descriptor and a buffer in memory추가적으로 버퍼링도 지원하며, 이 버퍼는 fflush()를 통해 지울 수 있다.
Buffered I/O
Error Handling for File I/O
- Handling Errors 예시
File I/O 예시
참고: https://suhwanc.tistory.com/131
https://jihooyim1.gitbooks.io/unixbasic/content/contents/02.html
https://slidesplayer.org/slide/14100172/
https://stackoverflow.com/questions/33106505/read-file-line-by-line-in-c-mostly-with-syscalls
#include <sys/stat.h> // mode_t #include <fcntl.h> #include <unistd.h> // 윈도우에선 <io.h>헤더 #include <stdio.h> #include <stdlib.h> // exit함수 #define BUFFER (10) int main(void) { int fd, n1, n2; mode_t mode; // 윈도우에선 unsigned short 형식자로 바꿔서 사용 char buf1[BUFFER]; char buf2[BUFFER]; mode = 0644; // mode(권한옵션)을 8진수로 저장 if(!(fd = open("test.txt",O_RDONLY, mode))) { perror("Open"); exit(1); } n1 = read(fd, buf, BUFFER - 1); // 마지막에 '\0'을 넣어주기 위해 "BUFFER - 1"크기로 설정 n2 = read(fd, buf2, BUFFER - 1); // read함수의 오프셋이 이어짐을 확인하기위해 read()함수를 2번실행 if (n == -1 || n2 == -1) { close(fd); perror("Read"); exit(1); } buf[n1] = '\0'; //read()함수가 읽어들인 값을 반환함을 이용하여 '\0'값을 넣어준다 buf2[n2] = '\0'; printf("n = %d, buf = %s\nn2 = %d, buf = %s\n", n1, buf1, n2, buf2); close(fd); return (0); } /*---test.txt파일 내용---*/ abcdefghijklmn /*---결과---*/ n1 = 9, buf1 = abcdefghi n2 = 5, buf2 = jklmn
https://kirkim.github.io/c/2021/02/20/lowfildefunc(2).html
https://slaystudy.com/c-program-to-search-a-word-in-a-given-file-and-display-all-its-position/
728x90'System Programming > Ubuntu Linux' 카테고리의 다른 글
시스템 프로그래밍 실습 6주차 : Daemon (0) 2021.10.03 시스템 프로그래밍 실습 5주차 : Processes (0) 2021.09.27 시스템 프로그래밍 실습 3주차 : Shell & Makefile & Git (0) 2021.09.16 시스템 프로그래밍 실습 2주차 : gcc & gdb (0) 2021.09.16 시스템 프로그래밍 실습 1주차 : 수업 개요 (0) 2021.09.02