Arquivos - Tratamento Baixo Nível - Programação 1 - Engenharia
Objetivos
- Funções de acesso a arquivos em baixo nível
- Acesso a múltiplos arquivos usando select()
Funções de acesso a arquivos em baixo nível.
Alguns conceitos iniciais
No acesso a baixo nível, as funções da biblioteca basicamente chamam o sistema operacional (via SYSTEM CALLs) sem qualquer intervenção. Qualquer arquivo será referenciado por um número inteiro que reflete o índice em uma TABELA DE ARQUIVOS abertos, mantida pelo sistema operacional para cada processo execução.
Tipicamente, quando um processo é iniciado ele já possui a tabela de arquivos abertos populada nas três primeiros posições. A posição 0 corresponde a entrada padrão (normalmente será o terminal associado), a posição 1 (saída padrão, também o terminal) e a saída de erro padrão (também o terminal).
Exemplo: O comando ls é implementado na forma de um programa que quando se executa se torna um processo no sistema com os arquivos de entrada e saída padrão abertos. Faça:
ls -l
A listagem sai no arquivo de saída padrão. Você poderia redirecionar esta saída:
ls -l > lista.txt
Se você tentar listar algo que não existe é um erro. A mensagem de erro é remetida para saída de erro padrão.
ls -l /gdhfgjdkhsjdf
Não adianta redirecionar a saída padrão que a mensagem continua a aparecer.
ls -l /fsadasdfsadf 1> lista.txt
Note que o 1 no redirecionador 1> pode ser omitido.
Mas é possível redirecionar a saída de erros também:
ls -l /fdsdfgsdfgfsdg 2> erro.txt
Abra um outro terminal e verifique com o comando ps, o terminal virtual associado. Suponha que seja /dev/pts/1
Redirecione esta saída para p terminal virtual do outro terminal:
ls -l > /dev/pts/1
Funções de acesso
O acesso é basicamente realizado por funções open(), close(), read() e write() (Ver aqui o manual da glib).
Escrevendo na saída padrão e de erro usando write()
#include <unistd.h>
#include <stdlib.h>
int main()
{
write(1, "Escrevendo dados na saída padrão\n", 35);
write(2, "Agora escrevendo na saída de erros\n",37);
exit(0);
}
Agora execute testando as redireções:
./esc
./esc 1> lixo.dat
./esc 1> lixo.dat 2> erro.dat
Vamos fazer um exemplo de cópia de arquivos passados como parâmetro:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
char c;
int in, out;
in = open("file.in", O_RDONLY);
out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
while(read(in,&c,1) == 1)
write(out,&c,1);
exit(0);
}
Exercício 1: Implementar um programa de cópia similar ao que existe no linux. Capture o nome dos arquivos da linha de comando e inclua testes de erro na abertura de arquivo.
Exercício 2: Modificar o exercício anterior para realizar múltiplas cópias (um arquivo para vários).
Acesso a múltiplos arquivos usando select()
Por vezes é necessário esperar dados de várias fontes diferentes. A função select permite testar a existência de dados em uma dada fonte de dados (arquivo). A deia geral é abrir os arquivos de interesse e colocar o descritor em conjunto do tipo fd_set. O programa ao executar o select sobre este conjunto é BLOQUEADO até que exista dados disponíveis em um dos arquivos. Uma macro FD_ISSET executada sobre o conjunto de descritores de arquivo permite detectar em qual arquivo foi alterado o status.
No exemplo a seguir, você deve fornecer para o programa quais terminais virtuais deseja escutar. O programa pergunta por um usuário em cada terminal. O usuário fornece seu nome e recebe em troca uma mensagem de boas vindas.
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/select.h>
char *MsgLogin="Entre com USERID\n";
int main(int argc, char *argv[])
{
int fd;
int fdmax;
int i,j;
size_t bytes_read;
fd_set master_set, myset;
char buffer[256];
/* Limpa o conjunto de descritores */
FD_ZERO(&master_set);
/* abre cada arquivo passado na linha de comando e insere no conjunto master_set */
for (i=1;i<argc;i++) {
if ((fd=open(argv[i],O_RDWR))==-1) {
perror("open");
exit(1);
}
FD_SET(fd,&master_set);
write (fd,MsgLogin,strlen(MsgLogin));
}
/* registra o maior descritor */
fdmax=fd;
for (;;) {
myset=master_set; /* deve ser copiado sempre pois o conjunto é alterado */
if (select(fdmax+1,&myset,NULL,NULL,NULL)==-1) {
perror("select");
exit(1);
}
/* rastreia o descritor onde houve entrada de dados */
for (i=0;i<=fdmax;i++) {
if (FD_ISSET(i,&myset)) {
if ((bytes_read=read(i,buffer,sizeof(buffer)))==-1) {
perror("read");
exit(1);
}
if (bytes_read>0) {
write (i, "Bom Dia ", 8);
for (j=0;j<bytes_read;j++) {
write(i, &buffer[j], 1);
}
buffer[j-1]=0; /* eliminar o Line feed e colocar NULL no final da string */
if (!strcmp("f", buffer))
goto final; /* ok um goto para polemizar... */
write (i,"\n",1);
}
}
}
}
final:
for (i=0;i<=fdmax;i++) {
close(i);
}
return 0;
}
Para executar o programa faã o seguinte:
- abra um terminal, e execute o comando ps no terminal. Verifique qual TTY está associado ao seu terminal, por exemplo pts/0
- abra outro terminal e também verifique o terminal com ps. Por exemplo, pts/1
- neste último terminal bloqueio o shell fazendo: while true; do sleep 3600; done </dev/null
- volte ao terminal original e execute o programa da forma:
readmult /dev/pts/0 /dev/pts/1
- entre com um userid em cada terminal e verifique o resultado
- para finalizar tecle f como userid