Планирование задач
Очень часто возникает необходимость запуска вспомогательных задач по расписанию. Если запускаемая задача -- обычный процесс, то помещаем ее в файл crontab. Если же задача является модулем ядра, то у нас есть две возможности. Первая состоит в том, чтобы поместить некую задачу в файл crontab, которая будет "будить" модуль системным вызовом в заданный момент времени, например, открывая файл. Это очень неэффективно, т.к. при запуске нового процесса из crontab приходится загружать программу в память и всем это только для того, чтобы "разбудить" модуль ядра, который уже находится в памяти.
Вместо этого мы попробуем создать функцию, которая будет вызываться по прерываниям от таймера. Для этого, создадим задачу struct work_struct. Эта структура будет хранить указатель на функцию, срабатывающую по таймеру. Затем, с помощью queue_delayed_work, поместим задачу в очередь tq_timer, где должны располагаться задачи, срабатывающие по таймеру. А так как предполагается срабатывание функции каждый раз, по истечении заданного интервала времени, мы должны всякий раз опять вставлять ее в очередь tq_timer.
И еще один немаловажный момент. Когда модуль выгружается командой rmmod, сначала проверяется счетчик обращений к модулю. Если он равен нулю, то вызывается module_cleanup. После чего модуль удаляется из памяти со всеми его функциями. И никто не проверяет -- содержит ли очередь задач таймера указатель на одну из удаляемых функций. По прошествии некоторого времени (с точки зрения человека -- практически мгновенно), ядро получит прерывание от таймера и попробует вызывать удаленную из очереди задачу. Но функции-то больше нет! В большинстве случаев страница памяти, где она была, будет рассматриваться как неиспользуемая и вы получите сообщение об ошибке. Но может случиться так, что на этом месте окажется некоторый другой код и тогда ваше дело -- табак. К сожалению, у нас нет достаточно простого способа удаления задачи из очереди таймера.
Так как cleanup_module не может вернуть код ошибки (она не имеет возвращаемого значения), то напрашивается решение -- приостановить процедуру завершения работы модуля. Вместо того, чтобы немедленно завершить работу функции cleanup_module, мы можем приостановить работу команды rmmod. Затем, установив глобальную переменную, сообщить функции, вызываемой по прерыванию таймера, чтобы она убрала себя из очереди (точнее -- чтобы она опять не вставляла себя в очередь). На ближайшем прерывании таймера, процесс rmmod будет "разбужен", когда функция удалит себя из очереди таймера и удаление модуля станет безопасным.
Пример 10-1. sched.c
/* * sched.c - реализация срабатывания по таймеру. * * Copyright (C) 2001 by Peter Jay Salzman */
/* * Необходимые заголовочные файлы */
/* * Обычные, для модулей ядра */ #include <linux/kernel.h> /* Все-таки мы работаем с ядром! */ #include <linux/module.h> /* Необходимо для любого модуля */ #include <linux/proc_fs.h> /* Необходимо для работы с /proc */ #include <linux/workqueue.h> /* очереди задач */ #include <linux/sched.h> /* Взаимодействие с планировщиком */ #include <linux/init.h> /* макросы __init и __exit */ #include <linux/interrupt.h> /* определение irqreturn_t */
struct proc_dir_entry *Our_Proc_File; #define PROC_ENTRY_FILENAME "sched" #define MY_WORK_QUEUE_NAME "WQsched.c"
/* * Счетчик срабатываний по таймеру */ static int TimerIntrpt = 0;
static void intrpt_routine(void *);
static int die = 0; /* 1 -- завершить работу */
/* * очередь задач, создается для того, * чтобы поместить в очередь таймера (workqueue.h) */ static struct workqueue_struct *my_workqueue;
static struct work_struct Task; static DECLARE_WORK(Task, intrpt_routine, NULL);
/* * Функция-обработчик прерывания от таймера. * Обратите внимание на аргумент типа void* * функция может получать дополнительные * аргументы посредством этого указателя. */ static void intrpt_routine(void *irrelevant) { /* * Нарастить счетчик */ TimerIntrpt++;
/* * Если признак завершения сброшен, * то опять вставить себя в очередь таймера */ if (die == 0) queue_delayed_work(my_workqueue, &Task, 100); }
/* * Запись данных в файл /proc. */ ssize_t procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data) { int len; /* Фактическое число записанных байт */
/* * Переменные объявлены как static, поэтому они располагаются не на стеке * функции, а в памяти модуля */ static char my_buffer[80];
static int count = 1;
/* * Все сведения выдаются за один присест, * поэтому, если смещение != 0, то значит * нам нечего больше сказать, поэтому возвращается * 0, в качестве признака конца файла. */ if (offset > 0) return 0;
/* * Заполнить буфер и получить его длину */ len = sprintf(my_buffer, "Timer called %d times so far\n", TimerIntrpt); count++;
/* * Указать адрес буфера */ *buffer_location = my_buffer;
/* * Вернуть длину буфера */ return len; }
/* * Функция инициализации - зарегистрировать файл в /proc */ int __init init_module() { int rv = 0; /* * Создать очередь задач с нашей задачей и поместить ее в очередь таймера */ my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME); queue_delayed_work(my_workqueue, &Task, 100);
Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL); Our_Proc_File->read_proc = procfile_read; Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->mode = S_IFREG | S_IRUGO; Our_Proc_File->uid = 0; Our_Proc_File->gid = 0; Our_Proc_File->size = 80;
if (Our_Proc_File == NULL) { rv = -ENOMEM; remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_INFO "Error: Could not initialize /proc/%s\n", PROC_ENTRY_FILENAME); }
return rv; }
/* * Завершение работы */ void __exit cleanup_module() { /* * Удалить файл из /proc */ remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME); /* Известить функцию обработки прерываний о завершении работы */ die = 1; cancel_delayed_work(&Task); flush_workqueue(my_workqueue); /* ждать пока отработает таймер */ destroy_workqueue(my_workqueue);
/* * Приостановить работу, пока intrpt_routine не * отработает в последний раз. * Это необходимо, поскольку мы освобождаем память, * занимаемую этой функцией. */
}
/* * некоторые функции, относящиеся к work_queue * доступны только если модуль лицензирован под GPL */ MODULE_LICENSE("GPL");