Já faz um tempo que não escrevo sobre desen­volvi­mento de device dri­vers em Linux. Na parte 2 desta série desen­volve­mos um device dri­ver com­pleto para aces­sar os leds do kit Friendl­yARM mini2440. Que tal estu­dar um pouco mais sobre a API do ker­nel do Linux?

Neste artigo vamos apren­der a tra­bal­har com threads em ker­nel space, e para exem­pli­ficar os con­ceitos, vamos fazer os leds do kit mini2440 piscarem.

THREADS?

Você já sabe que o Linux é um sis­tema opera­cional multi-tarefa, e que ele con­segue exe­cu­tar mais de uma tarefa ao mesmo tempo. É claro que em sis­temas com ape­nas 1 núcleo, ape­nas uma tarefa tem a atenção da CPU em deter­mi­nado momento, mas temos a impressão de que várias coisas estão acon­te­cendo simul­tane­a­mente. Isso ocorre dev­ido ao fato do Linux ser um sis­tema opera­cional pre­emp­tivo. O escalon­ador aloca uma fatia do tempo disponível para cada tarefa em exe­cução.

Se você tra­balha a um tempo desen­vol­vendo apli­cações para Linux, sabe que é pos­sível ter mais de uma linha de exe­cução em um processo. É o que chamamos de threads. E o con­ceito é sim­ples: “dividir para con­quis­tar”. Cada thread tem uma respon­s­abil­i­dade especí­fica, e você acaba otimizando o uso dos recur­sos da máquina, além de estru­tu­rar mel­hor o código e facil­i­tar a manutenção.

O que você talvez não saiba é que em ker­nel space tam­bém podemos ter mais de uma thread de exe­cução.

KERNEL THREADS?

Ker­nel threads são threads den­tro do ker­nel. Você pode por exem­plo ter tare­fas exe­cu­tando em back­ground em um device dri­ver, esperando por even­tos assín­cronos ou exe­cu­tando deter­mi­nada ativi­dade de tem­pos em tem­pos.

É fácil iden­ti­ficar as threads do ker­nel rodando no seu sis­tema. Ao lis­tar os proces­sos em exe­cução com o comando “ps”, os nomes entre “[]” são ker­nel threads. Veja um tre­cho da saída deste comando na minha máquina de desen­volvi­mento:

$ ps auxf
PID   USER     COMMAND
    1 root     init
    2 root     [kthreadd]
    3 root     [ksoftirqd/0]
    4 root     [events/0]
    5 root     [khelper]
   81 root     [kblockd/0]
   92 root     [khubd]
...

Da listagem acima, com exceção do processo “init”, o restante dos proces­sos são ker­nel threads.

Mas quando você vai pre­cisar de ker­nel threads? Em algu­mas situ­ações, na ver­dade. Se você pre­cisa esperar por even­tos assín­cronos, é bem provável que a solução seja criar uma thread no ker­nel para aguardar por este even­tos. Se você tem uma tarefa den­tro de um device dri­ver que leva bas­tante tempo para ser exe­cu­tada, você pode delegá-la para uma ker­nel thread. Se você pre­cisa exe­cu­tar deter­mi­nada tarefa peri­odica­mente, pode tam­bém usar uma thread no ker­nel para fazer isso.

Mas como isso fun­ciona na prática? É o que ver­e­mos agora!

NOSSO EXEMPLO

Mais uma vez, nosso exem­plo será exe­cu­tado no kit mini2440. Neste artigo aqui desen­volve­mos um device dri­ver para acen­der e apa­gar os leds deste kit. Agora ire­mos exten­der as fun­cional­i­dades deste device dri­ver para fazer os leds pis­carem. Da mesma forma que “0” apaga e “1” acende, enviar “2” ao led irá colocá-lo em um estado de “blink”, fazendo-o pis­car com uma fre­quên­cia de 1 segundo.

Exis­tem duas for­mas de tra­bal­har com threads no ker­nel: a antiga e ultra­pas­sada kernel_thread() e a atual e mel­hor estru­tu­rada kthread. Vamos ver exem­p­los com as duas.

Ire­mos criar uma thread que a cada 1 segundo irá ver­i­ficar se algum dos leds esta con­fig­u­rado para pis­car, e então invert­er­e­mos seu estado. Sim­ples, não? Então mãos à obra!

A ANTIGA INTERFACE KERNEL_THREADS

