Mastering Process Control: A Complete Guide to Process Management in C

Process management is a cornerstone of systems programming in C. Understanding how to create, control, and communicate with processes is essential for building robust applications, from simple command-line tools to complex server architectures. This comprehensive guide explores everything from basic process creation to advanced inter-process communication techniques.

What is a Process?

A process is an instance of a running program. It consists of:

  • Program code (text segment)
  • Data (initialized and uninitialized)
  • Stack (function calls, local variables)
  • Heap (dynamically allocated memory)
  • Process control block (PCB) with metadata
  • Open file descriptors
  • Environment variables
┌─────────────────────────────────────┐
│            Process                   │
│  ┌─────────────────────────────┐     │
│  │        Text Segment         │     │
│  │     (Program Code)          │     │
│  └─────────────────────────────┘     │
│  ┌─────────────────────────────┐     │
│  │        Data Segment         │     │
│  │   (Initialized/Uninitialized)│    │
│  └─────────────────────────────┘     │
│  ┌─────────────────────────────┐     │
│  │           Heap              │     │
│  │   (Dynamic Memory)          │     │
│  └─────────────────────────────┘     │
│  ┌─────────────────────────────┐     │
│  │           Stack             │     │
│  │   (Function Calls)          │     │
│  └─────────────────────────────┘     │
└─────────────────────────────────────┘

Process IDs and Basic Information

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void print_process_info() {
pid_t pid = getpid();           // Current process ID
pid_t ppid = getppid();         // Parent process ID
uid_t uid = getuid();           // Real user ID
uid_t euid = geteuid();         // Effective user ID
gid_t gid = getgid();           // Real group ID
gid_t egid = getegid();         // Effective group ID
printf("Process Information:\n");
printf("  PID:  %d\n", pid);
printf("  PPID: %d\n", ppid);
printf("  UID:  %d\n", uid);
printf("  EUID: %d\n", euid);
printf("  GID:  %d\n", gid);
printf("  EGID: %d\n", egid);
}

Process Creation with fork()

1. Basic fork() Usage

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void basic_fork() {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
}
else if (pid == 0) {
// Child process
printf("Child: PID=%d, PPID=%d\n", getpid(), getppid());
printf("Child: Hello from child process\n");
exit(0);
}
else {
// Parent process
printf("Parent: PID=%d, Child PID=%d\n", getpid(), pid);
printf("Parent: Waiting for child to complete...\n");
int status;
wait(&status);
printf("Parent: Child exited with status %d\n", WEXITSTATUS(status));
}
}

2. fork() Memory Space Demonstration

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int global_var = 100;  // Data segment
void demonstrate_memory_space() {
int stack_var = 200;
char *heap_var = malloc(100);
strcpy(heap_var, "Heap data");
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("\n--- Child Process ---\n");
printf("Before modification:\n");
printf("  global_var = %d\n", global_var);
printf("  stack_var = %d\n", stack_var);
printf("  heap_var = %s\n", heap_var);
// Modify variables
global_var = 999;
stack_var = 888;
strcpy(heap_var, "Child modified");
printf("\nAfter modification:\n");
printf("  global_var = %d\n", global_var);
printf("  stack_var = %d\n", stack_var);
printf("  heap_var = %s\n", heap_var);
free(heap_var);
exit(0);
}
else {
// Parent process
wait(NULL);
printf("\n--- Parent Process ---\n");
printf("After child modifications:\n");
printf("  global_var = %d (unchanged)\n", global_var);
printf("  stack_var = %d (unchanged)\n", stack_var);
printf("  heap_var = %s (unchanged)\n", heap_var);
free(heap_var);
}
}

3. fork() with File Descriptors

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
void demonstrate_fork_file_descriptors() {
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return;
}
pid_t pid = fork();
if (pid == 0) {
// Child writes to file
dprintf(fd, "Child process writing\n");
close(fd);
exit(0);
}
else {
// Parent also writes to same file
dprintf(fd, "Parent process writing\n");
close(fd);
wait(NULL);
}
}

Process Execution with exec() Family

