Exam Rank 04: Process & Parsing
Download Subject Files:
Exam Structure:
- Level 1: picoshell, ft_popen, or sandbox (process-focused)
- Level 2: argo or vbc (parsing-focused)
Key Concepts:
- Process creation with
fork() - Inter-process communication with
pipe() - File descriptor manipulation with
dup2() - Signal handling with
sigaction() - Recursive descent parsing
- Expression evaluation with operator precedence
1. Process Management Fundamentals
Section titled “1. Process Management Fundamentals”fork() - Creating Child Processes
Section titled “fork() - Creating Child Processes”fork() creates an exact copy of the current process.
#include <unistd.h>#include <sys/wait.h>
pid_t pid = fork();
if (pid == -1) { // Error - fork failed perror("fork"); exit(1);}else if (pid == 0) { // Child process // pid == 0 means "I am the child" printf("I am the child (PID: %d)\n", getpid()); exit(0);}else { // Parent process // pid == child's PID printf("I am the parent, child PID: %d\n", pid); wait(NULL); // Wait for child to finish}Key Points About fork()
Section titled “Key Points About fork()”| Parent | Child |
|---|---|
| Returns child’s PID | Returns 0 |
| Continues execution | Starts at same point |
| Has original file descriptors | Gets copies of file descriptors |
| Must wait() for children | Should exit() when done |
2. Pipes - Inter-Process Communication
Section titled “2. Pipes - Inter-Process Communication”How Pipes Work
Section titled “How Pipes Work”A pipe creates a unidirectional data channel:
- pipefd[0] = read end
- pipefd[1] = write end
int pipefd[2];
if (pipe(pipefd) == -1) { perror("pipe"); exit(1);}
// pipefd[0] - read from pipe// pipefd[1] - write to pipeConnecting Processes with Pipes
Section titled “Connecting Processes with Pipes”To connect cmd1 | cmd2:
- Create pipe
- Fork for cmd1: redirect stdout to pipe write end
- Fork for cmd2: redirect stdin from pipe read end
- Close unused pipe ends in all processes
int pipefd[2];pipe(pipefd);
pid_t pid1 = fork();if (pid1 == 0) { // Child 1 (cmd1): writes to pipe close(pipefd[0]); // Close read end dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipe close(pipefd[1]); // Close original write end execvp(cmd1[0], cmd1); exit(1);}
pid_t pid2 = fork();if (pid2 == 0) { // Child 2 (cmd2): reads from pipe close(pipefd[1]); // Close write end dup2(pipefd[0], STDIN_FILENO); // Redirect stdin from pipe close(pipefd[0]); // Close original read end execvp(cmd2[0], cmd2); exit(1);}
// Parent: close both ends and waitclose(pipefd[0]);close(pipefd[1]);waitpid(pid1, NULL, 0);waitpid(pid2, NULL, 0);Critical Rule: Close Unused Pipe Ends!
Section titled “Critical Rule: Close Unused Pipe Ends!”Why? A pipe’s read end returns EOF only when ALL write ends are closed. If you forget to close the write end in the reader process, it will hang forever waiting for input.
// WRONG - cmd2 will hang!pid_t pid2 = fork();if (pid2 == 0) { dup2(pipefd[0], STDIN_FILENO); // Forgot to close pipefd[1]! execvp(cmd2[0], cmd2);}
// CORRECTpid_t pid2 = fork();if (pid2 == 0) { close(pipefd[1]); // MUST close write end! dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); execvp(cmd2[0], cmd2);}3. dup2() - File Descriptor Redirection
Section titled “3. dup2() - File Descriptor Redirection”Basic Usage
Section titled “Basic Usage”dup2(oldfd, newfd) makes newfd point to the same file as oldfd.
// Redirect stdout to a fileint fd = open("output.txt", O_WRONLY | O_CREAT, 0644);dup2(fd, STDOUT_FILENO); // Now stdout writes to output.txtclose(fd); // Close original fd (stdout still works)printf("This goes to output.txt\n");Common Patterns
Section titled “Common Patterns”// Redirect stdout to pipe write enddup2(pipefd[1], STDOUT_FILENO);
// Redirect stdin from pipe read enddup2(pipefd[0], STDIN_FILENO);
// Redirect stderr to stdoutdup2(STDOUT_FILENO, STDERR_FILENO);4. execvp() - Executing Programs
Section titled “4. execvp() - Executing Programs”execvp vs execve
Section titled “execvp vs execve”| Function | Path Resolution | Environment |
|---|---|---|
execve(path, argv, envp) | Must be full path | You provide envp |
execvp(file, argv) | Searches PATH | Uses current environ |
// execvp - easier, searches PATHchar *argv[] = {"ls", "-la", NULL};execvp("ls", argv); // Will find /bin/ls automatically
// execve - more controlchar *envp[] = {"PATH=/bin", NULL};execve("/bin/ls", argv, envp);Important: execvp Never Returns on Success!
Section titled “Important: execvp Never Returns on Success!”execvp(cmd[0], cmd);// If we reach here, execvp failed!perror("execvp");exit(1); // Child must exit on exec failure5. Signal Handling
Section titled “5. Signal Handling”sigaction() - Modern Signal Handling
Section titled “sigaction() - Modern Signal Handling”#include <signal.h>
void handler(int sig) { // Handle signal write(1, "Caught signal\n", 14);}
int main() { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
alarm(5); // Send SIGALRM in 5 seconds pause(); // Wait for signal
return 0;}Using alarm() for Timeouts
Section titled “Using alarm() for Timeouts”alarm(timeout); // Schedule SIGALRM in 'timeout' seconds
// ... do work ...
alarm(0); // Cancel the alarmChecking How a Process Died
Section titled “Checking How a Process Died”int status;waitpid(pid, &status, 0);
if (WIFEXITED(status)) { // Process exited normally int exit_code = WEXITSTATUS(status); printf("Exited with code %d\n", exit_code);}else if (WIFSIGNALED(status)) { // Process killed by signal int sig = WTERMSIG(status); printf("Killed by signal: %s\n", strsignal(sig));}6. Recursive Descent Parsing
Section titled “6. Recursive Descent Parsing”The Grammar Approach
Section titled “The Grammar Approach”For expression parsing, define a grammar:
expr = term (('+') term)*term = factor (('*') factor)*factor = '(' expr ')' | NUMBERImplementation Pattern
Section titled “Implementation Pattern”int parse_expr(const char **s);int parse_term(const char **s);int parse_factor(const char **s);
// Factor: number or (expr)int parse_factor(const char **s) { if (**s == '(') { (*s)++; // Skip '(' int result = parse_expr(s); if (**s != ')') error("Expected ')'"); (*s)++; // Skip ')' return result; } if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; } error("Unexpected token"); return 0;}
// Term: factor (* factor)*int parse_term(const char **s) { int result = parse_factor(s); while (**s == '*') { (*s)++; result *= parse_factor(s); } return result;}
// Expr: term (+ term)*int parse_expr(const char **s) { int result = parse_term(s); while (**s == '+') { (*s)++; result += parse_term(s); } return result;}Why This Works for Operator Precedence
Section titled “Why This Works for Operator Precedence”*has higher precedence than+- By parsing
*interm(called first), it binds tighter 3+4*5parses as3+(4*5)= 23, not(3+4)*5= 35
7. JSON Parsing Basics
Section titled “7. JSON Parsing Basics”JSON Structure
Section titled “JSON Structure”value = string | number | objectobject = '{' (pair (',' pair)*)? '}'pair = string ':' valuestring = '"' characters '"'number = digitsParsing Approach
Section titled “Parsing Approach”int parse_value(FILE *f, json *dst) { int c = getc(f);
if (c == '"') { return parse_string(f, dst); } else if (isdigit(c)) { ungetc(c, f); return parse_number(f, dst); } else if (c == '{') { return parse_object(f, dst); } else if (c == EOF) { printf("Unexpected end of input\n"); return -1; } else { printf("Unexpected token '%c'\n", c); return -1; }}Handling Escape Sequences
Section titled “Handling Escape Sequences”int parse_string(FILE *f, json *dst) { // Already consumed opening " char buffer[1024]; int i = 0; int c;
while ((c = getc(f)) != EOF && c != '"') { if (c == '\\') { c = getc(f); if (c == '"' || c == '\\') { buffer[i++] = c; } else { // Invalid escape printf("Unexpected token '%c'\n", c); return -1; } } else { buffer[i++] = c; } }
if (c == EOF) { printf("Unexpected end of input\n"); return -1; }
buffer[i] = '\0'; // Store string in dst... return 1;}8. Common Pitfalls
Section titled “8. Common Pitfalls”File Descriptor Leaks
Section titled “File Descriptor Leaks”// WRONG - leaking fdsint pipefd[2];pipe(pipefd);pid_t pid = fork();if (pid == 0) { dup2(pipefd[1], 1); execvp(cmd[0], cmd);}// Parent forgot to close pipefd[0] and pipefd[1]!
// CORRECTif (pid == 0) { close(pipefd[0]); dup2(pipefd[1], 1); close(pipefd[1]); execvp(cmd[0], cmd); exit(1);}close(pipefd[0]);close(pipefd[1]);Zombie Processes
Section titled “Zombie Processes”// WRONG - creates zombiesfor (int i = 0; i < n; i++) { if (fork() == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}// Parent exits without waiting!
// CORRECTfor (int i = 0; i < n; i++) { pids[i] = fork(); if (pids[i] == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}for (int i = 0; i < n; i++) { waitpid(pids[i], NULL, 0);}execvp Failure Handling
Section titled “execvp Failure Handling”// WRONG - child continues after failed execif (fork() == 0) { execvp(cmd[0], cmd); // If execvp fails, child continues running parent's code!}
// CORRECTif (fork() == 0) { execvp(cmd[0], cmd); perror("execvp"); exit(1); // MUST exit!}9. Quick Reference
Section titled “9. Quick Reference”Process Functions
Section titled “Process Functions”| Function | Purpose |
|---|---|
fork() | Create child process |
wait(NULL) | Wait for any child |
waitpid(pid, &status, 0) | Wait for specific child |
exit(code) | Terminate process |
Pipe & Redirection
Section titled “Pipe & Redirection”| Function | Purpose |
|---|---|
pipe(int fd[2]) | Create pipe (fd[0]=read, fd[1]=write) |
dup2(old, new) | Redirect new to old |
close(fd) | Close file descriptor |
Execution
Section titled “Execution”| Function | Purpose |
|---|---|
execvp(file, argv) | Execute with PATH search |
execve(path, argv, envp) | Execute with full path |
Signals
Section titled “Signals”| Function | Purpose |
|---|---|
sigaction(sig, &sa, NULL) | Set signal handler |
alarm(seconds) | Schedule SIGALRM |
kill(pid, sig) | Send signal to process |
Status Macros
Section titled “Status Macros”| Macro | Purpose |
|---|---|
WIFEXITED(status) | True if exited normally |
WEXITSTATUS(status) | Get exit code |
WIFSIGNALED(status) | True if killed by signal |
WTERMSIG(status) | Get signal number |