Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внутренние механизмы ядра | Вперед |
Сравнение и примеры
Начнём со сравнений. Оставив в стороне рассмотрение softirq, как механизм тяжёлый, и уже достаточно обсуждённый, в том смысле, что его использование оправдано при требовании масштабирования высокоскоростных процессов на большое число обслуживающих процессоров в SMP. Две другие рассмотренные схемы — это тасклеты и очереди отложенных действий. Они представляют две различные схемы реализации отложенных работ в современном Linux, которые переносят работы из верхних половин в нижние половины драйверов. В тасклетах реализуется механизм с низкой латентностью, который является простым и ясным, а очереди работ имеют более гибкий и развитый API, который позволяет обслуживать несколько отложенных действий в порядке очередей. В каждой схеме откладывание (планирование) последующей работы выполняется из контекста прерывания, но только тасклеты выполняют запуск автоматически в стиле «работа до полного завершения», тогда как очереди отложенных действий разрешают функциям-обработчикам переходить в блокированные состояния. В этом состоит главное принципиальное отличие: рабочая функция тасклета не может блокироваться.
Теперь можно перейти к примерам. Уже отмечалось, что экспериментировать с аппаратными прерываниями достаточно сложно. Кроме того, в ходе проводимых занятий мне неоднократно задавали вопрос: «Можно ли тасклеты использовать автономно, вне процесса обработки прерываний?». Вот так мы и построим иллюстрирующие модули: сама функция инициализации модуля будет активировать отложенную обработку. Ниже показан пример для тасклетов:
mod_tasklet.c :
#include <linux/module.h> #include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/timex.h> MODULE_LICENSE("GPL"); cycles_t cycles1, cycles2; static u32 j1, j2; char tasklet_data[] = "tasklet_function was called"; /* Bottom Half Function */ void tasklet_function( unsigned long data ) { j2 = jiffies; cycles2 = get_cycles(); printk( "%010lld [%05d] : %s\n", (long long unsigned)cycles2, j2, (char*)data ); return; } DECLARE_TASKLET( my_tasklet, tasklet_function, (unsigned long)&tasklet_data ); int init_module( void ) { j1 = jiffies; cycles1 = get_cycles(); printk( "%010lld [%05d] : tasklet_scheduled\n", (long long unsigned)cycles1, j1 ); /* Schedule the Bottom Half */ tasklet_schedule( &my_tasklet ); return 0; } void cleanup_module( void ) { /* Stop the tasklet before we exit */ tasklet_kill( &my_tasklet ); return; }
Вот как выглядит его исполнение:
$ uname -a
Linux notebook.localdomain 2.6.32.9-70.fc12.i686.PAE #1 SMP Wed Mar 3 04:57:21 UTC 2010 i686 i686 i386 GNU/Linux
$ sudo insmod mod_tasklet.ko
$ dmesg | tail -n100 | grep " : "
51300758164810 [30536898] : tasklet_scheduled 51300758185080 [30536898] : tasklet_function was called
$ sudo rmmod mod_tasklet
$ sudo nice -n19 ./clock
00002EE46EFE8248 00002EE46F54F4E8 00002EE46F552148 1663753694
По временным меткам видно, что выполнение функции тасклета происходит позже планирования тасклета на выполнение, но латентность очень низкая (системный счётчик jiffies не успевает изменить значение, всё происходит в пределах одного системного тика), отсрочка выполнения составляет порядка 20000 процессорных тактов частоты 1.66 Ghz (показан уже обсуждавшийся тест из раздела о службе времени, нас интересует только последняя строка его вывода), это составляет порядка 12 микросекунд.
В следующем примере мы проделаем практически то же самое (близкие эксперименты для возможностей сравнения), но относительно очередей отложенных действий:
mod_workqueue.c :
#include <linux/module.h> #include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/timex.h> MODULE_LICENSE("GPL"); static struct workqueue_struct *my_wq; typedef struct { struct work_struct my_work; int id; u32 j; cycles_t cycles; } my_work_t; /* Bottom Half Function */ static void my_wq_function( struct work_struct *work ) { u32 j = jiffies; cycles_t cycles = get_cycles(); my_work_t *wrk = (my_work_t*)work; printk( "#%d : %010lld [%05d] => %010lld [%05d]\n", wrk->id, (long long unsigned)wrk->cycles, wrk->j, (long long unsigned)cycles, j ); kfree( (void *)wrk ); return; } int init_module( void ) { my_work_t *work1, *work2; int ret; my_wq = create_workqueue( "my_queue" ); if( my_wq ) { /* Queue some work (item 1) */ work1 = (my_work_t*)kmalloc( sizeof(my_work_t), GFP_KERNEL ); if( work1 ) { INIT_WORK( (struct work_struct *)work1, my_wq_function ); work1->id = 1; work1->j = jiffies; work1->cycles = get_cycles(); ret = queue_work( my_wq, (struct work_struct *)work1 ); } /* Queue some additional work (item 2) */ work2 = (my_work_t*)kmalloc( sizeof(my_work_t), GFP_KERNEL ); if( work2 ) { INIT_WORK( (struct work_struct *)work2, my_wq_function ); work2->id = 2; work2->j = jiffies; work2->cycles = get_cycles(); ret = queue_work( my_wq, (struct work_struct *)work2 ); } } return 0; } void cleanup_module( void ) { flush_workqueue( my_wq ); destroy_workqueue( my_wq ); return; }
Вот как исполнение проходит на этот раз (на том же компьютере):
$ sudo insmod mod_workqueue.ko
$ lsmod | head -n3
Module Size Used by mod_workqueue 1079 0 vfat 6740 1
$ ps -ef | grep my_
root 17058 2 0 22:43 ? 00:00:00 [my_queue/0] root 17059 2 0 22:43 ? 00:00:00 [my_queue/1] olej 17061 11385 0 22:43 pts/10 00:00:00 grep my_
- видим, как появился новый обрабатывающий поток ядра, с заданным нами именем, причём по одному экземпляру такого потока на каждый процессор системы.
$ dmesg | grep "=>"
#1 : 54741885665810 [32606771] => 54741890115000 [32606774] #2 : 54741885675880 [32606771] => 54741890128690 [32606774]
$ sudo rmmod mod_workqueue
На этот раз мы помещаем в очередь отложенных действий два экземпляра работы, и каждый из них отстрочен на 3 системных тика от точки планирования — здесь латентность реакции существенно больше случая тасклетов, что и соответствует утверждениям в литературе.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Демон ksoftirqd | Обсуждение и вопросы |