1. exec() Family Overview

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void demonstrate_exec_functions() {
// execl: list arguments
execl("/bin/ls", "ls", "-l", "-a", NULL);
// execlp: uses PATH environment variable
execlp("ls", "ls", "-l", NULL);
// execv: vector of arguments
char *args[] = {"ls", "-l", "-a", NULL};
execv("/bin/ls", args);
// execvp: uses PATH with vector
execvp("ls", args);
// execle: with environment
char *env[] = {"PATH=/bin", "USER=test", NULL};
execle("/bin/ls", "ls", "-l", NULL, env);
}

2. Complete exec() Example

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int execute_command(const char *cmd, char *const argv[]) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
else if (pid == 0) {
// Child process
execvp(cmd, argv);
// If exec returns, it failed
perror("execvp");
exit(1);
}
else {
// Parent process
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
return -1;
}
}
int main() {
char *ls_args[] = {"ls", "-l", "-a", NULL};
int result = execute_command("ls", ls_args);
printf("Command exited with status: %d\n", result);
return 0;
}

3. Custom Shell Implementation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CMD_LEN 1024
#define MAX_ARGS 64
void parse_command(char *cmd, char *args[]) {
int i = 0;
char *token = strtok(cmd, " \t\n");
while (token != NULL && i < MAX_ARGS - 1) {
args[i++] = token;
token = strtok(NULL, " \t\n");
}
args[i] = NULL;
}
void execute_pipeline(char *cmds[], int num_cmds) {
int pipe_fds[2];
int in_fd = 0;
pid_t pid;
for (int i = 0; i < num_cmds; i++) {
pipe(pipe_fds);
pid = fork();
if (pid == 0) {
// Child process
if (in_fd != 0) {
dup2(in_fd, STDIN_FILENO);
close(in_fd);
}
if (i < num_cmds - 1) {
dup2(pipe_fds[1], STDOUT_FILENO);
}
close(pipe_fds[0]);
close(pipe_fds[1]);
// Parse and execute command
char *args[MAX_ARGS];
parse_command(cmds[i], args);
execvp(args[0], args);
perror("execvp");
exit(1);
}
close(pipe_fds[1]);
if (in_fd != 0) close(in_fd);
in_fd = pipe_fds[0];
}
// Wait for all children
for (int i = 0; i < num_cmds; i++) {
wait(NULL);
}
}
int main() {
char input[MAX_CMD_LEN];
while (1) {
printf("mysh> ");
fflush(stdout);
if (fgets(input, sizeof(input), stdin) == NULL) {
break;
}
// Remove newline
input[strcspn(input, "\n")] = 0;
if (strcmp(input, "exit") == 0) {
break;
}
if (strlen(input) == 0) {
continue;
}
// Check for pipe
char *commands[MAX_ARGS];
int num_cmds = 0;
char *token = strtok(input, "|");
while (token != NULL && num_cmds < MAX_ARGS) {
commands[num_cmds++] = token;
token = strtok(NULL, "|");
}
if (num_cmds == 1) {
// Simple command
pid_t pid = fork();
if (pid == 0) {
char *args[MAX_ARGS];
parse_command(commands[0], args);
execvp(args[0], args);
perror("execvp");
exit(1);
}
else {
wait(NULL);
}
}
else {
// Pipeline
execute_pipeline(commands, num_cmds);
}
}
return 0;
}

Process Termination

1. exit() vs _exit()

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
void demonstrate_exit_types() {
printf("Demonstrating exit() vs _exit()\n");
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child: Using exit()\n");
printf("Child: This buffer will be flushed\n");
exit(0);  // Flushes stdio buffers
}
else {
wait(NULL);
}
pid = fork();
if (pid == 0) {
// Child process
printf("Child: Using _exit()\n");
printf("Child: This buffer may not be flushed\n");
_exit(0);  // No stdio flushing
}
else {
wait(NULL);
}
}

