Обмен технологиями

Сервер реактора с высоким параллелизмом [средний]

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Сервер реактора с высоким параллелизмом [средний]

4. Управление процессами и синхронизация процессов

1. Сигнал

1.1 Основные понятия о сигналах

Сигнал (signal) — это программное прерывание. Это метод передачи сообщений между процессами. Он используется для уведомления процесса о том, что произошло событие, но не может передать процессу какие-либо данные.

Существует множество причин, по которым генерируются сигналы. В Shell вы можете использовать.killиkillallКоманда для отправки сигнала:

kill -信号的类型 进程编号
killall -信号的类型 进程名
  • 1
  • 2

1.2 Типы сигналов

название сигналазначение сигналаДействие обработки по умолчаниюПричина подачи сигнала
ПОДПИШИТЕСЬ1АТерминал зависает или процесс управления завершается
SIGINT2АПрерывание клавиатуры Ctrl+c
СИГКВИТ3СНажата клавиша Escape на клавиатуре
СИГИЛЛ4СНезаконное указание
СИГТРАП5Синструкции точки останова
СИГАБРТ6ССигнал прерывания, выданный abort(3)
СИГБУС7Сошибка шины
СИГФПЭ8Сисключение с плавающей запятой
SIGKILL9Аkill -9 убивает процесс, этот сигнал нельзя перехватить и игнорировать
SIGUSR110АОпределяемый пользователем сигнал 1
СИГСЭГВ11СНеверная ссылка на память (массив выходит за пределы, операция с нулевым указателем)
SIGUSR212АОпределяемый пользователем сигнал 2
СИГПАЙП13АЗапись данных в канал без процесса чтения
СИГАЛРМ14АСигнал будильника, сигнал, отправленный функцией Alarm()
СИГТЕРМ15АСигнал завершения, сигнал, отправленный по умолчанию
SIGSTKFLT16Аошибка стека
СИГЧЛД17БГенерируется, когда дочерний процесс завершается
SIGCONT18ДВозобновить остановленный процесс
СИГНАЛ СТОП19ДОстановить процесс
SIGTSTP20ДНажмите кнопку остановки терминала
СИГТТИН21ДЗапросы фонового процесса на чтение терминала
СИГТТОУ22ДФоновый процесс запрашивает запись на терминал
СИГУРГ23БОбнаружение аварийного состояния (розетка)
SIGXCPU24СПревышен лимит процессорного времени
SIGXFSZ25СПревышен лимит размера файла
SIGVTALRM26Авиртуальный тактовый сигнал
СИГПРОФ27ААнализ тактовых сигналов
СИГВИНЧ28БИзменение размера окна
СИГПОЛЛ29БОпрос (Sys V)
SIGPWR30Асбой в электричестве
СИГСИС31СНезаконный системный вызов

Действием по умолчанию для A является завершение процесса.

По умолчанию действие B — игнорировать этот сигнал.

По умолчанию действие C — завершить процесс и создать дамп образа ядра.

Действием D по умолчанию является остановка процесса, и программа, перешедшая в состояние остановки, может продолжать выполнение.

1.3 Обработка сигналов

Процессы могут обрабатывать сигналы тремя способами:

  1. Для обработки этого сигнала используется операция системы по умолчанию. Операция по умолчанию для большинства сигналов — завершение процесса.
  2. Установите функцию обработки прерывания. После получения сигнала функция обработает его.
  3. Игнорируйте сигнал и ничего не делайте с сигналом, как будто его никогда и не было.

signal()Функции могут устанавливать способ обработки сигналов программой.

Объявление функции:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 1
  • 2
  • 3
  • 4

Описание параметра:

  • sig:Укажите сигнал для захвата.
  • func : Указатель на функцию обработки сигнала. Функция обработки должна получить целочисленный параметр, который представляет собой номер захваченного сигнала.
  1. SIG_DFL Макрос :SIG_DFL представляет метод обработки сигнала по умолчанию.использоватьSIG_DFLкакsignalВторой параметр функции указывает, что для сигнала используется системный метод обработки по умолчанию.
  2. SIG_IGN Макрос :SIG_IGN означает игнорирование сигнала.использоватьSIG_IGNкакsignal Второй параметр функции указывает, что когда процесс получит сигнал, он проигнорирует его и не будет выполнять никакой обработки. Это может предотвратить неожиданное завершение или прерывание процессов при определенных обстоятельствах.
  3. SIG_ERR:SIG_ERR Макросы используются для обозначения ошибок.это не такsignalВместо этого используется второй параметр функции какsignal Возвращаемое значение функции указывает на то, что вызов не удался.еслиsignalЕсли вызов функции завершится неудачей, она вернетSIG_ERR .Обычно это используется для обнаружения и обработкиsignalОшибка при вызове функции.

