50 内核定时器

本文详细介绍了Linux内核中的定时器机制,包括定时器外设、节拍率、全局变量HZ和jiffies的使用。还探讨了内核定时器的结构、功能和操作函数如init_timer、add_timer、del_timer等。此外,讲解了如何在驱动程序中使用内核定时器,以及如何通过ioctl进行设备交互。最后,给出了一段简单的驱动源码和测试应用示例,展示了如何在设备驱动中实现定时器功能。
摘要由CSDN通过智能技术生成

一、一些概念

  • 定时器外设
    通过一个 硬件定时器 来提供 时钟源(频率可调) ,由此产生 周期性的定时中断,系统使用 定时中断 来计时
    中断产生的频率就是 系统频率 ,即 节拍率
    节拍率 是可以设置的(通过图形化界面设置),在内核源码目录下 .config 文件中
    在这里插入图片描述
    HZ 表示 系统节拍率HZ=100 表示 系统每秒产生中断100次

  • 节拍率 越高,时间精度 就越高,但是中断产生更加频繁,加重系统的负担

  • 全局变量 HZ
    表示一秒的节拍数,每秒产生的定时中断的次数

 #undef HZ
 #define HZ CONFIG_HZ
 #define USER_HZ 100
 #define CLOCKS_PER_SEC (USER_HZ)
  • 全局变量 jiffies
    include/linux/jiffies.h
    用于记录系统从启动以来的系统节拍数(一共产生了多少次定时中断)
    系统每一次启动时会将 jiffies 初始化为 0
    jiffies / HZ 就是 系统当前的运行时间,单位是秒
    jiffies 有 溢出 的风险,溢出后会从开始计数,此为 绕回。32位的jiffies 50天产生绕回,64位的jiffies 绕回需要 5.8 亿年
    板子是32位的系统,处理 jiffies 的绕回是十分必要的

  • 内核定时器
    1、软件定时器不同于硬件定时器那样直接给周期值。软件定时器设置的是期满以后的时间点(jiffies + 你需要定时的时间长短)
    2、定时处理函数(超时时间到了以后设置的定时处理函数就会执行)
    3、内核定时器不是周期性的,一次超时时间到了就会关闭,若希望实现周期定时,那么就要在定时处理函数中重新开启定时器
    4、内核中使用 timer_list 结构体 来表示 内核定时器成员 有超时值、超时处理函数、传递给超时处理函数的参数 等

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

二、kernel api

  • init_timer 函数
    init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下
void init_timer(struct timer_list *timer)
timer:要初始化的定时器结构体指针
  • add_timer 函数
    用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行
void add_timer(struct timer_list *timer)
timer:要注册的定时器结构体指针
  • del_timer 函数
    del_timer 函数用于删除一个定时器
    不管定时器有没有被激活,都可以使用此函数删除。
    在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出
int del_timer(struct timer_list * timer)
timer:要删除的定时器结构体指针
返回值: 0,定时器还没被激活; 1,定时器已经激活
  • del_timer_sync 函数
    del_timer 函数的 同步版,会等待其他处理器使用完定时器再删除,
    del_timer_sync 不能使用在中断上下文中
int del_timer_sync(struct timer_list *timer)
timer:要删除的定时器结构体指针。
返回值: 0,定时器还没被激活; 1,定时器已经激活。
  • mod_timer 函数
    mod_timer 函数用于修改定时值
    如果定时器还没有激活的话, mod_timer 函数会激活定时器
int mod_timer(struct timer_list *timer, unsigned long expires)
timer:要修改超时时间(定时值)的定时器结构体指针。
expires:修改后的超时时间。
返回值: 0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。
  • 内核定时器一般的使用流程如下所示:
 struct timer_list timer; /* 定义定时器 */
 
 /* 定时器回调函数 */
 void function(unsigned long arg)
 {
	/*
 	* 定时器处理代码
 	*/

 	/* 如果需要定时器周期性运行的话就使用 mod_timer
 	* 函数重新设置超时值并且启动定时器。
 	*/
 	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(2000));
 }

 /* 初始化函数 */
 void init(void)
 {
 	init_timer(&timer); /* 初始化定时器 */

 	timer.function = function; /* 设置定时处理函数 */
 	timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
 	timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */

 	add_timer(&timer); /* 启动定时器 */
 }

 /* 退出函数 */
 void exit(void)
 {
 	del_timer(&timer); /* 删除定时器 */
 	/* 或者使用 */
 	del_timer_sync(&timer);
 }
  • 几个 jiffies 和 ms、μs、ns之间的转换函数
函数描述
int jiffies_to_msecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的毫秒
int jiffies_to_usecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的微秒
u64 jiffies_to_nsecs(const unsigned long j)将 jiffies 类型的参数 j 分别转换为对应的纳秒
long msecs_to_jiffies(const unsigned int m)将毫秒转换为 jiffies 类型。
long usecs_to_jiffies(const unsigned int u)将微秒转换为 jiffies 类型。
unsigned long nsecs_to_jiffies(u64 n)将纳秒转换为 jiffies 类型。
  • linux kernel 短延时函数(用于内核和驱动中)