2. atexit() Registration

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void cleanup_function1(void) {
printf("Cleanup 1: Closing resources...\n");
}
void cleanup_function2(void) {
printf("Cleanup 2: Saving state...\n");
}
void cleanup_function3(void) {
printf("Cleanup 3: Final message\n");
}
void demonstrate_atexit() {
// Register cleanup functions (called in reverse order)
atexit(cleanup_function3);
atexit(cleanup_function2);
atexit(cleanup_function1);
printf("Program running...\n");
printf("Exit handlers registered\n");
// Normal exit - all handlers called
exit(0);
// _exit() would NOT call handlers
// _exit(0);
}

Process Waiting and Monitoring

1. wait() and waitpid()

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void demonstrate_wait_functions() {
pid_t pid;
int status;
// Create multiple children
for (int i = 0; i < 3; i++) {
pid = fork();
if (pid == 0) {
// Child process
printf("Child %d (PID=%d) starting\n", i, getpid());
sleep(i + 1);
exit(i * 10);
}
}
// Wait for all children
while ((pid = wait(&status)) > 0) {
if (WIFEXITED(status)) {
printf("Child %d exited with status %d\n", 
pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child %d killed by signal %d\n", 
pid, WTERMSIG(status));
}
}
}
void demonstrate_waitpid() {
pid_t pids[5];
// Create children
for (int i = 0; i < 5; i++) {
pids[i] = fork();
if (pids[i] == 0) {
sleep(i + 1);
exit(i);
}
}
// Wait for specific child
printf("Waiting for child with PID %d...\n", pids[2]);
int status;
pid_t ret = waitpid(pids[2], &status, 0);
if (ret == pids[2]) {
printf("Child %d finished with status %d\n", 
ret, WEXITSTATUS(status));
}
// Wait for any child (non-blocking)
while ((ret = waitpid(-1, &status, WNOHANG)) > 0) {
printf("Child %d finished\n", ret);
}
// Wait for remaining children
while (wait(NULL) > 0);
}

2. Process Status and Resource Usage

#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
void monitor_child_resources(pid_t pid) {
int status;
struct rusage usage;
pid_t ret = wait4(pid, &status, 0, &usage);
if (ret != -1) {
printf("Child %d resource usage:\n", pid);
printf("  User CPU time: %ld.%06ld seconds\n",
usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("  System CPU time: %ld.%06ld seconds\n",
usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
printf("  Page faults (reclaimable): %ld\n", usage.ru_minflt);
printf("  Page faults (unreclaimable): %ld\n", usage.ru_majflt);
printf("  Voluntary context switches: %ld\n", usage.ru_nvcsw);
printf("  Involuntary context switches: %ld\n", usage.ru_nivcsw);
}
}

Orphan and Zombie Processes

1. Zombie Process Demonstration

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void create_zombie() {
pid_t pid = fork();
if (pid == 0) {
// Child exits immediately
printf("Child (PID=%d) exiting\n", getpid());
exit(0);
}
else {
// Parent sleeps without waiting
printf("Parent (PID=%d) sleeping. Child %d is zombie\n",
getpid(), pid);
printf("Run 'ps aux | grep Z' to see zombie process\n");
sleep(30);
// Now wait to clean up zombie
wait(NULL);
printf("Parent waited, zombie cleaned up\n");
}
}

2. Orphan Process and init Adoption

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void create_orphan() {
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child (PID=%d, PPID=%d) started\n", 
getpid(), getppid());
// Parent will exit, making this an orphan
sleep(5);
printf("After parent exit: Child (PID=%d, PPID=%d)\n",
getpid(), getppid());
printf("Child adopted by init (PID=1)\n");
exit(0);
}
else {
// Parent exits immediately
printf("Parent (PID=%d) exiting\n", getpid());
exit(0);
}
}

Process Daemonization

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
void daemonize() {
pid_t pid;
// Fork and exit parent
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid > 0) {
exit(0);  // Parent exits
}
// Child becomes session leader
if (setsid() < 0) {
perror("setsid");
exit(1);
}
// Ignore terminal I/O signals
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
// Fork again to ensure not session leader
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid > 0) {
exit(0);
}
// Change working directory
chdir("/");
// Clear file mode creation mask
umask(0);
// Close all open file descriptors
for (int i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
close(i);
}
// Redirect stdin, stdout, stderr to /dev/null
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
void run_daemon() {
daemonize();
// Daemon code here
FILE *log = fopen("/tmp/mydaemon.log", "a");
if (log) {
for (int i = 0; i < 10; i++) {
fprintf(log, "Daemon running: iteration %d\n", i);
fflush(log);
sleep(5);
}
fclose(log);
}
}

