[LINUX] The difference between foreground and background processes understood by the principle

Can't top run in the background?

Suddenly, do you know why processing on a Unix-like device results in the following? Also, is there any way to prevent this from happening?

python


$ top &
[1] 11424
$ jobs -l
[1]+11424 Stopped(Terminal output)         top

When you work with the OS in the shell, you often hear the terms background and foreground processes.

If you're an engineer, you probably haven't heard of it, and you probably know the difference, but you probably didn't have much opportunity to understand it systematically. ??

Originally I was writing an article with the intention of posting about the daemon process to Qiita, but due to the nature of the daemon process, I have to mention the background process, so I made one entry including the explanation. I thought it would be complicated, so this time I focused on the background process.

The above is the behavior when the program top is executed in the background. top is a program that monitors system resources in real time, but if you run it in the background, the process will stop immediately. Of course, that's because it's implemented, but why does it stop?

What is a background process in the first place? And what is a foreground process?

First, let's clarify the difference between the background process and the foreground process.

Foreground and background processes

Normally, the process started by the user should be started (forked) by the shell (assuming a command line interface in this case), so Ctrl + C when using a terminal driver from a peripheral such as a keyboard You can notify the process of SIGINT by entering. And the process notified of SIGINT will end in most situations, depending on the implementation.

A process group that is ready to accept input from a terminal in this way is called a foreground process group, and all other process groups that are not are called background process groups.

Alternatively, it is simply called a foreground process or a background process.

This difference is a bit confusing, but when you generally say that you want to do something in the background, it often means that the foreground process is a shell. There are many cases because there are cases where it is not.

Let's create a program to check the status.

python


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main (int c, char *argv[]) {

    for (;;){
        fputs("test\n", stdout);
        //0 is file descriptor associated with the streams stdin.
        printf("stdin is %d\n", (int)tcgetpgrp(0));
        //1 is file descriptor associated with the streams stdout.
        printf("stdout is %d\n", (int)tcgetpgrp(1));
        sleep(5);
    }   

    return EXIT_SUCCESS;
}

The tcgetpgrp function takes a file descriptor as an argument and gets the process group ID of the corresponding foreground process. This time we are passing the standard input and standard output file descriptors.

For the file descriptor, please refer to [Hacking the file descriptor of Linux] that I wrote before.

python


$ ./a.out

Execute the compiled executable file in the foreground. Launching in the foreground is just a matter of running.

python


test
stdin is 10455
stdout is 10455
test
stdin is 10455
stdout is 10455

Then, the string test and the process ID of the terminal foreground process group referenced in the standard input and standard output will be displayed on the screen.

Since this process has no software termination condition, it will not terminate forever unless a signal such as SIGINT is notified to the process from the outside.

If you check with ps -jf f,

python


UID        PID  PPID  PGID   SID  C STIME TTY      STAT   TIME CMD
tajima   10360 10359 10360 10360  0 21:55 pts/1    Ss     0:00 -bash
tajima   10455 10360 10455 10360  0 22:25 pts/1    S+     0:00  \_ ./a.out

Since the running PGID is 10455, we know that ./a.out is the foreground process. In other words, it is possible to notify, input, and output signals from the terminal to the ./a.out process.

I typed Ctrl + C from the keyboard as a trial, and when I notified SIGINT, it ended.

Now let's run it in the background.

python


$ ./a.out &

A trailing & will cause the shell to run the program in the background.

python


test
stdin is 10360
stdout is 10360
test
stdin is 10360
stdout is 10360

Of course, the process group ID has changed, but it is no exaggeration to say that it is the same as running a program in the foreground on the display.

If you check with ps -jf f,

python


UID        PID  PPID  PGID   SID  C STIME TTY      STAT   TIME CMD
tajima   10360 10359 10360 10360  0 21:55 pts/1    Ss+    0:00 -bash
tajima   10460 10360 10460 10360  0 22:52 pts/1    S      0:00  \_ ./a.out

Since the PGID of the terminal's foreground process group was 10360, we know that bash (shell) is the foreground process. And you can see that ./a.out is a background process. In other words, it is possible to notify and input signals to the bash process from the terminal.

If you try to enter Ctrl + C from the keyboard and notify SIGINT, it will appear as if it ended for a moment. But soon

python


test
stdin is 10360
stdout is 10360
test
stdin is 10360
stdout is 10360

Is repeated endlessly.

This is because the previous Ctrl + C was notified to bash, not to ./a.out.