изображение-20240709113614147

изображение-20240709113230874

изображение-20240709113240944

1.4. Для чего используются сигналы?

Сервисная программа работает в фоновом режиме. Если вы хотите остановить ее, убивать ее — не лучшая идея, потому что, когда процесс завершается, он внезапно завершает работу, и никакие последующие действия не выполняются.

Если вы отправите сигнал сервисной программе, после получения сигнала сервисная программа вызывает функцию и записывает в нее код последствия, и программа может завершить работу запланированным образом.

Отправка сигнала 0 сервисной программе позволяет определить, активна ли программа.

изображение-20240709135336848

1.5 Отправка сигналов

Операционная система Linux обеспечивает kill иkillall Команда отправляет сигнал программе. В программе можно использовать.kill() Библиотечные функции отправляют сигналы другим процессам.

Объявление функции:

int kill(pid_t pid, int sig);
  • 1

kill() Функция принимает параметрыsig Указанный сигнал передается в параметрpid указанный процесс.

параметр pid Есть несколько ситуаций:

  1. pid > 0 Передайте сигнал процессу какpid процесс.
  2. pid = 0 Передайте сигнал всем процессам в той же группе процессов, что и текущий процесс. Он часто используется родительским процессом для отправки сигналов дочернему процессу. Обратите внимание, что такое поведение зависит от реализации системы.
  3. pid < -1 Передайте сигнал идентификатору группы процессов|pid| всех процессов.
  4. pid = -1 Передает сигнал всем процессам, имеющим разрешение на отправку сигнала, но не процессу, отправившему сигнал.

2. Завершение процесса

Существует 8 способов завершить процесс, 5 из которых являются обычным завершением:

  1. существовать main() Для функцийreturn возвращаться;
  2. Вызывается в любой функции exit() функция;
  3. Вызывается в любой функции _exit() или_Exit() функция;
  4. Последний поток начинается со своей процедуры запуска (основная функция потока) с return возвращаться;
  5. Звонил в прошлой теме pthread_exit() возвращаться;

Есть три способа аварийного завершения:

  1. передача abort() прерывание функции;
  2. Сигнал принят;
  3. Последний поток отвечает на запрос отмены.

2.1 Статус завершения процесса

существовать main() В функцииreturn Возвращаемое значение является статусом завершения, если нет.return заявление или звонокexit(), то статус завершения процесса равен 0.

В оболочке просмотрите статус завершения процесса:

echo $?
  • 1

3 функции для нормального завершения процесса (exit() и_Exit() определяется ISO C,_exit() определяется POSIX):

void exit(int status);
void _exit(int status);
void _Exit(int status);
  • 1
  • 2
  • 3

status Статус завершения процесса.

изображение-20240709143530327

изображение-20240709143615950

2.2 Проблема с выпуском ресурсов

  • return Указывает, что при возврате функции будет вызван деструктор локального объекта.main() в функцииreturn Также вызывается деструктор глобального объекта.
  • exit() Указывает на завершение процесса, деструктор локального объекта вызываться не будет, будет вызываться только деструктор глобального объекта.
  • _exit() и_Exit() Выйдите напрямую, и никаких работ по очистке выполняться не будет.

2.3 Функция завершения процесса

Процесс доступен atexit() Регистрация функций завершает функции (до 32), эти функции будутexit() Автоматически вызывается.

int atexit(void (*function)(void));
  • 1

exit() Порядок вызова функций завершения меняется с момента регистрации.

изображение-20240709143824286

изображение-20240709143830549

3. Вызов исполняемой программы

3.1 функция system()

system()Функция предоставляет простой метод выполнения программы, передавая программу и параметры, которые необходимо выполнить, в виде строки.system()Просто функционируйте.

Объявление функции:

int system(const char * string);
  • 1

system()Возвращаемое значение функции вызывает больше проблем.

  1. Если исполняемая программа не существует,system()Функция возвращает ненулевое значение;
  2. Если выполнение программы прошло успешно и статус выполнения исполняемой программы равен 0,system()Функция возвращает 0;
  3. Если выполнение программы прошло успешно и статус завершения исполняемой программы не равен 0,system()Функция возвращает ненулевое значение.

3.2 семейство функций exec

execСемейства функций предоставляют еще один способ вызова программ (двоичных файлов или сценариев оболочки) внутри процесса.

