Shell is a program for managing and running processes. Next we will simulate a shell program. This instance will better understand the process creation and termination in Linux/Unix systems, and the relationship between parent and child processes. Next, paste the read part of the command in the source code:
Numargs = 0; while (numargs <maxargs) {printf ("Arg [% d]? ", Numargs); If (fgets (argbuf, arglen, stdin) & * argbuf! = '\ N') Arglist [numargs ++] = makestring (argbuf); else {If (numargs> 0) {Arglist [numargs] = NULL; execute (Arglist ); numargs = 0 ;}}}
This code is used to read user input commands and save them in the array of Arglist character pointers. Because the parameter type of inter-process communication is string, we select an array composed of pointers to the string as the passed parameter, and set the last pointer to null. After the command is read, the Execute function is called and the Arglist array is passed to it for operations on sub-processes. Next, let's take a look at the specific implementation of the Execute function.
Execute (char * Arglist []) {int PID, exitstatus; pid = fork (); // create a sub-process switch (PID) {Case-1: perror ("fork failed"); exit (1); case 0: execvp (Arglist [0], Arglist); // Replace the sub-process perror ("execvp failed "); exit (1); default: While (wait (& exitstatus )! = PID); // The parent process waits for printf ("Child exited with status % d, % d \ n", exitstatus> 8, exitstatus & 0377 );}}
In the Execute function, the fork () function is called first. What does the fork () function do? In fact, the fork () function creates a sub-process that is basically the same as the current process. After the control is transferred to the fork code in the kernel, the kernel allocates a new memory block and kernel data structure, and then copies the original process to the new process. Finally, add a new process to the running process and control it to return to the process again. At first, it may be quite strange. What should I do with a process that is basically the same? After reading the following content, you will know that as long as you call an execvp function, sub-processes will become completely different. Okay, now we have two processes, and their code is the same and they all run
PID = fork (); // create a sub-process
In this step, how can we determine which is the parent process and which is the child process? In fact, in the parent process, the return value of the fork function is the process ID of the child process, while in the child process, the fork returns 0, so we can determine the Parent and Child processes through the return value of fork. Next, go to the switch section. If fork returns-1, it indicates that the sub-process fails to be created. If the sub-process is in progress, execvp is called (in fact, execvp is not a system call, but a library function, it calls the kernel service by calling execve) to execute the specified program. Let's take a look at what the execvp function has done?
Result = execvp (const char * file, const char * argv [])
The first parameter specifies the process to be executed, such as the "ls" and "Ps" commands, the second parameter is a string pointer to the command to be executed and related parameters. By calling execvp, we can execute another process like "ls" in a process. Note that execvp clears the current process and loads the process specified by file. That is to say, when "ls" is executed, the perror statement under execvp will not be executed, because it has been replaced by the "ls" code. This is actually why we need to create sub-processes. If execvp is called in the parent process, we can only call one command in this shell program.
So we have to think about what the parent process is doing at this time? In fact, after fork, the Parent and Child processes are executed in parallel, and the effect we want is to wait for the parent process to continue execution after the child process ends. The following wait functions satisfy our wishes!
PID = wait (& Status)
The wait function is mainly used to pause the process that calls wait until the child process ends. Then, wait obtains the value passed to exit when the child process ends through status. Wait returns the PID of the ending process. If the process does not have a child process or has no termination status value,-1 is returned.
In this way, by constantly creating sub-processes, we can replace the sub-processes with the programs we want to execute and wait for the parent process. After the execution is completed, we can return to the parent process and simulate a shell program. Finally, the exit function of the process is used to end the process. If you exit, It refreshes all the streams and calls some functions to execute other exit-related operations defined by the current system. Finally, call the kernel operation _ exit to release the memory and close the related files.
In this way, through the call of several functions, we will help a process go through its short life. Actually, it's not that complicated to think about it ~
References: Understanding Unix/Linux programming ---- A Guide to Theory and Practice
Simulate shell (process function: fork (), execvp (), wait ())