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.
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.
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.
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.
proc commands: procps-3.2.8 GNU bash, version 4.1.2 The CPU is x86_64.
OS CentOS release 6.8
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-17)
Recommended Posts