Você está na página 1de 131

Programao Linux Avanada ca c

(...) Pero, con todo eso, me parece que el traducir de una lengua en otra, como no sea de las reinas de las lenguas, griega y latina, es como quien mira los tapices amencos por el revs, que aunque se veen las guras, son llenas e de hilos que las escurecen y no se veen con la lisura y tez de la haz, y el traducir de lenguas fciles ni arguye ingenio ni elocucin, como no le arguye a o el que traslada ni el que copia un papel de otro papel. (...) [II, 62] El ingenioso hidalgo Don Quijote de la Mancha Miguel de Cervantes

Traduzido por Jorge Barros de Abreu http://sites.google.com/site/cmatinf Verso - 0.05 - 05/2011 a

Sumrio a
I Programao UNIX Avanada com Linux ca c
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9
13 13 13 14 15 15 17 18 19 21 22 22 23 25 25 26 27 29 29 30 31 32 35 37 38 40 43 43 45

1 Iniciando 1.1 Editando com Emacs . . . . . . . . . . . . . . . . . . . . 1.1.1 Abrindo um Arquivo Fonte em C ou em C++ . . 1.1.2 Formatando Automaticamente . . . . . . . . . . . 1.1.3 Destaque Sinttico para Palavras Importantes . . a 1.2 Compilando com GCC . . . . . . . . . . . . . . . . . . . 1.2.1 Compilando um Arquivo Simples de Cdigo Fonte o 1.2.2 Linkando Arquivos Objeto . . . . . . . . . . . . . 1.3 Automatizando o Processo com GNU Make . . . . . . . 1.4 Depurando com o Depurador GNU (GDB) . . . . . . . . 1.4.1 Depurando com GNU Debugger (GDB) . . . . . . 1.4.2 Compilando com Informaes de Depurao . . . co ca 1.4.3 Executando o GDB . . . . . . . . . . . . . . . . . 1.5 Encontrando mais Informao . . . . . . . . . . . . . . . ca 1.5.1 Pginas de Manual . . . . . . . . . . . . . . . . . a 1.5.2 Info . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.3 Arquivos de Cabealho . . . . . . . . . . . . . . . c 2 Escrevendo Bom Software GNU/Linux 2.1 Interao Com o Ambiente de Execuo ca ca 2.1.1 A Lista de Argumentos . . . . . . 2.1.2 Convenes GNU/Linux de Linha co 2.1.3 Usando getopt long . . . . . . . . 2.1.4 E/S Padro . . . . . . . . . . . . a 2.1.5 Cdigos de Sa de Programa . . o da 2.1.6 O Ambiente . . . . . . . . . . . . 2.1.7 Usando Arquivos Temporrios . . a 2.2 Fazendo Cdigo Defensivamente . . . . . o 2.2.1 Usando assert . . . . . . . . . . . 2.2.2 Falhas em Chamadas de Sistema 3

. . . . . . . . . . . . . . . . de Comando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

2.3

2.2.3 Cdigos de Erro de Chamadas de Sistema . o 2.2.4 Erros e Alocao de Recursos . . . . . . . . ca Escrevendo e Usando Bibliotecas . . . . . . . . . . 2.3.1 Agrupando Arquivos Objeto . . . . . . . . . 2.3.2 Bibliotecas Compartilhadas . . . . . . . . . 2.3.3 Bibliotecas Padronizadas . . . . . . . . . . . 2.3.4 Dependncia de uma Biblioteca . . . . . . . e 2.3.5 Prs e Contras . . . . . . . . . . . . . . . . o 2.3.6 Carregamento e Descarregamento Dinmico a

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

46 49 50 51 52 55 55 57 58 61 61 62 62 64 64 64 65 69 71 74 76 76 77 79 83 84 86 87 89 90 91 92 93 94 96 96 99 100 101

3 Processos 3.1 Visualizando Processos . . . . . . . . . . . . . . . . . 3.1.1 Identicadores de Processos . . . . . . . . . . 3.1.2 Visualizando os Processos Ativos . . . . . . . 3.1.3 Encerrando um Processo . . . . . . . . . . . . 3.2 Criando Processos . . . . . . . . . . . . . . . . . . . . 3.2.1 Usando system . . . . . . . . . . . . . . . . . 3.2.2 Usando bifurcar e executar . . . . . . . . . . . 3.2.3 Agendamento de Processo . . . . . . . . . . . 3.3 Sinais . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Encerramento de Processo . . . . . . . . . . . 3.3.2 Esperando pelo Encerramento de um Processo 3.3.3 As Chamadas de Sistema da Fam wait . . lia 3.3.4 Processos do Tipo Zumbi . . . . . . . . . . . . 3.3.5 Limpando Filhos de Forma No Sincronizada a 4 Tarefas 4.1 Criao de Tarefas . . . . . . . . . . . ca 4.1.1 Enviando Dados a uma Tarefa . 4.1.2 Vinculando Tarefas . . . . . . . 4.1.3 Valores de Retorno de Tarefas . 4.1.4 Mais sobre IDs de Tarefas . . . 4.1.5 Atributos de Tarefa . . . . . . . 4.2 Cancelar Tarefas . . . . . . . . . . . . 4.2.1 Tarefas Sincronas e Assincronas 4.2.2 Sees Cr co ticas Incancelveis . . a 4.2.3 Quando Cancelar uma Tarefa . 4.3 Area de Dados Espec cos de Tarefa . 4.3.1 Manipuladores de Limpeza . . . 4.3.2 Limpeza de Tarefa em C++ . . 4.4 Sincronizao e Sees Cr ca co ticas . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

4.4.1 Condies de CCorrida . . . . . . . . . . . co 4.4.2 Mutexes . . . . . . . . . . . . . . . . . . . 4.4.3 Travas Mortas de Mutex . . . . . . . . . . 4.4.4 Testes de Mutex sem Bloqueio . . . . . . . 4.4.5 Semaforos para Tarefas . . . . . . . . . . . 4.4.6 Variveis Condicionais . . . . . . . . . . . a 4.4.7 Travas Mortas com Duas ou Mais Tarefas 4.5 Implementao de Tarefa em GNU/Linux . . . . ca 4.5.1 Manipulando Sinal . . . . . . . . . . . . . 4.5.2 Chamada de Sistema clone . . . . . . . . . 4.6 Processes Vs. Threads . . . . . . . . . . . . . . . 5 Open Publication License 6 Licena de Livre Publicao c ca

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

102 104 107 108 109 112 117 118 119 120 120 123 127

Listagem Cdigos Fonte o


1.1 1.2 1.3 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.1 3.2 3.3 3.4 3.5 3.6 3.7 4.1 4.2 4.3 4.4 4.5 4.6 4.7 Arquivo main.c cdigo fonte em C . . . . . . . . . . . . . . . o Arquivo reciprocal.cpp cdigo fonte em C++ . . . . . . . . . o Arquivo de cabealho reciprocal.hpp . . . . . . . . . . . . . c (Arquivo arglist.c) Usando argc e argv. . . . . . . . . . . . . . (getopt long.c) Usando a funo getopt long . . . . . . . . . . ca (print-env.c) Mostrando o Ambiente de Execuo . . . . . . . ca (client.c) Parte de um Programa Cliente de Rede . . . . . . . (temp le.c) Usando mkstemp . . . . . . . . . . . . . . . . . . (readle.c) Liberando Recursos em Condies Inesperadas . . co (test.c) Area da Biblioteca . . . . . . . . . . . . . . . . . . . . (app.c) Um Programa Que Utiliza as Funes da Biblioteca co Acima . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (titest.c) Usando a libti . . . . . . . . . . . . . . . . . . . . ( print-pid.c) Mostrando o ID do Processo . . . . . . . . . . . (system.c) Usando uma chamada ` funo system . . . . . . . a ca ( fork.c) Usando fork para Duplicar o Processo de um Programa ( fork-exec.c) Usando fork e exec Juntas . . . . . . . . . . . . (sigusr1.c) Usando um Manipulador de Sinal . . . . . . . . . . (zombie.c) Fazendo um Processo Zumbi . . . . . . . . . . . . . (sigchld.c) Limpando Processos lhos pelo manuseio de SIGCHLD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ( thread-create.c) Criando uma Tarefa . . . . . . . . . . . . . ( thread-create2) Cria Duas Tarefas . . . . . . . . . . . . . . . Funo main revisada para thread-create2.c . . . . . . . . . . ca ( primes.c) Calcula Nmeros Primos em uma Tarefa . . . . . . u (detached.c) Programa Esqueleto Que Cria uma Tarefa Desvinculada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (critical-section.c) Protege uma Transao Bancria com uma ca a Seo Cr ca tica . . . . . . . . . . . . . . . . . . . . . . . . . . . (tsd.c) Log Por Tarefa Implementado com Dados Espec cos de Tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 16 16 16 31 34 39 40 42 50 52 52 56 62 65 66 69 74 78 80 85 87 88 90 92 95 98

4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15

(cleanup.c) Fragmento de Programa Demonstrando uma Tarefa100 (cxx-exit.cpp) Implementando Sa Segura de uma Tarefa da com Excees de C++ . . . . . . . . . . . . . . . . . . . . . . 101 co ( job-queue1.c) Thread Function to Process Jobs from the Queue103 ( job-queue2.c) Funo de Tarefa da Fila de Trabalho, Proteca gida por um Mutex . . . . . . . . . . . . . . . . . . . . . . . . 106 ( job-queue3.c) Fila de Trabalhos Controlada por um Semforo 111 a (spin-condvar.c) Uma Implementao Simples de Varivel Conca a dicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 (condvar.c) Control a Thread Using a Condition Variable . . . 116 (thread-pid) Imprime IDs de processos para Tarefas . . . . . . 118

Parte I Programao UNIX Avanada ca c com Linux

ESSE CAP ITULO MOSTRA COMO EXECUTAR OS PASSOS bsicos a requeridos para criar um programa Linux usando a linguagem C ou a linguagem C++. Em particular, esse cap tulo mostra como criar e modicar cdigo fonte C e C++, compilar aquele cdigo, e depurar o resultado. Se voc o o e tem experincia em programao em ambiente Linux, voc pode pular agora e ca e para o Cap tulo 2, Escrevendo Bom Software GNU/Linuxpreste cuidadosa ateno ` seo 2.3, Escrevendo e Usando Bibliotecaspara informaes ca a ca co sobre linkagem/vinculao esttica versus linkagem/vinculao dinmica `s ca a ca a a quais voc pode no conhecer ainda. e a No decorrer desse livro, assumiremos que voc est familiarizado com as e a linguagens de programao C ou C++ e as funes mais comuns da biblioca co teca padro C. Os exemplos de cdigo fonte nesse livro esto em C, exceto a o a quando demonstrando um recurso particular ou complicao de programao ca ca em C++. Tambm assumiremos que voc conhece como executar operaes e e co bsicas na linha de comando do Linux, tais como criar diretrios e copiar a o arquivos. Pelo fato de muitos programadores de ambiente Linux terem iniciado programao no ambiente Windows, iremos ocasionalmente mostrar ca semelhanas e diferenas entre Windows e Linux. c c

11

12

Cap tulo 1 Iniciando


1.1 Editando com Emacs

Um editor o programa que voc usa para editar o cdigo fonte. Muitos e e o editores esto dispon a veis para Linux, mas o editor mais popular e cheio de recursos provavelmente GNU Emacs. e
Sobre o Emacs: Emacs muito mais que um editor. Emacs um programa inacreditavelmente e e poderoso, tanto que em CodeSourcery, Emacs afetuosamente conhecido como e Um Verdadeiro Programa, ou apenas o UVP de forma curta. Voc pode ler e e enviar mensagens eletrnicas de dentro do Emacs, e voc pode personalizar e o e extender o Emacs de formas muito numerosas para discorrer aqui. Voc pode e at mesmo navegar na web de dentro do Emacs! e

Caso voc esteja familiarizado com outro editor, voc pode certamente e e us-lo no lugar do Emacs. Note que o restante desse livro est vinculado ao a a uso do Emacs. Se ainda no tem ainda um editor Linux favorito, ento voc a a e deve seguir adiante com o mini-tutorial fornecido aqui. Se voc gosta do Emacs e deseja aprender sobre seus recursos avanados, e c voc pode considerar ler um dos muitos livros sobre Emacs dispon e veis. Um excelente tutorial Learning GNU Emacs, escrito por Debra Cameron, e Bill Rosenblatt, e Eric S. Raymond (Editora OReilly, 1996).

1.1.1

Abrindo um Arquivo Fonte em C ou em C++

Voc pode iniciar o Emacs digitando emacs em sua janela de terminal e prese sionado a tecla Enter. Quando Emacs tiver iniciado, voc pode usar os menus e localizados na parte superior para criar um novo arquivo fonte. Clique no menu File, escolha Open File, ento digite o nome do arquivo que voc a e 13

deseja abrir no minibuerlocalizado na parte inferior da tela.1 Se quiser criar um arquivo fonte na linguagem C, use um nome de arquivo que termine em .c ou em .h. Se voc quiser criar um arquivo fonte em C++, use um e nome de arquivo que termine em .cpp, .hpp, .cxx, .hxx, .C, ou .H. Quando o arquivo estiver aberto, voc pode digitar da mesma forma que faria em e qualquer programa processador de palavras comum. Para gravar o arquivo, escolha a entrada Saveno menu File. Quando voc tiver encerrado a utie lizao do Emacs, voc pode escolher a opo Exit Emacsno menuFile. ca e ca Se voc no gosta de apontar e clicar, voc pode usar tclas de atalho de tee a e clado para automaticamente abrir arquivos, gravar arquivos, e sair do Emacs. Para abrir um arquivo, digite C-x C-f. (O C-x signica pressionar a tecla ctrl e ento pressionar a tecla x.) Para gravar um arquivo, digite C-x C-s. Para a sair do Emacs, apenas digite C-x C-c. Se voc desejar adquirir um pouco e mais de habilidade com Emacs, escolha a entrada Emacs Tutorialno menu Help.O tutorial abastece voc com uma quantidade grande de dicas sobre e como usar Emacs efetivamente.

1.1.2

Formatando Automaticamente

Se voc est acostumado a programar em um Ambiente Integrado de Dee a senvolvimento (IDE)2 , voc conseqentemente estar tambm acostumado a e u a e ter o editor ajudando voc a formatar seu cdigo. Emacs pode fornecer o e o mesmo tipo de funcionalidade. Se voc abre um arquivo de cdigo em C e o ou em C++, Emacs automaticamente detecta que o arquivo contm cdigo e o fonte, no apenas texto comum. Se voc pressiona a tecla Tab em uma linha a e em branco, Emacs move o cursor para um ponto ajustado apropriadamente. Se voc pressionar a tecla Tab em uma linha que j contm algum texto, e a e Emacs ajusta o texto. Ento, por exemplo, suponha que voc tenha digitado a e o seguinte: int main ( ) { p r i n t f ( Alo , mundo\n ) ; } Se voc pressionar a tecla Tab na linha com a chamada ` funo printf, e a ca Emacs ir reformatar seu cdigo para parecer como mostrado abaixo: a o int main ( )
Se voc no est executando em um sistema X Window, voc ter de pressionar F10 e a a e a para acessar os menus. 2 Do ingls Integrated Development Environment. Em nosso bom portugus seria e e ADI
1

14

{ p r i n t f ( Alo , mundo\n ) ; } Note como a linha foi apropriadamente indentada. ` medida que seu uso do Emacs for acontecendo, voc ver como o Emacs A e a pode ajudar voc a executar todo tipo de complicadas tarefas de formatao. e ca Se voc for ambicioso, voc pode programar o Emacs para executar literale e mente qualquer tipo de formatao automtica que voc puder imaginar. ca a e Pessoas tm usado essa facilidade de programao para implementar modos e ca Emacs para editar todo tipo de documento, para implementar jogos3 , e para implementar interfaces para usurios acessarem bases de dados. a

1.1.3

Destaque Sinttico para Palavras Importantes a

Adicionalmente ` formatao de seu cdigo, Emacs pode destacar palavras a ca o facilmente ao ler cdigo em C e em C++ atravs da colorao de diferentes o e ca elementos sintticos. Por exemplo, Emacs pode atribuir a palavra chaves uma a certa cor, atribuir uma segunda cor diferente da anterior a tipos de dados internos tais como int, e atribuir a comentrios outra terceira cor diferente a das duas primeiras. A utilizao de cor torna muito mais fcil destacar alguns ca a erros comum de sintaxe. A forma mais fcil de habilitar cores editar o arquivo a e /.emacs e inserir aseguinte seqncia de caracteres: ue (global-font-lock-mode t) Grave o arquivo, saia do Emacs, e volte a ele em seguida. Agora abra um cdigo fonte em C ou em C++ e aproveite! o Voc possivelmente pode ter notado que a sequncia de caracteres que voc e e e inseriu dentro do seu .emacs semelhante a um cdigo da linguagem de e o programao LISP.Isso ocorre pelo fato de ser um cdigo LISP! Muitas partes ca o de cdigo do Emacs atualmente escrita em LISP. Voc pode adicionar o e e funcionalidades ao Emacs por meio de acrscimos em cdigo LISP. e o

1.2

Compilando com GCC

Um compilador converte um cdigo fonte leg o vel a seres humanos em um cdigo objeto leg o vel a computadores que pode ento ser executado. Os a
Tente executar o comando M-x dunnetse voc desejar divertir-se com um antiquae dro jogo de aventura em modo texto. Nota do tradutor: Dunnet um jogo distribu e do junto com o emacs cuja primeira verso datava dos idos de 1983. a
3

15

compiladores dispon veis em sistemas linux so todos parte da coleo de a ca 4 compiladores GNU, comumente conhecido como GCC. GCC tambm inclui e compiladores para as linguagens C, C++, Java, Objective-C, Fortran, e Ada. Esse livro est dirigido em sua grande parte em programao em C e C++. a ca Suponhamos que voc tenha um projeto como o da listagem 1.2 com um e arquivo de cdigo em C++ (reciprocal.cpp) e um arquivo de cdigo fonte em o o C (main.c) como o da listagem 1.1. Esses dois arquivos so supostamente a para serem compilados e ento linkados juntos para produzir um programa a chamado reciprocal.5 Esse programa ir calcular o rec a proco/inverso de um inteiro. Listagem 1.1: Arquivo main.c cdigo fonte em C o
#include <s t d i o . h> #include < s t d l i b . h> #include r e c i p r o c a l . hpp i n t main ( i n t a r g c , char a r g v ) { int i ; i = a t o i ( argv [ 1 ] ) ; p r i n t f ( The r e c i p r o c a l return 0 ; }

o f %d i s %g \n , i ,

reciprocal

(i));

Listagem 1.2: Arquivo reciprocal.cpp cdigo fonte em C++ o


#include <c a s s e r t > #include r e c i p r o c a l . hpp double r e c i p r o c a l ( i n t i ) { // I s h o u l d b e nonz e r o . a s s e r t ( i != 0 ) ; return 1 . 0 / i ; }

Existe tambm um arquivo de cabealho chamado reciprocal.hpp (veja a e c listagem 1.3). Listagem 1.3: Arquivo de cabealho reciprocal.hpp c
#i f d e f cplusplus extern C { #e n d i f extern #i f d e f } #e n d i f double r e c i p r o c a l cplusplus ( int i);

O primeiro passo converter o cdigo fonte em C e em C++ em cdigo e o o objeto.


Para mais informao sobre GCC, visite http://gcc.gnu.org. ca em Windows, arqu vos executveis geralmente possuem nomes que terminam em a .exe. Programas GNU/Linux, por outro lado, geralmente no possuem extenso. Ento, a a a o equivalente Windows do programa reciprocalpode provavelmente ser chamado reciprocal.exe; a verso Linux somente reciprocal. a e
5 4

16

1.2.1

Compilando um Arquivo Simples de Cdigo Fonte o

O nome do compilador C gcc. Para compilar um cdigo fonte em C, voc e o e usa a opo -c. Ento, por exemplo, inserindo isso no prompt de comando ca a compila o arquivo de cdigo fonte main.c: o % gcc -c main.c O arquivo objeto resultante chamado main.o. O compilador C++ chae e mado g++. Sua operao muito similar ao gcc; compilando reciprocal.cpp ca e realizada atravs do seguinte comando: e e % g++ -c reciprocal.cpp A opo -c diz ao compilador g++ para fornecer como sa um arquivo ca da objeto somente; sem essa opo, g++ iria tentar linkar o programa para proca duzir um executvel. Aps voc ter digitado esse comando, voc ir ter um a o e e a arquivo objeto chamado reciprocal.o. Voc ir provavelmente precisar de algumas outras opes para construir e a co qualquer programa razovelmente grande. A opo -I usada para dizer a ca e ao GCC onde procurar por arquivos de cabealho. Por padro, GCC olha c a no diretrio atual e nos diretrios onde cabealhos para bibliotecas padro o o c a esto instalados. Se voc precisar incluir arquivos de cabealho localizados a e c em algum outro lugar, voc ir precisar da opo -I. Por exemplo, supoe a ca nhamos que seu projecto tenha um diretrio chamado src, para arquivos o fonte, e outro diretrio chamado include. Voc pode compilar o arquivo o e reciprocal.cpp como segue abaixo para indicar que g++ deve usar o diretrio o ../includeadicionalmente para encontrar o arquivo de cabealho reciproc cal.hpp: % g++ -c -I ../include reciprocal.cpp Algumas vezes voc ir desejar denir macros na linha de comando. Por e a exemplo, no cdigo de produo, voc no ir querer o trabalho adicional da o ca e a a checagem de declarao presente em reciprocal.cpp; a checagem s existe para ca o ajudar a voc a depurar o programa. Voc desabilita a checagem denindo a e e macro NDEBUG. Voc pode ter adicionado uma declarao expl e ca cita #dene em reciprocal.cpp, mas issor requer modocao no cdigo fonte em si. E ca o mais fcil simplesmente denir NDEBUG na linha de comando, como segue: a % g++ -c -D NDEBUG reciprocal.cpp 17

Se voc tiver desejado denir NDEBUG para algum valor particular, voc e e pode ter feito algo como: % g++ -c -D NDEBUG=3 reciprocal.cpp Se voc estiver realmente construindo cdigo fonte de produo, voc e o ca e provavelmente deseja que o GCC otimize o cdigo de forma que ele rode to o a rapidamente quanto poss vel.Voc pode fazer isso atravs da utilizao da e e ca opo -O2 de linha de comando. (GCC tem muitos diferentes n ca veis de otimizao; o segundo n apropriado para a maioria dos programas.) Por ca vel e exemplo, o comando adiante compila reciprocal.cpp com otimizao habilica tada: % g++ -c -O2 reciprocal.cpp Note que compilando com otimizao pode fazer seu programa mais dif ca cil de depurar com um depurador (veja a Seo 1.4, Depurando com o Depuca rador GNU (GDB)). Tambm, em certas instncias, compilando com otie a mizao pode revelar erros em seu programa que no apareceriam em outras ca a situaes anteriores. co Voc pode enviar muitas outras opes ao compilador gcc e ao compilador e co g++. A melhor forma de pegar uma lista completa ver a documentao e ca em tempo real. Voc pode fazer isso digitando o seguinte na sua linha de e comando: % info gcc

1.2.2

Linkando Arquivos Objeto

Agora que voc compilou main.c e reciprocal.cpp, voc ir desejar link-los. e e a a Voc deve sempre usar o g++ para linkar um programa que contm cdigo e e o em C++, mesmo se ese cdigo C++ tambm contenha cdigo em C. Se seu o e o programa contiver somente cdigo em C, voc deve usar o gcc no lugar do o e g++. Pelo fato de o g++ est apto a tratar ambos os arquivos em C e em a C++, voc deve usar g++, como segue adiante: e % g++ -o reciprocal main.o reciprocal.o A opo -o fornece o nome do arquivo a ser gerado como sa no passo ca da de linkagem. Agora voc pode executar o reciprocal como segue: e % ./reciprocal 7 The reciprocal of 7 is 0.142857 18

Como voc pode ver, g++ linkou/vinculou automaticamente a biblioteca e C padro em tempo de execuo contendo a implementao da funo. Se a ca ca ca voc tiver precisado linkar outra biblioteca (tal como uma coleo de roe ca tinas/cdigos prontos para facilitar a criao de uma interface grca de o ca a 6 usurio) , voc pode ter especicado a biblioteca com a opo -l. Em GNU/a e ca Linux, nomes de biblioeca quase sempre comeam com lib. Por exemplo, a c biblioteca Pluggable Authentication Module(PAM) chamada libpam.a. e Para linkar a libpam.a, voc usa um comando como o seguinte: e % g++ -o reciprocal main.o reciprocal.o -lpam O compilador automaticamente adiciona o prexo libe o suxo .a7 . Da mesma forma que para os arquivos de cabealho, o linkador procura por c bibliotecarem alguns lugares padro, incluindo os diretrios /lib e /usr/lib a o onde esto localizadas as bibliotecas padro do sistema. Se voc deseja que a a e o linkador procure em outros diretrios tambm, voc deve usar a opo -L, o e e ca que a correspondente da opo -I discutida anteriormente. Voc pode usar e ca e essa linha para instruir o linkador a procurar por bibliotecas no diretrio o /usr/local/lib/pam antes de procurar nos lugares usuais: % g++ -o reciprocal main.o reciprocal.o -L/usr/local/lib/pam -lpam Embora voc no tenha a opo -I para instruir o preprocessor para proe a ca curar o diretrio atual, voc deve usar a opo -L para instruir o linkador o e ca a procurar no diretrio atual. Dizendo mais claramente, voc pode usar o e a seguinte linha para instruir o linkador a encontrar a biblioteca testno diretrio atual: o % gcc -o app app.o -L. -ltest

1.3

Automatizando o Processo com GNU Make

Se voc est acostumado a programar para o sistema operacional Windows, e a voc est provavelmente acostumado a trabalhar com um Ambiente Intee a grado de Desenvolvimento (IDE).Voc adiciona arquivos de cdigo fonte a e o seu projeto, e ento o IDE contri seu projeto automaticamente. Embora a o IDEs sejam dispon veis para GNU/Linux, esse livro no vai discut a -las. Em lugar de discutir IDEs, esse livro mostra a voc como usar o GNU Make para e
Nota do tradutor: QT ou Gtk Nota do tradutor: a biblioteca PAM pode ser encontrada em http://ftp.mgts.by/ pub/linux/libs/pam/library/.
7 6

19

automaticamente recompilar seu cdigo, que o que a maioria dos prograo e madores GNU/Linux atualmente fazem. A idia bsica por trs do make simples. Voc diz ao make os alvos que e a a e e voc deseja construir e ento fornece regras explanatria de como construir os e a o alvos desejados. Voc tambm especica dependncias que indicam quando e e e um alvo em particular deve ser reconstru do. Em nosso projeto exemplo reciprocal, existem trs alvos bvios: reciprocal.o, e o main.o, e o reciprocal executvel propriamente dito. Voc j tinha regras a e a em mente para reconstruir esses alvos na forma da linha de comando fornecidas previamente. As dependncias requerem um pouco de racioc e nio. Claramente, reciprocal depende de reciprocal.o e de main.o pelo fato de voc e no poder linkar o programa at voc ter constru cada um dos arquivos a e e do objetos. Os arquivos objetos devem ser reconstru dos sempre que o correspondente arquivo fonte mudar. Acontece mais uma modicao em reciproca cal.hpp isso tambm deve fazer com que ambos os arquivos objetos sejam e reconstru dos pelo fato de ambos os arquivos fontes incluirem aquele arquivo de cabealho. c Adicionalmente aos alvos bvios, deve-se ter sempre um alvo de limpeza. o Esse alvo remove todos os arquivos objetos gerados e programas de forma que voc possa iniciar de forma suave. A regra para esse alvo utiliza o coe mando rm para remover os arquivos. Voc pode reunir toda essa informao para o make colocando a informao e ca ca em um arquivo chamado Makele. Aqui est um exemplo de contedo de a u Makele: reciprocal: main.o reciprocal.o g++ $(CFLAGS) -o reciprocal main.o reciprocal.o main.o: main.c reciprocal.hpp gcc $(CFLAGS) -c main.c reciprocal.o: reciprocal.cpp reciprocal.hpp g++ $(CFLAGS) -c reciprocal.cpp clean: rm -f *.o reciprocal Voc pode ver que alvos so listados do lado esquerdo, seguidos por dois e a pontos e ento quaisquer dependncia so colocadas adiante dos dois pontos. a e a A regra para construir o referido alvo localiza-se na linha seguinte. (Ignore o $(CFLAGS) um pouco por um momento.) A linha com a regra para esse alvo 20

deve iniciar com um caractere de tabulao, ou make ir se confundir. Se ca a voc editar seu Makele no Emacs, Emacs ir ajudar voc com a formatao. e a e ca Se voc tiver removido os arquivos objetos que voc construiu anteriormente, e e e apenas digitar % make na linha de comando, voc ir ver o seguinte: e a % make gcc -c main.c g++ -c reciprocal.cpp g++ -o reciprocal main.o reciprocal.o Voc pode ver que make contri automaticamente os arquivos objetos e e o ento linka-os. Se voc agora modicar por algum motivo o main.c e digitar a e make novemente, voc ir ver o seguinte: e a % make gcc -c main.c g++ -o reciprocal main.o reciprocal.o Voc pode ver que make soube reconstruir main.o e re-linkar o programa, e mas o make no se incomodou em recompilar reciprocal.cpp pelo fato de a nenhuma das dependncias para reciprocal.o ter sofrido alguma modicao. e ca O $(CFLAGS) uma varivel do make. Vco pode denir essa varvel ou no e a e a Makele mesmo ou na linha de comando. GNU make ir substituir o valor a da varivel quando executar a regra. Ento, por exemplo, para recompilar a a com otimizao habilitada, voc deve fazer o seguinte: ca e % make clean rm -f *.o reciprocal % make CFLAGS=-O2 gcc -O2 -c main.c g++ -O2 -c reciprocal.cpp g++ -O2 -o reciprocal main.o reciprocal.o

1.4

Depurando com o Depurador GNU (GDB)

Note que o sinalizador -O2foi inserido no lugar de $(CFLAGS) na regra. Nessa seo, voc viu somente as mais bsicas capacidades do make. Voc ca e a e pode encontrar mais informaes digitando: co 21

% info make Nas pginas info de manual, voc ir encontrar informaes sobre como a e a co fazer para manuter um Makele simples, como reduzir o nmero de regras u que voc precisa escrever, e como automaticamente calcular dependncias. e e Voc pode tambm encontrar mais informao no livro GNU Autoconf, Aue e ca tomake, and Libtoolescrito por Gary V.Vaughan, Ben Elliston,Tom Tromey, e Ian Lance Taylor (New Riders Publishing, 2000).8

1.4.1

Depurando com GNU Debugger (GDB)

O depurador um programa que voc usa para descobrir porque seu proe e grama no est seguindo o caminho que voc pensa que ele deveria. Voc a a e e 9 far isso muitas vezes. O depurador GNU (GDB) o depurador usado pela a e maioria dos programadores em ambiente Linux. Voc pode usar GDB para e passear atravs de seu cdigo fonte, escolhendo pontos de parada, e examinar e o o valor de variveis locais. a

1.4.2

Compilando com Informaes de Depurao co ca

Para usar o GDB, voc ir ter que compilar com as informaes de depurao e a co ca habilitadas. Faa isso adicionado o comutador -g na linha de comando de c compilao. Se voc estiver usando um Makele como descrito anteriormente, ca e voc pode apenas escolher CFLAGS para -g quando voc executar o make, e e como mostrado aqui: % make CFLAGS=-g g++ -c -o reciprocal.o reciprocal.cpp cc -g -O2 main.c reciprocal.o -o main Quando voc compila com -g, o compilador inclui informaes extras nos e co arquivos objetos e executveis. O depurador usa essas informaes para a co descobrir quais endereos correspodem a determinada linha de cdigo e em c o qual arquivo fonte, como mostrar os valores armazenados em variveis locais, a e assim por diante.
Nota do tradutor: A verso eletrnica do livro pode ser encontrada em http:// a o sources.redhat.com/autobook/download.html. 9 ...a menos que seus programas sempre funcionem da primeira vez.
8

22

1.4.3

Executando o GDB

