内核定时器API实现点灯

1.内核定时器

定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。 Linux 内核定时器

采用系统时钟来实现,并不是6ull里面的硬件定时器。 Linux 内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,和我们使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下:

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;
    unsigned long expires;                 /* 定时器超时时间,单位是节拍数 */
    struct tvec_base *base;
 
    void (*function)(unsigned long);       /* 定时处理函数 */
    unsigned long data;                    /* 要传递给 function 函数的参数 */
 
    int slack;
 
#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

=========================

2.内核定时器API函数

要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器, tiemr_list 结构体的

expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时

器,那么这个定时器的超时时间就是 jiffies+msecs_to_jiffies(2000),function 就是定时器超时以后的定时处理函数,当定时时间到了以后,就会跳转到function执行。

定义好定时器后,还需要API函数(定义在linux/timer.h)来初始化定时器:

①、init_timer函数

init_timer 函数负责初始化 timer_list 类型变量,函数原型:

#define init_timer(timer)                        \
    __init_timer((timer), 0)
 
#define __init_timer(_timer, _flags)                    \
    init_timer_key((_timer), (_flags), NULL, NULL)
 
void init_timer_key(struct timer_list *timer, unsigned int flags,
            const char *name, struct lock_class_key *key)
    timer:要初始化的定时器。

②、add_timer函数

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

extern void add_timer(struct timer_list *timer);
timer:要初始化的定时器。

③、del_timer函数

用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出,函数原型:

extern int del_timer(struct timer_list * timer);
         timer:要初始化的定时器。
返回值:0,定时器没被激活,1,定时已经激活。

④、del_timer_sync函数

函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。函数原型:

extern int try_to_del_timer_sync(struct timer_list *timer);
timer:要初始化的定时器。

返回值:0,定时器没被激活,1,定时已经激活。

⑤、mod_timer函数

用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!函数原型如下:

extern int mod_timer(struct timer_list *timer, unsigned long expires);
timer:要修改超时时间的定时器。

expires:修改后的超时时间。

返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。

===============================

3.内核定时器的使用流程
struct timer_list timer; /* 定义定时器 */
 
/* 定时器回调函数 */
void function(unsigned long arg)
{
    /*
     * 定时器处理代码
     */
 
    /* 如果需要定时器周期性运行的话就使用 mod_timer
     * 函数重新设置超时值并且启动定时器。
     */
 
    mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));    /* 修改超时时间为2s */
}
 
/* 初始化函数 */
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);
}
4.Linux内核短延时函数

有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。 Linux 内核提供了毫秒、微秒和纳秒延时函数,如表:

函数描述

void ndelay(unsigned long nsecs)  
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
//  ms、us、ns延时函数

unlocked_ioctl和compat_ioctl

函数简介:

unlocked_ioctl\compat_ioctl是file_operation 结构体中的两个函数

unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。简单点来说,当用户空间应用程序调用 ioctl函数向驱动发送控制信息,驱动程序会执行unlocked_ioctl这个函数

long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);
        filep:设备文件名。
        cmd:应用程序发送过来的命令信息。后面我们会仔细说一下这个CMD命令如何创建。
        arg:应用程序发过来的参数。

compat_ioctl函数的功能与unlocked_ioctl函数一样,区别在于64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

long (*compat_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);

===========================

5.ioctl函数CMD命令

在linux内核中有帮助手册:linux/Documentation/ioctl/ioctl-decoding.txt这个文档中有介绍CMD这个命令:

#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \     //dir(读写)方向左移30位
     ((type) << _IOC_TYPESHIFT) | \    //type类型左移8位
     ((nr)   << _IOC_NRSHIFT) | \      //nr功能左移0位
     ((size) << _IOC_SIZESHIFT))      // size传递数据大小左移16位
 /**通过分析是dir、type、nr、size几个数都左移了一个不知道的宏的位数,通过查找发现这些宏如下,
 所以得到上边每行注释左移位数**/
#define _IOC_NRBITS 8                                                                                   
#define _IOC_TYPEBITS   8
# define _IOC_SIZEBITS  14
#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT+_IOC_NRBITS)  // 0+8 = 8                                                     
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT+_IOC_TYPEBITS) //8+8 = 16
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT+_IOC_SIZEBITS) //16+14 = 30

总结:我们通过分析_IOC这个宏可以发现他做了这么一件事,将一个32位的数拆成了四个部分,分别是dir、type、nr、size,分别如下解释和图示:

bit31~bit30:"区别读写" 区,作用是区分是读取命令还是写入命令;

bit29~bit16:"数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。

bit15~bit8 : " 魔数" (也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。

bit7~bit0 :"区别序号"区,是区分命令的命令顺序序号

这个CMD是一个32位的。31~30位是方向位,_IOR是向驱序读,_IOW是向驱动写。29~16位是用户空间向内核空间传输控制信息的数据大小,15~8位表示类型,驱动的标识位,一个特殊字符(ASCII)代表不同的一个驱动。7~0位就是不同的控制功能。

===========================

内核代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/errno.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <asm/ioctls.h>
 
 
/* 设备名称和个数 */
#define TIMER_CNT           1
#define TIMER_NAME          "timer"
 
  
/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)    
 
 
 
