BSP开发学习4Linux 内核时间管理

Posted 与光同程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BSP开发学习4Linux 内核时间管理相关的知识,希望对你有一定的参考价值。

Linux 内核时间管理

概述

Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 1000Hz,100Hz 等等说的就是系统节拍率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面:

-> Kernel Features
-> Timer frequency ( [=y])

配置好了以后可以在根目录.config文件中看到 CONFIG_HZ 定义频率

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0,jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:
示例代码 50.1.1.2 include/jiffies.h 文件代码段

76 extern u64 __jiffy_data jiffies_64;
77 extern unsigned long volatile __jiffy_data jiffies;

jiffies常用API:

将 jiffies 类型的参数 j 分别转换为对应的毫秒、微秒、纳秒。

int jiffies_to_msecs(const unsigned long j)
int jiffies_to_usecs(const unsigned long j)
u64 jiffies_to_nsecs(const unsigned long j)

将毫秒、微秒、纳秒转换为 jiffies 类型。

long msecs_to_jiffies(const unsigned int m)
long usecs_to_jiffies(const unsigned int u)
unsigned long nsecs_to_jiffies(u64 n)

内核延时

长延时

msecs_to_jiffies(msec); //将毫秒数转换为jiffies数
timer_before(a, b);
timer_after(b, a);

短延时

udelay(unsigned long usecs);
ndelay(unsigned long nsecs);
mdelay(unsigned long msecs);

以上三个都是忙等待,类似于while(time)time–;会一直占用CPU,所以对于毫秒级mdelay函数不建议使用,转而使用msleep函数代替。前两者主要用于硬件上对延时要求高的时候使用。

睡眠延时

void msleep(unsigned int msecs)

    unsigned long timeout = msecs_to_jiffies(msecs) + 1;

	while (timeout)
		timeout = schedule_timeout_uninterruptible(timeout);

msleep()是不可打断的睡眠,msleep_interruptible()是可打断的睡眠,所以在条件判断中!signal_pending(current)判断是否是被信号唤醒的(被信号唤醒signal_pending(current)返回值不为0),如果不是则继续执行schedule_timeout_interruptible()进行睡眠
  setup_timer_on_stack创建一个定时器,到期时间为expire = timeout + jiffies,到期后执行process_timeout,此函数就是用来调用wake_up_process唤醒当前进程,从schedule()下一句继续执行,但由于可能是被信号提前唤醒,所以执行timeout = expire - jiffies;得到剩余timeout的睡眠时间,返回给用户

内核定时器

Linux 内核定时器采用系统时钟来实现当超时时间到了以后设置的定时处理函数就会执行,和我们使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。Linux 内核使用 timer_list 结构体表示内核定时器,timer_list 定义在文件include/linux/timer.h 中,定义如下

struct timer_list 
 struct list_head entry;
 unsigned long expires; /* 定时器超时时间,单位是节拍数 */
 struct tvec_base *base;
 void (*function)(unsigned long); /* 定时处理函数 */
 unsigned long data; /* 要传递给 function 函数的参数 */
 int slack;
;

1、init_timer 函数
init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下。init_timer 函数原型如下:

void init_timer(struct timer_list *timer)

函数参数和返回值含义如下:

  • timer:要初始化定时器。
  • 返回值:没有返回值。

2、add_timer 函数
add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,
定时器就会开始运行,函数原型如下:

void add_timer(struct timer_list *timer)

函数参数和返回值含义如下:

  • timer:要注册的定时器。
  • 返回值:没有返回值。
    3、del_timer 函数
    del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。
    在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时
    器之前要先等待其他处理器的定时处理器函数退出。del_timer 函数原型如下:
int del_timer(struct timer_list * timer)

函数参数和返回值含义如下:

  • timer:要删除的定时器。
  • 返回值:0,定时器还没被激活;1,定时器已经激活。
    4、del_timer_sync 函数
    del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,
    del_timer_sync 不能使用在中断上下文中。del_timer_sync 函数原型如下所示:
int del_timer_sync(struct timer_list *timer)

函数参数和返回值含义如下:

  • timer:要删除的定时器。
  • 返回值:0,定时器还没被激活;1,定时器已经激活。
    5、mod_timer 函数
    mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时
    器!函数原型如下:
int mod_timer(struct timer_list *timer, unsigned long expires)

函数参数和返回值含义如下:

  • timer:要修改超时时间(定时值)的定时器。
  • expires:修改后的超时时间。
  • 返回值:0,调用 mod_timer 函数前定时器未被激活;1,调用 mod_timer 函数前定时器已被激活。关于内核定时器常用的 API 函数就讲这些,内核定时器一般的使用流程如下所示:

定时器驱动示例

/*
 * @Copyright: 
 * @FileNames: 
 * @Description: 
 * @Author: yangyue19@hikvision.com.cn
 * @Date: 2022-08-02 15:14:01
 * @Version: V1.0
 * @LastEditTime: 2022-08-02 17:29:20
 */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>

#include <linux/timer.h>

#define TIMER_MAJOR  201

#define CLOSE_CMD       0X00
#define OPEN_CMD        0X01
#define SER_PERIOD_CMD  0X02

typedef struct
    struct cdev cdev;
    dev_t devid;
    int major;
    int minor;

    //生成设备阶段
    struct class *class;
    struct device *device;
    //等待的时间
    int time_period;
    struct timer_list timer;
test_timer_t;

test_timer_t test_timer;

static int timer_open(struct inode *inode,struct file* filp)
    printk("TIMER OPEN:%d\\n",test_timer.time_period);
    return 0;

static int timer_release(struct inode *inode,struct file* filp)
    printk("TIMER CLOSE:%d\\n",test_timer.time_period);
    return 0;



static long timer_ioctl(struct  file*flip ,unsigned int cmd,unsigned long arg)

    unsigned long flags;
    printk("cmd%d\\r\\n",cmd);
    switch(cmd)
        case CLOSE_CMD:
            del_timer(&test_timer.timer);
            break;
        case OPEN_CMD:
            test_timer.time_period=arg;
            mod_timer(&test_timer.timer, jiffies+msecs_to_jiffies(test_timer.time_period));
            break;
        case SER_PERIOD_CMD:
            mod_timer(&test_timer.timer, jiffies + msecs_to_jiffies(arg));
            break;
        default:
            break;
    

static const struct file_operations timer_ops=
    .open=timer_open,
    .release=timer_release,
    .unlocked_ioctl=timer_ioctl
;

void timer_function(unsigned long arg)

    printk("I WAKE \\r\\n");
    //重启定时器
    mod_timer(&test_timer.timer, jiffies+msecs_to_jiffies(test_timer.time_period));


static int __init timer_init(void)
    int ret=0;
    test_timer.major=TIMER_MAJOR;
    test_timer.devid=MKDEV(test_timer.major,test_timer.minor);
    ret=register_chrdev_region(test_timer.devid,1,"timer");

    cdev_init(&test_timer.cdev,&timer_ops);
    test_timer.cdev.owner=THIS_MODULE;
    ret=cdev_add(&test_timer.cdev,test_timer.devid,1);
    if(ret)printk("CDEV ADD ERROR\\n");  
    

    test_timer.class=class_create(THIS_MODULE,"timer");
    test_timer.device=device_create(test_timer.class,NULL,test_timer.devid,NULL,"timer_0");

    init_timer(&test_timer.timer);

    test_timer.timer.function=timer_function;
    test_timer.timer.data=(unsigned long)&test_timer;

    test_timer.time_period=1000;


    return 0;

static void __exit timer_exit(void)

    del_timer(&test_timer.timer);

    cdev_del(&test_timer.cdev);
    unregister_chrdev_region(test_timer.devid,1);

    device_destroy(test_timer.class,test_timer.devid);
    class_destroy(test_timer.class);


module_init(timer_init);
module_exit(timer_exit);
MODULE_AUTHOR("YURI");
MODULE_LICENSE("GPL");

用户层程序

/*
 * @Copyright: 
 * @FileNames: 
 * @Description: 
 * @Author: yangyue19@hikvision.com.cn
 * @Date: 2022-07-29 13:44:24
 * @Version: V1.0
 * @LastEditTime: 2022-08-02 17:23:24
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#define DEVICE_NAME "/dev/timer_0"
#define CLOSE_CMD       0X00
#define OPEN_CMD        0X01
#define SER_PERIOD_CMD  0X02
char test_data[1024];
char buf[1024];
int main(int argc,void** argv)

    int i=0,ret=0,cmd=0;
    int fd=open(DEVICE_NAME,O_RDWR);
    if(fd<0)
        perror("open error");
        return -1;
    

    switch (atoi(argv[1]))
    
    case 0:
        cmd=CLOSE_CMD;
        break;
    case 1:
        cmd=OPEN_CMD;
        break;
    case 2:
        cmd=SER_PERIOD_CMD;
        break;
    default:
        break;
    
    ioctl(fd,cmd,atoi(argv[2]));

    close(fd);
    return 0;

以上是关于BSP开发学习4Linux 内核时间管理的主要内容,如果未能解决你的问题,请参考以下文章

BSP开发学习4Linux 内核时间管理

SylixOS BSP tick驱动流程

BSP开发学习3内核并发处理

BSP开发学习3内核并发处理

4Linux基础

踩准时钟节拍玩转时间转换,鸿蒙轻内核时间管理有妙招