A brief discussion on fork/vfork

fork/vfork浅谈

In *UNIX, multi-process programming can be implemented using fork/vfork. Here is a summary of the relevant knowledge.

fork

An existing process can create a new process by calling fork.

1
2
#include <unistd.h>
pid_t fork(void);

The process created by fork is called the child process. The call to the fork function will return twice—returns 0 in the child process, and the child process ID in the parent process. Both child and parent processes continue to execute the commands following the fork call. The child process is a copy of the parent process, obtaining a copy of the parent process’s data space, heap, and stack. Note: Parent and child processes do not share these storage spaces; they share the text segment (the portion of machine instructions executed by the CPU).

Accessing the data of the parent process in the child process is achieved through COW (copy on write) technology, where these areas are shared by the parent and child process, and the kernel changes their access privileges to read-only. If either the parent or child process attempts to modify these areas, the kernel creates a copy of the memory for the modified region, typically a page in virtual storage.

Note: It’s possible that pointers to modified objects after fork (in both child and parent) may receive the same addresses. However, they actually do not point to the same physical address. This is unrelated to COW because & retrieves the virtual address, and the virtual addresses of different processes are mapped to different physical addresses by the MMU.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int x=12;
printf("fork before: x = %d it's address is 0x%x\n",x,&x);
fflush(stdout);
pid_t child;
if((child=fork())<0){
printf("error!\n");
}else{
if(child>0){
printf("parent! The process ID is %d\n",getpid());
x=1;
printf("x on parent scope is %d address is 0x%x\n\n",x,&x);
fflush(stdout);
exit(0);
}else{
printf("child! The process ID is %d\n",getpid());
x=2;
printf("x on child scope is %d address is 0x%x\n\n",x,&x);
fflush(stdout);
}
}

Note: In C, printf is buffered. You need to manually fflush the buffer when the process ends. Alternatively, you can replace it with unbuffered write(STDOUT_FILENO,buffer,n);

getpid/getppid

You can obtain the current process ID with getpid() and the current process’s parent process ID with getppid().

The POSIX standard is:

File Sharing

After a fork, it is uncertain whether the child process or the parent process executes first; it depends on the scheduling algorithm used by the kernel. If synchronization is required between the parent and child processes, some form of inter-process communication is necessary.

Note: All the open file descriptors of the parent process are copied to the child process (equivalent to executing the dup function for each file descriptor). The parent and child processes each share the same file table entry for their identical open file descriptors (meaning they share the same offset).

fork fd shared

Therefore, if the parent and child processes access a file descriptor simultaneously without any synchronization measures, their outputs will be intermixed.

Differences Between Parent and Child Processes

  1. Different return values of fork
  2. Different process IDs
  3. Different parent process IDs for the two processes
  4. The child process does not inherit the file locks set by the parent process
  5. The child process clears pending alarms
  6. The child process’s pending signal set is initialized to an empty set
  7. The values of the child process’s tms_utime, tms_stime, tms_cutime, and tms_ustims are initialized to 0

fork Failure

Typically, fork fails for two reasons:

  • There are too many processes in the system
  • The total number of processes for the effective user ID exceeds the system limit (CHILD_MAX)

Two Uses of fork

  • To duplicate the parent process itself, allowing the parent and child processes to execute different code segments simultaneously.
  • For one process to execute another process (immediately calling exec after fork).

POSIX Standard for fork

vfork

The calling sequence and return values of vfork are the same as those of fork, but the semantics differ. The vfork function is used to create a new process with the purpose of execing a new program. vfork, like fork, creates a child process, but it does not completely copy the parent process’s address space to the child process because the child process will immediately call exec (or exit) and thus will not reference that address space. However, while the child process is executing exec, it runs in the parent’s space. If the child process modifies data (other than the variable storing the vfork return value), performs function calls, or returns without calling exec/exit, it may lead to unknown results.

1
2
3
4
5
6
7
8
9
10
11
12
13
pid_t childID;
int ival=123;
if((childID=vfork())<0){
printf("error\n");
}else{
if(childID==0){
ival=111;
exit(0);
}
}
printf("%d\n",ival);
// output
111

Another important difference between fork and vfork is that vfork guarantees that the child process runs first; the parent process can only be scheduled after the child has called exec or exit. If the child process calls either of these functions before relying on some action by the parent process, it will lead to a deadlock.

wait and waitpid

When a process terminates normally or abnormally, the kernel sends a SIGCHLD signal to its parent process. Since the termination of a child process is an asynchronous event (it can occur at any time during the parent process’s execution), this signal is also an asynchronous event sent by the kernel to the parent process. The parent process can ignore this signal or provide a function (signal handler) that gets called when this signal occurs.

What happens when a process calls wait or waitpid:

  • If all of its child processes are still running, it blocks.
  • If a process has terminated and is waiting for its parent to retrieve its termination status, the termination status of that child process is immediately returned.
  • If it has no child processes, it immediately returns an error.