To make this process a foreground process, that is, to link terminal I / O to ./a.out, look up the job number with the shell's built-in command jobs and find that number in fg. Switch to the foreground process with. By the way, the process is managed by the OS, but the job is managed by the running shell.

python


$ jobs
[1]Running./a.out &
$ fg %1

If you do

python


test
stdin is 10460
stdout is 10460
test
stdin is 10460
stdout is 10460

As you can see, the process group ID of the foreground process has changed to ./a.out.

So, internally, how does the state of the process change when the background process is forced into the foreground by fg?

Earlier, I explained that the function called tcgetpgrp is a function to get the process group ID of the foreground process, but on the contrary, there is also an API called tcsetpgrp function, which sets the terminal corresponding to the file descriptor to a specific process group. It has the role of setting it in the ID.

Let's write some code that makes the foreground process a background process without the help of the shell.

The point is that the process group ID of the shell should be the foreground process ID.

First, find out the process group ID of the current shell.

python


 PID  PGID   SID TTY      STAT   TIME COMMAND
15243 15243 15243 pts/1    Ss     0:00 -bash
25023 25023 15243 pts/1    R+     0:00  \_ ps -j f

Keep this in mind as the PGID is 15243.

python


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main (int c, char *argv[]) {

    for (;;){
        fputs("test\n", stdout);
        //0 is file descriptor associated with the streams stdin.
        printf("stdin is %d\n", (int)tcgetpgrp(0));
        //1 is file descriptor associated with the streams stdout.
        printf("stdout is %d\n", (int)tcgetpgrp(1));
        sleep(5);

        if (tcsetpgrp(0, 15243) < 0) {
            perror("tcsetpgrp() for stdin error");
        }   
        if (tcsetpgrp(1, 15243) < 0) {
            perror("tcsetpgrp() for stdout error");
        }   
    }   

    return EXIT_SUCCESS;

If I simply do it this way, I wonder if the foreground will turn the process into a background process after 5 seconds, but it doesn't work.

When tcsetpgrp succeeds and the process becomes a background process, when fputs tries to write to the terminal, a signal SIGTTOU is sent, but this default behavior is that the process is stopped because the process is stopped. Because it will do.

python


$jobs -l
[1]+29669 Stopped(Terminal output)         ./a.out

However, there is another small reason I need to explain, which I'll discuss later.

background execution of top

Here is the explanation for the first question.

Let's revisit the first question. Try running the top command in the background to understand the current system status.

python


$ top &

If you check the process status in this state,

jobs -l
[1]+6432 stopped(Terminal output)         top

You can see that it stopped by the terminal output. This means that the SIGTTOU notification caused the process to stop.

Let's check the source code of top.

top.c3342-3363



int main (int dont_care_argc, char *argv[])
{
   (void)dont_care_argc;
   before(*argv);
   windows_stage1();                    //                 top (sic) slice
   configs_read();                      //                 > spread etc, <
   parse_args(&argv[1]);                //                 > lean stuff, <
   whack_terminal();                    //                 > onions etc. <
   windows_stage2();                    //                 as bottom slice
                                        //                 +-------------+
                                        //                 +-------------+
   signal(SIGALRM,  end_pgm);
   signal(SIGHUP,   end_pgm);
   signal(SIGINT,   end_pgm);
   signal(SIGPIPE,  end_pgm);
   signal(SIGQUIT,  end_pgm);
   signal(SIGTERM,  end_pgm);
   signal(SIGTSTP,  suspend);
   signal(SIGTTIN,  suspend);
   signal(SIGTTOU,  suspend);
   signal(SIGCONT,  wins_resize_sighandler);
   signal(SIGWINCH, wins_resize_sighandler);

It happens inside a function called whack_terminal inside the main function.

top.c1932-1954


static void whack_terminal (void)
{
   struct termios newtty;

   if (Batch) {
      setupterm("dumb", STDOUT_FILENO, NULL);
      return;
   }
   setupterm(NULL, STDOUT_FILENO, NULL);
   if (tcgetattr(STDIN_FILENO, &Savedtty) == -1)
      std_err("failed tty get");
   newtty = Savedtty;
   newtty.c_lflag &= ~(ICANON | ECHO);
   newtty.c_oflag &= ~(TAB3);
   newtty.c_cc[VMIN] = 1;
   newtty.c_cc[VTIME] = 0;

   Ttychanged = 1;
   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtty) == -1) {
      putp(Cap_clr_scr);
      std_err(fmtmk("failed tty set: %s", strerror(errno)));
   }
   tcgetattr(STDIN_FILENO, &Rawtty);

It is caused by executing a function called tcsetattr that changes the attribute value of the terminal. If this function is executed as a background process, SIGTTOU will send the process and the execution will be paused.

So, for example, if you change the source code of top and compile it as shown below, you will have a top command that can be continuously executed even in the background.

top.c1932-1954


int main (int dont_care_argc, char *argv[])
{
   signal(SIGTTOU, SIG_IGN);
   signal(SIGTTIN, SIG_IGN);
   (void)dont_care_argc;
   before(*argv);
   windows_stage1();                    //                 top (sic) slice
   configs_read();                      //                 > spread etc, <
   parse_args(&argv[1]);                //                 > lean stuff, <
   whack_terminal();                    //                 > onions etc. <
   windows_stage2();                    //                 as bottom slice
                                        //                 +-------------+
                                        //                 +-------------+
   signal(SIGALRM,  end_pgm);
   signal(SIGHUP,   end_pgm);
   signal(SIGINT,   end_pgm);
   signal(SIGPIPE,  end_pgm);
   signal(SIGQUIT,  end_pgm);
   signal(SIGTERM,  end_pgm);
   signal(SIGTSTP,  suspend);
   //signal(SIGTTIN,  suspend);
   //signal(SIGTTOU,  suspend);
   signal(SIGCONT,  wins_resize_sighandler);
   signal(SIGWINCH, wins_resize_sighandler);

It simply tells the process to ignore the SIGTTOU and SIGTTIN signals and comments out the execution of the signal handler suspend. If you build top in this state,

python


$ top &

System resource status is periodically spit out to standard output asynchronously without stopping. I don't think it's practical in general, but I like it by calling it infinite top. If you are interested, please experiment.

By the way, in general, another case where the background process stops is assumed when the terminal is ready to accept input. This is notified of SIGTTIN, but its default behavior is the same process stop as SIGTTOU. Let's check this with a simple sample code as well.

input.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char *argv[]) {

    char a[10];

    if (fgets(a, sizeof(a), stdin) == NULL) {
        fprintf(stderr, "invalid input string.\n");
        exit(EXIT_FAILURE);
    }   

    a[strlen(a)-1] = '\0';

    printf("%s\n", a); 

    return EXIT_SUCCESS;
}

Run this in the background.

python


$ ./a.out &

When I check the process status,

python


$ jobs -l
[1]+6490 Stopped(Terminal input)         ./a.out

It is stopped by terminal input (SIGTTIN) like.

Let's check SIGTTOU with a simple program just in case. In the top example above, I explained that SIGTTOU's notification to a background process causes the process to stop, so that should be true.

out.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char *argv[]) {

    char a[10] = "test";

    for (;;) {
        printf("%s\n", a); 
        sleep(3);
    }   

    return EXIT_SUCCESS;
}

