File Offset
Location in the file at which the next read() or write() will commence
- Expressed as an ordinal byte position relative to the start of the file
- The first byte of the file is at offset 0
- Set to point to the start of the file when the file is opened
- Automatically adjusted by each subsequent call to read() or write()
- Successive read() and write() calls progress sequentially through a file
Reposition of file offset
- off_t lseek(int fd, off_t pos, int origin)
- Sets the file offset of a file descriptor to a given value
- origin
- SEEK_CUR
- Current file offset of fd is set to its current value plus pos
- pos of zero returns the current file offset value
- SEEK_END
- Current file offset of fd is set to the current length of the file plus pos
- pos of zero sets the offset to the end of the file
- SEEK_SET
- Current file offset of fd is set to pos
- pos of zero sets the offset to the beginning of the file
- SEEK_CUR
- Returns the new file offset on success and -1 on error
- e.g., returns -1 if resulting file offset would be negative
- pos는 원하는 offset의 위치를 나타내는 변수로 이는 origin의 값에 따라 의미가 달라진다.
Examples
lseek(fd, 0, SEEK_SET); /* Start of file */
lseek(fd, 0, SEEK_END); /* Next byte after the end of the file */
lseek(fd, -1, SEEK_END); /* Last byte of file */
lseek(fd, -10, SEEK_CUR); /* Ten bytes prior to current location */
lseek(fd, 10000, SEEK_END); /* 10,001 bytes past last byte of file */
What happens if a program seeks past the end of a file?
- Call to read() will return 0 (indicating EOF)
- It is possible to write bytes at an arbitrary point past the end of the file
- file hole: space in between the previous end of the file and the newly written bytes
- Reading from the hole returns a buffer of bytes containing 0 (null)
- file hole: space in between the previous end of the file and the newly written bytes
- 마지막 예시처럼, file의 용량을 넘어서는 file offset을 설정해도 된다.
- 이때, 그 file offset에서 read()를 한다면 EOF가 읽히고,
- write()를 한다면 파일에 데이터가 써지고, 기존 offset과 write()을 시작한 offset사이의 공간에는 file hole이 발생한다. 이 공간은 0 (null)로 채워진다.
- 만약, write()을 해서 file hole을 만들고, offset을 file hole로 바꾼다음 read()를 한다면, 0 (null)이 읽힐 것이다.
Positional Read
- ssize_t pread(int fd, void* buffer, size_t count, off_t pos)
- Reads up to count bytes into buf from the file descriptor fd at file position pos
- Completrly ignores the current file offset
- Instead of using the current offset, it uses the value provided by pos
- When done, it does not update the file offset
- lseek()의 SEEK_SET과 같이 pos의 위치부터 read()를 수행하는데,
- 기존의 file offset을 사용하지 않는다. 그렇기 때문에 read()한 만큼 file offset이 업데이트되지 않는다.
- 그렇기 때문에 sequential하게 read()를 수행해야 하는 경우는 그냥 read()가 더 유리하다.
Positional Write
- ssize_t pwrite(int fd, const void *buffer, size_t count, off_t pos)
- Writes up to count bytes from buf to the file descriptor fd at file position pos
- pread()와 마찬가지로 file offset을 update하지 않는다.
Shared File Descriptor
Shared file descriptor by fork()
int main(int argc, char *argv[]){
int fd = open("file.txt", O_RDONLY);
assert(fd >= 0);
int rc = fork();
if (rc == 0){
rc = lseek(fd, 10, SEEK_SET);
printf("C: offset %d\n", rc);
} else if (rc > 0){
wait(NULL);
printf("P: offset %d\n", (int) lseek(fd, 0, SEEK_CUR));
}
return 0;
}
/*
prompt> ./fork-seek
child: offset 10
parent: offset 10
prompt>
*/
+ fork()를 하면 fd를 공유한다. 즉 file의 offset을 공유하는 것이다.
+ 가장 왼쪽에 있는 file table은 process마다 존재하는 file table이다. child를 생성할때, fork()를 통해 file table까지 복사해가기 때문에, 부모와 자식 프로세스의 3번 fd가 가르키는 Global File Table의 데이터는 같다.
+ Global File Table의 File Entry에서 file offset을 관리하기 때문에, 부모와 자식 프로세스는 공유되는 file offset에 접근할 수 있음을 알 수 있다.
+ 하지만 같은 fd를 갖는다고 무조건 Global File Table상의 같은 File Entry를 갖는 것은 아닌데, 프로세스의 file table에서 쓰이는 fd는 프로세스마다 다르게 정의되기 때문에,
+ 부모 자식 프로세스가 아닌 또 다른 프로세스가 open()을 통해 file을 만든다면, 그 파일 역시 fd값이 3이겠지만, 다른 Global File Table상의 데이터를 가르키고 있을 것이다. pointer가 가르키는 데이터가 다르니까
Revisiting Previous Example
2024.11.13 - [[학교 수업]/[학교 수업] 시스템 프로그래밍] - [시스템 프로그래밍] File I/O
Example
int main(void)
{
char str[BUF_SIZE], rbuf[BUF_SIZE];
size_t len;
ssize_t ret;
int fd;
char *bufp = rbuf;
printf("Input: ");
if(fgets(str, BUF_SIZE, stdin) == NULL){
perror("fgets");
exit(1);
}
len = strlen(str);
/* open() before fork() */
fd = open("file1.txt", O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
if(fd < 0){
perror("open");
exit(1);
}
if(fork() == 0){
if(write(fd, str, len) < 0){
perror("write");
close(fd);
exit(1);
}
}
else{
wait(NULL);
while(len){
ret = read(fd, bufp, len);
if(ret < 0){
if(errno == EINTR)
continue;
perror("read");
close(fd);
exit(1);
}
len -= ret;
bufp += ret;
}
bufp = '\0';
printf("Output: %s\n", rbuf);
}
close(fd);
return 0;
}
+ child에서 write()을 하면서 file offset을 뒤로 끝까지 밀어놓고, parent에서는 그 offset에서부터 read()를 하려하므로, ret값이 0이 계속 나오게되고, len이 줄어들지 않기 때문에 무한 loop에 빠지게된다.
+ 만약 fork()하고 각자 open()을 한다면 Grobal File Table상에는 다른 File Entry를 가르키고 있기 때문에, offset을 공유하지 않지만, 위 예시에서는 fork()하기 전에 open()을 했기 때문에, 공유되는 file offset으로 인한 문제가 발생한다.
result
Example (Fixed Parent)
int main(void)
{
char str[BUF_SIZE], rbuf[BUF_SIZE];
size_t len;
ssize_t ret;
int fd;
char *bufp = rbuf;
printf("Input: ");
if(fgets(str, BUF_SIZE, stdin) == NULL){
perror("fgets");
exit(1);
}
len = strlen(str);
/* open() before fork() */
fd = open("file1.txt", O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
if(fd < 0){
perror("open");
exit(1);
}
if(fork() == 0){
if(write(fd, str, len) < 0){
perror("write");
close(fd);
exit(1);
}
}
else{
wait(NULL);
lseek(fd, 0, SEEK_SET); /* FIX!! */
while(len){
ret = read(fd, bufp, len);
if(ret < 0){
if(errno == EINTR)
continue;
perror("read");
close(fd);
exit(1);
}
len -= ret;
bufp += ret;
}
bufp = '\0';
printf("Output: %s\n", rbuf);
}
close(fd);
return 0;
}
result
Reference Count
int main(void)
{
....
fd = open("file1.txt", O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
....
if(fork() == 0){ /* Child */
....
}
else { /* Parent */
....
}
close(fd);
return 0;
}
+ open()은 한 번, close()는 두 번하는데 가능한가?
+ reference count를 통해 해당 file을 참조하고 있는 reference의 개수를 파악하고 있기 때문에, open()은 한 번만 했지만, close()를 두 번해도 상관이 없다.
Atomicity
Wtihin the context of single function calls, standard I/O operatios are atomic
- 하나의 write call에 대해서는 atomicity를 보장한다.
- read()는 signal에 의한 interrupt로 인해 끊길 수 있기 때문에 while문으로 감싸주어서 byte단위로 끊기는 것을 방지한다.
- 일반적인 standard I/O작업에 대해서는 atomicity를 보장한다.
'[학교 수업] > [학교 수업] 시스템 프로그래밍' 카테고리의 다른 글
[시스템 프로그래밍] Memory Mapped I/O (1) | 2024.11.24 |
---|---|
[시스템 프로그래밍] Multiplexed I/O (0) | 2024.11.24 |
[시스템 프로그래밍] File I/O (0) | 2024.11.13 |
[시스템 프로그래밍] Miscellaneous Thread Topics (1) | 2024.11.12 |
[시스템 프로그래밍] Thread Synchronization (0) | 2024.10.31 |