Unit4 - Subjective Questions
CSE325 • Practice Questions with Detailed Answers
Explain the working of the fork() system call. What are the possible return values, and what do they signify?
The fork() system call is used to create a new process by duplicating the calling process. The new process is referred to as the child process, and the calling process is referred to as the parent process.
Working Mechanism:
- It creates an exact copy of the parent's memory space (code, data, stack, heap) for the child.
- Both processes continue execution from the instruction immediately following the
fork()call.
Return Values:
fork() returns an integer value (pid_t) to distinguish between the parent and the child:
- Negative Value (< 0): The creation of a child process was unsuccessful.
- Zero (0): Returned to the newly created child process.
- Positive Value (> 0): Returned to the parent process. This value represents the Process ID (PID) of the newly created child.
Differentiate between a Zombie Process and an Orphan Process. How does the operating system handle them?
1. Zombie Process:
- Definition: A process that has completed execution (via
exit()) but still has an entry in the process table. This happens because the parent has not yet retrieved the child's exit status usingwait(). - State: It is in a 'defunct' state.
- Handling: The parent must call
wait()to reap the zombie. If the parent ignores it, the zombie remains until the parent dies.
2. Orphan Process:
- Definition: A process that remains running after its parent process has terminated or finished execution.
- State: It is an active, running process without an original parent.
- Handling: The OS (specifically the
initprocess, PID 1) adopts the orphan process.initperiodically callswait()to clean up adopted children when they finish.
Describe the exec() family of functions. Why is exec() often used immediately after fork()?
The exec() family of functions replaces the current process image with a new process image. It loads a new program into the process's memory space.
Common Functions:
execl(),execv(),execle(),execvp(), etc.- They differ in how arguments are passed (list vs. array) and whether the environment or path is specified.
Relationship with fork():
exec() is often used after fork() because fork() only creates a copy of the parent. To run a different program (e.g., when a shell runs a command like ls), the shell forks a child, and the child calls exec() to replace its copied memory with the code of the ls program. The PID remains the same, but the code, data, and stack are replaced.
Write a C program to demonstrate the creation of a Zombie process. Explain the logic.
A Zombie process is created when a child exits, but the parent sleeps or does busy work without calling wait().
C Program:
c
include <stdio.h>
include <stdlib.h>
include <unistd.h>
int main() {
pid_t pid = fork();
if (pid > 0) {
// Parent Process
printf("Parent (PID: %d) is sleeping... Check 'ps' for Zombie.\n", getpid());
sleep(10); // Sleep long enough for child to exit and become zombie
printf("Parent waking up and exiting.\n");
} else if (pid == 0) {
// Child Process
printf("Child (PID: %d) is exiting immediately.\n", getpid());
exit(0); // Child becomes a zombie here
} else {
perror("fork failed");
}
return 0;
}
Logic: The child prints a message and exits immediately. The parent sleeps for 10 seconds. During these 10 seconds, the child is dead but not reaped, making it a Zombie.
Explain the syntax and utility of the wait() system call. How does it assist in process synchronization?
Syntax:
c
include <sys/wait.h>
pid_t wait(int *status);
Utility:
- Reaping: It retrieves the exit status of a terminated child process, removing the child's entry from the process table (preventing Zombies).
- Blocking: If no child has exited when
wait()is called, the parent process blocks (suspends execution) until one of its children terminates.
Synchronization:
It enforces a specific order of execution. It ensures that the parent does not proceed or terminate until the child has finished its task. This is crucial when the parent depends on the result of the child's computation.
Compare wait() and waitpid() system calls. Why might a programmer prefer waitpid()?
1. wait():
- Waits for any child process to terminate.
- It blocks the caller until a child terminates.
2. waitpid():
- Syntax:
pid_t waitpid(pid_t pid, int *status, int options); - Can wait for a specific child process by specifying the
pid. - Can be non-blocking if the
WNOHANGoption is used (returns immediately if no child has exited).
Preference for waitpid():
Programmers prefer waitpid() when they need to control exactly which child to wait for (in multi-child environments) or when they want the parent to continue working without blocking (polling) using WNOHANG.
Analyze the following code snippet. How many times will "Hello" be printed? Justify your answer mathematically.
c
include <stdio.h>
include <unistd.h>
int main() {
fork();
fork();
fork();
printf("Hello\n");
return 0;
}
Answer: "Hello" will be printed 8 times.
Justification:
fork()creates a new process. The number of processes grows exponentially with the number offork()calls.- Formula for total processes created by
fork()calls: - Here, .
- Total processes = .
- Each of the 8 processes executes the
printfstatement once before terminating.
What are Signals in an Operating System? List and explain three common signals used in Linux.
Definition: Signals are a form of inter-process communication (IPC) used to notify a process that a specific event has occurred. They are software interrupts.
Common Signals:
- SIGINT (Signal Interrupt): Sent when the user types
Ctrl+Cin the terminal. The default action is to terminate the process. - SIGKILL: Forces a process to terminate immediately. Unlike other signals, this cannot be caught, blocked, or ignored by the process.
- SIGCHLD: Sent to a parent process when a child process terminates, stops, or continues. The parent usually catches this to call
wait().
Write a C program to implement an Orphan process.
Concept: An orphan process runs after its parent dies. To demonstrate this, the parent must exit while the child is still running (sleeping).
C Program:
c
include <stdio.h>
include <stdlib.h>
include <unistd.h>
int main() {
pid_t pid = fork();
if (pid > 0) {
// Parent Process
printf("Parent (PID: %d) is exiting...\n", getpid());
exit(0); // Parent dies immediately
} else if (pid == 0) {
// Child Process
printf("Child (PID: %d) created. Parent is %d.\n", getpid(), getppid());
sleep(5); // Sleep to ensure parent dies first
// When child wakes up, it is an orphan adopted by init
printf("Child woke up. My new Parent is %d.\n", getppid());
} else {
perror("fork error");
}
return 0;
}
Explain how the kill() system call works. Does it always kill a process?
Mechanism:
The kill() system call is used to send a signal to a process or a group of processes. Despite its name, it does not always "kill" the process; it merely sends a signal.
Syntax:
int kill(pid_t pid, int sig);
Behavior:
- If
sigisSIGKILLorSIGTERM, the process is likely to terminate. - If
sigisSIGSTOP, the process pauses. - If
sigisSIGUSR1, the process might execute a custom handler without terminating. - Therefore,
kill()is a signal sender, not strictly a process terminator, though termination is a common use case.
Discuss the various variants of the exec family (execl, execv, execle, execvp). How do their arguments differ?
The variants differ based on how arguments are passed and whether environment variables or path searching is handled.
-
execl(const char *path, const char *arg, ...):- l (list): Arguments are passed as a list of strings, terminated by NULL.
- Path must be absolute or relative.
-
execv(const char *path, char *const argv[]):- v (vector): Arguments are passed as an array of pointers (vector).
-
*`execle(const char path, const char arg, ..., char const envp[])`**:
- e (environment): Allows passing a custom environment array.
-
execvp(const char *file, char *const argv[]):- p (path): Looks for the file in the directories specified by the
PATHenvironment variable. The filename alone is sufficient; absolute path is not required.
- p (path): Looks for the file in the directories specified by the
What are the macros used to inspect the status integer returned by wait()? Explain any three.
When wait(int *status) returns, the integer status contains encoded information. Macros are used to decode it:
-
WIFEXITED(status):- Returns true (non-zero) if the child terminated normally (e.g., by calling
exit()or returning frommain).
- Returns true (non-zero) if the child terminated normally (e.g., by calling
-
WEXITSTATUS(status):- If
WIFEXITEDis true, this macro extracts the actual exit code (the lower 8 bits) passed by the child toexit().
- If
-
WIFSIGNALED(status):- Returns true if the child process was terminated by a signal (uncaught signal).
-
WTERMSIG(status):- If
WIFSIGNALEDis true, returns the number of the signal that caused the termination.
- If
Write a C program that creates a child process. The parent should wait for the child to finish and then print the exit status of the child.
c
include <stdio.h>
include <stdlib.h>
include <sys/wait.h>
include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child: Working...\n");
sleep(2);
printf("Child: Done. Exiting with status 5.\n");
exit(5);
} else if (pid > 0) {
// Parent process
int status;
printf("Parent: Waiting for child...\n");
wait(&status);
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
printf("Parent: Child exited normally with status: %d\n", exit_code);
}
} else {
perror("Fork failed");
}
return 0;
}
What is Copy-on-Write (CoW) in the context of fork()? How does it optimize process creation?
Definition: Copy-on-Write is an optimization strategy used in fork(). When fork() is called, the OS does not immediately duplicate the physical memory pages of the parent for the child.
Mechanism:
- Parent and child share the same physical pages initially.
- These pages are marked as read-only.
- If either process tries to write to a page, a hardware trap occurs.
- The OS then allocates a new physical copy of only that specific page to the writing process.
Optimization: This significantly speeds up process creation because usually, a child calls exec() immediately after fork(), replacing the memory anyway. CoW saves the overhead of copying data that might never be used.
How can a process handle signals? Explain the signal() system call with an example of handling SIGINT.
A process can handle signals in three ways: default action, ignore the signal, or execute a user-defined function (handler).
The signal() System Call:
Syntax: sighandler_t signal(int signum, sighandler_t handler);
It sets a function to handle a specific signal.
Example (Handling SIGINT/Ctrl+C):
c
include <stdio.h>
include <signal.h>
include <unistd.h>
// Handler function
void handle_sigint(int sig) {
printf("\nCaught signal %d (SIGINT). Exiting safely...\n", sig);
}
int main() {
// Register handler
signal(SIGINT, handle_sigint);
while(1) {
printf("Running... Press Ctrl+C\n");
sleep(1);
}
return 0;
}
Explain the concept of Process Groups and how signals can be sent to a whole group.
Process Groups:
A process group is a collection of one or more processes (usually associated with the same job). Each process group has a unique Process Group ID (PGID). The leader of the group has a PID equal to its PGID.
Sending Signals to a Group:
The kill command or kill() system call can target a group.
- If the
pidargument inkill(pid, sig)is 0, the signal is sent to all processes in the calling process's group. - If
pidis less than -1 (e.g.,-100), the signal is sent to the process group with ID|pid|(e.g., PGID 100).
This is useful for shells to terminate an entire pipeline of commands (e.g., ls | grep txt) with one interrupt.
What happens to open file descriptors when fork() is called? Explain with a scenario.
Behavior:
When fork() is executed, the child inherits copies of the parent's open file descriptors. Note that they share the same file table entry in the kernel.
Implication:
- This means they share the file offset.
- If the parent reads 10 bytes from a file, the file pointer advances by 10.
- If the child subsequently reads from the same file descriptor, it will start reading after those 10 bytes, not from the beginning.
- Changes to file flags (like O_NONBLOCK) by one process affect the other.
Derive the number of times the printf statement is executed in the following code:
c
for(int i=0; i<n; i++) {
fork();
}
printf("OS\n");
Derivation:
- Let the number of iterations be .
- In the first iteration (),
fork()is called once. Total processes: 2. - In the second iteration (), both existing processes call
fork(). Total processes: . - In general, after iterations, the total number of processes created is .
- The
printf("OS\n")statement is outside the loop. - Once the loop finishes, every single active process executes the code following the loop.
- Therefore, the print statement is executed by all processes.
Answer: times.
Why is the init process (or systemd) critical in the context of process management and orphans?
Role of init (PID 1):
The init process is the ancestor of all other processes.
Handling Orphans:
- When a parent process dies before its child, the child becomes an orphan.
- In UNIX/Linux, orphans are not left floating; they are immediately "re-parented" to the
initprocess. inithas a special responsibility: it periodically callswait().- This ensures that when an adopted orphan eventually finishes, its exit status is collected, preventing it from staying a Zombie indefinitely.
- Without
initacting as the "reaper of last resort," the system process table would fill up with defunct processes.
Explain the difference between exit() and _exit() system calls.
1. exit():
- This is a standard C library function (
<stdlib.h>). - Before passing control to the kernel, it performs cleanup operations.
- Cleanup: It flushes standard I/O buffers (e.g., prints pending
printfdata), calls functions registered withatexit(), and closes standard streams.
2. _exit():
- This is a system call (
<unistd.h>). - It terminates the process immediately without flushing stdio buffers or calling cleanup handlers.
- It returns control directly to the kernel.
Usage Context: _exit() is typically used in a child process created by fork() (especially if exec fails) to prevent the child from flushing the parent's buffered I/O data.