execСемейство функций объявлено следующим образом:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Уведомление

  1. Если выполнение программы завершается неудачно, напрямую возвращается -1, а причина сбоя сохраняется вerrnoсередина.
  2. Номер процесса нового процесса такой же, как и у исходного процесса, но новый процесс заменяет сегмент кода, сегмент данных и стек исходного процесса.
  3. Если выполнение прошло успешно, функция не вернется. При успешном вызове в основной программе.execПосле этого вызываемая программа заменит вызывающую, то естьexecНикакой код после функции не будет выполнен.
  4. В реальной разработке наиболее часто используетсяexecl()иexecv(), другие используются редко.

4. Создайте процесс

4.1 Linux-процессы 0, 1 и 2

Все процессы во всей системе Linux имеют древовидную структуру.

  • **Процесс № 0 (системный процесс)** является предком всех процессов. Он создал процессы № 1 и № 2.
  • **Процесс № 1 (systemd)** отвечает за инициализацию ядра и настройку системы.
  • **Процесс № 2 (kthreadd)** отвечает за планирование и управление всеми потоками ядра.

использоватьpstreeПосмотреть дерево процессов можно командой:

pstree -p 进程编号
  • 1

4.2 Идентификация процесса

Каждый процесс имеет уникальный идентификатор процесса, представленный неотрицательным целым числом. Несмотря на уникальность идентификаторов процессов, их можно использовать повторно. Когда процесс завершается, его идентификатор процесса становится кандидатом на повторное использование. Linux использует алгоритм отложенного повторного использования, поэтому идентификатор вновь созданного процесса отличается от идентификатора, используемого недавно завершенным процессом. Это предотвращает принятие новых процессов за завершенный процесс, использующий тот же идентификатор.

Функция для получения идентификатора процесса:

pid_t getpid(void);    // 获取当前进程的ID。
pid_t getppid(void);   // 获取父进程的ID。
  • 1
  • 2

4.3 функция fork()

Существующий процесс может вызватьfork()Функция создает новый процесс.

Объявление функции:

pid_t fork(void);
  • 1

Зависит отfork()Созданный новый процесс называется дочерним процессом.

fork() Функция вызывается один раз, но возвращает результат дважды. Разница между двумя возвращаемыми значениями заключается в том, что возвращаемое значение дочернего процесса равно 0, а возвращаемое значение родительского процесса — это идентификатор вновь созданного дочернего процесса.

Дочерний процесс и родительский процесс продолжают выполнятьсяfork()Код после этого, Дочерний процесс является копией родительского процесса. Дочерний процесс имеет копию пространства данных, кучи и стека родительского процесса (примечание: дочерний процесс владеет копией, не используемой совместно с родительским процессом).

fork()После этого порядок выполнения родительского и дочернего процессов не определен.

изображение-20240709221535371

изображение-20240709221546617

4.4 Два варианта использования fork()

  1. Родительский процесс хочет скопировать себя, а затем родительский процесс и дочерний процесс выполняют разные коды.Такое использование очень распространено в программах сетевых служб. Родительский процесс ожидает запроса на соединение от клиента. При поступлении запроса родительский процесс вызывает.fork(), позвольте дочернему процессу обрабатывать эти запросы, в то время как родительский процесс продолжает ждать следующего запроса на соединение.
  2. Процесс хочет выполнить другую программу.Такое использование очень распространено в оболочках: дочерний процесс начинается сfork()Позвонили сразу после возвращенияexec

4.5 Общие файлы

fork()Одна из особенностей заключается в том, что файловые дескрипторы, открытые в родительском процессе, будут скопированы в дочерний процесс, а родительский и дочерний процессы имеют одно и то же файловое смещение.

Если родительский процесс и дочерний процесс записывают данные в файл, на который указывает один и тот же дескриптор, без какой-либо синхронизации, их выходные данные могут быть смешаны друг с другом.

изображение-20240709222929369

изображение-20240709222803641

На этом этапе вы можете видеть, что имеется только 100 000 строк данных.

изображение-20240709222853769

изображение-20240709223236254

В это время должно быть 200 000 строк данных. Возможно, на одну строку меньше, потому что операция записи файла не является атомарной. В отсутствие механизма синхронизации два процесса могут попытаться записать разные части файла одновременно, что приведет к возникновению ошибки. запись не удалась. Данные мешают друг другу.

4.6 Функция vfork()

vfork()Вызовы функций и возвращаемые значения такие же, какfork()То же самое, но их семантика разная.

vfork()Функция используется для создания нового процесса, целью которого являетсяexecНовая программа, которая не копирует адресное пространство родительского процесса, поскольку дочерний процесс немедленно вызываетexec , поэтому адресное пространство родительского процесса не будет использоваться. Если дочерний процесс использует адресное пространство родительского процесса, могут возникнуть неизвестные результаты.

vfork()иfork()Еще одно отличие:vfork()Убедитесь, что дочерний процесс запускается первым, и вызовите его в дочернем процессе.execилиexitЗатем родительский процесс возобновляет работу.

5. Зомби-процесс

В операционной системе процесс-зомби относится к дочернему процессу, который завершился, но его родительский процесс еще не прочитал статус своего завершения. Хотя процесс-зомби больше не выполняется, он по-прежнему занимает запись в таблице процессов, чтобы ядро ​​могло сохранять информацию о статусе завершения процесса (например, идентификатор процесса, статус завершения и т. д.) до тех пор, пока родительский процесс не прочитает эту информацию.

5.1 Причины зомби-процессов

Если родительский процесс завершается раньше дочернего процесса, дочерний процесс будет размещен в процессе 1 (это также способ запустить процесс в фоновом режиме).

Если дочерний процесс завершается раньше родительского процесса и родительский процесс не обрабатывает информацию о выходе дочернего процесса, то дочерний процесс станет процессом-зомби.

5.2 Вред зомби-процессов

Ядро сохраняет структуру данных для каждого дочернего процесса, включая номер процесса, статус завершения, используемое время ЦП и т. д. Если родительский процесс обрабатывает информацию о выходе дочернего процесса, ядро ​​​​освободит эту структуру данных. Если родительский процесс не обрабатывает информацию о выходе дочернего процесса, ядро ​​не освободит эту структуру данных, и номер дочернего процесса всегда будет занят. Номера процессов, доступные в системе, ограничены. Если генерируется большое количество процессов-зомби, система не сможет генерировать новые процессы, поскольку доступных номеров процессов нет.

5.3 Методы предотвращения зомби-процессов

  1. Обработка сигнала SIGCHLD : Когда дочерний процесс завершается, ядро ​​отправляет сигнал SIGCHLD родительскому процессу.Если родительский процесс используетsignal(SIGCHLD, SIG_IGN)Сообщите ядру, что оно не заинтересовано в выходе дочернего процесса, и его структура данных будет освобождена сразу после выхода дочернего процесса.
  2. использоватьwait()/waitpid()функция: родительский процесс ожидает завершения дочернего процесса, вызывая эти функции, и получает статус завершения, тем самым освобождая ресурсы, занятые дочерним процессом.
pid_t wait(int *stat_loc); 
pid_t waitpid(pid_t pid, int *stat_loc, int options); 
pid_t wait3(int *status, int options, struct rusage *rusage); 
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
  • 1
  • 2
  • 3
  • 4

Возвращаемое значение — это номер дочернего процесса.

stat_loc Есть ли информация о завершении дочернего процесса:

а) При нормальном завершении макроса WIFEXITED(stat_loc) Вернуть истину, макросWEXITSTATUS(stat_loc) Статус прекращения можно получить;

б) Если он завершается ненормально, макрос WTERMSIG(stat_loc) Получает сигнал о завершении процесса.

изображение-20240709230911352

изображение-20240709231034423

изображение-20240709231050581

изображение-20240709231124375

изображение-20240709231140813

Если родительский процесс занят, вы можете захватить SIGCHLD Сигнал, вызываемый в функции обработки сигналаwait()/waitpid()

изображение-20240709231439475

изображение-20240709231422927

6. Несколько процессов и сигналов

[Отправка сигналов между процессами](##1.5 Отправка сигналов)

В многопроцессной сервисной программе, если подпроцесс получает сигнал выхода, он завершится самостоятельно.

Если родительский процесс получает сигнал выхода, он должен отправить сигналы выхода всем дочерним процессам, а затем завершиться сам.

изображение-20240711222919564

изображение-20240711222900141

изображение-20240711223111481

7. Общая память

Многопоточные процессы совместно используют адресное пространство процесса.Если нескольким потокам требуется доступ к одной и той же памяти, просто используйте глобальные переменные.

В нескольких процессах адресное пространство каждого процесса независимо и не используется совместно.Если нескольким процессам требуется доступ к одной и той же памяти, глобальные переменные использовать нельзя, можно использовать только общую память.

Общая память позволяет нескольким процессам (кровное родство между процессами не требуется) получать доступ к одному и тому же пространству памяти. Это наиболее эффективный способ совместного использования и передачи данных между несколькими процессами. Процессы могут подключать общую память к своему собственному адресному пространству. Если один процесс изменяет данные в общей памяти, данные, считываемые другими процессами, также изменятся.

Общая память не обеспечивает механизм блокировки, то есть, когда процесс читает/записывает общую память, он не мешает другим процессам читать/записывать ее.Если вы хотите заблокировать чтение/запись общей памяти, вы можете использовать семафор . Linux предоставляет набор функций для работы с общей памятью.

7.1 функция shmget

Эта функция используется для создания/получения общей памяти.

 int shmget(key_t key, size_t size, int shmflg);
  • 1
  • ключ Ключевое значение разделяемой памяти — целое число (typedef unsigned int key_t), обычно в шестнадцатеричном формате, например 0x5005, ключи разных общих воспоминаний не могут быть одинаковыми.
  • размер Размер разделяемой памяти в байтах.
  • шмфлг Права доступа к общей памяти такие же, как и права доступа к файлам, например0666|IPC_CREAT Указывает, что если общая память не существует, создайте ее.
  • возвращаемое значение: возвращает идентификатор общей памяти (целое число больше 0) в случае успеха, -1 в случае сбоя (в системе недостаточно памяти и нет разрешений).

изображение-20240711224223200

изображение-20240711224212293

использовать ipcs -m Вы можете просмотреть общую память системы, включая: ключ (key), идентификатор общей памяти (shmid), владельца (владелец), разрешения (perms) и размер (в байтах).

использовать ipcrm -m 共享内存id Общая память может быть удалена вручную следующим образом:

изображение-20240711225202860

Примечание. Контейнеры нельзя использовать для типов данных в общей памяти, можно использовать только базовые типы данных.

7.2 функция шмата

Эта функция используется для подключения общей памяти к адресному пространству текущего процесса.

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 1
  • шмид Зависит отshmget() Идентификатор общей памяти, возвращаемый функцией.
  • шмаддр Укажите адрес, по которому общая память подключена к текущему процессу. Обычно заполняется 0, чтобы система могла выбрать адрес общей памяти.
  • шмфлг Бит флага, обычно заполняется 0.

Возвращает начальный адрес общей памяти в случае успешного вызова и возвращает его в случае сбоя. (void *)-1

7.3 функция шмдт

Эта функция используется для отделения общей памяти от текущего процесса, что эквивалентно shmat() Обратная операция функции.

int shmdt(const void *shmaddr);
  • 1
  • шмаддр shmat() Адрес, возвращаемый функцией.

Возвращает 0, если вызов успешен, и -1, если он не удался.

7.4 функция шмктл

Эта функция используется для управления общей памятью. Наиболее часто используемой операцией является удаление общей памяти.

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • шмид shmget() Идентификатор общей памяти, возвращаемый функцией.
  • команда Инструкция по работе с общей памятью Если хотите удалить общую память, заполните.IPC_RMID
  • буф Адрес структуры данных, которая управляет общей памятью. Если вы хотите удалить общую память, введите 0.

Возвращает 0, если вызов успешен, и -1, если он не удался.

Обратите внимание, используйте root Созданная общая память не может быть удалена обычными пользователями независимо от созданных разрешений.

изображение-20240711230653886

изображение-20240711230522921

7.5 Круговая очередь

7.6 Круговая очередь на основе общей памяти

Возвращает 0, если вызов успешен, и -1, если он не удался.

7.4 функция шмктл

Эта функция используется для управления общей памятью. Наиболее часто используемой операцией является удаление общей памяти.

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • шмид shmget() Идентификатор общей памяти, возвращаемый функцией.
  • команда Инструкция по работе с общей памятью Если хотите удалить общую память, заполните.IPC_RMID
  • буф Адрес структуры данных, которая управляет общей памятью. Если вы хотите удалить общую память, введите 0.

Возвращает 0, если вызов успешен, и -1, если он не удался.

Обратите внимание, используйте root Созданная общая память не может быть удалена обычными пользователями независимо от созданных разрешений.

[Изображения по внешней ссылке передаются...(img-v6qW3XRA-1720711279572)]

[Изображения по внешней ссылке передаются...(img-CG0tGAne-1720711279572)]Не удалось передать изображение по внешней ссылке. Исходный сайт может иметь механизм защиты от кражи. Рекомендуется сохранить изображение и загрузить его напрямую.
Не удалось передать изображение по внешней ссылке. Исходный сайт может иметь механизм защиты от кражи. Рекомендуется сохранить изображение и загрузить его напрямую.
Не удалось передать изображение по внешней ссылке. Исходный сайт может иметь механизм защиты от кражи. Рекомендуется сохранить изображение и загрузить его напрямую.

7.5 Круговая очередь

7.6 Круговая очередь на основе общей памяти