Este é o código respon­sável pelo geren­ci­a­mento da thread usando a antiga inter­face kernel_thread():

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
#define BLINK_TIME  1000
 
/* flag to enable thread */
unsigned int thread_enabled = 1;
struct completion leds_blink_exit;
 
/* kernel thread to blink the leds */
static int leds_blink(void *unused)
{
    int i;
 
    daemonize("leds_blink");
 
    while (thread_enabled) {
 
        for (i = 0; i < NUM_LEDS; i++) {
            if (leds_dev[i].status == LED_BLINK) {
                changeLedStatus(leds_dev[i].number, LED_BLINK);
            }
        }
 
        msleep(BLINK_TIME);
    }
 
    complete_and_exit(&leds_blink_exit, 0);
}
 
/* driver initialization */
int __init leds_init(void)
{
    ...
 
    /* init completion struct */
    init_completion(&leds_blink_exit);
 
    /* initialize leds_blink therad */
    kernel_thread(leds_blink, NULL, CLONE_FS | CLONE_FILES |
                  CLONE_SIGHAND | SIGCHLD);
 
    ....
}
 
/* driver exit */
void __exit leds_exit(void)
{
    ...
 
    /* stop thread */
    thread_enabled = 0;
    wait_for_completion(&leds_blink_exit);
 
    ...
}

A thread é cri­ada na linha 37 com a função kernel_thread(). Esta função recebe basi­ca­mente um pon­teiro para a função da thread e alguns parâmet­ros que indicam quais recur­sos serão com­par­til­ha­dos com o processo pai.

A função da thread leds_blink() na linha 8 é bem sim­ples. Ela é basi­ca­mente um loop infinito que peri­odica­mente faz pis­car os leds com o estado con­fig­u­rado como LED_BLINK.

A com­plex­i­dade aqui esta no con­t­role de final­iza­ção da thread. Se remover­mos o módulo do device dri­ver sem aguardar a final­iza­ção da thread, esta ficará no sis­tema com o sta­tus de zom­bie. Pre­cisamos con­tro­lar man­ual­mente este processo, e faze­mos isso através da flag thread_enabled. Ao finalizar a exe­cução do módulo, seta­mos esta flag na linha 49 e aguardamos a final­iza­ção da exe­cução da thread na linha 50.

Para tes­tar, basta enviar “2” ao arquivo de dis­pos­i­tivo do led que se deseja pis­car. Por exem­plo, para pis­car o led 3:

$ echo 2 > /dev/leds3

O código com­pleto deste device dri­ver pode ser baix­ado aqui.

A inter­face kernel_thread é antiga, propensa a bugs, e não deve ser usada em novos pro­je­tos. A frase abaixo é da própria doc­u­men­tação do ker­nel, disponível em “documentation/feature-removal-schedule.txt”:

“kernel_thread is a low-level imple­men­ta­tion detail. Dri­vers should use the <linux/kthread.h> API instead which shields them from imple­men­ta­tion details and pro­vides a high­er­level inter­face that pre­vents bugs and code dupli­ca­tion”.

É por isso que você deve esque­cer tudo que leu acima… brin­cadeira! É bem capaz que você encon­tre um ou outro device dri­ver usando esta API, por isso é bom conhecê-la. Mas você verá que com a inter­face kthread as coisas são muito mais fáceis.

A INTERFACE KTHREAD

Os con­ceitos são os mes­mos. A inter­face kthread na ver­dade uti­liza a função kernel_thread inter­na­mente, mas abstraindo alguns con­ceitos, e tornando-a mais sim­ples e segura de usar:

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
#define BLINK_TIME  1000
 
/* kernel thread to blink the leds */
static int leds_blink(void *unused)
{
    int i;
 
    while (!kthread_should_stop()) {
 
        for (i = 0; i < NUM_LEDS; i++) {
            if (leds_dev[i].status == LED_BLINK) {
                changeLedStatus(leds_dev[i].number, LED_BLINK);
            }
        }
 
        msleep(BLINK_TIME);
    }
 
    return(0);
}
 
/* driver initialization */
int __init leds_init(void)
{
    ...
 
    /* initialize leds_blink therad */
    leds_blink_task = kthread_run(leds_blink, NULL, "%s", "leds_blink");
 
    ....
}
 
/* driver exit */
void __exit leds_exit(void)
{
    ...
 
    /* stop thread */
    kthread_stop(leds_blink_task);
 
    ...
}

