1/37
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
|---|
No study sessions yet.
(True or False): Signals are asynchronous messages delivered via the kernel; they can arrive at any time, and not only on traps.
A) True - Signals are asynchronous and can be delivered at any point during program execution, not just during traps. While some signals result from traps (like SIGSEGV from a bad memory access), signals can also come from external sources like hardware interrupts (keyboard input triggering SIGINT), other processes using kill(), or timer expirations. The kernel mediates all signal delivery regardless of the source.
(True or False): In a custom handler, printing with printf() is discouraged due to async-signal-safety; prefer write syscalls instead.
A) True - The printf() function is not async-signal-safe because it uses internal buffers and data structures that could be corrupted if a signal interrupts another call to printf(). If a signal handler calls printf() while the main program is also using printf(), the internal state can become inconsistent. The write() syscall is async-signal-safe because it's a simple syscall without complex internal state.
(True or False): Interrupts can interrupt each other.
A) True - Hardware interrupts can interrupt other interrupts depending on priority levels. The CPU and interrupt controller support nested interrupts where a higher-priority interrupt can preempt the handling of a lower-priority interrupt. This is a hardware-level mechanism to ensure critical events are handled promptly.
(True or False): Interrupts can interrupt the kernel
A) True - Interrupts are hardware events that can interrupt kernel execution. When the kernel is running (e.g., handling a syscall or managing resources), hardware interrupts like timer interrupts or I/O device interrupts can still occur and force the CPU to save kernel state and handle the interrupt. This is how the kernel regains control even when executing its own code.
(True or False): Use of signal() prevents other signals from arriving when you are currently handling a signal
B) False - The signal() function does NOT block other signals during handler execution by default. This is one of its major drawbacks. If multiple signals arrive while a handler is executing, they can interrupt the current handler, potentially causing race conditions or corrupted state. The sigaction() function with appropriate flags can block signals during handler execution, which is why it's preferred.
(True or False): A trap occurs when any hardware timer expires and interrupts the currently executing process.
B) False - A timer expiration is an interrupt, not a trap. Traps are synchronous events caused by specific instructions in the program (like syscalls or illegal operations). Interrupts are asynchronous hardware events like timer expirations that can occur at any time regardless of which instruction is executing.
(True or False): The kernel maintains a separate signal handler table for each running process.
A) True - Each process has its own signal handler table that defines how it responds to each signal. This is part of the process control block (PCB) maintained by the kernel.
(True or False): SIGKILL can be caught and handled to perform cleanup before process termination.
B) False - SIGKILL cannot be caught, blocked, or ignored. This is intentional design to ensure that the kernel always has a way to forcibly terminate any process. If processes could catch SIGKILL, a misbehaving or malicious process could refuse to die. SIGTERM should be used when you want to give a process a chance to clean up.
(True or False): The pause() syscall returns immediately if a signal has already been received but not yet handled.
B) False - The pause() syscall blocks until a signal is actually delivered and a signal handler returns. Pending signals that haven't been delivered yet don't cause pause() to return immediately. The process must actually receive and handle a signal for pause() to return. This creates potential race conditions if signals arrive between checking a condition and calling pause().
(True or False): All system calls are hardware interrupts.
B) False - System calls are traps, which are synchronous software-generated events caused by specific instructions, not hardware interrupts. When a program executes a syscall instruction, it deliberately causes a trap to transfer control to the kernel. Hardware interrupts are asynchronous events generated by hardware devices like timers, keyboards, or network cards.
(True or False): Signals generated from traps are always synchronous events.
A) True - When signals result from traps (like SIGSEGV from illegal memory access or SIGFPE from division by zero), they are synchronous because they are directly caused by executing a specific instruction. However, not all signals are synchronous; signals sent by other processes or resulting from hardware interrupts are asynchronous.
Which is synchronous and caused by a single instruction?
A) trap
B) interrupt
C) signal
D) clock
A) trap - Traps are synchronous events caused by specific instructions. When the instruction executes, it immediately causes a trap (like a syscall instruction or divide-by-zero). You can always identify exactly which instruction caused a trap. Interrupts are asynchronous and not tied to any specific instruction. Signals are messages delivered asynchronously by the kernel, and clock refers to hardware timers that generate interrupts.
Which statment is correct
A) SIGINT can only be sent from the terminal
B) SIGTERM is identical to SIGINT
C) SIGKILL cannot be caught/handled/ignored
D) SIGINT, SIGTERM and SIGKILL can be handled for graceful shutdown
C) SIGKILL cannot be caught/handled/ignored - SIGKILL and SIGSTOP are the only signals that cannot be caught, blocked, or ignored, ensuring the kernel always has a way to control processes. SIGINT can be sent by any process with proper permissions (not just from terminal), SIGTERM and SIGINT have different default sources but both can be handled, and SIGKILL cannot be handled at all.
If you want to send a signal SIG1 to a process with PID p, which syscall would you use:
A) raise(SIG1, p)
B) kill(p,SIG1)
C) signal(SIG1, p)
D) sendto(p, SIG1)
B) kill(p, SIG1) - The kill() syscall sends a signal to a specified process or process group. Despite its name, kill() doesn't necessarily terminate a process—it just sends a signal. The raise() function sends a signal to the calling process only and takes just one argument. The signal() function is for setting handlers, not sending signals. There is no sendto() function for signals.
Select which of the following is/are benifits to use sigaction over signal.
A) it is more portible across systems
B) guarantees the handler can run printf/malloc/etc
C) you can see what handler is currently set in the signal table
D) can block other signals during signal handling
A, C, D - sigaction() is more portable because signal() behavior varies across Unix systems (some reset handlers after each use). The oldact parameter lets you retrieve the current handler before changing it. The sa_mask field allows blocking other signals during handler execution. However, neither signal() nor sigaction() makes printf/malloc safe—async-signal-safety depends on the functions called, not the method used to register the handler.
Which of the following are true statements?
A) all signals come from synrconous events
B) all syscalls are interrupts
C) the kernel can be interrupted
D) a trap is always root-caused by software
C, D - The kernel can be interrupted by hardware interrupts even while executing. Traps are always caused by software executing specific instructions (syscalls, illegal operations, etc.). Not all signals come from synchronous events—signals can result from asynchronous sources like other processes or hardware events. Syscalls are traps (synchronous software interrupts), not hardware interrupts.
SIGSEGV default behavior is:
A) terminate
B) ignore
C) continue
D) stop but remain in memory
E) terminate and dump core
E) terminate and dump core - SIGSEGV (segmentation violation) default action is to terminate the process and generate a core dump file containing the process memory state for debugging. This is different from signals like SIGTERM or SIGINT which just terminate without a core dump. The core dump is valuable for debugging memory errors.
If a process is blocked in a read() syscall and receives a signal that is handled, what happens by default?
A) read() completes successfully with partial data (short read)
B) read() returns -1 and sets errno to EINTR
C) read() continues after the handler completes
D) The process terminates
B) read() returns -1 and sets errno to EINTR - By default, slow syscalls (like read() on devices that may block) are interrupted by signals. The syscall returns with an error, and errno is set to EINTR to indicate it was interrupted by a signal. Code must check for this and typically retry the syscall. The SA_RESTART flag in sigaction() can change this behavior to automatically restart the syscall.
Which signal cannot be sent using kill() from user space, even to your own process?
A) SIGKILL
B) SIGSEGV
C) SIGTERM
D) None; all signals can be sent using kill()
D) None; all signals can be sent using kill() - You can send any signal, including SIGKILL, to processes you have permission to signal (including your own). While SIGKILL cannot be caught or handled, it can certainly be sent. SIGSEGV is typically generated by the hardware/kernel on memory violations, but you can also send it explicitly with kill() for testing purposes.
In the context of CPU execution, what is a context switch?
A) When the CPU switches from user mode to kernel mode
B) When the CPU switches which process is executing
C) When a signal handler starts executing
D) When a trap occurs
B) When the CPU switches which process is executing - A context switch is when the kernel saves the state of one process and loads the state of another process to execute. This involves saving/restoring registers, program counter, stack pointer, and other CPU state. While mode switches (user/kernel) occur during context switches, they're not the same thing. Context switches allow multiple processes to share the CPU.
When using killall with no signal specified, which signal is sent by default?
A) SIGKILL
B) SIGTERM
C) SIGINT
D) SIGUSR1
B) SIGTERM - killall defaults to SIGTERM, which requests graceful termination. This gives processes a chance to clean up, close files, and exit properly. SIGTERM can be caught and handled, unlike SIGKILL. If you want to forcibly kill processes with killall, you must explicitly specify SIGKILL with -9 or -KILL.
What happens if you attempt to set a handler for SIGKILL using signal()?
A) The handler is successfully installed and used
B) signal() returns an error
C) The handler is successfully installed but never called
D) The system crashes
B) signal() returns an error - Both signal() and sigaction() will return an error (signal() returns SIG_ERR and sets errno to EINVAL) if you try to set handlers for SIGKILL or SIGSTOP. The kernel enforces this restriction to ensure it always has a way to control processes. You cannot catch, block, or ignore these signals. This is by design, if processes could block SIGKILL, a misbehaving process could refuse to terminate.
Which of the following functions is NOT async-signal-safe?
A) write()
B) strlen()
C) _exit()
D) getpid()
B) strlen() - strlen() is not async-signal-safe according to POSIX standards, though in practice it's a simple function. However, any function that might use internal state, allocate memory, or lock mutexes cannot be guaranteed async-signal-safe. write(), _exit(), and getpid() are all explicitly listed as async-signal-safe in the POSIX standard because they're simple syscalls without complex internal state.
What is the difference between a program and a process?
A program is a static executable file stored on disk containing compiled code and data. A process is a running instance of a program in memory with its own address space, open files, signal handlers, and execution state. Multiple processes can run the same program simultaneously, each with independent state. The kernel manages processes and provides them the abstraction that they have sole control of the CPU and memory.
What are system calls in UNIX?
System calls are the interface between user programs and the kernel, allowing processes to request privileged operations they cannot perform themselves. They are implemented as traps that cause a synchronous switch from user mode to kernel mode. Common syscalls include file operations (open, read, write), process control (fork, exec, exit), and signal management (kill, sigaction). Each syscall has a unique number that identifies which kernel function to execute.
Explain the difference between the ideas of SIGINT, SIGTERM, and SIGKILL. How should they be handled?
SIGINT (interrupt signal) is typically generated by Ctrl+C from the terminal and requests interruption. SIGTERM (termination signal) is a polite request to terminate, often used by system shutdown scripts. Both SIGINT and SIGTERM can be caught and handled for graceful shutdown—closing files, flushing buffers, saving state. SIGKILL forcibly terminates the process and cannot be caught, blocked, or ignored. It should be used as a last resort when a process refuses to respond to SIGTERM. Applications should handle SIGINT/SIGTERM for cleanup but cannot handle SIGKILL.
What are the steps that occur when a system call finishes execution in UNIX?
When a syscall completes, the kernel places the return value in a location the program can access (usually a register), restores the user process's registers and CPU state, checks for pending signals and delivers any that are ready, checks if the process's timeslice has expired and potentially performs a context switch to another process, then finally returns to user mode and continues execution at the instruction following the syscall. The signal check is particularly important because signals may have arrived while the kernel was executing the syscall.
Explain the difference between an interrupt and a signal in the context of exceptional control flow in a computer system. Provide an example of when each might be used.
An interrupt is an asynchronous hardware event (like a keyboard press, network packet arrival, or timer expiration) that causes the CPU to immediately stop and execute kernel code to handle the event. A signal is a software notification mechanism where the kernel notifies a process about an event; signals may originate from interrupts, traps, or other processes. Example interrupt: Timer hardware generating a clock interrupt every 1ms to allow the kernel to schedule processes. Example signal: The kernel sending SIGSEGV to a process after the memory unit detects an illegal memory access (the MMU triggers a trap, the kernel handles it and sends a signal to the process).
Consider the following scenario: A process is executing, and a SIGSEGV signal is generated. Describe what SIGSEGV is and how a process might handle this signal.
SIGSEGV (segmentation violation) is generated by the kernel when a process attempts to access memory it doesn't have permission to access; i.e. dereferencing a NULL pointer, accessing freed memory, or writing to read-only memory. The default action is to terminate and dump core. A process can install a custom handler for SIGSEGV to log the error, print debugging information, attempt recovery, or gracefully clean up before exiting. However, if the handler returns, the process typically re-executes the same instruction that caused the fault, causing another SIGSEGV. Handlers usually call _exit() after logging.
What is the purpose of the kill and killall command, and how are they used?
The kill command sends a signal to a specific process identified by its PID. Syntax is kill -SIGNAL PID or kill -N PID where N is the signal number. The killall command sends a signal to all processes matching a given program name. Syntax is killall [-SIGNAL] program_name. Both default to SIGTERM if no signal is specified. kill is used when you know the specific PID (from ps or stored in a variable), while killall is convenient for stopping all instances of a program. For example, kill -9 1234 force-kills PID 1234, while killall -HUP nginx sends SIGHUP to all nginx processes to reload configuration.
Explain the sigaction function and how it is used to handle signals?
sigaction() is the preferred POSIX function for signal handling because it provides portable, well-defined behavior. It takes three arguments: the signal number, a pointer to a new sigaction structure, and a pointer to retrieve the old action. The sigaction structure contains sa_handler (the handler function), sa_mask (signals to block during handler execution), and sa_flags (options like SA_RESTART, SA_NODEFER, SA_ONSTACK). Unlike signal(), sigaction() doesn't reset handlers after execution, allows blocking other signals during handling, and has consistent behavior across systems.
Why is it dangerous to use malloc() or free() inside a signal handler, and what should be done instead?
malloc() and free() maintain internal data structures (free lists, heap metadata) that are not protected against reentrant access. If a signal interrupts malloc() while it's modifying these structures, and the handler calls malloc(), the heap becomes corrupted. These functions are explicitly not async-signal-safe. Instead, allocate any needed memory before installing the signal handler and use static/global buffers. If dynamic memory is absolutely necessary, use memory pools pre-allocated before signals can arrive. For simple tasks, use fixed-size stack buffers in the handler. The general rule is to only call async-signal-safe functions (like write(), _exit(), getpid()) from signal handlers.
Given the following code, what will happen if the user presses Ctrl+C once while the program is sleeping? Describe the outputs and program behavior after sending the signal.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
write(1, "Caught signal\n", 14);
}
int main() {
signal(SIGINT, handler);
sleep(10);
printf("Press Ctrl+C\n");
pause();
printf("After pause\n");
return 0;
}When Ctrl+C is pressed during the sleep(10), SIGINT is delivered and the handler executes, printing "Caught signal\n". The sleep() call is interrupted and returns early (it doesn't complete the full 10 seconds). After the handler returns, execution continues with printf("Press Ctrl+C\n"), which prints. The program then reaches pause() and blocks, waiting for another signal. The program will remain stuck at pause() indefinitely unless another signal is sent. The key point is that the signal during sleep() interrupts sleep but is "consumed" by that interruption, meaning when pause() is called it will pause for another, seperate signal to return.
What is wrong with this code?
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
printf("Caught SIGKILL, cleaning up...\n");
}
int main() {
signal(SIGKILL, handler);
printf("Protected from SIGKILL\n");
while(1) sleep(1);
return 0;
}SIGKILL cannot be caught, blocked, or ignored. The signal(SIGKILL, handler) call will fail and return SIG_ERR, but this code doesn't check the return value. The handler will never be called. If another process sends SIGKILL to this process, it will be terminated immediately without any cleanup.
What will be the output of this program when compiled and run, then sent SIGUSR1 from another terminal?
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int count = 0;
void handler(int sig) {
count++;
write(1, "Signal\n", 7);
}
int main() {
signal(SIGUSR1, handler);
printf("PID: %d\n", getpid());
for(int i = 0; i < 5; i++) {
sleep(1);
printf("Count: %d\n", count);
}
return 0;
}The program prints its PID, then loops 5 times sleeping and printing the count. If SIGUSR1 is sent during execution (e.g., kill -USR1 <pid>), the handler executes, increments count, and prints "Signal". The sleep() call is interrupted and returns early. On subsequent iterations, the count will be higher. Example output:
PID: 12345
Count: 0
Signal
Count: 1
Count: 1
Signal
Count: 2
Count: 2Write a signal handler that toggles between paused and running states on delivery of a SIGUSR1 using signal(). When the program is running simply print "working" and sleep for 1 second:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
// TODO: Implement handlers and variables
int main() {
printf("PID: %d - Send SIGUSR1 to toggle pause\n", getpid());
while(1) {
// TODO: Implement pause/resume logic
}
return 0;
}#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int paused = 0; // can also use sigatomic
void toggle_handler(int sig) {
paused = !paused; // Toggle pause state
}
int main() {
signal(SIGUSR1, toggle_handler); // Install handler
printf("PID: %d - Send SIGUSR1 to toggle pause\n", getpid());
while(1) {
if(paused) pause(); // Wait for any signal
else {
printf("working\n");
sleep(1);
}
}
return 0;
}What is the output of this program? Explain what happens. (SIGSEGV is signal 11)
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void handler(int sig) {
printf("Handler called for signal %d\n", sig);
exit(0);
}
int main() {
signal(SIGSEGV, handler);
int *ptr = NULL;
printf("About to dereference NULL\n");
*ptr = 42;
printf("Bye bye!\n");
return 0;
}Output:
About to dereference NULL
Handler called for signal 11The NULL pointer dereference causes a segmentation fault. The kernel sends SIGSEGV (signal 11) to the process. The custom handler executes and prints the message. The handler calls exit(0), terminating the process. The line "Bye bye!" never executes.
Explain what's wrong with this approach to handling signals for graceful shutdown:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *logfile;
void handler(int sig) {
fprintf(logfile, "Shutting down due to signal %d\n", sig);
fclose(logfile);
exit(0);
}
int main() {
logfile = fopen("app.log", "a");
signal(SIGINT, handler);
signal(SIGTERM, handler);
// Application logic
while(1) {
// Do work
sleep(1);
}
return 0;
}Multiple problems: (1) fprintf() and fclose() are not async-signal-safe and can corrupt internal FILE structures if the signal interrupts other I/O operations. (2) exit() is also not async-signal-safe. (3) Better approach: Set a flag in the handler, check it in the main loop, and do cleanup there:
volatile sig_atomic_t shutdown_requested = 0;
void handler(int sig) {
shutdown_requested = 1; // Only set flag
}
int main() {
FILE *logfile = fopen("app.log", "a");
signal(SIGINT, handler);
signal(SIGTERM, handler);
while(!shutdown_requested) {
sleep(1);
}
// Safe cleanup in main context
fprintf(logfile, "Shutting down gracefully\n");
fclose(logfile);
return 0;
}