File I/O
File Input/Output (File I/O) forms the foundation of UNIX system programming. UNIX follows the principle that most objects can be accessed through file descriptors, including regular files, directories, pipes, terminals, sockets, and many devices. Applications interact with these objects using standardized POSIX system calls.
Learning Objectives
- Understand the UNIX File I/O model.
- Learn file descriptors.
- Study the basic file system calls.
- Understand how files are opened and closed.
- Prepare for advanced file operations.
1. What is File I/O?
File I/O refers to the mechanism through which applications read data from files and write data to files. In UNIX, File I/O is performed using unbuffered system calls provided by the operating system kernel. These system calls operate directly on file descriptors.
2. UNIX File Model
UNIX treats many resources as files. The same programming interfaces can be used to access regular files, terminals, pipes, sockets, and numerous device files. This unified design simplifies application development by providing a common I/O interface.
3. File Descriptors
A file descriptor is a small non-negative integer returned by the kernel when a file is successfully opened. Applications use the file descriptor to identify an open file when performing subsequent I/O operations.
4. Standard File Descriptors
| Descriptor | Name | Purpose |
|---|---|---|
| 0 | Standard Input | Read input. |
| 1 | Standard Output | Write normal output. |
| 2 | Standard Error | Write error messages. |
5. File Descriptor Table
Each process maintains a file descriptor table. Entries in this table reference open file descriptions maintained by the kernel.
6. open()
The POSIX open() system call opens an existing
file or creates a new file when appropriate flags are
specified.
On success, it returns a file descriptor.
| Prototype |
|---|
int open(const char *pathname,
int flags);
|
7. creat()
The creat() system call creates a new file for
writing or truncates an existing file before opening it.
Modern applications typically achieve the same behavior using
open() with appropriate flags.
8. close()
The POSIX close() system call releases a file
descriptor.
After a successful call, the descriptor no longer refers to
the previously opened file.
9. Basic File Operation Workflow
10. Practical POSIX Example
The POSIX open() function returns a file
descriptor that is later released using
close().
11. Interview Questions
- What is File I/O?
- What is a file descriptor?
- Which file descriptors are reserved by default?
- What does
open()return? - What happens if
open()fails? - What is the purpose of
creat()? - Why should
close()always be called? - Can sockets use file descriptors?
- What does the kernel maintain for each process?
- Why is the UNIX File I/O model considered uniform?
12. read()
The POSIX read() system call transfers data from
an open file descriptor into a memory buffer.
The number of bytes actually read may be less than the number
requested.
| Prototype |
|---|
ssize_t read(int fd,
void *buf,
size_t count);
|
13. write()
The POSIX write() system call transfers data from
a memory buffer to an open file descriptor.
The number of bytes written may be smaller than the requested
count.
| Prototype |
|---|
ssize_t write(int fd,
const void *buf,
size_t count);
|
14. Return Values
| Function | Success | Failure |
|---|---|---|
read() |
Number of bytes read | -1 |
write() |
Number of bytes written | -1 |
15. End-of-File (EOF)
When read() reaches the end of a file, it returns
0. This indicates that no more data is available
to be read.
16. Partial Reads and Writes
A successful read() or write() call
does not guarantee that the requested number of bytes was
transferred.
Applications should always check the returned byte count and,
if necessary, continue reading or writing until the desired
amount of data has been processed.
17. File Offset
Each open file description maintains a current file offset.
Successful read() and write()
operations automatically advance the file offset by the number
of bytes transferred.
18. lseek()
The POSIX lseek() system call changes the current
file offset associated with an open file descriptor.
It allows applications to reposition the offset before
performing subsequent I/O operations.
| Prototype |
|---|
off_t lseek(int fd,
off_t offset,
int whence);
|
19. lseek() Reference Positions
| Constant | Description |
|---|---|
SEEK_SET |
Beginning of the file. |
SEEK_CUR |
Current file offset. |
SEEK_END |
End of the file. |
20. Practical POSIX Example
The program opens a file, reads data into a buffer, and then closes the file descriptor.
21. Linux Commands
| Command | Description |
|---|---|
cat file.txt |
Display file contents. |
head file.txt |
Display the beginning of a file. |
tail file.txt |
Display the end of a file. |
wc -c file.txt |
Count bytes in a file. |
od -c file.txt |
Display file contents in character format. |
22. Interview Questions
- What does
read()return on success? - What indicates End-of-File?
- Why can partial reads occur?
- Why should the return value of
write()always be checked? - What is the purpose of
lseek()? - What is the difference between
SEEK_SETandSEEK_CUR? - What happens to the file offset after a successful read?
- Can
lseek()move beyond the end of a file? - What is an open file description?
- Which POSIX functions modify the current file offset?
23. Sparse Files (File Holes)
A sparse file contains regions where no actual data has been written. These unwritten regions are known as file holes. The filesystem records these regions efficiently without allocating physical storage until data is written.
24. File Descriptor Duplication
File descriptor duplication creates an additional file descriptor that refers to the same open file description. Both descriptors share the same file offset and file status flags.
25. dup()
The POSIX dup() function duplicates an existing
file descriptor and returns the lowest-numbered available
descriptor.
| Prototype |
|---|
int dup(int oldfd);
|
26. dup2()
The POSIX dup2() function duplicates a file
descriptor onto a specified descriptor number.
If the target descriptor is already open, it is closed before
being reused.
| Prototype |
|---|
int dup2(int oldfd,
int newfd);
|
27. dup3()
The dup3() function performs descriptor
duplication similar to dup2() while allowing
additional flags to be specified.
| Availability | Category |
|---|---|
| Linux | Linux Extension (Not POSIX) |
28. Descriptor Sharing
| Property | Shared? |
|---|---|
| File Offset | Yes |
| File Status Flags | Yes |
| Descriptor Flags | No |
Duplicated descriptors reference the same open file description, so changes to the file offset are visible through both descriptors.
29. fcntl()
The POSIX fcntl() function performs a variety of
operations on open file descriptors, including duplication,
retrieving descriptor flags, modifying file status flags, and
managing advisory file locks.
| Prototype |
|---|
int fcntl(int fd,
int cmd,
...);
|
30. Common fcntl() Commands
| Command | Purpose |
|---|---|
F_DUPFD |
Duplicate a file descriptor. |
F_GETFD |
Read descriptor flags. |
F_SETFD |
Modify descriptor flags. |
F_GETFL |
Read file status flags. |
F_SETFL |
Modify file status flags. |
31. Common File Status Flags
| Flag | Description |
|---|---|
O_RDONLY |
Open for reading only. |
O_WRONLY |
Open for writing only. |
O_RDWR |
Open for reading and writing. |
O_APPEND |
Append writes to the end of the file. |
O_NONBLOCK |
Enable non-blocking I/O. |
32. Practical POSIX Example
The program duplicates the standard output file descriptor and closes the duplicated descriptor before terminating.
33. Linux Commands
| Command | Description |
|---|---|
ls -ls sparse_file |
Compare logical and allocated file size. |
stat file.txt |
Display file metadata. |
file file.txt |
Identify the file type. |
hexdump -C file.txt |
Display file contents in hexadecimal. |
od -x file.txt |
Display file contents as hexadecimal words. |
34. Interview Questions
- What is a sparse file?
- Why are sparse files storage efficient?
- What does
dup()return? - How does
dup2()differ fromdup()? - Is
dup3()part of POSIX? - Which properties are shared by duplicated descriptors?
- What is the purpose of
fcntl()? - What is the difference between descriptor flags and file status flags?
- Which
fcntl()command retrieves file status flags? - What does
O_APPENDdo?
35. pread()
The POSIX pread() function reads data from a
specified file offset without changing the current file
offset associated with the open file description.
This makes it useful when multiple parts of a program need
to access different regions of the same file independently.
36. pwrite()
The POSIX pwrite() function writes data at a
specified file offset without modifying the current file
offset.
This enables writing to arbitrary locations in a file while
preserving the existing file position.
37. Scatter/Gather I/O
Scatter/Gather I/O allows multiple memory buffers to be processed using a single system call. This reduces the number of system calls required for complex data transfers.
38. readv() and writev()
| Function | Description |
|---|---|
readv() |
Reads data into multiple buffers. |
writev() |
Writes data from multiple buffers. |
39. Data Synchronization
Applications often write data into kernel buffers before it is stored on the underlying storage device. Synchronization functions request that buffered information be written to stable storage.
40. Synchronization Functions
| Function | Purpose |
|---|---|
sync() |
Requests synchronization of modified filesystem data. |
fsync() |
Synchronizes a specific file and its associated metadata. |
fdatasync() |
Synchronizes file data and required metadata. |
41. Atomic I/O Operations
An atomic operation is completed as a single indivisible action from the perspective of other processes. Certain POSIX file operations are defined to behave atomically under specified conditions, helping to avoid intermediate inconsistent states.
42. APUE / POSIX vs Linux
| Interface | Category |
|---|---|
open() |
POSIX |
read() |
POSIX |
write() |
POSIX |
close() |
POSIX |
lseek() |
POSIX |
pread() |
POSIX |
pwrite() |
POSIX |
readv() |
POSIX |
writev() |
POSIX |
dup3() |
Linux Extension |
43. Best Practices
- Always verify the return value of every I/O function.
- Handle partial reads and writes correctly.
- Close file descriptors when they are no longer needed.
- Prefer POSIX interfaces for portable applications.
- Use
pread()andpwrite()when file offsets must remain unchanged. - Use synchronization functions when persistent storage is required.
- Avoid unnecessary system calls by using scatter/gather I/O where appropriate.
44. Module Summary
This module introduced the POSIX File I/O model and covered file descriptors, opening and closing files, sequential reading and writing, file offsets, sparse files, descriptor duplication, descriptor management, synchronization operations, and scatter/gather I/O. These interfaces form the foundation for UNIX system programming and are widely used throughout higher-level libraries and applications.
45. Key Takeaways
- UNIX uses file descriptors to access open files and many other I/O objects.
open()returns a file descriptor used by subsequent I/O operations.read()andwrite()may transfer fewer bytes than requested.lseek()changes the current file offset for seekable files.dup()anddup2()duplicate file descriptors that share the same open file description.fcntl()manages descriptor properties and file status flags.pread()andpwrite()perform offset-based I/O without changing the current file position.readv()andwritev()support scatter/gather I/O using multiple buffers.fsync()andfdatasync()request synchronization of buffered data to storage.- Portable applications should rely primarily on standardized POSIX interfaces.
Concept Mastery Assessment — Module 17
Verify your conceptual understanding with 12 targeted technical assessment questions. Instant feedback and explanations are provided for every choice.