APIs do Kernel, Parte 3: Timers e listas no kernel 2.6

Processamento eficiente com APIs de adiamento de trabalho

O kernel Linux® inclui uma variedade de APIs voltadas para ajudar os desenvolvedores a construir aplicativos de kernel e driver mais simples e eficientes. Duas das APIs mais comuns que podem ser usadas para o adiamento de trabalho são as APIs de timers e gerenciamento de listas. Descubra essas APIs e aprenda a desenvolver aplicativos de kernel com timers e listas.

M. Tim Jones, Consultant Engineer, Emulex Corp.

M. Tim JonesM. Tim Jones é arquiteto de firmware integrado e autor das obras Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (agora, na segunda edição), AI Application Programming (na segunda edição) e BSD Sockets Programming from a Multilanguage Perspective. Sua experiência em engenharia vai desde o desenvolvimento de kernels para espaçonaves geossíncronas até a arquitetura de sistemas integrados e o desenvolvimento de protocolos de rede. Tim é engenheiro consultor da Emulex Corp. em Longmont, Colorado.



26/Abr/2010

Entre em contato com Tim

Tim é um de nossos autores mais conhecidos e prolíficos. Navegue por todos os artigos de Tim no developerWorks. Confira o perfil de Tim e entre em contato com ele, com outros autores e com leitores no My developerWorks.

Este artigo continua a tratar do tópico de adiamento de trabalho, que comecei em "Kernel APIs, Part 2: Deferrable functions, kernel tasklets, and work queues" (developerWorks, março de 2010). Desta vez, discutirei a application programming interface (API) de timer e um dos elementos centrais para todos os esquemas de adiamento de trabalho, a construção de lista de kernel. Também vou explorar a API de lista de kernel, usada por timers e outros mecanismos de adiamento de trabalho, como as filas de trabalhos.

Timers são parte integrante de qualquer sistema operacional, e você encontrará vários mecanismos de cronometragem. Vamos começar com uma rápida visão geral dos esquemas de timers do Linux, antes de nos aprofundar nas suas operações.

A origem do tempo (do Linux)

No kernel Linux, o tempo é medido por uma variável global chamada jiffies, que identifica o número de pulsos que ocorreram desde que o sistema foi inicializado. O modo pelo qual os pulsos são contados depende, no seu nível mais fundamental, da plataforma de hardware específica em que o kernel Linux está sendo executado; porém, ele é normalmente incrementado por meio de uma interrupção. A taxa de pulsação (a parte menos significante da jiffies) é configurável, mas em um recente kernel 2.6 para x86, um pulso equivale a 4ms (250Hz). A variável global jiffies é amplamente usada no kernel para vários propósitos, um dos quais é o tempo absoluto atual para calcular o valor do tempo limite para um timer (você verá exemplos disso mais adiante).


Timers do kernel

Há poucos esquemas diferentes para timers nos kernels 2.6 recentes. O mais simples e menos preciso de todos os timers (mas ainda assim adequado na maioria dos casos) é a API de timer. Essa API permite a construção de timers que operam no domínio jiffies (tempo limite mínimo de 4ms). Há também a API de timer de alta resolução, que permite construções de timers nas quais o tempo é definido em nanossegundos (bilionésimos de segundo). Dependendo do seu processador e da velocidade em que ele opera, a sua milhagem pode variar, mas a API oferece um modo de planejar limites de tempo abaixo do intervalo dos pulsos da jiffies.

Timers padrão

A API de timer padrão faz parte do kernel Linux há muito tempo (desde as versões mais antigas do kernel Linux). Embora ofereça menos precisão do que os timers de alta resolução, ela é ideal para os limites de tempo de drivers tradicionais, que fornecem cobertura de casos de erros ao lidar com dispositivos físicos. Em muitos casos, esses limites de tempo nunca disparam de verdade, mas sim são iniciados e subsequentemente removidos.