When I run this program in the background,

python


$ ./a.out &
test
test
jobs -l
[1]+6562 running./a.out &

The process is running and does not stop. What does that mean?

Actually, SIGTTOU is not as simple as SIGTTIN, and it requires settings that go into the control information of the terminal driver.

The kernel processing group that controls the data flow between the process and the external terminal is called the terminal driver (tty driver), and the behavior related to SIGTTOU changes depending on the settings of this terminal driver.

You can check the current terminal settings with the command stty.

python


$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok

Depending on the environment, SIGTTOU is sent to the process when the program is executed in the background only when the flag "to stop" is set in this setting.

To set tostop with the previous command, do as follows.

python


$ stty tostop
$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok tostop

A setting called tostop has been added. In this state, try running the previous program.

python


$ ./a.out &
jobs -l
[1]+11236 Stopped(Terminal output)         ./a.out

In this way, unlike before, the process is stopped by detecting the output to the terminal. Therefore, in an environment where the tostop flag is set from the beginning, you should be able to confirm that it will stop at the output to the terminal during background execution without any special settings.

To cancel the setting with the stty command, add-in front of the flag name.

python


$ stty -tostop
$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok

Now let's set it inside the program without using stty.

out2.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <signal.h>

int main (int argc, char *argv[]) {

    struct termios newtty, oldtty;

    if (tcgetattr(1, &oldtty) == -1) {
        fprintf(stderr, "tcgetattr() failed.\n");
        exit(EXIT_FAILURE);
    }   

    newtty = oldtty;
    newtty.c_lflag |= TOSTOP;

    if (tcsetattr(1, TCSAFLUSH, &newtty) == -1) {
        fprintf(stderr, "tcsetattr() failed.\n");
        exit(EXIT_FAILURE);
    }

    char a[10] = "test";

    for (;;) {
        printf("%s\n", a);
        sleep(3);
    }

    return EXIT_SUCCESS;
}