Process Groups and Sessions

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void demonstrate_process_groups() {
pid_t pid;
printf("Parent: PID=%d, PGID=%d, SID=%d\n",
getpid(), getpgrp(), getsid(0));
for (int i = 0; i < 3; i++) {
pid = fork();
if (pid == 0) {
// Child
printf("Child %d: PID=%d, PGID=%d, SID=%d\n",
i, getpid(), getpgrp(), getsid(0));
// Create new process group
if (i == 1) {
setpgid(0, 0);
printf("Child %d created new process group: PGID=%d\n",
i, getpgrp());
}
sleep(5);
exit(0);
}
}
// Send signal to specific process group
sleep(2);
printf("Sending SIGTERM to process group of child 1...\n");
killpg(getpgid(pid), SIGTERM);
// Wait for children
while (wait(NULL) > 0);
}

Inter-Process Communication (IPC)

1. Pipes

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
void pipe_communication() {
int pipefd[2];
char buffer[256];
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
pid_t pid = fork();
if (pid == 0) {
// Child: writer
close(pipefd[0]);  // Close read end
char *message = "Hello from child!";
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
exit(0);
}
else {
// Parent: reader
close(pipefd[1]);  // Close write end
read(pipefd[0], buffer, sizeof(buffer));
printf("Parent received: %s\n", buffer);
close(pipefd[0]);
wait(NULL);
}
}
void bidirectional_pipe() {
int pipe1[2], pipe2[2];
if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
perror("pipe");
return;
}
pid_t pid = fork();
if (pid == 0) {
// Child
close(pipe1[1]);  // Close write end of pipe1
close(pipe2[0]);  // Close read end of pipe2
char msg[256];
read(pipe1[0], msg, sizeof(msg));
printf("Child received: %s\n", msg);
char *reply = "Reply from child";
write(pipe2[1], reply, strlen(reply) + 1);
close(pipe1[0]);
close(pipe2[1]);
exit(0);
}
else {
// Parent
close(pipe1[0]);  // Close read end of pipe1
close(pipe2[1]);  // Close write end of pipe2
char *msg = "Hello from parent";
write(pipe1[1], msg, strlen(msg) + 1);
char reply[256];
read(pipe2[0], reply, sizeof(reply));
printf("Parent received: %s\n", reply);
close(pipe1[1]);
close(pipe2[0]);
wait(NULL);
}
}

2. Named Pipes (FIFOs)

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define FIFO_NAME "/tmp/myfifo"
void fifo_writer() {
mkfifo(FIFO_NAME, 0666);
int fd = open(FIFO_NAME, O_WRONLY);
if (fd == -1) {
perror("open");
return;
}
char *message = "Hello through FIFO!";
write(fd, message, strlen(message) + 1);
close(fd);
}
void fifo_reader() {
int fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
return;
}
char buffer[256];
read(fd, buffer, sizeof(buffer));
printf("Reader received: %s\n", buffer);
close(fd);
unlink(FIFO_NAME);
}

3. Shared Memory

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define SHM_SIZE 1024
void shared_memory_demo() {
int shmid;
char *shm_ptr;
// Create shared memory segment
shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return;
}
pid_t pid = fork();
if (pid == 0) {
// Child: attach and write
shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (char*)-1) {
perror("shmat");
return;
}
strcpy(shm_ptr, "Message from child");
printf("Child wrote: %s\n", shm_ptr);
shmdt(shm_ptr);
exit(0);
}
else {
// Parent: attach and read
wait(NULL);
shm_ptr = shmat(shmid, NULL, 0);
if (shm_ptr == (char*)-1) {
perror("shmat");
return;
}
printf("Parent read: %s\n", shm_ptr);
shmdt(shm_ptr);
// Remove shared memory
shmctl(shmid, IPC_RMID, NULL);
}
}

