A E/S assíncrona do Linux é uma inclusão relativamente recente no kernel Linux. Ela é um recurso padrão do kernel 2.6, mas é possível encontrar correções para a versão 2.4. A ideia básica por trás da AIO é permitir que um processo inicie diversas operações de E/S sem precisar bloquear ou esperar a conclusão de alguma. Posteriormente, ou após a notificação da conclusão da E/S, o processo pode recuperar os resultados da E/S.
Antes de falarmos da API AIO, vamos explorar os diferentes modelos de E/S que estão disponíveis para Linux. Não pretendemos fazer uma revisão cansativa, mas sim abranger os modelos mais comuns para ilustrar suas diferenças em relação à E/S assíncrona. A Figura 1 mostra os modelos síncronos e assíncronos, bem como os modelos com e sem bloqueio.
Figura 1. Matriz Simplificada dos Modelos Básicos de E/S do Linux
Cada um desses modelos de E/S tem padrões de uso que são vantajosos para determinados aplicativos. Esta seção explora brevemente cada um deles.
Um dos modelos mais comuns é o modelo de E/S de bloqueio síncrono. Neste modelo, o aplicativo de espaço de usuário executa uma chamada do sistema que resulta no bloqueio do aplicativo. Isso significa que o aplicativo é bloqueado até que a chamada do sistema seja concluída (dados transferidos ou com erros). O aplicativo de chamada está em um estado no qual não consome CPU e simplesmente espera pela resposta, assim, se torna eficiente em uma perspectiva de processamento.
A Figura 2 ilustra o modelo de E/S de bloqueio tradicional, que também é o modelo mais comum usado atualmente em aplicativos. Seus comportamentos são bem entendidos e seu uso é eficiente para aplicativos típicos. Quando a chamada do sistema
read é invocada, o aplicativo é bloqueado e o contexto alterna para o kernel. A chamada read é então iniciada e quando a resposta é retornada (do dispositivo do qual a leitura está sendo feita), os dados são movidos para o buffer de espaço do usuário. Depois, o aplicativo é desbloqueado (e a chamada
read é retornada).
Figura 2. Fluxo Típico do Modelo de E/S de Bloqueio Síncrono
Da perspectiva do aplicativo, a chamada read tem longa duração. Mas, na verdade, o aplicativo é bloqueado enquanto a chamada read é multiplexada com outro trabalho no kernel.
Uma variante menos eficiente de bloqueio síncrono é a E/S sem bloqueio síncrono. Neste modelo, um dispositivo é aberto como sem bloqueio. Isso significa que em vez de concluir uma E/S imediatamente, uma chamada
read pode retornar um código de erro indicando que o comando não pode ser atendido imediatamente (EAGAIN ou EWOULDBLOCK), como mostrado na Figura 3.
Figura 3. Fluxo Típico do Modelo de E/S sem Bloqueio Síncrono
A implicação da falta de bloqueio é que um comando de E/S pode não ser atendido imediatamente, exigindo que o aplicativo faça várias chamadas para aguardar a conclusão. Isso pode ser extremamente ineficaz porque, em muitos casos, o aplicativo deve aguardar ocupado até que os dados fiquem disponíveis ou tentar fazer outro trabalho enquanto o comando é executado no kernel. Também como mostrado na Figura 3,
esse método pode apresentar latência na E/S pois qualquer diferença entre os dados que estão sendo disponibilizados no kernel e o usuário que chama
read para retorno pode reduzir o processamento geral dos dados.
Outro paradigma de bloqueio é a E/S sem bloqueio, com notificações de bloqueio.
Neste modelo, a E/S sem bloqueio é configurada e depois a chamada do sistema select de bloqueio é usada para determinar quando há qualquer atividade para um descritor de E/S. Isso torna a chamada
select interessante, pois ela pode ser usada para fornecer notificação não apenas para um descritor, mas para muitos. Para cada descritor, é possível solicitar notificação da capacidade do descritor em gravar dados, da disponibilidade para ler dados e também da ocorrência de um erro.
Figura 4. Fluxo Típico do Modelo de E/S de Bloqueio Assíncrono (select)
O principal problema da chamada select é que ela não é muito eficiente. Embora seja um modelo conveniente para notificação assíncrona, seu uso para E/S de alto desempenho não é aconselhado.
E/S sem Bloqueio Assíncrono (AIO)
Finalmente, o modelo de E/S sem bloqueio assíncrono é um dos processamentos de sobreposição com E/S. O pedido read é retornado imediatamente, indicando que
read foi inicializado com êxito. O aplicativo pode então desempenhar outro processamento enquanto a operação de leitura é concluída em segundo plano.
Quando a resposta read chega, um sinal ou retorno de chamada baseado no encadeamento pode ser gerado para concluir a transação de E/S.
Figura 5. Fluxo Típico do Modelo de E/S sem Bloqueio Assíncrono
A capacidade de sobreposição de computação e processamento de E/S em um único processo para que, potencialmente, vários pedidos de E/S explore a diferença entre a velocidade do processamento e a velocidade da E/S. Embora um ou mais pedidos de E/S lentos estejam pendentes, a CPU pode executar outras tarefas ou, mais comumente, operar em E/Ss já concluídas enquanto outras E/Ss são iniciadas.
A próxima seção examina mais profundamente esse modelo, explora a API e depois demonstra diversos comandos.
Na taxonomia anterior de modelos de E/S, é possível ver a motivação da AIO. Os modelos de bloqueio exigem que o aplicativo de inicialização seja bloqueado quando a E/S é iniciada. Isso significa que não é possível sobrepor o processamento e a E/S ao mesmo tempo. O modelo sem bloqueio síncrono permite sobrepor o processamento e a E/S, mas requer que o aplicativo verifique o status da E/S constantemente. Isso não é feito para a E/S sem bloqueio assíncrono, que permite a sobreposição do processamento e da E/S, incluindo a notificação da conclusão da E/S.
A funcionalidade fornecida pela função select (E/S de bloqueio assíncrono) é semelhante à AIO, exceto pelo fato de que ela ainda é bloqueada. No entanto, ela é bloqueada nas notificações, não na chamada de E/S.
Esta seção explora o modelo de E/S assíncrono para Linux, para ajudá-lo a entender como aplicá-lo em seus aplicativos.
Em um modelo tradicional de E/S, há um canal de E/S identificado por um identificador exclusivo. No UNIX®, são descritores de arquivos (que são iguais para arquivos, canais, soquetes, etc). Na E/S de bloqueio, inicialize uma transferência e a chamada do sistema é retornada quando ela é concluída ou quando ocorre um erro.
Na E/S sem bloqueio assíncrono, é possível inicializar várias transferências ao mesmo tempo. Para isso, é necessário um contexto exclusivo para cada transferência, de modo que seja possível identificá-la após a conclusão. Na
AIO, isso é uma estrutura aiocb (Bloco de Controle de E/S da AIO). Essa estrutura contém todas as informações sobre uma transferência, incluindo um buffer de usuário para dados. Quando a notificação para uma E/S ocorre (chamada de conclusão) a estrutura aiocb é fornecida para identificar exclusivamente a E/S concluída. A demonstração da API mostra como fazer isso.
A API AIO é muito simples, mas fornece as funções necessárias para a transferência de dados com alguns modelos de notificação diferentes. A Tabela 1 mostra as informações da interface AIO, que são explicadas posteriormente nesta seção.
Tabela 1. APIs AIO
| Função da API | Descrição |
|---|---|
aio_read
| Solicita uma operação de leitura assíncrona |
aio_error
| Verifica o status de um pedido assíncrono |
aio_return
| Obtém o status de retorno de um pedido assíncrono completo |
aio_write
| Solicita uma operação assíncrona |
aio_suspend
| Suspende o processo de chamada até que um ou mais pedidos assíncronos sejam concluídos (ou falhem) |
aio_cancel
| Cancela um pedido de E/S assíncrono |
lio_listio
| Inicia uma lista de operações de E/S |
Cada uma dessas funções da API usa a estrutura aiocb
para a inicialização ou verificação. Essa estrutura tem diversos elementos, mas a Lista 1 mostra apenas aqueles que serão necessários usar.
Lista 1. A Estrutura aiocb Mostrando os Campos Relevantes
struct aiocb {
int aio_fildes; // Descritor de Arquivo
int aio_lio_opcode; // Válido somente para lio_listio (r/w/nop)
volatile void *aio_buf; // Buffer de Dados
size_t aio_nbytes; // Número de Bytes no Buffer de Dados
struct sigevent aio_sigevent; // Estrutura de Notificação
/* Campos internos */
...
};
|
A estrutura sigevent informa à AIO o que fazer quando a E/S for concluída. Será explorada essa estrutura na demonstração da AIO.
Agora, eu mostrarei como as funções individuais da API para AIO funcionam e como usá-las.
A função aio_read solicita uma operação de leitura assíncrona para um descritor de arquivo válido. O descritor de arquivo pode representar um arquivo, um soquete ou até mesmo um canal. A função aio_read
possui o seguinte protótipo:
int aio_read( struct aiocb *aiocbp ); |
A função aio_read é retornada imediatamente depois que o pedido é enfileirado. O valor de retorno é zero para êxito ou -1 para erro, em que errno é definido.
Para executar read, o aplicativo deve inicializar a estrutura
aiocb. O seguinte breve exemplo ilustra o preenchimento na estrutura de pedido
aiocb e o uso de
aio_read para executar um pedido read assíncrono (agora ignore a notificação).
Ele também mostra o uso da função aio_error, mas eu a explicarei posteriormente.
Lista 2. Código de Amostra para um Pedido read Assíncrono com aio_read
#include <aio.h>
...
int fd, ret;
struct aiocb my_aiocb;
fd = open( "file.txt", O_RDONLY );
if (fd < 0) perror("open");
/* Zerar estrutura aiocb (recomendado) */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
/* Alocar um buffer de dados para o pedido aiocb */
my_aiocb.aio_buf = malloc(BUFSIZE+1);
if (!my_aiocb.aio_buf) perror("malloc");
/* Inicializar os campos necessários no aiocb */
my_aiocb.aio_fildes = fd;
my_aiocb.aio_nbytes = BUFSIZE;
my_aiocb.aio_offset = 0;
ret = aio_read( &my_aiocb );
if (ret < 0) perror("aio_read");
while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;
if ((ret = aio_return( &my_iocb )) > 0) {
/* obter bytes ret em read */
} else {
/* read com falha, consultar errno */
}
|
Na Lista 2, depois do arquivo do qual os dados de leitura estão sendo abertos, limpe sua estrutura aiocb e depois aloque um buffer de dados. A referência para o buffer de dados é colocada em
aio_buf. Subsequentemente, inicie o tamanho do buffer em aio_nbytes. O aio_offset é configurado como zero (o primeiro deslocamento no arquivo). Configure o descritor de arquivo do qual a leitura está sendo feito em
aio_fildes. Depois de configurar esses campos, chame
aio_read para solicitar a leitura. É possível então fazer uma chamada para aio_error para determinar o status de
aio_read. Assim que o status for
EINPROGRESS, aguarde até que ele mude. Aqui, seu pedido terá sido bem-sucedido ou terá falhado.
Observe as similaridades ao ler a partir do arquivo com as funções da biblioteca padrão. Além da natureza assíncrona de aio_read, outra diferença é configurar o deslocamento para read. Em uma chamada
read típica, o deslocamento é mantido para você no contexto do descritor de arquivo. Para cada read, o deslocamento é atualizado para que reads subsequentes destinem o próximo bloco de dados. Isso não é possível com a E/S assíncrona porque é possível executar muitos pedidos read simultaneamente e, portanto, deve especificar o deslocamento para cada pedido read específico.
A função aio_error é utilizada para determinar o status de um pedido. Seu protótipo é:
int aio_error( struct aiocb *aiocbp ); |
Esta função pode retornar o seguinte:
-
EINPROGRESS, indicando que o pedido ainda não foi concluído -
ECANCELLED, indicando que o pedido foi cancelado pelo aplicativo -
-1, indicando que ocorreu um erro ao qual é possível consultar oerrno
Outra diferença entre a E/S assíncrona e a E/S de bloqueio padrão é que você não tem acesso imediato ao status de retorno de sua função porque não está fazendo o bloqueio na chamada read. Em uma chamada
read padrão, o status de retorno é fornecido no retorno da função. Com a E/S assíncrona, você usa a função
aio_return. Essa função tem o seguinte protótipo:
ssize_t aio_return( struct aiocb *aiocbp ); |
Esta função é chamada somente depois que a chamada aio_error
determinar que seu pedido foi concluído (com êxito ou com erro). O valor de retorno de aio_return é
idêntico aos sitemas de chamadas read ou write em contexto assíncrono (número de bytes transferidos ou -1 para erro).
A função aio_write é utilizada para solicitar uma gravação assíncrona. Seu protótipo de função é:
int aio_write( struct aiocb *aiocbp ); |
A função aio_write retorna imediatamente, indicando que o pedido foi enfileirado (com um retorno de 0 para êxito e -1 para falha, com errno corretamente definido).
Isso é semelhante à chamada do sistema read, embora uma diferença de comportamento seja digna de observação. Lembre-se de que o deslocamento a ser utilizado é importante com a chamada read. No entanto, com
write, o deslocamento só será importante se utilizado em um contexto de arquivo no qual a opção O_APPEND não está configurada.
Se
O_APPEND estiver configurada, então o deslocamento será ignorado e os dados serão anexados ao final do arquivo. Caso contrário, o campo aio_offset determina o deslocamento no qual os dados serão gravados no arquivo.
É possível utilizar a função aio_suspend para suspender (ou bloquear) o processo de chamada até que um pedido de E/S assíncrono seja concluído, um sinal seja mostrado ou ocorra um tempo de espera opcional. O responsável pela chamada fornece uma lista de referências
aiocb à qual a conclusão de pelo menos uma fará com que aio_suspend seja retornado. O protótipo de função para aio_suspend é:
int aio_suspend( const struct aiocb *const cblist[],
int n, const struct timespec *timeout );
|
Utilizar
aio_suspend é muito simples. Uma lista de referências
aiocb é fornecida. Se alguma delas for concluída, a chamada será retornada com 0. Caso contrário, -1 será retornado, indicando que ocorreu um erro. Consulte a Lista 3.
Lista 3. Utilizando a Função aio_suspend para Bloqueio em E/Ss Assíncronas
struct aioct *cblist[MAX_LIST]
/* Limpar a lista. */
bzero( (char *)cblist, sizeof(cblist) );
/* Carregar uma ou mais referências na lista */
cblist[0] = &my_aiocb;
ret = aio_read( &my_aiocb );
ret = aio_suspend( cblist, MAX_LIST, NULL );
|
Observe que o segundo argumento de aio_suspend são diversos elementos em cblist, não diversas referências aiocb. Qualquer elemento NULL em
cblist é ignorado por
aio_suspend.
Se um tempo de espera for fornecido para aio_suspend e ocorrer esse tempo de espera, -1será retornado e errno conterá
EAGAIN.
A função aio_cancel permite cancelar um ou todos os pedidos de E/S pendentes para um determinado descritor de arquivo. Seu protótipo é:
int aio_cancel( int fd, struct aiocb *aiocbp ); |
Para cancelar um único pedido, forneça o descritor de arquivo e a referência
aiocb. Se o pedido for cancelado com êxito, a função retorna AIO_CANCELED. Se o pedido for concluído, a função retorna AIO_NOTCANCELED.
Para cancelar todos os pedidos para um determinado descritor de arquivo, forneça esse descritor de arquivo e uma referência NULL para aiocbp. A função retorna AIO_CANCELED se todos os pedidos forem cancelados, AIO_NOT_CANCELED se pelo menos um pedido não puder ser cancelado e AIO_ALLDONE se nenhum dos pedidos puder ser cancelado. É possível então avaliar cada pedido individual da AIO utilizando
aio_error. Se o pedido tiver sido cancelado,
aio_error retornará -1 e errno será configurado como
ECANCELED.
Finalmente, a AIO permite iniciar várias transferências ao mesmo tempo utilizando a função de API lio_listio. Essa função é importante porque significa que é possível iniciar muitas E/Ss no contexto de uma única chamada do sistema (o que significa um comutador de contexto do kernel). Isso é ótimo em uma perspectiva de desempenho, portanto, vamos explorá-la. A função de API
lio_listio tem o seguinte protótipo:
int lio_listio( int mode, struct aiocb *list[], int nent,
struct sigevent *sig );
|
O argumento mode pode ser
LIO_WAIT ou LIO_NOWAIT. LIO_WAIT bloqueia a chamada até que todas as E/S sejam concluídas. LIO_NOWAIT retorna depois que as operações são enfileiradas. A lista é uma lista de referências aiocb, com o número máximo de
elementos definidos por nent. Observe que os elementos da
lista podem ser NULL, que
lio_listio ignora. A referência
sigevent define o método para a notificação de sinal quando todas as E/Ss estiverem concluídas.
O pedido para lio_listio é um pouco diferente do pedido read ou
write no qual a operação deve ser especificada. Isso é ilustrado na Lista 4.
Lista 4. Utilizando a Função lio_listio para Iniciar uma Lista de Pedidos
struct aiocb aiocb1, aiocb2;
struct aiocb *list[MAX_LIST];
...
/* Preparar o primeiro aiocb */
aiocb1.aio_fildes = fd;
aiocb1.aio_buf = malloc( BUFSIZE+1 );
aiocb1.aio_nbytes = BUFSIZE;
aiocb1.aio_offset = next_offset;
aiocb1.aio_lio_opcode = LIO_READ;
...
bzero( (char *)list, sizeof(list) );
list[0] = &aiocb1;
list[1] = &aiocb2;
ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );
|
A operação de leitura é anotada no campo aio_lio_opcode com LIO_READ. Para uma operação de escrita,
LIO_WRITE é usado, mas
LIO_NOP também é válido para nenhuma operação.
Agora que foram vistas as funções AIO que estão disponíveis, esta seção apresenta os métodos que é possível utilizar para a notificação assíncrona. Eu explorarei a notificação assíncrona através de sinais e retornos de chamadas de funções.
Notificação Assíncrona com Sinais
O uso de sinais para a Interprocess Communication (IPC) é um mecanismo tradicional no UNIX e também é suportado pela AIO. Neste paradigma, o aplicativo define um manipulador de sinal que é chamado quando ocorre um sinal especificado. O aplicativo especifica então que um pedido assíncrono mandará um sinal quando o pedido estiver concluído. Como parte do contexto de sinal, o pedido
aiocb específico é fornecido para rastrear vários pedidos possivelmente pendentes. A Lista 5 demonstra esse método de notificação.
Lista 5. Usando Sinais como Notificação para Pedidos da AIO
void setup_io( ... )
{
int fd;
struct sigaction sig_act;
struct aiocb my_aiocb;
...
/* Configurar o manipulador de sinal */
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_SIGINFO;
sig_act.sa_sigaction = aio_completion_handler;
/* Configurar o pedido da AIO */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset;
/* Vincular o pedido da AIO com o manipulador de sinal */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
my_aiocb.aio_sigevent.sigev_signo = SIGIO;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
/* Mapear o sinal para o manipulador de sinal */
ret = sigaction( SIGIO, &sig_act, NULL );
...
ret = aio_read( &my_aiocb );
}
void aio_completion_handler( int signo, siginfo_t *info, void *context )
{
struct aiocb *req;
/* Garantir que este seja nosso sinal */
if (info->si_signo == SIGIO) {
req = (struct aiocb *)info->si_value.sival_ptr;
/* O pedido foi concluído? */
if (aio_error( req ) == 0) {
/* Pedido concluído com êxito, obter o status de retorno */
ret = aio_return( req );
}
}
return;
}
|
Na Lista 5, você configura seu manipulador de sinal para capturar o sinal SIGIO na função aio_completion_handler. Você então inicializa a estrutura
aio_sigevent para mostrar
SIGIO para a notificação (que é especificada por meio da definição
SIGEV_SIGNAL em
sigev_notify). Quando read é concluído, seu manipulador de sinal extrai o aiocb específico da estrutura si_value do sinal, verifica o status do erro e retorna o status para determinar a conclusão da E/S.
Para desempenho, o manipulador de conclusão é ideal para dar continuidade à E/S, solicitando a próxima transferência assíncrona. Dessa forma, quando a conclusão é uma transferência é feita, você imediatamente inicia a próxima.
Notificação Assíncrona com Retornos de Chamada
Um mecanismo de notificação alternativo é o retorno de chamada do sistema. Em vez de mostrar um sinal para notificação, esse mecanismo chama uma função no espaço de usuário para notificação. Você inicializa a referência
aiocb na estrutura
sigevent para identificar exclusivamente o pedido específico que está sendo concluído; consulte a Lista 6.
Lista 6. Utilizando a Notificação de Retorno de Chamada de Encadeamento para Pedidos AIO
void setup_io( ... )
{
int fd;
struct aiocb my_aiocb;
...
/* Configurar o pedido da AIO */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset;
/* Vincular o pedido da AIO com um retorno de chamada do encadeamento */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
my_aiocb.aio_sigevent.notify_attributes = NULL;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
...
ret = aio_read( &my_aiocb );
}
void aio_completion_handler( sigval_t sigval )
{
struct aiocb *req;
req = (struct aiocb *)sigval.sival_ptr;
/* O pedido foi concluído? */
if (aio_error( req ) == 0) {
/* Pedido concluído com êxito, obter o status de retorno */
ret = aio_return( req );
}
return;
}
|
Na Lista 6, depois de criar seu pedido aiocb, é possível solicitar um retorno de chamada de encadeamento usando SIGEV_THREAD para o método de notificação.
Você então especifica o manipulador de notificações específico e carrega o contexto a ser transmitido para o manipulador (neste caso, uma referência ao próprio pedido
aiocb). No manipulador, você simplesmente distribui o ponteiro sigval recebido e utiliza as funções da AIO para validar a conclusão do pedido.
O sistema de arquivos proc contém dois arquivos virtuais que podem ser ajustados para o desempenho da E/S assíncrona:
- O arquivo /proc/sys/fs/aio-nr fornece o número atual de pedidos de E/S assíncronos de todo o sistema.
- O arquivo /proc/sys/fs/aio-max-nr é o número máximo de pedidos simultâneos permitidos. O máximo geralmente é 64KB, que é adequado a maioria dos aplicativos.
Utilizar a E/S assíncrona pode ajudá-lo a criar aplicativos de E/S mais rápidos e eficientes. Se seu aplicativo puder sobrepor o processamento e a E/S, então a AIO poderá ajudá-lo a criar um aplicativo que utilize, de forma mais eficiente, os recursos da CPU disponíveis para você. Embora esse modelo de E/S seja diferente dos padrões de bloqueio tradicionais encontrados na maioria dos aplicativos Linux, o modelo de notificação assíncrono é conceitualmente simples e pode simplificar seu design.
Aprender
-
A implementação de POSIX.1b explica os detalhes internos da AIO da perspectiva da Biblioteca GNU.
-
Suporte em Tempo Real no Linux explica mais sobre a AIO e diversas extensões em tempo real, do planejamento e E/S de POSIX a encadeamentos de POSIX e cronômetros de alta resolução (HRT).
-
Na zona Linux do Notas Sobre o Design
para a integração 2.5, obtenha informações sobre design e implementação da AIO no Linux.
-
Na zona Linux do developerWorks, encontre mais recursos para desenvolvedores Linux.
-
Fique atualizado com eventos técnicos e Webcasts do developerWorks.
Obter produtos e tecnologias
-
Com o Software de período experimental IBM, disponível para download diretamente do developerWorks, construa seu próximo projeto de desenvolvimento em Linux.
Discutir
-
Verifique blogs do developerWorks e envolva-se com a comunidade do developerWorks.

M. Tim Jones é um arquiteto de firmwares embarcados e autor de Inteligência Artificial: Sistemas de Abordagem, GNU/Linux, Programação de Aplicativos AI (atualmente em sua segunda edição), Programação de Aplicativos AI (em sua segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Sua formação em engenharia vai desde o desenvolvimento de kernels para nave espacial geossincrônica até a arquitetura de sistema embarcado e o desenvolvimento de protocolos de interligação de redes. Tim é um Engenheiro Consultor para a Emulex Corp. em Longmont, Colorado.