1 Linux 时间管理和内核定时器简介
1.1 内核时间管理简介
Linux
内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱 动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(
有的资料也叫系统频率
)
,比如
1000Hz
,
100Hz
等等说的就是
系统节拍率
。系统节拍率是可以设置的,单位是 Hz
,我们在编译
Linux
内核的时候可以通过图形化界面设置系统节拍率。
Linux
内核使用全局变量
jiffies
来记录系统从启动以来的
系统节拍数
,
系统启动的时候会将 jiffies 初始化为 0
,
jiffies
定义在文件
include/linux/jiffies.h
中,定义如下:
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
HZ 表示每秒的节拍数,jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。
1.2 内核定时器简介
定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。
Linux
内核定时器 采用系统时钟来实现,并不是我们在裸机篇中讲解的 PIT
等硬件定时器。
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;
};
要使用内核定时器首先要先定义一个
timer_list
变量,表示定时器。
expires
成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为
2
秒的定时
器,那么这个定时器的超时时间就是
jiffies+(2*HZ)
,因此
expires=jiffies+(2*HZ)。
定义好定时器以后还需要通过一系列的 API 函数来初始化此定时器,这些函数如下:
内核定时器使用方法演示
function
就是定时器超时以后的定时处理函数,我们要做的工作就放到这个函数里面,需要我们编写这个定 时处理函数。
定义好定时器以后还需要通过一系列的 API 函数来初始化此定时器,这些函数如下:
void init_timer(struct timer_list *timer)
void add_timer(struct timer_list *timer)
int del_timer(struct timer_list * timer)
int del_timer_sync(struct timer_list *timer)
int mod_timer(struct timer_list *timer, unsigned long expires)
内核定时器使用方法演示
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, 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);
}
1.3 Linux
内核短延时函数
void ndelay(unsigned long nsecs)
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
2 硬件原理图分析
通过设置一个定时器来实现周期性的闪烁 LED 灯。
3 实验程序编写
3 实验程序编写
我们使用内核定时器周期性的点亮和熄灭开发板上的
LED
灯,
LED 灯的闪烁周 期由内核定时器来设置,测试应用程序可以控制内核定时器周期。
3.1 修改设备树文件
3.1 修改设备树文件
同《pinctrl 和 gpio 子系统实验-基于正点原子IMX6ULL开发板》4.1
3.2 定时器驱动程序编写
新建工程,在 timer.c 里面输入如下内容:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.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)) //设置周期
/*TIMER 设备结构体*/
struct timer_dev
{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
struct timer_list timer; /* 定义一个定时器 */
int timeperiod; /* 定时周期,单位为 ms */
int led_gpio;
};
struct timer_dev timerdev; /*TIMER */
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 *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int value = 0;
struct timer_dev *dev = filp->private_data;
switch (cmd)
{
case CLOSE_CMD: /* 关闭定时器 */
del_timer_sync(&dev->timer);
break;
case OPEN_CMD: /* 打开定时器 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
case SETPERIOD_CMD: /* 设置定时器周期 */
ret = copy_from_user(&value, (int *)arg, sizeof(int));
if (ret < 0)
{
return -EFAULT;
}
dev->timeperiod = value;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
break;
default:
break;
}
return ret;
}
/*操作集*/
static const struct file_operations timerdev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = timer_ioctl,
.open = timer_open,
.release = timer_release,
};
/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)arg;
static int sta = 1;
sta = !sta;
gpio_set_value(dev->led_gpio, sta);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod));
}
/*初始化LED灯*/
int led_init(struct timer_dev *dev)
{
int ret = 0;
dev->nd = of_find_node_by_path("/gpioled");
if (dev->nd == NULL)
{
ret = -EINVAL;
goto fail_fd;
}
dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);
if (dev->led_gpio < 0)
{
ret = -EINVAL;
goto fail_gpio;
}
/* 初始化 led 所使用的 IO */
ret = gpio_request(dev->led_gpio, "led");
if (ret)
{
ret = -EBUSY;
printk("IO %d can't request!\r\n", dev->led_gpio);
goto fail_request;
}
ret = gpio_direction_output(dev->led_gpio, 1); /*设置输出,默认关灯*/
if (ret < 0)
{
ret = -EINVAL;
goto fail_gpioset;
}
return 0;
fail_gpioset:
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, TIMER_CNT, TIMER_NAME);
}
else
{
ret = alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);
timerdev.major = MAJOR(timerdev.devid);
timerdev.minor = MINOR(timerdev.devid);
}
if (ret < 0)
{
goto fail_devid;
}
printk("timerdev major =%d, minor =%d \r\n", timerdev.major, timerdev.minor);
/*2,初始化cdev*/
timerdev.cdev.owner = THIS_MODULE;
cdev_init(&timerdev.cdev, &timerdev_fops);
/*3,添加cdev*/
ret = cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
if (ret)
{
goto fail_cdevadd;
}
/*4,创建类*/
timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
if (IS_ERR(timerdev.class))
{
ret = PTR_ERR(timerdev.class);
goto fail_class;
}
/*5,创建设备*/
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;
}
/*6,初始化LED灯*/
ret = led_init(&timerdev);
if (ret < 0)
{
goto fail_ledinit;
}
/*7,初始化定时器*/
init_timer(&timerdev.timer);
timerdev.timeperiod = 500;
timerdev.timer.function = timer_func;
timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timeperiod);
timerdev.timer.data = (unsigned long)&timerdev;
add_timer(&timerdev.timer); /*添加到系统*/
return 0;
fail_ledinit:
fail_device:
class_destroy(timerdev.class);
fail_class:
cdev_del(&timerdev.cdev);
fail_cdevadd:
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
fail_devid:
return ret;
}
/*驱动出口函数*/
static void __exit timer_exit(void)
{
/*关灯*/
gpio_set_value(timerdev.led_gpio, 1);
/*删除定时器*/
del_timer(&timerdev.timer);
/*注销字符设备驱动*/
cdev_del(&timerdev.cdev);
unregister_chrdev_region(timerdev.devid, TIMER_CNT);
device_destroy(timerdev.class, timerdev.devid);
class_destroy(timerdev.class);
/*释放IO*/
gpio_free(timerdev.led_gpio);
}
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");
3.3
编写测试
APP
测试 APP 我们要实现的内容如下:
①、运行
APP
以后提示我们输入要测试的命令,输入
1
表示关闭定时器、输入
2
表示打开定时器,输入 3
设置定时器周期。
②、如果要设置定时器周期的话,需要让用户输入要设置的周期值,单位为毫秒。新建名为 timerAPP.c
的文件,然后输入如下所示内容:
#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>
/*
*argc:应用程序参数个数
* argv[]:具体的参数内容,字符串形式
* ./timerAPP <filename> <0:1> 0 关灯,1 开灯
* ./timerAPP /dev/timer
*/
#define CLOSE_CMD _IO(0xEF, 1) //关闭命令
#define OPEN_CMD _IO(0xEF, 2) //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
unsigned char cnt;
unsigned int cmd;
unsigned int arg;
unsigned char str[100];
if (argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("file %s open failed!\r\n", filename);
return -1;
}
/*循环读取*/
while (1)
{
printf("Input CMD:");
ret = scanf("%d", &cmd);
if (ret != 1)
{
gets(str); /*防止卡死*/
}
if (cmd == 1) /*关闭*/
{
ioctl(fd,CLOSE_CMD,&arg);
}
else if (cmd == 2)/*打开*/
{
ioctl(fd,OPEN_CMD,&arg);
}
else if (cmd == 3)/*设置周期*/
{
printf("Input Timer period:");
ret = scanf("%d",&arg);
if(ret !=1){
gets(str); /*防止卡死*/
}
ioctl(fd,SETPERIOD_CMD,&arg);
}
}
close(fd);
return 0;
}
4
运行测试
4.1 编译驱动程序和测试 APP
1、编译驱动程序
2 、编译测试 APP
4.2 运行测试
4.1 编译驱动程序和测试 APP
1、编译驱动程序
2 、编译测试 APP
4.2 运行测试
将编译出来的
timer.ko
和
timerAPP
这两个文件拷贝到
rootfs/lib/modules/4.1.15
目录中,重启开发板,进入到目录 lib/modules/4.1.15
中,输入如下命令加载
timer.ko
驱动模块:
驱动加载成功以后如下命令来测试:
led灯按照设置的定时器周期进行闪烁。
led灯按照设置的定时器周期进行闪烁。
卸载驱动