The terminal driver is set and acquired using the data type called termios structure. This code is similar to the setting part of the top code introduced earlier, but I think it is easier to understand why the background process stopped at the output to the terminal.

The point is that after acquiring the current terminal driver settings, the TOSTOP flag is set on it and the terminal driver is set again.

bash mods

By the way, I failed to make the background process from the foreground process by force due to the notification of SIGTTOU, so I will modify bash.

The operation will be different depending on the shell, but in the case of bash, the signal control processing is performed in the following part.

jobs.c1904-1910



 void
 default_tty_job_signals ()
 {
   set_signal_handler (SIGTSTP, SIG_DFL);
   set_signal_handler (SIGTTIN, SIG_DFL);
   set_signal_handler (SIGTTOU, SIG_DFL);
 }

So, change the process when creating a child process as follows.

jobs.c1762-1767


       if (pipeline_pgrp == shell_pgrp)
         ignore_tty_job_signals ();
       else
         default_tty_job_signals ();                                        
         
       set_signal_handler (SIGTTOU, SIG_IGN);  

Put set_signal_handler (SIGTTOU, SIG_IGN) around here and build bash. Since SIGTTIN and SIGTSTP are not particularly relevant this time, Ignore is not specified.

This way, the process that was stopped earlier will be started in the stopped foreground and then run in the background. Input from the terminal does not reach the started process.

However, as it is, the input and output from the terminal do not reach the started process, so it can not be said that it is strictly running in the background, but the principle difference between foreground and background execution is I think you understand.

To fully implement it, you need to understand how to handle job control, but I'd like to explore it separately as well.

This time, we approached the foreground process and the background process. Next time, based on these, I will approach the daemon process.

Referenced source code

proc commands: procps-3.2.8 GNU bash, version 4.1.2 The CPU is x86_64.

OS CentOS release 6.8

compiler

gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-17)

Recommended Posts

The difference between foreground and background processes understood by the principle
What is the difference between `pip` and` conda`?
About the difference between "==" and "is" in python
About the difference between PostgreSQL su and sudo
What is the difference between Unix and Linux?
Consideration of the difference between ROC curve and PR curve
The rough difference between Unicode and UTF-8 (and their friends)
Can BERT tell the difference between "candy (candy)" and "candy (rain)"?
What is the difference between usleep, nanosleep and clock_nanosleep?
How to use argparse and the difference between optparse
What is the difference between a symbolic link and a hard link?
Difference between process and job
Difference between "categorical_crossentropy" and "sparse_categorical_crossentropy"
Understand the difference between cumulative assignment to variables and cumulative assignment to objects
Difference between regression and classification
Difference between np.array and np.arange
Difference between MicroPython and CPython
Difference between ps a and ps -a
Difference between return and print-Python
I investigated the behavior of the difference between hard links and symbolic links
Difference between Ruby and Python split
Difference between java and python (memo)
Difference between list () and [] in Python
Difference between SQLAlchemy filter () and filter_by ()
Difference between == and is in python
Memorandum (difference between csv.reader and csv.dictreader)
(Note) Difference between gateway and default gateway
Difference between sort and sorted (memorial)
[Python] Difference between function and method
Difference between SQLAlchemy flush () and commit ()
Python --Difference between exec and eval
[Python] Difference between randrange () and randint ()
[Python] Difference between sorted and sorted (Colaboratory)
[Introduction to Python] What is the difference between a list and a tuple?
[Xg boost] Difference between softmax and softprob
[Django ORM] Difference between values () and only ()
Difference between PHP and Python finally and exit
Difference between @classmethod and @staticmethod in Python
Difference between append and + = in Python list
Difference between nonlocal and global in Python
Difference between linear regression, Ridge regression and Lasso regression
[Python] Difference between class method and static method
Java compilation and execution understood by CLI
Difference between docker-compose env_file and .env file
The subtle relationship between Gentoo and pip
[Python Iroha] Difference between List and Tuple
[python] Difference between rand and randn output
speed difference between wsgi, Bottle and Flask
Difference between numpy.ndarray and list (dimension, size)
Difference between ls -l and cat command
Difference and compatibility verification between keras and tf.keras # 1