/* timer结构体 */
typedef struct timer_dev {
    dev_t devid;                        /* 设备号 */
    int major;                          /* 主设备号 */
    int minor;                          /* 次设备号 */
    struct cdev dev;                    /* 设备 */
    struct class *class;                /* 类 */
    struct device *device;              /* 类的设备 */
    struct device_node  *nd;             /* 设备树节点 */
    int led_gpio;                       /* LED的GPIO编号 */
    struct timer_list timer;            /* 定时器 */
 
    int timerperiod;                    /* 定时器周期 */
    spinlock_t lock;                    /* 自旋锁 */
 
}timer_dev;
timer_dev timer;
 
static int timer_open (struct inode *inode, struct file *filep)
{
 
    filep->private_data = &timer;       /* 设置私有数据 */
 
    timer.timerperiod = 500;           /* 设置定时时间为1s */
 
    return 0;
}
 
 
static long timer_unlocked_ioctlioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    timer_dev *dev = filep->private_data;    /* 获取私有数据 */
    unsigned int timerperod = 0;
    unsigned long flag = 0;
    unsigned long value = 0;
 
	switch (cmd) {
	    case OPEN_CMD:         /*  打开定时器 */
 
            spin_lock_irqsave(&dev->lock,flag);        /* 自锁 */
            timerperod = dev->timerperiod;
            spin_unlock_irqrestore(&dev->lock,flag);   /* 解锁 */
            mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperod));
            break;
	    case CLOSE_CMD:         /* 关闭定时器 */
            del_timer(&dev->timer);
            break;
        case SET_PERIOD_CMD:     /*  修改定时器的周期  */
            ret = copy_from_user(&value, (int *)arg, sizeof(int));
            if(ret < 0) {
                return -EFAULT;
            }
            spin_lock_irqsave(&dev->lock,flag);
            dev->timerperiod = value;
            spin_unlock_irqrestore(&dev->lock,flag);
            mod_timer(&dev->timer,jiffies + msecs_to_jiffies(value));
            break;
	}
    return 0;
 
}
 
 
 
/* 设备文件操作集合 */
const struct file_operations timer_opts = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlocked_ioctlioctl,
};
 
/* LED灯初始化 */
int led_init(timer_dev * ptimer)
{
    int ret = 0;
    
    timer_dev *dev = ptimer;
 
    /* 获取LED节点和信息 */
    dev->nd = of_find_node_by_path("/gpioled");
 
    /* 得到GPIO的编号 */
    dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpio", 0);
    if(dev->led_gpio < 0) {
        ret = -EINVAL;
        printk("fail get gpio\r\n");
        goto fail_getgpio;
    }
 
    /* 申请GPIO */
    ret = gpio_request(dev->led_gpio, "led_gpio");
    if(ret) {
        printk("fail gpio request\r\n");
        ret = -EBUSY;
        goto fail_request;
    }
 
    /* 设置GPIO输入输出 */
    ret = gpio_direction_output(dev->led_gpio, 1);           /* 输出模式 给1关灯,默认关灯 */
    if(ret){
        printk("fail gpio set output\r\n");
        ret = -EBUSY;
        goto fail_setout;
    }
 
    return 0;
fail_setout:
    gpio_free(dev->led_gpio);
fail_request:
fail_getgpio:
    return ret;
}
 
