从零开始之驱动发开、linux驱动(十四、字符驱动之按键中断方式实现和等待队列分析)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82708840

第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使用.

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82708840