[Linux] Realize daemon process | Take tcpServer as an example

This article was first published in Mu Xue’s humble house

This article will use the tcp server code as the basis to describe how to daemonize the process and run it in the background

1. Daemon

The so-called daemon process is a process that has nothing to do with other processes; it runs independently in the background of the system, and will continue to run unless it exits or receives a signal to terminate

1.1 Process group

In the bash we use, there will only be one foreground process group at the same time

image-20230209101802180

As shown in the figure, when a foreground process starts running, we cannot start a second foreground process in the current terminal.

Add & amp; after the running command to temporarily let the current process run in the background. Note that although tcp is running in the background at this time, for it, the file descriptor of stdin/stdout/stderr still points to the input and output of the current bash, so its log will still print to the current terminal.

Use the ps command to view the information of the current process, where ppid is the parent process of the current process, that is, the current bash, pid is the process number, pgid is the group number of the process. You can see that this group number is different from the group number of the grep command.

image-20230209103412568

We use this c language code to call fork twice, which is equivalent to creating 3 child processes.

#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{<!-- -->
    fork();
    fork();
    sleep(100);
    return 0;
}

At this time, check the process information again, and you can see that the process group pgid of the four processes is the same, and it is the same as the pid of the first test; this means that the first test is the parent Process, the latter three are sub-processes.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-GiI0XFdN-1681715550272)(https://img.musnow.top/i/2023/02/63e45da7770c3.png )]

1.2 Process session

Here is another piece of information that we didn’t know much about before, what is the sid of the process?

Still the above example, as can be seen in the figure, the sid of the test and grep we executed are the same, and both are equal to the ppid of the first test process (the pid of bash)

image-20230209104248061

This shows that the five processes in the figure belong to the same process session, this session is the bash we are currently opening, and sid is used to represent the process session;

This is why we must have a terminal when we log in to linux. The linux system creates a session and loads bash to provide services to users.

Since there is a session, there must be a resource limit for the session. Once full, starts killing some processes.

./test &

Even if we use & amp; to let the process run in the background, it may be affected by the creation/closing of the session and be killed by the operating system? For example, we will bash of the currently running process Turn off, the foreground process will be terminated directly, and the background process will also be affected (it may or may not be terminated, depending on the system)

This is inconsistent with our requirements for the tcp server: what we need is to allow the process of the tcp server to run stably in the background all the time, Let the operating system leave it alone; unless the system memory is full and the load is heavy When there is really no other way, the operating system can come and kill him.

In order not to let the daemon process be affected by the process session, we must make it independent, form a process group and a new session by itself

This independent process can be called daemon process/genie process

2. Implementation

2.1 Write it yourself

Don’t think it’s difficult to write this, it’s actually very simple!

2.1.1 setsid

The setsid interface that needs to be used here, its function is like the name, is to set the process session group of the current process

 #include <unistd.h>
       pid_t setsid(void);

But there is a requirement for calling this function: the calling process cannot be the leader of the process group!

For example, in the figure below, the first test is the leader of the process group, and it cannot call this function. will report an error

image-20230209104248061

So how do you keep yourself from becoming the leader of the process group? It’s very simple, just create a child process and it’s ok!

 if (fork() > 0)
        exit(0);//The parent process exits directly

2.1.2 Redirect to dev/null

If you don’t know what is /dev/null, in short, this is a data trash can under linux. Unlike the Windows Recycle Bin, which stores deleted data, this trash can is a black hole, and the things thrown in will not be stored, but will be discarded directly!

The daemon process needs to redirect the default 0.1.2 file descriptors to dev/null, because after setting it as an independent process group and process session, the current process It is not associated with bash.

At this point, the bash pointed to by 0 1 2 is invalid by default! If it is not redirected, when using cout to print, an exception will be thrown (it can be understood as writing content to a file that does not exist), the server will exit directly, and the daemon process cannot be implemented.

After redirection, all printouts will be thrown into the /dev/null file trash can, so you don’t need to worry about the above problems.

 if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {<!-- -->
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. Close unnecessary fd
        // Because fd is only temporarily used for redirection, it can be turned off after the operation is completed
        if(fd > STDERR_FILENO)
            close(fd);
    }

You may be wondering, the log information has also been thrown into the trash can, what should I do?

It’s very simple, because the logs of our server all use the logging function in log.hpp, so we only need to redo the output of the logging function Directed to the log file, it’s ok!

2.1.3 chdir (optional)

The purpose of this operation is to modify the working path. As a server process, a lot of log information is stored in the /etc/ directory instead of the current path. For safety, you should also use the absolute path instead of the relative path to avoid Unable to read and write files caused by working directory switching

However, if you use an absolute path, even if we do not modify the working directory, we can still access it normally; so this operation is optional

2.1.4 Signal Capture

The advantage of writing this function by ourselves is that we can customize and capture some signals in it, and add our own custom methods to these signals;

For example, SIGPIPE is the signal of the pipeline. When the reading end of the pipeline is closed, the writing end will be terminated; at this time, the writing end will receive this signal. If this signal is not SIG_IGN ignored, our server will terminate directly!

signal(SIGPIPE, SIG_IGN);

In addition to this signal, we can also custom capture the No. 2 or No. 3 signal, set the exit signal, so that the server can safely exit (save log information to disk, release resources, etc.; although the process exits The operating system will do these things for us later, but we can make the project more standardized by writing it this way)

2.1.5 Complete code

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include  // required by O_RDWR

void daemonize()
{
    int fd = 0;
    // 1. Ignore SIGPIPE (pipe read and write, the read end is closed, and the write end will receive a signal to terminate)
    signal(SIGPIPE, SIG_IGN);
    // 2. Change the working directory of the process
    // chdir(); // Can be changed or not
    // 3. Let yourself not be the leader of the process group
    if (fork() > 0)
        exit(0);
    // 4. Set yourself as an independent session
    setsid();
    // 5. Redirect 0,1,2
    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {<!-- -->
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. Close unnecessary fd
        // Because fd is only temporarily used for redirection, it can be turned off after the operation is completed
        if(fd > STDERR_FILENO)
            close(fd);
    }
    // There is another operation here, which is to close stdin/stdout/stderr
    // But this will cause the process to exit abnormally as long as there is a printout code
}

That’s right, just this little bit of code can turn our tcp server into a daemon process!

image-20230209134324709

At this time, our client can still connect to the server normally and get the result

image-20230209134353714

2.2 nohup

no hang up (no hang up), used to run commands in the background of the system without hanging up, and exiting the terminal will not affect the running of the program. Use the nohup command to execute a process, you can make this process a process that is not affected by the terminal exit

nohup ./test &

At this point, nohup will create a nohup.out file in the current directory to record the output information of the test process (if > or >> performs redirection, it will not be created)

image-20230209110836838

Through ps, we can see that the process session of the current test process is still the same as bash, but if we close the current bash, the test process can still run normally, but the parent process will become the operating system1, our goal has been achieved

image-20230209111411664

2.3 deamon interface

There is an interface daemon in the linux system, which can help us realize the daemon process

 #include <unistd.h>
       int daemon(int nochdir, int noclose);

After understanding the writing method of the daemon process, the role of these two parameters is obvious

  • The first parameter nochdir indicates whether the working directory needs to be modified; if it is set to 0, the working directory will be switched to the / system root directory
  • The second parameter noclose indicates whether to redirect the basic io to /dev/null; set it to 0 to redirect

The following is the description in the man manual

If nochdir is zero, daemon() changes the calling process's current working directory to the root directory ("/"); otherwise, the cur‐
rent working directory is left unchanged.

If noclose is zero, daemon() redirects standard input, standard output and standard error to /dev/null; otherwise, no changes are made
to these file descriptors.

We directly use a simple code to demonstrate

#include <unistd.h>

int main()
{<!-- -->
    //No need to modify the working directory, the first parameter is set to 1
    //Because no printing is performed, the redirection is set to 1, and no redirection is performed
    int ret = daemon(1,1);
    sleep(100);
    return 0;
}

After running, you can see that the parent id of this process is the operating system, which forms a process group and process session; it is the same as the function we wrote

image-20230209131905946

3. Redirect log

Because the daemon throws the input and output into the trash, we need to redirect the output of the log

#define LOG_PATH "./log.txt" //log.txt under the working path

// This class is only used for redirection, no need to add other things in it
class Logdup
{<!-- -->
public:
    Logdup()
        :_fdout(-1),_fderr(-1)
    {<!-- -->}
    Logdup(const char* pout=LOG_PATH, const char* perr="")
        :_fdout(-1),_fderr(-1)
    {<!-- -->
        //If only the first pout is passed in, it means redirecting perr and pout to a path
        umask(0);
        int logfd = open(pout, O_WRONLY | O_CREAT | O_APPEND, 0666);
        assert(logfd != -1);
        _fdout = _fderr = logfd;//Assignment can be connected
        //Check if it is an empty string
        if(strcmp(perr,"")!=0)//Not the same, it means that the path of err is set separately
        {<!-- -->
            logfd = open(perr, O_WRONLY | O_CREAT | O_APPEND, 0666);
            assert(logfd != -1);
            _fderr = logfd;
        }
        dup2(_fdout, 1);//redirect stdout
        dup2(_fderr, 2);//redirect stderr
    }

    ~Logdup()
    {<!-- -->
        if(_fdout != -1)
        {<!-- -->
            fsync(_fdout);
            fsync(_fderr);
            // Write to disk first and then close
            close(_fdout);
            close(_fderr);
        }
    }
private:
    int _fdout;//Redirected log file descriptor
    int _fderr;//redirected wrong file descriptor
};c

After doing all this, we run the server, and indeed the log.txt file is created, but it is empty

image-20230209143230108

This is because our data is actually written in the buffer, we need to add a refresh mechanism in the logging, so that the data can be written to the hard disk as soon as possible to avoid log loss

 fflush(out); // Flush the data in the C buffer to the OS
    fsync(fileno(out));// Write the data in the OS to the hard disk

At this time, run the server again, and you can see that the log is quickly written to the file.

image-20230209144101425

over

Done!