Veja como ficou sim­ples. Usamos ape­nas 3 funções: kthread_run() para ini­ciar a thread (linha 28), kthread_should_stop() para que a thread saiba quando deve finalizar sua exe­cução (linha 8) e kthread_stop() para finalizar a thread (linha 39).

O código com­pleto deste device dri­ver pode ser baix­ado aqui.

Vocês viram como é fácil usar threads no ker­nel do Linux. Mas deve­mos ter sem­pre muito cuidado com o que faze­mos em ker­nel space. Den­tro do ker­nel, temos acesso com­pleto a todos os recur­sos da máquina. E com o poder vem a respon­s­abil­i­dade. Por­tanto, use este recurso com sabedoria.

Um abraço,

Ser­gio Prado

VN:F [1.9.17_1161]
Rat­ing: 8.4/10 (5 votes cast)
Linux Device Dri­vers — Tra­bal­hando com Ker­nel Threads, 8.4 out of 10 based on 5 ratings

Posts rela­ciona­dos:

  1. Linux Device Dri­vers — Parte 1
  2. Linux Device Dri­vers — Parte 2
  3. Device Dri­vers e geren­ci­a­mento dinâmico de dispositivos
  • Thi­ago Moreira

    ótimo artigo Ser­gio. mais tarde imple­mentarei na minha placa e apren­derei como detec­tar even­tos assín­cronos para usar em meu pro­jeto. abraço

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • Júlio Hof­fi­mann

    Oi Ser­gio,
    Parabéns pelo blog, arti­gos de altís­sima qual­i­dade! Adi­cionei aos meus feeds.
    Abraço!

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • http://aplnx.blogspot.com Cláu­dio

    Parabens Sér­gio. Gostei muito das infor­mações do seu site. A sua doc­u­men­tação está muito mel­hor que a orig­i­nal do site da friendly ARM. O pro­duto é bom, mas doc­u­men­tação em chinês é bem complicado.

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • David Lewin

    Nice work Ser­gio ! this is, by far, the most com­plete and inter­est­ing site about linux embed­ded and the mini2440.
    Is it pos­si­ble to have some­times arti­cles in eng­lish as Google trans­late put really strange things (and tries to trans­late the code also) ?

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
    • http://www.sergioprado.org ser­gio­prado

      Hi David!

      Thanks! I’m plan­ning to start writ­ing in Eng­lish in a near future. I know that Google Trans­la­tor does a good job trans­lat­ing a few words, but when it comes to trans­lat­ing a full arti­cle, it is a disaster!

      Well, in the mean­time, please let me know if you have any ques­tions about the arti­cles, and keep fol­low­ing the blog!

      VA:F [1.9.17_1161]
      Rating: 0.0/5 (0 votes cast)
  • David Lewin

    Thank you for your answer Ser­gio,
    In fact I for­got to say that this is worse in other lan­guages than Eng­lish. Why this? because I wanted to pro­pose you some trans­la­tions in French & Ital­ian on my own, but this have to be in a “clean” Eng­lish at start.  If this sounds ok for you, just send me an email.

    Else­where, yes I have some ques­tions about the arti­cles for device dri­vers but maybe it will be bet­ter to do such via email and thus not “pol­lu­ate” your blog.
     
    Your blog really rocks, this is for sure, inter­est­ing not only because I have mini2440 but also about Linux.
    PS : as the “new” 3.0 is out, maybe an idea would be an arti­cle about the Arm –stagging-integration.…

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
  • http://code.google.com/p/bugsec/ Anto­nio , Cooler_

    fasci­nante bem expli­cado,  Emb­ora eu não tenha ponto empírico com o uso do mesmo,me parece que se for para fazer algo semel­hante ao fork(),as var­iáveis iriam se sobre­por pre­cis­aria usar futex() cor­reto ?
     

    VA:F [1.9.17_1161]
    Rating: 0.0/5 (0 votes cast)
    • http://www.sergioprado.org Ser­gio Prado

      É isso aí Anto­nio. Quando usamos futex(), temos por baixo dos panos uma ker­nel thread (imple­men­tando uma work queue) tratando o acesso à deter­mi­nado recurso por diver­sos proces­sos user space.

      Um abraço!

      VA:F [1.9.17_1161]
      Rating: 0.0/5 (0 votes cast)