详细内容参考正点原子《I.MX6U 嵌入式 Linux 驱动开发指南 V1.6》第50章。
补充:ioctl 用户与驱动之间的协议
ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
- dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据。
- type(device type),是个0-0xff的数或者一个字符,占8bit。这个数是用来区分不同的驱动的,像设备号一样,内核有一个文档(Documentation/ioctl/ioctl-number.txt)给出一些推荐的或者已经被使用的幻数。
- nr(number),命令编号/序数,8 bit,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增。
- size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,只需要填参数的类型,如int,函数就会帮你检测类型的正确性然后赋值sizeof(int)。
为了方便我们会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令,函数原型如下所示:
#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)))
_IO: 定义不带参数的 ioctl 命令
_IOW: 定义带写参数的 ioctl 命令(copy_from_user)
_IOR: 定义带读参数的ioctl命令(copy_to_user)
_IOWR: 定义带读写参数的 ioctl 命令
本文介绍了Linux内核中的时间管理和内核定时器的使用,包括全局变量jiffies、HZ的概念以及内核定时器的结构体timer_list。详细阐述了内核定时器的初始化、添加、删除和修改超时时间的API,并给出了内核定时器的示例代码。此外,还提到了Linux内核的短延时函数ndelay、udelay和mdelay。最后,展示了如何通过内核驱动控制LED灯周期性闪烁的代码,并提供了用户空间测试应用的编写方法。

3369

被折叠的 条评论
为什么被折叠?



