Source: Lupa open-source community
Release date:
Network Programming in Linux is divided into two parts: Server programming and client programming. Generally, a daemon process must be created before a server program receives client connection requests. Daemon is a very important concept in Linux/Unix programming, because when creating a daemon, we need to have access to sub-processes, process groups, meeting periods, signal mechanisms, files, directories, control terminals, and other concepts. So we will discuss in detail the daemon process, it is very helpful for beginners to learn the relationship between processes.
First, let's look at the code for converting a common process into a daemon process:
---------------------------
/*************************************** *************************
Function: daemonize
Description: Detach the server process from the current context, creating a pristine, predictable environment in which it will execute.
Arguments: servfd file descriptor in use by server.
Return Value: none.
Cballs: none.
Globals: none.
**************************************** ************************/
Void daemonize (servfd)
Int servfd;
{
Int childpid, FD, fdtablesize;
/* Ignore terminal I/O, stop signals */
Signal (sigttou, sig_ign );
Signal (sigttin, sig_ign );
Signal (sigtstp, sig_ign );
/* Fork to put us in the background (whether or not the user
Specified '&' on the command line */
If (childpid = fork () <0 ){
Fputs ("failed to fork first childrn", stderr );
Exit (1 );
}
Else if (childpid> 0)
Exit (0);/* terminate parent, continue in child */
/* Dissociate from Process Group */
If (setpgrp (0, getpid () <0 ){
Fputs ("failed to become process group leaderrn", stderr );
Exit (1 );
}
/* Lose controlling Terminal */
If (FD = open ("/dev/tty", o_rdwr)> = 0 ){
IOCTL (FD, tiocnotty, null );
Close (FD );
}
/* Close any open file descriptors */
For (FD = 0, fdtablesize = getdtablesize (); FD <fdtablesize; FD ++)
If (FD! = Servfd)
Close (FD );
/* Set working directory to allow filesystems to be unmounted */
Chdir ("/");
/* Clear the inherited umask */
Umask (0 );
/* Setup zombie prevention */
Signal (sigcld, (sigfunc *) reap_status );
}
---------------------------
In Linux, to convert a common process to a daemon, perform the following steps:
1. Call the fork () function to create a child process, stop the parent process, and keep the child process running. Because when a process is started by shell as a foreground process, if the parent process is aborted, the child process is automatically converted to the background process. In addition, in the next step, we need to create a new meeting period, which requires that the process for creating a meeting period is not the leader process of a process group. When the parent process is aborted and the child process continues to run, the Group ID of the Process Group and the child process ID are not equal.
The fork () function is defined:
----------------------
# Include <sys/types. h>
# Include <unistd. h>
Pid_t fork (void );
----------------------
If the fork () function is called once, two values are returned. The returned values are the returned values of the child process and the returned values of the parent process. The returned values of the child process are "0", and the returned values of the parent process are the ID of the child process. If an error occurs, "-1" is returned ".
1. Ensure that the process does not obtain any control terminal. This is to avoid displaying that a program is running and cannot be closed when you close some terminals. The common practice in this step is to call the setsid () function to create a new meeting period.
The setsid () function is defined:
----------------------
# Include <sys/types. h>
# Include <unistd. h>
Pid_t setsid (void );
----------------------
In the first step, we have ensured that the process calling this function is not the process group leader, so calling this function will create a new meeting. The result is: first, this process program is the first process of the meeting period (Session leader, the first process of the system default meeting period is the process of creating the meeting period ). In addition, this process is the only process during the meeting. Then, the process becomes the leader of a new process group. The group ID of the new process group is the process ID of the process. Finally, ensure that the process has no control terminal. Even if the process has a control terminal before setsid () is called, the contact will be released after the meeting is created. If the process that calls this function is the leader of a process group, the function returns the error message "-1 ".
Another method is to make the process unable to obtain the control terminal, as shown below:
----------------------
If (FD = fopen ("/dev/tty", 0_rdwr)> = 0 ){
IOCTL (FD, tiocnotty, null );
Close (FD );
}
----------------------
/Dev/tty is a stream device and also a terminal ing. Call the close () function to close the terminal.
3. signal processing. Generally, some signals must be ignored. The signal is equivalent to software interruption. in Linux/Unix, the signal mechanism provides a method to process asynchronous events. End Users enter the key that causes the interruption, or the system sends a signal, this will terminate the operation of one or more programs through the signal processing mechanism.
All signals have their own names. These names start with "sig", but they are different in the future. We can use these names to find out what happened in the system. When the signal appears, we can ask the system to perform the following three operations:
A. Ignore the signal. Most signals use this processing method, but the sigkill and sigstop signals cannot be ignored.
B. Capture signals. This is the most flexible operation method. This means that when a signal occurs, we can call a function to process the response. The most common situation is: if the sigchid signal is captured, it indicates that the sub-process has been terminated and can be used as the capture function to call waitpid () the process ID of the sub-process obtained by the function has ended. If a process creates a temporary file, you need to write a signal capture function for the Process Termination signal sigterm to clear the temporary files.
C. Execute the default action of the system. For the vast majority of signals, the default action of the system is to terminate the process.
In Linux, there are many types of signals. I will not introduce them here. If you want to learn more about these signals, you can view the header file <Sigal. h>, these signals are defined as positive integers, that is, their signal numbers. The signal () function must be used to process signals. The function is described in detail as follows:
-----------------------------------------------------------------
# Include <signal. h>
Void (* signal (INT signo, void (* func) (INT );
-----------------------------------------------------------------
The signo parameter indicates the signal name. The func parameter value can be used in the following situations as needed: (1) the constant sig_dfl indicates that the system performs the default action. (2) constant sig_ign, indicating that the signal is ignored. (3) Address of the processing function to be called after receiving the signal. The signal capturing program should have an integer parameter but no return value. The signal () function returns a function pointer, And the pointer to the function should have no return value (void), which actually points to the previous signal capturing program.
Next we will return to our daemonize () function. This function ignores three signals when creating a daemon:
Signal (sigttou, sig_ign );
Signal (sigttin, sig_ign );
Signal (sigtstp, sig_ign );
The meanings of these three signals are: sigttou indicates the background process write control terminal, sigttin indicates the background process read control terminal, and sigtstp indicates that the terminal is suspended.
4. disable unnecessary file descriptors, and open a new file descriptor for the standard input, standard output, and standard error output (it can also inherit the standard input, standard output, and standard error output file descriptor of the parent process, this operation is optional ). In our routine, because it is a proxy server program and the daemonize () is executed after the listen () function is executed, we need to keep the listening socket that has been converted successfully, so we can see the following statement:
If (FD! = Servfd)
Close (FD );
5. Call the function chdir ("/") to change the current working directory to the root directory. This is to ensure that our process does not use any directory. Otherwise, our daemon will always occupy a directory, which may prevent the Super User From detaching a file system.
6. Call the umask (0) function to set the blocking word created in the file mode to "0 ". This is because some permission may be prohibited if the word is blocked by the method of creating the inherited file. For example, our daemon needs to create a set of readable and writable files, while the blocking method of the file created by the daemon inherited from the parent process may block the two permissions, the read or write operations on a newly created group of files will not take effect. Therefore, you must set the blocking word created in the file mode to "0 ".
At the end of the daemonize () function, we can see such a signal capture processing statement:
Signal (sigcld, (sigfunc *) reap_status );
This is not a necessary step in the process of creating a daemon. Its function is to call our custom reap_status () function to process zombie processes. Reap_status () is defined in the routine:
-----------------------------------------------------------------
/*************************************** *************************
Function: reap_status
Description: handle a sigcld signal by reaping the exit status of the perished child, and discarding it.
Arguments: none.
Return Value: none.
Cballs: none.
Globals: none.
**************************************** ************************/
Void reap_status ()
{
Int PID;
Union wait status;
While (pid = wait3 (& status, wnohang, null)> 0)
;/* Loop while there are more dead children */
}
-----------------------------------------------------------------
The original text of the above signal capture statement is:
Signal (sigcld, reap_status );
As we have just said, the second parameter of the signal () function must have an integer parameter but no return value. Reap_status () has no parameters, so the original statement cannot pass during compilation. Therefore, I added the sigfunc () type definition in the pre-compilation section. Here, we use force type conversion for reap_status. In BSD systems, sigchld signals are usually used to process information related to sub-process termination. sigcld is a signal name defined in System V, if you set the processing method of the sigcld signal to capture, the kernel will immediately check whether there are child processes in the system that have terminated the processing. If so, the signal capture handler will be called immediately.
Generally, wait (), waitpid (), wait3 (), or wait4 () are called in the signal capture processing program to return the termination status of the sub-process. The difference between these "wait" functions is that when the child process that requires the function "wait" has not been terminated, wait () will block its callers; while in waitpid () you can set the parameter so that the caller is not blocked, and the wait () function is not set to wait for a specific sub-process. It is waiting for the one that is terminated first in all sub-processes of the caller, when waitpid () is called, the ID of the child process to be waited must be set in the parameter. The wait3 () and wait4 () parameters have one more "rusage" than the wait () and waitpid () parameters ". The reap_status () in the routine calls the function wait3 (), which is supported by the BSD system. We will list it with the definition of wait4:
-----------------------------------------------------------------
# Include <sys/types. h>
# Include <sys/Wait. H>
# Include <sys/time. h>
# Include <sys/resource. h>
Pid_t wait3 (int * statloc, int options, struct rusage * rusage );
Pid_t wait4 (pid_t PID, int * statloc, int options, struct rusage * rusage );
-----------------------------------------------------------------
If the pointer statloc is not "null", it points to the subprocess termination status returned. The PID parameter is the ID of the waiting sub-process. The options parameter is our control option, generally wnohang or wuntraced. The wnohang option is used in the routine, that is, if the sub-process's termination status cannot be returned immediately (for example, because the sub-process is not terminated), wait until the function is not blocked, and then return "0 ". The wuntraced option indicates that if the system supports job control, if the status of the child process to be waited has been suspended and its status has never been reported since it was paused, its status is returned. If the parameter rusage is not "null", it points to the resource digest returned by the kernel by the terminating process and all its sub-processes, the summary includes the user's total CPU time, page missing times, and number of received signals.
Statement: The Lupa open-source community publishes this article only to transmit information, and does not agree or disagree with it.