Voc pode iniciar digitando: e % gdb reciprocal Quando o gdb iniciar, voc ver o prompt do GDB: e a (gdb) O primeiro passo executar sseu programa dentro do depurador. Apenas e insira o comando rune quaisquer argumentos do programa que voc est e a depurando. Tente executar o programa sem qualquer argumento, dessa forma 10 : (gdb) run Starting program: reciprocal Program received signal SIGSEGV, Segmentation fault. 0xb7e7e41b in ____strtol_l_internal () from /lib/libc.so.6 O problema que no existe nenhum cdigo de vericao de entradas e a o ca errneas na funo main. O programa espera um argumento, mas nesse o ca caso o programa estava sendo executado sem argumentos. A mensagem de SIGSEGV indicar uma interrupo anormal do programa11 . GDB sabe que ca a interrupo anormal que ocorreu agora aconteceu em uma funo chamada ca ca strtol l internal. Aquela funo est na biblioteca padro. Voc pode ver ca a a e a pilha usando o comando where12 : (gdb) where #0 0xb7e7e41b #1 0xb7e7e180 #2 0xb7e7b401 #3 0x08048486 ) at main.c:9 in in in in ____strtol_l_internal () from /lib/libc.so.6 strtol () from /lib/libc.so.6 atoi () from /lib/libc.so.6 main (argc=Cannot access memory at address 0x0

Voc pode ver a partir dessa tela que a funo main chamou a funo e ca ca atoicom um apontador NULL, que a fonte de todo o problema. e Voc pode subir dois n e veis na pilha at encontrar a funo main atravs do e ca e uso do comando up:
Nota do tradutor: a sa foi obtida em um gdb verso 6.8 em 2009 sendo portanto da a uma atualizao da verso dispon em 2000 que foi o ano da publicao original ca a vel ca 11 Em ingls: crash e 12 Nota do tradutor: a sa foi obtida em um gdb verso 6.8 em 2009 sendo portanto da a uma atualizao da verso dispon em 2000 que foi o ano da publicao original ca a vel ca
10

23

(gdb) up 2 #2 0xb7e7b401 in atoi () from /lib/libc.so.6 Note que gdb capaz de encontrar o cdigo de main.c, e mostra a linha e o onde a chamada errnea de funo ocorreu. Voc pode ver os valores das o ca e variveis usando o comando print: a (gdb) print argv[1] No symbol "argv" in current context. O que conrma que o problema relamente um apontador NULL passado e dentro da funo atoi. ca Voc pode escolher um ponto de parada atravs do uso do comando break: e e (gdb) break main Breakpoint 1 at 0x8048475: file main.c, line 9. Esse comando dene um ponto de parada na primeira linha de main. Agora tente executar novamente o programa com um argumento, dessa forma:
13

(gdb) run 7 The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: reciprocal 7 Breakpoint 1, main (argc=2, argv=0xbfa0d334) at main.c:9 9 i = atoi (argv[1]); Voc pode ver que o depurador alcanou o ponto de parada. Voc pode e c e dar um passo adiante da chamada ` funo atoi usando o comando next: a ca (gdb) next 10 printf ("The reciprocal of \%d is \%g\\n", i, reciprocal (i)); Se voc desejar ver o que est acontecendo dentro de reciprocal, use o e a comando stepcomo segue:
Algumas pessoas tm comentado que colocando um ponto de parada em main um e e pouco esquisito porque de maneira geral voc somente desejar fazer isso quando main j e a a estiver quebrada.
13

24

(gdb) step reciprocal (i=7) at reciprocal.cpp:6 6 assert (i != 0); Current language: auto; currently c++ Voc est agora no corpo da funo reciprocal. Voc pode perceber que e a ca e mais conveniente o uso do gdb de dentro do Emacs em lugar de usar o gdb e diretamente na linha de comando. Use o comando M-x gdb para iniciar o gdb em uma janela Emacs. Se voc tiver parado em um ponto de parada, e Emacs automaticamente mostra o arquivo fonte apropriado. Dessa forma ca mais fcil descobrir o que est acontecendo quando voc olha no arquivo a a e completo em lugar de apenas em uma linha de texto.

1.5

Encontrando mais Informao ca

Praticamente toda distribuio GNU/Linux vem com uma grande quantica dade de documentao util. Voc pode ter aprendido mais do que estamos ca e falando aqui nesse livro por meio da leitura da documentao em sua disca tribuio Linux (embora isso possa provavelmente levar mais tempo). A ca documentao no est sempre bem organizada, de forma que a parte comca a a plicada encontrar o que precisa. Documentao tambm algumas vezes e ca e e desatualizada, ento tome tudo que voc vier a ler como pouca informao. a e ca Se o sistema no comportar-se no caminho apontado pela pgina de manual a a e como ela diz que deve ser, por exemplo, isso pode estar ocorrendo pelo fato de a pgina de manual estar desatualizada. Para ajudar voc a navegar, a e aqui est as mais uteis fontes de informao sobre programao avanada em a ca ca c GNU/Linux.

1.5.1

Pginas de Manual a

Distribuies GNU/Linux incluem pginas de manual para os comandos mais co a padronizados, chamadas de sistema, e funes da biblioteca padro. As man co a pagesso divididas em sees numeradas; para programadores, as mais ima co portantes so as seguintes: a (1) Comandos de usurio a (2) Chamadas de sistema (3) Funes da biblioteca padro co a (8) Comandos de Sistema/administrativos 25

Os nmeros denotam sees das pginas de manual. As pginas de mau co a a nual do GNU/Linux vm instaladas no seu sistema; use o comando manpara e acess-las. Para ver uma pgina de manual, simplesmente chame-a escrea a vendo man nome, onde nome um comando ou um nome de funo. Em e ca alguns poucos casos, o mesmo nome aparece em mais de uma seo; voc ca e pode especicar a seo explicitamente colocando o nmero da seo antes ca u ca do nome. Por exemplo, se voc digitar o seguinte, voc ir receber de volta a e e a pgina de manual para o comando sleep(na seo 1 da pagina de manual a ca do GNU/Linux): % man sleep Para ver a pgina de manual da funo de biblioteca sleep, use o coa ca mando adiante: % man 3 sleep Cada pgina de manual inclui um sumrio on-line do comando ou da a a funo. O comando whatis nomemostra todas as pginas de manual (em ca a todas as sees) para um comando ou funo que coincidir com nome. Se co ca voc no tiver certeza acerca de qual comando ou funo voc deseja, voc e a ca e e pode executar uma pesquisa por palavra chave sobre as linhas de sumrio, a usando man -k palavrachave. Pginas de manual incluem uma grande quantidade de informaes muito a co uteis e deve ser o primeiro lugar onde voc vai para obter ajuda. A pgina e a de manual para um comando descreve as opes de linha de comando e arguco mentos, entrada e sa da, cdigos de erro, congurao, e coisas semelhantes. o ca A pgina de manual para um chamada de sistema ou para uma funo de a ca biblioteca descreve os parmetros e valores de retorno, listas de cdigos de a o efeitos colaterais, e especica quais arquivos devem ser inclu dos 14 usar se voc desejar chamar essa funo. e ca

1.5.2

Info

A documentao de sistema do tipo Infopossuem documentao mais detaca ca lhada para muitos dos principais componentes do sistema GNU/Linux, alm e de muitos outros programas. Pginas Infoso documentos no formato de a a hipertexto, semelhantes a pginas Web. Para ativar o navegador de pginas a a Info no formato texto, apenas digite infoem uma janela de shell. Voc ir e a ser presenteado com um menu de documentos Info instalado em seu sistema.
14

Nota do tradutor: usado na diretiva #include

26

(Pressione Ctrl+H para mostrar teclas de navegao em um documento Info.) ca O conjunto de documentos Info que so mais uteis em nosso contexto so esa a ses 15 : Emacs: (emacs). The extensible self-documenting text editor. gcc: (gcc). The GNU Compiler Collection. Gdb: (gdb). The GNU debugger. Libc: (libc). C library. Info: (info). Documentation browsing system. A maioria de todas as ferramentas padronizadas de programao em amca biente GNU/Linux (incluindo o ld, o linkador; como, o assemblador; e gprof, o proler) so acompanhados com pginas Info bastante uteis. Voc a a e pode ir diretamente a uma documento Info em particular especicando o nome da pgina Info na linha de comando: a % info libc Se voc zer a maioria de sua programao no Emacs, voc pode acessar e ca e o navegador interno de pginas Info digitando M-x infoou C-h i. a

1.5.3

Arquivos de Cabealho c

Voc pode aprender muito sobre funes de sistema que esto dispon e co a veis e como us-las olhando nos arquivos de cabealho do sistema. Esses arquivos a c localizam-se em /usr/include e em /usr/include/sys. Se voc estiver recee bendo erros de compilao ao utilizar uma chamada de sistema, por exemplo, ca d uma olhada no arquivo de cabealho correspondente para vericar se a e c assinatura da funo a mesma que a que est listada na pgina de manual. ca e a a Em sistemas GNU/Linux, muitos dos detalhes b

Nota do tradutor:colocados na ordem e na forma em que aparecem e com a redao ca que aparece no GNU texinfo verso 4.13 em janeiro de 2009. a

15

27

28

Cap tulo 2 Escrevendo Bom Software GNU/Linux


ESSE CAP ITULO ABRANGE ALGUMAS TECNICAS BASICAS QUE GRANDE PARTE dos programadores GNU/Linux utilizam. Atravs das e orientaes apresentadas adiante, voc estar apto a escrever programas que co e a trabalhem bem dentro do ambiente GNU/Linux e atenda `s expectativas a dos usurios GNU/Linux no que corresponde a como os programas devem a trabalhar.

2.1

Interao Com o Ambiente de Execuo ca ca

Quando voc estudou inicialmente C ou C++, aprendeu que a funo especial e ca main o ponto de entrada principal para um programa. Quando o sistema e operacional executa seu programa, o referido sistema operacional fornece automaticamente certas facilidades que ajudam ao programa comunicar-se com o prprio sistema operacional e com o usurio. Voc provavelmente o a e aprendeu sobre os dois primeiros parmetros para a funo principal main, a ca comumente chamados argce argv, os quais recebem entradas para o seu programa. Voc aprendeu sobre stdoute stdin(ou sobre os uxos coute e cinna linguagem C++) que fornecem entrada e sa no console. Esses da recursos so fornecidos atravs das linguagens C e C++, e eles interagem a e com o sistema GNU/Linux de certas maneiras. GNU/Linux fornece outras formas de interagir com o sistema operacional alm das especicadas nesse e pargrafo. a 29

2.1.1

A Lista de Argumentos

Voc executa um programa a partir de um prompt de shell atravs da e e digitao do nome do programa. Opcionalmente, voc pode fornecer inca e formaes adicionais para o programa atravs da digitao de uma ou mais co e ca palavras aps o nome do programa, separadas por espaos. Essas palao c vras adiconais so chamadas argumentos de linha de comando. (Voc pode a e tambm incluir um argumento que contm espaos, empacotando os argue e c mentos entre apstrofos.) De forma mais geral, isso referente a como a lista o e de argumentos do programa passada pelo fato de essa lista no precisar ser e a originria de linha de comando de shell. No Cap a tulo 3, Processosvoc ir e a ver outro caminho para chamar um programa, no qual um programa pode especicar a lista de argumentos de outro programa diretamente. Quando um programa chamado a partir do shell, a lista de argumentos contm a e e linha de comando completa, incluindo o nome do programa e quaisquer argumentos de linha de comando que possa ter sido fornecido. Suponhamos, por exemplo, que voc chame o comando lsem seu shell para mostrar o e contedo do diretrio ra e os correspondentes tamanhos dos arquivos com u o z essa linha de comando: % ls -s / A lista de argumentos que o programa lsacima possui consta de trs are gumentos. O primeiro deles o nome do programa propriamente dito, como e especicado na linha de comando, lsa saber. O segundo e o terceiro elementos da lista de argumentos so os dois argumentos de linha de comando, a o -se a /. A funo mainde seu programa pode acessar a lista de argumentos por ca meio dos parmetros da funo mainargce argv(se voc por acaso no a ca e a utiliza esses dois argumentos, voc pode simplesmente omit e -los). O primeiro parmetro, argc, um inteiro que representa o nmero de argumentos na lista a e u de argumentos. O segundo parmentro, argv, um vetor de apontadores de a e caracteres. O tamanho do vetor argc, e os elementos do vetor apontam para e os elementos da lista de argumentos, com cada elemento da lista terminado com o caractere nulo /0.1 A utilizao de argumentos de linha de comando to fcil quanto examinar ca e a a os contedos de argc e argv. Se voc no estiver interessado no nome do u e a programa propriamente dito, lembre-se de ignorar o primeiro elemento. Logo abaixo temos a listagem 2.1 demonstra como usar argc e argv.
1

Nota do tradutor: ver [?] p. 113.

30

Listagem 2.1: (Arquivo arglist.c) Usando argc e argv.


#include <s t d i o . h> i n t main ( i n t a r g c , char a r g v [ ] ) { p r i n t f ( The name o f t h i s program i s % s . \ n , a r g v [ 0 ] ) ; p r i n t f ( T h i s program was i n v o k e d w i t h %d a r g u m e n t s . \ n , a r g c 1 ) ; / Were any commandl i n e a r g u m e n t s i f ( argc > 1) { / Yes , p r i n t them . / int i ; p r i n t f ( The a r g u m e n t s a r e : \ n ) ; f o r ( i = 1 ; i < a r g c ; ++i ) p r i n t f ( %s \n , a r g v [ i ] ) ; } return 0 ; } specified? /

2.1.2

Convenes GNU/Linux de Linha de Comando co

Quase todos os programas GNU/Linux obedecem algumas convenes sobre co como argumentos de linha de comando so interpretados. O argumentos que a so passados a programas so de duas categorias: opes (ou sinalizadores) a a co e outros argumentos. As opes modicam como o programa se comporta, co enquanto outros argumentos fornecem entradas (por exemplo, os nomes de arquivos de entrada). Opes chegam de duas formas: co Opes curtas consistindo de um hifen simples e um caractere simples co (comunmente em caixa baixa ou caixa alta). Opes curtas so rpidas co a a de digitar. Opes longas consistindo de dois h co fens, seguidos por um nome composto em caixa baixa e caixa baixa e h fens. Opes longas so fceis co a a de lembrar e fceis de ler (em scripts shell, por exemplo). a Habitualmente, um programa fornece ambas as formas curta e longa para a maioria das opes que so suportadas pelo referido programa, a forma curta co a por uma questo de brevidades e a forma longa por uma questo de clareza. a a Por exemplo, a maioria dos programas entendem as opes -h e --help, e as co tratam de forma identica. Normalmente, quando um programa chamado e em um shell sem nenhum argumento, quaisquer opes desejadas seguem o co nome do programa imediatamente. Algumas opes esperam que um arguco mento as siga imediatamente. Muitos programas, por exemplo, interpretam a opo --output qualquercoisa como especicando que a sa de um proca da grama deva ser colocada em um arquivo chamado qualquercoisa. Aps as o opes, podem existir adiante delas outros argumentos de linha de comando, co tipicamente arquivos de entrada ou dados de entrada. Por exemplo, o comando ls -s / mostra o contedo do diretrio ra A Opo -s modica u o z. ca 31

o comportamento padro do ls instruindo o ls a mostrar o tamanho (em kia lobytes) de cada entrada.O argumento / diz ao ls qual diretrio listar. A o opo --size sinnimo da opo -s, de forma que o mesmo comando ca e o ca pode poderia ter sido digitado como ls --size /. A codicao GNU padro lista os nomes de alguns opes de linha de ca a co comando comumente usadas. Se voc planeja fornecer qualquer quaisquer e opes similares a alguma dessas, uma boa idia usar os nomes especicaco e e dos na codicao padro. Seu programa ir se comportar mais como outros ca a a programas e ir ser mais fcil para os usurios aprenderem. Voc pode visua a a e alizar o guia de Condicao GNU Padro para opes de linha de comando ca a co digitando o seguinte em um propt de comandos de um shell na maioria dos sistemas GNU/Linux systems2 : % info "(standards)User Interfaces"

2.1.3

Usando getopt long

A passagem de opes de linha de comando uma tarefa tediosa. Felizco e mente, a biblioteca C GNU fornece um funo que voc usar em prograca e mas em C e em programas em C++ para fazer esse trabalho de alguma forma um pouco mais fcil (embora ainda assim um pouco incmoda). Essa a o funo, getopt long, recebe ambas as formas curta e longa de passagem ca de parmetros. Se voc for usar essa funo, inclua o arquivo de cabealho a e ca c <getopt.h>. Suponha, por exemplo, que voc est escrevendo um programa que para e a e aceitar as trs opes mostradas na tabela 2.1. e co Tabela 2.1: Opes do Programa Exemplo co Forma Curta Forma Longa Propsito o -h help Mostra sumrio de uso e sai a -o nomearquivo output nomearquivo Especica o nome do arquivo de sa da -v verbose Mostra mensagens detalhadas

Adicionalmente, o programa deve aceitar zero ou mais argumentos de linha de comando, que so os nomes de arquivos de entrada. a
Nota do tradutor: o guia de Condicao GNU Padro tambm pode ser acesca a e sado via http://www.gnu.org/prep/standards/html node/User-Interfaces.html# User-Interfaces
2

32

Para usar a funo getopt long, voc deve fornecer duas estruturas de dados. ca e A primeira uma seqncia de caracteres contendo as opes vlidas em sua e ue co a forma curta, cada letra simples. Uma opo que necessite de um argumento ca seguida de dois pontos. Para o seu programa, a seqncia de caracteres e ue ho:vindica que as opes vlidas so -h, -o, e -v, com a segunda dessas trs co a a e opes devendo ser seguida por um argumento. co Para especicar as opes longas dispon co veis, voc constri um vetor de elee o mentos de estruturas de opes. Cada elemento corespondendo a uma opo co ca longa e tendo quatro campos. Em circunstncias normais, o primeiro campo a o nome da opo longa (na forma de uma seqncia de caracteres, sem e ca ue os dois h fens); o segundo campo 1 se a opo precisa de argumento, ou e ca 0 em caso contrrio; o terceiro campo NULL; e o quarto um caractere a e e constante especicando a forma curta que sinnimo da referida opo de e o ca forma longa. O ultimo elemento do vetor deve ter todos os campos zerados como adiante. Voc pode construir o vetor como segue: e const struct option long_options[] = { { "help", 0, NULL, h }, { "output", 1, NULL, o }, { "verbose", 0, NULL, v }, { NULL,0, NULL, 0} }; Voc chama a funo getopt long, passando a ela os argumentos argc e e ca argv que so passados ` funo main, a seqncia de caracteres descrevendo a a ca ue as opes curtas, e o vetor de elementos de estruturas de opes descrevendo co co as opes longas. co Cada vez que voc chamar getopt long, a funo getopt long passa uma e ca opo simples, retornando a letra da forma curta para aquela opo ou ca ca -1 se nenhuma opo for encontrada. ca Tipicamente, voc ir chamar getopt long dentro de um lao, para proe a c cessar todas as opes que o usurio tiver especicado, e voc ir maco a e a nusear as opes espec co cas usando o comando switch. Se a funo getopt long encontra uma opo invlida (uma opo que ca ca a ca voc no especicou como uma opo curta vlida ou como uma opo e a ca a ca longa vlida), a funo getopt long imprime uma mensagem de erro e a ca retorna o caractere ? (um ponto de interrogao). A grande maioria ca dos programas ir encerrar a execuo em resposta a isso, possivelmente a ca aps mostrar informaes de utilizao. o co ca 33

Quando se estiver manuseando uma opo que precisa de um arguca mento, a varvel global optarg aponta para o texto daquele argumento. a Aps getopt long terminar de manusear todas as opes, a varivel o co a global optind conter o a ndice (dentro de argv) do primeiro argumento no classicado como vlido. a a A listagem 2.2 mostra um exemplo de como voc pode usar getopt long e para processar seus argumentos. Listagem 2.2: (getopt long.c) Usando a funo getopt long ca
#include <g e t o p t . h> #include <s t d i o . h> #include < s t d l i b . h> / The name o f t h i s p r o g r a m . const char program name ; /

/ P r i n t s u s a g e i n f o r m a t i o n f o r t h i s p r o g r a m t o STREAM ( t y p i c a l l y Does n o t s t d o u t o r s t d e r r ) , and e x i t t h e p r o g r a m w i t h EXIT CODE . return . / void p r i n t u s a g e { f p r i n t f ( stream f p r i n t f ( stream h o v exit ( exit code } ( FILE stream , int exit code )

, Usage : %s o p t i o n s [ i n p u t f i l e . . . ] \ n , program name ) ; , h e l p D i s p l a y t h i s u s a g e i n f o r m a t i o n . \ n o u t p u t f i l e n a m e Write o u t p u t t o f i l e . \ n v e r b o s e P r i n t v e r b o s e m e s s a g e s . \ n ) ; );

/ Main p r o g r a m e n t r y p o i n t . ARGC c o n a i n s number o f a r g u m e n t e l e m e n t s ; ARGV i s an a r r a y o f p o i n t e r s t o them . / i n t main ( i n t a r g c , char a r g v [ ] ) { int next option ; / A s t r i n g l i s t i n g v a l i d s h o r t o p t i o n s l e t t e r s . / const char const s h o r t o p t i o n s = ho : v ; / An a r r a y d e s c r i b i n g v a l i d l o n g o p t i o n s . / const s t r u c t o p t i o n l o n g o p t i o n s [ ] = { { help , 0 , NULL, h } , { output , 1 , NULL, o } , { verbose , 0 , NULL, v } , { NULL, 0 , NULL, 0 } / R e q u i r e d a t end o f }; / The name o f t h e f i l e t o r e c e i v e p r o g r a m o u t p u t , standard output . / const char o u t p u t f i l e n a m e = NULL ; / Whether t o d i s p l a y v e r b o s e m e s s a g e s . / int verbose = 0 ; / Remember t h e name o f t h e program , t o The name i s s t o r e d i n a r g v [ 0 ] . / program name = a r g v [ 0 ] ; do { next option = getopt long incorporate

list

array .

o r NULL f o r

in

messages .

( a r g c , argv , s h o r t o p t i o n s , l o n g o p t i o n s , NULL) ; switch ( n e x t o p t i o n ) { case h : / h o r h e l p / / U s e r h a s r e q u e s t e d u s a g e i n f o r m a t i o n . Print i t to standard o u t p u t , and e x i t w i t h e x i t c o d e z e r o ( n o r m a l t e r m i n a t i o n ) . / p r i n t u s a g e ( stdout , 0) ; case o : / o o r o u t p u t / / T h i s o p t i o n t a k e s an a r g u m e n t , output filename = optarg ; break ;

t h e name o f

the

output

file .

34

case v : / v o r v e r b o s e / verbose = 1; break ; case ? : / The u s e r s p e c i f i e d an i n v a l i d o p t i o n . / / P r i n t u s a g e i n f o r m a t i o n t o s t a n d a r d e r r o r , and e x i t w i t h c o d e one ( i n d i c a t i n g a b o n o r m a l t e r m i n a t i o n ) . / p r i n t u s a g e ( stderr , 1) ; case 1: break ; / Done w i t h options . /

exit

default : / S o m e t h i n g abort () ; } } while ( n e x t o p t i o n != 1) ;

else :

unexpected .

/ Done w i t h o p t i o n s . OPTIND p o i n t s t o f i r s t nono p t i o n For d e m o n s t r a t i o n p u r p o s e s , p r i n t them i f t h e v e r b o s e specified . / i f ( verbose ) { int i ; f o r ( i = o p t i n d ; i < a r g c ; ++i ) p r i n t f ( Argument : %s \n , a r g v [ i ] ) ; } / The main p r o g r a m g o e s return 0 ; } here . /

argument . o p t i o n was

O uso de getopt long pode ser visto como muito trabalho, mas escrevendo cdigo para passar as opes de linha de comando propriamente ditas pode o co ser mais trabalhoso ainda. A funo getopt long muito sosticada e permite ca e grande exibilidade na especicao de qual tipo de opo aceitar. Todavia, ca ca uma boa idia adiar a adoo de recursos mais avanados e segurar-se um e e ca c pouco na estrutura bsica de opo descrita acima. a ca

2.1.4

E/S Padro a

A biblioteca C padro fornece uxos de entrada e sa padro (stdin e sta da a dout, respectivamente). Tanto a entrada como a sa padro so usadas por da a a scanf, printf, e outras funes de biblioteca. Na tradio UNIX, o uso da co ca entrada e da sa padro comum e habitual para programas GNU/Linux. da a e Isso permite encadeamento de multiplos programas usando pipes de shell e redirecionamentos de entrada e sa da. (Veja a pgina de manual para o seu a shell preferido para aprender a sintaxe nesses caso de pipes e redirecionamentos.) A biblioteca C tambm fornece stderr, o uxo padro de erro. Programas e a podem mostrar mensagens de erro para a sa padro de erro em lugar da a de enviar para a sa padro. Esse tipo de comportamento permite aos da a usurios separarem a sa normal e mensagens de erro, por exemplo, atravs a da e do redirecionamento da sa padro para um arquivo enquanto permite a da a impresso da sa de erro para o console. A funo fprintf pode ser usada a da ca para imprimir para a sa padro de erro stderr, por exemplo: da a fprintf (stderr, (Error: ...")); 35

Esses trs uxos3 so tambm access e a e veis com os comandos bsicos UNIX a de E/S (read, write, e assim por diante) por meio dos trs descritores de are quivo usados em shell. Os descritores so 0 para stdin, 1 para stdout, e 2 a para stderr. Quando um programa for chamado, pode ser algumas vezes util redirecio nar ambas a sa padro e a sa de erro para um arquivo ou pipe. A da a da sintaxe para fazer isso varia nos diversos shells; para shells do estilo Bourne (incluindo o bash, o shell padro na maioria das distribuies GNU/Linux), a co dois exemplos so mostrados logo abaixo: a

% programa > arquivo_saida.txt 2>&1 % programa 2>&1 | filtro A sintaxe 2>&1 indica que o descritor 2 de arquivo (stderr ) deve ser entregue no descritor de arquivo 1 (stdout). Note que 2>&1 deve vir aps o um redirecionamento de arquivo (a primeira linha exemplo logo acima) mas deve vir antes de um redirecionamento por meio de pipe (a segunda linha exemplo logo acima). Note que stdout armazenada em uma rea temporria. Dados escritos e a a para stdout no so enviados para o console (ou para outro dispositivo caso a a haja redirecionamento) imediatamente. Dados escritos para stdout so ena viados para o console em trs situaes: quando a rea de armazenamento e co a temporrio esteja preenchida compleamente, quando o programa terminar a normalmente ou quando stdout for fechada. Voc pode explicitamente dese carregar a rea de armazenamento temporria atravs da seguinte chamada: a a e fflush (stdout); Por outro lado, stderr no armazenada em um local temporrio; dados a e a escritos para stderr vo diretamente para o console. 4 a Isso pode produzir alguns resultados surpreendentes. Por exemplo, esse lao c no mosta um ponto a cada segundo; em vez disso, os pontos so armazenados a a em uma rea temporria, e um grupo de pontos mostrado todos de uma a a e unica vez quando o limite de armazenamento da rea temporria alcanado. a a e c
Nota do tradutor:stdin, stdout e stderr. Em C++, a mesma distino se mantm para cout e para cerr, respectivamente. Note ca e que a marca endl descarrega um uxo adicionalmente ` impresso um caractere de nova a a linha; se voc no quiser descarregar um uxo (por razes de performace, por exemplo), e a o use em substituio a endl uma constante de nova linha, \n. ca
4 3

36

while ( 1 ) { printf ( . ); sleep (1); } No lao adiante, todavia, o ponto aparece uma vez a cada segundo: c while ( 1 ) { f p r i n t f ( stderr , . ) ; sleep (1); }

2.1.5

Cdigos de Sa de Programa o da

Quando um programa termina, ele indica sua situao de sa com um ca da cdigo de sa o da. O cdigo de sa um inteiro pequeno; por conveno, um o da e ca cdigo de sa zero denota execuo feita com sucesso, enquanto um cdigo o da ca o de sa diferente de zero indica que um erro ocorreu. Alguns programas da usam diferentes valores de cdigos diferentes de zero para distingir erros o u espec cos. Com a maioria dos shells, poss e vel obter o cdigo de sa o da do programa executado mais recentemente usando a varivel especial $?. a Aqui est um exemplo no qual o comando ls chamado duas vezes e seu a e cdigo de sa mostrado a cada chamada. No primeiro caso, ls executa o da e corretametne e retorna o cdigo de sa zero. No segundo caso, ls encontrou o da um erro (porque o o nome de arquivo especicado na linha de comando no a existe) e dessa forma retorna um cdigo de sa diferente de zero: o da % ls / bincoda etc libmisc nfs proc sbinusr boot dev home lost+found mnt opt root tmp var % echo $? 0 % ls bogusfile ls: bogusfile: No such file or directory % echo $? 1 Um programa em C ou em C++ especica seu cdigo de sa atravs o da e do retorno do cdigo de sa devolvido pela funo main. Existem ouo da ca tros mtodos de fornecer cdigos de sa e o da, e cdigos de sa especial so o da a atribu dos a programas que terminam de forma diferente da esperada (por meio de um sinal). Isso ser discutido adicionalmente no Cap a tulo 3. 37

2.1.6

O Ambiente

GNU/Linux fornece a cada programa sendo executado um ambiente. O ambiente uma coleo de pares varivel/valor. Ambos nome de variveis e ca a a de ambiente e seus valores respectivos so sequncias de caracteres. Por a e conveno, nomes de variveis de ambiente so grafados com todas as letras ca a a em maiscula. u Voc provavelmente j est familiarizado com muitas variveis de ambiente e a a a mais comuns. Por exemplo: USER contm seu nome de usurio. e a HOME contm o caminho para seu diretrio de usurio. e o a PATH contm uma lista de itens separada por ponto e v e rgula dos diretrios os quais GNU/Linux busca pelo comando que voc chamar. o e DISPLAY contm o nome e o nmero do display do servidor sobre o e u qual janelas de programas grcos do X iro aparecer. a a Seu shell, como qualquer outro programa, tem um ambiente. Shells fornecem mtodos para examinar e modicar o ambiente diretamente. Para e mostrar o ambiente atual em seu shell, chame o programa printenv. Vrios a shells possuem diferentes sintaxes internas para a utilizao de variveis de ca a ambiente; o que mostrado adiante a sintaxe no estilo dos shells do tipo e e Bourne. O shell automaticamente cria uma varivel shell para cada varivel de a a ambiente que encontrar, de forma que voc acessar valores de variveis e a de ambiente usando a sintaxe $nomedevariavel. Por exemplo: % echo $USER samuel % echo $HOME /home/samuel Voc pode usar o comando export para exportar uma varivel shell dene a tro do ambiente. Por exemplo, para modicar a varivel de ambiente a EDITOR, voc pode usar o seguinte: e % EDITOR=emacs % export EDITOR 38

Ou, de forma curta e rpida: a % export EDITOR=emacs Em um programa, voc acessa uma varivel de ambiente com a funo e a ca getenv na <stdlib.h>. A funo getenv pega um nome de varivel e reca a torna o valor correspondente como uma seqncia de caracteres, ou NULL ue se a referida varivel no tiver sido denida no ambiente. Para modicar ou a a limpar variveis de ambiente, use as funes setenv e unsetenv, respectivaa co mente. Listar todas as variveis de um ambiente um pouco complicado. a e Para fazer isso, voc deve acessar uma varivel global especial chamada ene a viron, que denida na biblioteca C GNU. Essa varivel, do tipo char**, e a um vetor de apontadores terminado com o caractere NULL que apontam e para seqncias de caracteres. Cada seqncia de caracteres contendo uma ue ue varivel de ambiente, na forma VARIAVEL=valor. O programa na listagem a 2.3, por exemplo, simplesmente mostra na tela todas as variveis de ambiente a atravs de um lao ao longo do vetor de apontadores environ. e c Listagem 2.3: (print-env.c) Mostrando o Ambiente de Execuo ca
#include <s t d i o . h> / The ENVIRON v a r i a b l e extern char e n v i r o n ; contains the environment . /

i n t main ( ) { char v a r ; f o r ( v a r = e n v i r o n ; v a r != NULL ; ++v a r ) p r i n t f ( %s \n , v a r ) ; return 0 ; }

No modique o ambiente propriamente dito; use as funes setenv e a co unsetenv para fazer as modicaes que voc precisar. Comumente, quando co e um novo programa iniciado, ele herda uma cpia do ambiente do programa e o que o chamou (o programa de shell, se o referido programa tiver sido chamado de forma interativa). Dessa forma, por exemplo, programas que voc executa e a partir de um programa de shell pode examinar os valores das variveis de a ambiente que voc escolheu no shell que o chamou. e Variveis de ambiente so comumente usadas para indicar informaes de a a co congurao a programas. Suponha, por exemplo, que voc est escrevendo ca e a um programa que se conecta a um servidor de internet para obter alguma informao. voc pode ter escrito o programa de forma que o nome do ca e servidor especicado na linha de comando. Todavia, suponha que o nome e do servidor no seja alguma coisa que os usurios iro modicar muitas vezes. a a a Voc pode usar uma varivel especial de ambiente digamos SERVER NAME e a para especicar o nome do servidor; se SERVER NAME no existir, um a 39