函数描述
void mdelay(unsigned long msecs)毫秒延时函数
void udelay(unsigned long usecs)微秒延时函数
void ndelay(unsigned long nsecs)纳秒延时函数
  • ioctl 函数详解
    在这里插入图片描述
    1、dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
    2、type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
    ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
    3、nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
    4、size涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

  • 应用程序里面调用 ioctl,对应到驱动结构体ops里面的

// struct file_operations 结构体,fs.h
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

//在 64 位系统上,32 位的应用程序调用将会使用函数  compat_ioctl。
//在 32 位的系统上运行 32 位的应用程序调用的是 unlocked_ioctl。
//ioctl的命令是自己定义的但要符合相关规则
// include/uapi/asm-generic/ioctl.h 
/* used to create numbers */
// type是幻数 8bit;nr是序数 8bit;数据传输方向 2bit;size是数据大小 14bit 
#define _IO(type,nr)		 	// 没有参数的命令
#define _IOR(type,nr,size)		// 该命令从驱动里面读数据
#define _IOW(type,nr,size)		// 该命令向驱动里面写数据
#define _IOWR(type,nr,size)		// 双向数据传输
#define _IOR_BAD(type,nr,size)	_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr)		(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)		(((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)		(((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)		(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

/* ...and for the drivers/sound files... */

#define IOC_IN		(_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT		(_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT	((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK	(_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT	(_IOC_SIZESHIFT)

三、驱动源码

  • timer驱动
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/of.h>
#include<linux/of_address.h>
#include<linux/of_irq.h>
#include<linux/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/jiffies.h>

#define TIMER_CNT 1
#define TIMER_NAME "timer"

#define CLOSE_CMD           _IO(0xef, 1)
#define OPEN_CMD            _IO(0xef, 2)
#define SETPERIOD_CMD       _IOW(0xef, 3, int) 

static long timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static int timer_open(struct inode *inode, struct file *filp);
static int timer_release(struct inode *inode, struct file *filp);

struct timer_dev
{
    dev_t devid;			//设备号
    u32 major; 				//主设备号
    u32 minor; 				//次设备号
    struct cdev cdev;		//用于注册字符设备 
    struct class *class; 	//用于自动创建设备节点
    struct device *device;	//用于自动创建设备节点
    struct device_node *nd;	//指向设备数节点
    int led_gpio;			//gpio标号
    struct timer_list timer;//表示一个定时器
    int timer_period;   	//定时器超时值
}; 

static const struct file_operations timerdev_fops = 
{
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_ioctl, // 成员名为 unlocked_ioctl
    .release =timer_release,
};

struct timer_dev timerdev; 

static int timer_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &timerdev;
    return 0;
}

static int timer_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static long timer_ioctl(struct file *file, unsigned int cmd,
						unsigned long arg)
{
    struct timer_dev *dev = file->private_data;
    int val = 0;
    int ret = 0;

	switch (cmd) {
	case CLOSE_CMD:
        printk("%s(%d)\n", __FILE__, __LINE__);
        // 删除一个定时器
		del_timer_sync(&dev->timer);
		break;

	case OPEN_CMD: 
		// 设置一个定时器的超时值,激活定时器
		del_timer(&dev->timer);
        init_timer(&dev->timer);
        dev->timer.function = timer_func;
        dev->timer.expires = jiffies + \
                              msecs_to_jiffies(dev->timer_period);
        dev->timer.data = (unsigned long)dev;
        add_timer(&dev->timer);
		break;

	case SETPERIOD_CMD: 
		// 从 app 向 驱动 设置定时周期
		// 第二个参数 arg 对应 app中 ioctl(fd, SETPERIOD_CMD, &arg) arg的地址
		ret = copy_from_user(&val, (void *)arg, sizeof(int));
        printk("%s(%d) val = %d arg = %d\n", __FILE__, __LINE__, val, *(int *)arg);
        if(ret < 0)
        {
            printk("%s(%d):fail\n", __FILE__, __LINE__);
            return -1;
        }
        dev->timer_period = val;
        // 设置定时器的超时值,激活定时器
        mod_timer(&dev->timer, jiffies+msecs_to_jiffies(dev->timer_period));
		break;
	default:
		ret = -ENOTTY;
		break;
	}

	return ret;
}

// 超时处理函数
static void timer_func(unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev* )arg;
    static u8 status = 1;
    status = !status ;
    gpio_set_value(dev->led_gpio, status);
    mod_timer(&dev->timer, jiffies+msecs_to_jiffies(dev->timer_period));
}

