第十一章 计划任务
11.1.计划任务
经常的话,我们有“内务处理”,它会在特定时间或者一直来处理内务。如果这个任务由进程完成了,我们就会把它放到
crontab
文件。如果任务由内核模块完成了,我们有两种可能性。第一个是把进程放到crontab
文件,这个文件将会由系统调用在必要时候唤起模块,例如打开一文件。这种是非常低效的,然而--我们不用crontab
运行一个新进程,读一个新的可执行文件到内存,然后所有这个仅仅只是唤起一个在内存的内核模块。
与之相反的是,我们能创建一个函数,这个函数一旦每一次时间中断触发就会被调用。我们所要做的就是创建一个task,把它放到一个workqueue_struct
结构,这个结构会有一个指针指向函数。然后,我们使用queue_delayed_work
来把那个任务放到任务列表,调用my_workqueue
,这个队列是任务即将在下一个时间中断执行的列表。因为我们想要函数来保证一直执行,对于下一次时间中断,我们需要当它被调用的时候把它放到my_workqueue
后面。
还有一个重要的我们需要记住的点是。当一个模块被rmmod移除的时候,首先它的引用计数会被检查。如果它是0,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 /* We're doing kernel work */
#include /* Specifically, a module */
#include /* Necessary because we use the proc fs */
#include /* We scheduale tasks here */
#include /* We need to put ourselves to sleep
and wake up later */
#include /* For __init and __exit */
#include /* 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");
第12章 中断处理
12.1.中断处理
12.1.1.中断处理
除了上一章节,到目前为止我们在内核做的每件事都是回应一个进程的要求。要不就是通过处理一个特殊文件,发送一个
ioctl()
或者发一个系统调用。但是内核的工作不是仅仅来回应进程的要求。另一个工作,它的每一点工作都很重要,就是与连接到机器的硬件对话。
这里在CPU和电脑的其他硬件交互有两种类型。第一个类型是当CPU給硬件下命令时候,另外是当硬件需要告诉CPU一些事。第二个,调用中断,是更难的实现的,因为它需要解决在什么时候时候硬件而不是适合CPU。硬件设备一般有一个非常小的RAM,那么如果当可用的时候你不读他们的信息,那么你就丢失数据了。
在Linux下,硬件中断是叫做IRQ‘s(InterruptRe quests)。这有两种IRQ‘s类型,short和long。一个short IRQ是想要占据非常短的一段时间的一种方式,这个很短的时间是指的机器被阻塞的剩余部分的时候,然后又没有其他中断会被处理。一个long IRQ是指占据一个较长的时间,在这个长时间里面,其他的中断也会发生(但是这个中断不会来自同一个设备)。如果有可能,那么最好就是把中断处理申明为long。
当CPU收到一个中断,他就会停止正在做的(除非它正在执行一个更重要的中断,在这种情况下,它会处理这个更重要的只有当这个更重要的中断停了才会处理这个中断),保留了特定的参数到栈里面,然后调用中断处理函数。这个就意味着这个某些东西并不允许在中断中处理他们自己,因为系统会在一个未知的状态。对于中断处理函数解决办法就是立马做它需要做的事,通常是从硬件读东西或者发东西到硬件,然后把这个新信息的处理放到后面的时间(这个被叫做“bottom half”)然后返回。内核随后有保障的尽快调用这个bottom half--然后当它做的时候,在内核模块允许的每件事都会被允许。
实现这个的方法是调用request_irq()
当相关的IRQ被收到后,来得到你的中断处理调用。这个函数收到:IRQ number
,the name of the function
,flags
,a name for /proc/interrupts
和a parameter
来传到中断处理方法。通常,有特定的可用的IRQs号码。这里有多少IRQs是由硬件决定的。flags能包含SA_SHIRQ
来表示你想要与其他中断处理方法分享这个IRQ(通常因硬件设备号码与IRQ是同一个号码)还能包含SA_INTERRUPT
来表示这个事一个快速中断。如果这在这个IRQ已经没有了方法或者如果两个都想要分享的时候 这个函数才会成功。
然后,从中断处理方法中,我们和硬件交流并且使用queue_work() mark_bh(BH_IMMEDIATE)
来安排bottom half。12.1.2. 在intel结构的键盘
这一章节的剩下的部分都是intel的解读。如果你不想不是运行在intel平台,那就没什么用。甚至不要尝试编译这个代码。
在写这章节样例代码时,我有一个问题。在一方面,一个有用的例子一棒能运行自己的电脑上并得到有效的结果。另一方面,内核已经包含了所有的通用设备的设备驱动,且那些设备驱动将不会与我们将要写的驱动共存。我发现的解决方法是写一些对于键盘的中断,然后首先使得的常规的键盘中断失效。因为它作为一个静态标识在内核源码文件中定义的(具体的在:drivers/char/keyboad.c
),那就没办法解决了。在insmod
插入代码之前,在另一个终端sleep 120;如果你重视你的文件系统那么reboot。
这个代码绑定自己的IRQ 1,这个号码是键盘控制在Intel结构下的的IRQ。那么,,当它收到一个键盘中断,它就读键盘的状态(那是inb(0x64)
的目的)然后扫描代码,然后返回键盘的值。接下来只要内核认为它可行,它就会运行got_char
这个运行会給键盘所用的扫描吗码(扫描码的头7位)然后看是否已经按下了(第8个位为0)还是释放了(第八位为1)。
例子12-1.intrpt.c
/*
* intrpt.c - An interrupt handler.
*
* Copyright (C) 2001 by Peter Jay Salzman
*/
/*
* The necessary header files
*/
/*
* Standard in kernel modules
*/
#include /* We're doing kernel work */
#include /* Specifically, a module */
#include
#include
#include /* We want an interrupt */
#include
#define MY_WORK_QUEUE_NAME "WQsched.c"
static struct workqueue_struct *my_workqueue;
/*
* This will get called by the kernel as soon as it's safe
* to do everything normally allowed by kernel modules.
*/
static void got_char(void *scancode)
{
printk(KERN_INFO "Scan Code %x %s.\n",
(int)*((char *)scancode) & 0x7F,
*((char *)scancode) & 0x80 ? "Released" : "Pressed");
}
/*
* This function services keyboard interrupts. It reads the relevant
* information from the keyboard and then puts the non time critical
* part into the work queue. This will be run when the kernel considers it safe.
*/
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* This variables are static because they need to be
* accessible (through pointers) to the bottom half routine.
*/
static int initialised = 0;
static unsigned char scancode;
static struct work_struct task;
unsigned char status;
/*
* Read keyboard status
*/
status = inb(0x64);
scancode = inb(0x60);
if (initialised == 0) {
INIT_WORK(&task, got_char, &scancode);
initialised = 1;
} else {
PREPARE_WORK(&task, got_char, &scancode);
}
queue_work(my_workqueue, &task);
return IRQ_HANDLED;
}
/*
* Initialize the module - register the IRQ handler
*/
int init_module()
{
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
/*
* Since the keyboard handler won't co-exist with another handler,
* such as us, we have to disable it (free its IRQ) before we do
* anything. Since we don't know where it is, there's no way to
* reinstate it later - so the computer will have to be rebooted
* when we're done.
*/
free_irq(1, NULL);
/*
* Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
* SA_SHIRQ means we're willing to have othe handlers on this IRQ.
* SA_INTERRUPT can be used to make the handler into a fast interrupt.
*/
return request_irq(1, /* The number of the keyboard IRQ on PCs */
irq_handler, /* our handler */
SA_SHIRQ, "test_keyboard_irq_handler",
(void *)(irq_handler));
}
/*
* Cleanup
*/
void cleanup_module()
{
/*
* This is only here for completeness. It's totally irrelevant, since
* we don't have a way to restore the normal keyboard interrupt so the
* computer is completely useless and has to be rebooted.
*/
free_irq(1, NULL);
}
/*
* some work_queue related functions are just available to GPL licensed Modules
*/
MODULE_LICENSE("GPL");
第十三章 对称多处理
13.1. 对称多处理
最早且最便宜的方式之一来提高硬件表现是多加几个CPU。这个要么通过使得不同的CPU处理不同的工作要么通过使他们同时做同样的工作来完成的(对称多处理,a.k.a.SMP)。但是要似的非对称多处理机达到有效率的事需要知道电脑应该做的什么样的特定任务,这个任务对于一般的目标操作系统的是不可用的,这种目标系统就有LINUX。另一方面,对称多处理实际上更容易实现。
这种容易是相对容易的,作者意思是:这不是真的容易。在一个对称多处理环境下,CPU共享同样的内存,然后作为一个结果代码运行在单核CPU上的内存块就会被另外的代码影响。你就不能再确定一个你在之前的代码中已经设定值的变量;当你没看到的时候,其他CPU可能已经用过了这个变量。很明显,这种程序很容易出现。
在进程编程的例子中这已不是个问题,非常常见的,因为一个进程通常在一个时间会运行在单核CPU上。而内核,另一方面,它就可以被不同的运行在不同CPU的进程来调用。
在2.0.x的版本,这不是个问题,因为整个内核在一个大的自旋锁(spinlock)。这个意味着如果一个CPU在内核里面,然后另一个CPU想要进内核,对于这个例子来说,因为系统调用,它就不得不等到第一个CPU做完了才会得到调用。这个会舍得LinuxSMP安全,但是会降低效率。
在2.2.x版本,多个CPU能在同一时间进入内核。这个就是一些写模块的时候需要注意的点。第十四章 常见误区
14.1. 常见误区
在你出师前,我需要提醒你几点。如果我没有提醒你,然后发生了错误,请告诉我问题并把书本还给我。
你不能这么做。在一个内核模块,你只能使用内核函数,这些函数你能在`/proc/kallsyms`看到。
你可能需要用到中断只是一下子,那么是可以的,但是如果你不能在后面enable,你的系统可能就被困住了,那么你就不得不关机了。
我可能不一定要提醒你这个,但是以防万一的,反正我说了。
附录A 从2.0变到2.2
A.1. 2.4到2.6的变化
A.1.1.从2.4到2.6的变化
我不知道整个内核是否能把所有的改变都写成文档。一些移植的提示能通过对比LKMPG的2.4内核副本的版本找到。除了这些,对于那些想要移植从2.4到2.6的驱动可能能访问:http://lwn.net/Articles/driver-porting/.如果你仍然在这找不到一个例子恰恰满足你的需求,能找到一个和你驱动类似的驱动然后看两个内核的版本。文件比较工具例如xxdiff活着meld能给你很大的帮助。检查是否你的驱动是由文档(
linux/Documentation/
)转化来的。在开始移植之前,为了以免你的死机,你最好找到适当的mailinglist和别人提问的要点。附录B 展望未来
B.1 从哪里来?
我能很容易的塞进更多的章节进这本书。我本可以加入一个章节关于创建一个文件系统,或者关于加入一个协议栈(好像有的酒很需要它--你已经挖穿地板来找支持LINUX协议栈的东西了)。我本可以加入我们还没有触碰的内核机制说明解释,例如bootstrapping或者disk interface。
然而,我选择不这么做。我的目标是写一本书来仅仅开启内核模块编程的秘密,然后以教通用的技术为目的。对于人们来说,对于内核编程很感兴趣,我建议Juan-Mariano de Goyeneche
的[Juan-Mariano de Goyeneche’s](Juan-Mariano de Goyeneche’s).同样,如Linux说的,学内核编程最好的办法就是自己读内核源码。
如果你感兴趣更多的短小的内核模块,我建议Phrack 杂志。即使你对于security不感兴趣,但是作为一个程序员,内核模块有很多好的例子说明你能在内核做的事情,并且这些模块都很短你不需要花费太多的功夫就能理解。
我希望在你想要成为高级程序员的道路上我能帮助到你,或者至少通过这些技术能玩得开心。接下来,如果你写了有用处的内核模块,我喜欢你能发表在GPL,以便我能使用它们。
如果你想要对这个导引做贡献,请联系维护人员得到更多的细节。如你所见,这是一个占位置的章节,期待能有更多关于sysfs的例子。索引 略