valor padro usado. Parte do seu programa pode parecer como mostrado a e na listagem 2.4. Listagem 2.4: (client.c) Parte de um Programa Cliente de Rede
#include <s t d i o . h> #include < s t d l i b . h> i n t main ( ) { char s e r v e r n a m e = g e t e n v ( SERVER NAME ) ; i f ( s e r v e r n a m e == NULL) / The SERVER NAME e n v i r o n m e n t v a r i a b l e was n o t default . / s e r v e r n a m e = s e r v e r . my company . com ; p r i n t f ( a c c e s s i n g s e r v e r %s \n , s e r v e r n a m e ) ; / A c c e s s t h e s e r v e r h e r e . . . / return 0 ; }

set .

Use t h e

Suponhamos que o programa acima seja chamado de client. Assumindo que voc no tenha criado ou que no tenha sido criada anteriormente a e a a varivel SERVER NAME, o valor padro para o nome do servidor usado: a a e % client accessing server server.my-company.com Mas fcil especicar um servidor diferente: e a % export SERVER_NAME=backup-server.emalgumlugar.net % client accessing server backup-server.emalgumlugar.net

2.1.7

Usando Arquivos Temporrios a

Algumas vezes um programa necessita criar um arquivo temporrio, para a armazenar grandes dados por alguns instantes ou para entreg-los a outro a programa. Em sistemas GNU/Linux, arquivos temporrios so armazenados a a no diretrio /tmp. Quando zer uso de arquivos temporrios, voc deve estar o a e informado das seguintes armadilhas: Mais de uma instncia de seu programa pode estar sendo executada a simultneamente (pelo mesmo usurio ou por diferentes usurios). As a a a instncias devem usar diferentes nomes de arquivos temporrios de a a forma que eles no colidam. a As permisses dos arquivos temporrios devem ser ajustadas de tal o a forma que somente usurios autorizados possam alterar a execuo a ca do programa atravs de modicao ou substituio do arquivo teme ca ca porrio. a 40

Nomes de arquivos temporrios devem ser gerados de forma imprea vis externamente; de outra forma, um atacante pode usar a espera vel entre a vericao de que um nome de arquivo fornecido j est sendo ca a a usado e abrir um novo arquivo temporrio. a GNU/Linux fornece funes, mkstemp e tmple, que cuidam desses reco cursos para voc de forma adequada (e adicionalmente muitas funes que e co no cuidam). Qual voc ir usar depende de seu planejamento de manusear a e a o arquivo temporrio para outro programa, e de se voc deseja usar E/S a e UNIX (open, write, e assim por diante) ou as funes de controle de uxos co da biblioteca C (fopen, fprintf, e assim por diante). Usando mkstemp A funo mkstemp criar um nome de arquivo temca a porrio de forma unica a partir de um modelo de nome de arquivo, cria o a arquivo propriamente dito com permisses de forma que somente o usurio o a atual possa acess-lo, e abre o arquivo para leitura e escrita. O modelo de a nome de arquivo uma seqncia de caracteres terminando com XXXXXX e ue (seis letras X maisculas); mkstemp substitui as letras X por outros caracu teres de forma que o nome de arquivo seja unico. O valor de retorno e um descritor de arquivo; use a fam de funes aparentadas com a funo lia co ca write para escrever no arquivo temporrio. Arquivos temporrios criados a a com mkstemp no so apagados automaticamente. Compete a voc remoa a e ver o arquivo temporrio quando o referido arquivo temporrio no mais a a a for necessrio. (Programadores devem ser muito cuidadosos com a limpeza a de arquivos temporrios; de outra forma, o sistema de arquivos /tmp ir a a encher eventualmente, fazendo com que o sistema que inoperante.) Se o arquivo temporrio for de uso interno somente e no for manuseado por outro a a programa, uma boa idia chamar unlink sobre o arquivo temporrio imee e a diatamente. A funo unlink remove a entrada do diretrio correspondente ca o a um arquivo, mas pelo fato de arquivos em um sistema de arquivo serem contados-referenciados, o arquivos em si mesmos no so removidos at que a a e no hajam descritores de arquivo abertos para aquele arquivo. Dessa forma, a seu programa pode continuar usando o arquivo temporrio, e o arquivo evoa lui automaticamenete at que voc feche o descritor do arquivo. Pelo fato de e e GNU/Linux fechar os descritores de arquivo quando um programa termina, o arquivo temporrio ir ser removido mesmo se seu programa terminar de a a forma abrupta. O par de funes na listagem 2.5 demonstra mkstemp. Usadas juntas, esco sas duas funes tornam fcil escrever o contedo de uma rea temporria co a u a a de armazenamento na memria para um arquivo temporrio (de forma que o a 41

a memoria possa ser liberada ou reutilizada) e de forma que esse contedo u armazenado possa ser trazido de volta ` memria mais tarde. a o Listagem 2.5: (temp le.c) Usando mkstemp
#include < s t d l i b . h> #include <u n i s t d . h> / A h a n d l e f o r a t e m p o r a r y f i l e c r e a t e d t h i s implementation , i t s j u s t a f i l e typedef i n t t e m p f i l e h a n d l e ; with w r i t e t e m p f i l e . descriptor . / In

/ W r i t e s LENGTH b y t e s f r o m BUFFER i n t o a t e m p o r a r y f i l e . The temporary f i l e i s immediately u n l i n k e d . Returns a handle to temporary f i l e . /

the

t e m p f i l e h a n d l e w r i t e t e m p f i l e ( char b u f f e r , s i z e t l e n g t h ) { / C r e a t e t h e f i l e n a m e and f i l e . The XXXXXX w i l l b e r e p l a c e d w i t h c h a r a c t e r s t h a t make t h e f i l e n a m e u n i q u e . / char t e m p f i l e n a m e [ ] = /tmp/ t e m p f i l e .XXXXXX ; i n t f d = mkstemp ( t e m p f i l e n a m e ) ; / U n l i n k t h e f i l e i m m e d i a t e l y , s o t h a t i t w i l l b e r e m o v e d when t h e f i l e descriptor is closed . / unlink ( temp filename ) ; / W r i t e t h e number o f b y t e s t o t h e f i l e f i r s t . / w r i t e ( f d , &l e n g t h , s i z e o f ( l e n g t h ) ) ; / Now w r i t e t h e d a t a i t s e l f . / w r i t e ( fd , b u f f e r , l e n g t h ) ; / Use t h e f i l e d e s c r i p t o r a s t h e h a n d l e f o r t h e t e m p o r a r y f i l e . / return f d ; } / Reads t h e c o n t e n t s o f a t e m p o r a r y f i l e TEMP FILE c r e a t e d w i t h write temp file . The r e t u r n v a l u e i s a n e w l y a l l o c a t e d b u f f e r t h o s e c o n t e n t s , w h i c h t h e c a l l e r must d e a l l o c a t e w i t h f r e e . LENGTH i s s e t t o t h e s i z e o f t h e c o n t e n t s , i n b y t e s . The temporary f i l e i s removed . /

of

char r e a d t e m p f i l e ( t e m p f i l e h a n d l e t e m p f i l e , s i z e t l e n g t h ) { char b u f f e r ; / The TEMP FILE h a n d l e i s a f i l e d e s c r i p t o r t o t h e t e m p o r a r y f i l e . / int fd = t e m p f i l e ; / Rewind t o t h e b e g i n n i n g o f t h e f i l e . / l s e e k ( f d , 0 , SEEK SET ) ; / Read t h e s i z e o f t h e d a t a i n t h e t e m p o r a r y f i l e . / read ( fd , leng th , s i z e o f ( l e n g t h ) ) ; / A l l o c a t e a b u f f e r and r e a d t h e d a t a . / b u f f e r = ( char ) m a l l o c ( l e n g t h ) ; read ( fd , b u f f e r , l e n g t h ) ; / C l o s e t h e f i l e d e s c r i p t o r , w h i c h w i l l c a u s e t h e t e m p o r a r y f i l e t o g o away . / c l o s e ( fd ) ; return b u f f e r ; }

Usando tmple Se voc est usando as funes de E/S da biblioteca C e a co e no precisa passar o arquivo temporrio para outro programa, voc pode a a e usar a funo tmple. Essa funo cria e abre um arquivo temporrio, e ca ca a retorna um apontador de arquivo para esse mesmo arquivo temporrio. O a arquivo temporrio j unlinked, como no exemplo anterior, de forma que a ae ser apagado automaticamente quando quando o apontador de arquivo for a fechado (com fclose) ou quando o programa terminar. GNU/Linux fornece muitas outras funes para a gerao de arquivos temco ca porarios e nomes de arquivos temporrios, incluindo mktemp, tmpnam, e a a tempnam. No use essas funes, apesar disso, pelo fato de elas possu a co rem problemas de conabilidade e segurana j mencionados anteriormente. c a 42

2.2

Fazendo Cdigo Defensivamente o

Escrevendo programas que executam atualmente sob uso normal trabae lhoso; escrever programas que comportam-se de forma elegante em situaes co de falha mais trabalhoso ainda. Essa seo demonstra algumas tcnicas de e ca e codicao para encontrar erros facilmente e para detectar e recuperar-se de ca problemas durante a execuo de um programa. ca As amostras de cdigo apresentadas mais adiante nesse livro omitem erros o extensivos de vericao e recuperao de cdigo pelo fato de isso eventualca ca o mente vir a obscurecer a funcionalidade bsica que se deseja apresentar aqu a . Todavia, O exemplo nal no cap tulo ??, Uma Amostra de uma Aplicao ca GNU/Linuxretorna ` demonstrao de como usar essas tcnicas para escrea ca e ver programas robustos.

2.2.1

Usando assert

Um bom objetivo para se ter em mente quando criamos um cdigo fonte o de uma aplicao que erros comuns ou mesmo erros inesperados podem ca e fazer com que o programa falhe de forma dramtica, to facilmente quanto a a poss vel. O uso de assert ir ajudar voc a encontrar erros facilmente no a e desenvolvimento e na fase de teste. Falhas que no se mostram de forma a evidente passam surpreendentemente e muitas vezes desapercebidas e no se a mostram at que a aplicao esteja nas mos do usurio. e ca a a Um dos mais simples mtodos de vericar condies inesperadas a macro e co e assert da biblioteca C padro. O argumento para essa macro uma expresso a e a Booleana. O programa terminado se a expresso Booleana avaliar para e a false, aps mostrar uma mensagem de erro contendo o cdigo fonte e o nmero o o u da linha e o texto da expresso. A macro assert muito util para uma larga a e variedade de vericaes de consistncias internas em um dado programa. co e Por exemplo, use assert para testar a validade de argumentos de funes, co para testar condies prvias e condies pstumas de chamadas a funes co e co o co (e chamadas a mtodos, em C++), e para testar valores de retorno. e Cada utilizao de assert serve no somente como uma vericao em tempo ca a ca de execuo de uma condio, mas tambm como documentao sobre a ca ca e ca operao do programa dentro do cdigo fonte. Se eu programa contiver um ca o assert (condio) que diz a algum para ler seu cdigo fonte pelo fato de a ca e o condio obrigatriamente ter de ser verdadeira naquele ponto do programa, ca o e se a condio no verdadeira, temos a um erro no programa. Para ca a e cdigo de desempenho cr o tico, vericaes tais como a utilizao de assert co ca pode impor uma perda muito grande de desempenho. Nesses casos,voc e pode compilar seu cdigo com a macro NDEBUG denida, atravs do uso o e 43

do sinalizador -DNDEBUG na sua linha de comando de compilao. Com ca NDEBUG denida, aparces da macro assert iro ser preprocessadamente o a descartadas. O preprocessamento dessa forma uma boa idia no sentido e e de permitir fazer o uso de assert somente quando necessrio por razes de a o performace, embora que, somente com arquivos fonte de desempenho cr tico. Pelo fato de ser poss vel o descarte preprocessadamente da macro assert, garanta que qualquer expresso que voc venha a usar com assert no tenha a e a efeitos colaterais. Especicamente, voc no deve chamar funes dentro e a co de expresses assert, no deve atribuir valores a variveis e no deve usar o a a a modicadores de operao tais como ++. ca Suponhamos, por exemplo, que voc chame uma funo, fazer algumacoisa, e ca repetidamente em um lao. A funo fazer algumacoisa retorna zero em c ca caso de sucesso e no zero em caso de falha, mas voc no espera que esse a e a comportamento venha a falhar em seu programa. Voc pode ter tentado e escrever: for (i = 0; i < 100; ++i) assert (fazer_algumacoisa () == 0); Todavia, voc pode encontrar que essa vericao em tempo de execuo e ca ca impe uma grande perda de desempenho e decide mais tarde recompilar com o NDEBUG denida. Isso ir remover a chamada a assert inteiramente, de a forma que a expresso nunca ir ser avaliada e fazer algumacoisa nunca ir a a a ser chamada. Voc pode, ao invs do cdigo anterior escrever o seguinte: e e o for (i = 0; i < 100; ++i) { int status = fazer_algumacoisa (); assert (status == 0); } Outra coisa para se ter em mente que voc no deve usar assert para e e a testar entradas invlidas de usurio. Usurios no gostam quando aplicaes a a a a co simplesmente terminam abruptamente com uma mensagem de erro criptografada, mesmo em resposta a uma entrada invlida. Voc deve sempre a e vericar entradas invlidas e produzir mensagens de erro coerentes e lgicas a o em resposta a uma tal entrada invlida. Use assert somente para vericaes a co internas em tempo de execuo. ca Alguns bons lugares para usar assert so esses: a Vericao contra apontadores nulos, por exemplo, como argumentos ca vlidos a funes. A mensagem de erro gerada por assert (pointer != a co NULL), 44

Assertion pointer != ((void *)0) failed. mais informativa que a mensgem de erro que pode resultar se seu e programa simplesmente tentar acessar um apontador nulo: Segmentation fault (core dumped) Verique condies sobre valores de parmetros passados a funes. co a co Por exemplo, se uma funo deve ser chamada somente com um valor ca positivo para o parmetro qualquercoisa, use o seguinte no comeo do a c corpo da funo: ca assert (qualquercoisa > 0); Isso ir ajudar voc a detectar uso inadequado da funo, e essa prtica a e ca a tambm faz com que esteja muito claro a algum que ao ler o cdigo e e o fonte da funo ver que existe uma restrio sobre valores do parmetro. ca a ca a Evolua; use assert de forma liberal em toda a extenso de seu cdigo. a o

2.2.2

Falhas em Chamadas de Sistema

A maioria de ns originalmente aprendeu como escrever programas que exeo cutam at o nal ao longo de um caminho bem denido. Dividimos o proe grama em tarefas e subtarefas, e cada funo completa uma tarefa atravs de ca e chamadas a outras funes para executar as subtarefas correspondentes. Forco necendo entradas apropriadas, esperamos que uma funo produza a sa ca da correta e os efeitos corretos. As realidades das peas do computador e dos c programas de computador intromete-se nesse sonho perfeito. Computadores possuem recursos limitados; peas falham; muitos programas funcionam ao c mesmo tempo; usurios e programas cometem erros. Isso muitas vezes no a limite entre a aplicao e o sistema operacional que essas realidades exibem ca por si mesmas. Portanto, quando formos usar chamadas de sistema para acessar recursos, para realizar operaes de E/S, ou para outro propsito, co o e importante entender no somente o que ocorre quando a chamada acontece, a mas tambm quando e como a chamada de sistema pode falhar. Chamadas e de sistema falham de muitas formas. Por exemplo: O sistema pode extrapolar os recursos dispon veis de hardware (ou o programa excede os limites de recursos impostos pelo sistema para um programa simples). Por exemplo, o programa pode tentar alocar muita memria, escrever muito no disco, ou abrir muitos arquivos ao mesmo o tempo. 45

GNU/Linux pode bloquear uma certa chamada de sistema quando um programa tenta executar uma operao para a qual no tiver permisso. ca a a Por exemplo, um programa pode tentar escrever em um arquivo marcado como para somente leitura, acessar a memria de outro processo, o ou encerrar outro programa de usurio. a Os argumentos a uma chamada de sistema podem ser invlidos, ou a devido ao usurio fornecer entradas invlidas ou devido a um erro no a a programa. Por exemplo, o programa pode passar a outro programa um endereo invlido de memria ou um descritor de arquivo invlido c a o a para uma chamada de sistema. Ou, um programa pode tentar abrir um diretrio como um arquivo comum, ou pode passar o nome de um o arquivo comum a uma chamada de sistema que espera um diretrio. o Uma chamada de sistema falha por razes externar a um programa. o Isso acontee na maioria das vezes quando uma chamada de sistema c acessa um dispositivo. O dispositivo pode estar danicado ou pode no suportar uma operao em particular, ou talvez um disco no est a ca a a inserido no dispositivo de leitura e escrita em disco. Uma chamada de sistema pode muitas vezes ser interrompida por um evento externo, tal como a entrega de um sinal. Isso no necessaria amente indica falha externa, mas isso em resposta ` chamada de um a programa para reiniciar a chamada de sistema, se for desejvel. a Em um programa bem escrito que faz uso extensivo de chamadas de sistema, a falha de chamada de sistema causa o aparecimento de mais cdigo o devotado a detectar e manusear erros e outras circunstncias excepcionais a que o cdigo espec o co dedicado ao trabalho principal do programa.

2.2.3

Cdigos de Erro de Chamadas de Sistema o

A maioria das chamadas de sistema retorna zero se a operao terminar corca retamente, ou um valor diferente de zero caso a operao resultar em falha. ca (Muitas outras chamadas, apesar disso, possuem diferentes convees de vaco lores de retorno; por exemplo, a chamada malloc retorna um apontador nulo para indicar falha. Sempre leia a pgina de manual cuidadosamente quando a for usar uma chamada de sistema.) Embora essa informao possar suciente ca para determinar se o programa deva continuar a execuo normalmente, a ca leitura da pgina de manual provavelmente no fornece informao suciente a a ca para um recuperao satisfatria de erros. ca o 46

A maioria das chamadas de sistema usam uma varivel especial chamada era rno para armazenar informaes adicionais em caso de falha. 5 Quando uma co chamada vier a falhar, o sistema ajusta errno para um valor indicando o que aconteceu de errado. Pelo fato de todas as chamadas de sistema usarem a mesma varivel errno para armazenar informaes de erro, voc deve copiar a co e o valor para outra varivel imediatamente aps ocorrer a falha na chamada. a o A errno ir ter seu valor atual apagado e preenchido com outros valores da a prxima vez que voc zer uma chamada de sistema. o e Valores de erro so inteiros; os valores poss a veis so fornecidos pelas maa cros de prprocessamento, por conveno nomeadas em letras maisculas e e ca u iniciando com E, por exemplo, EACCES e EINVAL. Sempre use essas macros para referir-se a valores de errno em lugar de valores inteiros. Inclua o cabealho <errno.h> se voc for usar valores de errno. c e GNU/Linux fornece uma funo conveniente, strerror, que retorna uma desca crio em forma de sequncia de caracteres de um cdigo de erro que se ca e o encontra armazenado em errno, adequada para usar em mensagens de erro. Inclua o arquivo de cabealho <string.h> caso voc resolva usar a funo c e ca strerror. GNU/Linux tambm fornece perror, que mostra a descrio do erro diretae ca mente para o uxo stderr. Passe a perror uma sequncia de caracteres para e ser usada como prexo a ser mostrado antes da descrio de erro, que deve ca habitualmente incluir o nome da funo que falhou. Inclua o arquivo de ca cabealho <stdio.h> caso voc resolva usar a funo perror. c e ca O fragmento de cdigo adiante tenta abrir um arquivo; se a abertura falhar, o o cdigo mostra uma mensagem de erro e encerra a execuo do programa. o ca Note que a chamada open retorna um descritor de arquivo aberto se o operador open obtiver sucesso em sua tarefa, ou -1 se a operao falhar. ca
f d = open ( a r q u i v o d e e n t r a d a . t x t , O RDONLY ) ; i f ( f d == 1) { / A a b e r t u r a f a l h o u . M o s t r a uma menssagem d e e r r o e s a i . / f p r i n t f ( s t d e r r , e r r o ao a b r i r o a r q u i v o : %s \n , s t r e r r o r ( e r r n o ) ) ; exit (1); }

dependendo de seu programa e da natureza da chamada de sistema, a ao ca apropriada ao caso de falha pode ser mostrar uma mensagem de erro para cancelar uma operao, abortar o programa, tentar novamente, ou mesmo ca para ignorar o erro. A meno desse comportamento importante pelo fato ca e de ser necessrio incluir cdigo que manuseie todos os poss a o veis modos de falha de uma forma ou de outra. Um poss cdigo de erro que voc deve car de olho, especialmente com vel o e funes de E/S, EINTR. Algumas funes, tais como read, select, e sleep, co e co
Atualmente, por razes de trabalhar de forma segura, errno implementada como o e uma macro, mas usada como uma varivel global. e a
5

47

podem precisar de um intervalo de tempo signicativo para executar. Essas so consideradas funes de bloqueio pelo fato de a execuo do programa a co ca ser bloqueada at que a chamada seja completada. Todavia, se o programa e recebe um sinal enquanto estiver bloqueado em uma dessas chamadas, a chamada ir retornar sem completar a operao. Nesse caso, errno ajustada a ca e para EINTR. Comumente, voc ir querer chamar novamente a chamada de e a sistema que foi interrompida pelo sinal nesse caso. Adiante encontra-se um fragmento de cdigo que utiliza a chamada chown o para mudar o dono de um arquivo fornecido pelo caminho para o usurio esa pecicado atravs de user id. Se a chamada vier a falhar, o programa executa e uma ao que depende do valor de errno. Note que quando detectamos o ca que provavelmente um erro no programa ns saimos usando abort ou mane o ter (assert), o que causa a gerao de um arquivo principal. Esse arquivo ca pode ser util para depurao aps o encerramento do programa. Para outros ca o erros irrecuperveis, tais como condies de tentativas de acesso a reas de a co a memria no alocadas pelo sistema operacional ao programa em questo, o a a saimos usando a opo sair e um valor de sa no nulo em lugar de arquivo ca da a principal (core) pelo fato de que um arquivo core no vir a ser muito util. a
r v a l = chown ( path , u s e r i d , 1); i f ( r v a l != 0 ) { / Grava e r r n o p e l o f a t o d e p o d e r s e r s o b r e s c r i t o p e l a p r o x i m a chamada d e int e r r o r c o d e = errno ; / A o p e r a c a o f a l h a chown d e v e r e t o r n a r 1 em c a s o d e e r r o . / a s s e r t ( r v a l == 1); / V e r i f i c a o v a l o r d e e r r n o , e e x e c u t a a a c a o a p r o p r i a d a . / switch ( e r r o r c o d e ) { case EPERM: / P e r m i s s a o n e g a d a . / case EROFS : / PATH e s t a em um s i s t e m a d e a r q u i v o s o m e n t e l e i t u r a . / case ENAMETOOLONG: / PATH e m u i t o l o n g o . / case ENOENT: / PATH nao e x i t e . / case ENOTDIR : / Um c o m p o n e n t e d e PATH nao e h um d i r e t o r i o . / case EACCES : / Um c o m p o n e n t e d e PATH nao e s t a a c e s s i v e l . / / A l g o e s t a e r r a d o com o a r q u i v o . M o s t r e uma mensagem d e e r r o . / f p r i n t f ( s t d e r r , e r r o mudando o dono de %s : %s \n , path , s t r e r r o r ( e r r o r c o d e ) ) ; / Nao e n c e r r a o p r o g r a m a ; t a l v e z f o r n e c a o ao u s u a r i o uma c h a n c e p a r a e s c o l h e r o u t r o a r q u i v o . . . / break ; case EFAULT : / PATH contem um e n d e r e c o d e memoria abort ( ) ; s i s t e m a . /

invalido .

Isso

e h p r o v a v e l m e n t e um e r r o . /

case ENOMEM: / E x e c u t o u f o r a da memoria do k e r n e l . / f p r i n t f ( s t d e r r , %s \n , s t r e r r o r ( e r r o r c o d e ) ) ; exit (1); default : / Alguma o u t r a e r r o s de c o d i g o abort ( ) ; }; }

c o i s a , i n e s p e r a d o , c o d i g o d e e r r o . Tentamos manusear t o d o s p o s s i v e i s ; s e t i v e r m o s o m i t i d o algum , i s s o e h um e r r o ! /

os

Voc pode simplesmente usar o cdigo abaixo, que comporta-se da mesma e o forma se a chamada obtem sucesso:
r v a l = chown ( path , a s s e r t ( r v a l == 0 ) ; user \ id , 1);

48

Mas se a chamada vier a falhar, a alternativa de cdigo acima no faz o a nenhum esforo para reportar, manusear, ou para se recuperar dos erros. c Se voc usa a primeira forma, a segunda forma, ou algum meio termo entre e as duas vai depender da necessidade de seu sistema no tocante a deteco e ca recuperao de erros. ca

2.2.4

Erros e Alocao de Recursos ca

Muitas vezes, quando uma chamada de sistema falha, mais apropriado cane celar a operao atual mas no terminar o programa porque o cancelamento ca a simples pode tornar poss recuperar-se do erro. Uma forma de fazer isso vel retornar da funo em que se est no momento em que ocorreu o erro, e ca a passando um cdigo de retorno para a funo chamadora indicando o erro. o ca Caso voc decida retornar a partir do meio de uma funo, importante e ca e garantir que quaisquer recursos que tenham sido alocados com sucesso previamente na funo seja primeiramente liberado. Esses recursos podem incluir ca memria, descritores de arquivo, apontadores para arquivo, arquivos temo porrios, objetos de sincronizao, e assim por diante. De outra forma, se a ca seu programa continuar sendo executado, os recursos alocados anteriormente a ` ocorrncia da falha iro ser retirados. e a Considere, por exemplo, uma funo que faa a leitura de um arquivo ca c em um espao temporrio de armazenamento. A funo pode seguir esses c a ca passos: 1. Alocar o espao temporrio de armazenamento. c a 2. Abrir o arquivo. 3. Ler a partir do arquivo na rea temporria de armazenamento. a a 4. Fechar o arquivo. 5. Devolver o espao emporrio de armazenamento. c a Se o arquivo no existir, o Passo 2 ir falhar. Um caminho de ao a a ca pode ser retornar um apontador a partir da funo. Todavia, se o espao ca c de armazenamento temporrio j tiver sido alocado no Passo 1, existe um a a risco de perder aquela memria. Voc deve lembrar de desalocar o espao o e c temporrio de armazenamento em algum lugar com o decorrer de qualquer a uxo de controle do qual voc no venha a retornar. Se o Passo 3 vier a falhar, e a voc no somente deve desalocar o espao temporrio de armazenamento e a c a antes de retornar, mas tambm deve fechar o arquivo. e A listagem 2.6 mostra um exemplo de como voc pode escrever essa funo. e ca 49

Listagem 2.6: (readle.c) Liberando Recursos em Condies Inesperadas co


#include #include #include #include #include < f c n t l . h> < s t d l i b . h> <s y s / s t a t . h> <s y s / t y p e s . h> <u n i s t d . h> size t length )