Process Scheduling and Priorities

#include <sched.h>
#include <sys/resource.h>
#include <errno.h>
void demonstrate_process_priorities() {
// Get current priority
int priority = getpriority(PRIO_PROCESS, 0);
printf("Current priority: %d\n", priority);
// Set higher priority (requires root)
if (setpriority(PRIO_PROCESS, 0, -10) == 0) {
printf("Priority increased to -10\n");
} else if (errno == EPERM) {
printf("Need root to increase priority\n");
}
// Get scheduling policy
int policy = sched_getscheduler(0);
switch(policy) {
case SCHED_FIFO:
printf("Scheduling: FIFO (real-time)\n");
break;
case SCHED_RR:
printf("Scheduling: Round Robin (real-time)\n");
break;
case SCHED_OTHER:
printf("Scheduling: Normal (time-sharing)\n");
break;
default:
printf("Scheduling: Unknown\n");
}
// Set real-time scheduling (requires root)
struct sched_param param;
param.sched_priority = 1;
if (sched_setscheduler(0, SCHED_FIFO, &param) == 0) {
printf("Set real-time scheduling\n");
}
}

Process Limits

#include <sys/resource.h>
#include <errno.h>
void demonstrate_process_limits() {
struct rlimit limit;
// Get core file size limit
getrlimit(RLIMIT_CORE, &limit);
printf("Core file size: soft=%lu, hard=%lu\n",
limit.rlim_cur, limit.rlim_max);
// Get CPU time limit
getrlimit(RLIMIT_CPU, &limit);
printf("CPU time: soft=%lu, hard=%lu\n",
limit.rlim_cur, limit.rlim_max);
// Get file size limit
getrlimit(RLIMIT_FSIZE, &limit);
printf("File size: soft=%lu, hard=%lu\n",
limit.rlim_cur, limit.rlim_max);
// Get open files limit
getrlimit(RLIMIT_NOFILE, &limit);
printf("Open files: soft=%lu, hard=%lu\n",
limit.rlim_cur, limit.rlim_max);
// Get process count limit
getrlimit(RLIMIT_NPROC, &limit);
printf("Processes: soft=%lu, hard=%lu\n",
limit.rlim_cur, limit.rlim_max);
// Set new limit (if allowed)
limit.rlim_cur = 1024;
if (setrlimit(RLIMIT_NOFILE, &limit) == 0) {
printf("Open files limit increased to %lu\n", limit.rlim_cur);
}
}

