第8节的轮询方式查询按键,非常的浪费CPU资源,这一节主要就是来解决这个问题的。
第12节的中断程序框架也给了出来,这一节我们来完成中断方式的代码实现和分析。
这节是通过使用等待队列的方式来解决CPU资源来等待信号浪费问题。
其主要原理是应用层读按键值,在驱动这边先先检查有没有按键事件发生,如果有则直接返回键值给应用程序。
如果没有,则把该内核进程加入到一个等待队列中(队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。即满足先进先出(删)的形式FIFO),并让该进程进行休眠。
在按键事件发生后,由按键事件,主动唤醒该等待队列。之后程序继续回到原来睡眠的地方继续向下执行,返回键值给应用程序。
假设我们是在读函数里检查,是否有按键事件发生,如果有,则直接返回,如果没有,则让系统休眠,直到按键等待事件发生,在按键事件中唤醒等待队列。这是一次读操作的流程。
等待队列头是在一个设备的驱动程序中定义,具设体来说是属于某个设备驱动的。该设备可被多个应用进程打开,使用。
而我们的驱动如果是阻塞访问的,则必须要某个I/O事件到达,应用进程才能正确访问。而在I/O事件没到来之前,如果应用进程访问该设备。则驱动程序中检测到该I/O事件确实还没准备好,则该设备驱动会先把该进程加入到设备所在的等待队列中,之后主动让该进程睡眠。在I/O准备好后,该I/O事件主动唤醒该设备驱动等待队列上的进程。
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/highmem.h> /* For wait_event_interruptible */
#include <asm/gpio.h>
#include <asm/uaccess.h>
static unsigned int major;
static struct class *third_class;
static struct device *third_dev;
static unsigned char key_val;
/* 定义一个等待队列的头 */
static DECLARE_WAIT_QUEUE_HEAD(button_wait_q);
/* 定义一个事件标志 */
static volatile int ev_press = 0;
struct pin_desc {
unsigned int pin;
unsigned int key_val;
};
/* 按下时 值分别是 0x01 , 0x02 */
/* 松开时 值分别是 0x00 , 0x00 */
static struct pin_desc pins_desc[] = {
{S5PV210_GPH0(2), 0x01},
{S5PV210_GPH0(3), 0x02},
};
static irqreturn_t irq_handler(int irq, void *dev_id)
{
struct pin_desc *p = dev_id;
int pin_val;
pin_val = gpio_get_value(p->pin);
/* 得到键值,判断时按下还是松开 */
if(pin_val)
{
/* 松开 */
key_val &= ~p->key_val;
}
else
{
/* 按下 */
key_val |= p->key_val;
}
/* 标记有事件发生 */
ev_press = 1;
wake_up_interruptible(&button_wait_q);
return IRQ_HANDLED;
}
/* open函数 */
static int third_drv_open(struct inode *inode, struct file *file)
{
int ret = 0;
ret = request_irq(IRQ_EINT(2), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint2",&pins_desc[0]);
if(ret)
{
printk(KERN_ERR"request_irq IRQ_EINT(2) fail");
}
ret = request_irq(IRQ_EINT(3), irq_handler, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "irq-eint3",&pins_desc[1]);
if(ret)
{
printk(KERN_ERR"request_irq IRQ_EINT(3) fail");
}
return 0;
}
ssize_t third_drv_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
int len;
if(size < 1)
{
return -EINVAL;
}
/* 等待事件到来,如果evpress为0,则进入睡眠 */
wait_event_interruptible(button_wait_q, ev_press);
/* 赋值只是为了消除告警 */
len = copy_to_user(array , &key_val, 1);
/* 清除中断事件标志 */
ev_press = 0;
return 1;
}
int third_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT(2), &pins_desc[0]);
free_irq(IRQ_EINT(3), &pins_desc[1]);
return 0;
}
static const struct file_operations third_drv_file_operation = {
.owner = THIS_MODULE,
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
static int __init third_drv_init(void)
{
/* 获取一个自动的主设备号 */
major = register_chrdev(0,"third_drv",&third_drv_file_operation);
if(major < 0)
{
printk(KERN_ERR"register_chrdev third_drv fail \n");
goto err_register_chrdev;
}
/* 创建一个类 */
third_class = class_create(THIS_MODULE, "third_class");
if(!third_class)
{
printk(KERN_ERR"class_create third_class fail\n");
goto err_class_create;
}
/* 创建从属这个类的设备 */
third_dev = device_create(third_class,NULL,MKDEV(major, 0), NULL, "button");
if(!third_dev)
{
printk(KERN_ERR"device_create third_dev fail \n");
goto err_device_create;
}
return 0;
/* 倒影式错误处理机制 */
err_device_create:
class_destroy(third_class);
err_class_create:
unregister_chrdev(major,"third_drv");
err_register_chrdev:
return -EIO;
}
static void __exit third_drv_exit(void)
{
/* 注销类里面的设备 */
device_unregister(third_dev);
/* 注销类 */
class_destroy(third_class);
/* 注销字符设备 */
unregister_chrdev(major,"third_drv");
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
比较简单的部分是,按键值的多少。
eint2按下是1,松开是0.
eint3按下是2,松开是0
两个都按下是3,松开是0
应用程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
char buf[2];
int fd = open("/dev/button", O_RDWR);
if(fd < 0)
{
printf("open /dev/%s fail\n",argv[1]);
return -1;
}
while(1)
{
read(fd, buf, 1);
printf("buf = %d, \n", buf[0]);
}
close(fd);
return 0;
}
~
上面应用程序有个问题,就是没法退出,必须要Ctrl + z强制退出或永远后台运行(kill杀死),不影响我们对驱动的分析和学习。
这节主要分析的是等待队列。
1.什么是的等待队列
等待队列是一种实现阻塞和唤醒的内核机制,很早就作为一个基本的功能单位出现在Linux内核中,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。
假设某些进程的执行依赖于某一个事件的发生,当事件发生后,所依赖它的多个任务才能执行,否则这些进程都不能执行 (让出CPU,进行睡眠),需要进行等待事件发生,其中等待该事件的所有进程会组成一个队列。其中事件发生后,该事件需要唤醒等待它的进程们。
2.相关数据结构
/* 等待队列头 */
struct __wait_queue_head {
spinlock_t lock; /* 自旋锁 */
struct list_head task_list; /* 链表头 */
};
typedef struct __wait_queue_head wait_queue_head_t;
/* 等待队列项 */
struct __wait_queue {
unsigned int flags; /* 标记状态 */
#define WQ_FLAG_EXCLUSIVE 0x01
void *private; /* 通常指向当前进程的 task_struct 结构体 */
wait_queue_func_t func; /* 通常是唤醒任务时使用的函数 */
struct list_head task_list; /* 等待任务链表 */
};
3.等待队列的组织结构
4.常用接口
定义
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
/* 定义一个等待队列头,默认会初始化好自旋锁和链表头 */
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
/* 定义一个等待队列项,默认也会初始化好里面的内容 */
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
/* 初始化一个新的等待队列项,current是当前进程的task_struct */
#define init_wait(wait) \
do { \
(wait)->private = current; \
(wait)->func = autoremove_wake_function; \
INIT_LIST_HEAD(&(wait)->task_list); \
(wait)->flags = 0; \
} while (0)
队列增加
/* 设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
/* 设置等待的进程为互斥进程,并将其添加进等待队列头(q)的队头中 */
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue_tail(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
队列删除
/* 在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除。 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
进入等待
/* 在 condition为0时进入进入等待队列,并睡眠该进程直到条件成立,在condition为非0值时忽略此次操作 */
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
/* 在condition为非0值时忽略此次操作 ,在 condition为0时进入进入等待队列,并睡眠该进程timeout时间
* 后,自动唤醒该进程(当然也可以在timeout前使用wake_up唤醒)
*/
#define wait_event_timeout(wq, condition, timeout) \
({ \
long __ret = timeout; \
if (!___wait_cond_timeout(condition)) \
__ret = __wait_event_timeout(wq, condition, timeout); \
__ret; \
})
/* 在condition为非0值时忽略此次操作 ,在 condition为0时进入进入等待队列睡眠直到条件成立
* cmd1命令在睡眠前执行 ,cmd2命令在睡眠后执行命令 */
#define wait_event_cmd(wq, condition, cmd1, cmd2) \
do { \
if (condition) \
break; \
__wait_event_cmd(wq, condition, cmd1, cmd2); \
} while (0)
/* 在condition为非0值时忽略此次操作 ,在 condition为0时进入进入等待队列睡眠直到条件成立
*
*/
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__ret = __wait_event_interruptible(wq, condition); \
__ret; \
})
其中wait_event和wait_event_interrupt的区别是wait_event_interrupt设置了TASK_INTERRUPTIBLE标记,使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒。
可见要唤醒一个被wait_event_interrupt睡眠的进程,在condotion为真的条件下,调用wake_up或被信号唤醒。
而被wait_event休眠的进程必须在condotion为真的条件下,调用wake_up。不能被信号之类唤醒。
在两者的基础上还可以增加超时自动唤醒功能。
唤醒队列
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) /* 唤醒队列一个项 */
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL) /* 唤醒队列多个项 */
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL) /* 唤醒所有项 */
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1) /* 和wake_up一样,调用使用了自旋锁 */
#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0) /* */
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
进入等待队列使用的和唤醒使用相同的一对操作,比如
wait_event(wq, condition) / wake_up(x)
wait_event_timeout(wq, condition, timeout) / wake_up(x)
wait_event_cmd(wq, condition, cmd1, cmd2) / wake_up(x)
wait_event_interruptible(wq, condition) / wake_up_interruptible(x)
经过对比,可以发现,我们这里使用wait_event和wait_event_interrupt都可以实现我们的功能。
如果想要接收信号的话,则就必须用wait_event_interrupt了,我们一般写应用程序,通常都是要用到信号的。
接下来我们看一下使用wait_event和wait_event_interrupt的对比
先看使用wait_event
使用ctrl + c不能强制中断
后台运行,使用kill -9也不能杀死
使用wait_event_interrupt睡眠的
可以被ctrl + c中断
也可以被成功杀死。
接下来我们简单来分析一下源码
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \ /* condition为0,则进入等待队列 */
__ret = __wait_event_interruptible(wq, condition); \
__ret; \
})
/* 标识可被信号打断TASK_INTERRUPTIBLE,schedule为任务调度函数 */
#define __wait_event_interruptible(wq, condition) \
___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0, \
schedule()) /* 这里传入调度函数 */
#define ___wait_event(wq, condition, state, exclusive, ret, cmd) \
({ \
__label__ __out; \
wait_queue_t __wait; \ /* 定义等待队列 */
long __ret = ret; /* explicit shadow */ \
\
INIT_LIST_HEAD(&__wait.task_list); \ /* 初始化链表 */
if (exclusive) \ /* 我们这里是0 */
__wait.flags = WQ_FLAG_EXCLUSIVE; \
else \
__wait.flags = 0; \
\
for (;;) { \
long __int = prepare_to_wait_event(&wq, &__wait, state);\ /* 初始化等待队列,查询是否有中断或杀死信号到来,设置当前进程状态 */
\
if (condition) \ /* 在condition到来后推出循环,第一次通常是condition不成立 */
break; \
\
if (___wait_is_interruptible(state) && __int) { \ /* 判断是否可被信号中断的,若可被中断,且收到信号,则进入 */
__ret = __int; \
if (exclusive) { \
abort_exclusive_wait(&wq, &__wait, \
state, NULL); \
goto __out; \
} \
break; \ /* 只有可被中断,且收到信号,才推出 */
} \
\
cmd; \ /* 系统调度处理,在这里休眠该进程,去执行其他进程 */
} \
finish_wait(&wq, &__wait); \ /* 完成等待 */
__out: __ret; \
})
其中要说明的是在某个地方唤醒后,将继续执行系统调度处理的下一行代码。
我们又会继续检查condition,即必须要condition非0才能退出这里,然后执行之前wait_event睡眠的地方。
prepare_to_wait_event
long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
if (signal_pending_state(state, current)) /* 判断是不是有挂起信号, */
return -ERESTARTSYS;
wait->private = current; /* 初始化为当前进程的task_struct */
wait->func = autoremove_wake_function; /* 条件满足时的唤醒操作 */
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list)) {
if (wait->flags & WQ_FLAG_EXCLUSIVE)
__add_wait_queue_tail(q, wait); /* 增加到等待队列中 */
else
__add_wait_queue(q, wait);
}
set_current_state(state); /* 设置当前状态到当前进行状态中 */
spin_unlock_irqrestore(&q->lock, flags);
return 0; /* 正常返回0 */
}
static inline int signal_pending_state(long state, struct task_struct *p)
{
if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL))) /* 检查不是可被中断(打断),不能打断则会直接return 0;继续挂起,直到condition为非0 */
return 0;
if (!signal_pending(p)) /* 检查是不是有信号到来,没有的话继续挂起 */
return 0;
/* 若是可中断的,或收到来杀死信号,则也返回非0值 */
return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
}
/* 应用层有发送杀死信号,也退出等待 */
static inline int __fatal_signal_pending(struct task_struct *p)
{
return unlikely(sigismember(&p->pending.signal, SIGKILL));
}
可见,这里主要就是初始化来等待队列,和把等待队列加入了我们定义的头等待头所在的等待队列中。
调度系统就不再这里分析了,现在内核的调度比较复杂,使用了各种平衡算法。
我目前也没怎么看懂。
再看一下唤醒队列
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags); /* 上锁 */
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags); /* 解锁 */
}
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next;
/* 查找队列 */
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
/* 执行退出,当然这里可能一次唤醒多个,所以检查nr_exclusive就可以,flags标记了condition是否成立了 */
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
func函数实在进入等待队列时初始化的
/* 自动唤醒函数 */
int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
int ret = default_wake_function(wait, mode, sync, key);
if (ret) /* 确定condition可用等,返回1,则删除等待队列中这一项 */
list_del_init(&wait->task_list);
return ret;
}
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
void *key)
{
/* 尝试唤醒该线程 */
return try_to_wake_up(curr->private, mode, wake_flags);
}
/**
* try_to_wake_up - wake up a thread
* @p: the thread to be awakened
* @state: the mask of task states that can be woken
* @wake_flags: wake modifier flags (WF_*)
*
* Put it on the run-queue if it's not already there. The "current"
* thread is always on the run-queue (except when the actual
* re-schedule is in progress), and as such you're allowed to do
* the simpler "current->state = TASK_RUNNING" to mark yourself
* runnable without the overhead of this.
*
* Return: %true if @p was woken up, %false if it was already running.
* or @state didn't match @p's state.
*/
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
unsigned long flags;
int cpu, success = 0;
/*
* If we are going to wake up a thread waiting for CONDITION we
* need to ensure that CONDITION=1 done by the caller can not be
* reordered with p->state check below. This pairs with mb() in
* set_current_state() the waiting thread does.
*/
/*很有可能需要唤醒一个thread的函数,某个条件必须成立,为了取到最新的没有没优化
的条件数值,使用内存屏障来实现.*/
smp_mb__before_spinlock();
raw_spin_lock_irqsave(&p->pi_lock, flags); /* 禁止本地中断 */
if (!(p->state & state)) /* 检查进程的状态p->state是否属于被当作参数传递给函数的状态掩码state */
goto out;
success = 1; /* we're going to change ->state ,我们唤醒 */
/*获取这个进程当前处在的cpu上面,并不是时候进程就在这个cpu上运行,后面会挑选cpu*/
cpu = task_cpu(p);
/*
* Ensure we load p->on_rq _after_ p->state, otherwise it would
* be possible to, falsely, observe p->on_rq == 0 and get stuck
* in smp_cond_load_acquire() below.
*
* sched_ttwu_pending() try_to_wake_up()
* [S] p->on_rq = 1; [L] P->state
* UNLOCK rq->lock -----.
* \
* +--- RMB
* schedule() /
* LOCK rq->lock -----'
* UNLOCK rq->lock
*
* [task p]
* [S] p->state = UNINTERRUPTIBLE [L] p->on_rq
*
* Pairs with the UNLOCK+LOCK on rq->lock from the
* last wakeup of our task and the schedule that got our task
* current.
*/
smp_rmb();
/*使用内存屏障保证p->on_rq的数值是最新的.如果task已经在rq里面,即进程已经处于
runnable/running状态.ttwu_remote目的是由于task p已经在rq里面了,并且并没有完全
取消调度,再次会wakeup的话,需要将task的状态翻转,将状态设置为TASK_RUNNING,这样
task就一直在rq里面运行了.这种情况直接退出下面的流程,并对调度状态/数据进行统计*/
if (p->on_rq && ttwu_remote(p, wake_flags))
goto stat;
#ifdef CONFIG_SMP /* 多处理器 */
/*
* If the owning (remote) cpu is still in the middle of schedule() with
* this task as prev, wait until its done referencing the task.
*/
while (p->on_cpu)
cpu_relax();
/*
* Pairs with the smp_wmb() in finish_lock_switch().
*/
smp_rmb();
/*根据进程p的状态来确定,如果调度器调度这个task是否会对load有贡献.*/
p->sched_contributes_to_load = !!task_contributes_to_load(p);
p->state = TASK_WAKING; /*设置进程状态为TASK_WAKING*/
/*根据进程的所属的调度类调用相关的callback函数,这里进程会减去当前所处的cfs_rq的最小
vrunime,因为这里还没有确定进程会在哪个cpu上运行,等确定之后,会在入队的时候加上新cpu的
cfs_rq的最小vruntime*/
if (p->sched_class->task_waking)
p->sched_class->task_waking(p);
/*根据进程p相关参数设定和系统状态,为进程p选择合适的cpu供其运行*/
cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
/*如果选择的cpu与进程p当前所在的cpu不相同,则将进程的wake_flags标记添加需要迁移
,并将进程p迁移到cpu上.*/
if (task_cpu(p) != cpu) {
wake_flags |= WF_MIGRATED;
set_task_cpu(p, cpu);
}
#endif /* CONFIG_SMP */
*该句是重点,进程p入队操作并标记p为runnable状态,同时执行wakeup preemption,即唤醒抢占(支持内核抢占的话)*/
ttwu_queue(p, cpu);
stat:/*调度相关的统计*/
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
根据进行状态state来决定是否需要继续唤醒动作,这就是p->state & state的目的
获取进程p所在的当前cpu
如果当前cpu已经在runqueue里面了,则需要翻转进程p的状态为TASK_RUNNING,这样进程p就一直在rq里面,不需要继续调度,退出唤醒动作
如果进程p在cpu上,则期待某个时刻去修改这个数值on_cpu为0,这样代码可以继续运行.对于cpu_relax()真实含义如下:cpu_relax()函数解析
根据WALT算法来实现rq当前task和新唤醒的task p相关的task load和rq相关的runnable_load的数值,具体怎么实现看:WALT基本原理
为task p挑选合适的cpu
如果挑选的cpu与进程p所在的cpu不是同一个cpu,则进程task migration操作
将进程p入队操作
统计调度相关信息作为debug使用.