char r e a d f r o m f i l e ( const char f i l e n a m e , { char b u f f e r ; int fd ; s s i z e t bytes read ;

/ A l l o c a t e t h e b u f f e r . / b u f f e r = ( char ) m a l l o c ( l e n g t h ) ; i f ( b u f f e r == NULL) return NULL ; / Open t h e f i l e . / f d = open ( f i l e n a m e , O RDONLY) ; i f ( f d == 1) { / o p e n f a i l e d . Deallocate buffer before returning . / free ( buffer ) ; return NULL ; } / Read t h e d a t a . / b y t e s r e a d = read ( fd , b u f f e r , l e n g t h ) ; i f ( b y t e s r e a d != l e n g t h ) { / r e a d f a i l e d . D e a l l o c a t e b u f f e r and c l o s e f d b e f o r e r e t u r n i n g . free ( buffer ) ; c l o s e ( fd ) ; return NULL ; } / E v e r y t h i n g s f i n e . C l o s e t h e f i l e and r e t u r n t h e b u f f e r . / c l o s e ( fd ) ; return b u f f e r ; }

Gnu/Linux limpa a memria alocada, limpa os arquivos abertos, e libera o a maioria de outros recursos quando um programa encerra, de forma que no necessrio desalocar espaos temporrios de armazenamento e fechar a e a c a arquivos antes de chamar exit. Voc pode precisar liberar manualmente outros recursos compartilhados, e todavia, tais como arquivos temporrios e memria comapartilhada, que poa o dem potencialmente sobreviver ao encerramento de um programa.

2.3

Escrevendo e Usando Bibliotecas

Virtualmente todos os programas so linkados usando uma ou mais bibliotea cas. Qualquer programa que usa uma funo C (tais como printf ou malloc) ca ir ser linkado incluindo a biblioteca C de rotinas que atuam em tempo de a execuo. Se seu programa tem uma interface grca de usurio (GUI), seu ca a a programa ser linkado incluindo bibliotecas que fazem janelas. Se seu proa grama usa uma base de dados, o provedor da base de dados ir fornecer a a voc bibliotecas que voc pode usar para acessar a base de dados convenientee e mente. Em cada um desses casos, voc deve decidir se ir linkar a biblioteca e a estaticamente ou dinmicamente. Se voc escolher estaticamente, seu proa e grama ir ser maior e mais pesado na hora de atualizar, mas provavelmente a fcil de desenvolver. Se voc linkar dinmicamente, seu programa ir ser a e a a 50

menor, fcil de atualizar, mas pesado para desenvolver. Essa seo explica a ca como likar de ambas as formas estaticamente e dinmicamente, examinar os a reexos dessa escolha em mais detalhes, e fornecer algumas regras prticas a de manuseiopara decidir que tipo de linkagem melhor para voc. e e

2.3.1

Agrupando Arquivos Objeto

Um agrupamento de arquivos objeto (ou biblioteca esttica) simplesmente a e vrios arquivos objeto armazenados como se fossem um arquivo unico. 6 a Quando voc fornece um agrupamento de arquivos objeto ao programa que e faz linkagem, ele procura no agrupamento de arquivos objeto pelo arquivo tipo objeto que ele precisa, extrai o referido arquivo, e anexa-o ao seu programa quase da mesma forma que seria se voc tivesse fornecido o referido e arquivo objeto diretamente. Voc pode criar uma biblioteca esttica usando o comando ar. Arquivos e a de biblioteca esttica tradicionalmente usam a extenso .a em lugar da exa a tenso .o usada por um arquivos objeto comuns. Aqui est como voc pode a a e combinar test1.o e test2.o em um arquivo simples libtest.a: % ar cr libtest.a test1.o test2.o Os sinalizadores crdizem ao ar para criar a biblioteca esttica. 7 Agora a voc pode incluir essa biblioteca esttica em seu programa usando a opo e a ca -ltest com o gcc ou com o g++, como descrito na Seo 1.2.2, Linkando ca Arquivos Objetono Cap tulo 1, Iniciando. Quando o programa de linkagem encontra uma biblioteca esttica na linha a de comando, ele procura na biblioteca esttica por todas as denies de a co s mbolo (funes ou variveis) que so referenciadas a partir dos arquivos co a a objeto que ele j tiver processado mas no ainda denido. Os arquivos objeto a a que denem aqueles s mbolos so extra a dos da biblioteca esttica e inclu a dos no executvel nal. Pelo fato de o programa linkador procurar na biblioteca a esttica ` medida que elas aparecem na linha de comando, faz sentido colocar a a a biblioteca esttica no nal da linha de comando. Por exemplo, suponhamos a que test.c contenha o cdigo na listagem 2.7 e app.c contenha o cdigo na o o listagem 2.8.
Um agrupamento de arquivos objeto grosseiramente o equivalente ao arquivo .LIB e do Windows. 7 Voc pode usar outros sinalizadores para remover um arquivo de uma biblioteca e esttica ou executar outras operaes em uma bilioteca esttica. Essas operaes so a co a co a raramente usadas mas esto documentadas na pgina de manual do textitar. a a
6

51

Listagem 2.7: (test.c) Area da Biblioteca


int f ( ) { return 3 ; }

Listagem 2.8: (app.c) Um Programa Que Utiliza as Funes da Biblioteca co Acima


extern i n t f () ; i n t main ( ) { return f ( ) ; }

Agora suponhamos que test.o seja combinado com alguns outros arquivos objetos para produzir uma bilbioteca esttica libtest.a. A seguinte linha de a comando ir falhar: a % gcc -o app -L. -ltest app.o app.o: In function main: app.o(.text+0x4): undefined reference to f collect2: ld returned 1 exit status A mensagem de erro indica que mesmo que libtest.a contenha uma denio de f, o programa de linkagem no a encontra. Isso ocorre pelo fato ca a de que a libtest.a foi pesquisada quando em primeiro lugar e antes de app.o, e naquele ponto o programa de linkagem no viu nenhuma referncia a f. a e Por outro lado, se usarmos a linha abaixo, nenhuma mensagem de erro e mostrada: % gcc -o app app.o -L. -ltest A razo que a referncia a f em app.o faz com que o programa de a e e linkagem inclua o arquivo objeto test.o contido na biblioteca esttica libtest.a. a

2.3.2

Bibliotecas Compartilhadas

Uma biblioteca compartilhada (tambm conhecida como um objeto compare tilhado, ou como uma biblioteca linkada dinamicamente) similar a uma e biblioteca esttica no sentido de que uma biblioteca dinmica um agrupaa a e mento de arquivos objeto. Todavia, existem muitas diferenas importantes.A c diferena mais fundamental que quando uma biblioteca compartilhada for c e linkada em um programa, o executvel nal no conter o cdigo que est prea a a o a sente na biblioteca compartilhada. Ao invs disso, o executvel meramente e a contm uma referncia ` biblioteca compartilhada. Se muitos programas no e e a 52

sistema forem linkados usando a mesma biblioteca compartilhada, eles iro a todos referencia a referida biblioteca compartilhada, mas nenhum deles ir a conter algum cdigo da biblioteca. Dessa forma, a biblioteca compartio e lhadapor todos os programas que foram linkados fazendo referncia a ela. e Uma segunda diferena que uma biblioteca compartilhada no meramente c e a e uma coleo de arquivos objeto, entre os quais objetos o programa de linkaca gem escolhe aquele que necessrio para satisfazer refercias no denidas e a e a no cdigo principal do programa que est sendo linkado. Ao invs disso, os o a e arquivos objetos que compes a biblioteca compartilhada esto combinados o a dentro de um arquivo objeto simples de forma que um programa que tiver sido linkado referenciando uma biblioteca compartilhada sempre inclua todo o cdigo presente na biblioteca, em lugar de apenas aquelas pores que foo co rem necessrias. Para criar uma bibioteca compartilhada, voc deve compilar a e os objetos que iro compor a biblioteca usando a opo -fPIC no compilador, a ca da seguinte forma: % gcc -c -fPIC test1.c A opo -fPIC 8 diz ao compilador que voc estar usando test1.o como ca e a parte de um objeto compartilhado.
Cdigo Independente da Posio - (PIC) o ca PIC habilita o suporte a cdigo independente da posio. As funes em o ca co uma biblioteca compartilhada podem ser chamadas em diferentes endereos c em diferentes programas, de forma que o cdigo no objeto compartilhado no o a ca dependente do endereo (ou posio) a partir do qual chamado. Essa c ca e considerao no tem impacto sobre voc, como programador, exceto que voc ca a e e deve lembrar-se de usar o sinalizador -fPIC quando estiver compilando algum cdigo que ir ser usado em uma biblioteca compartilhada. o a

Ento voc combina os arquivos objetos dentro de uma biblioteca coma e partilhada, como segue: % gcc -shared -fPIC -o libtest.so test1.o test2.o A opo -shared diz ao programa de linkagem produzir uma biblioteca ca compartilhada em lugar de um arquivo executvel comum. As bibliotecas a compartilhadas usam a extenso .so, que usada para objeto compartilhado. a e Da mesma forma que nas bibliotecas estticas, o nome sempre comea com a c lib para indicar que o arquivo uma biblioteca. e A linkagem fazendo referncia a uma biblioteca compartilhada da mesma e e
8

Position-Independent Code

53

forma que a linkagem referenciando uma biblioteca esttica. Por exemplo, a a linha abaixo ir fazer a linkagem referenciando libtest.so se libtest.so esa tiver no diretrio atual, ou em um dos diretrios de busca de bibliotecas o o padronizados do sistema: % gcc -o app app.o -L. -ltest Suponhamos agora que ambas as biblioteca libtest.a e libtest.so estejam dispon veis. Ento o programa de linkagem deve uma das bibliotecas e no a a outras. O programa de linkagem busca cada diretrio (primeiramente aqueles o especicados com a opo -L, e ento aqueles nos diretrios pardronizados ca a o de bibliotecas do sistema). Quando o programa de linkagem encontra um diretrio que contenha qualquer uma ou libtest.a ou libtest.so, o programa o de linkagem para a busca nos diretrios. Se somente uma das duas variantes o estiver presente no diretrio, o programa de linkagem escolhe aquela vario ante que foi encontrada em primeiro lugar. De outra forma, o programa de linkagem escolhe a verso compartilhada, a menos que voc explicitamente a e instrua ao programa de linkagem para proceder de outra forma. Voc pode e usar a opo -static para exigir bibliotecas estticas. Por exemplo, a linha de ca a comando adiante ir usar a biblioteca esttica libtest.a, mesmo se a biblioteca a a compartilhada libtest.so estiver tambm presente: e % gcc -static -o app app.o -L. -ltest O comando ldd mostra as bibliotecas compartilhadas que so referencia adas dentro de um executvel. Essas bibliotecas precisam estar dispon a veis quando o executvel for chamado. Note que o comando ldd ir listar uma a a biblioteca adicional chamada ld-linux.so, que uma parte do mecanismo de e linkagem dinmica do GNU/Linux. a Usando a Varivel de Ambiente LD LIBRARY PATH Quando voc a e zer a linkagem de um programa referenciando uma biblioteca compartilhada, o programa de linkagem no coloca o caminho completo da locaa lizao da biblioteca compartilhada no executvel resultante. Ao invs disso, ca a e o programa de linkagem coloca apenas o nome da biblioteca compartilhada. Quando o programa for executado, o sistema busca pela biblioteca compartilhada e a torna dispon para ser usada pelo programa que precisa dela. vel O sistema busca somente no /lib e no /usr/lib por padro. Se uma biblioa teca compartilhada que for referenciada por seu programa executvel estiver a instalada fora daqueles diretrios, essa biblioteca compartilhada no ir ser o a a encontrada, e o sistema ir se recusar a executar o programa. a Uma soluo para esse problema usar a opo -Wl,-rpath ao usar o proca e ca grama de linkagem. Suponhamos que voc use o seguinte: e 54

% gcc -o app app.o -L. -ltest -Wl,-rpath,/usr/local/lib

Ento, quando o programa app estiver executando, o sistema ir buscar a a em /usr/local/lib por qualquer biblioteca compartilhada requerida. Outra soluo para esse problema ajustar a varivel de ambiente LD LIca e a BRARY PATH na hora da execuo do programa de linkagem. Da mesma ca forma que a varivel de ambiente PATH, LD LIBRARY PATH uma lista de a e diretrios separados por ponto e v o rgula. Por exemplo, se LD LIBRARY PATH for /usr/local/lib:/opt/lib, ento /usr/local/lib e /opt/lib sero buscaa a dos antes dos diretrios padro /lib e /usr/lib. Voc deve tambm notar que o a e e se voc tiver LD LIBRARY PATH, o programa de linkagem ir buscar os e a diretrios fornecidos l adicionalmente aos diretrios fornecidos com a opo o a o ca 9 -L quando estiver construindo um executvel. a

2.3.3

Bibliotecas Padronizadas

Mesmo se voc no especicar qualquer bibliotecas durante a fase de line a kagem, o seu programa certamente usa uma biblioteca compartilhada. Isso acontece pelo fato de GCC automaticamente fazer a linkagem usando a biblioteca C padro, a libc, mesmo sem voc pedir. As funes matemticas a e co a da biblioteca C padro no esto inclu a a a das na libc; ao invs disso, as funes e co matemticas constituem uma biblioteca separada, a libm, a qual voc precisa a e especicar explicitamente. Por exemplo, para compilar e fazer a linkagem do programa compute.c que utiliza funes trigonomtricas tais como sin e cos, co e voc deve chamar o seguinte cdigo: e o % gcc -o compute compute.c -lm Se escrever um programa em C++ e zer a linkagem dele usando os comandos c++ ou g++, voc ir tambm usar a biblioteca padro C++, e a e a libstdc++, automaticamente.

2.3.4

Dependncia de uma Biblioteca e

Uma biblioteca ir muitas vezes depender de outra biblioteca . Por exemplo, a muitos sistemas GNU/Linux incluem a libti, uma biblioteca que contm e funes para leitura e escrita de arquivos de imagem no formato TIFF. Essa co biblioteca, por sua vez, utiliza as bibliotecas libjpeg (rotinas de imagens no
Voc pode ver uma referncia a LD RUN PATH em alguma documentao na intere e ca net. No acredite no que voc l; essa varivel atualmente no faz nada em GNU/Linux. a e e a a
9

55

formato JPEG) e libz (rotinas de compresso). A listagem 2.9 mostra um pea queno programa que usa a biblioteca libti para abrir um arquivo de imagem no formato TIFF. Listagem 2.9: (titest.c) Usando a libti
#include <s t d i o . h> #include < t i f f i o . h> i n t main ( i n t a r g c , char a r g v ) { TIFF t i f f ; t i f f = TIFFOpen ( a r g v [ 1 ] , r ) ; TIFFClose ( t i f f ) ; return 0 ; }

Grave esse arquivo fonte como titest.c. Para compilar esse programa e fazer a linkagem referenciando a libti, especique a opo -lti na sua linha ca de linkagem: % gcc -o tifftest tifftest.c -ltiff Por padro, o comando acima ir selecionar a biblioteca compartilhada a a pela verso da libti, encontrada em /usr/lib/libti.so. Pelo fato de libti a utilizar libjpeg e libz, uma verso de biblioteca compartilhada dessas duas a e tambm puxada (uma biblioteca compartilhada pode tambm apontar para e e outra biblioteca compartilhada da qual depende). Para vericar isso, use o comando ldd : % ldd tifftest linux-gate.so.1 => (0xffffe000) /lib/libsafe.so.2 (0xb7f58000) libtiff.so.3 => /usr/lib/libtiff.so.3 (0xb7ee6000) libc.so.6 => /lib/libc.so.6 (0xb7d9a000) libdl.so.2 => /lib/libdl.so.2 (0xb7d96000) libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0xb7d76000) libz.so.1 => /usr/lib/libz.so.1 (0xb7d62000) libm.so.6 => /lib/libm.so.6 (0xb7d3c000) /lib/ld-linux.so.2 (0xb7f5f000) Bibliotecas estticas, por outro lado, no podem apontar para outras a a biblioteca. Se voc decidir fazer a linkagem com a verso esttica da libti e a a especicando a opo -static na sua linha de comando, voc ir encontrar ca e a s mbolos no resolvidos: a
% gcc -static -o tifftest tifftest.c -ltiff /usr/lib/.../libtiff.a(tif_aux.o): In function TIFFVGetFieldDefaulted: (.text+0x621): undefined reference to pow /usr/lib/.../libtiff.a(tif_jpeg.o): In function TIFFjpeg_data_src: (.text+0x189): undefined reference to jpeg_resync_to_restart /usr/lib/.../libtiff.a(tif_jpeg.o): In function TIFFjpeg_destroy: ...

56

Para fazer a linkagem desse programa estaticamente, voc deve especicar e as outras duas bibliotecas explicitamente: % gcc -static -o tifftest tifftest.c -ltiff -ljpeg -lz Ocasionalmente, duas bibliotecas iro ser mutuamente dependentes. Em a outras palavras, a primeira biblioteca esttica ir referenciar s a a mbolos na segunda biblioteca esttica, e vice versa. Essa situao geralmente provea ca e niente de um planejamento falho, mas aparece ocasionalmente. Nesses casos, voc pode repetir uma biblioteca multiplas vezes na linha de comando. O e programa de linkagem ir refazer a procura na biblioteca cada vez que isso a ocorrer. Por exemplo, a linha adiante ir fazer com que libqqcoisa.a seja a procurada multiplas vezes: % gcc -o app app.o -lqqcoisa -loutracoisa -lqqcoisa De forma que, mesmo se libqqcoisa.a referencie s mbolos em liboutracoisa.a, e vice versa, o programa ir ser linkado com sucesso. a

2.3.5

Prs e Contras o

Agora que voc sabe tudo sobre bibliotecas estticas e bibliotecas compare a tilhadas, voc est provavelmente se perguntando qual usar. Existe umas e e poucas consideraoes maiores para ter em mente. c Uma grande vantagem de uma biblioteca compartilhada que essa biblie oteca compartilhada economiza espao no sistema onde o programa estiver c instalado. Se voc estiver instalando 10 programas, e eles todos fazem uso e da mesma biblioteca compartilhada, ento voc libera uma grande quantia e dade de espao usando uma biblioteca compartilhada. Se voc tiver usado c e biblioteca esttica em substituio ` compatilhada, a biblioteca est inclu a ca a a da em todos os 10 programas repetidamente. Ento, usando bibliotecas coma partilhadas libera espao em disco. As bibliotecas compartilhadas tambm c e reduzem tempos cpia e libera recursos de coneco se seu programa est o ca a sendo copiado a partir da web. Uma vantagem relacionada `s bibliotecas a compartilhadas que o usurios podem escolher entre atualizar as biblioe a tecas com ou sem atualizar todos os programas que dependem delas. Por exemplo, suponha que voc produza uma biblioteca compartilhada que gee rencia coneces HTTP. Muitos programas podem depender dessa biblioteca. co Se voc encontrar um erro nessa biblioteca, voc pode atualizar a biblioteca. e e instantaneamente, todos os programas que dependerem da biblioteca iro ser a corrigidos; voc no ter que refazer a linkagem de todos os programas que e a a 57

seria o caminho adotado caso se estivesse usando a linkagem esttica. As vana tagem acima fariam voc pensar em usar sempre a biblioteca compartilhada. e Todavia, razes substanciais existem para o uso da biblioteca esttica em o a lugar da compartilhada. O fato que uma atualizao com o uso de uma bica blioteca compartilhada afeta todos os programas que dependem dela pode ser uma desvantagem. Por exemplo, se voc estiver desenvolvendo um programa e de alta disponibilidade, voc pode preferir fazer a linkagem referenciando e uma biblioteca esttica de forma que uma atualizao de bibliotecas coma ca partilhadas no sistema no afete seu programa. (De outra forma, usurios a a podem atualizar a biblioteca compartilhada, afetando seu programa que foi compilado referenciando bibliotecas compartilhadas e causarem uma parada no programa, e ento chamar sua linha de suporte ao usurio, censurando a a voc!) Se voc est indo pelo caminho de no instalar suas biblioteca no /lib e e a a ou no /usr/lib, voc deve denitivamente pensar duas vezes sobre usar uma e biblioteca compartilhada. (Voc no espera instalar suas bibliotecas naquee a les diretrios se voc no esperar que usurios que iro instalar seu software o e a a a possuam privilgio de administrador.) Particularmente, a opo/artif de e ca cio compilao -Wl,-rpath no ir servir de nada se voc no sabe onde as biblica a a e a otecas esto indo parar. E pedindo a seus usurios para ajustar a varivel a a a de ambiente LD LIBRARY PATH signica uma tarefa extra para eles. Pelo fato de cada usurio ter de fazer isso individualmente, isso uma substancial a e e adicional carga de responsabilidade. Voc ir ter que pesar essas vantagens e a e desvantagens para cada programa que voc vier a distribuir. e

2.3.6

Carregamento e Descarregamento Dinmico a

Algumas vezes voc pode desejar carregar algum cdigo em tempo de execue o ca o sem explicitamente fazer a linkagem daquele cdigo. Por exemplo, cono sidere uma aplicao que suporta mdulos do tipo plug-in, tal como um ca o navegador internet. O navegador permite a desenvolvedores externos ao projeto criar acessrios para fornecer ao navegador funcionalidades adicionais. o Os desenvolvedores externos criam bibliotecas compartilhadas e as colocam em uma localizao conhecida pelo navegador. O navegador ento autoca a maticamente carrega o cdigo nessas bibliotecas. Essa funcionalidade est o a dispon em ambiente GNU/Linux atravs do uso da funo dlopen. Voc j vel e ca e a pode ter aberto uma biblioteca compartilhada chamada libtest.so chamando a funo dlopen da forma abaixo: ca dlopen ("libtest.so", RTLD_LAZY) (O segundo parmetro um sinalizador que indica como associar s a e mbolos na biblioteca compartilhada. Voc pode consultar as pginas de manual inse a 58

taladas no seu sistema sobre dlopen se voc desejar mais informao, mas e ca RTLD LAZY comumente a opo que voc deseja.) Para usar funes de e ca e co carregamento dinmico, inclua o arquivo de cabealho <dlfcn.h> e faa a a c c linkagem com a opo -ldl para selecionar a biblioteca libdl. ca O valor de retorno dessa funo um void * que usado como um manipuca e e lador para a biblioteca compartilhada. Voc pode passar esse valor para a e funo dlsym para obter o endereo de uma funo que tiver sido chamada ca c ca com a biblioteca compartilhada. Por exemplo, se libtest.so dene uma funo ca chamada minha funcao, voc pode ter chamado a minha funcao como segue: e void* manipulador = dlopen ("libtest.so", RTLD_LAZY); void (*test)() = dlsym (manipulador, "minha_funcao"); (*test)(); dlclose (manipulador); A funo dlsym pode tambm ser usada para obter um apontador para ca e uma varivel esttica na biblioteca compartilhada. a a Ambas as funes dlopen e dlsym retornam NULL se no obtiverem suco a cesso. no evento descrito acima, voc pode chamar a funo dlerror (sem e ca parmetros) para obter uma mensagem de erro em formato leg aos hua vel manos descrevendo o problema. A funo dlclose descarrega a biblioteca compartilhada. Tecnicamente, a ca funo dlopen carrega a biblioteca somente se a referida biblioteca j no ca a a tiver sido chamada anteriormente. Se a biblioteca j tiver sido chamada, a dlopen simplesmente incrementa o contador de referncia da biblioteca. Sie milarmente, a funo dlclose decrementa o contador de referncia e ento ca e a descarrega a biblioteca somente se o contador de referncia tiver alcanado e c o valor zero. Se voc est escrevendo um cdigo em sua biblioteca compartilhada em C++, e a o voc ir provavelmente desejar declarar aquelas funes e variveis que voc e a co a e planeja acessar a partir de algum lugar com o especicador de linkagem extern C. Por exemplos, se a funo C++ minha funcao estiver em uma ca biblioteca compartilhada e voc desejar acessar essa funo com a funo e ca ca dlsym, voc deve declarar a minha funcao como segue: e extern "C" void minha_funcao (); Isso evita que o compilador C++ desgure o nome da funo, pelo fato ca de o compilador C++ poder mudar o nome da funo de minha funo para ca ca um diferente, um nome mais engraado ao olhar que expresse informaes c co extras sobre a funo. Um compilador C no ir desgurar nomes; os nomes ca a a iro ser usados qualquer que seja o nome que voc fornea para sua funo a e c ca ou varivel. a 59

60

Cap tulo 3 Processos


UMA INSTANCIA EXECUTANDO UM PROGRAMA CHAMA-SE UM PROCESSO. Se voc tem duas janelas de terminal exibindo informaes em e co sua tela, ento voc est provavelmente executando o mesmo programa de a e a terminal duas vezes e voc tem dois processos de terminal. Cada janela de e terminal est provavelmente executando um shell; cada shell sendo execua tado um outro processo. Quando voc chama um comando em um shell, o e e programa correspondente executado em um novo processo; o processo de e shell continua quando o processo do comando chamado se completar. Programadores avanados muitas vezes utilizam muitos processos em coc operao em uma aplicao simples para habilitar a capacidade da aplicao ca ca ca de executar mais de uma coisa ao mesmo tempo, para incrementar robustez da aplicao, e para fazer uso dos programas j existentes. ca a A maioria das funes de manipulao de processos descritas nesse cap co ca tulo so similares a aquelas em outros sistemas UNIX. A maioria declarada no a e arquivo de cabealho <unistd.h>; verique a pgina de manual de cada c a funo para ter certeza. ca

3.1

Visualizando Processos

Sempre que voc senta em seu computador para us-lo, exitem processos em e a atividade. Todos os programas sendo executados usam um ou mais processos. Vamos iniciar dando uma olhada nos processos j existentes em seu a computador. 61

3.1.1

Identicadores de Processos

Cada processo em um sistema GNU/Linux identicado por seu unico e nmero de identicao, algumas vezes referenciado como pid. Identicadou ca res de Processos so nmeros inteiros de 16-bit que so atribuidos sequnciala u a e mente pelo kernel GNU/Linux a cada vez que um novo processo criado. e Todo processo tem um processo pai (exceto o processo init, descrito na Seo 3.3.4, Processos do Tipo Zumbi). Dessa forma, voc pode pensar de ca e processos em um sistema GNU/Linux como organizados em uma rvore, com a o processo init sendo a ra principal que originou toda a rvore. A identiz a cao do processo pai, ou ppid, simplesmente o nmero de identicao ca e u ca do processo pai. Quando zermos referncia ao nmero de identicao de e u ca um processo em um programa em C ou em C++, sempre usa-se a denio ca de tipo pid t, que feita em <sys/types.h>. Um programa pode obter o e nmero de identicao do processo que o est executando com a chamada u ca a de sistema getpid(), e o programa tambm pode obter o nmero de identie u cao de processo do processo que o originou com a chamada de sistema ca getppid(). Por exemplo, o programa na listagem 3.1 mostra o o nmero de u identicao do processo que o est executando e o nmero de identicao ca a u ca do processo que o originou. Listagem 3.1: ( print-pid.c) Mostrando o ID do Processo
1 2 3 4 5 6 7 8 9 #include <s t d i o . h> #include <u n i s t d . h> i n t main { printf printf return } () ( The p r o c e s s i d i s %d\n , ( i n t ) g e t p i d ( ) ) ; ( The p a r e n t p r o c e s s i d i s %d\n , ( i n t ) g e t p p i d 0;

() ) ;

Observe que se voc chamar esse programa muitas vezes, um ID diferente e de processo ser reportado a cada vez que voc chamar o programa pelo a e fato de cada chamada estar em um novo processo. Todavia, se voc chamar e o programa vrias vezes a partir da mesma janela de shell, o nmero de a u identicao do processo que o originou (isto , a nmero de identicao do ca e u ca processo do shell) o mesmo. e

3.1.2

Visualizando os Processos Ativos

O comando ps mostra os processos que estiverem sendo executados sobre seu sistema. A verso GNU/Linux do ps tem muitas opes pelo fato de a co a verso GNU/Linux tenta ser compat com as verses do ps de muitas a vel o 62

outras variantes UNIXs. Essas opes controlam quais processos so listados co a e qual informao sobre cada processo dever ser mostrada. ca a Por padro, chamando ps mostra os processos controlados pelo terminal a ou janela de terminal na qual o comando ps for chamado. Por exemplo: % ps PID TTY 21693 pts/8 21694 pts/8

TIME CMD 00:00:00 bash 00:00:00 ps

Essa chamada de ps mostra dois processos. O primeiro, o bash, um shell e executando sobre o referido terminal. O segundo a instncia de execuo e a ca do programa ps propriamente dito. A primeira coluna, rotulada PID, mostra o nmero de identicao de cada processo listado na sa do comando. u ca da Para uma olhada mais detalhada em o que est sendo executado no seu a sistema GNU/Linux, use o seguinte: % ps -e -o pid,ppid,command A opo -e instrui o ps a mostrar todos os processos sendo executados no ca sistema. A opo -o pid,ppid,command diz ao ps qual informao mostrar ca ca sobre cada processo no caso acima, o ID do processo, o ID do processo pai, e o comando sendo executado no referido processo.
Formatos de Sa do ps da Com a opo -o fornecida ao comando ps, voc especica a informao soca e ca bre o processo que voc deseja na sa no formato de uma lista separada e da por v rgulas. por exemplo, ps -o pid,user, start time,command mostra o ID do processo, o nome do usurio dono do processo, o tempo decorrido desde a quando o processo comeou, e o comando que est executando o processo. c a Veja a pgina de manual do comando ps para a lista completa dos cdigos a o de campo. Voc pode usar as opes -f (lista completa), -l (lista longa), ou e co -j (lista de tarefas) ao invs da opo -o acima e usar esses trs diferentes e ca e formatos predenidos de listagem (completa, longa ou de tarefas).

Aqui est algumas linhas iniciais e nais de sa so comando ps em meu a da sistema. Voc pode ver diferentes sa e das, dependendo do que estiver sendo executado em seu sistema. % ps -e -o pid,ppid,command PID PPID COMMAND 63

1 2 3

0 init [5] 1 [kflushd] 1 [kupdate]

... 21725 21693 xterm 21727 21725 bash 21728 21727 ps -e -o pid,ppid,command Note que o ID do processo pai do comando ps, 21727, o ID do bash, o e shell a partir do qual chamou-se o ps. O processo pai do bash por sua vez e o de nmero 21725, o ID do processo do programa xterm no qual o shell est u a sendo executado.

3.1.3

Encerrando um Processo

Voc pode encerrar um processo que est sendo executado com o comando e a kill. Simplesmente especicando na linha de comando o ID do processo a ser encerrado. O comando kill trabalha enviando ao processo um SIGTERM, ou sinal de encerramento.1 Isso faz com que o processo encerre, a menos que o programa em execuo explicitamente manipule ou mascare o sinal SIGTERM. Sinais ca so descritos na Seo 3.3, Sinais. a ca

3.2

Criando Processos

Duas tcnicas so usadas para criar um novo processo. A primeira relae a e tivamente simples mas deve ser usada de forma bem comedida e econmica o pelo fato de ser ineciente e de ter considerveis riscos de segurana. A sea c gunda tcnica mais complexa mas fornece grande exibilidade, rapidez, e e e segurana. c

3.2.1

Usando system

A funo system na biblioteca C padro fornece um caminho fcil para execa a a cutar um comando dentro de um programa, principalmente se o comando tiver sido digitado dentro de um shell. De fato, a funo system cria um subca processo que executa o shell Bourne padro (/bin/sh) e repassa o comando a
Voc pode tambm usar o comando kill para enviar outros sinais a um processo. Isso e e descrito na Seo 3.3.1, Encerramento de Processo. e ca
1

64