Timers de kernel simples são implementados usando a timer wheel. A ideia foi apresentada originalmente em 1997 por Finn Arne Gangstad. Ela ignora o problema de gerenciar um grande número de timers, mas faz um bom trabalho em gerenciar um número razoável de timers—o caso típico. (A implementação original do timer simplesmente mantinha os timers duplamente vinculados em ordem de expiração. Embora simples em conceito, a abordagem não era escalável.) A timer wheel é uma coleção de depósitos, na qual cada depósito representa um período de tempo no futuro para a expiração do timer. Os depósitos são definidos através do uso de tempo logarítmico com base em cinco depósitos. Usando jiffies como a granularidade de tempo, vários grupos são definidos, representando períodos de expiração futuros (onde cada grupo é representado por uma lista de timers). A inserção de timers ocorre por meio do uso de operações de listas que são de complexidade O(1), com expiração ocorrendo no tempo O(N). A expiração de timers ocorre em uma operação em cascata, na qual os timers são removidos dos depósitos de granularidade mais alta e inseridos em depósitos de granularidade mais baixa, conforme seus tempos de expiração diminuem. Agora vamos dar uma olhada na API para essa implementação de timer.

A API de timer

O Linux fornece uma API simples para a construção e o gerenciamento de timers. Ela consiste de funções (e funções de ajuda) para a criação, o cancelamento e o gerenciamento de timers.

Os timers são definidos pela estrutura timer_list, que inclui todos os dados necessários para implementar um timer (incluindo ponteiros de listas e estatísticas opcionais de timers, que são configuradas no tempo de compilação). A partir da perspectiva do usuário, a estrutura timer_list contém um prazo de expiração, uma função de retorno de chamada (quando/se o timer expirar), e um contexto fornecido pelo usuário. O usuário deve então inicializar o timer, o que pode ser feito de algumas maneiras diferentes. O método mais simples é uma chamada para setup_timer, que inicializa o timer e define a função de retorno de chamada e o contexto fornecidos pelo usuário. Como alternativa, o usuário pode definir esses valores (função e dados) no timer e simplesmente chamar init_timer. Observe que init_timer é chamado internamente por setup_timer"

void init_timer( struct timer_list *timer );
void setup_timer( struct timer_list *timer, 
                     void (*function)(unsigned long), unsigned long data );

Com o timer inicializado, o usuário agora deve definir o prazo de expiração, o que é feito através de uma chamada para mod_timer. Como os usuários normalmente fornecem um prazo de expiração no futuro, eles tipicamente adicionam jiffies aqui para deslocar do tempo atual. Os usuários também podem excluir um timer (caso não tenha expirado) através de uma chamada para del_timer:

int mod_timer( struct timer_list *timer, unsigned long expires );
void del_timer( struct timer_list *timer );

Finalmente, os usuários podem determinar se o timer está pendente (ainda não disparado) através de uma chamada para timer_pending (1 retorna caso o timer esteja pendente):

int timer_pending( const struct timer_list *timer );

Exemplo de timer

Vamos dar uma olhada em algumas dessas funções da API na prática. A Listagem 1 fornece um módulo do kernel simples, que demonstra os principais aspectos da API de timer simples. Dentro de init_module, inicializamos um timer com setup_timer e então o colocamos para funcionar com uma chamada para mod_timer. Quando o timer expira, a função de retorno de chamada (my_timer_callback) é invocada. Finalmente, a exclusão do timer (via del_timer) ocorre quando removemos o módulo. (Observe a verificação de retorno de del_timer, que identifica se o timer ainda está em uso.)

Listagem 1. Explorando a API de timer simples
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

static struct timer_list my_timer;

void my_timer_callback( unsigned long data )
{
  printk( "my_timer_callback called (%ld).\n", jiffies );
}

int init_module( void )
{
  int ret;

  printk("Timer module installing\n");

  // my_timer.function, my_timer.data
  setup_timer( &my_timer, my_timer_callback, 0 );

  printk( "Starting timer to fire in 200ms (%ld)\n", jiffies );
  ret = mod_timer( &my_timer, jiffies + msecs_to_jiffies(200) );
  if (ret) printk("Error in mod_timer\n");

  return 0;
}

void cleanup_module( void )
{
  int ret;

  ret = del_timer( &my_timer );
  if (ret) printk("The timer is still in use...\n");

  printk("Timer module uninstalling\n");

  return;
}

É possível aprender mais sobre a API de timer em ./include/linux/timer.h. Embora a API de timer simples seja fácil e eficiente, ela não oferece a precisão requerida para aplicativos em tempo real. Para isso, vamos dar uma olhada em uma recente adição ao Linux que suporta timers de resolução mais alta.

Timers de alta resolução

Timers de alta resolução (ou hrtimers) fornecem uma estrutura de alta precisão para o gerenciamento de timers independente da estrutura de timers que acabamos de discutir, devido à complexidade de mesclar as duas estruturas. Embora os timers operem na granularidade da jiffies, hrtimers operam na granularidade dos nanossegundos.

A estrutura do hrtimer é implementada de modo diferente da API de timer tradicional. No lugar de depósitos e cascatas de timers, hrtimers mantém uma estrutura de dados de timers ordenada de acordo com o tempo (os timers são inseridos em ordem de tempo para minimizar o processamento durante o tempo de ativação). A estrutura de dados usada é uma árvore vermelha-preta, que é ideal para aplicativos focados em desempenho (e está disponível genericamente como uma biblioteca no kernel).

A estrutura hrtimer está disponível como uma API no kernel e também é usada por aplicativos de espaço do usuário através de nanosleep, itimers e da interface de timers da Portable Operating System Interface (POSIX). Ela foi injetada no kernel 2.6.21.

API de timer de alta resolução

A API hrtimer apresenta algumas semelhanças com a API tradicional, assim como algumas diferenças fundamentais para lidar com o controle de tempo adicional. A primeira coisa que notamos é que o tempo não é representado em jiffies, mas sim em um tipo de dado especial chamado ktime. Essa representação esconde alguns dos detalhes sobre como gerenciar o tempo de forma eficiente nessa granularidade. A API formaliza a distinção entre tempo absoluto e relativo, requerendo que o responsável pela chamada especifique o tipo.

Como a API tradicional de timer, os timers são representados por uma estrutura—neste caso, hrtimer. Essa estrutura define o timer a partir da perspectiva do usuário (função de retorno de chamada, prazo de expiração, etc.) e também incorpora as informações de gerenciamento (onde o timer está na árvore vermelha-preta, estatísticas opcionais, etc.).

O processo começa com a inicialização de um timer através de hrtimer_init. A chamada inclui o timer, a definição do clock e um modo de timer ("único" ou "reiniciar"). O clock a ser usado é definido em ./include/linux/time.h e representa os vários clocks que o sistema suporta (como o clock em tempo real ou um clock monotônico, que simplesmente representa o tempo a partir de um ponto de partida, como o boot do sistema.) Uma vez que um timer tenha sido inicializado, ele pode ser iniciado com hrtimer_start. Essa chamada inclui o prazo de expiração (em ktime_t) e o modo do valor de tempo (valor absoluto ou relativo).

void hrtimer_init( struct hrtimer *time, clockid_t which_clock, 
			enum hrtimer_mode mode );
int hrtimer_start(struct hrtimer *timer, ktime_t time, const 
			enum hrtimer_mode mode);

Uma vez que um hrtimer tenha sido iniciado, ele pode ser cancelado através de uma chamada para hrtimer_cancel ou hrtimer_try_to_cancel. Cada função inclui a referência hrtimer assim como o timer a ser interrompido. Essas funções diferem no sentido de que a função hrtimer_cancel tenta cancelar o timer, mas caso este já tenha disparado, ela esperará a função de retorno de chamada acabar. A função hrtimer_try_to_cancel também tenta cancelar o timer, mas é diferente por retornar uma falha caso o timer já tenha disparado.

int hrtimer_cancel(struct hrtimer *timer);
int hrtimer_try_to_cancel(struct hrtimer *timer);

