일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Value too long for column
- 티스토리챌린지
- 코루틴 컨텍스트
- JanusGateway
- table not found
- terminal
- Spring Batch
- PytestPluginManager
- vfr video
- 코루틴 빌더
- JanusWebRTCGateway
- taint
- VARCHAR (1)
- JanusWebRTCServer
- k8s #kubernetes #쿠버네티스
- 깡돼후
- 오블완
- 자원부족
- mp4fpsmod
- kotlin
- tolerated
- python
- preemption #
- 달인막창
- 겨울 부산
- PersistenceContext
- 개성국밥
- pytest
- 헥사고날아키텍처 #육각형아키텍처 #유스케이스
- JanusWebRTC
너와 나의 스토리
[Unix] CH2. File - system call, Standard I/O 본문
File and Filesystem
- File
- 바이트의 연속적인 시퀀스
- 운영체제에서 강요하는 형식이 없다
- 각 바이트는 디스크 파일에서 개별적으로 주소 지정 가능
- 파일은 또한 외부 장치에 대한 균일한 인터페이스이다
- Filesystem
- 컴퓨터 파일과 데이터를 저장하고 조직하는 메서드
- 파일을 찾고 접근하기 쉽게 한다.
- 파일 시스템은 데이터 저장 장치를 사용할 수도 있다 ex) CD-ROM, hard disk
2.1 UNIX file access primitives
UNIX primitives
-
open, read, write, lseek, close -> unbuffered I/O
-
함수 호출 실행 동안만 캐시 안에 데이터가 존재하고, 함수 호출 끝나면 캐시 안의 데이터가 지워진다.
-
File Descriptor
- 프로세스에서 열린 파일의 목록을 관리하는 테이블의 인덱스
- kernel에서 열려있는 모든 파일은 파일 디스크립터가 참조한다.
- 양의 정수
- 종료 파일을 열거나 새 파일을 만들 때 커널은 프로세스에 파일 디스크립터를 반환한다.
- read나 write의 인자
The open() system call
- 파일을 열거나 생성할 수 있다.
- flags:
- O_RDONLY: Open for reading only #0
- O_WRONLY: Open for writing only #1
- O_RDWR: Open for reading and writing #2
*참고:
O_RDONLY|O_WRONLY이면 0|1이 되서 그냥 1이 됨 => O_WRONLY랑 동일
그렇기 때문에 O_RDWR을 따로 써줘야 함
- optional flags:
- O_APPEND: 기존 데이터 보존하고, 뒤에 이어서 저장
- O_CREAT: 만약 파일이 존재하지 않다면 생성하라
- O_EXCL: O_CREAT도 지정되어 있고 파일이 이미 있는 경우 오류를 생성하라
- O_TRUNC: 파일이 있는 경우 기존 데이터 삭제 (파일 길이 0 됨)
- O_NONBLOCK: 비차단 파일 열기
- mode:
- O_CREAT flag 쓸 때만 사용
- File security permission
기존에 newfile이란 파일에 "abcdexg"라고 적혀 있다고 하자. O_WRONLY|O_CREATE하고 "xyz"를 적게되면 파일은 "xyzdexg"로 해당 부분이 덮어 씌여진다.
하지만 조건에 O_TRUNC를 추가하게되면, 원래 newfile에 있던 내용이 전부 지워지고 xyz만 씌여지게된다.
Security permission은 뒤에서 다시 설명하겠습니다.
chmod도 뒤에 나와요
Symbolic names for file permissions
The creat() system call
- 파일을 만드는 다른 방법
- 파일이 이미 있는 경우 두 번째 인수가 무시됨 (파일 원래의 권한이 유지됨)
- open( )과 달리 creat( )는 파일 디스크립터를 반환하기 전에 항상 기존 파일의 데이터를 삭제한다. ♣
- creat()은 항상 writing only로 파일 open할 때만 사용 가능
Owner and permission of a new file
-
open 또는 create를 사용해서 우리가 파일을 만들 때,
-
상위 디렉토리에 새 링크가 추가되므로 쓰기 권한이 필요하다.
-
디렉토리는 디렉토리에 있는 파일 목록을 포함하는 파일일 뿐이다.
-
즉, 디렉토리 안에 데이터(파일)을 write 할 수 있는 권한이 필요하다.
-
-
소유(own)는 누가 하는가?
-
소유자는 프로세스의 effective user-ID로부터 설정된다.
-
그룹이 상위 디렉토리의 group-ID 또는 effective group 중 하나로 설정됨
-
The close( ) systme call
- 열려 있는 파일을 닫는다
- 프로그램 실행이 완료되면, 모든 파일들은 자동적으로 닫히게 된다. -> 프로세스 테이블 날아감
The read( ) systme call
- 파일을 읽으면 현재 파일 위치에서 메모리로 바이트가 복사된 다음 파일 위치를 업데이트한다.
- 인자:
- filedes: file descriptor -> open()이나 creat()으로 얻을 수 있다.
- buffer: 카피될 데이터가 들어있는 배열이나 구조체의 포인터
- n: 파일로부터 읽을 바이트 수
- File descriptor table에서 File table로는 직접 접근이 가능하지만 File table에서 File로는 system call을 통해서 간접 접근만 가능하다
- 같은 파일이라도 open 할 때마다 File table 새로 만들어진다.
- parent-child 관계이면 file table 공유 가능 -> file table의 count가 올라감
The write( ) systme call
- 파일을 쓰면 메모리에서 현재 파일 위치로 바이트가 복사된 다음 현재 파일 위치가 업데이트된다.
- 인자:
- filedes: file descriptor
- buffer: 쓸 데이터에 대한 포인터
- n: 쓸 바이트 수
- 프로그램이 write 하기 위해 기존 파일을 여는 경우, 파일의 이전 데이터는 새로운 문자로 덮어 써진다.
- open에서 O_APPEND 옵션을 지정한 경우 파일의 오프셋이 파일의 현재 끝으로 설정된다.
기존에 파일이 존재 했다면, 전부 지워라.
read한 길이와 write한 길이가 같아야 정상
nread가 양수: 내가 읽은 데이터 크기
nread가 0: 읽은 데이터가 없다
nread가 음수: 에러
read, write and efficiency
- buffer size는 disk block size의 배수로 해줘야 시스템 효율성이 증가한다. (system call 횟수가 감소하므로)
- system call 횟수를 줄여야 효율성이 증가한다
- 프로그램이랑 커널 모드 변경하는 게 expensive
- write system call은 굉장히 빠르다. 왜일까?
- 디스크를 읽는 일은 메모리를 읽는 것보다 훨씬 느리다. -> 그래서 들어올 때마다 적지 않음
- write 할 때 즉시 디스크에 데이터를 넣는 것이 아니라, 커널의 버퍼 캐시에 넣어 두었다가 나중에 한 번에 disk에 옮겨 적는다. -> delayed writing
- 디스크에 에러가 있거나 커널이 멈춘다면, 그때 disk에 적음.
* 페이지 캐시
- f리눅스는 파일 I/O의 성능 향상을 위해 페이지 캐시라는 메모리 영역을 만들어 사용한다.
- 한번 읽은 파일의 내용을 페이지 캐시라는 영역에 저장시켜 놨다가 다시 한 번 동일한 파일 접근이 일어나면 디스크에서 읽지 않고 페이지 캐시에서 읽어서 제공해 주는 방식이다.
- 즉, 파일의 내용을 저장하고 있는 캐시이다.
* 버퍼 캐시
- 블록 디바이스가 가지고 있는 블록 자체에 대한 캐시이다.
- 즉, 파일 시스템의 메타 데이터와 관련된 블록들을 저장하고 있는 캐시이다.
- temporal parameter - 시스템 콜 끝나면 사라짐
* IPC 중요
The lseek() system call
- 열린 파일의 오프셋은 lseek를 호출하여 명시적으로 설정할 수 있다.
- file offset은 일반 파일에서 다음 read 또는 write가 발생할 때 표시할 위치이다.
- 인자:
- offset: start_flag로부터 떨어진 거리(바이트 수)
- start_flag: 시작 위치
~10 이렇게 쓰다가 lseek를
통해 100으로 넘어가서 write하게 되면, 그 사이 공간은 null로 채워지게된다.
만약 위 방법으로 작성된 파일이 카피되면 그 null 값들은 0으로 채워지게된다.
File Share
- 모든 프로세스에는 프로세스 테이블에 엔트리가 있다. 각 프로세스 테이블 엔트리 안에는 열린 파일 디스크립터의 테이블이 있다.
- 파일 디스크립터 flag
- 파일 테이블 엔트리 포인터
- process table은 kernel 안에 존재
- 커널은 모든 열린 파일들을 위해 파일 테이블을 관리한다. 각 파일 테이블 엔트리는 다음을 포함한다.
- 파일의 상태 flag (read, write, ...)
- 현재 파일 오프셋
- 파일의 v-node 테이블 엔트리 포인터
- 각 열린 파일(또는 디바이스)는 v-node 구조체를 가진다
- v-node 구조체는 파일 유형에 대한 정보와 파일에서 작동하는 함수에 대한 포인터를 포함한다.
fd flags는 사용 중인지(flag=1), 아닌지 (flag=0)을 나타냄
- i-node information:
- 파일에 관련된 모든 정보를 가짐
- 파일마다 유니크하게 가짐
- 파일을 여러 번 오픈하면 파일 테이블이 여러 개 일 수는 있지만 i-node는 동일하다
- 파일 디스크립터도 여러개
close(fd3)하면 fd3가 가리키던 파일 테이블은 없어진다.
The dup() and dup2() system call
- dup(1) : 새로운 파일 디스크립터가 생겨서 1(파일 디스크립터)이 가리키던 파일 테이블을 똑같이 가리킴
- 새로운 파일 디스크립터를 반환하지만, 숫자만 다를 뿐, 원래의 파일 디스크립터(1)와 같은 파일을 가리킨다.
- fcntl(1, F_DUPFD, 0)에서 0은 "새로 만들어서 복사하라"는 의미
- dup2(3, fd4) : fd4가 fd3이 참조하는 파일 테이블 엔트리를 참조하고, 만약 fd4가 열려진 파일 디스크립터일 때는 그걸 먼저 닫고 난 후에 복제한다.
- 그리고 두 파일 디스크립터가 같은 파일 테이블을 가리키므로 파일 테이블의 count는 2가 된다.
- 즉, dup()은 새로운 파일 디스크립터가 생성되는 것이고, dup2()는 기존 파일 디스크립터가 다른 파일 테이블을 가리키도록 하는 것이다.
The fcntl() system call
- 이미 열려있는 파일의 속성을 바꿀 수 있다.
- 인자
- cmd: 프로그래머는 정수 cmd 매개변수에 대한 값을 선택하여 특정 함수를 선택한다.
- F_DUPFD : 기존의 디스크립터를 복제
- F_GETFD or F_SETFD : Get/set file descriptor flags -> ch05
- F_GETFL or F_SETFL : Get/set file status flags
- F_GETOWN or F_SETOWN : Get/set asynchronous I/O ownership -> ch06
- F_GETLK, F_SETLK, or F_SETLKW : Get/set record locks -> ch08
- cmd: 프로그래머는 정수 cmd 매개변수에 대한 값을 선택하여 특정 함수를 선택한다.
result가
0 1 : read only
1 0 : write only
1 1 : read and write
Standard input, standard output and standard error
리눅스의 모든 프로세스들은 세 개의 오픈 파일(파일 디스크립터)를 가진다. (파일 구조에 대한 포인터)
-> standard input / output / error files
- Standard Input:
- 셸 스크립트(shell scripts)를 보다 쉽게 작성할 수 있도록 파일로 추상화된 키보드이다.
- Standard Output:
- 스크립트 및 프로그램 작성을 더 쉽게 하기 위해 파일로 추상화된 셸 창 또는 스크립트가 실행되는 터미널이다.
- Standard Error:
- standard output과 같다. 스크립터가 실행되는 셸 창(shell window)이나 터미널
파일 디스크립터는 단순히 열린 파일을 나타내는 숫자이다.
기본적으로, 파일 디스크립터 0은 standard input을 나타내며 종종 stdin으로 축약된다.
파일 디스크립터 1은 standard output(stdout)을 나타내고,
파일 디스크립터 2는 standard error(stderr)을 나타낸다.
이러한 파일을 다른 위치로 리디렉션 하려는 경우 파일 디스크립터 수는 0에서 올라간다.
이 세 가지 standard I/O stream은 사전 정의 된 파일 포인터(predefined file pointers) stdin, stdout, stderr를 통해 참조된다.
Buffering
- 표준 I/O 라이브러리가 제공하는 버퍼링의 목표는 최소 수의 read와 write 호출을 사용하는 것이다.
- Fully buffered
- 실제 I/O는 표준 I/O 버퍼가 꽉 차면 발생한다.
- flush라는 용어는 표준 I/O 버퍼의 write을 설명한다. 버퍼는 버퍼가 채워질 때와 같은 표준 I/O 루틴에 의해 자동으로 flush되거나, 우리는 fflush 기능을 호출하여 스트림을 flush할 수 있다.
- Line buffered
- 표준 I/O 라이브러리는 입력이나 출력에서 개행문자를 마주할 때 I/O를 수행한다.
- 일반적으로 터미널과 관련하여 스트림에서 사용된다. (예: 표준 입력 및 표준 출력)
- Unbuffered
- 표준 I/O 라이브러리는 문자(characters)를 버퍼링하지 않는다.
Redirecting Standard Output
- I/O redirection
- 입출력을 대상으로 표준 입력, 출력, 오류를 사용하지 않고 다른 경로인 파일로 재지정하는 것
- 즉, 명령은 redirection을 통해 파일로부터 입력받을 수 있고, 파일로 출력할 수 있음
- 입출력 재지정을 통해 출력과 입력의 방향을 변경할 수 있음
- Standard input
- 키보드 입력을 파일에서 받도록 대체 하는 것
- '<' 연산자를 사용해서 키보드로 연결된 표준 입력 방향을 파일로 변경(명시적)
- cat 명령어를 사용하는 것과 동일한 결과를 나타냄
- Standard output
- 명령의 실행 결과나 에러 상황을 화면에 출력(표준 출력, 에러)하지 않고 파일로 저장
- '>' 연산자를 사용한 표준 출력 재지정
- 파일명 앞에 '>' 연산자를 사용
- '>' 연산자로 출력 방향을 지정할 때 목적 파일은 항상 처음부터 다시 작성됨 (파일 덮어씀)
- '>>' 연산자를 사용한 표준 출력 재지정
- 파일명 앞에 '>>' 연산자 사용
- 존재하지 않는 파일이면 > 연산자를 사용한 것 처럼 파일이 생성되고, 파일이 존재하는 경우는 이어서 작성된다. (파일 뒤에 추가로 작성됨)
- Standard error
- redirection 연산자가 필요 없음. 쉘은 파일 디스크립터 번호를 이용해서 재지정 할 수 있는 표기를 지원
- 파일 디스크립터: 표준 입력:0, 표준 출력:1, 표준 오류:2
- 파일 디스크립터 번호를 redirection 연산자 앞에 위치
System Call vs Library Call
- System call
- 프로그램 내부에서 메모리를 항당 또는 해제하지 않음
- 어떤 운영체제에서의 특정한 자원을 가지고 싶을 때 주로 사용
- unbuffered I/O
- system call 끝나면 다 사라짐
- Library call
- user friendly interface: 알기 쉽게
- buffered I/O
- 나중에 다시 쓸 수 있음 => 시스템 효율 ↑
- 내부에서 메모리 할당하여 return 가능
The Standard I/O Library
- UNIX I/O (system call)
- 간단한 바이트 시퀀스 형태로만 데이터를 다룬다
- 다른 모든 것을 프로그래머에게 맡긴다
- 효율성 고려 사항도 개발자의 손에 넘어간다
- Standard I/O
- automatic buffering
- 프로그래머 친화적인 인터페이스
- 라이브러리를 쉽게 사용하도록 만듦
- 프로그래머가 능률에 대해 걱정하는 것을 면제
- Standard I/O와 UNIX I/O 차이
- 파을을 describe하는 방법
- standard I/O는 FILE* 사용, UNIX I/O는 file descriptors 사용 -> 아마도...
- standard I/O routines은 시스템 콜 주위에서 작성된다.
system call 함수에 f 붙이면 library라고 생각하면 될 듯
- C standard library (libc.a)는 higher-level standard I/O 함수들을 포함한다
- Examples of standard I/O functions:
- 파일 열고 닫기 (fopen and fclose)
- 바이트 읽고 적기 (fread and fwrite)
- text lines 읽고 적기 (fgets and fputs) -> 1byte씩 읽고 씀
- Formatted reading and writing (fscanf and fprintf)
fopen( )
- 인자(type)
- r or rb : open for reading
- w or wb: 0 길이로 자르거나 writing 위해 create
- a or ab: 파일 끝에 wrting을 위해 open, 파일 없으면 생성해서 write
- r+ or r+b or rb+: reading과 writing을 위해 open
- w+ or w+b or wb+: 0 길이로 자르거나 reading이나 writing을 위해 생성
- a+ or a+b or ab+: 파일 끝에서 reading, writing을 위해 파일 열거나 생성
getc( ), putc( ) -> buffering
- istream: 파일 포인터
- 읽어온 데이터를 user space 안의 버퍼에 저장 -> 나중에 사용 가능
Buffering
- 표준 I/O는 버퍼링 메커니즘(buffering mechanism)에 의해 이러한 비효율성을 방지한다.
- 사용되는 버퍼는 일반적으로 스트림에서 처음 I/O를 사용할 때 malloc을 호출하는 표준 I/O 함수 중 하나에 의해 얻어진다.
User space buffer와 Kernel buffer
fopen()을 호출하면 library call이 malloc()을 호출하여 FILE 구조를 위한 공간을 할당하고, 이 FILE 구조 내에 I/O에 대한 버퍼가 있다. 그러나 write system call이 실제로 데이터를 커널 버퍼에 기록한다.
fwrite()는 FILE 구조에서 운영되는 standard library의 일상적인 작업이지만 write()는 시스템 콜이다. fwrite()는 내부적으로 write()를 사용한다. fwrite()가 데이터를 write() 시스템 콜에 전달할 준비가 될 때까지 user space I/O buffering을 제공하지 않는다.
즉, User space buffer에 저장했다가 시스템 콜해서 kernel buffer에 저장하는 듯
fprintf()로 에러 메시지를 작성해보자
출처: [unix system programming 2nd]
출처: 리눅스의 페이지 캐시와 버퍼 캐시 - https://brunch.co.kr/@alden/25
'Unix > 이론' 카테고리의 다른 글
[Unix] CH.8 IPC (message queue, semaphores, shared memory) (0) | 2019.12.08 |
---|---|
[Unix] CH.7 Pipe, FIFO, I/O Multiplexing (0) | 2019.11.18 |
[Unix] CH6. Signal and signal processing (0) | 2019.11.10 |
[Unix] CH3. File - ownership (0) | 2019.10.10 |
[Unix] Ch1. Basic concepts and terminology (0) | 2019.09.26 |