struct timer_list
)不占用 CPU 资源(在等待期间):
定时器基于内核的 时间轮(timer wheel) 机制,由系统时钟中断(tick
)驱动。
在定时器到期前,CPU 可以执行其他任务,定时器只是挂载在队列中。
到期时通过 中断上下文 触发回调函数(如 timer_fun
),此时会短暂占用 CPU。
特点:
低精度:依赖 jiffies
(通常 1ms~10ms 精度)。
不阻塞进程:适合延迟执行非实时任务(如你的 LED 控制示例)。
ndelay
, udelay
)主动占用 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);