para aquele shell para execuo. Por exemplo, o programa na Listagem 3.2 ca chama o comando ls para mostrar o contedo do diretrio ra como se voc u o z, e digitasse ls -l / dentro de um shell. Listagem 3.2: (system.c) Usando uma chamada ` funo system a ca
1 2 3 4 5 6 7 8 #include < s t d l i b . h> i n t main ( ) { int r e t u r n v a l u e ; r e t u r n v a l u e = system ( l s l / ) ; return r e t u r n v a l u e ; }

A funo system retorna a condio de sa do comando do shell. Se o ca ca da shell propriamente dito no puder ser executado, a funo system retorna o a ca cdigo 127; se outro erro ocorrer, a funo system retorna -1. o ca Pelo fato de a funo system usar um shell para chamar seu comando, ela ca d margens a recursos, limitaes, e a falhas de segurana do shell de seu sisa co c tema. Voc no pode saber seguramente sobre a disponibilidade de qualquer e a verso em particular do shell Bourne. Em muitos sistemas UNIX, /bin/sh a e uma ligao simblica para outro shell. Por exemplo, na maioria dos sisteca o mas GNU/Linux, o /bin/sh aponta para o bash (o Bourne-Again SHell ), e diferentes distribuies GNU/Linux utilizam diferentes verses do bash. Chaco o mando um programa com privilgios de administrador com a funo system, e ca pode exemplo, pode ter diferentes resultados sob diferentes sistemas GNU/Linux. Devido ao que foi aqui exposto, prefer usar o mtodo fork and e vel e exec (bifurcar e executar) para criar processos.

3.2.2

Usando bifurcar e executar

A API 2 do DOS e do Windows possuem a fam spawn de funes. Essas lia co funes recebem como argumento o nome de um programa para executar e co criam uma nova intncia de processo daquele programa. O GNU/Linux no a a contm somente uma funo que faz tudo isso de uma vez s. Ao invs disso, e ca o e fornece uma funo, a funo fork, que cria um processo lho que uma cpia ca ca e o exata de seu processo pai. GNU/Linux fornece outro conjunto de funes, a co fam das funes exec, que faz com que um processo em particular no mais lia co a seja uma instncia de um programa e ao invs disso torne-se uma instncia de a e a outro programa. Para criar um novo processo, voc primeiramente deve usar e a funo fork para fazer uma cpia do processo atual que est executando ca o a
2

Application Programming Interface

65

seu programa. A seguir voc usa a funo exec para transformar um desses e ca dois processos iguais em uma instncia do programa que voc deseja criar. a e Chamando a funo fork Quando um programa chama a funo fork, ca ca um processo clone do processo que fez a chamada, chamado processo lho, criado. O processo pai continua executando o programa na instruo e ca imediatamente aps a instruo que chamou a funo fork. O processo lho, o ca ca tambm, executa o mesmo programa a partir da mesma posio de instruo. e ca ca Como fazer para os dois processos diferirem? Primeiramente, o processo lho um novo processo e portanto tem um novo ID de processo, distinto e do ID de seu processo pai. Um caminho para um programa distinguir se ele mesmo est em um processo pai ou em um processo lho chamar a funo a e ca getpid da biblioteca C padro. Todavia, a funo fork fornece diferentes vaa ca lores de retorno quando chamada a partir de um processo pai ou a partir de um processo lho - um processo entrana chamada a fork, dois processos saemcom diferentes valores de retorno. O valor de retorno no processo pai o ID de processo do processo lho. O valor de retorno no processo lho e zero. Pelo fato de nenhum processo mesmo ter um ID de processo com o e valor zero, isso torna fcil para o programa distinguir se est sendo executado a a como o processo pai ou processo lho. A listagem 3.3 um exemplo de utilizao da funo fork para duplicar e ca ca o processo de um programa. Note que o primeiro bloco da declarao if ca e executado somente no processo pai, enquando a clusula else executada no a e processo lho. Listagem 3.3: ( fork.c) Usando fork para Duplicar o Processo de um Programa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <s t d i o . h> #include <s y s / t y p e s . h> #include <u n i s t d . h> i n t main ( ) { pid t child pid ; printf ( t h e main program p r o c e s s id i s %d\n , ( i n t ) getpid () ) ;

child pid = fork () ; i f ( c h i l d p i d != 0 ) { p r i n t f ( t h i s i s t h e p a r e n t p r o c e s s , w i t h i d %d\n , ( i n t ) g e t p i d ( ) ) ; p r i n t f ( t h e c h i l d s p r o c e s s i d i s %d\n , ( i n t ) c h i l d p i d ) ; } else p r i n t f ( t h i s i s t h e c h i l d p r o c e s s , w i t h i d %d\n , ( i n t ) g e t p i d ( ) ) ; return 0 ; }

66

Usando a Fam exec As funes exec substituem o programa que est lia co a sendo executado em um processo por outro programa. Quando um programa chama uma funo exec, o processo que abriga a chamada feita ` funo exec ca a ca imediatamente cessa de executar o programa atual e inicia a execuo de um ca novo programa a partir do in cio desse mesmo novo programa, assumindo que a chamada ` funo exec tenha sido executada com sucesso. a ca Dentro da fam de funes exec, existem funes que variam de forma lia co co muito pequena na parte que se refere a compatibilidade e no que se refere ` a maneira de serem chamadas. Funes que possuem a letra pem seus nomes (execvp e execlp) aceico tam um nome de programa e procuram por um programa que tenha o nome recebido no atual caminho de execuo; funes que no contiveca co a rem o pno nome devem receber o caminho completo de localizao ca do programa a ser executado. Funes que possuem a letra vem seus nome (execv, execvp, e execve) co aceitam a lista de argumentos para o novo programa como um vetor terminado pelo caractere NULL de apontadores para sequncias de cae ractere. Funes que contiverem a letra l(execl, execlp, e execle) aceico tam a lista de argumentos usando o mecanismo varargs da linguagem C. 3 As funes que possuem a letra eem seus nomes (execve e execle) co aceitam um argumento adicional, um vetor de variveis de ambiente. a O argumento deve ser um vetor de apontadores para sequncia de cae racteres terminado pelo caractere NULL. Cada sequncias de caractere e deve ser da forma VARIAVEL=valor. Pelo fato de a funo exec substituir o programa chamado por outro, ela ca nunca retorna a menos que um erro ocorra. A lista de argumentos passada ao programa anloga aos argumentos de e a linha comando que voc especica a um programa quando voc o executa e e a partir de um shell. Eles esto disponiveis atravs dos parmetros argc a e a e de argv passados ` funo main. Lembre-se, quando um programa for a ca chamado a partir de um shell, o shell ajusta o primeiro elemento da lista de argumentos (argv[0] ) para o nome do programa, o segundo elemento da lista
Veja http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library toc.html #SEC472 e tambm http://gcc.gnu.org/onlinedocs/gccint/Varargs.html. e
3

67

de argumentos (argv[1] ) para o primeiro argumento da linha de comando, e assim por diante. Quando voc usar uma funo exec em seu programa, e ca voc, tambm, deve passar o nome da funo como o primeiro elemento da e e ca lista de argumentos.

Usando fork e exec Juntas Um modelo comum para executar um subprograma dentro de um programa primeiramente bifurcar o processo e ento e a executar o subprograma. Isso permite que o programa que fez a chamada continue a execuo no processo pai enquanto o mesmo programa que fez a ca chamada substitu pelo subprograma no processo lho. e do

O programa na Listagem 3.4, da mesma forma que a Listagem 3.2, mostra o contedo do diretrio ra usando o comando ls. Diferindo do exemplo u o z anterior, de outra forma, a Listagem 3.4 chama o comando ls diretamente, passando ao ls os argumentos de linha de comando -le /ao invs de chamar e o ls a partir de um shell. 68

Listagem 3.4: ( fork-exec.c) Usando fork e exec Juntas


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include #include #include #include <s t d i o . h> < s t d l i b . h> <s y s / t y p e s . h> <u n i s t d . h>

/ Spawn a c h i l d p r o c e s s r u n n i n g a new p r o g r a m . PROGRAM i s t h e name o f t h e program t o run ; t h e p a t h w i l l be s e a r c h e d f o r t h i s program . ARG LIST i s a NULL e r m i n a t e d l i s t o f c h a r a c t e r s t r i n g s t o b e t p a s s e d as t h e program s argument l i s t . Returns the pr ocess id of t h e spawned p r o c e s s . / i n t spawn ( char program , char { pid t child pid ; arg list )

/ D u p l i c a t e t h i s p r o c e s s . / child pid = fork () ; i f ( c h i l d p i d != 0 ) / T h i s i s t h e p a r e n t p r o c e s s . / return c h i l d p i d ; else { / Now e x e c u t e PROGRAM, s e a r c h i n g f o r i t i n t h e p a t h . / e x e c v p ( program , a r g l i s t ) ; / The e x e c v p f u n c t i o n r e t u r n s o n l y i f an e r r o r o c c u r s . / f p r i n t f ( s t d e r r , an e r r o r o c c u r r e d i n e x e c v p \n ) ; abort () ; } } i n t main ( ) { / The a r g u m e n t l i s t t o p a s s t o t h e l s command . / char a r g l i s t [ ] = { ls , / a r g v [ 0 ] , t h e name o f t h e p r o g r a m . / l , / , NULL / The a r g u m e n t l i s t must end w i t h a NULL . }; / Spawn a c h i l d p r o c e s s r u n n i n g t h e l s command . returned child process id . / spawn ( l s , a r g l i s t ) ; printf ( done w i t h main program \n ) ;

Ignore

the

return 0 ; }

3.2.3

Agendamento de Processo

GNU/Linux faz o agendamento dos processos pai e processos lho independentemente; no existe garantias de qual dos dois ir ser executado em pria a meiro lugar, ou quanto tempo de execuo previamente ir decorrer antes de ca a GNU/Linux interromp-lo e liberar o ciclo de processamento para o outro e processo (ou para algum outro processo do sistema que no os processos pai a e lho aqui citados) ser executado. Em particular, nenhuma parte, alguma parte, ou todo o processo do comando ls pode executar em um processo lho antes de o processo pai que o criou ser encerrado.4 GNU/Linux promete que cada processo ir ser executado em algum momento - nenhum processo ir a a
Um mtodo para denir a ordem de execuo de dois processos apresentado na e ca e seo 3.3.2, Esperando pelo Encerramento de um Processo. ca
4

69

ser totalmente discriminado na distribuio dos recursos de execuo.5 ca ca Voc pode especicar que um processo menos importante e deve ree e ceber uma prioridades mais baixa atribuindo a esse processo um valor alto de gentileza. Por padro, todo processo recebe um valor de gentileza zero. a Um valor de gentileza mais alto signica que o processo recebe uma menor prioridade de execuo; de modo contrrio, um processo com um baixo (isto ca a , negativo) valor de gentileza recebe mais tempo de execuo. e ca Para executar um programa com um valor de gentileza no nulo, use o a comando nice, especicando o valor de gentileza com a opo -n. Por exemca plo, adiante mostra-se como voc pode chamar o comando sort entrada.txt e > saida.txt, que corresponde a uma longa operao de ordenao, como ca ca reduzida prioridade de forma que essa operao de ordenao no torne o ca ca a sistema muito lento: % nice -n 10 sort input.txt > output.txt Voc pode usar o comando renice para modicar o n de gentileza de e vel um processo sendo executado a partir da linha de comando. Para modicar o n de gentileza de um processo que est em execuo vel a ca a partir de outro programa, use a funo nice. O argumento dessa funo ca ca e um valor de incremento, que adicionado ao n de gentileza do do proe vel cesso est executando o programa cujo n de gentileza se deseja mudar. a vel Lembre-se que um valor positivo aumenta o valor de gentileza e dessa forma reduz a prioridade de execuo de um processo. ca Note que somente um processo com privilgios de usurio root pode exee a cutar um ou outro processo com um valor de gentileza negativo ou reduzir o valor de gentileza de um processo que est sendo executado. Isso signica a que voc pode especicar valores negativos para os comando nice e renice soe mente quando est acessando o computador como super-usurio, e somente a a um processo executando com privilgios de super-usurio pode enviar um e a valor negativo para a funo nice da glibc. Esse comportamento previne que ca usurios comuns consigam prioriade de execuo em nome de outros usurios a ca a que no o seu prprio usando o sistema. a o
O autor refere-se aos algor tmos de escalonamento. kernel.org/doc/#5.1
5

Veja tambm http://www. e

70

3.3

Sinais

Sinais so mecanismos usados como forma de comunicao e manipulao a ca ca de processos em GNU/Linux. O tpico que fala de sinais muito extenso; o e aqui falaremos sobre alguns sinais mais importantes e tcnicas que so usadas e a para controlar processos. Um sinal uma mensagem especial enviada a um processo. Sinais so e a ass ncronos; quando um processo recebe um sinal, o referido processo manipula o sinal imediatamente, sem encerrar a funo que est processando no ca a momento ou mesmo sem encerrar a linha de cdigo que ele est executando o a no momento. Existem muitas dzias de diferentes sinais, cada um com um u signicado diferente. Cada tipo de sinal especicado atravs de seu nmero e e u de sinal, mas em programas, voc comumente se refere a um sinal atravs de e e seu nome. Em GNU/Linux, os sinais so denidos em /usr/include/bits/a signum.h. (Voc no deve incluir esse arquivo de cabealho diretamente em e a c seu programa; ao invs disso, use <signal.h>.) e Quando um processo recebe um sinal, esse mesmo processo pode ter uma entre muitas respostas/comportamentos, dependendo do comportamento do sinal recebido. Para cada sinal, existe um comportamento padro, que detera mina o que acontece ao processo se o programa executado no processo no a especica algum outro comportamento. Para a maioria dos tipos de sinal, um programa especica algum comportamento ou ignora o sinal ou chama uma funo especial manipuladora de sinal para responder ao sinal. Se uma ca funo manipuladora de sinal for usada, o programa atualmente em execuo ca ca colocado em estado de espera, a funo manipuladora de sinal executada, e ca e e, quando a funo manipuladora de sinal retornar, o programa que estava ca sendo executado na hora da chegada do sinal retomado pelo processo e e continua do ponto onde parou. O sistema GNU/Linux envia sinais a processos em resposta a condies co espec cas. Por exemplo, os sinais SIGBUS (erro de bus), SIGSEGV (violao de segmentao), e SIGFPE (exceo de ponto utuante) podem ser ca ca ca enviados a um processo que tenta executar uma operao inlegal. O comporca tamento padro para esses sinais encerrar o processo e produzir um arquivo a e core. Um processo pode tambm enviar um sinal a outro processo. Um uso e comum desse mecanismo encerrar outro processo enviando um sinal SIGe 71

TERM ou um sinal SIGKILL.

Outro uso comum enviar um comando a um programa que est sendo e a executado. Dois sinais denidos pelo usurioso reservados com esse oba a jetivo: SIGUSR1 e SIGUSR2. O sinal SIGHUP algumas vezes usado para e esse propsito tambm, comumente para acordar um programa que est coo e a chilando ou fazer com que um programa releia seus arquivos de congurao. ca A funo sigaction pode ser usada para congurar um comportamento ca de sinal. O primeiro parmetro o nmero do sinal. Os dois parmetros a e u a imediatamente a seguir so apontadores para estruturas da funo sigaction; a ca o primeiro dos dois contm o comportamento desejado para aquele nmero e u de sinal, enquanto o segundo recebe o comportamento atualmente existente. O campo mais importante tanto na primeira como na segunda estrutura apontadas da funo sigaction sa handler. O sa handler pode receber um ca e dos trs valores abaixo: e a SIG DFL, que especica o comportamento padro para o sinal. SIG IGN, que especica a possibilidade de o sinal pode ser ignorado. Um apontador para uma funo manipuladora de sinal. A funo deve ca ca receber um parmetro, o nmero do sinal, e retornar void 7 . a u Pelo fato de sinais serem ass ncronos, o programa principal pode estar em um estado muito frgil quando um sinal processado e dessa forma tambm a e e enquanto uma funo manipuladora de sinal est sendo executada. Porca a tanto, voc deve evitar executar quaisquer operaes de E/S ou chamar a e co maior parte das funes de biblioteca e de sistema a partir de manipuladores co de sinal. Um manipulador de sinal executa o trabalho m nimo necessrio para resa ponder ao sinal, e ento retornar o controle ao programa principal (ou ena cerrar o programa). Na maioria dos casos, a tarefa do manipulador de sinal consiste simplesmente em gravar o fato de que um sinal ocorreu. O programa principal ento verica periodicamente se um sinal ocorreu e reage conforme a
Qual a diferena? O sinal SIGTERM pergunta a um processo se ele pode terminar; o c processo pode ignorar a requisio por mascaramento ou ignorar o sinal. O sinal SIGKILL ca sempre encerra o processo imediatamente pelo fato de o processo no poder mascarar ou a ignorar o sinal SIGKILL. 7 Vazio
6

72

isso.

E poss que uma funo manipuladora de sinal seja interrompida por vel ca meio da entrega de outro sinal. Embora isso seja uma ocorrncia rara, se e vier a ocorrer, ir ser muito dif diagnosticar e depurar o problema. (Isso a cil um exemplo de uma condio de corrida, discutida no Cap e ca tulo 4, TarefasSeo 4.4, Sincronizao e Sees Cr ca ca co ticas.) Portanto, voc deve ser e muito cuidadoso sobre o que seu programa faz em uma funo manipuladora ca de sinal.

Mesmo a atribuio de um valor a uma varivel global pode ser perigoso ca a pelo fato de que a atribuio poder ser atualmente realizada em duas ou ca mais instrues de mquina, e um segundo sinal pode ocorrer entre essas co a duas instrues de mquina, abandonando a varivel em um estado corromco a a pido. Se voc vier a usar uma varivel global para marcar um sinal a partir e a de uma funo manipuladora de sinal, essa varivel deve ser do tipo especial ca a sig atomic t. GNU/Linux garante que atribuies a variveis desse tipo so co a a realizadas em uma instruo simples e portanto no pode ser interrompida ca a no meio do caminho. Em GNU/Linux, sig atomic t um int comum; de e fato, atribuies a tipos inteiros do tamanho de int ou de menor tamanho, co ou para apontadores, so atmicos. Se voc deseja escrever um programa a o e que seja portvel para qualquer sistema UNIX padronizado, apesar do que a a foi aqui escrito, use o tipo sig atomic t para variveis globais.

O esqueleto de programa na Listagem 3.5 por exemplo, utiliza uma funo ca manipuladora de sinal para contar o nmero de vezes que o programa recebe u SIGUSR1, um dos sinais reservados para uso por aplicao. ca 73

Listagem 3.5: (sigusr1.c) Usando um Manipulador de Sinal


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include #include #include #include #include < s i g n a l . h> <s t d i o . h> < s t r i n g . h> <s y s / t y p e s . h> <u n i s t d . h> sigusr1 count = 0;

sig atomic t

void h a n d l e r ( i n t s i g n a l n u m b e r ) { ++s i g u s r 1 c o u n t ; } i n t main ( ) { struct s i g a c t i o n sa ; memset (& sa , 0 , s i z e o f ( s a ) ) ; s a . s a h a n d l e r = &h a n d l e r ; s i g a c t i o n ( SIGUSR1 , &sa , NULL) ; / Do some l e n g t h y / . . . / stuff here . /

p r i n t f ( SIGUSR1 was r a i s e d %d t i m e s \n , return 0 ; }

sigusr1 count ) ;

3.3.1

Encerramento de Processo

Normalmente, um processo encerra atravs de um entre dois caminhos. Ou e o programa que est sendo executado chama a funo exit, ou a fuo main a ca ca do programa retorna. Cada processo tem um cdigo de sa o da: um nmero u que o processo retorna a seu processo pai. O cdigo de sa o argumento o da e passado ` funo exit, ou o valor retornado a partir da funo main. a ca ca Um processo pode tambm terminar de forma abrupta, em resposta a um e sinal. Por exemplo, os sinais SIGBUS, SIGSEGV, e SIGFPE mencionados anteriormente fazem com que o processo encerre. Outros sinais so usados a para encerrar um processo explicitamente. O sinal SIGINT enviado a um e processo quando o usrio tenta encerr-lo digitando Ctrl+C em seu termia a nal. O sinal SIGTERM enviado pelo comando kill. A disposio padro em e ca a ambos os casos encerrar o processo. Por meio de chamada ` funo abort, e a ca um processo envia a si mesmo o sinal SIGABRT, que encerra o processo e produz um arquivo core. O mais poderoso sinal para encerrar um processo e SIGKILL, que encerra um processo imediatamente e no pode ser bloqueado a ou manuseado por um programa. Qualquer desses sinais pode ser enviado usando o comando kill por meio da especicao de um sinalizador extra de linha de comando; por exemplo, ca para encerrar um processo perturbador por meio do envio de a esse processo de um SIGKILL, use o seguinte comando, onde pid o nmero de identie u cao do seu processo perturbador: ca 74

% kill -KILL pid Para enviar um sinal a partir de um programa, use a funo kill. O ca primeiro parmetro o ID do processo alvo. O segundo parmetro o nmero a e a e u do sinal; use SIGTERM para simular o comportamento padro do comando a kill. Por exemplo, sendo child pid o ID de processo do processo lho, voc e pode usar a funo kill para encerrar um processo lho a partir do processo ca pai por meio de um chamado ` funo kill como o seguinte: a ca kill (child_pid, SIGTERM); Inclua cabealhos <sys/types.h> e <signal.h> caso voc resolva usar a c e funo kill. ca Por conveno, o cdigo de sa usado para indicar se o programa foi ca o da e executado corretamente. Um cdigo de sa com valor zero indica execuo o da ca correta, enquanto um cdigo de sa no nulo indica que um erro ocorreu. o da a No caso de ocorrncia de erro, o valor particular retornado pode fornecer e alguma indicao da natureza do erro. E uma boa idia apegar-se a essa ca e conveno em seus programas pelo fato de outros componentes do sistema ca GNU/Linux assumirem esse comportamento. Por exemplo, programas de shells assumem essa conveno quando voc conecta multiplos programas ca e com os operadores && (sinal lgico e) e (sinal lgico para ou). Poro o tanto, voc deve explicitamente retornar zero a partir de sua funo main, a e ca menos que um erro acontea. c Com a maioria dos shells, poss obter o cdigo de sa da maioria dos e vel o da programas para o mais recentemente programa executado usando a varivel a especial$?. Segue um exemplo no qual o comando ls chamado duas vezes e e seu cdigo de sa mostrado aps cada chamada. no primeiro caso, o da e o o comando ls executa corretamente e retorna o cdigo de sa zero. No o da segundo caso, ls encontra um erro (pelo fato de o nomedearquivo especicado na linha de comando no existir) e dessa forma retorna um cdigo de sa a o da no nulo. a
% ls / bin coda etc lib misc nfs proc sbin usr boot dev home lost+found mnt opt root tmp var % echo $? 0 % ls arquivobogus ls: impossivel acessar arquivobogus: Arquivo ou diretorio nao encontrado

75

% echo $? 1

Note que apesar de o tipo de dado do parmetro da funo exit ser int a ca e a funo main retornar um tipo de dado int, GNU/Linux no preserva os ca a 32 bits completos do cdigo de retorno. De fato, voc deve usar cdigos de o e o sa somente entre zero e 127. Cdigos de sa acima de 128 possuem um da o da signicado especial quando um processo for encerrado por meio de um sinal, seus cdigos de sa so 128 mais o nmero do sinal. o da a u

3.3.2

Esperando pelo Encerramento de um Processo

Se voc tiver digitado e executado o exemplo de fork e exec na Listagem e 3.4, voc pode ter notado que a sa fornecida pelo programa ls muitas e da vezes aparece aps o programa principalter sido completado. Isso ocorre o pelo fato de o processo lho, no qual ls estava sendo executado, agene dado independentemente do processo pai. Pelo fato de GNU/Linux ser um sistema operacional multitarefa, ambos os processos parecem ser executados simultneamente, e voc no pode prever se o programa ls ir ter uma chance a e a a de ser executado antes ou depois de o seu processo pai ser executado. Em algumas situaes, apesar disso, desejvel que o processo pai espere co e a at que um ou mais prodessos lhos se completem. Isso pode ser realizado e com a fam wait de chamadas de sistema. Essas funes permitem a voc lia co e esperar que um processo termine sua execuo, e habilite o processo pai ca recuperar informao sobre o encerramento de seu processo lho. Existem ca quatro diferentes chamadas de sistema na fam wait; voc pode escolher lia e pegar pouca ou muita informao sobre o processo encerrado, e voc pode ca e escolher se preocupar acerca de qual processo lho encerrou.

3.3.3

As Chamadas de Sistema da Fam wait lia

A funo mais simples da fam chamada apenas wait. Essa funo bloca lia e ca queia o processo que est fazendo a chamada at que um de seus processos a e lhos encerre (ou ocorra um erro). A funo wait retorna um cdigo que ca o reete a situao atual por meio de um argumento apontador inteiro, do ca qual voc pode extrair informao sobre como o porcesso lho terminou. Por e ca exemplo, a macro WEXITSTATUS extrai o cdigo de sa do processo lho. o da Voc pode usar a macro WIFEXITED para determinar a partir da situao e ca de sa de um processo lho se o referido processo terminou normalmente da 76

(por meio da funo exit ou retornando a partir da funo main) ou foi enca ca cerrado por meio de um sinal que no pode ser manipulado. Nesse ultimo a caso, use a macro WTERMSIG para extrair a partir de sua situao de sa ca da o nmero do sinal atravs do qual o processo em questo foi encerrado. Aqui u e a est a funo main de um exemplo com fork e com exec novamente. Dessa a ca vez, o processo pai chama wait para esperar at que o processo lho, no qual e o comando ls est sendo executado, termine. a
i n t main ( ) { int c h i l d s t a t u s ; / The a r g u m e n t l i s t t o p a s s t o t h e l s command . / char a r g l i s t [ ] = { ls , / a r g v [ 0 ] , t h e name o f t h e p r o g r a m . / l , / , NULL / The a r g u m e n t l i s t must end w i t h a NULL . }; / Spawn a c h i l d p r o c e s s r u n n i n g t h e l s command . r e t u r n e d c h i l d p r o c e s s ID . / spawn ( l s , a r g l i s t ) ;

Ignore

the

/ Wait f o r t h e c h i l d p r o c e s s t o c o m p l e t e . / w a i t (& c h i l d s t a t u s ) ; i f (WIFEXITED ( c h i l d s t a t u s ) ) p r i n t f ( the c h i l d p r o c e s s e x i t e d normally , with e x i t WEXITSTATUS ( c h i l d s t a t u s ) ) ; else p r i n t f ( t h e c h i l d p r o c e s s e x i t e d a b n o r m a l l y \n ) ; return 0 ; }

c o d e %d\n ,

Muitas chamadas de sistema similares esto dispon a veis em GNU/Linux, que so mais ex a veis ou fornecem mais informao sobre a sa de um ca da processo lho. A funo waitpid pode ser usada para esperar pela sa ca da de um processo lho espec co em lugar de esperar pelo trmino de algum e processo no espec a co. A funo wait3 retorna estat ca sticas de uso de CPU sobre o processo lho que est encerrando, e a funo wait4 permite a voc a ca e especicar opes adicionais sobre quais processos aguardar. co

3.3.4

Processos do Tipo Zumbi

Se um processo lho termina enquanto seu pai est chamando uma funo a ca wait, o processo lho desaparece e sua situao de encerramento informada ca e a seu processo pai por meio da chamada wait. Mas o que acontece quando um processo lho termina e o processo pai no est chamando a funo wait? a a ca O processo lho simplesmente desaparece? No, porque a informao sobre a ca seu encerramento - se ele terminou normalmente ou no, e se tiver terminado a normalmente, o que sua situao de sa mostra agora - pode ser perdida. ca da Quando um processo lho termina e o processo pai no est chamando a a a funo wait, ele torna-se um processo zumbi. ca

77

Um processo zumbi um processo que tenha terminado mas no tenha e a da responsabilidade do processo pai limpar o sistema sido limpo ainda. E de sua criana zumbi. As funes wait fazem isso, tambm, de forma que c co e no seja necessrio rastrear se seu processo lho est ainda executando antes a a a de esperar por ele. Suponhamos, por exemplo, que um programa faa um c fork criando um processo lho, execute alguma outra computao, e ento ca a chame a funo wait. Se o processo lho no tiver terminado nesse ponto, o ca a processo pai ir bloquear na chamada a wait at que o processo lho encerre. a e Se o processo lho encerrar antes que o processo pai chame wait, o processo lho torna-se um zumbi. Quando o processo api chama wait, a situao atual ca de encerramento do lho zumbi extra e da, o processo lho apagado, e a e chamada a wait retorna imediatamente. O que acontece se o processo pai no limpa seus lhos? Eles permanecem a soltos no sistemas, como processos zumbis. O programa na Listagem 3.6 cria um processo lho atravs de fork, que se encerra imediatamente e ento o e a mesmo programa que criou o processo lho vai cochilar por um minuto, sem mesmo limpar o processo lho. Listagem 3.6: (zombie.c) Fazendo um Processo Zumbi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include < s t d l i b . h> #include <s y s / t y p e s . h> #include <u n i s t d . h> i n t main ( ) { pid t child pid ; / C r e a t e a c h i l d p r o c e s s . / child pid = fork () ; i f ( c h i l d p i d > 0) { / T h i s i s t h e p a r e n t p r o c e s s . sleep (60) ; } else { / T h i s i s t h e c h i l d p r o c e s s . exit (0) ; } return 0 ; }

Sleep

f o r a minute .

Exit

immediately .

Tente compilar esse arquivo em um executvel chamado fazer-zumbi. a Rode esse executvel, e enquanto ele ainda estiver sendo executado, liste os a processos no sistema usando o seguinte comando em outra janela: % ps -e -o pid,ppid,stat,cmd O comando acima lista o ID de processo, ID do processo pai, situao ca atual do processo, e linha de comando do processo. Observe que, adicionalmente ao processo pai do processo fazer-zumbi, existe outro processo fazerzumbi listado. Esse o processo lho; note que seu ID de processo pai est e a 78

ao lado do ID de processo do processo fazer-zumbi principal. O processo lho marcado como <defunct>, e seu cdigo de situao atual Z, de zumbi.8 e o ca e O que acontece quando o programa principal fazer-zumbitermina quando o processo pai sai, sem ter chamado a funo wait? Fica o processo zumbi ca continua vagando por a No tente executar o comando ps novamente, e ? a notar que ambos os processos pai e lho fazer-zumbi se foram. Quando um programa sai, seus lhos so herdados por um processo especial, o programa a init, o qual sempre executa com o ID de processo como sendo 1 ( o primeiro e processo iniciado quando GNU/Linux passa pelo processo de inicializao). ca O processo init automaticamente limpa qualquer processo lho zumbi que ele herda.

3.3.5

Limpando Filhos de Forma No Sincronizada a

Caso voc esteja usando um processo lho simplesmente para executar outro e programa, funciona de forma satisfatria chamar a funo wait imediatao ca mente no processo pai, que ir bloquear at que o processo lho seja complea e tado. Mas muitas vezes, voc ir desejar que o processo pai continue sendo e a executado, como um ou mais processos lhos executando de forma sincronizada. Como pode voc garantir que limpou processos lhos que j tenham e a completado sua tarefa de forma que voc no esquea por a pelo sistema e a c processo zumbis, os quais consomem recursos de sistema, com informaes co falsas por a ? Uma abordagem pode ser a chamada pelo processo pai das funes wait3 co ou wait4 periodicamente, para limpar lhos zumbis. Chamando a funo ca wait com esse objetivo no funciona bem pelo fato de que, se nenhum proa cesso lho terminar, a chamada a wait ir bloquear o processo pai at que a e algum processo lho encerre. Todavia, as funes wait3 e wait4 recebem co um parmetro sinalizador adicional, para o qual voc pode passar o valor a e sinalizador WNOHANG. Com esse sinalizador, a funo chamada executa ca em modo no bloqueador de processo pai e ir limpar um processo lho que a a terminou se existir algum, ou simplesmente retornar se no houver nenhum a processo lho executando. O valor de retorno da chamada o ID do processo e do lho encerrado, ou zero no caso de no haver nenhum processo sendo exea cutado.
Em um slackware 12.2 a seguinte: PID PPID STAT 9152 9133 S+ 9153 9152 Z+
8

a sa da, mostrando somente as duas linhas que interessam, foi CMD ./fazer-zumbi [fazer-zumbi] <defunct>

79

Uma soluo mais elegante noticar o processo pai quando um lho ca e conclui sua tarefa. Existem muitas formas de fazer isso usando os mtodos e discutidos no Cap tulo ??, Comunicao Entre Processosmas afortunadaca mente GNU/Linux faz isso para voc, usando sinais. Quando um processo e lho cumpre sua tarefa, GNU/Linux envia ao processo pai o sinal SIGCHLD. A disposio padro desse sinal no fazer nada, coisa que talvez voc possa ca a e a e no ter notado antes. a Dessa forma, um caminho fcil para limpar processos lhos pelo maa e nuseio de SIGCHLD. Certamente, durante a limpeza de processos lhos, e importante guardar sua situao atual de encerramento se essa informao ca ca for necessria, pelo fato de uma vez que o processo for limpo usando wait, a a sua informao de encerramento no mais estar dispon ca a a vel. A Listagem 3.7 mostra um exemplo de programa que usa uma funo manipuladora de ca SIGCHLD para limpar seus processos lhos. 9

Listagem 3.7: (sigchld.c) Limpando Processos lhos pelo manuseio de SIGCHLD


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include #include #include #include < s i g n a l . h> < s t r i n g . h> <s y s / t y p e s . h> <s y s / w a i t . h> child exit status ;

sig atomic t

void c l e a n u p c h i l d p r o c e s s ( i n t s i g n a l n u m b e r ) { / C l e a n up t h e c h i l d p r o c e s s . / int s t a t u s ; w a i t (& s t a t u s ) ; / S t o r e i t s e x i t s t a t u s i n a g l o b a l v a r i a b l e . child exit status = status ; } i n t main ( ) { / H a n d l e SIGCHLD b y c a l l i n g c l e a n struct s i g a c t i o n s i g c h l d a c t i o n ; memset (& s i g c h l d a c t i o n , 0 , s i z e o f s i g c h l d a c t i o n . s a h a n d l e r = &c l e a n s i g a c t i o n (SIGCHLD , &s i g c h l d a c t i o n / Now do t h i n g s , / . . . / return 0 ; } including

up child process . ( sigchld action ) ) ; up child process ; , NULL) ; child process .

forking a

O cdigo em clean up child process pode no trabalhar corretamente se houver mais o a que um processo lho. O kernel do GNU/Linux ir somente chamar o manipulador de sinal a uma vez se dois ou mais processos lhos encerrarem quase ao mesmo tempo. Portanto, caso haja mais de um processo lho, o manipulador de sinal deve repetidamente chamar por waitpid (ou uma das outras funes relacionada) com a opo WNOHANG at que co ca e waitpid retorne.

80

Note como o manipulador de sinal armazena a situao de sa do proca da cesso lho em uma varivel global, da qual o programa principal pode acessa a la. Pelo fato de a varivel se atribu em um manipulador de sinal, ela (a a da varivel global) do tipo sig atomic t. a e

81

82

Cap tulo 4 Tarefas


TAREFAS1 ,COMO PROCESSOS, SAO UM MECANISMO PARA PERMITIR A UM PROGRAMA fazer mais de uma coisa ao mesmo tempo. Da mesma forma que acontece com processos, tarefas parecem executar concorrentemente; o kernel GNU/Linux agenda-os de forma no sincroa nizada, interrompendo cada uma dessas tarefas de tempos em tempos para fornecer a outros uma chance para executar. Conceitualmente, uma tarefa existe dentro de um processo. Tarefas so a menor unidade de execuo que processos. Quando voc chama um a ca e programa, GNU/Linux cria um novo processo e esse processo cria uma tarefa simples, que executa o programa sequencialmente. Essa tarefa pode criar tarefas adicionais; todas essas tarefas executam o mesmo programa no mesmo processo, mas cada tarefa pode estar executando uma parte diferente do programa em qualquer tempo fornecido. Ns vimos como um programa pode atravs de um fork criar um processo o e lho. O processo lho inicialmente executa seu programa pai, na memria o virtual do processo pai, com descritores de arquivo do processo pai e assim por diante copiado tudo do processo pai. O processo lho pode modicar sua memria fechar descritores de arquivo, e coisas parecidas sem afetar seu o processo pai, e vice-versa.2 Quando um programa no processo lho cria outra tarefa, apesar disso, nada copiado. A tarefa criadora e a tarefa e criatura compartilham o mesmo espao de memria, os mesmos descritores c o de arquivo, e outros recursos de sistema como o original. Se uma tarefa muda o valor de uma varivel, por exemplo, o outra tarefa sequencialmente a ir ver o valor modicado. Similarmente, se uma tarefa fecha um descritor a de arquivo, outra tarefa pode no ler aquele descritor ou no escrever para a a aquele descritor. Pelo fato de um processo e todas as suas tarefas poderem
1 2

Threads O processo pai pode fazer vrios procedimentos sem afetar o lho. a

83

