Рейтинг@Mail.ru

Наши друзья и партнеры

UnixForum
купить дешевый 
компьютер родом из Dhgate.com




Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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   Обсуждение и вопросы

Поделиться: