Wissen Sie plötzlich, warum die Verarbeitung auf einem Unix-ähnlichen Gerät Folgendes bewirkt? Gibt es auch eine Möglichkeit, dies zu verhindern?
python
$ top &
[1] 11424
$ jobs -l
[1]+11424 gestoppt(Terminalausgang) top
Wenn Sie mit dem Betriebssystem in der Shell arbeiten, hören Sie häufig die Begriffe Hintergrund- und Vordergrundprozesse.
Wenn Sie Ingenieur sind, haben Sie wahrscheinlich noch nichts davon gehört, und Sie kennen wahrscheinlich den Unterschied, aber Sie hatten wahrscheinlich nicht viel Gelegenheit, ihn systematisch zu verstehen. ??
Ursprünglich schrieb ich einen Artikel mit der Absicht, über den Daemon-Prozess in Qiita zu posten, aber aufgrund der Art des Daemon-Prozesses muss ich den Hintergrundprozess erwähnen und habe einen Eintrag mit der Erklärung gemacht. Ich dachte, es wäre kompliziert, also konzentrierte ich mich diesmal auf den Hintergrundprozess.
Das Obige ist das Verhalten, wenn die Programmoberseite im Hintergrund ausgeführt wird. top ist ein Programm, das Systemressourcen in Echtzeit überwacht. Wenn Sie es jedoch im Hintergrund ausführen, wird der Prozess sofort gestoppt. Das liegt natürlich daran, dass es implementiert ist, aber warum hört es auf?
Was ist überhaupt ein Hintergrundprozess? Und was ist ein Vordergrundprozess?
Lassen Sie uns zunächst den Unterschied zwischen dem Hintergrundprozess und dem Vordergrundprozess klären.
Normalerweise sollte der vom Benutzer gestartete Prozess von der Shell gestartet (gegabelt) werden (in diesem Fall unter der Annahme der Befehlszeilenschnittstelle). Wenn Sie also den Terminaltreiber von einem Peripheriegerät wie einer Tastatur aus verwenden, "Strg + C" Sie können den Vorgang von SIGINT durch Eingabe benachrichtigen. Der über SIGINT gemeldete Prozess wird in den meisten Situationen abhängig von der Implementierung beendet.
Die Prozessgruppe, die bereit ist, Eingaben vom Terminal auf diese Weise zu akzeptieren, wird als Vordergrundprozessgruppe bezeichnet, und alle anderen Prozessgruppen, die nicht als Hintergrundprozessgruppe bezeichnet werden.
Alternativ wird es einfach als Vordergrundprozess oder Hintergrundprozess bezeichnet.
Dieser Unterschied ist etwas verwirrend, aber wenn Sie allgemein sagen, dass Sie etwas im Hintergrund tun möchten, bedeutet dies oft, dass der Vordergrundprozess eine Shell ist. Es gibt viele Fälle, weil es Fälle gibt, in denen dies nicht der Fall ist.
Lassen Sie uns ein Programm erstellen, um den Status zu überprüfen.
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;
}
Die Funktion tcgetpgrp verwendet einen Dateideskriptor als Argument und ruft die Prozessgruppen-ID des entsprechenden Vordergrundprozesses ab. Dieses Mal übergeben wir die Deskriptoren für Standardeingabe- und Standardausgabedateien.
Informationen zum Dateideskriptor finden Sie unter [Hacken des Linux-Dateideskriptors], das ich zuvor geschrieben habe.
python
$ ./a.out
Führen Sie die kompilierte ausführbare Datei im Vordergrund aus. Das Starten im Vordergrund ist nur eine Frage des Laufens.
python
test
stdin is 10455
stdout is 10455
test
stdin is 10455
stdout is 10455
Anschließend werden der Zeichenkettentest und die Prozess-ID der Vordergrundprozessgruppe des Terminals, auf die in der Standardeingabe und Standardausgabe verwiesen wird, auf dem Bildschirm angezeigt.
Da dieser Prozess in der Software keine Beendigungsbedingung aufweist, wird er nicht für immer beendet, es sei denn, ein Signal wie SIGINT wird dem Prozess von außen mitgeteilt.
Wenn Sie mit ps -jf f
prüfen,
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
Da die laufende PGID 10455 ist, wissen wir, dass ./a.out der Vordergrundprozess ist. Mit anderen Worten, es ist möglich, Signale vom Terminal an den ./a.out-Prozess zu benachrichtigen, einzugeben und auszugeben.
Ich habe als Testversion "Strg + C" über die Tastatur eingegeben, und als ich SIGINT benachrichtigte, endete es.
Lassen Sie es uns jetzt im Hintergrund ausführen.
python
$ ./a.out &
Wenn Sie am Ende & hinzufügen, führt die Shell das Zielprogramm im Hintergrund aus.
python
test
stdin is 10360
stdout is 10360
test
stdin is 10360
stdout is 10360
Natürlich hat sich die Prozessgruppen-ID geändert, aber es ist keine Übertreibung zu sagen, dass dies mit dem Ausführen eines Programms im Vordergrund auf dem Display identisch ist.
Wenn Sie mit ps -jf f
prüfen,
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
Da die PGID der terminalen Vordergrundprozessgruppe 10360 war, wissen wir, dass die Bash (Shell) der Vordergrundprozess ist. Und Sie können sehen, dass ./a.out der Hintergrundprozess ist. Mit anderen Worten, es ist möglich, Signale vom Terminal aus zu benachrichtigen und in den Bash-Prozess einzugeben.
Wenn Sie versuchen, "Strg + C" über die Tastatur einzugeben und SIGINT zu benachrichtigen, wird es so angezeigt, als ob es für einen Moment beendet wäre. Aber bald
python
test
stdin is 10360
stdout is 10360
test
stdin is 10360
stdout is 10360
Wird endlos wiederholt.
Dies liegt daran, dass das vorherige Strg + C
benachrichtigt wurde, um zu schlagen, nicht um ./a.out.
Um diesen Prozess zu einem Vordergrundprozess zu machen, dh um die Ein- / Ausgabe des Terminals mit ./a.out zu verknüpfen, suchen Sie die Jobnummer mit dem in der Shell (bash) integrierten Befehl jobs
und suchen Sie diese Nummer in fg. Wechseln Sie mit in den Vordergrund.
Der Prozess wird übrigens vom Betriebssystem verwaltet, der Job jedoch von der laufenden Shell.
python
$ jobs
[1]Laufen./a.out &
$ fg %1
Wenn Sie tun
python
test
stdin is 10460
stdout is 10460
test
stdin is 10460
stdout is 10460
Wie Sie sehen können, hat sich die Prozessgruppen-ID des Vordergrundprozesses in ./a.out geändert.
Wie ändert sich also intern der Status des Prozesses, wenn der Hintergrundprozess von fg in den Vordergrund gedrängt wird?
Zuvor habe ich erklärt, dass die Funktion tcgetpgrp eine Funktion zum Abrufen der Prozessgruppen-ID des Vordergrundprozesses ist. Im Gegenteil, es gibt auch eine API namens tcsetpgrp, die das dem Dateideskriptor entsprechende Terminal als bestimmte Prozessgruppe angibt. Es hat die Aufgabe, es in der ID festzulegen.
Schreiben wir Code, der den Vordergrundprozess ohne Hilfe der Shell in einen Hintergrundprozess verwandelt.
Der Punkt ist, dass die Prozessgruppen-ID der Shell die Prozess-ID im Vordergrund sein sollte.
Ermitteln Sie zunächst die Prozessgruppen-ID der aktuellen 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
Beachten Sie dies, da die PGID 15243 lautet.
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;
Wenn ich es einfach so mache, frage ich mich, ob der Vordergrund den Prozess nach 5 Sekunden in einen Hintergrundprozess verwandelt, aber es funktioniert nicht.
Wenn ein Prozess, der nach dem Erfolg von tcsetpgrp zu einem Hintergrundprozess wurde, versucht, über fputs in das Terminal zu schreiben, wird ein Signal SIGTTOU gesendet. Dieses Standardverhalten besteht jedoch darin, dass der Prozess gestoppt wird, weil der Prozess gestoppt wird. Weil es gemacht wird.
python
$jobs -l
[1]+29669 gestoppt(Terminalausgang) ./a.out
Es gibt jedoch noch einen weiteren kleinen Grund, dies zu erklären, auf den ich später noch eingehen werde.
Hier ist die Erklärung für die erste Frage.
Lassen Sie uns die erste Frage noch einmal betrachten. Versuchen Sie, den Befehl top im Hintergrund auszuführen, um den aktuellen Systemstatus zu verstehen.
python
$ top &
Wenn Sie den Status des Prozesses in diesem Status überprüfen,
jobs -l
[1]+6432 gestoppt(Terminalausgang) top
Sie können sehen, dass es vom Terminalausgang gestoppt wurde. Dies bedeutet, dass die SIGTTOU-Benachrichtigung den Prozess gestoppt hat.
Lassen Sie uns den Quellcode von top überprüfen.
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);
Dies geschieht innerhalb einer Funktion namens whack_terminal innerhalb der Hauptfunktion.
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);
Dies wird durch die Ausführung einer Funktion namens tcsetattr verursacht, die den Attributwert des Terminals ändert. Wenn diese Funktion als Hintergrundprozess ausgeführt wird, sendet SIGTTOU den Prozess und die Ausführung wird angehalten.
Wenn Sie beispielsweise den Quellcode von top wie unten gezeigt ändern und kompilieren, erhalten Sie einen Befehl top, der auch im Hintergrund kontinuierlich ausgeführt werden kann.
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);
Es weist den Prozess einfach an, die Signale SIGTTOU und SIGTTIN zu ignorieren, und kommentiert die Ausführung des Signal-Handler-Suspend aus. Wenn Sie in diesem Zustand oben bauen,
python
$ top &
Der Status der Systemressourcen wird regelmäßig asynchron ohne Unterbrechung an die Standardausgabe ausgegeben. Ich denke nicht, dass es im Allgemeinen praktisch ist, aber ich mag es, wenn ich es unendlich oben nenne. Wenn Sie interessiert sind, versuchen Sie es bitte.
Übrigens wird im Allgemeinen ein anderer Fall angenommen, in dem der Hintergrundprozess stoppt, wenn das Terminal bereit ist, Eingaben zu akzeptieren. SIGTTIN wird hier benachrichtigt, aber sein Standardverhalten ist der gleiche Prozessstopp wie bei SIGTTOU. Lassen Sie uns dies auch mit einem einfachen Beispielcode überprüfen.
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;
}
Führen Sie dies im Hintergrund aus.
python
$ ./a.out &
Wenn ich den Status des Prozesses überprüfe,
python
$ jobs -l
[1]+6490 gestoppt(Klemmeneingang) ./a.out
Es wird durch Klemmeneingang (SIGTTIN) wie gestoppt.
Lassen Sie uns SIGTTOU mit einem einfachen Programm für alle Fälle überprüfen. Im obigen oberen Beispiel habe ich erklärt, dass die Benachrichtigung von SIGTTOU an Hintergrundprozesse dazu führt, dass der Prozess gestoppt wird. Dies sollte also der Fall sein.
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;
}
Wenn ich dieses Programm im Hintergrund starte,
python
$ ./a.out &
test
test
jobs -l
[1]+6562 läuft./a.out &
Der Prozess läuft und stoppt nicht. Was bedeutet das?
Tatsächlich ist SIGTTOU nicht so einfach wie SIGTTIN und erfordert Einstellungen, die in die Steuerinformationen des Terminaltreibers eingehen.
Die Verarbeitungsgruppe des Kernels, die den Datenfluss zwischen dem Prozess und dem externen Terminal steuert, wird als Terminaltreiber (tty-Treiber) bezeichnet, und das Verhalten in Bezug auf SIGTTOU ändert sich abhängig von der Einstellung dieses Terminaltreibers.
Sie können die aktuellen Terminaleinstellungen mit dem Befehl stty überprüfen.
python
$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok
Abhängig von der Umgebung wird SIGTTOU nur dann an den Prozess gesendet, wenn das Programm im Hintergrund ausgeführt wird, wenn in dieser Einstellung das Flag "Anhalten" gesetzt ist.
Gehen Sie wie folgt vor, um den Stopp mit dem vorherigen Befehl einzustellen.
python
$ stty tostop
$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok tostop
Eine Einstellung namens tostop wurde hinzugefügt. Versuchen Sie in diesem Zustand, das vorherige Programm auszuführen.
python
$ ./a.out &
jobs -l
[1]+11236 gestoppt(Terminalausgang) ./a.out
Auf diese Weise wird der Prozess im Gegensatz zu zuvor gestoppt, indem der Ausgang zum Terminal erkannt wird. In einer Umgebung, in der das Topstop-Flag von Anfang an gesetzt ist, sollten Sie daher bestätigen können, dass es während der Hintergrundausführung ohne spezielle Einstellungen am Ausgang des Terminals stoppt.
Um die Einstellung mit dem Befehl stty abzubrechen, fügen Sie vor dem Flaggennamen ein.
python
$ stty -tostop
$ stty
speed 9600 baud; line = 0;
eol = M-^?; eol2 = M-^?;
-brkint ixany
-echok
Jetzt legen wir es im Programm fest, ohne stty zu verwenden.
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;
}
Der Terminaltreiber wird unter Verwendung eines Datentyps namens Termios-Struktur eingestellt und erfasst. Dieser Code ähnelt dem Einstellungsteil des zuvor eingeführten Top-Codes, aber ich denke, es ist einfacher zu verstehen, warum der Hintergrundprozess aufgrund der Ausgabe an das Terminal gestoppt wurde.
Der Punkt ist, dass nach dem Abrufen der aktuellen Terminaltreibereinstellungen das TOSTOP-Flag darauf gesetzt und der Terminaltreiber erneut gesetzt wird.
Übrigens konnte ich aufgrund der Benachrichtigung von SIGTTOU keinen gewaltsamen Hintergrundprozess aus einem Vordergrundprozess erstellen, daher werde ich bash ändern.
Die Operation ist je nach Shell unterschiedlich, aber im Fall von Bash wird die Signalsteuerungsverarbeitung im folgenden Teil durchgeführt.
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);
}
Ändern Sie daher den Prozess beim Erstellen eines untergeordneten Prozesses wie folgt.
jobs.c1762-1767
if (pipeline_pgrp == shell_pgrp)
ignore_tty_job_signals ();
else
default_tty_job_signals ();
set_signal_handler (SIGTTOU, SIG_IGN);
Setzen Sie set_signal_handler (SIGTTOU, SIG_IGN) hier herum und erstellen Sie bash. Da SIGTTIN und SIGTSTP diesmal nicht besonders relevant sind, wird Ignorieren nicht angegeben.
Dadurch kann der zuvor gestoppte Prozess im gestoppten Vordergrund gestartet und dann im Hintergrund ausgeführt werden. Die Eingabe vom Terminal erreicht den gestarteten Prozess nicht.
Da die Eingabe und Ausgabe vom Terminal jedoch nicht den gestarteten Prozess erreichen, kann nicht gesagt werden, dass er ausschließlich im Hintergrund ausgeführt wird, aber der Hauptunterschied zwischen Vordergrund- und Hintergrundausführung besteht darin Ich denke du verstehst.
Um es vollständig zu implementieren, müssen Sie verstehen, wie die Jobsteuerung funktioniert, aber ich möchte es auch separat untersuchen.
Dieses Mal näherten wir uns dem Vordergrundprozess und dem Hintergrundprozess. Basierend auf diesen werde ich mich das nächste Mal dem Daemon-Prozess nähern.
proc-Befehle: procps-3.2.8 GNU bash, version 4.1.2 Die CPU ist x86_64.
OS CentOS release 6.8
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-17)
Recommended Posts