executar somente um programa de cada vez, se alguma tarefa dentro de um processo chama uma das funes exec 3 , todas as outras tarefas so nalizadas co a (o novo programa pode, certamente, criar novas tarefas). GNU/Linux implementa o padro POSIX para Interface de Programao a ca 4 de Aplicao (API) de tarefa (conhecido como pthreads) . Todas funes de ca co tarefa e tipos de dado so declarados no arquivo de cabealho <pthread.h>. a c As funes posix de tarefa no esto inclu co a a das na biblioteca C padro. Ao a invs disso, elas esto na libpthread, ento voc deve adicionar -lpthread ` e a a e a linha de comando quando voc zer a linkagem de seu programa. e

4.1

Criao de Tarefas ca

Cada tarefa identicada por um ID (identicador) de tarefa. Quando for e se referir a IDs de tarefa em programas feitos em C ou em C++, use o tipo pthread t. Sobre criao, cada tarefa executa uma funo de tarefa. Essa funo de ca ca ca tarefa apenas uma funo comum e contm o cdigo que a tarefa deve exee ca e o cutar. Quando a funo retorna, a tarefa encerra. Em ambiente GNU/Linux, ca funes de tarefa recebem um parmetro simples, do tipo void*, e possuem co a o tipo de dado retornado tambm void*. O parmetro o argumento da tae a e refa: GNU/Linux passa o valor conforme a tarefa sem olhar para o contedo. u Seu programa pode usar esse parmetro para passar dados para uma nova a tarefa. Reciprocamente, seu programa pode usar o valor de retorno para passar dados a partir de uma tarefa existente de volta ao criador da tarefa. e A funo pthread create cria uma nova tarefa. Voc alimenta a pthca read create com o seguinte: 1. Um apontador para uma varivel do tipo pthread t, na qual o ID de a tarefa da nova tarefa est armazenado. a 2. Um apontador para um objeto de atributo de tarefa. Esse apontador controla detalhes de como a tarefa interage com o restante do programa. Se voc passa um dado NULL como atributo de tarefa, uma tarefa ir e a ser criado com os os atributos padronizados de encadeamanto. Atributos de tarefa so discutidos na Seo 4.1.5, Atributos de Tarefa. a ca 3. Um apontador para a funo de tarefa. Esse apontador um apontador ca e de funo comum, do seguinte tipo: ca
Relembrando que a fam de funes exec substituem o programa que est sendo lia co a executado por outro 4 p-tarefa ou POSIX-tarefa ou ainda tarefas POSIX
3

84

void* (*) (void*) 4. Um valor de argumento de tarefa do tipo void*. Todo o resto que voc e enviar simplesmente passado como argumento para a funo de tarefa e ca quando a tarefa inicia sua execuo. ca Uma chamada a pthread create retorna imediatamente, e a tarefa original continua executando as instrues imediatamente aps a chamada. Enquanto co o isso, a nova tarefa inicia-se executando a funo de tarefa. GNU/Linux ca agenda ambas as tarefas de forma no sincronizada, e seu programa continua a independentemente da ordem relativa na qual instrues so executadas em co a duas tarefas. O programa na Listagem 4.10 cria uma tarefa que imprime xs continuamente para a sa de erro. Aps chamar pthread create, a tarefa principal da o imprime os continuamente para a sa de erro. da Listagem 4.1: ( thread-create.c) Criando uma Tarefa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <p t h r e a d . h> #include <s t d i o . h> / P r i n t s x s to stderr . The p a r a m e t e r is unused . Does n o t return . /

void p r i n t x s ( void unused ) { while ( 1 ) fputc ( x , stderr ) ; return NULL ; } / The main p r o g r a m . /

i n t main ( ) { pthread t thread id ; / C r e a t e a new t h r e a d . The new t h r e a d w i l l r u n t h e function . / p t h r e a d c r e a t e (& t h r e a d i d , NULL, &p r i n t x s , NULL) ; / P r i n t o s c o n t i n u o u s l y t o s t d e r r . / while ( 1 ) fputc ( o , stderr ) ; return 0 ; }

print xs

Compile e faa a linkagem desse programa usando o seguinte cdigo: c o \% cc -o thread-create thread-create.c -lpthread Tente execut-lo para ver o que ocorre. Preste atenao ao padro ima c a previs de xs e os devido ` alternncia de agendamentos do Linux com vel a a relao `s duas tarefas. ca a Sob circunstncias normais, uma tarefa encerra-se por meio de uma entre a duas formas. Uma forma, como ilustrado previamente, por meio do retorno e da funo de tarefa. O valor de retorno da funo de tarefa usado para ca ca e ser o valor de retorno da tarefa. Alternativamente, uma tarefa pode sair 85

explicitamente por meio de uma chamada a pthread exit. Essa funo pode ca ser chamada de dentro da funo de tarefa ou a partir de alguma outra funo ca ca chamada diretamente ou indiretamente pela funo de tarefa. O argumento ca para pthread exit o valor de retorno da tarefa. e

4.1.1

Enviando Dados a uma Tarefa

O argumento de tarefa fornece um mtodo conveniente de enviar dados a e tarefas. Pelo fato de o tipo de dado do argumento ser void*, apesar disso, voc no pode enviar grande quantidade de dados diretamente atravs do e a e argumento. Ao invs disso, use o argumento de tarefa para enviar um apone tador para alguma estrutura ou vetor de dados. Uma tcnica comumente e usada denir uma estrutura para cada funo de tarefa, a qual contm os e ca e parmetrosesperados pela funo de tarefa. a ca Usando o argumento de tarefa, torna-se fcil reutilizar a mesma funo a ca de tarefa para muitas tarefas. Todos essas tarefas executam o mesmo cdigo, o mas sobre diferentes dados. O programa na Listagem 4.2 similar ao exemplo anterior. O referido e programa cria duas novas tarefas, um para imprimir xs e o outro para imprimir os. Ao invs de imprimir innitamente, apesar disso, cada tarefa ime prime um nmero xo de caracteres e ento encerra-se retornando ` funo u a a ca de tarefa. A mesma funo de tarefa, char print, usada em ambas as ca e tarefas, mas cada tarefa congurada diferentemente usando a estrutura e char print parms. 86

Listagem 4.2: ( thread-create2) Cria Duas Tarefas


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <p t h r e a d . h> #include <s t d i o . h> / P a r a m e t e r s to print function . /

struct c h a r p r i n t p a r m s { / The c h a r a c t e r t o p r i n t . / char c h a r a c t e r ; / The number o f t i m e s t o p r i n t int count ; }; / P r i n t s a number o f which i s a p o i n t e r

it .

c h a r a c t e r s t o s t d e r r , a s g i v e n b y PARAMETERS, t o a s t r u c t c h a r p r i n t p a r m s . /

void c h a r p r i n t ( void p a r a m e t e r s ) { / C a s t t h e c o o k i e p o i n t e r t o t h e r i g h t t y p e . / struct c h a r p r i n t p a r m s p = ( struct c h a r p r i n t p a r m s ) parameters ; int i ; f o r ( i = 0 ; i < p o u n t ; ++i ) >c f p u t c ( p h a r a c t e r , s t d e r r ) ; >c return NULL ; } / The main p r o g r a m . i n t main ( ) { pthread t thread1 id ; pthread t thread2 id ; struct c h a r p r i n t p a r m s struct c h a r p r i n t p a r m s /

thread1 args ; thread2 args ;

/ C r e a t e a new t h r e a d t o p r i n t 3 0 , 0 0 0 x s . / thread1 args . character = x ; t h r e a d 1 a r g s . count = 30000; p t h r e a d c r e a t e (& t h r e a d 1 i d , NULL, &c h a r p r i n t , &t h r e a d 1 a r g s ) ; / C r e a t e a new t h r e a d t o p r i n t 2 0 , 0 0 0 o s . / thread2 args . character = o ; t h r e a d 2 a r g s . count = 20000; p t h r e a d c r e a t e (& t h r e a d 2 i d , NULL, &c h a r p r i n t , &t h r e a d 2 a r g s ) ; return 0 ; }

Mas Espere! O programa na Listagem 4.2 tem um erro srio nele. A e tarefa principal (que executa a funo main) cria as estruturas do parmetro ca a de tarefa (thread1 args e thread2 args) como variveis locais, e ento passa a a apontadores para essas estruturas destinados `s tarefas que cria. O que fazer a para previnir o Linux do agendamento das trs tarefas de tal forma que e a tarefa principal termine antes de qualquer das duas outras tarefas terem terminado? Nada! Mas caso isso ocorra, a memria contendo as estruturas o do parmetro da tarefa ter sido liberada enquanto as outras duas tarefas a a estiverem ainda acessando-a.

4.1.2

Vinculando Tarefas

Uma soluo forar main a esperar at que as outras duas tarefas tenham ca e c e terminado. O que precisamos de uma funo similar ` funo wait que e ca a ca espere pelo m de uma tarefa ao invs de esperar pelo m de um processo. e 87

A funo desejada pthread join, que recebe dois argumentos: o ID de tarefa ca e da tarefa pelo qual vai esperar, e um apontador para uma varvel do tipo a void* que ir receber o valor de retorno da tarefa terminada. Se voc no a e a quiser preocupar-se com o valor de retorno, passe NULL como o segundo argumento. A Listagem 4.3 mostra a funo main corrigida para o exemplo de falha ca na listagem 4.2. Nessa verso, main no encerra at que ambas as tarefas a a e imprimindo xs e os tenham sido completadas, ento eles no mais utilizam a a as estruturas de argumento. Listagem 4.3: Funo main revisada para thread-create2.c ca
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <p t h r e a d . h> #include <s t d i o . h> / P a r a m e t e r s to print function . /

struct c h a r p r i n t p a r m s { / The c h a r a c t e r t o p r i n t . / char c h a r a c t e r ; / The number o f t i m e s t o p r i n t int count ; }; / P r i n t s a number o f which i s a p o i n t e r

it .

c h a r a c t e r s t o s t d e r r , a s g i v e n b y PARAMETERS, to a s t r u c t char print parms . /

void c h a r p r i n t ( void p a r a m e t e r s ) { / C a s t t h e c o o k i e p o i n t e r t o t h e r i g h t t y p e . / struct c h a r p r i n t p a r m s p = ( struct c h a r p r i n t p a r m s ) parameters ; int i ; f o r ( i = 0 ; i < p o u n t ; ++i ) >c f p u t c ( p h a r a c t e r , s t d e r r ) ; >c return NULL ; } / The main p r o g r a m . /

i n t main ( ) { pthread t thread1 id ; pthread t thread2 id ; struct c h a r p r i n t p a r m s struct c h a r p r i n t p a r m s

thread1 args ; thread2 args ;

/ C r e a t e a new t h r e a d t o p r i n t 3 0 0 0 0 x s . / thread1 args . character = x ; t h r e a d 1 a r g s . count = 30000; p t h r e a d c r e a t e (& t h r e a d 1 i d , NULL, &c h a r p r i n t , &t h r e a d 1 a r g s ) ; / C r e a t e a new t h r e a d t o p r i n t 2 0 0 0 0 o s . / thread2 args . character = o ; t h r e a d 2 a r g s . count = 20000; p t h r e a d c r e a t e (& t h r e a d 2 i d , NULL, &c h a r p r i n t , &t h r e a d 2 a r g s ) ; / Make pthread / Make pthread sure join sure join the f i r s t thread has f i n i s h e d . / ( t h r e a d 1 i d , NULL) ; the second thread has f i n i s h e d . / ( t h r e a d 2 i d , NULL) ; safely return . /

/ Now we can return 0 ; }

A moral da estria: garanta que qualquer dado que seja passado a uma o tarefa por referncia seja mantido na memria, mesmo que por uma tarefa e o 88

diferente, at que voc tenha certeza que a tarefa tenha terminado com esse e e dado. Essa garantia verdadeira em ambos os casos tanto para variveis e a locais, que so removidas quando as tarefas saem do ambiente no qual foram a denidas, quanto para variveis alocadas em grupo/pilha, que voc libera a e atravs de um chamado a free (ou usando delete em C++). e

4.1.3

Valores de Retorno de Tarefas

Se o segundo argumento que voc passar a pthread join for no nulo, o valor e a de retorno da tarefa ser colocado na localizao apontada por aquele argua ca mento. O valor de retorno da tarefa, da mesma forma que o argumento de tarefa, do tipo void*. Se voc desejar devolver um dado do tipo int simples e e ou outro nmero pequeno, voc pode fazer isso facilmente convertendo o valor u e para void* e ento convertendo de volta para o tipo apropriado aps chamar a o 5 pthread join. O programa na Listagem 4.4 calcula o ensimo nmero primo e u em uma tarefa isolada. O valor de retorno dessa tarefa isolada o nmero e u primo desejado. A tarefa principal, enquanto isso, est livre para executar a outro cdigo. Note que o algor o tmo de divises sucessivas usado em como tmos pute prime completamente ineciente; consulte um livro sobre algor numricos se voc precisar calcular muitos primos em seus programas. e e

Note que esse procedimento perde a portabilidade, e cabe a voc garantir que seu e valor pode ser convertido seguramente para void* e ser convertido de volta sem perder bits.

89

Listagem 4.4: ( primes.c) Calcula Nmeros Primos em uma Tarefa u


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <p t h r e a d . h> #include <s t d i o . h> / Compute s u c c e s s i v e p r i m e n u m b e r s ( v e r y Nth p r i m e number , w h e r e N i s t h e v a l u e void c o m p u t e p r i m e ( void a r g ) { int candidate = 2 ; int n = ( ( int ) arg ) ; while ( 1 ) { int f a c t o r ; int i s p r i m e = 1 ; / T e s t p r i m a l i t y b y s u c c e s s i v e d i v i s i o n . / f o r ( f a c t o r = 2 ; f a c t o r < c a n d i d a t e ; ++f a c t o r ) i f ( c a n d i d a t e % f a c t o r == 0 ) { is prime = 0; break ; } / I s t h i s t h e p r i m e number we r e l o o k i n g f o r ? / if ( is prime ) { i f (n == 0 ) / R e t u r n t h e d e s i r e d p r i m e number a s t h e t h r e a d return ( void ) c a n d i d a t e ; } ++c a n d i d a t e ; } return NULL ; } i n t main ( ) { pthread t thread ; int which prime = 5000; int prime ; / S t a r t t h e c o m p u t i n g t h r e a d , up t o t h e 5 0 0 0 t h p r i m e number . / p t h r e a d c r e a t e (& t h r e a d , NULL, &c ompute pr ime , &w h i c h p r i m e ) ; / Do some o t h e r w o r k h e r e . . . / / Wait f o r t h e p r i m e number t h r e a d t o c o m p l e t e , and g e t t h e r e s u l t . p t h r e a d j o i n ( t h r e a d , ( void ) &p r i m e ) ; / P r i n t t h e l a r g e s t p r i m e i t c o m p u t e d . / p r i n t f ( The %dth p r i m e number i s %d . \ n , w h i c h p r i m e , p r i m e ) ; return 0 ; } inefficiently ). Return t h e p o i n t e d t o b y ARG. /

return

value .

4.1.4

Mais sobre IDs de Tarefas

Ocasionalmente, util para uma sequncia de cdigo determinar qual tarefa a e e o est executando. A funo pthread self retorna o ID da tarefa que a chamou. a ca Esse ID de tarefa pode ser comparado com outro ID de tarefa usando a funo ca pthread equal. Essas funes podem ser uteis para determinar se um ID de tarefa em co particular corresponde ao ID da tarefa atual. Por exemplo, um erro para e uma tarefa chamar pthread join para vincular-se a si mesma. (Nesse caso, pthread join ir retornar o cdigo de erro EDEADLK.) Para vericar isso a o antecipadamente, voc pode usar um cdigo como o que segue: e o
if ( ! pthread \ e q u a l ( pthread \ s e l f ( ) , p t h r e a d \ j o i n ( o t h e r \ t h r e a d , NULL ) ; other \ thread ))

90

4.1.5

Atributos de Tarefa

Atributos de tarefa fornecem um mecanismo para ajuste preciso do comportamento de tarefas individuais. Lembrando que pthread create aceita um argumento que um apontador para um objeto de atributo de tarefa. Se e voc informar um apontador nulo, os atributos de ancadeamento padronie zados so usados para congurar a nova tarefa. Todavia, voc pode criar e a e personalizar um objeto de atributo de tarefa para especicar outros valores para os atributos. 6 Para especicar atributos personalizados de tarefas, voc deve seguir esses e passos: 1. Crie um objeto pthread attr t. O caminho mais fcil de fazer isso a e simplesmente declarar uma varivel automtica desse tipo. a a 2. Chame pthread attr init, informando um apontador para ese objeto. Esse procedimento inicializa os atributos com seus valores padronizados. 3. Modique o objeto de atributo de forma que contenha os valores de atributo desejados. 4. Informe um apontador para o objeto de atributo ao chamar pthread create. 5. Chame pthread attr destroy para liberar o objeto de atributo. A varivel a pthread attr t propriamente dita no liberada/apagada; A varivel a e a pthread attr t pode ser reinicializada com pthread attr init. Um objeto de atributo de tarefa simples pode ser usado para muitas tarefas. No necessrio manter o objeto de atributo de encademamento a e a por ai aps as tarefas terem sido criadas. o Para a maioria das tarefar de programao para criao de aplicativos ca ca em GNU/Linux, somente um atributo de tarefa tipicamente de interesse e (os outros atributos dispon veis so primariamente para especicidades de a programao em tempo real). Esse atributo o estado de desvinculao da ca e ca tarefa. Uma tarefa pode ser criada como uma tarefa vinculvel (o padro) ou a a como uma tarefa desvinculada. Uma tarefa vinculvel, como um processo, a no tem seus recursos de sistema liberados automaticamente pelo GNU/Lia nux quando termina sua execuo. Ao invs disso, o estado de sa da tarefa ca e da
Para mais detalhes sobre threads/tarefas TUTORIALS/LinuxTutorialPosixThreads.html
6

veja

http://www.yolinux.com/

91

vagueia sem destino no sistema (semelhantemente a um processo zumbi) at e que outra tarefa chame pthread join para obter seu valor de retorno. Somente ento so seus recursos liberados. Uma Tarefa desvinculada, ao cantrrio, a a a tem seus recursos de sistema automaticamete liberados quando termina sua execuo. Pelo fato de uma tarefa desvinculada ter seus recursos liberados ca automaticamente, outra tarefa pode no conseguir informaes sobre sua a co conclus atravs do uso de pthread join ou obter seu valor de retorno. a e Para atribuir o estado desvinculado a um objeto de atributo de tarefa, use a funo pthread attr setdetachstate. O primeiro argumento um apontador ca e para o objeto de atributo de tarefa, e o segundo o estado desvinculado e desejado. Pelo fato de o estado vinculvel ser o padro, necessrio chamar a a e a a funo pthread attr setdetachstate somente para criar tarefas desvinculadas; ca informe PTHREAD CREATE DETACHED como o segundo argumento. O cdigo na Listagem 4.5 cria uma tarefa desvinculada usando o atributo o de tarefa desvinculada para a tarefa. Listagem 4.5: (detached.c) Programa Esqueleto Que Cria uma Tarefa Desvinculada
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <p t h r e a d . h> void t h r e a d f u n c t i o n ( void t h r e a d a r g ) { / Do w o r k h e r e . . . / return NULL ; } i n t main ( ) { pthread attr t attr ; pthread t thread ; pthread pthread pthread pthread a t t r i n i t (& a t t r ) ; a t t r s e t d e t a c h s t a t e (& a t t r , PTHREAD CREATE DETACHED) ; c r e a t e (& t h r e a d , &a t t r , &t h r e a d f u n c t i o n , NULL) ; a t t r d e s t r o y (& a t t r ) ; / the second thread . /

/ Do w o r k h e r e . . . / No n e e d return 0 ; } to join

Mesmo se uma tarefa for criada com o estado vinculvel, ele pode ser a transformado em uma tarefa desvinculada. Para fazer isso, chame pthread detach. Uma vez que seja desvinculada, ela no pode se tornar vina culvel novamente. a

4.2

Cancelar Tarefas

Sob circunstncias normais, uma tarefa encerra-se quando seu estado de sa a da normal, ou pelo retorno de seu valor de retorno ou por uma chamada ` e a 92

funo pthread exit. Todavia, poss para uma tarefa requisitar que outra ca e vel tarefa termine. Isso chamado cancelar uma tarefa. e Para cancelar uma tarefa, chame a funo pthread cancel, informando o ca ID de tarefa da tarefa a ser cancelada. Uma tarefa cancelada pode mais tarde ser vinculada; de fato, voc pode vincular uma tarefa cancelada para e liberar seus recursos, a menos que a tarefa seja desvinculada (veja a Seo ca 4.1.5, Atributos de Tarefa). O valor de retorno de uma tarefa cancelada e o valor especial fornecido por PTHREAD CANCELED. Muitas vezes uma tarefa pode ter alguma parte de seu cdigo que deva o ser executado em um estilo tudo ou nada. Por exemplo, a tarefa pode alocar alguns recursos, us-los, e ento liberar esses mesmos recursos em seguida. a a Se a tarefa for cancelada no meio do cdigo, pode no ter a oportunidade o a de liberar os recursos como era esperado, e dessa forma os recursos iro ser a perdidos. Para contar com essa possibilidade, poss e vel para uma tarefa controlar se e quando ela pode ser cancelada. Uma tarefa pode estar em um dos trs esatod abaixo com relao a cane ca celar tarefas. A tarefa pode ser cancelvel de forma no sincronizada. Isso que dizer a a que a tarefa pode ser canceladq em qualquer ponto de sua execuo. ca A tarefa pode ser cancelvel sincronizadamente. A tarefa pode ser a cancelada, mas no em algum ponto determinado de sua execuo. Ou a ca ao contrrio, requisies de cancelamento so colocadas em uma rea a co a a temporria de armazenamento, e a tarefa cancelada somente quando a e forem alcanados pontos espec c cos em sua execuo. ca Uma tarefa pode ser incancelvel. Tentativas de cancelar a tarefa so a a silenciosamente ignoradas. Quando criada inicialmente, uma tarefa cancelvel sincronizadamente. e a

4.2.1

Tarefas Sincronas e Assincronas

Uma tarefa cancelvel assincronizadamente pode ser cancelado em qualquer a ponto de sua exxecuo. Um encademento cancelvel sincronizadamente, ca a ao contrrio, pode ser cancelado somente em lugares determinados de sua a execuo. Esses lugares so chamados pontos de cancelamento. A tarefa ir ca a a armazenar uma requisio de cancelamento at que o ponto de cancelamento ca e seguinte seja alcanado. c Para fazer uma tarefa assincronizadamente cancelvel, use pthread setcan a celtype. A funo pthread setcanceltype afeta tarefa que fez o chamado. O ca 93

primeiro argumento deve ser PTHREAD CANCEL ASYNCHRONOUS para tornar a tarefa assincronizadamente cancelvel, ou PTHREAD CANCEL DE a FERRED para retornar a tarefa ao estado de sincronizadamente cancelvel. a O segundo argumento, se no for nulo, um apontador para uma varivel a e a que ir receber o tipo de cancelamento anterior para a tarefa. A chamada a abaixo por exemplo, for example, transforma a tarefa que est fazendo a a chamada assincronizadamente cancelvel. a
pthread\_setcanceltype (PTHREAD\_CANCEL\_ASYNCHRONOUS, NULL);

O que constitui um ponto de cancelamento, e onde deve ele ser colocado? O caminho mais direto para criar um ponto de cancelamento chamar a e funo pthread testcancel. Essa chamada faz unicamente atender um pedido ca de cancelamento que se encontra pendente em uma tarefa sincronizadamente cancelvel. Voc deve chamar a funo pthread testcancel periodicamente a e ca durante computaes longas em uma funo de tarefa, em pontos onde a co ca tarefa pode ser cancelada sem desperdiar quaisquer recursos ou produzir c outros efeitos igualmente danosos. Certas outras funes trazem implicitamente pontos de cancelamento co tambm. So elas listadas na pgina de manual da funo pthread cancel. e a a ca 7 Note que outras funes podem usar essas funes internamente e dessa co co forma serem pontos de cancelamento.

4.2.2

Sees Cr co ticas Incancelveis a

Uma tarefa pode desabilitar o cancelamento de si mesma completamente com a funo pthread setcancelstate. Da mesma forma que pthread setcanceltype, ca a funo pthread setcancelstate afeta a tarefa que zer a chamada. O primeiro ca argumento PTHREAD CANCEL DISABLE para disabilitar a cancelabilie dade, ou PTHREAD CANCEL ENABLE para reabilitar a cancelabilidade. O segundo argumento, se no for null, aponta para uma varivel que ir rea a a ceber o estado de cancelamento anterior. A chamada a seguir, por exemplo, desabilita a cancelabilidade da tarefa na tarefa que zer a referida chamada.
pthread\_setcancelstate (PTHREAD\_CANCEL\_DISABLE, NULL);

Usando a funo pthread setcancelstate habilita voc a implementar sees ca e co cr ticas. Uma seo cr ca tica uma sequncia de cdigo que deve ser executado e e o ou em sua totalidade ou parcialmente; em outras palavras, se uma tarefa
Nota do Tradutor:usei o comando man pthread cancele no encontrei a referida a man page instalada no unbuntu 10.10 default mas na internet existem pelo menos duas verses de man page para pthread cancel. o
7

94

inicia-se executando uma seo cr ca tica, essa tarefa deve continuar at o nal e da seo cr ca tica sem ser cancelada. Por exemplo, suponhamos que voc est escremvendo uma rotina para um e a programa bancrio que transfere dinheiro de uma conta para outra. Para faa zer isso voc deve adicionar valor ao balano em uma conta e abater o mesmo e c valor do balano de outra conta. Se a tarefa que estiver executando sua rotina c for cancelado exatamente no pssimo momento entre essas duas operaes, o e co programa pode ter um aumento esprio do depsito total causado pela falha u o na concluso da transao. Para previnir essa possibilidade, coloque as duas a ca operaes dentro de uma seo cr co ca tica. Voc pode implementar a transferncia com uma funo tal como a proe e ca cess transaction, mostrada na Listagem 4.6.This function disables thread cancellation to start a critical sec- tion before it modies either account balance. Listagem 4.6: (critical-section.c) Protege uma Transao Bancria com ca a uma Seo Cr ca tica
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <p t h r e a d . h> #include <s t d i o . h> #include < s t r i n g . h> / An a r r a y of balances in accounts , indexed b y a c c o u n t number . /

float account balances ; / T r a n s f e r DOLLARS f r o m a c c o u n t FROM ACCT t o a c c o u n t TO ACCT . Return 0 i f t h e t r a n s a c t i o n s u c c e e d e d , o r 1 i f t h e b a l a n c e FROM ACCT i s too small . / int p r o c e s s t r a n s a c t i o n { int o l d c a n c e l s t a t e ; ( int from acct , int to acct , float dollars )

/ Check t h e b a l a n c e i n FROM ACCT . / i f ( account balances [ from acct ] < d o l l a r s ) return 1 ; / B e g i n c r i t i c a l s e c t i o n . / p t h r e a d s e t c a n c e l s t a t e (PTHREAD CANCEL DISABLE, &o l d c a n c e l s t a t e ) ; / Move t h e money . / a c c o u n t b a l a n c e s [ t o a c c t ] += d o l l a r s ; a c c o u n t b a l a n c e s [ f r o m a c c t ] = d o l l a r s ; / End c r i t i c a l s e c t i o n . / p t h r e a d s e t c a n c e l s t a t e ( o l d c a n c e l s t a t e , NULL) ; return 0 ; }

Note que importante restaurar o estado de cancelamento antigo no nal e da seo cr ca tica em lugar de escolher incondicionalmente o estado PTHREA D CANCEL ENABLE. restaurando o estado antigo ao invs de usar ine condicionalmente PTHREAD CANCEL ENABLE habilita voc a chamar e a funo process transaction seguramente de dentro de outra seo cr ca ca tica como no caso mostrado acima, permitindo que o estado de cancelamento seja colocado da mesma forma que se encontrava antes da sua interveno. ca 95

4.2.3

Quando Cancelar uma Tarefa

Em geral, uma boa idia no cancelar tarefas para encerrar a execuo de e e a ca uma tarefa, exceto em circunstncias raras. Durante operaes normais, a a co melhor estratgia indicar ` tarefa que ela deve encerrar, e ento esperar o e e a a trmino da tarefa por seu estilo prprio e ordeiro. Iremos discutir tcnicas e o e para comunicao com tarefas mais tarde no atual cap ca tulo, e no Cap tulo ??, Comunicao Entre Processos. ca

4.3

Area de Dados Espec cos de Tarefa

Ao contrrio dos processos, todas as tarefas em um programa simples coma partilham o mesmo espao de endereamento. Isso signica que se uma tarefa c c modica uma localizao na memria (por exemplo, uma varivel global), a ca o a mudana vis para todas as outras tarefas. Isso permite que multiplas c e vel tarefas operem sobre os mesmos dados sem o uso mecanismos de comunicao ca entre processos (que so descritos no Cap a tulo ??). Cada tarefa tem dua prpria pilha de chamadas, apesar do exposto acima. o Isso permite que cada tarefa executar cdigo diferente e chamar e retornar de o subrotinas no caminho usual. Como no programa de tarefa simples, cada chamada a uma subrotina em cada tarefa tem seu prprio conjunto de variveis o a locais, que armazenada na pilha para aquela tarefa. e Algumas vezes, todavia, desejvel duplicar uma certa varivel de forma e a a que cada tarefa tenha uma cpia separada. GNU/Linux suporta isso forneo cendo cada tarefa com uma rea de dados espec a cos de tarefa. As variveis a armazenadas nessa rea so duplicadas para cada tarefa, e cada tarefa pode a a modicar sua cpia da varivel sem afetar outras tarefas. Devido ao fato o a de todas as tarefas compartilharem o mesmo espao de memria, dados esc o pec cos de tarefano podem ser acessados usando referncias normais de a e variveis. Gnu/Linux fornece funes especiais para modicar e recuperar a co valores da rea de dados espec a cos de tarefa. Voc pode criar tantos dados espec e cos de tarefa quantos voc quiser, e cada um do tipo void*. Cada item referenciado por uma chave. Para criar e uma nova chave, e dessa forma um novo item de dado para cada tarefa, use a funo pthread key create. O primeiro argumento um apontador para ca e uma varivel do tipo denido em pthread key t. Esse valor de chave pode a ser usado por cada tarefa para acessar sua prpria cpia do correspondente o o item dos dados. O segundo argumento a pthread key create uma funo e ca 96

de limpeza. Se voc informar um apontador de funo aqui, GNU/Linux e ca automaticamente chama aquela funo indicada pelo apontador informado ca quando cada tarefa terminar sua execuo, informando o valor espec ca co da tarefa que corresponde a aquela chave. Isso particularmente adequado pelo e fato de a funo de limpeza ser chamada mesmo se a tarefa for cancelada em ca algum ponto arbitrrio em sua execuo. Se o valor espec a ca co da tarefa for null, a funo de limpeza da tarefa no chamada. Se voc no precisa de ca a e e a uma funo de limpeza, voc pode informar null ao invs de um apontador ca e e de funo. ca

Aps voc ter criado uma chave, cada tarefa pode modicar seu vao e lor espec co correspondente para aquela chave chamando a funo pthca read setspecic. O Primeiro argumento a chave, e o segundo do tipo e e void* e corresponde ao valor espec co da tarefa a ser armazenado. Para recuperar algum item de dados espec cos da tarefa, chame a funo pthca read getspecic, informando a chave como seu argumento.

Suponhamos, por exemplo, que sua aplicao distribua um trabalho entre ca diversas tarefas. Para propsitos de auditoria, cada tarefa tem um arquivo de o log separado, no qual mensagens de progresso, para os trabalhos executados por aquela tarefa, so gravadas. A rea especica de dados um lugar a a e conveniente para armazenar o apontador para o arquivo de log de cada tarefa.

A Listagem 4.7 mostra como voc pode implementar isso. A funo prine ca cipal nesse programa exemplo cria uma chave para armazenar o apontador ao arquivo espec co da tarefa e ento armazenar as informaes em tha co a e read log key. Pelo fato de thread log key ser uma varivel global, ela compartilhada por todas as tarefas. Quando cada tarefa inicia executando sua funo de tarefa, a atarefa abre um arquivo de log e armazena o apontador ca de arquivo sob aquela chave. Mais tarde, qualquer dessas tarefas pode chamar write to thread log para escrever uma mensagem para o arquivo de log espec co de tarefa. A funo write to thread log recupera o apontador de ca arquivo para o arquivo de log da tarefa para dados espec cos de tarefa e escreva a mensagem. 97

Listagem 4.7: (tsd.c) Log Por Tarefa Implementado com Dados Espec cos de Tarefa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <m a l l o c . h> #include <p t h r e a d . h> #include <s t d i o . h> / The k e y u s e d t o a s s o c a t e a l o g f i l e static pthread key t thread log key ; / W r i t e MESSAGE t o the log file for pointer with each thread . /

the

current

thread .

void w r i t e t o t h r e a d l o g ( const char m e s s a g e ) { FILE t h r e a d l o g = ( FILE ) p t h r e a d g e t s p e c i f i c f p r i n t f ( t h r e a d l o g , %s \n , m e s s a g e ) ; } / C l o s e the log file p o i n t e r THREAD LOG . /

( thread log key ) ;

void c l o s e t h r e a d l o g ( void t h r e a d l o g ) { f c l o s e ( ( FILE ) t h r e a d l o g ) ; } void t h r e a d f u n c t i o n ( void a r g s ) { char t h r e a d l o g f i l e n a m e [ 2 0 ] ; FILE t h r e a d l o g ; / G e n e r a t e t h e f i l e n a m e f o r t h i s t h r e a d s l o g f i l e . / s p r i n t f ( t h r e a d l o g f i l e n a m e , t h r e a d%d . l o g , ( i n t ) p t h r e a d s e l f ( ) ) ; / Open t h e l o g f i l e . / t h r e a d l o g = f o p e n ( t h r e a d l o g f i l e n a m e , w ) ; / S t o r e t h e f i l e p o i n t e r i n t h r e a d s p e c i f i c d a t a u n d e r t h r e a d l o g k e y . pthread setspecific ( thread log key , thread log ) ; w r i t e t o t h r e a d l o g ( Thread s t a r t i n g . ) ; / Do w o r k h e r e . . . / return NULL ; } i n t main ( ) { int i ; pthread t

threads [ 5 ] ;

/ C r e a t e a k e y t o a s s o c i a t e t h r e a d l o g f i l e p o i n t e r s i n thread s p e c i f i c data . Use c l o s e t h r e a d l o g t o c l e a n up t h e pointers . / p t h r e a d k e y c r e a t e (& t h r e a d l o g k e y , c l o s e t h r e a d l o g ) ; / C r e a t e t h r e a d s t o do t h e w o r k . / f o r ( i = 0 ; i < 5 ; ++i ) p t h r e a d c r e a t e (&( t h r e a d s [ i ] ) , NULL, t h r e a d f u n c t i o n , NULL) ; / Wait f o r a l l t h r e a d s t o f i n i s h . / f o r ( i = 0 ; i < 5 ; ++i ) p t h r e a d j o i n ( t h r e a d s [ i ] , NULL) ; return 0 ; }

