Pular para conteúdo

Fundamentos UNIX

Unix

  • Sistema projetado na Bell Labs (abandonado)
  • Reescrito em uma linguaguem de alto nível
  • Ritchiie cria a linguagem C e reescreve o UNIX
  • Sistema em linguagem de alto nível foi amplamente aceita pela comunidade e facilitou a portabilidade do sistema para múltiplas arquiteturas

O sistema Linux foi desenvolvido em 1991 por Linus Torvalds, código do linux compartilhado no grupo de discussão do sistema do Tanenbaum Minix. É um projeto ativo e amplamente utlizado até hoje focado exclusivamente no desenvolvimento do Kernel.

  • O linux precisa de software para ser útil a um computador, utiliza o código do projeto GNU.
  • Ao se utilizar e customizar as versãoes de diversos códigos, é criada uma distribuição Linux. GNU/Linux != Linux

Hoje, UNIX é um termo utilizado para uma grande família de Sistemas Operacionais

  • Família BSD: FreeBSD, OpenBS
  • Família Solaris: SunOS
  • Família: Mac OS X

Esses sistemas possuem estruturas de diretórios e comandos de terminal similares.

  • Comando para listar processos e verificar seu estado: ps
ps -ax -o pid,ppid,command
  • Cada processo possui um identificador único chamado de "process id": pid
  • Todo processo possui um processo pai, e então contém o "parent process id", ppid
  • Em C (ou C++), pode usar o cabeçalho para ter acesso à API do sistema operacional POSIX e usar uma série de funções para manipulação de processos
  • Obter o de pid e ppid
#include <stdio.h>
#include <unistd/h>

int main(){
    pid_t meupid = getpid(); //pid_t -> T de Type
    printf("Pid atual do processos: %d\n", meupid);

    pid_t pidpao = getppid();
    printf("Pid do pai do processo: %d\n", pidpai);

    return 0;
}

Criação de Processos

Os processo podem ser cirados tipicamente em um terminal, onde são digitados os comandos, dentro de um código-fonte, é posível executar o comando system, essa chamda irá criar um subprocesso de shell (/bin/sh) que irá efetuar a execução do comando

  • Um programa em C que lista todos os arquivos da pasta atual
#include <stdlib.h>

int main(){
    return system("ls -la");
}

  • Esse código não é portátil e funcionará exclusivamente em sistemas UNIX
  • No Windows, é comum a prática de utilizar o system("pause") para parar a execução do código C ao final dele, quando se utilizar uma IDE que invoca o terminal Windows (que fecha rapidamente).

Outras linguagens (scripts Bash) são mais apropriados quando se deseja exceutar comandos (ou diversos comandos) em u m terminal, sem que seja necessário cirar um binário executável para isso.

  • Para se criar novos processos em C, recomenda-se criar processo utilizando o fork()

  • A se chamar, um novo processo é criado e quase todo o conteúdo da tabela de processos é copiado para o novo processo.

  • Informações como pid, ppid, são únicos mas o processo filho continua a execução do mesmo potno que o processo pai. Além disso, o retorno da função é diferente para os processos e pode ser usado para diferneciá-los.
int main(){ 
    pid_t id_filho;
    printf("Processo incial possui pid: %d\n", getpid());
    if_filho = fork();
    if(if_filho == 0){
        printf("Ola, sou o processo filho de pid: %d\n", getpid());
    }else{
        printf("Ola, sou o processo pai e criei o filho: %d\n", id_filho);
    }
    return 0;
}

Carga de Arquivo Executável

  • Desta forma, os processos criados por clonagem e executarão o mesmo programa, algun sistema permitei criar novo processo através de uma nova imagem de programa
  • No UNIX, deve-se realizar uma nova chamada exec
  • Existe um conjunto de funções exec, todas com o objetivo de alterar o programa em execução do processo atual
  • A função retorna apenas em caso de falha: em caso de sucesso a imagem em execução é alterada e o programa é executado a partir do inicio

As famílias de função exec: - P: (execvp, execlp ..) procuram excetucar o novo programa de diferentes diretórios do PATH, sem a letra é necessário utilizar full path do binário - V: (execv ...) são utilizadas para criar programas com argumentos - E: Também recebem uma lsita de variáveis de ambiente

Normalmente, a função exec é usada e, conjunto com o forl, para não se a;terar um programa que já existe em execução a muito tempo