// 初始化led对应的gpio
int led_init(struct timer_dev *dev)
{
    int ret = 0;
    // 寻找设备树中的gpio对应节点
    dev->nd = of_find_node_by_path("/gpioled");
    if(dev->nd == NULL)
    {
        ret = -1;
        printk("%s(%d)\n", __FILE__, __LINE__);
        goto fail_fd;
    }
    // 获取gpio标号
    dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
    if(dev->led_gpio < 0)
    {
        ret = -1;
        printk("%s(%d)\n", __FILE__, __LINE__);
        goto fail_gpio;
    }
    //向内核申请gpio标号
    ret = gpio_request(dev->led_gpio, "led");
    if(ret)
    {
        ret = -1;
        printk("%s(%d)\n", __FILE__, __LINE__);
        goto fail_request;
    }
    // 设置gpio为输出
    ret = gpio_direction_output(dev->led_gpio, 1);
    if(ret < 0)
    {
        ret = -1;
        printk("%s(%d)\n", __FILE__, __LINE__);
        goto fail_setdir;
    }

    return 0;
fail_setdir:
	// gpio_request、使用完之后要gpio_free
    gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:
    return ret;
}


static int __init timer_init(void)
{
    int ret = 0;
	// 1、获取设备号
    timerdev.major = 0;
    if(timerdev.major)
    {	
    	// 自己指定一个主设备号,并向内核注册
        timerdev.devid = MKDEV(timerdev.major, 0);
        ret = register_chrdev_region(timerdev.devid, 1, TIMER_NAME);
    }
    else
    {
    	// 由内核分配一个可用的设备号
        ret = alloc_chrdev_region(&timerdev.devid, 0, 1, TIMER_NAME);
        timerdev.major = MAJOR(timerdev.devid);
        timerdev.minor = MINOR(timerdev.devid);
    }
    if(ret < 0)
    {
        printk("%d:Register timer dev error.\r\n", __LINE__);
        goto fail_devid;
    }

	// 2、初始化cdev结构体,并用它来向内核注册字符设备
    timerdev.cdev.owner = THIS_MODULE;
    cdev_init(&timerdev.cdev, &timerdev_fops);
    ret = cdev_add(&timerdev.cdev, timerdev.devid, 1);
    if(ret < 0)
    {
        goto fail_cdev;
    }

	// 3、创建 class、device结构体,用于自动创建设备节点
    timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timerdev.class))
    {
        ret = PTR_ERR(timerdev.class);
        goto fail_class;
    }

    timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);
    if(IS_ERR(timerdev.device))
    {
        ret = PTR_ERR(timerdev.device);
        goto fail_device;
    }

	// 4、初始化 led
    ret = led_init(&timerdev);
    if(ret < 0)
    {
        printk("%s(%d)\n", __FILE__, __LINE__);
        goto fail_ledinit;
    }
    
    // 5、初始化一个定时器相关 / 一个 struct timer_list 结构体
     timerdev.timer_period = 500; 
     init_timer(&timerdev.timer);
    // 设置周期
    // 将周期值转换成定时器的超时值
    timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timer_period);
    // 定时器的超时处理函数
    timerdev.timer.function = timer_func;
    /* 指定超时处理函数的参数 */
    timerdev.timer.data = (u32)&timerdev;
    add_timer(&timerdev.timer);
    printk("%d:---------------\r\n", __LINE__);

    return 0;
    
fail_ledinit:
	device_destroy(timerdev.class, timerdev.devid);
fail_device:
    class_destroy(timerdev.class);
fail_class:
    cdev_del(&timerdev.cdev);
fail_cdev:
    unregister_chrdev_region(timerdev.devid, 1);
fail_devid:
    printk("%d:---------------\r\n", __LINE__);
    return ret;
}

static void __exit timer_exit(void)
{
    gpio_set_value(timerdev.led_gpio, 1);
    del_timer(&timerdev.timer);
    gpio_free(timerdev.led_gpio);
	cdev_del(&timer.cdev);
	unregister_chrdev_region(timerdev.devid, TIMER_CNT);
	device_destroy(timerdev.class, timerdev.devid);
	class_destroy(timerdev.class);
}

module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
  • 测试APP

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/ioctl.h>

#define CLOSE_CMD           _IO(0xef, 1)
#define OPEN_CMD            _IO(0xef, 2)
#define SETPERIOD_CMD       _IOW(0xef, 3, int) // write period to driver

int main(int argc, char **argv)
{
    int ret = 0;
    int fd = 0;
    char *filename;
    unsigned char databuf[1];
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("Can't open file \"%s\"\r\n", filename);
        return -1;
    }
    
    while(1)
    {
        printf("Plz input cmd:");
        ret = scanf("%d", &cmd);
        getchar();
        if(1 == cmd)            //close
        {
            ioctl(fd, CLOSE_CMD, &arg);
            printf("%s(%d)\n", __FILE__, __LINE__);
        }
        else if(2 == cmd)       //open 
        {
            ioctl(fd, OPEN_CMD, &arg);
        }
        else if(3 == cmd)       //set period
        {
            printf("Plz input the period of timer:");
            ret = scanf("%d", &arg); 
            printf("%s(%d)arg = %d\n", __FILE__, __LINE__, arg);
            getchar();
            ioctl(fd, SETPERIOD_CMD, &arg); // 注意此处传入的是地址,驱动接收端是 unsigned long arg
        }
    }

    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值