Complete Example: Process Manager

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#define MAX_PROCESSES 100
#define MAX_CMD_LEN 256
typedef struct {
pid_t pid;
char command[MAX_CMD_LEN];
int status;  // 0: running, 1: stopped, 2: terminated
} ProcessInfo;
ProcessInfo processes[MAX_PROCESSES];
int process_count = 0;
void add_process(pid_t pid, const char *cmd) {
if (process_count < MAX_PROCESSES) {
processes[process_count].pid = pid;
strncpy(processes[process_count].command, cmd, MAX_CMD_LEN - 1);
processes[process_count].status = 0;
process_count++;
}
}
void remove_process(pid_t pid) {
for (int i = 0; i < process_count; i++) {
if (processes[i].pid == pid) {
for (int j = i; j < process_count - 1; j++) {
processes[j] = processes[j + 1];
}
process_count--;
break;
}
}
}
void list_processes() {
printf("\n%-10s %-20s %-10s\n", "PID", "COMMAND", "STATUS");
printf("----------------------------------------\n");
for (int i = 0; i < process_count; i++) {
const char *status_str;
switch (processes[i].status) {
case 0: status_str = "Running"; break;
case 1: status_str = "Stopped"; break;
case 2: status_str = "Terminated"; break;
default: status_str = "Unknown";
}
printf("%-10d %-20s %-10s\n",
processes[i].pid, processes[i].command, status_str);
}
printf("\n");
}
void kill_process(pid_t pid, int signal) {
if (kill(pid, signal) == 0) {
printf("Signal %d sent to process %d\n", signal, pid);
if (signal == SIGTERM || signal == SIGKILL) {
// Mark for removal (will be cleaned up on wait)
}
} else {
perror("kill");
}
}
void background_execute(char *cmd) {
pid_t pid = fork();
if (pid == 0) {
// Child process
char *args[] = {"/bin/sh", "-c", cmd, NULL};
execvp("/bin/sh", args);
perror("execvp");
exit(1);
}
else if (pid > 0) {
// Parent process
add_process(pid, cmd);
printf("[%d] %d\n", process_count, pid);
}
else {
perror("fork");
}
}
void foreground_execute(char *cmd) {
pid_t pid = fork();
if (pid == 0) {
// Child process
char *args[] = {"/bin/sh", "-c", cmd, NULL};
execvp("/bin/sh", args);
perror("execvp");
exit(1);
}
else if (pid > 0) {
// Parent process - wait for child
add_process(pid, cmd);
int status;
waitpid(pid, &status, 0);
remove_process(pid);
}
else {
perror("fork");
}
}
void reap_zombies() {
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("Process %d terminated\n", pid);
remove_process(pid);
}
}
void signal_handler(int sig) {
printf("\nReceived signal %d\n", sig);
reap_zombies();
}
int main() {
char input[MAX_CMD_LEN];
// Set up signal handler
signal(SIGCHLD, signal_handler);
signal(SIGINT, signal_handler);
printf("Process Manager\n");
printf("Commands:\n");
printf("  <command> &     - Run in background\n");
printf("  <command>       - Run in foreground\n");
printf("  jobs            - List processes\n");
printf("  kill <pid>      - Terminate process\n");
printf("  kill -9 <pid>   - Force kill process\n");
printf("  exit            - Exit\n\n");
while (1) {
printf("pm> ");
fflush(stdout);
if (fgets(input, sizeof(input), stdin) == NULL) {
break;
}
// Remove newline
input[strcspn(input, "\n")] = 0;
if (strlen(input) == 0) {
continue;
}
if (strcmp(input, "exit") == 0) {
break;
}
else if (strcmp(input, "jobs") == 0) {
list_processes();
}
else if (strncmp(input, "kill", 4) == 0) {
int sig = SIGTERM;
pid_t pid;
if (strncmp(input, "kill -9", 7) == 0) {
sig = SIGKILL;
pid = atoi(input + 7);
} else {
pid = atoi(input + 4);
}
if (pid > 0) {
kill_process(pid, sig);
}
}
else {
// Check for background execution
int len = strlen(input);
if (len > 0 && input[len - 1] == '&') {
input[len - 1] = '\0';
background_execute(input);
} else {
foreground_execute(input);
}
}
reap_zombies();
}
// Clean up all processes
for (int i = 0; i < process_count; i++) {
kill(processes[i].pid, SIGTERM);
}
return 0;
}

Best Practices Summary

PracticeWhy It Matters
Always check fork() return valuefork can fail
Handle child processes with wait()Prevents zombies
Use _exit() in child after forkAvoids flushing stdio buffers twice
Set up signal handlersHandle SIGCHLD, SIGINT, SIGTERM
Close unused pipe endsPrevents resource leaks
Check exec() returnexec only returns on error
Use volatile sig_atomic_tFor variables modified in signal handlers
Daemonize properlyFork twice, close file descriptors
Monitor resource usageDetect memory leaks, CPU issues
Document process behaviorHelps maintainers understand IPC

Conclusion

Process management in C provides the foundation for building sophisticated, multi-process applications. From simple process creation with fork() to complex inter-process communication with pipes and shared memory, these tools enable developers to harness the full power of modern operating systems.

Key takeaways:

  • Understand process lifecycle: Creation, execution, termination
  • Handle child processes properly: Prevent zombies, reap properly
  • Choose appropriate IPC: Pipes for simple, shared memory for performance
  • Set up signal handlers: Graceful shutdown, child reaping
  • Consider security: Drop privileges, sanitize inputs
  • Monitor resources: Track usage, set appropriate limits

By mastering process management, you can build robust, scalable, and efficient C applications that leverage multiple processes for improved performance, isolation, and reliability.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper