📄 (ldd) ch06-时间流(转载).txt
字号:
另一个与任务队列有关的概念是中断时间。在Linux中,中断时间是个软件上的
概念,取决于内核的全局变量intr_count。任一时候该变量都记录了正在执行的中断处
理程序被嵌套的层数*。
一般的计算流程中,当处理器允许某个进程时,intr_count值为0。当intr_coun
t不为零时,,执行的代码就与系统的其他部分是异步的了。这些异步代码可以是硬件中
断的处理或者是“软件中断”-与任何进程都无关的一个任务,我们称它在“中断时间
内”运行。这种异步代码是不允许做某些操作的;特别的,它不能使当前进程进入睡眠
,因为current指针的值与正在运行的软件中断代码无关。
典型的例子是退出系统调用时要执行的代码。如果因为某个原因此时还有任务需
要得到执行,内核可以一退出系统调用就处理它。这是个“软件中断”,intr_count值
在处理这个待执行的任务之前会先加1。由于主线指令流被中断了,该函数算是在“中断
时间”内被处理的。
当intr_count非零时,不能激活调度器。这也就意味着不允许调用kmalloc(GFP_
当intr_count非零时,不能激活调度器。这也就意味着不允许调用kmalloc(GFP_
KERNEL)。在中断时间内,只能进行原子性的分配(见第7章“掌握内存”的“优先权参数
”一节),而原子性的分配较“普通的”分配更容易失败。
如果运行在中断时间的代码调用了调度器,类似“Aiee: scheduling in
interrupt”这样的错误信息和以16进制显示的调用点处的地址会打印到控制台上。2.1.
37之后的版本,oops消息也会打印出来,通过分析寄存器的值可以进行调试。在中断时
间内如果试图非原子性地按优先权分配内存,也会显示包括着调用者的调用点处地址的
错误信息。
预定义的任务队列
延迟任务执行的简单方法是使用内核维护的任务队列。这种队列有下面描述的四种,但
驱动程序只能用前三种。任务队列的定义在头文件<linux/queue.h>中,你的驱动程序代
码要将它包含(include)进来。
tq_scheduler队列
当调度器被运行时该队列就会被处理。因为此时调度器在被调度出的进程的上下文中运
行,所以该队列中的任务几乎可以做任何事;它们不会在中断时运行。
tq_timer队列
该队列由定时器队列处理程序(timer tick)运行。因为该处理程序(见函数do_timer)是
在中断时间运行的,该队列中的所有任务就也是在中断时间内运行的了。
tq_immediate队列
立即队列在系统调用返回时或调度器运行时尽快得到处理的(不管两种情况谁先发生了)
。该队列是在中断时间内得到处理的。
tq_disk队列
1.2版的内核不再提供这种任务队列了,内存管理例程内部使用,模块不能使用。
使用任务队列的一个设备驱动程序的执行流程可见图6-1。该图演示了设备驱动程序是如
使用任务队列的一个设备驱动程序的执行流程可见图6-1。该图演示了设备驱动程序是如
何在中断处理程序中将一个函数插入tq_scheduler队列中的。
被执行的代码
中断
从中断返回
数据
关键字
处理器代码
调度器
驱动程序代码
(指向任务)
"sync"位
(指向next)
图 6-1:任务队列使用的执行流程
示例程序是如何工作的
延迟计算的示例程序是jiq(Just In Queue)模块,本节中抽取了它的部分源码。
该模块创建/proc文件,可以用dd或者其他工具来读;这与jit模块很相似。该示例程序
使用了动态/proc文件因此不能在Linux1.2上运行。
读jiq文件的进程进入睡眠状态直到缓冲区满*。缓冲区由不断运行的任务队列来
填充。任务队列的每遍运行都将在要填充的缓冲区中添加一个字符串;该字符串记录了
当前时间(jiffies值),该遍的current进程和intr_count值。
该/proc文件最好是用dd count=1命令一次性地读进来;如果你用cat命令,read
方法要被多次调用,输出结果会有重迭,详情可见第4章“调试技术”的“使用/proc文
件系统”一节。
件系统”一节。
填充缓冲区的代码都在jiq_print函数中,任务队列的每遍运行都要调用它。打
印函数没什么意思,不在这里列出;我们还是来看看插入队列的任务的初始化代码:
struct tq_struct jiq_task; /* 全局变量;初始化为零 */
/* 该行在init_module()中 */
jiq_task.routine = jiq_print;
jiq_task.data = (void *)&jiq_data;
这里没必要清零jiq_task结构变量的sync域和next域,因为静态变量已由编译器初始化
为零了。
为零了。
调度器队列
最容易使用的任务队列是tq_scheduler队列,因为该队列中的任务不会在中断时间内运
行,因此少了很多限制。
/proc/jiqsched文件是使用tq_scheduler队列的示例文件。该文件的read函数以如下的
方式将任务jiq_task放进tq_scheduler队列中:
/*
* 使用调度器队列的例子 -- /proc/jiqsched
*/
int jiq_read_sched(char *buf, char **start, off_t offset, int len, int
unused)
{
{
jiq_data.len = 0; /* 还未打印,长度为0 */
jiq_data.buf = buf; /* 打印到这个缓冲区中 */
jiq_data.jiffies = jiffies; /* 开始时间 */
/* jiq_print会调用 queue_task() 使自己重新进入 jiq_data.queue队列 */
jiq_data.queue = &tq_scheduler;
queue_task(&jiq_task, &tq_scheduler); /* 准备运行*/
interruptible_sleep_on(&jiq_wait); /* 进入睡眠队列只到任务完成 */
return jiq_data.len;
return jiq_data.len;
}
读读/proc/jiqsched文件很有意思,因为它显示调度器在何时运行-jiffies值表明调度
器激活的时间。如果系统中有些正在占用CPU的进程,那么队列中各任务的运行间会有些
延迟;因为调度器要在若干时钟滴答后才会抢先那些进程。打开这个文件会花上好几秒
钟,因为它长达100行(在Alpha机器上是200行)。
测试这些情形最简单方法是跑一个执行空循环的进程。load50是个增加机器负载的程序
,它在用户空间执行50个并发的忙循环;你可以在示例程序中找到它的源码(misc-progs
/load50.c)。当在系统中运行load50程序时,head命令将从/proc/jiqsched文件中抽取
类似如下的信息:
time delta intr_count pid command
1643733 0 0 701 head
1643733 0 0 701 head
1643747 14 0 658 load50
1643747 0 0 3 kswapd
1643755 8 0 655 load50
1643761 6 0 666 load50
1643764 3 0 650 load50
1643767 3 0 661 load50
1643769 2 0 659 load50
1643769 0 0 6 loadmonitor
注意到调度队列是在进入schedule过程后就执行的,因此current进程就是刚刚被调度出
去的进程。这就是为什么/proc/jiqsched文件的第一行总是读该文件的那个进程;它正
进入睡眠状态,就要被调出。还可以发现,kswapd和loadmonitor(这是我在我的系统上
进入睡眠状态,就要被调出。还可以发现,kswapd和loadmonitor(这是我在我的系统上
运行的一个程序)的执行时间都少于1个时钟滴答,而load50是在它的时间片耗尽后被抢
先,这离它获得处理器有好几个时钟滴答。
当系统中实际上没有任何进程在运行时,current进程总是空闲(idle)任务(0号进程,历
史性的被称为"swapper"),任务队列或者是不断地运行或者是每隔1个时钟滴答运行一次
。如果处理器不能进入“暂停”("halted")状态,调度器和任务队列就将不断运行;如
果处理器能被0号进程暂停(halt),它们每隔1个时钟滴答才运行一次。暂停的(halted)
处理器只能由中断唤醒。当中断发生时,空闲进程运行调度器(及相应的队列)。下面显
示了在没有负载的系统运行运行命令head /proc/jiqsched得到的结果:
time delta intr_count pid command
1704475 0 0 730 head
1704476 1 0 0 swapper
1704477 1 0 0 swapper
1704478 1 0 0 swapper
1704478 0 0 6 loadmonitor
1704479 1 0 0 swapper
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -