定时器、延时

前瞻:

1. 软件定时器(如 struct timer_list

  • 不占用 CPU 资源(在等待期间):

    • 定时器基于内核的 时间轮(timer wheel) 机制,由系统时钟中断(tick)驱动。

    • 在定时器到期前,CPU 可以执行其他任务,定时器只是挂载在队列中。

    • 到期时通过 中断上下文 触发回调函数(如 timer_fun),此时会短暂占用 CPU。

  • 特点

    • 低精度:依赖 jiffies(通常 1ms~10ms 精度)。

    • 不阻塞进程:适合延迟执行非实时任务(如你的 LED 控制示例)。


2. 忙等待延时(如 ndelayudelay

  • 主动占用 CPU

    • 通过空循环消耗 CPU 周期实现精确短延时(见前文分析)。

    • 不释放 CPU,期间不能调度其他任务。

  • 特点

    • 高精度(纳秒/微秒级)。

    • 仅用于原子上下文(如中断、自旋锁内)。


关键区别

特性 定时器(timer_list 忙等待(udelay
是否占用 CPU ❌ 不占用(等待期间) ✅ 完全占用
精度 低(ms 级) 高(ns/us 级)
适用场景 延迟任务、非实时操作 原子上下文、短延时
调度影响 可睡眠,触发回调时占用 不可睡眠,持续阻塞
#include 
#include 
#include 
#include 
#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
static dev_t devno;//设备号
static struct cdev *pcdev;//字符设备结构体
static struct class* pclass;//设备类
static struct device *pdevice;//设备
static char ledstat;//自定义的led灯状态变量


//接收映射的虚拟地址
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIODIR;
static void __iomem *pGPIODR;


static void timer_fun(unsigned long data);

DEFINE_TIMER(my_timer, timer_fun, 0, 0);
static void timer_fun(unsigned long data)
{
    pr_info("timer_fun success\n");
    //回收定时器
    del_timer(&my_timer);
    return;
}

//文件操作函数
static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{
    int ret;
    //当用户调用read时,把ledstat的值给用户
    ret = copy_to_user(puser,&ledstat,sizeof(ledstat));
    
    pr_info("read success\n");
    return sizeof(ledstat);
}
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
{
    unsigned int tmpvalue;
    int ret;
    ledstat = 0;
    //修改定时器超时时间
    mod_timer(&my_timer,jiffies+3*HZ);
    //add_timer(&my_timer);
    //拷贝用户调用write写入的数据到ledstat
    ret = copy_from_user(&ledstat,puser,sizeof(ledstat));
    //写入1开灯
    if(ledstat == '1'){
        //置0开灯
        tmpvalue = readl(pGPIODR);
        tmpvalue &= ~(0x01 << 3);
        writel(tmpvalue, pGPIODR);
    }
    //写入0关灯
    if(ledstat == '0'){
        //置1关灯
        tmpvalue = readl(pGPIODR);
        tmpvalue |= (0x01 << 3);
        writel(tmpvalue, pGPIODR);
    }
    
    pr_info("write success\n");
    return sizeof(ledstat);
}
static int led_open(struct inode *node, struct file *fp)
{
    udelay(100);
    pr_info("open success\n");
    return 0;
}
static int led_release(struct inode *node, struct file *fp)
{
    pr_info("release success\n");
    return 0;
}
//文件操作函数结构体
static struct file_operations fops = {
    .owner = THIS_MODULE, //计数,表示有几个模块调用文件操作
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write     
};

//对/sys/下的属性文件读写操作,当用户查看属性时自动调用show,当写入属性值时调用store
static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{   
    ssize_t len = 0;
    //如果为1,把LED_ON传给用户
    if(1 == ledstat)
    {
        len = sprintf(buf,"LED_ON\n");
    }
    if(0 == ledstat)
    {
       len = sprintf(buf,"LED_OFF\n");
    }
 
    return len;//返回打印多少个字节
}
static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    unsigned int tmpvalue = 0;
    char tmpbuff[32] = {0};
    //把用户写入的数据传给tmpbuff
    sscanf(buf, "%s", tmpbuff);
    //传的LED_ON,数据寄存器置0开灯
    if(!strcmp(tmpbuff,"LED_ON"))
    {
        tmpvalue = readl(pGPIODR);
        tmpvalue &= ~(0x01 << 3);
        writel(tmpvalue, pGPIODR);
        ledstat = 1;
    }
    else if(!strcmp(tmpbuff,"LED_OFF"))
    {
        //置1关灯
        tmpvalue = readl(pGPIODR);
        tmpvalue |= (0x01 << 3);
        writel(tmpvalue, pGPIODR);
        ledstat = 0;
    }
    return count;//实际接收到的字节数
}
static struct device_attribute led_attr =  __ATTR(bright, 0664, led_show, led_store);//绑定属性读写函数跟属性节点

//驱动入口内调函数
static int __init led_init(void)
{
    int tmpvalue;//局部变量
    struct device_node *pdtsdevice = NULL;
    unsigned int regaddr[10] = {0};
    //申请设备号
    alloc_chrdev_region(&devno, 0,1,"myled");
    pr_info("alloc_chrdev_region success major:%d  minor:%d\n",MAJOR(devno),MINOR(devno));
    //申请字符设备结构体
    pcdev = cdev_alloc();
    pr_info("cdev_alloc success!\n");
    //绑定文件操作函数结构体
    pcdev->ops = &fops;
    pr_info("fops add success!\n");
    //绑定设备号
    cdev_add(pcdev,devno,1);
    pr_info("cdev_add success!\n");
    //创建设备类
    pclass = class_create(THIS_MODULE,"led_class");
    pr_info("class creat success\n");
    //创建设备
    pdevice = device_create(pclass,NULL,devno,NULL,"led%d",0);
    pr_info("device creat success\n");
    //虚拟地址映射
    // pCCGR1 = ioremap(0x020c406c,4);
    // pIOMUX = ioremap(0x020e0068,4) ;
    // pIOPAD = ioremap(0x020e02f4,4) ; 
    // pGPIODIR = ioremap(0x0209c004,4) ;
    // pGPIODR = ioremap(0x0209c000,4) ;

    // pCCGR1 = devm_ioremap(pdevice,0x020c406c,4);//换成该函数完成地址映射,后面销毁设备时就会解除地址映射
    // pIOMUX = devm_ioremap(pdevice,0x020e0068,4) ;
    // pIOPAD = devm_ioremap(pdevice,0x020e02f4,4) ; 
    // pGPIODIR = devm_ioremap(pdevice,0x0209c004,4) ;
    // pGPIODR = devm_ioremap(pdevice,0x0209c000,4) ;
    pdtsdevice = of_find_node_by_path("/myled");//获取设备树创建的led节点
    of_property_read_u32_array(pdtsdevice,"reg",regaddr,10);//读取设备树led节点保存的寄存器地址
    pCCGR1 = devm_ioremap(pdevice,regaddr[0],regaddr[1]);
    pIOMUX = devm_ioremap(pdevice,regaddr[2],regaddr[3]);
    pIOPAD = devm_ioremap(pdevice,regaddr[4],regaddr[5]);
    pGPIODIR = devm_ioremap(pdevice,regaddr[6],regaddr[7]);
    pGPIODR = devm_ioremap(pdevice,regaddr[8],regaddr[9]);
    pr_info("ioremap success\n");
    //GPIO外设时钟使能
    tmpvalue = readl(pCCGR1);
    tmpvalue &= ~(0x3 << 26);
    tmpvalue |= (0x3 << 26);
    writel(tmpvalue,pCCGR1);
    //引脚复用为GPIO
    writel(0x5, pIOMUX);
    //设置引脚电器属性
    writel(0x10B0, pIOPAD);
    //GPIO方向设为输出
    tmpvalue = readl(pGPIODIR);
    tmpvalue |= 0x1 << 3;
    writel(tmpvalue, pGPIODIR);
    
    //置1关灯
        tmpvalue = readl(pGPIODR);
        tmpvalue |= (0x01 << 3);
        writel(tmpvalue, pGPIODR);
        ledstat = 0;
    //创建设备属性节点
    device_create_file(pdevice,&led_attr);
    pr_info("device_create_file success");
    init_timer(&my_timer);
    pr_info("led init success\n");
    return 0;
}

//驱动出口内调函数
static void __exit led_exit(void)
{
    //解除地址映射
    // iounmap(pCCGR1);
    // iounmap(pIOMUX);
    // iounmap(pIOPAD);
    // iounmap(pGPIODIR);
    // iounmap(pGPIODR);
    //销毁设备
    device_destroy(pclass,devno);
    //销毁设备类
    class_destroy(pclass);
    //删除字符设备结构体
    cdev_del(pcdev);
    //释放设备号
    unregister_chrdev_region(devno,1);
    
    pr_info("led exit success\n");
    return;
}

//驱动入口
module_init(led_init);
//驱动出口
module_exit(led_exit);

MODULE_LICENSE("GPL");

前瞻结束

17.内核定时器

1.概念:

        赫兹(Hz)在内核中表示系统一秒所对应的节拍数量,目前为100。在内核中的.config中定义, jiffies表示系统节拍,记录系统启动到当前累加的数字,如果想通过内核定时器计时则定时时间为: jiffers + (N*100)定时时间到达后可以调用回调函数。

函数接口:

函数名 功能

init_timer 初始化定时器

DEFINE_TIMER 定义定时器

add_timer 激活定时器

del_timer 删除定时器

mod_timer 修改定时器超时时间

time_before 判断时间a是否在b之前

time_after 判断时间a是否在b之后

jiffies_to_usecs jiffies转换为us

jiffies_to_msecs jiffies转换为ms

usecs_to_jiffies us转换为jiffies

msecs_to_jiffies ms转换为jiffies

函数原型:

init_timer(timer);
DEFINE_TIMER(_name, _function, _expires, _data);
void add_timer(struct timer_list *timer);
int del_timer(struct timer_list *timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
time_after(a,b);
time_before(a,b);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned long usecs_to_jiffies(const unsigned int u);
unsigned long msecs_to_jiffies(const unsigned int m)

2.延时

        Linux内核实现了延时函数, delay相关函数是忙等待,一直占用CPU资源。 sleep相关函数不会一直占用CPU资源,会在睡眠的过程中释放CPU,让进程挂起,但是Linux系统是非实时操作系统,所以当CPU切换出该进程到CPU切换回该进程中间的时间一般大于睡眠设定的时间。

函数接口:

函数名 功能
ndelay 忙等待延时ns
udelay 忙等待延时us
mdelay 忙等待延时ms
msleep 睡眠ms
msleep_interruptible 可中断睡眠ms
ssleep 睡眠s
static inline void ndelay(unsigned long x);
udelay(n);
mdelay(n);
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
static inline void ssleep(unsigned int seconds);

3.高精度定时器

         如果我们想定时为us或者ns级别的定时就需要使用内核中的高精度定时器。

函数接口:

函数名 功能
hrtimer_init 初始化高分辨率定时器
hrtimer_start 启动高分辨率定时器
hrtimer_forward_now 调整高分辨率定时器到期时间
hrtimer_cancel 取消高分辨率定时器
ktime_set 设置定时器的到期时间
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode
mode);
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode
mode);
static inline u64 hrtimer_forward_now(struct hrtimer *timer, ktime_t interval);
int hrtimer_cancel(struct hrtimer *timer);
static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs);

你可能感兴趣的:(定时器、延时)