If a process calls wait due to receiving a SIGCHLD signal, it is expected that wait will return immediately. However, if wait is called at random times, the process may block.

1
2
3
4
5
#include <sys/wait.h>
// Returns the process ID on success, or 0 on error
pid_t wait(int *statloc);
// pid parameter, status buffer (can be NULL), extra options
pid_t waitpid(pid_t pid,int *statloc,int options);

The pid parameter works as follows:

pid parameter Meaning
pid==-1 Wait for any child process; in this case, waitpid is equivalent to wait
pid>0 Wait for a child process with ID equal to pid
pid==0 Wait for any process with a group ID equal to the calling process’s group ID
pid<-1 Wait for any child process with a group ID equal to the absolute value of pid

The options parameter allows us to further control the behavior of waitpid, this parameter can be either 0 or the result of bitwise operations with the constants in the table below:

Constant Description
WCONTINUED If job control is supported, returns the status of any child process specified by pid that has resumed execution after being stopped and whose status has not yet been reported (XSI extension of POSIX.1)
WNOHANG If the child process specified by pid is not immediately available, waitpid does not block, and the return value is 0
WUNTRACED If a specific implementation supports job control and any child process specified by pid has been stopped, and its status has not been reported since stopping, this option returns its status. The WIFSTOPPED macro determines whether the return value corresponds to a stopped child process.

On successful execution, both wait and waitpid functions return the process ID of the terminated child process; if there is an error, they return 0 or -1. waitpid will also store the termination status of that child process in the storage location pointed to by statloc.

The differences between these two functions are:

  • wait blocks its caller until one of its child processes has terminated, while waitpid has an option that allows the caller not to block.
  • waitpid does not wait for the first terminated child process after it is called; it has several options to control all waiting processes.

If a process has already terminated and is a zombie process, wait will immediately obtain the status of the child process; otherwise, wait will block its caller until one child process has terminated. Since wait returns the process ID of the terminated child process, it always knows which process has terminated.

The integer status returned by these two functions (statloc) is defined by the implementation.

waitpid provides three features not offered by wait:

  • waitpid can wait for a specific process, while wait will return the status of any terminated child process.
  • waitpid offers a non-blocking version of wait. Sometimes it is desirable to acquire the status of a child process without blocking.
  • waitpid supports job control through the WUNTRACED and WCONTINUED options.

POSIX Standard for wait/waitid/waitpid

Orphan Processes

What happens if the parent process terminates before the child process? For all processes whose parent process has terminated, their parent process becomes the init process. These processes are said to be adopted by init. In short: when a process terminates, the kernel checks all active processes to determine if they are child processes of the terminating process. If they are, the parent process ID is changed to 1 (the PID of init). This method ensures that every process has a parent process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pid_t child;
if((child=fork())<0){
printf("error\n");
}else{
if(child==0){
printf("current Process id: %d\tparent ID: %d\n",getpid(),getppid());
sleep(2);
printf("current Process id: %d\tparent ID: %d\n",getpid(),getppid());
fflush(stdout);
}else{
sleep(1);
printf("parent end.\n");
}
}

Running the above code shows that when the parent process ends before the child process, the child process’s parent ID changes to init (1) (adopted by init);

Zombie Processes

If the child process terminates before the parent, how can the parent retrieve the child’s status during its checks? If the child process has disappeared completely, the parent process cannot obtain its termination status when it is finally ready to check if the child has terminated. The kernel preserves a certain amount of information for each terminated child process, so when the parent process of the terminated child calls wait/waitpid, it can retrieve this information. This information includes at least the process ID, the child process’s termination status, and the total CPU time used by that process. The kernel can release all storage areas used by the terminated process and close all its open files.

In UNIX terminology, a process that has terminated but has not been handled by its parent process (i.e., the parent has not retrieved information about the terminated child process or released the resources it occupies) is called a zombie process. The state of zombie processes is printed as Z by the ps(1) command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pid_t child;
if((child=fork())<0){
printf("error\n");
}else{
if(child==0){
printf("child Process ID: %d\tparent ID: %d\n",getpid(),getppid());
printf("child Process end.\n");
fflush(stdout);
exit(0);
}else{
sleep(5);
system("ps -o pid,ppid,state,tty,command");
printf("parent end.\n");
}
}

Executing the above program shows:
zombie process

exec

It was previously mentioned that a child process can call one of the exec functions to execute a new program after fork. When a process calls an exec function, the currently executing program is completely replaced by the new program, and the new program starts executing from its main function. Since calling exec does not create a new process, the process ID before and after remains unchanged; exec only replaces the current program’s text segment, data segment, heap, and stack with a new program from the disk.
POSIX Standard for exec

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:A brief discussion on fork/vfork
Author:LIPENGZHA
Publish Date:2017/03/06 13:40
Word Count:8.7k Words
Link:https://en.imzlp.com/posts/2658/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!