/* 定时器定时时间到回调函数 */
void  timer_timerout (unsigned long arg)
{
    static int status = 1;
    unsigned long flags;
    int timerperiod  = 0;
    timer_dev *dev = (timer_dev *)arg;
 
    /* 设置LED灯电平 */
    status = !status;
    gpio_set_value(dev->led_gpio,status);
 
    spin_lock_irqsave(&dev->lock,flags);                    /* 自锁 */
    timerperiod = dev->timerperiod;
    spin_unlock_irqrestore(&dev->lock, flags);               /* 解锁 */
 
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
}
 
 
/* 入口函数 */
static int __init timer_init(void)
{
    int ret = 0;
 
    /* 初始化自旋锁 */
    spin_lock_init(&timer.lock);
    timer.timerperiod = 500;
 
    /* 注册设备号 */
    timer.major = 0;
    if(timer.major) {       /* 指定设备号 */
        timer.devid = MKDEV(timer.major,0);
        ret = register_chrdev_region(timer.devid, TIMER_CNT, TIMER_NAME);
    }else {                 /* 没有指定设备号 */
        ret = alloc_chrdev_region(&timer.devid, 0, TIMER_CNT, TIMER_NAME);
        timer.major = MAJOR(timer.devid);
        timer.minor = MINOR(timer.devid);
    }
    if(ret < 0) {
        printk("fail devid\r\n");
        goto fail_devid;
    }
    printk("major = %d,minor = %d\r\n",timer.major,timer.minor);            /* 打印设备号 */
 
    /* 注册设备 */
    timer.dev.owner = THIS_MODULE;
    cdev_init(&timer.dev, &timer_opts);
    ret = cdev_add(&timer.dev, timer.devid, TIMER_CNT);
    if(ret < 0) {
        printk("fail dev\r\n");
        goto fail_dev;
    }
 
    /* 自动创建节点信息 */
    timer.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timer.class)) {
        ret = PTR_ERR(timer.class);
        printk("fail class\r\n");
        goto fail_class;
    }
    timer.device = device_create(timer.class, NULL, timer.devid, NULL, TIMER_NAME);
    if(IS_ERR(timer.device)) {
        ret = PTR_ERR(timer.device);
        printk("fail device\r\n");
        goto fail_device;
    }
 
    /* 初始化LED灯 */
    ret = led_init(&timer);
    if(ret < 0) {
        printk("fail led init\r\n");
        goto fail_led_init;
    }
 
    /* 初始化定时器 */
    init_timer(&timer.timer);
    timer.timer.data = (unsigned long) &timer;
    timer.timer.function = timer_timerout;
    mod_timer(&timer.timer, jiffies + msecs_to_jiffies(timer.timerperiod));
 
    return 0;
 
fail_led_init:
fail_device:
    class_destroy(timer.class);
fail_class:
    cdev_del(&timer.dev);
fail_dev:
    unregister_chrdev_region(timer.devid, TIMER_CNT);
fail_devid:
    return ret;
}
 
 
/* 出口函数 */
static void __exit timer_exit(void)
{
    /* 关灯 */
    gpio_set_value(timer.led_gpio, 1);
 
    /* 删除定时器 */
    del_timer_sync(&timer.timer);
 
    /* 注销GPIO */
    gpio_free(timer.led_gpio);
 
    /* 删除类的设备 */
    device_destroy(timer.class, timer.devid);
 
    /* 删除类 */
    class_destroy(timer.class);
 
    /* 删除设备 */
    cdev_del(&timer.dev);
 
    /* 删除设备号 */
    unregister_chrdev_region(timer.devid, TIMER_CNT);
    
    printk("timer exit\r\n");
}
 
 
/* 注册入口和出口函数 */
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");
上层代码编写

============

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
 
 
 
/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)  
 
/*
 *  main主程序 
 *  argc:argv数字个数,一般指传递给函数的参数数量
 *  argv:具体的参数内容,一般都是字符串格式
 *  return:0表示成功
 * 
*/
int main(int argc, char *argv[])
{
    int fd,ret;
    char *FileName;
    int cmd,arg;
    unsigned char str[100];
 
    /* 判断使用命令参数是否正确 */
    if(argc != 2){
        printf("命令使用错误!\r\n");
        ret = -1;
        goto fail_open;
    }
 
    /* 打开程序 */
    FileName = argv[1];
    fd = open(FileName,O_RDWR);
    if(fd < 0){
        printf("应用程序打开设备文件失败!\r\n");
        ret = fd;
        goto fail_open;
    }
 
    
    while(1) {
        printf("Please input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1) {
            gets(str);
        }
 
        if(cmd == 1) {
            ret = ioctl(fd, OPEN_CMD, &arg);
        } else if(cmd == 2) {
            ret = ioctl(fd, CLOSE_CMD, &arg);
        } else if(cmd == 3) {
            printf("Input period:");
            ret = scanf("%d",&arg);
            if(ret != 1) { 
                gets(str);
            }
            ret = ioctl(fd, SET_PERIOD_CMD, &arg);
        }
    }
 
    /* 关闭文件 */
    close(fd);
    return 0;
 
fail_open:
    return ret;
}
相关推荐
极客代码10 天前
【Linux】设备驱动中的ioctl详解
linux·内核·驱动·设备驱动·iocto
不知火猪1 个月前
最新雷蛇鼠标键盘驱动Razer Synapse 4(雷云) 下载与安装
计算机外设·驱动·雷云·雷蛇驱动
楼兰公子1 个月前
相机主要调试参数
arm开发·驱动·camera·v4l2
极客代码2 个月前
【Linux】【字符设备驱动】深入解析
linux·驱动开发·unix·驱动·字符设备驱动
沐多2 个月前
linux模拟HID USB设备及wireshark USB抓包配置
驱动
极客代码2 个月前
【Linux】内核驱动模块
linux·内核·内核模块·unix·驱动
郁大锤2 个月前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p2 个月前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板
小仇学长2 个月前
Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)
linux·驱动·spi·mcp2515
通俗_易懂3 个月前
44-RK3588s调试 camera-engine-rkaiq(rkaiq_3A_server)
人工智能·计算机视觉·rk3588·驱动·camera·imx415