Linux System Developer Interview Questions and Answers
Basic Linux Knowledge
Q: What happens when you type 'ls -l' in a terminal? Explain the entire process.
Answer: When you type 'ls -l' and press Enter, the following process occurs:
- The shell (e.g., bash) reads the input and parses it.
- The shell identifies 'ls' as an external command and '-l' as an argument.
- The shell fork()s to create a child process.
- The child process uses execve() to replace itself with the 'ls' program.
- The 'ls' program:
- Parses command-line arguments
- Opens the current directory (or specified directory)
- Reads directory entries using getdents() system call
- For each file:
- Calls stat() to get file information
- Formats the output (permissions, owner, size, date, name)
- Writes the formatted output to stdout
- The parent shell waits for the 'ls' command to complete
- The shell displays a new prompt
Q: What is the Linux kernel?
A: The Linux kernel is the core component of Linux operating systems. It's a free and open-source, monolithic, modular Unix-like operating system kernel. It manages:
- System hardware resources
- Process scheduling
- File systems
- Device drivers
- System calls
Key characteristics:
- Written primarily in C
- Created by Linus Torvalds in 1991
- Released under GNU General Public License v2
Q: Explain the boot process of a Linux system.
Answer: The Linux boot process consists of the following stages:
-
BIOS/UEFI Stage
- Power-on self-test (POST)
- Identifies boot device
-
Bootloader Stage (e.g., GRUB)
- Loads kernel image into memory
- Passes control to kernel with initial RAM disk (initrd)
-
Kernel Stage
- Initializes hardware and memory
- Mounts root filesystem
- Starts init process (PID 1)
-
Init Stage
- SystemD or traditional SysV init
- Starts system services
- Brings up network interfaces
- Mounts additional filesystems
-
Runlevel/Target Stage
- Reaches the specified runlevel or target
- System is ready for use
Q: What is the difference between a soft link and a hard link?
Answer:
Hard Links:
- Share the same inode number as the original file
- Can't cross filesystem boundaries
- Can't link to directories (usually)
- File content is only deleted when all hard links are deleted
- Same file size as the original file
Example creating a hard link:
ln original.txt hardlink.txt
Soft Links (Symbolic Links):
- Contain a path to the original file
- Can cross filesystem boundaries
- Can link to directories
- Can become dangling if original file is deleted
- Very small in size (just contains the path)
Example creating a soft link:
ln -s original.txt softlink.txt
Q: Explain the difference between user space and kernel space.
A:
Kernel Space:
- Highest privileged level (Ring 0)
- Full access to hardware
- Executes kernel code and device drivers
- Cannot be accessed directly by user applications
User Space:
- Lower privilege level (Ring 3)
- Limited access to hardware
- Executes user applications
- Must use system calls to access kernel services
Example of crossing the boundary:
// User space code
int fd = open("file.txt", O_RDONLY); // System call to kernel space
// Kernel space implementation
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
// Kernel code here
}
Q: What are system calls? List and explain five common ones.
A: System calls are interfaces between user space programs and the kernel.
Five common system calls:
- fork()
pid_t pid = fork();
Creates a new process by duplicating the calling process
- read()
ssize_t bytes = read(fd, buffer, count);
Reads data from a file descriptor
- write()
ssize_t bytes = write(fd, buffer, count);
Writes data to a file descriptor
- open()
int fd = open("file.txt", O_RDONLY);
Opens a file or creates it if it doesn't exist
- close()
int status = close(fd);
Closes a file descriptor
Q: Explain the Linux process scheduling algorithm.
A: Linux uses the Completely Fair Scheduler (CFS):
Key concepts:
-
Virtual Runtime (vruntime)
- Tracks process execution time
- Normalized by process priority
-
Red-Black Tree
- Processes sorted by vruntime
- O(log n) insertion and removal
Example of how priority affects scheduling:
struct sched_param param;
param.sched_priority = 51; // Range: 1-99 for real-time
sched_setscheduler(pid, SCHED_FIFO, ¶m);
Scheduler classes (in order of priority):
- Stop scheduler (internal use)
- Deadline scheduler
- Real-time scheduler
- CFS scheduler
- Idle scheduler
Q: What is a page fault? Explain different types.
A: A page fault occurs when a program tries to access memory that is mapped in the virtual address space but not loaded in physical memory.
Types of page faults:
-
Minor Page Fault
- Page is in memory but not marked in MMU
- No disk I/O required
-
Major Page Fault
- Page must be loaded from disk
- Requires disk I/O
-
Invalid Page Fault
- Access to invalid memory address
- Results in segmentation fault
Example of handling page faults in kernel:
static int __do_page_fault(struct mm_struct *mm, unsigned long addr,
unsigned int flags, struct task_struct *tsk)
{
struct vm_area_struct *vma;
int fault;
vma = find_vma(mm, addr);
if (!vma)
return VM_FAULT_BADMAP;
fault = handle_mm_fault(vma, addr, flags);
return fault;
}
Q: What are the different types of process states in Linux?
Answer: Linux processes can be in the following states:
-
Running (R)
- Currently executing on a CPU or waiting to be executed
-
Sleeping
- Interruptible Sleep (S): Waiting for an event, can be interrupted
- Uninterruptible Sleep (D): Usually I/O, can't be interrupted
-
Stopped (T)
- Process has been stopped, usually by user signal (SIGSTOP)
-
Zombie (Z)
- Process has completed but parent hasn't read its exit status
-
Dead (X)
- Process is being terminated
Example command to see process states:
ps aux | awk '{print $8}' | sort | uniq -c
System Programming
Q: What is the difference between a process and a thread?
Answer:
Processes:
- Have separate memory spaces
- Have their own file descriptors, program counter, stack
- Communication between processes requires IPC mechanisms
- Higher overhead for creation and context switching
- More isolated and secure
Threads:
- Share the same memory space within a process
- Share file descriptors, code, and data segments
- Can communicate through shared memory
- Lower overhead for creation and context switching
- Less isolated, potential for race conditions
Example of creating a process vs a thread:
// Process creation
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// Child process
} else {
// Parent process
}
// Thread creation
#include <pthread.h>
void* thread_function(void* arg) {
// Thread code
return NULL;
}
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
6. Q: What are signals in Linux? How do you handle signals in a program?
Answer: Signals are software interrupts used for inter-process communication. They can be:
- Sent by the kernel to processes
- Sent by processes to other processes
- Sent by processes to themselves
Common signals:
- SIGTERM (15): Termination request
- SIGKILL (9): Immediate termination
- SIGINT (2): Interactive attention (Ctrl+C)
- SIGSEGV (11): Segmentation violation
Example of signal handling:
#include <signal.h>
#include <stdio.h>
void signal_handler(int signum) {
printf("Caught signal %d\n", signum);
}
int main() {
// Register signal handler
signal(SIGINT, signal_handler);
while(1) {
printf("Running...\n");
sleep(1);
}
return 0;
}
Debugging & Performance
Q: How would you profile a Linux application for performance optimization?
Answer: Several tools and techniques can be used:
- perf - Linux profiling tool
perf record ./myapp
perf report
- gprof - GNU profiler
gcc -pg program.c -o program
./program
gprof program gmon.out > analysis.txt
- Valgrind - Memory profiler
valgrind --tool=callgrind ./myapp
- strace - Trace system calls
strace -c ./myapp
Key areas to look for:
- CPU usage (user vs system time)
- Memory allocation/deallocation patterns
- I/O operations
- Cache misses
- System call usage
Example of using perf to find hotspots:
perf record -g ./myapp
perf report --stdio
Coding Questions
Q: Write a program to create a daemon process.
Answer:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
int main() {
// Fork off the parent process
pid_t pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS); // Parent exits
}
// Create new session
if (setsid() < 0) {
exit(EXIT_FAILURE);
}
// Fork again (recommended)
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(EXIT_SUCCESS);
}
// Set file permissions
umask(0);
// Change working directory
chdir("/");
// Close all open file descriptors
for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
// Open logs
openlog("mydaemon", LOG_PID, LOG_DAEMON);
// Daemon-specific initialization
while (1) {
syslog(LOG_NOTICE, "Daemon is running");
sleep(30);
}
closelog();
return EXIT_SUCCESS;
}
Key points about daemons:
- Double forking ensures the process isn't a session leader
- Changing directory to / prevents locking mounted filesystems
- Closing file descriptors prevents resource leaks
- Using syslog for logging as stdout/stderr are closed
Q: What are signals in Linux? How do you handle them?
A: Signals are software interrupts used for inter-process communication.
Common signals:
- SIGTERM (15) - Termination request
- SIGKILL (9) - Immediate termination
- SIGINT (2) - Interactive attention (Ctrl+C)
- SIGSEGV (11) - Segmentation violation
Signal handling example:
#include <signal.h>
void signal_handler(int signum) {
printf("Caught signal %d\n", signum);
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
while(1) {
sleep(1);
}
return 0;
}
Sending signals:
// Send signal to another process
kill(pid, SIGTERM);
// Send signal to process group
killpg(pgid, SIGTERM);
Q: What is a deadlock? How can you prevent it?
A: A deadlock occurs when two or more processes are waiting indefinitely for resources held by each other.
Four conditions for deadlock:
- Mutual Exclusion
- Hold and Wait
- No Preemption
- Circular Wait
Example of potential deadlock:
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void* thread1_function(void* arg) {
pthread_mutex_lock(&mutex1);
sleep(1); // Increase chance of deadlock
pthread_mutex_lock(&mutex2);
// ... critical section ...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void* thread2_function(void* arg) {
pthread_mutex_lock(&mutex2);
sleep(1);
pthread_mutex_lock(&mutex1);
// ... critical section ...
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
Prevention strategies:
- Lock ordering
- Lock timeout
- Deadlock detection
- Use lock-free algorithms
Q: Explain the difference between threads and processes.
A:
Processes:
- Separate address space
- Higher creation overhead
- More isolation
- IPC required for communication
Threads:
- Shared address space
- Lower creation overhead
- Less isolation
- Can communicate through shared memory
Example of creating both:
// Process creation
pid_t pid = fork();
if (pid == 0) {
// Child process
exit(0);
}
// Thread creation
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, NULL);
Memory comparison:
// Process - separate memory
int main() {
int x = 5;
if (fork() == 0) {
x = 6; // Only changes in child
exit(0);
}
// Parent's x is still 5
}
// Thread - shared memory
void* thread_func(void* arg) {
int* x = (int*)arg;
*x = 6; // Changes for all threads
return NULL;
}
Q: What is a race condition? How can you prevent it?
A: A race condition occurs when multiple threads access shared data concurrently, and the outcome depends on the order of execution.
Example of a race condition:
int counter = 0;
void* increment(void* arg) {
for (int i = 0; i < 1000000; i++) {
counter++; // Race condition here
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Counter: %d\n"); // Will be less than 2000000
}
Prevention methods:
- Mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* safe_increment(void* arg) {
for (int i = 0; i < 1000000; i++) {
pthread_mutex_lock(&mutex);
counter++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
- Atomic Operations
#include <stdatomic.h>
atomic_int counter = 0;
void* atomic_increment(void* arg) {
for (int i = 0; i < 1000000; i++) {
atomic_fetch_add(&counter, 1);
}
return NULL;
}
Top comments (0)