file

Observe que thread function no precisa fechar o arquivo de log. Isso a ocorre pelo fato de que ao ser o arquivo de log criado, close thread log foi especicada como a funo de limpeza para aquela chave. Sempre que uma ca tarefa encerra, GNU/Linux chama aquela funo, informando o valor esca pec co de tarefa para a chave do log espec co da tarefa. Essa funo toma ca o cuidado de fechar o arquivo de log. 98

4.3.1

Manipuladores de Limpeza

As funes de limpeza para dados espec co cos de tarefa so necessrias para a a garantir que recursos no sejam perdidos quando a tarefa encerrar ou ser a cancelada. Algumas vezes, do comeo ao m, util estar apto a especicar c e funes de limpeza sem criar novos itens de dados espec co cos de tarefa que e duplicado para cada tarefa. GNU/Linux fornece cabealhos de limpeza para c esse propsito. o

Um manipulador de limpeza simplesmente uma funo que deve ser chae ca mada quando a tarefa termina. O manipulador recebe um parmetro simples a do tipo void*, e seu valor de argumento fornecido quando o manipulador e registrado. Isso facilita o uso da mesma funo manipuladora para liberar e ca recursos em multiplas instncias. a

Um manipulador um procedimento temporrio, usado para liberar um e a recurso somente se a tarefa encerrar ou for cancelada ao invs de terminar e a execuo de uma regio particular de cdigo. Sob circunstncias normais, ca a o a quando a tarefa no encerra e no cancelada, o recurso deve ser liberado a a e explicitamente e o manipulador de limpeza deve ser removido.

Para registrar manipulador de limpeza, chame a funo pthread cleanup ca push, informando um apontador para a funo de limpeza e o valor do seu ca argumento void*. A chamada a pthread cleanup push deve ser equilibrada por uma correspondente chamada a pthread cleanup pop, que remove o registro do maniplulador de limpeza. Por convenincia, pthread cleanup pop recebe e um argumento sinalizador do tipo int; se o sinalizador for diferente de zero, a ao de limpeza executada imediatamente e seu registro removido. ca e e

O fragmento de programa na Listagem 4.8 mostra como voc pode pse sivelmente usar um manipulador de limpeza para garantir que um espao c temporrio de armazenamento alocado dinamicamente seja limpo se a tarefa a terminar. 99

Listagem 4.8: (cleanup.c) Fragmento de Programa Demonstrando uma Tarefa


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <m a l l o c . h> #include <p t h r e a d . h> / A l l o c a t e a t e m p o r a r y buffer . /

void a l l o c a t e b u f f e r ( s i z e t { return m a l l o c ( s i z e ) ; } / D e a l l o c a t e a t e m p o r a r y void d e a l l o c a t e b u f f e r { free ( buffer ) ; }

size )

buffer .

( void b u f f e r )

void d o s o m e w o r k ( ) { / A l l o c a t e a t e m p o r a r y b u f f e r . / void t e m p b u f f e r = a l l o c a t e b u f f e r ( 1 0 2 4 ) ; / R e g i s t e r a c l e a n u p h a n d l e r f o r t h i s b u f f e r , t o d e a l l o c a t e case t h e t h r e a d e x i t s or i s c a n c e l l e d . / pthread cleanup push ( deallocate buffer , temp buffer ) ; / Do some w o r k h e r e cancelled . . . / that might call pthread exit or might be

it

in

/ U n r e g i s t e r t h e c l e a n u p h a n d l e r . S i n c e we p a s s a nonz e r o t h i s a c t u a l l y performs t h e c l e a n u p by c a l l i n g deallocate buffer . / pthread cleanup pop (1) ; }

value ,

Pelo fato de o argumento a pthread cleanup pop ser diferene de zero nesse e caso, a funo de limpeza deallocate buer chamada automaticamente aqui ca e no precisa ser chamada explicitamente. Nesse simples caso, pudemos ter a a funo da biblioteca padro liberando diretamente como nosso manipulador ca a de limpeza ao invs de deallocate buer. e

4.3.2

Limpeza de Tarefa em C++

Programadores em C++ esto acostumados limpar livremente empacotando a aes de limpeza em objetos destrutores. Quando os objetos saem fora do co escopo, ou por que um bloco executado para completar alguma coisa ou e pelo fato de uma exceo ser esquecida, C++ garante que destrutores seca jam chamados para aquelas variveis automticas que tiverem as referidas a a excees e blocos. Esse comportamento de C++ fornece um mecanismo maco nipulador para garantir que cdigo de limpeza seja chamado sem importar o como o bloco terminou. a Se uma tarefa chama a funo pthread exit, C++ no garante que desca trutores sejam chamados para todas as variveis automticas na pilha da a a tarefa. Uma maneira inteligente de recuperar essa funcionalidade invocar e a funo pthread exit no n mais alto da funo de tarefa abandonando ca vel ca alguma exceo especial. ca 100

O programa na Listagem 4.9 demonstra isso. Usando essa tcnica, uma e funo indica sua inteno de encerrar a tarefa abandonando uma ThreadEca ca xitException ao invs de chamar pthread exit diretamente. Pelo fato de a e exceo ter sido detectada na funo de tarefa de n mais alto, todas as ca ca vel variveis locais sobre a pilha da tarefa sero destru a a das como se a exceo ca limpasse a si mesma. Listagem 4.9: (cxx-exit.cpp) Implementando Sa Segura de uma Tarefa da com Excees de C++ co
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <p t h r e a d . h> extern b o o l should exit thread immediately () ;

c l a s s ThreadExitException { public : / C r e a t e an e x c e p t i o n s i g n a l l i n g t h r e a d T h r e a d E x i t E x c e p t i o n ( void r e t u r n v a l u e ) : thread return value ( return value ) { } / A c t u a l l y e x i t t h e t h r e a d , u s i n g t h e constructor . / void DoThreadExit ( ) { pthread exit ( thread return value ) ; } private : / The r e t u r n v a l u e t h a t w i l l void t h r e a d r e t u r n v a l u e ; }; void d o s o m e w o r k ( ) { while ( 1 ) { / Do some u s e f u l if } }

exit

w i t h RETURN VALUE .

return

value

provided

in

the

b e u s e d when

exiting

the

thread .

things

here . . .

( should exit thread immediately () ) throw T h r e a d E x i t E x c e p t i o n ( / t h r e a d s

return

v a l u e = / NULL) ;

void t h r e a d f u n c t i o n ( void ) { try { do some work ( ) ; } c a t c h ( T h r e a d E x i t E x c e p t i o n ex ) { / Some f u n c t i o n i n d i c a t e d t h a t we s h o u l d ex . DoThreadExit ( ) ; } return NULL ; }

exit

the

thread .

4.4

Sincronizao e Seoes Cr ca c ticas

Programar com tarefas muito complicado pelo fato de que a maioria dos e programas feitos usando tarefas serem programas que competem uns com os outros. Em particular, no existe caminho para saber quando o sistema a ir agendar uma tarefa para ser executada e quando o sistema ir executar a a outra tarefa. Uma tarefa pode ser executada pelo sistema por tempo muito 101

longo, ou o sistema pode alternar entre diversas tarefas muito rapidamente. Em um sistema com mltiplos processadores, o sistema pode mesmo agendar u multiplas tarefas para serem executadas literalmente ao mesmo tempo. Depurar um programa que usa tarefa dif pelo fato de voc no poder e cil e a sempre e facilmente reproduzir o comportamento que causa o problema. Voc e pode executar o programa e ter tudo trabalhando perfeitamente; a prxima o vez que voc executar o programa, ele pode cair. No existe caminho para e a fazer o sistema agendar as tarefas exatamente da mesma maneira que foi feito anteriormente. A mais recente causa da maioria dos erros envolvendo tarefas que as taree fas diferentes acessando a mesma informao na memria. Como mencionado ca o anteriormente, como mencionado anteriormente, esse comportamento de diversas tarefas acessaem a mesma informao um dos poderosos aspctos de ca e e uma tarefa, mas esse comportamento tambm pode ser perigoso. Se uma tarefa atualiza parcialmente uma estrutura de dados quando outra tarefa acessa a mesma estrutura de dados, confuso vai provavelmente acontecer. Muitas a vezes, programas que usam tarefa e possuem erros carregam um cdigo que o ir trabalhar somente se uma tarefa recebe agendamento muitas vezes mais a ou mais cedo que outra tarefa. Esses erros so cahmados condies de cora co rida; as tarefas esto competindo uma com a outra para modicar a mesma a estrutura de dados.

4.4.1

Condies de CCorrida co

Suponhamos que seu programa tenha uma srie de trabalhos enleirados e que so processados por muitas tarefas concorrentes. A la de trabalhos a e representada por uma lista lincada de objetos de estrutura de trabalho. Aps o cada tarefa terminar uma operao, ela verica a la para ver se um trabalho ca adicional est dispon a vel. Se job queue for diferente de null, a tarefa remove o trabalho do topo da lista lincada e posiciona job queue no prximo trabalho o da lista. A funo de tarefa que processa trabalhos na la pode parecer-se ca com a Listagem 4.10. 102

Listagem 4.10: ( job-queue1.c) Thread Function to Process Jobs from the Queue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <m a l l o c . h> struct job { / L i n k f i e l d f o r struct job next ; / O t h e r }; / A l i n k e d l i s t o f p e n d i n g struct job job queue ; extern void p r o c e s s j o b / P r o c e s s queued jobs jobs . / fields linked list . /

describing

work t o

be done . . .

( struct job ) ; until the queue is empty . /

void t h r e a d f u n c t i o n ( void a r g ) { while ( j o b q u e u e != NULL) { / Get t h e n e x t a v a i l a b l e j o b . / struct job n e x t j o b = job queue ; / Remove t h i s j o b f r o m t h e l i s t . / job queue = job queue >n e x t ; / C a r r y o u t t h e w o r k . / process job ( next job ) ; / C l e a n up . / free ( next job ) ; } return NULL ; }

Agora suponhamos que duas tarefas encerrem um trabalho aproximadamente ao mesmo tempo, mas somente um trabalho reste na la. A primeira tarefa verica se job queue null ; encontrando que no , a tarefa entra e a e no lao e armazena o apontador para o objeto de trabalho em next job. c Nesse ponto, o sistema GNU/Linux interrompe a primeira tarefa e agenda a segunda. A segunda tarefa tambm verica job queue e encontrando que e no , tambm atribui o mesmo apontador de trabalho para next job. Por a e e desafortunada coincidncia, temos agora duas tarefas executando o mesmo e trabalho. Para piorar a situao, uma tarefa ir deslincar o objeto de trabalho da ca a lista, permitindo que job queue contenha null. Quando a outra tarefa avaliar job queue-next, uma falha de segmentao ir aparecer. ca a Esse um exemplo de condio de corrida. Sob afortunadascircunstne ca a cias, esse particular agendamento de duas tarefas podem nunca ocorrer, e a condio de corrida pode nunca mostrar-se. Somente em circunstncias ca a diferenciadas, talvez ao executar sobre um sistema muito pesado (ou sobre um novo servidor multiprocessado de um importante usurio!) pode o erro a mostrar-se. Para eliminar condies de corrida, voc precisa de um caminho para co e fazer operaes atmicas. Uma operao atmica indivis e no pode ser co o ca o e vel a interrompida; uma vez que a operao for iniciada, no ir ser pausada ou ca a a interrompida at que se complete, e nenhuma outra operao ir tomar o seu e ca a 103

lugar enquanto isso. Nesse exemplo em particular, voc ir querer vericar e a job queue; se no estivar vazia, remover o primeiro trabalho, tudo isso junto a como uma operao atmica simples. ca o

4.4.2

Mutexes

A soluo para o problema da condio de corrida da la de trabalho ca ca e permitir que somente uma tarefa por vez acesse a la de tarefas. Assim que uma tarefa inicia olhando na la, nenhuma outra tarefa deve estar apta a acessar a la at que a primeira tarefa tenha decidido ou processar um e trabalho e, se zer isso , tiver removido o trabalho da lista. A implementao disso requer suporte por parte do sistema operacional. ca GNU/Linux fornece mutexes, abreviatura de trava de excluso mtua 8 . Um a u mutex uma trava especial que somente uma tarefa pode travar a cada e vez. Se uma tarefa trava um mutex e ento uma segunda tarefa tambm a e tenta travar o mesmo mutex, a segunda tarefa bloqueada, ou colocada em e espera. somente quando a primeira tarefa destrava o mutex a segunda e tarefa desbloqueada e permitido sua execuo. GNU/Linux garante que e ca condies de corrida no ocorram em mekio a tarefas que tentem travar um co a mutex ; somente uma tarefa ir mesmo pegar a trava, e todas as outras tarefas a iro ser bloqueadas. a Pensando em um mutex como a trava de uma porta de banheiro. Quem chegar primeiro entra no banheiro e trava a porta. Se alguma outra pessoa tenta entrar no banheiro enquanto ela estiver ocupado, aquela pessoa encontrar a porta fechada e ir ser forada a esperar do lado de fora at que o a c e ocupante aparea. c Para criar um mutex, crie uma varivel do tipo pthread mutex t e informe a um apontador para essa varivel criada para a funo pthread mutex init. O a ca segundo argumento de pthread mutex init um apontador para um objeto de e atributo de mutex, que especica os atributos de um mutex. Da mesma forma que ocorre com a funo pthread create, se o apontador de atributo for nulo, ca atributos padronizados so assumidos. A Varivel mutex deve ser inicializada a a somente uma unica vez. Esse fragmento de cdigo adiante demonstra a o declarao e a inicializao de uma varivel mutex. ca ca a pthread \ mutex \ t mutex ; pthread \ mutex \ i n i t (\&mutex , NULL ) ; Outra maneira mais simples de criar um mutex com atributos padronizados inicializar o referido mutex com o valor especial PTHREAD MUTEX e
8

Nota do tradutor:MUTual EXclusion

104

INITIALIZER. Nenhuma chamada adicional a pthread mutex init necessria. e a Essa forma particularmente conveniente para variveis globais (e, em C++, e a membros de dados estticos). O fragmento de cdigo acima poderia equivaa o lentemente ter sido escrito como segue:

pth read \ mutex \ t mutex = PTHREAD\ MUTEX\ INITIALIZER ;

Uma tarefa pode tentar travar um mutex por meio de uma chamada a pthread mutex lock referindo-se ao dito mutex. Se o mutex estiver desbloqueado, ele torna-se travado e a funo retorna imediatamente. Se o mutex ca estiver travado por outra tarefa, pthread mutex lock bloqueia a execuo e ca retorna somente quando o mutex for desbloquieado pela outra tarefa. Diversas tarefas ao mesmo tempo podem ser bloqueadas ao tentarem usar um mutex travado. Quando o mutex for desbloqueado, somente uma das tarefas bloqueadas (escolhida de forma imprevis vel) desbloqueada e permitido e e que a referida tarefa trave o mutex ; as outras tarefas continuam bloqueadas.

Uma chamada a pthread mutex unlock desbloqueia um mutex. Essa funo ca deve sempre ser chamada a partir da mesma tarefa que travou o mutex.

A listagem 4.11 Listing 4.11 shows another version of the job queue example. Now the queue is protected by a mutex. Before accessing the queue (either for read or write), each thread locks a mutex rst. Only when the entire sequence of checking the queue and removing a job is complete is the mutex unlocked.This prevents the race condition previously described. 105

Listagem 4.11: ( job-queue2.c) Funo de Tarefa da Fila de Trabalho, ca Protegida por um Mutex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <m a l l o c . h> #include <p t h r e a d . h> struct job { / L i n k f i e l d f o r struct job next ; / O t h e r }; / A l i n k e d l i s t o f p e n d i n g struct job job queue ; extern void p r o c e s s j o b jobs . / fields

linked

list .

describing

work t o

be done . . .

( struct job ) ;

/ A mutex p r o t e c t i n g j o b q u e u e . / p t h r e a d m u t e x t j o b q u e u e m u t e x = PTHREAD MUTEX INITIALIZER ; / P r o c e s s queued jobs until the queue is empty . /

void t h r e a d f u n c t i o n ( void a r g ) { while ( 1 ) { struct job n e x t j o b ; / L o c k t h e mutex on t h e j o b q u e u e . / p t h r e a d m u t e x l o c k (& j o b q u e u e m u t e x ) ; / Now i t s s a f e t o c h e c k i f t h e q u e u e i s empty . / i f ( j o b q u e u e == NULL) n e x t j o b = NULL ; else { / Get t h e n e x t a v a i l a b l e j o b . / next job = job queue ; / Remove t h i s j o b f r o m t h e l i s t . / job queue = job queue >n e x t ; } / U n l o c k t h e mutex on t h e j o b q u e u e , s i n c e we r e d o n e w i t h q u e u e f o r now . / p t h r e a d m u t e x u n l o c k (& j o b q u e u e m u t e x ) ; / Was t h e q u e u e empty ? i f ( n e x t j o b == NULL) break ; If so , end t h e thread . /

the

/ C a r r y o u t t h e w o r k . / process job ( next job ) ; / C l e a n up . / free ( next job ) ; } return NULL ; }

Todo o acesso a job queue, o apontador de dados compartilhados, vem entre a chamada a pthread mutex lock e a chamada a pthread mutex unlock. Um objeto de trabalho, armazenado em next job, acessado de fora dessa e regio somente aps aquele objeto de trabalho ter sido removido da la e a o estar, dessa forma, inacess a outras tarefas. vel Note que se a la estiver vazia (isto , job queue for null ), ns no sa e o a mos fora do lao imediatamente pelo fato de termos que manter o mutex perc manentemente travado e devemos prevenir que qualquer outra tarefa acesse a la de trabalhos novamente pois ela est vazia. Ao invs disso, lembraa e mos esse fato escollhendo next job para null e sair fora do lao somente aps c o desbloquear o mutex. O uso de mutex para travar job queue no automtico; cabe a voc a e a e 106

adicionar o cdigo para travar o mutex antes de acessar job queue e tambm o e o cdigo para destravar job queue posteriormente. Por exemplo, uma funo o ca para adicionar um trabalho ` la de trabalhos pode parecer-se com isso: a void e n q u e u e j o b ( struct j o b new job ) { p t h r e a d m u t e x l o c k (& j ob qu eu e mu te x ) ; new job>next = j o b q u e u e ; j o b q u e u e = new job ; p t h r e a d m u t e x u n l o c k (& j ob qu eu e mu te x ) ; }

4.4.3

Travas Mortas de Mutex

Mutexes fornecem um mecanismo para permitir que uma tarefa bloquei a execuo de outra. Esse procedimento abre a possibilidade de uma nova ca classe de falhas, chamadas travas mortas. Uma trava morta ocorre quando uma ou mais tarefas esto presas esperando por alguma coisa que nunca ir a a ocorrer. Um tipo simples de trava morta ocorre quando a mesma tarefa tenta bloquear um mutex duas vezes em uma linha. O comportamento nesse caso depende de qual tipo de mutex est sendo usado. Existem trs tipos de a e mutex : rpido - travando um mutex rpido (o tipo padro) far com que ocorra a a a a uma trava morta. Uma tentativa de travar os blocos mutex at que e o mutex seja desbloqueado. Mas pelo fato de a tarefa que travou o mutex estar bloqueada nesse mesmo mutex, a trava no pode nunca ser a liberada. recursivo - travando um mutex recursivo no causa uma trava morta. a Um mutex recursivo pode seguramente ser travado vrias vezes pela a mesma tarefa. O mutex recursivo lembra quantas vezes pthread mutex lock foi chamada sobre o mesmo mutex pela tarefa que segura a trava; a tarefa que segura a trava deve fazer o mesmo nmero de chamadas a u pthread mutex unlock antes do mutex atual ser desbloqueado e outra tarefa conseguir travar o mutex liberado. vericao de erro - GNU/Linux ir detectar e sinalizar uma trava ca a dupla sobre um mutex de vericao de erro que poderia de outra ca 107

forma causar uma trava morta. A segunda chamada consecutiva a pthread mutex lock retorna o cdigo de falha EDEADLK o Por padro, um mutex GNU/Linux do tipo rpido. Para criar um a e a mutex de um dos outros dois tipos, primeiro crie um objeto de atributo de mutex declarando uma varivel do tipo pthread mutexattr t e chamado ptha read mutexattr init sobre um apontador para a varivel do tipo pthread mutex a attr t. A seguir ajuste o tipo do mutex chamando pthread mutexattr setkind np; o primeiro argumento um apontador para o objeto de atributo de mue tex, e o segundo PTHREAD MUTEX RECURSIVE NP para um mutex e recursivo, ou PTHREAD MUTEX ERRORCHECK NP para um mutex de vericao de erro. Informe um apontador para esse atributo de objeto na ca funo pthread mutex init para criar um mutex do tipo de vericao de erro, ca ca e ento destrua o objeto de atributo com a funo pthread mutexattr destroy a ca A sequncia de cdigo abaixo ilustra a criao de um.mutex de vericao e o ca ca de erro, por exemplo:
pthread pthread pthread pthread pthread pthread mutexattr t attr ; m u t e x t mutex ; m u t e x a t t r i n i t (\& a t t r ) ; m u t e x a t t r s e t k i n d n p (\& a t t r , PTHREAD MUTEX ERRORCHECK NP ) ; m u t e x i n i t (\&mutex , \& a t t r ) ; m u t e x a t t r d e s t r o y (\& a t t r ) ;

Como sugerido pelo suxo np, os mutexes do tipo recursivo e de vericao de erro so espec ca a cos do GNU/Linux e no so portveis. Todavia, a a a no geralmente aconselhado usar esses dois tipos de mutexes em programas. a e (Mutexes de vericao de erro podem ser uteis quando se faz depuraes, ca co apesar disso.)

4.4.4

Testes de Mutex sem Bloqueio

Ocasionalmente, util testar se um mutex est travado sem sofrer bloqueio e a algum relativamente a esse mutex. Por exemplo, uma tarefa pode precisar travar um mutex mas pode ter outro trabalho para fazer ao invs ser bloquee ada se o mutex j estiver travado. Pelo fato de que pthread mutex lock no a a ir retornar at que o mutex se torne desbloqueado, alguma outra funo a e ca e necessria. a GNU/Linux fornece pthread mutex trylock para esse propsito. Se voc o e chamar pthread mutex trylock sobre um mutex destravado, voc ir travar e a o mutex como se voc tivesse chamado called pthread mutex lock, e pthe read mutex trylock ir retornar zero. Todavia, se o mutex j estiver bloa a queado por outra tarefa, pthread mutex trylock no ir bloquear a tarefa a a atual. Ao invs disso, pthread mutex trylock ir retornar imediatamente com e a o cdigo de erro EBUSY. A trava de mutex mantida pela outra tarefa no o a e afetada. Voc pode tentar mais tarde travar o mutex. e 108

4.4.5

Semaforos para Tarefas

No exemplo precedente, no qual muitas tarefas processam trabalhos a portir de um la, a funo de tarefa principal das tarefas realizam o prximo ca o trabalho at que nenhum trabalho seja esquecido e ento termina a tarefa. e a Esse esquema trabalho se todos os trabalhos forem enleirados previamente ou se novos trabalhos forem enleirados to rapidamente quanto as tarefas a os processam. Todavia, se as tarefas trabalham muito rapidamente, a la de trabalhos ir esvaziar e as tarefas encerraram. Se novos trabalhos forem a mais tarde enleirados, nenhuma tarefa pode restar para process-los. O que a podemos apreciar ao invs do exposto acima um mecanismo para bloquear e e as tarefas quando a la esvaziar at que novos trabalhos estejam dispon e veis. Um semforo fornece um mtodo conveniente para fazer isso. Um semforo a e a um contador que pode ser usado para sincronizar multiplas tarefas. Da e mesma forma que com o mutex, GNU/Linux garante que a vericao ou a ca modicao do valor de um semforo pode ser feito de forma segura, sem ca a criar condies de corrida. co Cada semforo tem um valor de contagem, que um inteiro no negativo. a e a Um semforo suporta duas operaes bsicas: a co a Uma operao wait decrementa o semforo de 1. Se o valor j for zero, a ca a a operao bloqueia at que o valor do semforo torne-se positivo (devido ca e a a ao de alguma outra tarefa). Quando o valor do semforo torna-se ca a positivo, ele decrementado de 1 e a operao de espera retorna. e ca Uma operao post incrementa o valor do semforo de 1. Se o semforo ca a a era anteriormente zero e outras tarefas esto bloqueadas em uma operaa ca o wait sobre o atual semforo, uma daquelas tarefas desbloqueada a e e sua operao wait realiza-se (o que acarreta o retorno do valor do ca semforo a zero). a Note que GNU/Linux fornece duas implementaes de semforos ligeiraco a mente diferentes. A primeira que descrevemos aqui a implementao de e ca semforos POSIX padro. Use os semforos POSIX quando comunicandoa a a se entre tarefas.. A outra implementao, usada para comunicao entre ca ca processos, descrita na Seo ??, Semforos de Processos. Se voc usa e ca a e semforos, inclua semaphore.h. a Um semforo representado por uma varvel sem t. Antes de usar a a e a varivel, voc deve inicializ-la usando a funo sem init, informando um a e a ca apontador para a varivel sem t. O segundo parmetro deve ser zero9 , e o a a
Um valor diferente de zero pode indicar a semforo que pode ser compartilhado por a vrios processos, o que no suportado pelo GNU/Linux para esse tipo de semforo. a a e a
9

109

terceiro parmetro o valor inicial do semforo. Se voc no mais precisar a e a e a de um semforo, bom liberar seus recursos com sem destroy. a e

Para operaes do tipo wait, use sem wait. Para operaes do tipo co co post, use sem post. Uma funo que no faz bloqueio do tipo wait, chaca a mada sem trywait, tambm fornecida. sem trywait semelhante a pthe e e read mutex trylock se a operao do tipo wait puder ser bloqueada pelo fato ca de o valor valor do semforo ser zero, a funo retorna imediatamente, com a ca o valor de erro EAGAIN, ao invs de efetuar o bloqueio. e

GNU/Linux tambm fornece uma funo para recuperar o valor atual de e ca um semforo, sem getvalue, a qual coloca o valor em um apontador para uma a varivel do tipo int por meio de seu segundo argumento. Voc no deve usar a e a o valor do semforo. Voc usa essa funo para tomar a deciso de fazer ou a e ca a um wait ou um post sobre o semforo, apesar disso. Usar o valor do semforo a a pode levar a uma condio de corridan: Outra tarefa pode mudar o valor do ca semforo entre a chamada a sem getvalue e a chamada a outra funo de a ca semforo. Use as funes atmicas post e wait ao invs de usar o valor do a co o e semforo. a

Retomando para nosso exemplo de la de trabalho, podemos usar um semforo para contar o nmero de trabalhos esperando na la. A Listagem a u 4.12 controla a la com um semforo. A funo enqueue job adiciona um a ca novo trabalho ` la. a 110

Listagem 4.12: ( job-queue3.c) Fila de Trabalhos Controlada por um Semforo a


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <m a l l o c . h> #include <p t h r e a d . h> #include <semaphore . h> struct job { / L i n k f i e l d f o r struct job next ; / O t h e r }; / A l i n k e d l i s t o f p e n d i n g struct job job queue ; extern void p r o c e s s j o b jobs . / fields

linked

list .

describing

work t o

be done . . .

( struct job ) ;

/ A mutex p r o t e c t i n g j o b q u e u e . / p t h r e a d m u t e x t j o b q u e u e m u t e x = PTHREAD MUTEX INITIALIZER ; / A s e m a p h o r e c o u n t i n g sem t job queue count ; / P e r f o r m onet i m e t h e number o f jobs in the queue . /

initialization

of

the

job

queue .

void i n i t i a l i z e j o b q u e u e ( ) { / The q u e u e i s i n i t i a l l y empty . / j o b q u e u e = NULL ; / I n i t i a l i z e t h e s e m a p h o r e w h i c h c o u n t s i n i t i a l v a l u e s h o u l d be zero . / s e m i n i t (& j o b q u e u e c o u n t , 0 , 0 ) ; } / P r o c e s s queued jobs until the queue is

jobs

in

the

queue .

Its

empty .

void t h r e a d f u n c t i o n ( void a r g ) { while ( 1 ) { struct job n e x t j o b ; / Wait on t h e j o b q u e u e s e m a p h o r e . If i t s value is positive , i n d i c a t i n g t h a t t h e q u e u e i s n o t empty , d e c r e m e n t t h e c o u n t b y one . I f t h e q u e u e i s empty , b l o c k u n t i l a new j o b i s e n q u e u e d . s e m w a i t (& j o b q u e u e c o u n t ) ; / L o c k t h e mutex on t h e j o b q u e u e . / p t h r e a d m u t e x l o c k (& j o b q u e u e m u t e x ) ; / B e c a u s e o f t h e s e m a p h o r e , we know t h e q u e u e i s n o t empty . Get the next a v a i l a b l e job . / next job = job queue ; / Remove t h i s j o b f r o m t h e l i s t . / job queue = job queue >n e x t ; / U n l o c k t h e mutex on t h e j o b q u e u e , s i n c e we r e d o n e w i t h t h e q u e u e f o r now . / p t h r e a d m u t e x u n l o c k (& j o b q u e u e m u t e x ) ; / C a r r y o u t t h e w o r k . / process job ( next job ) ; / C l e a n up . / free ( next job ) ; } return NULL ; } / Add a new j o b to the front of the job queue . / / )

