https://indradhanush.github.io/blog/writing-a-unix-shell-part-2/
https://www.geeksforgeeks.org/zombie-processes-prevention/
https://www.geeksforgeeks.org/exit-status-child-process-linux/
https://www.cnblogs.com/wuyuegb2312/p/3399566.html
http://www.ppmy.cn/news/11615.html
Part Ⅰ--child process
How does a shell work?
A shell parses commands entered by the user and executes this.To be able to do this,the workflow of the shell will look like this:
startup the shell
wait for user input
parse user input
execute the command and return the result
go back to 2.
However,we cannot execute the command the command in the main thread itself,because of the following reasons:
An error caused by a command may cause the entire shell to stop working.
Independent commands should have their own process blocks. This is known as isolation and falls under fault tolerance.
A fault-tolerant design enables a system to continue its intended operation, possibly at a reduced level, rather than failing completely, when some part of the system fails.
To be able to avoid this, we use the system call fork.
Significantly,parent process and child process are running the same program, but it does not mean they are identical. OS allocate different data and states for these two processes, and the control flow of these processes can be different.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
intmain() {
pid_t child_pid=fork();
//Zero: Returned to the newly created child process.
if(child_pid==0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",getpid(),child_pid);
} else {//Positive value: Returned to parent or caller. The value contains process ID of newly created child process.
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",getpid(),child_pid);
}
return 0;
}
### Child ###
Current PID: 14477 and Child PID: 0
### Parent ###
Current PID: 14468 and Child PID: 14477
or
### Parent ###
Current PID: 14468 and Child PID: 14477
### Child ###
Current PID: 14477 and Child PID: 0
To block process
We can use sleep system call to block child process or parent process.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t child_pid=fork();
//Zero: Returned to the newly created child process.
if(child_pid==0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",getpid(),child_pid);
} else {//Positive value: Returned to parent or caller. The value contains process ID of newly created child process.
sleep(1);//sleep for one second
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",getpid(),child_pid);
}
return 0;
}
You will see
### Child ###
Current PID: 14600 and Child PID: 0
and after a span of one second
### Parent ###
Current PID: 14591 and Child PID: 14600
Similarly,if you add the sleep(1) call to the child segment of our code instead,you will see parent process be executed immediately.But you will notice that the parent process has terminated.Then the child one is executed.
Zombie Processes and their Prevention
Zombie Process:
https://www.geeksforgeeks.org/zombie-processes-prevention/
When a process is created in UNIX using fork() system call, the parent process is cloned. If the parent process calls wait() system call, then the execution of the parent is suspended until the child is terminated. At the termination of the child, a ‘SIGCHLD’ signal is generated by the kernel which is delivered to the parent. Parent, on receipt of ‘SIGCHLD’ reads the status of the child from the process table.
Even though the child is terminated, there is an entry in the process table corresponding to the child where the status is stored. When the parent collects the status, this entry is deleted (or we also say the parent reaped its child process) . Thus, all the traces of the child process are removed from the system. If the parent decides not to wait for the child’s termination and executes its subsequent task, then at the termination of the child, the exit status is not read. Hence, there remains an entry in the process table even after the termination of the child. This state of the child process is known as the Zombie state.
Why should we prevent the creation of the Zombie process?
However,there is one process table per system. The size of the process table is finite. If too many zombie processes are generated, then the process table will be full. That is, the system will not be able to generate any new process, then the system will come to a standstill. Hence, we need to prevent the creation of zombie processes.
How to prevent it?
(*)wait() system call : It suspends execution of the calling process until one of its children terminates. Syntax of wait() system call:
pid_twait(int*status);
(*)The waitpid() system call : It suspends execution of the calling process until a child specified by pid argument has changed state. Syntax of waitpid() system call :
pid_twaitpid(pid_tpid, int*status, intoptions)
Note: By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument such as WIFEXITED, WEXITSTATUS etc.If we don't want to use it,options=0
The value of pid can be :
Less than -1 : Meaning wait for any child process whose process group ID is equal to the absolute value of pid.
Equal to -1 : Meaning wait for any child process.
Equal to 0 : Meaning wait for any child process whose process group ID is equal to that of the calling process.
Greater than 0 : Meaning wait for the child whose process ID is equal to the value of pid.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t child_pid;
pid_t wait_pid;
int stat_loc;
child_pid=fork();
if(child_pid==0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
} else {
waitpid(-1, &stat_loc, 0);
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
}
return 0;
}
This is the basic frame of the shell.