Chapter 48: File I/O System Calls: open()
and close()
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the concept of a file descriptor and its central role in Linux I/O.
- Implement the
open()
system call to establish access to files and devices. - Configure file access modes and creation flags to control I/O behavior.
- Utilize the
close()
system call to properly release system resources. - Develop robust C programs that perform basic file operations on an embedded Linux system like the Raspberry Pi 5.
- Debug common errors related to file access, permissions, and descriptor management.
Introduction
In the world of Linux and UNIX-like operating systems, a profound and elegant design philosophy prevails: everything is a file. This is not merely a convenient abstraction but a foundational principle that shapes how software interacts with hardware and system resources. Whether your program is reading sensor data from a I2C bus, writing to a serial console, sending data over a network socket, or accessing a traditional text file on an SD card, the underlying mechanism is the same. The kernel presents all these resources as file-like objects, providing a unified and consistent API for all input/output (I/O) operations. This elegant abstraction is the cornerstone of Linux’s power and flexibility, especially in the embedded domain.
This chapter introduces the two most fundamental system calls that serve as the gateway to this unified I/O model: open()
and close()
. The open()
system call is the key that unlocks access to a file, device, or other resource. It asks the kernel to prepare a resource for I/O and, if successful, returns a small, non-negative integer—a file descriptor—that acts as a handle for all subsequent operations. The close()
system call is its essential counterpart, informing the kernel that the program has finished with the resource, allowing it to release locks and free precious system resources. For an embedded system like the Raspberry Pi 5, where resources are often more constrained than on a desktop, mastering this simple yet powerful pair of functions is not just an academic exercise; it is a critical skill for writing efficient, reliable, and robust applications.
Technical Background
The File Descriptor: A Handle to the Kernel’s I/O World
To understand file I/O in Linux, one must first grasp the concept of the file descriptor. When a process successfully opens a file, the kernel creates an entry in a system-wide table of all open files, often called the global file table. This entry contains crucial information about the file, such as its location on the storage device (its inode), the current read/write offset, and the access modes for this particular open instance (e.g., read-only).
However, the process itself does not interact with this global table directly. Instead, the kernel allocates an entry in a second, per-process table called the file descriptor table. Each entry in this table simply points to an entry in the global file table. The index of this entry in the per-process table is the file descriptor—a simple integer. By convention, UNIX-like systems reserve the first three file descriptors for standard I/O streams:
- 0: Standard Input (
stdin
) – The default source of input, typically the keyboard. - 1: Standard Output (
stdout
) – The default destination for output, typically the screen or terminal. - 2: Standard Error (
stderr
) – The default destination for error messages, also typically the screen.
When you call open()
, the kernel finds the lowest-numbered unused slot in your process’s file descriptor table, sets it up to point to the correct entry in the global file table, and returns that slot number to you. From that point on, when you make other I/O system calls like read()
, write()
, or lseek()
, you simply pass this file descriptor. The kernel uses it to quickly look up all the necessary context from its internal tables.
Think of it like checking into a hotel. You give the front desk your name and details (the filename and flags). In return, you don’t get the entire hotel room itself, but a simple key card with a room number (the file descriptor). For the rest of your stay, you don’t need to repeat your personal details; you just present the key card to access the elevator, your room, and other hotel services. When you check out (close()
), you return the key card, and the hotel can assign that room to someone else. This level of abstraction is incredibly efficient. It decouples the program from the low-level details of the filesystem or device driver, allowing for clean, portable, and powerful code.