É possível verificar se o hrtimer ativou o seu retorno de chamada através de um chamada para hrtimer_callback_running. Observe que essa função é chamada internamente por hrtimer_try_to_cancel, visando retornar um erro se a função de retorno de chamada do timer for cancelada.

int hrtimer_callback_running(struct hrtimer *timer);

A API ktime

Ainda não discutida aqui, a API ktime oferece um rico conjunto de funções para gerenciar o tempo em alta resolução. É possível ver a API ktime em ./linux/include/ktime.h.

Um exemplo de hrtimer

Usar a API hrtimer é muito simples, como exibido na Listagem 2. Em init_module, começamos definindo o tempo relativo para o tempo limite (neste caso, 200ms). Então inicializamos o nosso hrtimer com uma chamada para hrtimer_init (usando o clock monotônico), e, em seguida, definimos a função de retorno de chamada. Finalmente, iniciamos o timer usando o valor ktime criado anteriormente. Quando o timer dispara, a função my_hrtimer_callback é chamada, retornando HRTIMER_NORESTART, de modo que o timer não é reiniciado automaticamente. Na função cleanup_module, a limpeza é feita através do cancelamento do timer com hrtimer_cancel.

Listagem 2. Explorando a API hrtimer
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>

MODULE_LICENSE("GPL");

#define MS_TO_NS(x)	(x * 1E6L)

static struct hrtimer hr_timer;

enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
{
  printk( "my_hrtimer_callback called (%ld).\n", jiffies );

  return HRTIMER_NORESTART;
}

int init_module( void )
{
  ktime_t ktime;
  unsigned long delay_in_ms = 200L;

  printk("HR Timer module installing\n");

  ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );

  hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
  
  hr_timer.function = &my_hrtimer_callback;

  printk( "Starting timer to fire in %ldms (%ld)\n", delay_in_ms, jiffies );

  hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );

  return 0;
}

void cleanup_module( void )
{
  int ret;

  ret = hrtimer_cancel( &hr_timer );
  if (ret) printk("The timer was still in use...\n");

  printk("HR Timer module uninstalling\n");

  return;
}

Há muito mais sobre a API hrtimer do que foi abordado aqui. Um dos aspectos interessantes é a habilidade de definir o contexto de execução da função de retorno de chamada (como nos contextos softirq e hardiirq). É possível aprender mais sobre a API hrtimer com o arquivo de inclusão em ./include/linux/hrtimer.h.


Listas de Kernel

Como mencionado anteriormente neste artigo, listas são estruturas úteis, e o kernel fornece uma implementação eficiente para o uso genérico. Além disso, você pode encontrar listas sob as APIs que exploramos até aqui. Entender a API de lista duplamente ligada ajudará o seu trabalho de desenvolvimento com essa estrutura de dados eficiente, assim como seu entendimento sobre a grande quantidade de código no kernel que utiliza listas. Agora vamos fazer um rápido tour pela API de lista do kernel.

A API fornece uma estrutura list_head, usada para representar não somente a cabeça da lista (âncora), mas também os ponteiros de lista internos à estrutura. Vamos dar uma olhada em uma estrutura de exemplo que inclui capacidades de lista (consulte a Listagem 3). Observe a adição da estrutura list_head, que é usada para as ligações de objetos. E observe que podemos adicionar a estrutura list_head em qualquer lugar da nossa estrutura, e—por meio de alguns truques de GCC (list_entry e container_of, definidos em ./include/kernel/kernel.h)—é possível desreferenciar a partir do ponteiro de lista para o superobjeto.

Listagem 3. Estruturas de exemplo com referências de listas
struct my_data_structure {
	int value;
	struct list_head list;
};

Como qualquer implementação de lista, é necessário ter uma cabeça de lista que serve como a âncora para a lista. Isso é feito, normalmente, com a macro LIST_HEAD, que fornece a declaração e a inicialização da lista. Essa macro cria um objeto list_head de estrutura, no qual é possível adicionar objetos.

LIST_HEAD( new_list )