int main(){
    char *arg[] = {"ls", "-la", NULL};
    pid_t id_filho = fork();
    if(id_filho != 0)
        printf("ola, sou o processo pai e criei o filho %d\n", id_filho);
    else{
        execvp("ls", arg);
        printf("Erro\n");
        return 1;
    }
    return 0;
}

Sinais

Sinais são mecanismo para comunicar e manipular processo no UNIX, a liguagem C padrão provê a biblioteca signal.h. Quando um sinal é recebido pelo processo, a função atual é interrompida e o sinal é tradado por uma função especial chamda de "handler", no entando o sinal pode ser ingorado

  • Um dos usos mais comuns de sinais é para terminar o processo em execução. Para isso são usados os sinais SIGTERM e SIGKILL. Também são definifos sinais de usuário, para que o programados possa definir um uso para eles: SIGUSR1 e SIGUSR2
  • Os sinais também são usados para o sistema operacional informar erros, SIGSEGC (falha de segmentação), SIGFPE (exeção de ponto flutuante)
  • A função signal é usada para alterar o comportamento, também podemos usar o sigaction, veja o manual signal 2, especialemnte a parte "Portability:"
#include <signal.h>
#include <stdio.h>

int contador = 0;

void handler(int signal_number){
    contador++;
}

int main(){
    struct sigtation sa = {}; // Inicializa sem nada para evitar lixo de memória
    sa.sa_hanlder = &handler;
    sigaction(SIGUSR1, &sa, NULL);
    printf("Pressione E para terminar a execucao: \n");
    while (getchar() != 'E')
        ;
    printf("SIGURS1 recebido %d vezes \n", cotador);

    return 0;
}
  • Se testar esse código, verá qie le irá imprimir que nenhum sinal foi recebido, mas como enviar um sinal para o processo, pode ser usado o comando ou função kill.
  • Quando se executa o código anterior:
    • ./programa_de_sinais
  • Usamos o programa ps para determinar o pis do process (por exemplo, 2240)
  • E enviamos o sinal com o comando kill
    • [user@station ~]$ kill -SIGURS1 2240

Desta forma, o código anterior irá imprimir quantas vezes , o sinal foi recebido. A função kill também pode ser utilizada para enviar um sinal para o processo em um código fonte C: - int kill(pid_t pid, int sig); - Por exemplo - kill(pid_filho, SIGUSR1)

Término de Processos

Por padrão, o comando kill envia o signal SIGTERM, sujo padrão é terminar a execução de um processo, Dois sinais podem ser usados para terminar:

  • SIGTERM
  • SIGKILL

O sinal SIGTERM pode ser ignorado, mas o SIGKILL não pode. Além disso, um processo pode terminar por vontade própria de duas formas:

  • Retornando da função main
  • Executando a função exit

Normalmente:

  • exit(0) é utilizado para indicar sucesso
  • exit(1) é usado para indicar um erro

A função wait pode receber um argumento do tipo *int, para indicar o status da saída. As macros WIFEXITED e WEXITSTATIS podem ser usadas para ler o inteiro e testar se o processo terminou e o valor de retorno.

Aguardando o Término

Quando o processo pai evoca o processo filho, ele pode aguardar o fim do processo utilzando a função wait(), se o filho não terminou, o processo pai irá bloquar aguardando o término do processo para aguardar a chegada de um sinal, também podemos utilizar a função pause()

#include <stdlib.h>
#include <unistd.h>
#include  <sys/wait.g>

int main(){
    pis_t id_filho;
    int status;

    printf("Processo incial possui pid: %d\n", getpid());
    id_filho = fork();
    if (id_filho == 0){
        printf("Ola, sou o processo filho de pid: %d!\n", getpid());
        exit(0);
    }

    wait(NULL);

    printf("Ola, sou o processo pai e o filho terminou!\n");
    return 0;
}

Processos Zumbis

Quando um processo cria outro processo, assume-se que ele irá receber as informações de término do processo pela função wait, se ele não estiver em wait, o processo filho fica em um estado zumbi, indicando que deseja terminar, mas ainda não aguardado pelo processo pai. Só quando a função wait for chamada pelo pai, o resultado da execução é passado para o pai, o processo filho é deletado e a função wait retorna imediatamente.

#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main(){
    pid_t id_filho;

    filho = fork();

    if(filho > 0)
        sleep(6);
    else
        exit(0);
    wait(NULL);

    return 0;
}