著者:Ori Pomerantz
翻译:徐辉
10.调度任务
经常地,我们有必须定时做或者经常做的“家务事”。如果这个任务由一个进程完成,我们可以把通过把它放入crontab文件而做到。如果这个任务由一个内核模块完成,我们有两种可能的选择。第一种是把一个进程放入crontab文件,它将在必要的时候通过一个系统调用唤醒模块,比如打开一个文件。然而,这样做时非常低效的,我们需要运行一个crontab外的新进程,把一个新的执行表读入内存,而所有这些只是为了唤醒一个内存中的内核模块。
我们不需要这样做。我们可以创建一个函数,在每个时间中断时被调用。方法是创建一个任务,包含在一个结构体tq_struct里,在此结构中包含一个指向函数入口地址的指针。然后,我们使用queue_task把这个任务放入一个叫做tq_timer的任务列表中,这是一个在下次时间中断时要执行的任务列表。因为我们希望这个函数被持续执行,我们需要在每次调用厚把它放回tq_timer中以备下次时间中断。
这里还有一点需要记住。当一个模块被rmmod删除时,首先他的索引计数器被检查。如果是0,就调用module_cleanup。然后,这个模块以及它的所有函数都从内存中删除。没有人检查是否时钟的任务列表仍然包含指向这些函数的指针,而现在已不可用。很久以后(从计算机看来,在人的眼睛里是很短的,可能是百分之一秒),内核有了一个时钟中断,试图调用任务列表中的所有函数。不幸的是,这个函数已不存在。在多数情况下,它所在的内存还未被使用,而你得到了一个极端错误的信息。但是,如果有别的代码出在相同的地址,情况会非常糟糕。不幸的是,我们没有一个从任务列表中注销一个任务的方法。
既然cleanup_module函数不能返回一个错误马(它是void型函数),那么解决方法是就不要让它返回。而是调用sleep_on或module_sleep_on(注10.1)把rmmod进程挂起。在此之前,它设置一个变量通知在时钟中断时调用的函数停止附加自己。那么,在下次时钟中断时,rmmod进程将被唤醒,而我们的函数已经不在队列中,这样就可以很安全的删除模块。
ex sched.c
/* sched.c - scheduale a function to be called on
* every timer interrupt. */
/* Copyright (C) 1998 by Ori Pomerantz */
/* The necessary header files */
/* Standard in kernel modules */
#include /* Were doing kernel work */
#include /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS
#include
#endif
/* Necessary because we use the proc fs */
#include
/* We scheduale tasks here */
#include
/* We also need the ability to put ourselves to sleep
* and wake up later */
#include
/* In 2.2.3 /usr/include/linux/version.h includes a
* macro for this, but 2.0.35 doesnt - so I add it
* here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
/* The number of times the timer interrupt has been
* called so far */
static int TimerIntrpt = 0;
/* This is used by cleanup, to prevent the module from
* being unloaded while intrpt_routine is still in
* the task queue */
static struct wait_queue *WaitQ = NULL;
static void intrpt_routine(void *);
/* The task queue structure for this task, from tqueue.h */
static struct tq_struct Task = {
NULL, /* Next item in list - queue_task will do
* this for us */
0, /* A flag meaning we havent been inserted
* into a task queue yet */
intrpt_routine, /* The function to run */
NULL /* The void* parameter for that function */
};
/* This function will be called on every timer
* interrupt. Notice the void* pointer - task functions
* can be used for more than one purpose, each time
* getting a different parameter. */
static void intrpt_routine(void *irrelevant)
{
/* Increment the counter */
TimerIntrpt++;
/* If cleanup wants us to die */
if (WaitQ != NULL)
wake_up(&WaitQ); /* Now cleanup_module can return */
else
/* Put ourselves back in the task queue */
queue_task(&Task, &tq_timer);
}
/* Put data into the proc fs file. */
int procfile_read(char *buffer,
char **buffer_location, off_t offset,
int buffer_length, int zero)
{
int len; /* The number of bytes actually used */
/* This is static so it will still be in memory
* when we leave this function */
static char my_buffer[80];
static int count = 1;
/* We give all of our information in one go, so if
* the anybody asks us if we have more information
* the answer should always be no.
*/
if (offset > 0)
return 0;
/* Fill the buffer and get its length */
len = sprintf(my_buffer,
""Timer was called %d times so far
"",
TimerIntrpt);
count++;
/* Tell the function which called us where the
* buffer is */
*buffer_location = my_buffer;
/* Return the length */
return len;
}
struct proc_dir_entry Our_Proc_File =
{
0, /* Inode number - ignore, it will be filled by
* proc_register_dynamic */
5, /* Length of the file name */
""sched"", /* The file name */
S_IFREG | S_IRUGO,
/* File mode - this is a regular file which can
* be read by its owner, its group, and everybody
* else */
1, /* Number of links (directories where
* the file is referenced) */
0, 0, /* The uid and gid for the file - we give
* it to root */
80, /* The size of the file reported by ls. */
NULL, /* functions which can be done on the
* inode (linking, removing, etc.) - we dont
* support any. */
procfile_read,
/* The read function for this file, the function called
* when somebody tries to read something from it. */
NULL
/* We could have here a function to fill the
* files inode, to enable us to play with
* permissions, ownership, etc. */
};
/* Initialize the module - register the proc file */
int init_module()
{
/* Put the task in the tq_timer task queue, so it
* will be executed at next timer interrupt */
queue_task(&Task, &tq_timer);
/* Success if proc_register_dynamic is a success,
* failure otherwise */
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
return proc_register(&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif
}
/* Cleanup */
void cleanup_module()
{
/* Unregister our /proc file */
proc_unregister(&proc_root, Our_Proc_File.low_ino);
/* Sleep until intrpt_routine is called one last
* time. This is necessary, because otherwise well
* deallocate the memory holding intrpt_routine and
* Task while tq_timer still references them.
* Notice that here we dont allow signals to
* interrupt us.
*
* Since WaitQ is now not NULL, this automatically
* tells the interrupt routine its time to die. */
sleep_on(&WaitQ);
}