通常,我们有一些 "内务处理" 任务需要在特定时间或每隔一段时间完成。 如果任务要由进程完成,我们会将其放在crontab文件中。 如果任务要由内核模块完成,我们有两种可能性。 第一种是将一个进程放在crontab文件中,该进程会在必要时通过系统调用唤醒模块,例如通过打开一个文件。 然而,这是非常低效的 -- 我们从crontab启动一个新进程,将新的可执行文件读取到内存中,而这一切仅仅是为了唤醒一个已经在内存中的内核模块。
与其这样做,我们可以创建一个函数,该函数将在每次定时器中断时被调用一次。 我们的做法是创建一个任务,保存在一个 workqueue_struct 结构中,该结构将保存一个指向该函数的指针。 然后,我们使用queue_delayed_work将该任务放在一个名为 my_workqueue 的任务列表中,该列表是在下一次定时器中断时要执行的任务列表。 因为我们希望该函数持续执行,所以我们需要在每次调用它时,将其放回 my_workqueue,以便在下一次定时器中断时再次执行。
这里还有一点我们需要记住。 当模块通过 rmmod 移除时,首先会检查其引用计数。 如果为零,module_cleanup将被调用。 然后,模块将从内存中移除,包括其所有函数。 需要正确关闭,否则会发生不好的事情。 请参阅下面的代码,了解如何以安全的方式完成此操作。
示例 11-1. sched.c
/* * sched.c - scheduale a function to be called on every timer interrupt. * * Copyright (C) 2001 by Peter Jay Salzman */ /* * The necessary header files */ /* * Standard in kernel modules */ #include <linux/kernel.h> /* We're doing kernel work */ #include <linux/module.h> /* Specifically, a module */ #include <linux/proc_fs.h> /* Necessary because we use the proc fs */ #include <linux/workqueue.h> /* We scheduale tasks here */ #include <linux/sched.h> /* We need to put ourselves to sleep and wake up later */ #include <linux/init.h> /* For __init and __exit */ #include <linux/interrupt.h> /* For irqreturn_t */ struct proc_dir_entry *Our_Proc_File; #define PROC_ENTRY_FILENAME "sched" #define MY_WORK_QUEUE_NAME "WQsched.c" /* * The number of times the timer interrupt has been called so far */ static int TimerIntrpt = 0; static void intrpt_routine(void *); static int die = 0; /* set this to 1 for shutdown */ /* * The work queue structure for this task, from workqueue.h */ static struct workqueue_struct *my_workqueue; static struct work_struct Task; static DECLARE_WORK(Task, intrpt_routine, NULL); /* * 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 (die == 0) queue_delayed_work(my_workqueue, &Task, 100); } /* * Put data into the proc fs file. */ ssize_t procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int *eof, void *data) { int len; /* The number of bytes actually used */ /* * It's static so it will still be in memory * when we leave this function */ static char my_buffer[80]; /* * We give all of our information in one go, so if 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 called %d times so far\n", TimerIntrpt); /* * Tell the function which called us where the buffer is */ *buffer_location = my_buffer; /* * Return the length */ return len; } /* * Initialize the module - register the proc file */ int __init init_module() { /* * Create our /proc file */ Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL); if (Our_Proc_File == NULL) { remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_ALERT "Error: Could not initialize /proc/%s\n", PROC_ENTRY_FILENAME); return -ENOMEM; } 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; /* * Put the task in the work_timer task queue, so it will be executed at * next timer interrupt */ my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME); queue_delayed_work(my_workqueue, &Task, 100); printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_FILENAME); return 0; } /* * Cleanup */ void __exit cleanup_module() { /* * Unregister our /proc file */ remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME); die = 1; /* keep intrp_routine from queueing itself */ cancel_delayed_work(&Task); /* no "new ones" */ flush_workqueue(my_workqueue); /* wait till all "old ones" finished */ destroy_workqueue(my_workqueue); /* * Sleep until intrpt_routine is called one last time. This is * necessary, because otherwise we'll deallocate the memory holding * intrpt_routine and Task while work_timer still references them. * Notice that here we don't allow signals to interrupt us. * * Since WaitQ is now not NULL, this automatically tells the interrupt * routine it's time to die. */ } /* * some work_queue related functions * are just available to GPL licensed Modules */ MODULE_LICENSE("GPL"); |