É possível criar uma cabeça de lista manualmente (por exemplo, se a nossa cabeça de lista estiver em outra estrutura) através do uso da macro LIST_HEAD_INIT.

Com a inicialização principal completa, é possível manipular a lista usando as funções list_add e list_del (além de muitas outras). Agora, vamos ao código de exemplo, que ilustra melhor o uso da API.

Exemplo de API de lista

A Listagem 4 fornece um simples módulo do kernel que explora várias das funções de API de lista (embora muitas outras possam ser encontradas em ./include/linux/list.h). Esse exemplo cria duas listas, preenche-as na função init_module e, em seguida, manipula as listas na função cleanup_module.

No inicio, criamos nossa estrutura de dados (my_data_struct), que inclui alguns dados e, em seguida, duas cabeças de lista. Esse exemplo demonstra que é possível inserir um objeto em várias listas ao mesmo tempo. Então criamos duas cabeças de lista (my_full_list e my_odd_list).

Na função init_module, criamos 10 objetos de dados e os carregamos nas listas (todos os objetos em my_full_list, e todos os objetos com valores ímpares em my_odd_list) usando a função list_add. Observe que list_add recebe dois argumentos, o primeiro sendo a referência da lista dentro do objeto a ser usado e o segundo sendo a âncora da lista. Isso demonstra a habilidade de um objeto de dados estar presente em várias listas, usando os truques internos do kernel para identificar o superobjeto que contém a referência de lista.

A função cleanup_module ilustra mais alguns recursos da API de lista, o primeiro dos quais é a macro list_for_each, que simplifica a iteração da lista. Para essa macro, fornecemos uma referência para o objeto atual (pos) e a referência de lista a ser iterada. Para cada iteração, recebemos uma referência list_head, que pode ser fornecida para list_entry para identificar o objeto contêiner (nossa estrutura de dados). Especifique a estrutura e a variável da lista dentro da estrutura, que é usada internamente para fazer a desreferência de volta ao contêiner.

Para emitir a lista ímpar, use outra macro de iteração chamado list_for_each_entry. Essa macro é mais simples, pois fornece automaticamente a estrutura de dados, removendo a necessidade de executar um list_entry.

Finalmente, usamos list_for_each_safe para iterar a lista visando liberar os elementos alocados. Essa macro permite a iteração na lista com meios de segurança contra a remoção de uma entrada da lista (que faremos como parte da iteração). Usamos list_entry para chegar ao nosso objeto de dados (para liberá-lo de volta ao pool do kernel), e então usamos list_del para liberar a entrada na lista.

Listagem 4. Explorando a API de lista
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>

MODULE_LICENSE("GPL");

struct my_data_struct {
  int value;
  struct list_head full_list;
  struct list_head odd_list;
};

LIST_HEAD( my_full_list );
LIST_HEAD( my_odd_list );


int init_module( void )
{
  int count;
  struct my_data_struct *obj;

  for (count = 1 ; count < 11 ; count++) {

    obj = (struct my_data_struct *)
            kmalloc( sizeof(struct my_data_struct), GFP_KERNEL );

    obj->value = count;

    list_add( &obj->full_list, &my_full_list );

    if (obj->value & 0x1) {
      list_add( &obj->odd_list, &my_odd_list );
    }

  }

  return 0;
}


void cleanup_module( void )
{
  struct list_head *pos, *q;
  struct my_data_struct *my_obj;

  printk("Emit full list\n");
  list_for_each( pos, &my_full_list ) {
    my_obj = list_entry( pos, struct my_data_struct, full_list );
    printk( "%d\n", my_obj->value );
  }

  printk("Emit odd list\n");
  list_for_each_entry( my_obj, &my_odd_list, odd_list ) {
    printk( "%d\n", my_obj->value );
  }

  printk("Cleaning up\n");
  list_for_each_safe( pos, q, &my_full_list ) {
    struct my_data_struct *tmp;
    tmp = list_entry( pos, struct my_data_struct, full_list );
    list_del( pos );
    kfree( tmp );
  }

  return;
}

