// XK> foreshadowing 1: fork () and Exec
The only way to derive a new process in UNIX is the fork () function (some systems may provide its various variants ). All descriptors opened in the parent process are shared by the child process after fork.
The only way for program files stored on the hard disk to be executed by UNIX is to call one of the exec functions by an existing process. (The exec function has six functions, which have the same effect, different call parameters ). Exec does not create a new process, but replaces the current process (that is, the process that calls exec, also called the process) image with the program file, and the new program is usually from main () start execution. The process id remains unchanged. By default, the descriptor opened by the called process remains open across exec. (You can use fcntl to set the fd_cloexec descriptor flag to disable it ).
Because exec replaces the original process image, it is often fork () first, and then calls exec in the child process.
// XK> foreshadowing 2: Main () and exit ()
C Programs are always executed from main (). When the kernel uses an exec function to execute C Programs, it first calls a startup routine, the startup routine obtains command line parameters and environment variable values from the kernel. When an executable file is created through the C source program, the connector specifies the system startup routine as the starting address of the program.
The startup example is written in a common assembly language in the form of C language:
Exit (main (argc, argv )); |
So that exit () is called immediately after return from main (). Exit () is mainly used for two tasks: 1. Execute the clean and close operation of the standard I/O library. Call fclose () for all open streams, which causes all buffered output data to be flushed to the file. 2. Return exit status. Usually the return value of main.
// XK> foreshadowing 3: Process Termination
There are eight methods to terminate the process.
5 normal termination:
1. Return from main.
2. call exit (). execute some cleanup operations (call and execute the terminate handler set by each atexit (), close all standard I/O streams, etc.), and then enter the kernel, it should still call _ exit () system Call.
3. Call _ exit () or _ exit (). immediately enter the kernel without cleaning. _ Exit () is described by posix.1, and _ exit () and exit () are described by Iso c. The kernel processes the _ exit () system call by releasing the resources owned by the process and sending the sigchld signal to the parent process (the default processing is ignore ).
4. The last thread is returned from its startup routine.
5. The last thread calls pthread_exit ().
Three types of exceptional termination:
1. call abort (). This function sends the SIGABRT signal to the calling process.
2. receive a signal and terminate it. For most signals, if the process does not capture it, the process will usually terminate immediately and generate the image of the process in the memory as the core dump file core in the current directory.
3. The last thread responds to the cancellation request. The thread can be canceled by other threads in the same process.
// XK> foreshadowing 4: Unix signals are generally not queued
When a signal event occurs, the kernel generates a signal and then delivers it to the corresponding process. This is usually because the kernel sets a certain form of flag in the progress table, in short, it takes time for the kernel to perform these operations. During the interval between signal generation and delivery, the signal is called pending ). In addition, the process can also set signal delivery blocking. If a blocked signal is generated for the process, and the signal is processed by the system by default or captured, the kernel keeps the signal in the pending state until the process blocks the signal or ignores the signal.
If the signal is successfully delivered to the process,ThisWhat happens when the signal occurs multiple times? Most Unix systems do not queue signals, that is, the system only delivers the signal once. Only systems supporting posix.1 real-time expansion can deliver this signal multiple times.
// XK> OK. Start now
// XK> ----------------------------------------------------------------
In many scenarios, fork () is used to derive a sub-process. Pay attention to the issue of zombie processes.
// XK> generation of zombie Processes
Generally, after a process is terminated, the system automatically recycles all resources allocated to the process. However, when a child process is terminated, it is in the "zombie" State, because the parent process may need to detect the exit code of the Child Process Termination. Therefore, the execution process of a child process is complete, but it still exists in the system. The table items in the progress table that represent the child process have not been released, until the parent process is terminated normally or the parent process calls wait ()/waitpid () to check the termination status of the child process. If the parent process terminates abnormally, the child process automatically takes the process with the PID of 1 (that is, the INIT process) as its parent process, the zombie process will remain in the progress table until it is discovered and released by the INIT process.
// XK> zombie process Processing
In the parent process, you can call wait () to wait for the parent process to end and check the exit status of the child process. If the child process has not ended, the parent process will be blocked and wait until the child process ends. If the child process has ended, the child process that has been frozen will release the resource.
What if I don't want the parent process to be blocked? When the child process ends, it will send a sigchld signal to the parent process. You can put the wait () call into the signal processing function of the sigchid signal.
But be careful! Creating a signal processing function and calling wait () in it is not enough to prevent zombie processes. Consider the following scenario:
Because UNIX processes are lightweight, the easiest way to write concurrent server programs in UNIX is to fork a sub-process to serve every customer. Now let's assume that N socket descriptors are opened in a client process and they all establish a TCP connection with such a concurrent server (simulating that when the server load is very large, multiple clients almost establish or disconnect at the same time ), then the server will fork out n sub-processes to respond to the n connection requests. When a customer processes terminate, N socket descriptors are almost closed at the same time. When each socket descriptor is closed, a fin message is sent to the server client, notifying the server that data will no longer be written to the socket. Assume that the processing of the server sub-process is to receive data from the client until the execution of the program ends when the fin message is received, then the n sub-processes of the server end almost simultaneously, almost simultaneously sends sigchld signals to the concurrent server parent process. The tragedy is that UNIX signals are generally not queued, so this can easily lead to loss of sigchld signals terminated by some server sub-processes. These Sub-processes will not be sent to the parent process wait, it becomes a zombie process.
The correct solution is to call waitpid (). This function is more powerful than wait (): 1. It can be a sub-process ID. 2. You can set the wnohang option to prevent the parent process from blocking when the specified child process has not been terminated.
In the preceding scenario, the concurrent server program can traverse all sub-processes with waitpid () at a time to clear the zombie sub-processes. Note that you must set the wnohang option. Blocking concurrent servers is intolerable.