嵌入式学习(五)——嵌入式Linux设备驱动开发(二)

姓名:李萌怡  学号: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

你可能感兴趣的:(嵌入式学习(五)——嵌入式Linux设备驱动开发(二))