1. fork function
1.1 copy-on-write
Why does fork return two values?
Answer: This is because copy-on-write occurs.
After calling fork, the address space and page table of the parent and child processes are exactly the same. Only when the child process wants to change variables, a new space will be opened up for the child process. This technique is called copy-on-write.
1. 2 Reasons for fork failure
If the number of calling processes is too large, the fork call will fail.
2. Process terminated
2.1 What does the os do when the process terminates?
Answer: Release the relevant kernel data structures and corresponding data and codes requested by the process.
2.2 Common ways of process termination
a. The code runs and the result is correct
b. The code runs to completion and the result is incorrect
Why is the return value of the main function 0? Can it be other value?
Answer: Yes, the return value of the main function is returned to the upper-level process to judge the result of the process execution. Returning 0 means the result of the program running is correct, and non-zero means the result is wrong. Returning different non-zero values represents different error reasons. At the same time, the returned number is too abstract, and it is necessary to design a solution to convert the returned error code into a string.
In fact, each process has a process exit code, and the exit code can be converted into a string by functions such as strerror.
c. The code did not finish running and the program crashed
If the program crashes, the exit code is meaningless.
At this time os will send a process signal to kill the process
Summary: If the program runs normally, the exit status of the process is indicated by the exit code, and whether the result is correct is indicated by zero and non-zero. If the process terminates abnormally, kill the process with different numbers of kill commands according to the type of signal sent.
2.3 How to terminate a process with code?
- return statement + exit code, only the exit statement inside the main function terminates the process.
- exit + exit code : exit( exit code).
- _exit + exit code: _exit( exit code).
- The difference between _exit and exit: _exit will directly call the system interface to close the program directly, regardless of buffer issues, but exit will help deal with the funeral.
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 5 int main() 6 { 7 printf("I'm out\n"); 8 sleep(3); 9 exit(1); 10 _exit(1); 11 return 0; 12 } // \n Refresh the buffer, if you remove \n and use _exit, this sentence will not be printed
3. Process waiting
3.1 Why there is a process waiting
The parent process reclaims the resources of the child process and obtains the exit information of the child process by means of process waiting.
3.2 How to perform process waiting
Call the system interface wait, waitpid waits for the child process to die before recycling. The wait/waitpid method returns to the parent process by obtaining the relevant process exit code or process exit signal in the process structure PCB.
3.2.1 wait method
wait + status.
The status here can be null.
3.2.3 waitpid method
waitpid has three parameters, pid, status, options
pid: If it is non-zero, it means the pid of the child process to be waited for, and if -1 is passed, it means to wait for any child process.
status: This is an output parameter, and wait stores the status reason of the child process exit into this variable.
It should be noted that status is designed based on binary bits,
The lower 16 digits of the status number, the upper eight digits represent the exit code, and the lower seven digits represent the exit signal. You can use the following figure to view the exit code.
At the same time, the system provides us with two macro replacements, WIFEXITED and WEXITSTATUS, and the exit code can also be checked by macro replacement.
WIFEXITED(status): True if this is the status returned by a normally terminated child process. (Check to see if the process exited normally)
WEXITSTATUS(status): If WIFEXITED is non-zero, extract the exit code of the child process. (Check the exit code of the process)
Specific usage:
if (WIFEXITED(status)) { printf("exit code: %d\n", WEXITSTATUS(status)); } else { printf("Process exited abnormally\n"); }
options: options defaults to 0, which means blocking waiting, but if you want to do something else during the waiting period, you need to change the options parameter to WNOHANG (wait no hang), which is also a macro. If you use a non-blocking calling method , then multiple calls are required to access whether the child process exits.
Example:
int num = 0; while (!num) { int status; pid_t res = waitpid(-1, &status, WNOHANG); if (res > 0) { //Indicates waiting for success && child process exits printf("child process has exited, exit code: %d\n", WEXITSTATUS(status)); num = 1; //Parent process exit loop detection } else if (res == 0) { //Wait for success && child process has not exited yet printf("The child process has not died yet\n"); //other things can be done here //... } else { //wait for failure printf("wait for failure\n"); num = 1; //Waiting for failure also exits the detection } }
4. Process substitution
The child process and the parent process created by fork share a certain piece of code, but what if the child process wants to have a brand new piece of code of its own? This requires process substitution.
4.1 What is process substitution?
Process replacement is to replace a child process by calling a system interface and loading a brand new program from disk.
4.2 How does process replacement work?
By establishing a new page table mapping, the current process points to new code and data.
4.3 How to use code for process replacement
To call the execl series system interface to load a new program into memory
Take the execl function as an example to explain:
The function signature is as follows:
int execl(const char *path, const char *arg, ...)0
There are "2" parameters here, path represents the path of the target program, arg and ... are regarded as one parameter, representing a variable parameter list: multiple parameters with an indefinite number can be passed in, but must end with NULL at the end to indicate the end of input.
Take the ls command as an example, this function can be filled like this
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
Take a test program written by myself as an example
The test program only has one sentence to print hello world, and the printed result after the proc is replaced in the afternoon is as shown
If the replacement fails, it will return -1, and if it succeeds, it will not return a value, because execl replaces everything from its own beginning to the last statement, and it has been replaced, so there is nothing else to return.
/
execv: Different from the l type interface (cl series, you can use list to remember, and pass parameters one by one.), v type can use vector to remember, and pass parameters need to use an array to store instructions.
Instructions:
There are two main types of differences: class v and class l (class v is regarded as a vector, and parameters are passed through an array; class l is regarded as a list, and parameters are passed one by one), class p and class e (p is PATH, which can be Do not write the path, and search through the environment variable; e is env, and the environment variable can be passed to the replaced process through an array).
execlp: execlp("ls", argv);
execvp: execvp("ls", argv); //argv stores the parameters to be used
execle: execle("/usr/bin/ls", "ls", "-a", NULL, env)
execvpe:("ls", "ls", "-a", NULL, env)//env stores the environment variables to be passed
If it is the call interface that the system really provides to us, it has to be execve. All the bottom-level calls of the above functions are execve interfaces, which are the basic encapsulation made by the system.
Tips
In the bottom line mode of vim, %s/replacement target/replacement source/g can be replaced in batches.
5. Write a minishell
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #define NUM 1024 #define SIZE 32 char login[NUM]; char* work[SIZE]; int main() { //Get command -- Parse command -- Execute special command -- Execute //parent process waits for child process while (1) { //1. Get command printf("[zy@VM-12-14-centos----Myshell]$ "); fflush(stdout); memset(login, '\0', sizeof login); //set all to '\0' if (fgets(login, sizeof login, stdin) == NULL) { continue; } //remove the last '\n' login[strlen(login) - 1] = '\0'; //check //printf("Command: %s\n", login); //2. Parsing commands //strtok work[0] = strtok(login, " "); int cnt = 1; if (strcmp(work[0], "ls") == 0) { work[cnt++] = "--color=auto"; } while (work[cnt++] = strtok(NULL, " "));//Split and pass //check //for (cnt = 0; work[cnt]; ++cnt) // printf("Command %d: %s\n", cnt + 1, work[cnt]); // //3. Execute the cd command if (strcmp(work[0], "cd") == 0) { if (work[1] == NULL) continue; chdir(work[1]); } //4. Execution //Create a child process for execution, while the parent process waits pid_t id = fork(); if (id == 0) { //child //replace process execvp(work[0], work); printf("guess what? can you see me?\n"); } else if (id < 0) { printf("Creation failed\n"); continue; } //parent process int status; pid_t res = waitpid(-1, &status, 0); if (res > 0) printf("Child process exited with exit code: %d\n", WEXITSTATUS(status)); } return 0; }
After testing, the basic instructions can be realized with colors.