Subtopic 2
Syscalls Compared
Code Examples
Key Differences at a Glance
While fork() and vfork() have identical signatures and return values, their internal behavior is completely different. Knowing these differences is important for understanding legacy code, embedded systems, and POSIX compliance discussions.
| Property | fork() | vfork() |
|---|---|---|
| Address space | Child gets own CoW copy | Child shares parent’s space |
| Page table setup | New page tables created | No page tables created |
| Parent execution | Runs concurrently with child | Suspended until child exec/_exit |
| Child modifies vars | Only affects child’s copy | Modifies parent’s variables! |
| Child can call exit() | Yes (but _exit() preferred) | NO — must use _exit() |
| Child can return from main | Yes | NO — undefined behavior |
| Execution order | Indeterminate | Child always runs first |
| Speed (fork+exec) | Fast with CoW | Slightly faster (no PT setup) |
| Safety | Safe for general use | Dangerous, narrow use only |
| POSIX status | Standard, required | Obsolescent since SUSv4 |
The textbook’s own vfork demo — shows parent suspended and stack sharing:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int istack = 222; /* a local (stack) variable */
switch (vfork()) {
case -1:
perror("vfork"); exit(EXIT_FAILURE);
case 0:
/* CHILD: running in parent's address space.
Parent is suspended until we call exec or _exit. */
sleep(3); /* to demonstrate parent is suspended */
/* DANGEROUS: modifies parent's stack variable */
istack *= 2; /* istack is now 444 in PARENT's stack */
printf("[Child ] istack=%d\n", istack);
/* Must use _exit(), never exit() */
_exit(EXIT_SUCCESS);
default:
/* PARENT resumes here after child calls _exit */
printf("[Parent] istack=%d\n", istack);
/* istack is 444 here — child modified parent's var! */
exit(EXIT_SUCCESS);
}
}
[Child ] istack=444 (child modified it)[Parent] istack=444 (parent sees the change!)
With fork(), parent would see istack=222 (independent copy).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
int main(void)
{
time_t t;
printf("[%ld] Parent: about to vfork\n", (long)time(NULL));
pid_t pid = vfork();
if (pid == -1) { perror("vfork"); exit(1); }
if (pid == 0) {
printf("[%ld] Child: running (parent suspended)\n",
(long)time(NULL));
sleep(3); /* parent is blocked during this sleep */
printf("[%ld] Child: about to _exit\n",
(long)time(NULL));
_exit(0);
}
/* Parent only reaches here AFTER child's _exit */
printf("[%ld] Parent: resumed after child's _exit\n",
(long)time(NULL));
wait(NULL);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>
#define ITER 1000
double bench_fork(size_t mem_mb)
{
char *buf = malloc(mem_mb * 1024 * 1024);
memset(buf, 'A', mem_mb * 1024 * 1024);
struct timespec s, e;
clock_gettime(CLOCK_MONOTONIC, &s);
for (int i = 0; i < ITER; i++) {
pid_t pid = fork();
if (pid == 0) { _exit(0); }
waitpid(pid, NULL, 0);
}
clock_gettime(CLOCK_MONOTONIC, &e);
free(buf);
return ((e.tv_sec - s.tv_sec)*1e6 +
(e.tv_nsec - s.tv_nsec)/1e3) / ITER;
}
double bench_vfork(size_t mem_mb)
{
char *buf = malloc(mem_mb * 1024 * 1024);
memset(buf, 'A', mem_mb * 1024 * 1024);
struct timespec s, e;
clock_gettime(CLOCK_MONOTONIC, &s);
for (int i = 0; i < ITER; i++) {
pid_t pid = vfork();
if (pid == 0) { _exit(0); }
waitpid(pid, NULL, 0);
}
clock_gettime(CLOCK_MONOTONIC, &e);
free(buf);
return ((e.tv_sec - s.tv_sec)*1e6 +
(e.tv_nsec - s.tv_nsec)/1e3) / ITER;
}
int main(void)
{
size_t sizes[] = { 1, 10, 50 };
printf("%-8s %-18s %-18s\n",
"Mem(MB)", "fork() us/call", "vfork() us/call");
printf("%-8s %-18s %-18s\n", "------", "--------------", "---------------");
for (int i = 0; i < 3; i++) {
double tf = bench_fork(sizes[i]);
double tv = bench_vfork(sizes[i]);
printf("%-8zu %-18.2f %-18.2f\n", sizes[i], tf, tv);
}
printf("\nNote: With CoW, fork() and vfork() speeds are similar.\n");
printf("vfork() advantage is marginal on modern Linux.\n");
return 0;
}
No. The parent is completely suspended after vfork() until the child calls exec() or _exit(). This is fundamentally different from fork(), where both parent and child run concurrently. vfork() execution is sequential: child runs first, parent resumes after.
No! The child shares the parent’s address space, so modifying a local variable in the child modifies the parent’s stack frame. This is one of the major dangers of vfork(). The t_vfork.c example shows this explicitly. The child should do nothing except call exec() or _exit().
Undefined behavior. Returning from main() in the child would unwind the parent’s stack frame, corrupt the parent’s call stack, and likely crash both parent and child. Always use _exit() in a vfork() child. This is one of the most dangerous mistakes with vfork().