Existem muitas outras funções para fazer adições à cauda em vez da cabeça (list_add_tail), juntar listas (list_splice), e testar os conteúdos de uma lista (list_empty). Consulte os Recursos para obter mais detalhes sobre as funções de lista do kernel.


Indo além

Este artigo explorou algumas APIs que demonstram a habilidade de segregar funcionalidades quando preciso (APIs timer e hrtimer de alta precisão), assim como tornar o código comum para sua reutilização (API de lista). Timers tradicionais fornecem um mecanismo eficiente para limites de tempo típicos de drivers, enquanto hrtimers fornecem um nível mais alto de qualidade de serviço com recursos de timers mais precisos. A API de lista fornece uma interface muito genérica, mas altamente funcional e eficiente. Se você escrever códigos de kernel, vai se deparar com uma ou todas essas três APIs; portanto, definitivamente vale a pena explorá-las.

Recursos

Aprender

  • Aprenda sobre outros esquemas de adiamento de trabalho (tasklets e filas de trabalhos) em "Kernel APIs, Part 2: Deferrable functions, kernel tasklets, and work queues" (developerWorks, março de 2010).
  • Para obter mais informações sobre a implementação dos timers tradicionais, incluindo detalhes sobre a timer wheel em cascata, confira esta postagem de Ingo Molnar na lista de e-mails do kernel Linux. O texto fornece uma visualização detalhada da API de timer, sua lógica e detalhes consideráveis sobre sua implementação.
  • Thomas Gleixner oferece uma ótima introdução ao subsistema de timers e a divisão entre timers e timers de alta resolução nesta postagem sobre o patch do ktimers na lista de e-mails do kernel Linux. O texto também apresenta detalhes sobre o uso dos timers por parte de subsistemas de níveis mais altos.
  • Para obter uma visão mais profunda sobre as alterações no sistema de tempo do Linux (que fornece a base para os timers no kernel), confira o artigo Hrtimers and Beyond: Transforming the Linux Time Subsystems (PDF). Esse artigo explica detalhadamente a estrutura do subsistema geral de tempo e as alterações necessárias para suportar múltiplas APIs de tempo.
  • A API de lista do Linux usa extensões GCC para identificar o contêiner de um objeto que inclui uma estrutura de ponteiros de listas (usando typeof e offsetof). Para aprender sobre outras extensões GCC, confira "GCC hacks in the Linux kernel" (developerworks, novembro de 2008).
  • O FAQ/LinkedLists no Website KernelNewbies fornece uma boa introdução às listas e alguns dos internos.
  • Na zona Linux do developerWorks, encontre centenas de artigos e tutoriais de instruções, assim como downloads, fóruns de discussão e muitos outros recursos para desenvolvedores e administradores Linux.
  • Mantenha-se atualizado com os eventos técnicos e webcasts do developerWorks focados em uma variedade de produtos da IBM e em tópicos da indústria de TI.
  • Participe de um briefing gratuito do developerWorks Live! para se atualizar rapidamente sobre produtos e ferramentas da IBM, assim como tendências da indústria de TI.
  • Assista às demos on-demand do developerWorks, que abrangem desde demonstrações de instalações e configurações de produtos para iniciantes, até funcionalidades avançada para desenvolvedores experientes.
  • Siga o developerWorks no Twitter.

Obter produtos e tecnologias

  • Avalie os produtos da IBM da forma que melhor lhe convém: Faça o download de uma versão de teste de produtos, experimente um produto on-line, use um produto em um ambiente de nuvem ou passe algumas horas no SOA Sandbox aprendendo como implementar a Arquitetura Orientada a Serviço de forma eficiente.

Discutir

  • Envolva-se na comunidade My developerWorks. Entre em contato com outros usuários do developerWorks enquanto explora os blogs, fóruns, grupos e wikis dos desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=485687
ArticleTitle=APIs do Kernel, Parte 3: Timers e listas no kernel 2.6
publish-date=04262010