Unlocking the Door: The open()
System Call
The open()
system call is the primary function for gaining access to a file. Its prototype, found in the <fcntl.h>
header, is as follows:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, ...);
Let’s break down its arguments and return value.
const char *pathname
: This is a standard C string representing the path to the file you wish to open (e.g.,/home/pi/data.txt
or/dev/i2c-1
). The kernel uses this path to locate the resource within the filesystem hierarchy.int flags
: This integer argument is a bitmask that tells the kernel how you want to open the file. It is the most critical parameter, controlling both access mode and other operational behaviors. These flags are combined using the bitwise OR (|
) operator. The flags fall into two main categories: access mode flags and file creation flags....
: The optional third argument, amode_t mode
, is only required when theflags
argument includesO_CREAT
. It specifies the file permissions (e.g., read, write, execute for the owner, group, and others) for the newly created file. We will explore this in more detail shortly.
Access Mode Flags
You must specify exactly one of the following three flags to define the fundamental I/O permissions for the lifetime of the file descriptor:
O_RDONLY
: Open the file for read-only access. Any attempt to write to the resulting file descriptor will result in an error.O_WRONLY
: Open the file for write-only access. Any attempt to read from the descriptor will fail.O_RDWR
: Open the file for read-write access. Both reading and writing are permitted.
These flags are mutually exclusive in their base form. Attempting to combine them, such as O_RDONLY | O_WRONLY
, does not equal O_RDWR
and leads to undefined behavior. You must choose the single flag that represents the access you need.
File Creation and Operational Flags
In addition to the access mode, you can OR several other flags to modify the behavior of the open()
call. These are some of the most common and important ones in embedded development:
O_CREAT
: If the file specified bypathname
does not exist, it will be created. If it does exist, this flag has no effect unlessO_EXCL
is also specified. When usingO_CREAT
, you must provide the thirdmode
argument toopen()
to set the permissions of the new file.O_EXCL
: Used in conjunction withO_CREAT
, this flag ensures that the caller is the one who creates the file. If the file already exists,open()
will fail and set the globalerrno
variable toEEXIST
. This combination provides an “atomic” test-and-set operation, which is crucial for preventing race conditions where multiple processes might try to create the same file simultaneously.O_TRUNC
: If the file already exists and is successfully opened for writing (O_WRONLY
orO_RDWR
), its length is truncated to zero. All existing data is discarded. If the file does not exist, this flag has no effect.O_APPEND
: This flag forces all writes to occur at the end of the file, regardless of any priorlseek()
operations. This is essential for log files or any situation where you must guarantee that data is only ever added to the end. The kernel ensures that positioning the file offset and writing the data is an atomic operation, preventing data from being interleaved and corrupted if multiple processes are appending to the same file.O_NONBLOCK
: When opening a resource that can block, such as a FIFO (named pipe) or a serial device, this flag causes theopen()
call (and subsequent I/O calls) to return immediately without waiting. If opening the resource would normally require waiting for another process or for a hardware condition, the call will either succeed immediately or fail witherrno
set toENXIO
. This is a cornerstone of writing non-blocking, event-driven applications.
The mode
Argument
When O_CREAT
is used, the third argument to open()
becomes mandatory. This mode_t mode
argument specifies the file permissions for the new file. These permissions are represented as an octal number, similar to the chmod
command-line utility. The permissions are defined in <sys/stat.h>
and common values include:
A common combination is 0644
in octal, which is equivalent to S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
. This grants read/write permission to the file’s owner and read-only permission to everyone else.
Tip: The permissions you specify with the
mode
argument are filtered by the process’s umask. The umask is a system setting that automatically turns off certain permission bits for newly created files. For example, a common umask of0022
will prevent group and other write permissions from being set, even if you request them in themode
argument. This is a security feature to prevent accidentally creating world-writable files.
Return Value and Error Handling
The return value of open()
is the key to its usage.
- On success,
open()
returns a new, non-negative integer file descriptor. - On failure,
open()
returns-1
.
When an error occurs, the function sets a global integer variable named errno
to a value that indicates the specific reason for the failure. The <errno.h>
header file defines symbolic names for these error codes (e.g., EACCES
for permission denied, ENOENT
for file not found).
A robust program must always check the return value of open()
. Ignoring a -1
return value and attempting to use it as a file descriptor will lead to unpredictable and often disastrous results, as other system calls will fail or, even worse, operate on an unintended file (since -1
is not a valid descriptor, but other negative values might be misinterpreted). The standard way to report these errors is by using the perror()
or strerror()
functions, which translate the errno
code into a human-readable error message.
#include <errno.h>
#include <string.h>
// ... inside a function
int fd = open("somefile.txt", O_RDONLY);
if (fd == -1) {
// An error occurred. Print a message and exit.
perror("Failed to open somefile.txt");
// Alternatively:
// fprintf(stderr, "Error opening file: %s\n", strerror(errno));
return 1; // Or exit(EXIT_FAILURE);
}
Closing the Door: The close()
System Call
Just as every open()
call consumes a resource, a corresponding close()
call is required to release it. The close()
system call de-allocates the file descriptor, making it available for reuse by subsequent open()
calls. It also decrements the reference count in the global file table entry. When this count drops to zero (meaning no process has this file open anymore), the kernel releases its in-memory structures related to the file.
Its prototype is deceptively simple:
#include <unistd.h>
int close(int fd);
int fd
: The file descriptor to be closed.
The return value is straightforward:
- On success,
close()
returns0
. - On failure,
close()
returns-1
and setserrno
.
While close()
can fail (for instance, if an NFS filesystem loses its connection while trying to flush buffered data), failures are rare in typical embedded use cases. However, it is still good practice to check the return value. The most common error is EBADF
, which indicates that the fd
argument was not a valid, open file descriptor. This often happens if you try to close a descriptor that has already been closed or was never valid in the first place (e.g., the original open()
call failed).
Forgetting to close file descriptors is a classic resource leak. In a long-running embedded application, this can be fatal. Each process has a limit on the number of open file descriptors it can have (viewable with ulimit -n
). If your application continuously opens files without closing them, it will eventually exhaust its file descriptor table. At that point, all subsequent calls to open()
, socket()
, or any other function that allocates a file descriptor will fail, likely causing the entire application to stop functioning.
Warning: When a process terminates, either by calling
exit()
or by returning frommain()
, the kernel automatically closes all of its open file descriptors. While this provides a safety net, it is not a substitute for disciplined programming. Explicitly closing every file descriptor you open is a hallmark of robust, maintainable, and professional code. It makes the resource lifecycle clear and prevents subtle bugs, especially in complex applications or long-running daemons.
Practical Examples
In this section, we will walk through several practical examples on the Raspberry Pi 5. These examples assume you are working directly on the Pi with a standard Raspberry Pi OS installation, which includes the GCC compiler and all necessary development tools. You can write the code in a text editor (like nano
or vim
) and compile it directly on the command line.
flowchart TD subgraph "File I/O Workflow" A(Start) --> B{"Call open(path, flags, mode)"}; style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff B --> C{fd == -1?}; style C fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff C -- "Yes (Error)" --> D["perror('open failed')<br>Exit Program"]; style D fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff C -- "No (Success)" --> E["Use fd for I/O operations"]; style E fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff E --> F{"Loop:<br>read() or write()"}; style F fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff F --> G{I/O Finished?}; style G fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff G -- No --> F; G -- Yes --> H{"Call close(fd)"}; style H fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff H --> I{"close() == -1?"}; style I fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff I -- "Yes (Error)" --> J["perror('close failed')<br>Report Error"]; style J fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937 I -- "No (Success)" --> K(End); style K fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff J --> K; end
Example 1: Creating and Writing to a New File
This first example demonstrates the most basic use case: creating a new file, writing a short string to it, and closing it. This pattern is fundamental for creating log files, configuration files, or storing output data.
Code (create_file.c
)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main() {
const char *filepath = "rpi_test.txt";
const char *content = "Hello from Raspberry Pi 5!\n";
int fd; // File descriptor
// Define the flags for opening the file
// O_WRONLY: Open for write-only access.
// O_CREAT: Create the file if it does not exist.
// O_TRUNC: If the file exists, truncate it to zero length.
int flags = O_WRONLY | O_CREAT | O_TRUNC;
// Define the permissions for the new file (if created)
// 0644: Owner can read/write, group and others can only read.
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // or just 0644
printf("Attempting to open '%s' for writing...\n", filepath);
// Open the file
fd = open(filepath, flags, mode);
// ALWAYS check the return value of open()
if (fd == -1) {
perror("Error opening file");
return EXIT_FAILURE;
}
printf("File opened successfully. File descriptor is: %d\n", fd);
// Write the content to the file
ssize_t bytes_written = write(fd, content, strlen(content));
if (bytes_written == -1) {
perror("Error writing to file");
close(fd); // Attempt to close even on error
return EXIT_FAILURE;
}
printf("%zd bytes written to the file.\n", bytes_written);
// Close the file
if (close(fd) == -1) {
perror("Error closing file");
return EXIT_FAILURE;
}
printf("File closed successfully.\n");
return EXIT_SUCCESS;
}
Build and Execution Steps
- Save the Code: Save the code above into a file named
create_file.c
. - Compile: Open a terminal on your Raspberry Pi 5 and compile the program using GCC. The
-o
flag specifies the name of the output executable.gcc create_file.c -o create_file
- Run the Program: Execute the compiled program.
./create_file
Expected Output
You should see the following output on your terminal:
Attempting to open 'rpi_test.txt' for writing...
File opened successfully. File descriptor is: 3
28 bytes written to the file.
File closed successfully.
Note: The file descriptor value
3
is typical because descriptors 0, 1, and 2 are already in use forstdin
,stdout
, andstderr
by your shell.
- Verify the Result: Use the
ls -l
command to check that the file was created with the correct permissions and use thecat
command to view its contents.ls -l rpi_test.txt
Output should look like this (owner/group might differ):-rw-r–r– 1 pi pi 28 Jul 22 02:24 rpi_test.txtcat rpi_test.txt
Output:Hello from Raspberry Pi 5!
Example 2: Reading from an Existing File
This example shows how to open an existing file for reading and display its contents to standard output. It complements the previous example.
Code (read_file.c
)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define BUFFER_SIZE 128
int main() {
const char *filepath = "rpi_test.txt";
int fd;
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
printf("Attempting to open '%s' for reading...\n", filepath);
// Open the file for read-only access.
// No O_CREAT, so open() will fail if the file doesn't exist.
fd = open(filepath, O_RDONLY);
if (fd == -1) {
perror("Error opening file");
return EXIT_FAILURE;
}
printf("File opened successfully. File descriptor is: %d\n", fd);
// Read from the file into the buffer
bytes_read = read(fd, buffer, BUFFER_SIZE - 1); // Leave space for null terminator
if (bytes_read == -1) {
perror("Error reading from file");
close(fd);
return EXIT_FAILURE;
}
// Null-terminate the buffer to treat it as a string
buffer[bytes_read] = '\0';
printf("Read %zd bytes. Content:\n---\n%s\n---\n", bytes_read, buffer);
// Close the file
if (close(fd) == -1) {
perror("Error closing file");
return EXIT_FAILURE;
}
printf("File closed successfully.\n");
return EXIT_SUCCESS;
}
Build and Execution Steps
- Prerequisite: Ensure the file
rpi_test.txt
exists from running the first example. - Save and Compile: Save the code as
read_file.c
and compile it.gcc read_file.c -o read_file
- Run: Execute the program.
./read_file
Expected Output
Attempting to open 'rpi_test.txt' for reading...
File opened successfully. File descriptor is: 3
Read 28 bytes. Content:
---
Hello from Raspberry Pi 5!
---
File closed successfully.
Example 3: Using O_EXCL
for Safe File Creation
This example demonstrates how to use the O_CREAT | O_EXCL
flag combination to safely create a “lock” file. This is a common pattern in shell scripting and system daemons to ensure that only one instance of a program is running at a time.
flowchart TD subgraph "Atomic Lock File Creation" A(Start) --> B{"open(path, O_CREAT | O_EXCL)"}; style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff B --> C{"open() failed?"}; style C fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff C -- "No (Success)" --> D["File was created.<br><b>Lock acquired.</b><br>Proceed with critical section."]; style D fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff C -- "Yes (Failure)" --> E{errno == EEXIST?}; style E fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff E -- "Yes" --> F["<b>Lock is already held.</b><br>Another instance is running.<br>Exit or wait."]; style F fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff E -- "No" --> G["An unexpected error occurred.<br>(e.g., Permission Denied)<br>Handle other error."]; style G fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937 D --> H("Release lock:<br>close(fd), unlink(path)"); style H fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff H --> I(End); F --> I; G --> I; style I fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff end
Code (lock_file.c
)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main() {
const char *lockfile = "/tmp/my_app.lock";
int fd;
printf("Attempting to create lock file: %s\n", lockfile);
// Atomically create the file. Fails if it already exists.
fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd == -1) {
// EEXIST is the expected error if the lock is held
if (errno == EEXIST) {
fprintf(stderr, "Error: Lock file already exists. Another instance may be running.\n");
} else {
// Another unexpected error occurred
perror("Error creating lock file");
}
return EXIT_FAILURE;
}
printf("Lock file created successfully. FD: %d\n", fd);
printf("Application is now 'running'. Press Enter to release lock and exit.\n");
// In a real application, the main logic would go here.
// We just wait for user input as a placeholder.
getchar();
// Clean up by closing and deleting the lock file.
close(fd);
if (unlink(lockfile) == -1) {
perror("Warning: Failed to delete lock file");
}
printf("Lock released. Exiting.\n");
return EXIT_SUCCESS;
}
Build and Execution Steps
- Save and Compile: Save the code as
lock_file.c
and compile it.gcc lock_file.c -o lock_file
- First Run: Execute the program in one terminal.
./lock_file
Terminal 1 Output:Attempting to create lock file: /tmp/my_app.lock
Lock file created successfully. FD: 3
Application is now 'running'. Press Enter to release lock and exit.
The program will now wait. - Second Run: While the first instance is running, open a second terminal and try to run the program again.
./lock_file
Terminal 2 Output:Attempting to create lock file: /tmp/my_app.lock
Error: Lock file already exists. Another instance may be running.
This second instance fails immediately, as intended. - Cleanup: Go back to the first terminal and press
Enter
. The program will exit, and the lock file will be removed. You can then run the program again successfully.
Common Mistakes & Troubleshooting
Even with simple functions like open()
and close()
, several common pitfalls can trip up new and experienced developers alike. Understanding these issues is key to writing robust code.
Exercises
- File Existence Checker:
- Objective: Write a C program named
check_exist.c
that takes a single command-line argument (a file path). - Steps:
- The program should attempt to
open()
the file inO_RDONLY
mode. - If the
open()
call succeeds, print a message “File ‘[filename]’ exists.” and thenclose()
the file descriptor. - If
open()
fails, checkerrno
. Iferrno
isENOENT
, print “File ‘[filename]’ does not exist.” If it’s another error (likeEACCES
), useperror()
to print the specific error.
- The program should attempt to
- Verification: Run your program with the path to an existing file, a non-existent file, and a file you don’t have permission to read (e.g.,
/etc/shadow
).
- Objective: Write a C program named
- Append to a Log File:
- Objective: Create a program
log_append.c
that appends a timestamped message to a log file. - Steps:
- Define a log file path, e.g.,
/tmp/app.log
. - Open the file using the flags
O_WRONLY | O_CREAT | O_APPEND
. Use permissions0644
. - If successful, create a message string containing the current time (you can use the
time()
andctime()
functions for this). - Write this message to the file and close it.
- Define a log file path, e.g.,
- Verification: Run the program multiple times. Use
cat /tmp/app.log
to verify that each run adds a new line to the end of the file without overwriting the previous content.
- Objective: Create a program
- Conditional File Creation:
- Objective: Write a program
create_if_not_exist.c
that creates a configuration file, but only if it doesn’t already exist. It should write a default setting into the file upon creation. - Steps:
- Attempt to open a file (e.g.,
config.ini
) usingO_WRONLY | O_CREAT | O_EXCL
. - If the call succeeds, it means the file was newly created. Write a default line like “VOLUME=10\n” into the file, then close it. Print a message “config.ini created with default settings.”
- If the call fails because
errno
isEEXIST
, print a message “config.ini already exists. No action taken.”
- Attempt to open a file (e.g.,
- Verification: Run the program once and check the file’s content. Run it a second time and verify that the content is unchanged and the correct message is displayed.
- Objective: Write a program
- File Descriptor Inspector:
- Objective: Write a program
inspect_fds.c
to see how file descriptors are allocated. - Steps:
- Open a file (e.g.,
file1.txt
) and print its file descriptor. Do not close it yet. - Open another file (e.g.,
file2.txt
) and print its file descriptor. - Open a third file (e.g.,
file3.txt
) and print its descriptor. - Now,
close()
the second file descriptor (forfile2.txt
). - Finally, open a fourth file (e.g.,
file4.txt
) and print its descriptor. Observe which number is reused. - Close all remaining open file descriptors before exiting.
- Open a file (e.g.,
- Verification: The output should show sequentially increasing file descriptors (e.g., 3, 4, 5). After closing descriptor 4, the next
open()
should reuse it, and the new descriptor should also be 4.
- Objective: Write a program
- Simulating a
cat
Utility:- Objective: Create a simplified version of the
cat
utility namedmy_cat.c
that reads a file specified on the command line and prints its entire content to standard output. - Steps:
- Check that a filename was provided as a command-line argument.
open()
the specified file for reading (O_RDONLY
).- In a loop,
read()
data from the file into a buffer (e.g., 4096 bytes). - The
read()
call returns the number of bytes actually read. If this number is greater than 0,write()
that many bytes from the buffer to standard output (file descriptor1
). - The loop should continue until
read()
returns 0 (end of file) or -1 (error). - Handle all errors and
close()
the file descriptor before exiting.
- Verification: Run
./my_cat /etc/os-release
and compare its output to running the realcat /etc/os-release
. They should be identical.
- Objective: Create a simplified version of the
Summary
- Unified I/O Model: In Linux, every resource, including hardware devices and network connections, can be treated as a file.
- File Descriptors: A file descriptor is a non-negative integer returned by
open()
that serves as a handle for all subsequent I/O operations on that resource. The first three descriptors (0, 1, 2) are reserved forstdin
,stdout
, andstderr
. open()
System Call: This is the gateway to file I/O. It takes apathname
and a set offlags
as arguments. Theflags
control the access mode (O_RDONLY
,O_WRONLY
,O_RDWR
) and other behaviors (O_CREAT
,O_TRUNC
,O_APPEND
,O_EXCL
).mode
Argument: When creating a file withO_CREAT
, a thirdmode
argument is mandatory to specify the file’s access permissions (e.g.,0644
).close()
System Call: This function is essential for releasing a file descriptor and its associated kernel resources. Failing to close descriptors leads to resource leaks.- Error Handling: It is critical to always check the return value of system calls. A return of
-1
indicates an error, and the globalerrno
variable contains a code specifying the cause.perror()
is the standard tool for printing human-readable error messages.
Further Reading
open(2)
Linux Programmer’s Manual: The definitive, authoritative documentation for theopen
system call. Accessible on your Linux system viaman 2 open
or at https://man7.org/linux/man-pages/man2/open.2.htmlclose(2)
Linux Programmer’s Manual: The official man page for theclose
system call. Accessible viaman 2 close
.- The Linux Programming Interface by Michael Kerrisk. Chapters 4 and 5 provide an exhaustive and highly respected treatment of file I/O and system calls.
- Advanced Programming in the UNIX Environment by W. Richard Stevens and Stephen A. Rago. A classic text that provides deep insight into the design philosophy of UNIX-style I/O.
- Raspberry Pi Documentation – The Linux kernel: Official documentation from the Raspberry Pi Foundation regarding their customized Linux kernel, useful for understanding device-specific behavior. https://www.raspberrypi.com/documentation/computers/linux_kernel.html
- Buildroot User Manual: While not directly about system calls, understanding how an embedded Linux system is built with tools like Buildroot provides context for where device files and filesystems originate. https://buildroot.org/downloads/manual/manual.html
- LWN.net: A premier source for in-depth articles about Linux kernel development, including detailed explanations of how system calls are implemented and evolve. https://lwn.net