Unit3 - Subjective Questions
CSE325 • Practice Questions with Detailed Answers
Define the open() system call in Linux. Explain its syntax and common flags used.
The open() system call is used to establish a connection between a file and a file descriptor. It creates an open file description that refers to a file and a file descriptor that refers to that open file description.
Syntax:
c
include <fcntl.h>
int open(const char pathname, int flags);
int open(const char pathname, int flags, mode_t mode);
Common Flags:
- O_RDONLY: Open the file for reading only.
- O_WRONLY: Open the file for writing only.
- O_RDWR: Open the file for both reading and writing.
- O_CREAT: Create the file if it does not exist. Requires the
modeargument. - O_APPEND: Append data to the end of the file.
- O_TRUNC: If the file already exists and is a regular file and the access mode allows writing, it will be truncated to length 0.
Explain the read() system call with its prototype and return values.
The read() system call attempts to read up to a specified number of bytes from a file descriptor into a buffer.
Prototype:
c
include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
Parameters:
- fd: The file descriptor to read from.
- buf: The buffer where the read data is stored.
- count: The maximum number of bytes to read.
Return Values:
- Positive Integer (>0): The number of bytes successfully read. This may be less than
countif fewer bytes are available. - Zero (0): Indicates End of File (EOF).
- -1: Indicates an error occurred (e.g., invalid fd, interrupted call).
errnois set appropriately.
Describe the write() system call. How does it differ from library functions like printf or fprintf?
write() writes data from a buffer to a file referred to by the file descriptor.
Syntax:
c
ssize_t write(int fd, const void *buf, size_t count);
Difference between write() and library functions:
- System Call vs Library:
writeis a kernel-level system call, whileprintfis a standard C library function (stdio.h). - Buffering:
printfis usually buffered (data is collected in a buffer before being written to the OS), whereaswritesends data directly to the kernel. - File Handle:
writeuses an integer file descriptor (fd), whilefprintfuses aFILE *stream structure. - Overhead: System calls involve a context switch (user mode to kernel mode), making them computationally more expensive per call compared to buffered library calls.
What is the purpose of the close() system call? What happens if a process fails to close a file?
Purpose:
The close() system call allows a process to release a file descriptor so that it is no longer referred to by that process. It frees the unused file descriptor for reuse.
Syntax:
c
int close(int fd);
Consequences of failing to close:
- Resource Leak: File descriptors are a finite resource. If a program opens files in a loop without closing them, it will eventually run out of descriptors (error:
Too many open files). - Data Integrity: Although the kernel typically closes files upon process termination, relying on this is bad practice. Explicitly closing ensures buffers are flushed properly (in the context of library wrappers) and locks are released immediately.
Explain the lseek() system call. Discuss the significance of the whence parameter.
lseek() repositions the file offset of the open file description associated with the file descriptor.
Syntax:
c
off_t lseek(int fd, off_t offset, int whence);
Significance of whence:
The whence parameter determines how the new offset is calculated:
- SEEK_SET: The file offset is set to
offsetbytes from the beginning of the file. - SEEK_CUR: The file offset is set to its current location plus
offsetbytes. - SEEK_END: The file offset is set to the size of the file plus
offsetbytes.
Return Value: Upon success, it returns the resulting offset location as measured in bytes from the beginning of the file. On error, it returns -1.
Write a C program using system calls to copy the contents of one file to another. The file names should be provided as command-line arguments.
c
include <fcntl.h>
include <unistd.h>
include <stdio.h>
include <stdlib.h>
define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int sourceFd, destFd;
ssize_t bytesRead, bytesWritten;
char buffer[BUFFER_SIZE];
if (argc != 3) {
printf("Usage: %s <source> <destination>\
", argv[0]);
return 1;
}
// Open source file for reading
sourceFd = open(argv[1], O_RDONLY);
if (sourceFd == -1) {
perror("Error opening source file");
return 1;
}
// Open dest file for writing, create if not exists, truncate if it does
// Mode 0644 gives read/write to owner, read to group/others
destFd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (destFd == -1) {
perror("Error opening destination file");
close(sourceFd);
return 1;
}
// Read from source and write to destination
while ((bytesRead = read(sourceFd, buffer, BUFFER_SIZE)) > 0) {
bytesWritten = write(destFd, buffer, bytesRead);
if (bytesWritten != bytesRead) {
perror("Write error");
close(sourceFd);
close(destFd);
return 1;
}
}
if (bytesRead == -1) {
perror("Read error");
}
close(sourceFd);
close(destFd);
return 0;
}
What are File Descriptors? Which file descriptors are opened by default for a process?
File Descriptors (FD):
A file descriptor is a non-negative integer that uniquely identifies an open file in a computer's operating system. It acts as a handle or index into the process's file descriptor table, which points to the system-wide open file table.
Default File Descriptors:
When a program starts, the shell typically opens three file descriptors automatically:
- 0 (Standard Input / stdin): Used for reading input (default: keyboard).
- 1 (Standard Output / stdout): Used for writing output (default: monitor/screen).
- 2 (Standard Error / stderr): Used for writing error messages (default: monitor/screen).
These are often defined as macros: STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO in <unistd.h>.
Explain the concept of File Holes (Sparse Files) and how lseek is used to create them.
File Holes:
A file hole occurs when a program seeks past the end of a file and writes data at a later position. The gap between the old end-of-file and the new write position is called a "hole." Ideally, the file system does not allocate physical disk blocks for the hole; it simply marks the range as zero bytes.
Creation using lseek:
- Open a file.
- Write some data.
- Call
lseekwith an offset well beyond the current end of the file (e.g.,lseek(fd, 1000, SEEK_END)). - Call
writeto write data at the new position.
When the file is read later, the OS returns null bytes (\0) for the hole area. This is useful for creating large sparse files (e.g., disk images) without consuming actual disk space for empty regions.
Differentiate between O_APPEND and using lseek to the end of a file before writing. Why is O_APPEND preferred in concurrent environments?
Difference:
- lseek + write: The process manually moves the pointer to the end (
lseek(fd, 0, SEEK_END)) and then callswrite(). These are two separate system calls. - O_APPEND: The file offset is set to the end of the file atomically by the kernel immediately before each write operation.
Concurrency Preference:
In a multi-process or multi-threaded environment where multiple processes write to the same file (e.g., a log file), using lseek then write leads to a Race Condition.
- Process A lseeks to end.
- Context switch occurs.
- Process B lseeks to end and writes data.
- Process A writes data.
Result: Process A overwrites Process B's data because the "end" moved.O_APPENDensures the seek and write happen atomically, preventing data corruption.
What is the role of the mode argument in the open system call? Give an example of setting permissions to rw-r--r--.
Role of Mode:
The mode argument specifies the file permissions (access rights) to be used if a new file is created. It is only used when O_CREAT is specified in the flags. It is modified by the process's umask (actual permissions = mode & ~umask).
Permissions Structure:
- User (Owner)
- Group
- Others
Example for rw-r--r-- (Read/Write for Owner, Read for Group, Read for Others):
- Octal:
0644 - Macros (sys/stat.h):
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
Code Example:
c
int fd = open("myfile.txt", O_CREAT | O_WRONLY, 0644);
Write a C program snippet to read the last 10 bytes of a file using lseek and read.
c
include <fcntl.h>
include <unistd.h>
include <stdio.h>
int main() {
int fd = open("data.txt", O_RDONLY);
if (fd < 0) {
perror("Open failed");
return 1;
}
char buffer[11]; // 10 bytes + null terminator
// Move cursor to 10 bytes before the end of file
// -10 offset relative to SEEK_END
if (lseek(fd, -10, SEEK_END) == -1) {
perror("Seek failed");
close(fd);
return 1;
}
// Read the 10 bytes
ssize_t bytesRead = read(fd, buffer, 10);
if (bytesRead > 0) {
buffer[bytesRead] = '\0'; // Null terminate for printing
printf("Last 10 bytes: %s\
", buffer);
}
close(fd);
return 0;
}
Explain the error handling mechanism for file system calls. How is errno used?
Error Return Values:
Most file system calls (open, read, write, lseek) return -1 to indicate an error occurred.
The errno Variable:
When a system call fails, the OS sets a global integer variable called errno (defined in <errno.h>) to a specific error code indicating the reason for failure.
Common Error Codes:
ENOENT: No such file or directory.EACCES: Permission denied.EBADF: Bad file descriptor.
Usage:
Programmers can use perror("message") to print a human-readable error description to standard error, or strerror(errno) to get the string representation of the error code.
Example:
c
if (open("nonexistent.txt", O_RDONLY) == -1) {
perror("Error opening file"); // Prints: "Error opening file: No such file or directory"
}
Explain the relationship between the File Descriptor Table, the File Table, and the Inode Table.
The Unix/Linux file system uses three data structures to manage open files:
-
Process File Descriptor Table:
- Local to each process.
- Maps the integer File Descriptor (fd) to a pointer in the System-wide File Table.
-
System-wide Open File Table (File Table):
- Contains entries for every open file in the system.
- Stores the File Offset (current read/write position) and File Status Flags (O_RDONLY, etc.).
- Multiple file descriptors (from different processes or
dupin the same process) can point to the same entry here.
-
Inode Table:
- Contains the actual file metadata (permissions, owner, size, location on disk).
- The File Table entry points to the Inode.
Significance: If two processes open the same file independently, they have different entries in the File Table (separate offsets). If they fork(), the child inherits the fd pointing to the same File Table entry (shared offset).
How can a program create a file that ensures it fails if the file already exists? Which flag combination is used?
To create a file ensuring that the operation fails if the file already exists (preventing accidental overwriting), the O_EXCL flag is used in conjunction with O_CREAT.
Flag Combination: O_CREAT | O_EXCL
Behavior:
- If the file does not exist: The file is created.
- If the file does exist: The
open()call returns-1anderrnois set toEEXIST.
Usage:
c
int fd = open("lockfile", O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd == -1) {
// File exists or other error
}
This mechanism is often used for file locking.
What does the O_TRUNC flag do when used with the open system call?
The O_TRUNC flag stands for "Truncate".
Behavior:
When O_TRUNC is specified along with a write access mode (e.g., O_WRONLY or O_RDWR):
- If the file does not exist, it behaves according to other flags (e.g., creates it if
O_CREATis present). - If the file already exists and is a regular file, its length is truncated to 0 bytes.
- All existing data in the file is discarded.
Use Case: This is commonly used when you want to overwrite a file completely with new content, ensuring no remnants of the old data remain.
Write a C program that writes the string "Hello OS" to the Standard Output (screen) using the write() system call instead of printf.
c
include <unistd.h>
include <string.h>
int main() {
const char *msg = "Hello OS\
";
// STDOUT_FILENO is the macro for file descriptor 1 (Standard Output)
// We use strlen to calculate the exact number of bytes to write
ssize_t bytesWritten = write(STDOUT_FILENO, msg, strlen(msg));
if (bytesWritten == -1) {
// Handle error (though writing to stderr usually uses write as well)
return 1;
}
return 0;
}
Explain the behavior of read() when the requested number of bytes (count) is larger than the actual bytes remaining in the file.
When read(fd, buffer, count) is called:
Scenario: The file pointer is near the end of the file, and the number of bytes remaining (let's say ) is less than the requested count.
Behavior:
- The system reads the available bytes into the buffer.
- The function returns the integer (which is ).
- The file offset is advanced by bytes (reaching the end of the file).
- The next call to
read()will return0, indicating End-of-File (EOF).
Therefore, a return value smaller than count (but greater than 0) does not necessarily indicate an error; it usually indicates that the end of the file was reached or, for devices like terminals/pipes, that a line/packet was processed.
What is the maximum number of files a process can open? How can this limit be checked or changed?
Limit:
The OS imposes a limit on the number of file descriptors a single process can have open simultaneously. This prevents a single process from exhausting system resources.
Checking the limit:
- Command Line:
ulimit -n - Programmatically: Using
sysconf(_SC_OPEN_MAX)orgetrlimit(RLIMIT_NOFILE, &limit).
Changing the limit:
- Soft Limit: Can be increased by the process up to the Hard Limit.
- Hard Limit: Can only be increased by a privileged process (root).
- Error: If the limit is reached,
open()returns -1 and setserrnotoEMFILE(Too many open files).
Compare Buffered I/O vs Unbuffered I/O in the context of file system calls.
| Feature | Buffered I/O (Standard I/O) | Unbuffered I/O (System I/O) |
|---|---|---|
| Functions | fopen, fread, fwrite, fprintf |
open, read, write |
| Level | High-level (C Standard Library) | Low-level (OS Kernel System Calls) |
| Data Handling | Uses internal buffers (user space) to reduce system calls. Data is flushed only when buffer is full or explicitly requested. | Writes directly to the kernel (kernel buffers). |
| Efficiency | Generally faster for many small reads/writes due to fewer context switches. | Slower for many small writes due to context switch overhead per call. Faster for large block transfers. |
| Portability | Highly portable across OSs supporting C. | Specific to POSIX/Unix-like systems. |
| Handles | FILE * pointer. |
Integer File Descriptor (int fd). |
Describe the logic to determine the size of a file using lseek.
To determine the size of a file without reading the entire content, lseek can be used to move the file offset pointer to the very end.
Logic:
- Open the file using
open(). - Call
lseekwith offset0and whenceSEEK_END. - The return value of
lseekrepresents the current offset in bytes from the beginning of the file. Since we moved to the end, this value equals the total file size. - (Optional but recommended) Restore the file position to the beginning if further reading is required.
Code Snippet:
c
int fd = open("file.txt", O_RDONLY);
off_t fileSize = lseek(fd, 0, SEEK_END);
printf("Size: %ld bytes\
", fileSize);
close(fd);