延时又称为等待,延时分为两类:忙延时和休眠延时。
忙延时: 当任务进行忙延时时,任务将会导致所占用的CPU资源进行白白消耗,类似原地空转。
休眠延时: 进程进入休眠状态,进程会释放所占用的CPU资源给其他进程使用。
忙延时应用在等待时间极短的场合,进程和中断都可以使用忙延时。
休眠延时应用在等待时间较长的场合,只能用于进程,不能用于中断。
注意:CPU资源在进程之间的切换也是需要时间的消耗的 。
忙延时相关的延时函数:
ndelay(int n) //纳秒级延时
ndelay(10);//忙延时10纳秒
udelay(int n) //微秒级延时
udelay(10);//忙延时10微秒
mdelay(int n) //毫秒级延时
mdelay(5); //忙延时5毫秒,CPU空转5秒
休眠延时相关的延时函数:
msleep(int n); //毫秒级休眠延时
msleep(100);//休眠延时100毫秒
ssleep(int n); //秒级休眠延时
ssleep(10);//休眠延时10秒
schedule();//永久休眠
schedule_timeout(5 * HZ);//休眠延时5秒钟,
问:进程在内核空间虽然可以调用msleep / ssleep / schedule / schedule_timeout,能够进行随时随地的进入休眠等待状态,但是不能被随时随地被唤醒。如何才能让进程在内核控件进行随时随地的休眠和被唤醒呢?
示例应用场景:
一个进程能够获取按键的操作状态(按下或者松开)的需求分析:进程调用read系统调用函数来获取按键的操作状态(按下或者松开)。由于用户操作按键(按下或者松开)本身就是一个随机过程(开心了按两下,不开心不操作),read读进程为了能够及时获取到用户的按键操作,首先想到采用轮训方式(死等,while(1)),但是这种方式会大量消耗CPU资源,大大降低了CPU的利用率(CPU永远只做一件事),于是乎想到轮训的死对头中断机制也就是说进程调用read系统调用函数来获取按键的操作状态,最终进程调用到底层驱动的read接口,如果进程在驱动的read接口中发现按键无操作(既没有按下也没有松开),干脆让read进程在驱动的read接口中进行休眠等待,等待着按键有操作,一旦read进程在驱动的read接口中进行休眠等待,read进程会释放掉占用的CPU资源给其他进程使用(中断不需要给,它会直接抢占),一旦将来用户对按键进行按下或者松开操作,势必产生按键中断,表示按键有操作,此时只需在按键的中断处理函数中去唤醒休眠的read进程一旦read进程被唤醒,read进程再去读取按键的操作状态即可返回到用户空间,此时此刻,CPU至少做2件事(一个是执行read进程,另一个是其他进程),大大提高了CPU的利用率。
所以,只要用户进程操作外设,外设的数据处理速度远远慢于CPU,将来驱动势必利用等待队列来实现休眠等待外设准备好数据。
等带队列能让用户进程在内核空间随时随地休眠、随时随地被唤醒。
等待队列中操作的对象就是进程。
等待队列=等待+队列 = 要休眠的进程排成一队,形成等待队列,这些休眠的进程要等待某个事件到来,事件没有发生就休眠去等待。
这里举个类似下图的例子:
小鸡作为要休眠的进程(驱动完成)、鸡妈妈作为等待队列头(驱动完成)、老鹰作为进程调度器(由内核完成)。
具体编程步骤:
wait_queue_head_t wq; //定义等待队列头对象
init_waitqueue_head(&wq);//初始化等待队列头
wait_queue_t wait; //定义装载休眠进程的容器(造小鸡)
init_waitqueue_entry(&wait, current); //将当前要休眠的进程添加到容器wait中
add_wait_queue(&wq, &wait);
set_current_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠类型
set_current_state(TASK_UNINTERRUPTIBLE);//设置为不可中断的休眠类型
schedule();
//注意:不能单独调用此函数,否则要休眠的当前进程
//会默认放到内核的默认等待队列中,将来如果要唤醒,做不到随时随地了
set_current_state(TASK_RUNNING);
remove_wait_queue(&wq, &wait);
if(signal_pending(current))
{
printk("进程由于接收到了信号引起的唤醒!\n");
return -ERESTARTSYS;
}
else
{
printk("进程由于驱动主动引起唤醒!\n");
//接下里被唤醒的进程就可以访问硬件
//说明硬件数据准备就绪了
}
wake_up(&wq);//唤醒wq等待队列中所有的休眠进程
wake_up_interruptible(&wq);//唤醒wq等待队列中所有的休眠类型为可中断的进程
实现一个使read操作的进程休眠,另一个进程操作write唤醒被休眠的进程的驱动。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //TASK_INTERRUPTIBLE等
//定义一个等待队列头对象(造鸡妈妈)
static wait_queue_head_t rwq;
static ssize_t wake_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//1.定义初始化装载休眠进程的容器(构造小鸡)
//将当前进程添加到容器中
//一个进程一个容器
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
//2.将当前进程添加到等待队列中去
add_wait_queue(&rwq, &wait);
//3.设置当前进程休眠的状态类型为可中断
set_current_state(TASK_INTERRUPTIBLE);
//4.当前进程正式进入休眠状态
//此时代码停止不前
//当前进程释放CPU资源
//一旦被唤醒,进程继续往下执行
printk("读进程[%s][%d]将进入休眠状态...\n",
current->comm, current->pid);
schedule();
//5.一旦被唤醒,设置进程的状态为运行
set_current_state(TASK_RUNNING);
//6.将被唤醒的进程从队列中移除
remove_wait_queue(&rwq, &wait);
//7.判断进程被唤醒的原因
if(signal_pending(current)) {
printk("读进程[%s][%d]由于接收到了信号引起唤醒\n",
current->comm, current->pid);
return -ERESTARTSYS;
} else {
printk("读进程[%s][%d]由于驱动主动引起唤醒.\n",
current->comm, current->pid);
}
return count;
}
static ssize_t wake_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
//1.唤醒休眠的读进程
printk("写进程[%s][%d]唤醒读进程\n",
current->comm, current->pid);
wake_up(&rwq);
return count;
}
//定义初始化硬件操作接口对象
static struct file_operations wake_fops = {
.owner = THIS_MODULE,
.read = wake_read,
.write = wake_write
};
//定义初始化混杂设备对象
static struct miscdevice wake_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mywake",
.fops = &btn_fops
};
static int wake_init(void)
{
//注册混杂设备对象
misc_register(&wake_misc);
//初始化等待队列头(武装鸡妈妈)
init_waitqueue_head(&rwq);
return 0;
}
static void wake_exit(void)
{
//卸载混杂设备对象
misc_deregister(&wake_misc);
}
module_init(wake_init);
module_exit(wake_exit);
MODULE_LICENSE("GPL");
obj-m += wake_drv.o
all:
make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
make -C /opt/kernel SUBDIRS=$(PWD) clean
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/mywake", O_RDWR);
if (fd < 0)
return -1;
if(argc != 2) {
printf("用法:%s \n" , argv[0]);
return -1;
}
if(!strcmp(argv[1], "r"))
read(fd, NULL, 0); //启动一个读进程
else if(!strcmp(argv[1], "w"))
write(fd, NULL, 0); //启动一个写进程
close(fd);
return 0;
}
实现一个休眠等待读取按键值的驱动,进程任务休眠等待按键,按键按下并唤醒进程读取当前按键的键值。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //TASK_INTERRUPTIBLE等
#include
//声明描述按键信息的数据结构
struct btn_resource {
int gpio; //按键对应的GPIO编号
char *name;//按键名称
int code;//按键值
};
//声明上报按键信息的数据结构
struct btn_event {
int state; //上报按键的状态:1:按下;0:松开
int code; //上报按键值
};
//定义初始化四个按键的硬件信息对象
static struct btn_resource btn_info[] = {
{
.gpio = PAD_GPIO_A + 28,
.name = "KEY_UP",
.code = KEY_UP
}
};
//分配内核缓冲区,记录当前操作的按键信息
static struct btn_event kbtn;
//定义一个等待队列头对象(造鸡妈妈)
static wait_queue_head_t rwq;
static ssize_t btn_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//1.定义初始化装载休眠进程的容器(构造小鸡)
//将当前进程添加到容器中
//一个进程一个容器
wait_queue_t wait;
init_waitqueue_entry(&wait, current);
//2.将当前进程添加到等待队列中去
add_wait_queue(&rwq, &wait);
//3.设置当前进程休眠的状态类型为可中断
set_current_state(TASK_INTERRUPTIBLE);
//4.当前进程正式进入休眠状态
//此时代码停止不前
//当前进程释放CPU资源
//一旦被唤醒,进程继续往下执行
schedule();
//5.一旦被唤醒,设置进程的状态为运行
set_current_state(TASK_RUNNING);
//6.将被唤醒的进程从队列中移除
remove_wait_queue(&rwq, &wait);
//7.判断进程被唤醒的原因
if(signal_pending(current)) {
printk("读进程[%s][%d]由于接收到了信号引起唤醒\n",
current->comm, current->pid);
return -ERESTARTSYS;
} else {
//此时的kbtn已经被中断处理函数进行赋值操作
copy_to_user(buf, &kbtn, sizeof(kbtn));
}
return count;
}
//中断处理函数
static irqreturn_t button_isr(int irq, void *dev)
{
//1.获取当前操作的按键的硬件信息
struct btn_resource *pdata = dev;
//2.获取按键的状态和按键值保存在全局变量中
kbtn.state = gpio_get_value(pdata->gpio);
kbtn.code = pdata->code;
//3.一旦有按键操作,硬件上势必产生中断
//也就说明按键有操作,应该唤醒read进程读取按键的信息
wake_up(&rwq);
return IRQ_HANDLED; //中断返回有可能才轮到read进程执行
}
//定义初始化硬件操作接口对象
static struct file_operations btn_fops = {
.owner = THIS_MODULE,
.read = btn_read,
};
//定义初始化混杂设备对象
static struct miscdevice btn_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mybtn",
.fops = &btn_fops
};
static int btn_init(void)
{
int i;
//注册混杂设备对象
misc_register(&btn_misc);
//初始化等待队列头(武装鸡妈妈)
init_waitqueue_head(&rwq);
//申请GPIO资源
//申请中断资源,注册中断处理函数
for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
int irq = gpio_to_irq(btn_info[i].gpio);
gpio_request(btn_info[i].gpio,
btn_info[i].name);
request_irq(irq, button_isr,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
btn_info[i].name, &btn_info[i]);
}
return 0;
}
static void btn_exit(void)
{
int i;
//各种释放
for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
int irq = gpio_to_irq(btn_info[i].gpio);
gpio_free(btn_info[i].gpio);
free_irq(irq, &btn_info[i]);
}
//卸载混杂设备对象
misc_deregister(&btn_misc);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
//声明按键信息数据结构
struct btn_event {
int state; //按键状态:1:松开;0:按下
int code; //按键值
};
int main(int argc, char *argv[])
{
int fd;
struct btn_event btn; //分配用户缓冲区,记录按键的信息
//打开设备
fd = open("/dev/mybtn", O_RDWR);
if (fd < 0) {
printf("打开设备失败!\n");
return -1;
}
while(1) {
read(fd, &btn, sizeof(btn));
printf("按键[%d]的状态为[%s]\n",
btn.code, btn.state ?"松开":"按下");
}
//关闭设备
close(fd);
return 0;
}
obj-m += btn_drv.o
all:
make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
make -C /opt/kernel SUBDIRS=$(PWD) clean
使用等待队列的方式能够很好的解决应用层的进程调用外设却需要等待外设准备就绪的问题。