Васильев Андрей Михайлович, 2024
Версии презентации
В момент запуска процесса ему выделяются стандартные потоки для ввода и вывода информации
0
, stdin
— стандартный поток ввода1
, stdout
— стандартный поток вывода2
, stderr
— стандартный поток вывода сообщений об ошибкахПроцесс, инициирующий запуск другого процесса, определяет на какие файловые дескрипторы будут переданы работающему процессу
Процесс во время своей работы может самостоятельно изменить направление потоков, однако эта возможность используется в основном управляющими процессами (например интерпретатором Bash)
System.out.println("Текст выведен на стандартный поток вывода")
System.err.println("Текст выведен на поток ошибок")
import sys
print("Текст на стандартный поток вывода")
print("Текст на поток ошибок", file=sys.stderr)
#include <stdio.h>
int main() {
printf("Поток вывода\n");
fprintf(stderr, "Поток ошибок\n");
}
Эмулятор терминала общается с пользователем только по двум каналам:
Если специальных действий не было предпринято, тогда
stdout
и stderr
выводится на экран в историиС точки зрения пользователя потоки stdout
и stderr
выводятся как результат работы приложения, ничем не отличимые друг от друга
Если данные с потоков приходят одновременно, то возможно их пересечение, обычно происходит путём чередования строк
В Linux для создания нового процесса используется системный вызов:
#include <unistd.h>
pid_t fork(void); // man 2 fork
Процесс-ребёнок является точной копией процесса родителя
Процесс-ребёнок разделяет с процессом-родителем все ресурсы, включая:
На основе кода возврата новый процесс должен решить: что ему делать дальше
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(void)
{
pid_t pid;
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
pid = fork();
switch (pid) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
case 0:
puts("Child exiting.");
exit(EXIT_SUCCESS);
default:
printf("Child is PID %jd\n", (intmax_t) pid);
puts("Parent exiting.");
exit(EXIT_SUCCESS);
}
}
$ gcc info.c -o fork-test
$ ./fork-test
Child is PID 43594
Parent exiting.
Child exiting.
exec
#
Для замены исполняемого кода используются системные вызовы exec*
, man 2 execve
exec
#
/* myecho.c */
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
for (size_t j = 0; j < argc; j++)
printf("argv[%zu]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS);
}
/* execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
static char *newargv[] = { NULL, "hello", "world", NULL };
static char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE);
}
$ cc myecho.c -o myecho
$ cc execve.c -o execve
$ ./execve ./myecho
argv[0]: ./myecho
argv[1]: hello
argv[2]: world
freopen
#
Системный вызов позволяет открыть новый файл, но вместо создания нового файлового дескриптора, будет использован один из существующих
Обычно используется для замены файловых дескрипторов стандартных потоков
stdin
, stderr
, stdout
stdout.log
freopen("output.log", "w", stdout);
Документация доступна в man 3 fopen
freopen
#
/* execve-freopen.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
static char *newargv[] = { NULL, "hello", "world", NULL };
static char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
freopen("output.log", "w", stdout);
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE);
}
$ cc execve-freopen.c -o execve-freopen.c
$ ./execve-freopen ./myecho