void e n q u e u e j o b ( / P a s s j o b s p e c i f i c { struct job new job ;

data

here . . .

/ A l l o c a t e a new j o b o b j e c t . / new job = ( struct job ) malloc ( s i z e o f ( struct job ) ) ; / S e t t h e o t h e r f i e l d s o f t h e j o b s t r u c t h e r e . . . / / L o c k t h e mutex on t h e j o b q u e u e b e f o r e a c c e s s i n g p t h r e a d m u t e x l o c k (& j o b q u e u e m u t e x ) ; / P l a c e t h e new j o b a t t h e h e a d o f t h e q u e u e . / >n e x t = j o b q u e u e ; n e w j o b job queue = new job ; it . /

/ P o s t t o t h e s e m a p h o r e t o i n d i c a t e a n o t h e r j o b i s a v a i l a b l e . If t h r e a d s a r e b l o c k e d , w a i t i n g on t h e s e m a p h o r e , one w i l l become u n b l o c k e d s o i t can p r o c e s s t h e j o b . / s e m p o s t (& j o b q u e u e c o u n t ) ;

111

/ U n l o c k t h e j o b q u e u e mutex . / p t h r e a d m u t e x u n l o c k (& j o b q u e u e m u t e x ) ; }

Antes de pegar um trabalho da primeira posio da la, cada tarefa ir ca a primeiramente realizar uma operao wait sobre o semforo. Se o valor do ca a semforo for zero, indicando que a la est vazia, a tarefa ser simplesmente a a a bloqueada at que o valor do semforo torne-se positivo, indicando que um e a trabalho foi adicionado ` la. a A funo enqueue job adiciona um trabalho ` la. Da mesma forma que ca a thread function, a funo enqueue job precisa travar o mutex da la antes de ca modicar a la. Aps adicionar um trabalho ` la, a funo enqueue job efeo a ca tua uma operao do tipo post no semforo, indicando que um novo trabalho ca a est dispon a vel. Na verso mostrada na Listagem 4.12, as tarefas que atuam a sobre os trabalhos nunca terminam; se no houverem trabalhos dispon a veis em algum momento, todas as tarefas simplesmente bloqueiam em sem wait.

4.4.6

Variveis Condicionais a

Mostramos como usar um mutex para proteger uma varivel contra acessos a simultneos de duas tarefas e como usar semforos para implementar um cona a tador compartilhado. Uma varivel condicional uma terceiro dispositivo de a e sincronizao que GNU/Linux fornece; com variveis condicionais, voc pode ca a e implementar condicionais mais complexas sob as quais tarefas executam. Suponhamos que voc escreva uma funo que executa um lao innitae ca c mente, fazendo algum trabalho a cada iterao. O lao da tarefa , todavia, ca c precisa ser controlado por um sinalizador: o lao executa somente quando o c sinalizador est ativo; quando o sinalizador est desativado, o lao para. a a c A listagem 4.13 mostra como voc pode implementar a funo suposta e ca acima girando em um lao. Durante cada iterao do lao, a funo de tarefa c ca c ca verica se o sinalizador est ativo. Pelo fato de o sinalizador ser acessado por a vrias tarefas, ele protegido por um mutex. Essa implementao pode ser a e ca correta, mas no eciente. A funo de tarefa ir gastar recursos de CPU a e ca a sempre que sinalizador estiver dasativado, at que alguma circunstncia que e a possa fazer com que o sinalizador torne-se ativado. 112

Listagem 4.13: (spin-condvar.c) Uma Implementao Simples de Varivel ca a Condicional


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <p t h r e a d . h> extern void do wor k () ;

int t h r e a d f l a g ; pthread mutex t thread flag mutex ; void i n i t i a l i z e f l a g ( ) { p t h r e a d m u t e x i n i t (& t h r e a d f l a g m u t e x , NULL) ; thread flag = 0; } / C a l l s d o w o r k spins . / repeatedly while the thread flag is set ; otherwise

void t h r e a d f u n c t i o n { while ( 1 ) { int f l a g i s s e t ;

( void t h r e a d a r g )

/ P r o t e c t t h e f l a g w i t h a mutex l o c k . / p t h r e a d m u t e x l o c k (& t h r e a d f l a g m u t e x ) ; f l a g i s s e t = thread flag ; p t h r e a d m u t e x u n l o c k (& t h r e a d f l a g m u t e x ) ; if ( flag is set ) do wor k ( ) ; / E l s e don t do a n y t h i n g . } return NULL ; } / S e t s the value of the thread flag t o FLAG VALUE . /

Just

loop

again .

void s e t t h r e a d f l a g ( i n t f l a g v a l u e ) { / P r o t e c t t h e f l a g w i t h a mutex l o c k . / p t h r e a d m u t e x l o c k (& t h r e a d f l a g m u t e x ) ; thread flag = flag value ; p t h r e a d m u t e x u n l o c k (& t h r e a d f l a g m u t e x ) ; }

Uma varivel condicional capacita voc a implementar uma condio sob a e ca a qual uma tarefa executa e, inversamente, a condio sob a qual a tarefa ca bloqueada. Enquanto toda tarefa que podencialmente modica o senso e da condio usa a varivel condicional propriamente, GNU/Linux garante ca a que tarefas bloqueadas na condio ir ser desbloqueada quando a condio ca a ca mudar. Da mesma forma que com um semforo, uma tarefa pode esperar por a uma varivel condicional. Se tarefa A espera por uma varivel condicional, a a a tarefa A bloqueada at que alguma outra tarefa, uma tarefa B, sinalize e e a mesma varivel condicional. Diferentemente do semforo, uma varivel a a a condicional no tem contador ou memria; a tarefa A deve esperar pela a o varivel condicional antes da tarefa B sinalize essa mesma varivel condicional a a novamente. Se a tarefa B sinaliza a varivel condicional antes que a tarefa a A espere pela mesma varivel condicional, o sinal perdido, e a tarefa A a e ca bloqueada at que alguma outra tarefa sinalizae a varivel condicional e a novamente. 113

Adiante mostra-se como voc poderia usar uma varivel condicional para e a fazer a tarefa acima de forma mais eciente: O lao em thread function verica o sinalizador. Se o sinalizador est c a desativado, a tarefa espera pela varivel condicional. a a o A funo set thread ag sinaliza a varivel condicional aps modicar ca o valor do sinalizador. Por esse caminho, se o lao estiver bloqueado c na varivel condicional, ir ser desbloqueado e vericar a condicional a a a novamente. Existe um problema com isso: h uma condio de corrida entre vericar a ca o valor do sinalizador e modicar seu valor ou esperar pela varivel condicia onal. Suponhamos que thread function vericou o sinalizador e encontrou-a desabilitada. Naquele momento, o GNU/Linux agendou uma pausa para aquela tarefa e retomou a tarefa principal. Por alguma coincidncia, a tae refa pprincipal est em set thread ag. set thread ag ajusta o sinalizador e a sinaliza a varivel condicional. Pelo fato de nenhuma tarefa estar esperando a pela varivel condicional naquele momento (lembre que thread function esa tava pausada antes antes de poder esperar pela varivel condicional), o sinal a perdido. Agora, quando GNU/Linux reagenda a outra tarefa, ela inicia e esperando pela varivel condicional e pode acabar bloqueada para sempre. a Para resolver esse problema, precisamos de um caminho para travar o sinalizador e a varivel condicional juntos com um mutex simples. Afortua nadamente, GNU/Linux fornece exatamente esse mecanismo. Cada varivel a condicional deve ser usada conjuntamente com um mutex, para prevenir essa sorte condicional de corrida. Usando esse esquema, a funo de tarefa segue ca os passos abaixo: 1. O lao em thread function trava o mutex e l o valor do sinalizador. c e 2. Se o sinalizadro estiver ativado, isso desbloqueia o mutex e executa a funo de trabalho. ca 3. Se o sinalizadro estiver desativado, isso atomicamente desbloqueia o mutex e espera pela varivel condicional. a A funcionalidade cr tica aqui est no passo 3, no qual GNU/Linux permite a a voc destravar o mutex e esperar pela varivel condicional atomicamente, e a sem a possibilidade de outra tarefa interferir. Isso elimina a possibilidade que outra tarefa possa modicar o valor da varivel condicional entre o teste a de thread function do valor do sinalizador e espere pela varivel condicional. a 114

Uma varivel condicional representada por uma instncia de pthread cond t. a e a Lembrando que cada varivel condicional deve ser acompanhada de um mua tex. Abaixo temos as funes que manipulam variveis condicional: co a pthread cond init inicializa uma varivel condicional. O primeiro ara gumento um apontador para a instncia pthread cond t. O segundo e a argumento, um apontador para uma objeto de atributo de varivel a condicional , o qual ignorado em GNU/Linux. O mutex deve ser e inicializado separadamente, como descrito na Seo 4.4.2, Mutexes. ca pthread cond signal sinaliza uma varivel condicional. Uma tarefa sima ples que bloqueada conforme o estado da varivel condicional ir ser e a a desbloqueada. Se nenhuma outra tarefa estiver bloqueada conforme a varivel de condio, o sinal ignorado. O argumento um apontador a ca e e para a instncia pthread cond t. a Uma chamada similar, pthread cond broadcast, desbloqueia todos as tarefas que estiverem bloqueadas conforme a varivel condicional, ao a invs de apenas uma. e pthread cond wait bloqueia a tarefa que a est chamado at que a a e varivel de condio for sinalizada. O argumento um apontador par a ca e e a instncia pthread cond t. O segundo argumento um apontador para a instncia de mutex pthread mutex t. Quando pthread cond wait for a chamada, o mutex deve j estar travado por meio da tarefa que o chaa mou. A funo pthread cond wait atomicamente desbloqueia o mutex ca e bloqueia sob a varivel de condio. Quando a varivel de condio a ca a ca for sinalizada e a tarefa que chamou desbloquear, pthread cond wait automaticamente readquire uma trava sob o mutex. Sempre que seu programa executar uma ao que pode modicar o senso ca da condio voc est protegendo com a varivel condicional e seu programa ca e a a deve executar os passos adiante. (No nosso exemplo, a condio o estado ca e do sinalizador da tarefa, de forma que esses passos devem ser executados sempre que o sinalizador for modicado.) 1. Travar o mutex a varivel condicional. a 2. Executar a ao que pode mudar o senso da condio (no nosso exemca ca plo, ajustar o sinalizador). 3. Sinalizar ou transmitir a varivel condicional, dependendo do compora tamento desejado. 115

4. Desbloquear o mutex acompanhando a varivel condicional. a A Listagem 4.14 mostra o exemplo anterior novamente, agora usando uma varivel condicional para proteger o sinalizador da tarefa. Note que na funo a ca thread function, uma trava sob o mutex mantida antes de vericar o valor de e thread ag. Aquela trava automaticamente liberada por pthread cond wait e antes de bloquear e automaticamente readquirida posteriormente. Tambm e e note que set thread ag trava o mutex antes de ajustar o valor de thread ag e sinalizar o mutex. Listagem 4.14: (condvar.c) Control a Thread Using a Condition Variable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <p t h r e a d . h> extern void do wor k () ;

int t h r e a d f l a g ; pthread cond t thread flag cv ; pthread mutex t thread flag mutex ; void i n i t i a l i z e f l a g ( ) { / I n i t i a l i z e t h e mutex and c o n d i t i o n v a r i a b l e . p t h r e a d m u t e x i n i t (& t h r e a d f l a g m u t e x , NULL) ; p t h r e a d c o n d i n i t (& t h r e a d f l a g c v , NULL) ; / I n i t i a l i z e t h e f l a g v a l u e . / thread flag = 0; } / C a l l s d o w o r k r e p e a t e d l y the f l a g is clear . / while the thread flag

is

set ;

blocks

if

void t h r e a d f u n c t i o n ( void t h r e a d a r g ) { / Loop i n f i n i t e l y . / while ( 1 ) { / L o c k t h e mutex b e f o r e a c c e s s i n g t h e f l a g v a l u e . / p t h r e a d m u t e x l o c k (& t h r e a d f l a g m u t e x ) ; while ( ! t h r e a d f l a g ) / The f l a g i s c l e a r . Wait f o r a s i g n a l on t h e c o n d i t i o n v a r i a b l e , i n d i c a t i n g the f l a g v a l u e has changed . When t h e s i g n a l a r r i v e s and t h i s t h r e a d u n b l o c k s , l o o p and c h e c k t h e f l a g again . / p t h r e a d c o n d w a i t (& t h r e a d f l a g c v , &t h r e a d f l a g m u t e x ) ; / When we v e g o t t e n h e r e , we know t h e f l a g must b e s e t . Unlock t h e mutex . / p t h r e a d m u t e x u n l o c k (& t h r e a d f l a g m u t e x ) ; / Do some w o r k . / do wor k ( ) ; } return NULL ; } / S e t s the value of the thread flag t o FLAG VALUE . /

void s e t t h r e a d f l a g ( i n t f l a g v a l u e ) { / L o c k t h e mutex b e f o r e a c c e s s i n g t h e f l a g v a l u e . / p t h r e a d m u t e x l o c k (& t h r e a d f l a g m u t e x ) ; / S e t t h e f l a g v a l u e , and t h e n s i g n a l i n c a s e t h r e a d f u n c t i o n i s b l o c k e d , w a i t i n g f o r t h e f l a g t o become s e t . However , t h r e a d f u n c t i o n can t a c t u a l l y c h e c k t h e f l a g u n t i l t h e mutex i s unlocked . / thread flag = flag value ; p t h r e a d c o n d s i g n a l (& t h r e a d f l a g c v ) ; / U n l o c k t h e mutex . / p t h r e a d m u t e x u n l o c k (& t h r e a d f l a g m u t e x ) ; }

A condio protegida pela varivel condicional pode ser arbitrariamente ca a complexa. Todavia, antes de executar qualquer operao que possa mudar ca 116

o senso da condio, uma trava de mutex deve ser requerida, e a varivel ca a condicional deve ser sinalizada depois. Uma varivel condicional pode tambm ser usada sem uma condio, a e ca simplesmente como um mecanismo para bloquear uma tarefa at que oue tra tarefa acorde-a. Um sinalizador pode tambm ser usado para aquele e propsito. A principal diferena que um sinalizador lembrao chamada o c e para acordar mesmo se nenhuma tarefa tiver bloqueada sobre ele nauela ocasio, enquanto uma varivel condicional discarta a chamada para acora a dar a menos que alguma tarefa esteja atualmente bloqueada sob essa mesam varivel condicional naquela ocasio. Tambm, um sinalizador entrega soa a e mente um simples acorde por post; com pthread cond broadcast, um nmero u arbitrrio e desconhecido de tarefas bloqueadas pode ser acordado na mesma a ocasio. a

4.4.7

Travas Mortas com Duas ou Mais Tarefas

Travas mortas podem ocorrer quando duas (ou mais) tarefas estiverem bloqueadas, esperando que uma condio ocorra e que somente outra das duas ca (ou mais) pode fazer acontecer. Por exemplo, se uma tarefa A est bloquea ada sob uma varivel condicional esperando pela tarefa B sinalize a varivel a a condicional, e a tarefa B est bloqueada sob uma varivel de condio espea a ca rando que a tarefa A sinalize essa mesma varivel de condio, uma trava a ca morta ocorreu pelo fato de nenhuma das tarefas envolvidas ir sinalizar para a a outrar. Voc deve evitar a todo custo a possibilidade de tais stuaes pelo e co fato de elas serem bastante dif ceis de detectar. Um erro comum que causa uma trava morta envolve um problema no qual mais de uma tarefa est tentando travar o mesmo conjunto de objetos. Por a exemplo, considere um programa no qual duas diferentes tarefas, executando duas diferentes funes de tarefa, precisam travar os mesmos dosi mutexes. co Suponhamos que a tarefa A trave o mutex 1 e a seguir o mutex 2, e a tarefa B precise travar o mutex 2 antes do mutex 1. Em um sucientemente desafortunado cenrio de agendamento, GNU/Linux pode agendar a tarefa a A por um tempo suciente para travar o mutex 1, e entoagende a tarefa B, a que prontamente trava mutex 2. Agora nenhuma tarefa pode progredir pelo fato de cada uma estar bloqueada sob um mutex que a outra tarefa mantm e bloqueada. Acima temos um exemplo de um problema genrico de trava morta, que e pode envolver no somente sincronizao de objetos tais como mutexes, mas a ca tambm outros recursos, tais como travas sob arquivos ou dispositivos. O e problema ocorre quando multiplas tarefas tentam travar o mesmo conjunto de recursos em diferentes ordens. A soluo garantir que todas as tarefas ca e 117

que travam mais de um recurso trave tambm na mesma ordem. e

4.5

Implementao de Tarefa em GNU/Lica nux

A implementao de tarefas POSIX em GNU/Linux difere da implementao ca ca de tarefa de muitos outros sistemas semelhantes ao UNIX em um importante caminho: no GNU/Linux, tarefas so implementadas como processos. Sema pre que voc chamar pthread create para criar uma nova tarefa, GNU/Linux e cria um novo processo que executa aquela tarefa. Todavia, esse processo no a o mesmo que o processo criado com fork ; particularmente, o processo crie c c ado com pthread create compartilha o mesmo espao de endereo e recursos que o processo original em lugar de receber cpias. o O programa thread-pid mostra la Listagem 4.15 demonstra isso. O programa cria uma tarefa; ambas a nova tarefa e a original chamam a funo getpid e ca imprimem seus respectivos IDs de processo e ento giram innitamente. a Listagem 4.15: (thread-pid) Imprime IDs de processos para Tarefas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <p t h r e a d . h> #include <s t d i o . h> #include <u n i s t d . h> void t h r e a d f u n c t i o n ( void a r g ) { f p r i n t f ( stderr , c h i l d thread pid / S p i n f o r e v e r . / while ( 1 ) ; return NULL ; }

i s %d\n , ( i n t )

getpid

() ) ;

i n t main ( ) { pthread t thread ; f p r i n t f ( s t d e r r , main t h r e a d p i d i s %d\n , ( i n t ) g e t p i d p t h r e a d c r e a t e (& t h r e a d , NULL, &t h r e a d f u n c t i o n , NULL) ; / S p i n f o r e v e r . / while ( 1 ) ; return 0 ; }

() ) ;

Execute o programa em segundo plano, e ento chame ps x para mostrar a seus processos executando. Lembre-se de matar o programa thread-pid depois pois o mesmo consome muito da CPU sem fazer absolutamente nada. Aqui est como a sa do ps x pode parecer: a da % cc thread-pid.c -o thread-pid -lpthread % ./thread-pid \& [1] 14608 main thread pid is 14608 child thread pid is 14610 118

\% ps x PID TTY STAT 14042 pts/9 S 14608 pts/9 R 14609 pts/9 S 14610 pts/9 R 14611 pts/9 R \% kill 14608 [1]+ Terminated

TIME COMMAND 0:00 bash 0:01 ./thread-pid 0:00 ./thread-pid 0:01 ./thread-pid 0:00 ps x ./thread-pid

Noticao de Controle de Trabalho no Shell ca As linhas iniciam-se com [1] so do shell. Quando voc executa um programa a e em segundo plano, o shell atribui um nmero de trabalho para ele nesse u caso, 1 e imprime o pid do programa programa. Se o trabalho em segundo plano encerra-se, o shell mostra esse fato da prxima vez que voc chamar um o e comando.

Chamo a ateno para o fato de que existem trs processos executando o ca e programa thread-pid. O primeiro desses, com o pid 14608, a tarefa principal e no programa; o terceiro, com pid 14610, a tarefa que criamos para executar e thread function. O que dizer da segunda tarefa, com pid 14609? Essa a tarefa gee renteque parte da implementao interna de tarefas em GNU/Linux. A tae ca refa gerente criada na primeira vez que um programa chama pthread create e para criar uma nova tarefa.

4.5.1

Manipulando Sinal

Suponhamos que um programa multitarefa receba um sinal. Em qual tarefa das tarefas multiplas deve ser chamado o manipulador para esse sinal? O comportamento da interao entre sinais e tarefas varia de entre os diversos ca sistemas ooperacionais semelhantes ao UNIX. Em GNU/Linux, o comportamento ditado pelo fato de que as tarefas so implementadas como processos. e a Pelo fato de cada tarefa ser um processo separado, e pelo fato de um sinal ser entregue para um processo em particular, no existe ambiguidade sobre a qual tarefa recebe o sinal. Tipicamente, sinais enviados de fora do programa so enviados para o processo correspondente ` tarefa principal do programa. a a Por exemplo, se um programa executa forks e o processo lho faz um execs sobre um programa multitarefa, o processo pai ir manter o id de processo a da tarefa principal do programa do processo lho e ir usar aquele id de a processo para enviar sinais para seu lho. Esse comportamento geralmente e 119

uma boa conveno a seguir por voc mesmo quando enviar sinais para um ca e programa multitarefa. Note que esse aspecto da implementao em GNU/Linux das tarefas ca e uma varincia da tarefa POSIX padro. No cone nesse comportamento a a a em programas que so parecem serem portveis. a a Dentro de um programa multitarefa, pos para uma tarefa enviar um e vel sinal especicamente para outra tarefa. Use a funo pthread kill para fazer ca isso. O primeiro parmetro um ID de tarefa, e seu segundo parmetro a e a e um nmero de sinal. u

4.5.2

Chamada de Sistema clone

Embora tarefas em GNU/Linux criadas em um mesmo programa serem implementadas como processos separados, eles compartilham seu espao virtual c de memria e outros recursos. Um processo lho criado com uma operao o ca fork, todavia, recebe cpias desses itens. Como o tipo da forma do processo o e criado? A chamada de sistema GNU/Linux clone uma forma generalizada de e fork e de pthread create que permite a quem est chamando especicar quais a recursos so compartilhados entre o processo que est chamando e o processo a a criado recentemente. Tambm, clone requer que voc especique a regio e e a de memria para a pilha de execuo que o novo processo ir usar. Embora o ca a mencionemos clone aqui para satisfazer a curiosidade do leitor, essa chamada de sistema no deve frequentemente ser usada em programas. a Use fork para criar novos processos ou pthread create para criar tarefas.

4.6

Processes Vs. Threads

Para alguns programas que se beneciam da concorrncia, a deciso entre e a usar processos ou tarefas pode ser dif cil. Aqui esto algumas linhas guias a para ajudar voc a decidir qual modelo de concorrncia melhor se ajusta ao e e seu programa: Todas as tarefas em um programa devem rodar o mesmo executvel. a Um processo lho, por outro lado, pode rodar um executvel diferente a atravs da funo exec. e ca Uma tarefa errante pode prejudicar outras tarefas no mesmo processo pelo fato de tarefas compartilharem o mesmo espao de memria virtual c o e outros recursos. Por exemplo, uma brbara escrita na memria por a o meio de um ponteiro no inicializado em uma tarefa pode corromper a 120

a memria vis para outra tarefa. Um processo errante, por outro o vel lado, no pode fazer isso pelo fato de cada rocesso ter uma cpia do a o espao de memria do programa. c o A cpia de memria para um novo processo cria um trabalho adicioo o nal diminuindo a performace em comparao ` criao de uma nova ca a ca tarefa. Todavia, a cpia executada somente quando a memria moo e o e dicada, de forma que o penalti minimo se o processo lho somente e l a memria. e o Tarefas podem ser usadas por programas que precisam de paralelismo no e granulado. Por exemplo, se um problema pode ser quebrado em multiplos, trabalhos proximamente identicos, Tarefas podem ser uma boa escolha. Processos podem ser usados por programas que precisam de paralelismo rude. Compartilhando dados em torno de tarefas trivial pelo fato de tarefas e compartilharem a mesma memria (Todavia, grande cuidado deve ser o tomado para evitar condies de corrida, como descrito anteriormente). c Compartilhando dados em torno de processos requer o uso de mecanismos IPC 10 , como descrito no Cap tulo ??. Compartilhar dados em torno de processos pode ser incmodo mas faz multiplos processos o parecer menos com navegar em erros de concorrncia. e

10

Nota do tradutor:Comunicao Entre Processos ca

121

122

Cap tulo 5 Open Publication License


Version 1.0 I. Requirements on Both Unmodied and Modied Versions The Open Publication works may be reproduced and distributed in whole or in part, in any medium, physical or electronic, provided that the terms of this license are adhered to and that this license or an incorporation of it by reference (with any options elected by the author(s) and/or publisher) is displayed in the reproduction. Proper form for an incorporation by reference is as follows: Copyright year by authors name or designee.This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, vX.Y or later (the latest version is presently available at http://www.opencontent.org/openpub/). The reference must be immediately followed with any options elected by the author(s) or publisher of the document (see Section VI, License Options). Commercial redistribution of Open Publication-licensed material is permitted. Any publication in standard (paper) book form shall require the citation of the original publisher and author.The publisher and authors names shall appear on all outer surfaces of the book. On all outer surfaces of the book, the original publishers name shall be as large as the title of the work and cited as possessive with respect to the title. 306 Appendix E Open Publication License Version 1.0 II. Copyright The copyright to each Open Publication is owned by its author(s) or designee. III. Scope of License The following license terms apply to all Open Publication works, unless otherwise explicitly stated in the document. Mere aggregation of Open Publication works or a portion of an Open Publication work with other works or programs on the same media shall not cause this license to apply to those other works.The aggregate work shall contain a notice specifying the inclusion of the Open Publication material and appro123

priate copyright notice. Severability. If any part of this license is found to be unenforceable in any n jurisdiction, the remaining portions of the license remain in force. No warranty. Open Publication works are licensed and provided as iswith- n out warranty of any kind, express or implied, including, but not limited to, the implied warranties of merchantability and tness for a particular purpose or a warranty of noninfringement. IV Requirements on Modied Works . All modied versions of documents covered by this license, including translations, anthologies, compilations, and partial documents, must meet the following requirements: 1. The modied version must be labeled as such. 2. The person making the modications must be identied, and the modications must be dated. 3. Acknowledgement of the original author and publisher, if applicable, must be retained according to normal academic citation practices. 4. The location of the original unmodied document must be identied. 5. The original authors (or authors) name(s) may not be used to assert or imply endorsement of the resulting document without the original authors (or authors) permission. V Good-Practice Recommendations . In addition to the requirements of this license, it is requested from and strongly rec- ommended of redistributors that: 1. If you are distributing Open Publication works on hard copy or CD-ROM, you provide email notication to the authors of your intent to redistribute at least 30 days before your manuscript or media freeze, to give the authors time to provide updated documents.This notication should describe modications, if any, made to the document. Open Publication Policy Appendix 307 2. All substantive modications (including deletions) be either clearly marked up in the document or else described in an attachment to the document. 3. Finally, although it is not mandatory under this license, it is considered good form to oer a free copy of any hard copy and CD-ROM expression of an Open Publication-licensed work to its author(s). VI. License Options The author(s) or publisher of an Open Publicationlicensed document may elect cer- tain options by appending language to the reference to or copy of the license.These options are considered part of the license instance and must be included with the license (or its incorporation by reference) in derived works. A. To prohibit distribution of substantively modied versions without the explicit permission of the author(s). Substantive modicationis dened as a change to the semantic content of the document and excludes mere changes in format or typographical corrections. To accomplish this, add the phrase Distribution of substantively modied ver- sions of this document is prohibited without the explicit permission of the copyright holderto the license reference or copy. B. To prohibit any 124

publication of this work or derivative works in whole or in part in standard (paper) book form for commercial purposes is prohibited unless prior permission is obtained from the copyright holder. To accomplish this, add the phrase Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holderto the license reference or copy. Open Publication Policy Appendix (This is not considered part of the license.) Open Publication works are available in source format via the Open Publication home page at http://works.opencontent.org/. Open Publication authors who want to include their own license on Open Publication works may do so, as long as their terms are not more restrictive than the Open Publication license. If you have questions about the Open Publication License, please contact David Wiley, or the Open Publication Authors List at opal@opencontent.org, via email. To subscribe to the Open Publication Authors List, send email to opal-request@opencontent.org with the word subscribein the body. 308 Appendix E Open Publication License Version 1.0 To post to the Open Publication Authors List, send email to opal@opencontent.org, or simply reply to a previous post. To unsubscribe from the Open Publication Authors List, send email to opal-request@opencontent.org with the word unsubscribein the body.

125

126

Cap tulo 6 Licena de Livre Publicao c ca

1. Licena de Livre Publicao Esta uma traduo no-ocial da Open Puc ca e ca a blication Licence verso 1.0, de 8 de junho de 1999, e no substituto legal a a e para a Licena original, dispon em http://www.opencontent.org/openpub. c vel Entretanto, esta traduo poder auxiliar pessoas que falem Portugus a enca a e tender melhor a licena Openpub. c permitido a qualquer pessoa copiar e distribuir cpias desse documento E o de licena, desde que sem a implementao de qualquer mudana. c ca c OPEN PUBLIC LICENCE Draft v1.0, 8 june 1999 1.1. Requisitos comuns `s verses no-modicada a o a e modicada Os trabalhos protegidos pela Licena de Livre Publicao (Open c ca Publication Licence) podem ser reproduzidos e distribu dos no todo ou em parte, em qualquer meio f sico ou eletrnico, desde que os termos desta licena o c estejam inclu dos, e que esta licena ou uma incorporao dela por referncia c ca e (com quaisquer das opes escolhidas pelo autor ou editor) estejam presentes co na reproduo. ca A forma apropriada para uma incorporao por referncia : ca e e Note: Copyright(c) (ano) (nome do autor ou proprietrio da obra). Este a material somente poder ser distribu se sujeito aos termos e condies a do co rmados na Licena de Livre Publicao (Open Publication Licence), verso c ca a X.Y ou superior (a verso mais atual encontra-se dispon em http://www.opencontent.org/openpub/ a vel Esta referncia, devidamente preenchida com os dados da publicao, e ca deve ser seguida imediatamente com quaisquer opes escolhidas pelos autoco res ou editor do documento. E permitida a redistribuio comercial de material licenciado pela Licena ca c de Livre Publicao (Open Publication Licence). ca Qualquer publicao no formato livro padro (papel) requer obrigatorica a amente a citao dos autores e editor originais. Os nomes dos autores e do ca editor devem aparecer em todas as superf cies externas do livro. Em todas 127

as faces externas do livro, o nome do editor original deve estar impresso em tamanho to grande quanto o t a tulo do trabalho, e citado como proprietrio a em relao `quele t ca a tulo. Copyright O copyright de todo trabalho protegido pela Licena de Livre Publicao c ca (Open Publication Licence) pertence aos autores ou proprietrios. a Escopo desta licena c Os termos de licena a seguir aplicam-se a todos os trabalhos protegidos c pela Licena de Livre Publicao (Open Publication Licence), a no ser que c ca a explicitamente indicado no trabalho. A mera adio de trabalhos protegidos pela Licena de Livre Publicao ca c ca (Open Publication Licence) ou partes de trabalhos protegidos pela Licena c de Livre Publicao (Open Publication Licence) em uma mesma m ca dia que contenha outros trabalhos ou programas no protegidos por essa licena no a c a decorre em aplicao da Licena de Livre Publicao (Open Publication Lica c ca cence) para esses outros trabalhos. O trabalho resultante deve explicitamente conter uma nota especicando a incluso do material protegido pela Licena a c de Livre Publicao (Open Publication Licence) e o aviso de copyright aproca priado. APLICABILIDADE. Se alguma parte desta licena no puder ser aplic a cada em alguma jurisdio, as partes restantes deste documento continuam ca sendo aplicadas. AUSENCIA DE GARANTIA. Os trabalhos protegidos pela Licena de c Livre Publicao (Open Publication Licence) so fornecidos como esto, ca a a sem garantias de qualquer tipo, expl cita ou impl cita, incluindo, mas no a limitado a, as garantias impl citas de comercializao e convenincia para ca e um propsito particular, ou garantia de no-infrao. o a ca Requisitos para trabalhos modicados Todas as verses modicadas de documentos cobertos por esta licena, o c incluindo tradues, antologias, compilaes e documentao parcial, deve co co ca seguir os requisitos abaixo: A verso modicada deve ser indicada como tal. a As pessoas que zerem as modicaes e as datas de modicao devem co ca ser identicadas. O reconhecimento dos autores e editor originais (se aplicvel) deve ser a mantido de acordo com as prticas acadmicas usuais de citao. a e ca O local da verso no-modicada do documento deve ser indicado. a a Os nomes originais dos autores no devem ser utilizados para indicar ou a garantir seu endosso ao documento resultante sem a autorizao expressa dos ca autores. Prticas recomendadas a 128

Em adio aos requisitos desta licena, solicitado e extremamente recoca c e mendado aos redistribuidores que: Se os trabalhos protegidos pela Licena de Livre Publicao (Open Puc ca blication Licence) estiverem sendo distribu dos em impressos ou CD-ROM, os autores sejam informados por email, ao menos trinta dias antes, para que os autores tenham tempo de providenciar documentao atualizada. Esta ca noticao deve descrever as modicaoes introduzidas no documento, se ca c existirem. Todas as modicaes substanciais (incluindo excluses) devem ser marco o cadas claramente no documento, ou ento descritas em um anexo ao docua mento. Finalmente, mesmo no sendo obrigatrio sob esta licena, considerado a o c e de bom tom oferecer uma cpia sem nus de todo o material modicado o o (impresso e CD-ROM) para os autores originais. Termos opcionais Os autores e editores de documentos protegidos pela Licena de Livre c Publicao (Open Publication Licence) podem escolher certas opes de lica co cena simplesmente incluindo alguns pargrafos aps a cpia da licena ou c a o o c sua referncia. Estas opes so consideradas parte da licena e devem ser e co a c inclu das com ela (ou com a referncia a ela) nos trabalhos derivados. e As opes que se aplicam a este trabalho so: co a vedada a distribuio de verses com modicaes substanciais deste A: E ca o co documento sem a expressa permisso dos proprietrios do direito autoral. a a B: E vedada a distribuio deste trabalho ou qualquer derivado seu em ca qualquer formato de livro padro (papel) sem a prvia autorizao dos proa e ca prietrios do direito autoral. a Pol ticas de Publicaes Livres co (O texto a seguir no considerado parte da licena.) a e c Os trabalhos protegidos pela Licena de Livre Publicao (Open Public ca cation Licence) esto dispon a veis na home page da Open Publication. Os autores de trabalhos protegidos pela Licena de Livre Publicao c ca (Open Publication Licence) podem incluir suas prprias licenas nesses trao c balhos, desde que os termos dessa licena no sejam mais restritrivos que os c a da Licena de Livre Publicao (Open Publication Licence). c ca Em caso de dvidas sobre a Licena de Livre Publicao (Open Publiu c ca cation Licence), contactar David Wiley ou a lista de autores de publicaes co livres via email. Para se inscrever na lista de autores de publicaes livres (Open Publico cation Authors List), mande um email para opal-request@opencontent.org com a palavra subscribe no corpo da mensagem. 129

Para enviar mensagens para a lista de autores de publicaes livres (Open co Publication Authors List), mande um email para opal@opencontent.org ou simplesmente responda a uma mensagem postada. Para se desinscrever na lista de autores de publicaes livres (Open Publico cation Authors List), mande um email para opal-request@opencontent.org com a palavra unsubscribe no corpo da mensagem.

130

Referncias Bibliogrcas e a

131

Você também pode gostar