姓名:李萌怡 学号:19020100103 学院:电子工程学院
转自:https://blog.csdn.net/light_in_dark/article/details/73368942
【嵌牛导读】:对于嵌入式系统的学习来说,学习嵌入式Linux驱动开发是十分重要的环节。本文对嵌入式linux设备系统开发的内核部分作以简要叙述及总结。
【嵌牛鼻子】:内核 时间 定时器
【嵌牛提问】:内核的延迟和定时是什么?应该怎样处理?
【嵌牛正文】
一、内核的时间
(1)Tick(滴答)
内核采用了一个新的时间单位来进行计时。该时间单位称为tick(滴答),一个tick对应硬件定时器两次中断之间的时间间隔。当前内核每秒钟硬件定时器会发生HZ次中断。tick和秒的换算关系为: 1 tick = 1/HZ秒。
HZ是在内核make menuconfig(内核的.config文件)时确定,如果要修改HZ值,需要重新编译内核。
(2)相对时间
内核从开机开始记录一个相对时间。内核利用全局变量jiffies来记录从开机到当前时间所经过的tick的总量。
jiffies++的工作是由硬件定时器的中断处理函数完成的。jiffies的类型是unsigned long,在32位平台上最大值为4G。因此,当HZ为1000时,大约49.7天会溢出一次。为了避免溢出,可以使用64位的变量jiffies_64,大约2000亿天会溢出一次。
内核里的很多延迟和定时,都是用jiffies进行判断的。如果要访问jiffies,应包含头文件”linux/sched.h”。
(3)绝对时间
1969 UNIX诞生,绝对时间为从1970年1月1日0时0分0秒开始到现在的时间。在内核中用两个结构体来记录绝对时间:timeval和timespec,在头文件“linux/time.h”中。
#include
struct timeval tval;
struct timespec tspec;
调用内核的函数来获得绝对时间
do_gettimeofday(&tval);
getnstimeofday(&tspec);
打印绝对时间
printk("%lds : %ldus\n", tval.tv_sec, tval.tv_usec);
printk("%lds : %ldns\n", tspec.tv_sec, tspec.tv_nsec);
二、内核的延迟和定时
延迟:当前程序停下来,等待某个条件满足。延迟是不得已的,如果程序可以运行,就不应该延迟;
定时:启动定时器,由内核(硬件定时器的中断处理函数)在未来的某个时间为启动定时器者完成某项工作。
(1)基于忙循环的延迟
如果是比较短时间的延迟,则可以通过在cpu上运行一段循环来延迟,如果要延迟较长的时间,则需要将当前进程置入睡眠来延迟。
#include
ndelay(10); //延迟10ns
udelay(20); //延迟20us
mdelay(30); //延迟30ms
内核用for循环实现上述3个函数。其中udelay用的最多,一般用于寄存器间设置的间隔。延迟时间和for循环次数的转换是一个经验值。内核在开机时会运行1s(或1个tick)的循环,然后记录循环的次数。udealy等的时间就通过该次数进行转换。这个次数记录在/proc/cpuinfo文件的变量bogomips中。
(2)基于等待队列的延迟(基于睡眠)
A、进程状态和运行队列
进程的核心结构体为“linux/sched.h”中的task_struct,区分进程的3个核心状态:
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
处于TASK_RUNNING状态的进程会组织在一个运行队列中,2.6.23内核以后,通过CFS调度器(Completely Fair Scheduler, 完全公平调度器)来调度,运行队列中的进程用rb_tree组织在一起。
B、直接睡眠延时(基于等待队列)
set_current_state(state);
state可以用TASK_INTERRUPT或TASK_UNINTERRUPTIBLE
schedule_timeout(delaytime);
delaytime以tick为单位,例如睡眠3s,则dalaytime可设置为3*HZ。
C、等待队列延时
每个要等待的条件都可以分配对应的等待队列,每个队列有一个等待队列头(wait_queue_head_t),等待队列定义在“linux/wait.h”头文件中。
#include
//声明等待队列头
wait_queue_head_t mywait;
//队列头使用前要初始化
init_waitqueue_head(&mywait);
//在char驱动的write函数中,如果缓冲区满则睡眠
ssize_t my_write(...)
{
int ret;
if (dev->wp == dev->buf_size) {
//wait_event(mywait, dev->wp < dev->buf_size);
ret = wait_event_interruptible(mywait, dev-wp < dev->buf_size);
if (ret != 0)
return -ERESTARTSYS;
}
//如果缓冲区非满,则可写
...
}
//在ioctl函数中,可以让缓冲区复位,此时可以唤醒等待队列中的睡眠进程
int my_ioctl(xxx)
{
//如果复位缓冲区,则唤醒整个队列
memset(xxx);
dev->wp = 0;
//wake_up(&mywait);
wake_up_interruptible(&mywait);
...
}
(3)定时器
定时器的特征:
A、启动定时器的人和执行定时器的人不一样
一般启动定时器的常常是某个进程或者中断,而内核中负责执行定时器的是硬件定时器的中断处理函数
B、定时器的执行时间一定是未来
内核利用jiffies来确定定时器的执行时间
C、定时器对应的函数只执行一次
一般谁准备定时器,谁提供执行函数;执行函数的人是内核的硬件定时器中断。如果希望实现一个循环的定时器,则需要在执行函数中自行将定时器再重新启动定时器。
D、定时器的函数在中断上下文(context)执行
因此,执行函数中不能睡眠
定时器的核心结构体定义在头文件“linux/timer.h”中,为timer_list。timer_list由启动者准备,当启动定时器后,timer_list会形成一个链表,由内核的硬件定时器中断来检查链表,看有没有定时器到时。
#include
//声明定时器
struct timer_list mytimer;
//定时器的执行函数
//当定时器到期后,由硬件定时器中断执行一次
static void
my_timer_func(unsigned long data)
{
...//不可睡眠
}
//初始化定时器
setup_timer(&mytimer, my_timer_func, data);
初始化定时器时传入的参数为timer_list的指针;执行函数;传给执行函数的参数
//启动定时器
mod_timer(&mytimer, jiffies+HZ);
//定时器一旦启动,就会加入一个timer_list的链表,一旦到时,就会被执行。
//启动定时器的人和执行的人不是一个。即使启动者退出,定时器仍然执行。
//删除定时器
del_timer(&mytimer);
如果模块要rmmod,在卸载之前,必须删除所有没执行的定时器。
————————————————
版权声明:本文为CSDN博主「light_in_dark」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/light_in_dark/article/details/73368942