Unix I/O
리눅스에서 파일은 바이트로 이루어진 녀석
그래서 종류도 다양할 수 있음
- Regular file
- Directory
- Socket
Opening Files
open() 은 커널에게 “이 파일을 사용하겠다”고 알리는 시스템 호출
이게 성공하면 파일 디스크립터를 반환해줌.

반환값인 fd 는 해당 파일을 가리키는 식별 번호
이걸로 나중에 read(), write(), close() 등을 호출함.
만약에 open()이 실패하면 fd == -1 이고 에러로는 perror() 출력
근데 내가 이렇게 직접 호출하기 전에 프로세스가 시작할 때 기본적으로 파일을 3개 열고 시작함.
- 0 : stdin, 표준 입력
- 1 : stdout, 표준 출력
- 2 : stderr , 표준 에러 출력
아무 파일을 열기 전에 기본적으로 0, 1, 2는 이미 열려 있음
그래서 내가 open() 으로 파일을 열면 그 때는 fd가 3일거임.
Closing Files
리눅스에서 시스템 자원은 유한하니까 파일 열고 놓고 안쓰고 그러지 말자.
다 썼고, 안쓸거면 닫아라

close(fd) 는 커널에게 “이제 이 파일 안쓸거야” 라고 알려주는 시스템 호출임
인자로는 열러있는 파일 디스크립터(fd)를 줘야 함.
반환값으로는 0이면 성공, -1이면 실패임
그리고 이미 닫은 파일을 또 닫으려고 하면 에러임
그래서 별 문제 없을 거 같아도 항상 반환값을 확인하는게 중요함.
Reading Files

- fd : 내가 읽은 파일의 파일 디스크립터
- buf : 데이터를 저장할 버퍼
- count : 최대 몇 바이트까지 읽을 것인지
현재 파일의 위치(offset)에서부터 최대 count 바이트 까지 읽어서 buf에 복사됨
최대 count이지 딱 count가 아님!!!! 덜 읽을 수도?? = short count
실제로 읽은 바이트 수를 반환하고 이 수만큼 파일 위치도 자동으로 앞으로 이동함.
Short Count
read() 는 항상 count 개수만큼 읽는 게 아님
int n = read(fd, buf, 512);
if (n < 512) {
// EOF에 도달했거나, 인터럽트로 인해 더 적게 읽음
}
- 파일의 끝에 가까워서 더 이상 읽을게 없거나
- 인터럽트에 의해서 덜 읽었거나
그러면 읽은 바이트 수만 큼만 리턴함
에러 아님!!!!!!!!!!!!!!
그리고 읽을게 없으면 아예 없으면 0을 리턴하고
에러가 발생하면 -1을 리턴함
Writing Files

read() 랑 정반대 동작
- fd : 데이터를 쓸 파일 디스크립터
- buf : 데이터를 담고 있는 메모리 버퍼
- count : 최대 몇 바이트를 쓸 지
buf 에 들어 있는 count 바이트 중 일부나 전부를 현재 파일 위치부터 파일에 씀
이후에는 파일 오프셋이 자동으로 앞으로 감.
내가 512바이트 쓰라고 했는데도 다 못쓸 수 있음.
에러가 아님
이유로는
- 디스크 공간 부족
- 쓰다가 인터럽트 발생
마찬가지로 short count 가능
Simple Unix I/O example
사용자의 표준 입력을 표준 출력으로 한 바이트씩 복사하는 코드

여기서 STDIN_FILENO 는 0일거임.
while의 조건은 사용자의 입력이 존재하는 한 0이 아닐거임.
STDOUT_FILENO 는 터미널 디스플레이이 표시함
걍 echo 하는 거임
lseek()
off_t lseek(int fd, off_t offset, int whence);
- fd : 파일 디스크립터
- offset : 이동할 바이트 수, 해당 파일 포인터의 위치에서 얼마나 이동할 지
- whence : 기준점
// 현재 위치(오프셋) 확인하기
off_t pos = lseek(fd, 0, SEEK_CUR);
// 파일의 시작위치에서 5바이트 앞으로 이동
lseek(fd, 5, SEEK_SET);
write(fd, "HELLO", 5);
buffer를 문자열로 사용가능함
이 녀석은 마지막에 null 포함하고 있음
그래서 6바이트 캐릭터를 담을 수 있는 상수 버퍼임.
근데 5개만 wirte 하는 것
off_t end = lseek(fd, 0, SEEK_END);
printf("File size" %ld bytes\n",end);
파일 끝에서 0바이트 떨어진 곳(= 걍 파일의 끝)으로 이동
RIO Package
이건 걍 책에만 있는건데 short count가 문제라서 그걸 고려해서 함수들을 만든거임
Unbuffered I/O
- rio_readn(int fd, void *usrbuf, size_t n)
- rio_writen(int fd, void *usrbuf, size_t n)
Buffered I/O
- rio_readline()
- rio_readnb()
Buffered RIO는 thread-safe하고 , 같은 파일 디스크립터에 대해서 여러 RIO 함수가 뒤섞여서 사용 가능하다.
(thread-safe ≠ no data race, 쓰레드 세이프하다는 말이 데이터 레이스가 없다는 말이 아님!!!!!!!)
쓰레드 세이프 하다는 것은 서로 다른 쓰레드가 있을 때, fd가 같더라도 rio 객체가 달라서(서로 다른 내부 버퍼를 가져서) 접근하는게 안전하다는 것
근데 같은 fd에 대해서 다른 내부 버퍼를 가지고 있다는 것은 쓰레드1의 버퍼가 처음으러부터 8바이트 읽고, 쓰레드2가 처음부터 8바이트 읽고 싶었지만 쓰레드2는 그 다음부터 읽어진다는 것 ⇒ 데이터 레이스 존재
그래서 결론은 읽는데 문제는 없지만 결과가 보장되지 않는다는 것!!
Unbuffered RIO Input and Output

n은 얼마나 읽을 건지, 쓸 건지
- rio_readn() : short count가 발생할 수 있지만 파일의 끝일 때만 발생한다.
- rio_writen() : short count가 발생하지 않는다! (never)
얘네들은 OS 내부에서 커널에서의 공유 버퍼임.
그래서 mutex로 원자적으로 관리되기 때문에 interleaved 되어도 상관 없음
⇒ 쓰레드 세이프
rio_readn()

여기서 read(fd, bufp, nleft) 를 while문 안에서 계속 반복적으로 수행하도록 함.
에러나면 걍 리턴하는건 자명
- nleft : 내가 앞으로 읽어야 할 바이트 수
- bufp : 내가 읽은것을 저장할 버퍼
- nread : 내가 현재 읽은 바이트 수
반복문을 돌 때마다 nleft랑 bufp가 변하고 있는 것을 알 수 있다.
반복문으로 못 읽은 걸 읽을 수록 nleft 는 읽은 만큼 줄어들거임
반복문으로 못 읽은 걸 읽을 수록 bufp 의 위치는 읽은 만큼 이동해야함.
Buffered RIO Input Functions

- rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
- rio_readnb(rio_t *rp, void *usrbuf, size_t n)
Buffered I/O: Implementation

이제 버퍼드가 어떻게 작동하는지 살짝 살펴보자.
기존에는 계에속 read() 를 반복적으로 호출했는데, 이젠 안그래도 됨.
일단 크게 잡고 1번 read() 을 호출해서 내부 버퍼에 저장해줌.
그리고 이 내부 버퍼에서 차근 차근 읽음
그러다가 가져온걸 다 읽었으면 그때 다시 크게 잡아서 read()
(반복, …)
How the Unix Kernel Represents Open Files

- Descriptor Table : 각 프로세스마다 존재
- Open File Table : 커널 전역에서 공유
- v-node Table : 파일의 시스템 레벨에서의 메타데이터
File sharing
하나의 파일을 여러 디스크립터로 열었을 때 어떻게 되나

동일한 파일을 open() 으로 두 번 열었을 때 발생하는 상황
그러면 fd 도 다르고, 슬롯이 연결되는 Open File Table 도 다름.
하지만!!!!! v-node table 은 같은 걸 가리킴
각자 자기만의 파일 포지션을 가짐.
서로 읽는거에 영향을 주지 않음
하지만 결국 같은 파일이라서 내용이 수정되면 그건 공유됨.
How Processes Share Files: fork

부모가 포크를 해서 자식을 만들면 자식은 부모의 디스크립터 테이블을 그대로 복사 받음
값은 같지만 테이블은 별도임!!!
하지만 이 디스크립터들이 가리키는 open file table은 같은 곳을 가리킴
여기서 참조 카운트가 2로 증가함 (자기한테 들어오는 화살표가 2개니까)
I/O Redirection

- dup2(oldfd, newfd) : newfd를 oldfd와 같은 파일로 연결시킴
(근데 이거 어따 써.. 저렇게 해서 뭐할건데…)

Buffering in Standard I/O
printf("h");
printf("e");
printf("l");
printf("l");
printf("o\n");
이런 코드가 있을 때 이걸 write() 로 한 글자씩 처리하면 시스템 콜을 계속 해야되서 엄청 느려짐
이걸 피하기 위해서 버퍼링 사용
printf() 는 버퍼에 글자만 집어 넣음
그러다가 \n, fflush(), exit() 을 만나면 발생함.
이렇게 하면 1번만 시스템 콜 하니까 엄청 빨라짐
'지식 > 시스템프로그래밍' 카테고리의 다른 글
Exceptional Control Flow: Signals and Nonlocal Jumps (2) | 2025.04.10 |
---|---|
Exceptional Control Flow: Exceptions and Processes (0) | 2025.03.29 |