一、时钟中断
硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1
jiffies_64 是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies
HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200
二、延时机制
-
短延迟:忙等待
1. void ndelay(unsigned long nsecs) 2. void udelay(unsigned long usecs) 3. void mdelay(unsigned long msecs)
-
长延迟:忙等待
使用jiffies比较宏来实现
time_after(a,b) //a > b time_before(a,b) //a < b //返回值为布尔类型。例如 time_after(a,b) 表示时间戳 a 是否晚于时间戳 b //延迟100个jiffies unsigned long delay = jiffies + 100; while(time_before(jiffies,delay)) { ; } //延迟2s unsigned long delay = jiffies + 2*HZ; while(time_before(jiffies,delay)) { ; }
-
睡眠延迟----阻塞类
void msleep(unsigned int msecs); //深度睡眠 unsigned long msleep_interruptible(unsigned int msecs); //浅度睡眠
延时机制的选择原则:
-
异常上下文中只能采用忙等待类
-
任务上下文短延迟采用忙等待类,长延迟采用阻塞类
三、定时器
(1)定义定时器结构体
struct timer_list
{
struct list_head entry; //链表管理很多个定时器,每个定时器都有超时值
unsigned long expires; // 期望的时间值 jiffies + x * HZ
void (*function)(unsigned long); // 时间到达后,执行的回调函数,软中断异常上下文
unsigned long data; //传给回调函数的实参,常转换为地址来使用
};
其中list_head是链表,内核中通过链表管理很多个定时器,每个定时器都有一个超时值。硬件时钟每经过一个时间,都会进行一个计数,同时触发一个软中断,在软中断服务程序中(异常上下文,不能调用任何可能引起阻塞的函数),会进来查看定时器是否超时了,通过现在的tick值与超时tick值进行比较,如果超时了会触发回调函数,其中data用来给回调函数传实参。
(2)初始化定时器
init_timer(struct timer_list *)
(3)增加定时器 ------ 定时器开始计时
void add_timer(struct timer_list *timer);
加入到链表,定时器才开始工作
(4)删除定时器 -------定时器停止工作
int del_timer(struct timer_list * timer);
(5)修改定时器
一般如果想实现每隔x秒钟设置一个闹钟,需要把这个函数放在回调函数的最后。
int mod_timer(struct timer_list *timer, unsigned long expires);
模板
定义struct timer_list tl类型的变量
init_timer(...);//模块入口函数
//模块入口函数或open或希望定时器开始工作的地方
tl.expires = jiffies + n * HZ //n秒
tl.function = xxx_func;
tl.data = ...;
add_timer(....);
//不想让定时器继续工作时
del_timer(....);
void xxx_func(unsigned long arg)
{
......
mod_timer(....);//如需要定时器继续隔指定时间再次调用本函数
}
四、课堂练习—秒设备
目标:在应用程序测试读取字符设备时打印秒计数值
second.c,中断定时器不能被多个应用打开,所以使用原子来防止被重复打开
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <asm/uaccess.h> #include <asm/atomic.h> int major = 11; int minor = 0; int mysecond_num = 1; struct mysecond_dev { struct cdev mydev; atomic_t openflag;//1 can open, 0 can not open struct timer_list timer; int second; }; struct mysecond_dev gmydev; void timer_func(unsigned long arg) { struct mysecond_dev * pmydev = (struct mysecond_dev *)arg; pmydev->second++; mod_timer(&pmydev->timer, jiffies + 1 * HZ); } int mysecond_open(struct inode *pnode,struct file *pfile) { struct mysecond_dev *pmydev = NULL; pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mysecond_dev,mydev)); pmydev = (struct mysecond_dev *)pfile->private_data; //运算后结果为0则返回真,否则返回假表示设备已经被打开 if(atomic_dec_and_test(&pmydev->openflag)) { pmydev->timer.expires = jiffies + 1 * HZ; pmydev->timer.function = timer_func; pmydev->timer.data = (unsigned long)pmydev; add_timer(&pmydev->timer); return 0; } else { atomic_inc(&pmydev->openflag); printk("The device is opened already\n"); return -1; } } int mysecond_close(struct inode *pnode,struct file *pfile) { struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data; del_timer(&pmydev->timer); atomic_set(&pmydev->openflag,1); return 0; } int mysecond_read(struct file *pfile,char __user *puser, size_t size, loff_t *p_pos) { struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data; int ret = 0; if(size < sizeof(int)) { printk("the expect read size is invalid\n"); return -1; } if(size >= sizeof(int)) { size= sizeof(int); } ret = copy_to_user(puser, &pmydev->second, size); if(ret) { printk("copy to user failed\n"); return -1; } return 0; } struct file_operations myops = { .owner = THIS_MODULE, .open = mysecond_open, .release = mysecond_close, .read = mysecond_read, }; int __init mysecond_init(void) { int ret = 0; dev_t devno = MKDEV(major,minor); /*申请设备号*/ ret = register_chrdev_region(devno,mysecond_num,"mysecond"); if(ret) { ret = alloc_chrdev_region(&devno,minor,mysecond_num,"mysecond"); if(ret) { printk("get devno failed\n"); return -1; } major = MAJOR(devno);//容易遗漏,注意 } /*给struct cdev对象指定操作函数集*/ cdev_init(&gmydev.mydev,&myops); /*将struct cdev对象添加到内核对应的数据结构里*/ gmydev.mydev.owner = THIS_MODULE; cdev_add(&gmydev.mydev,devno,mysecond_num); //初始化置位1 atomic_set(&gmydev.openflag,1); //初始化定时器 init_timer(&gmydev.timer); ; return 0; } void __exit mysecond_exit(void) { dev_t devno = MKDEV(major,minor); cdev_del(&gmydev.mydev); unregister_chrdev_region(devno,mysecond_num); } MODULE_LICENSE("GPL"); module_init(mysecond_init); module_exit(mysecond_exit);
Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
endif
testsecond_app.c
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int fd = -1;
int sec;
if(argc < 2)
{
printf("The argument is too few\n");
return -1;
}
//打开字符驱动时,即会启动定时器
fd = open(argv[1],O_RDONLY);
if(fd < 0)
{
printf("open %s failed \n", argv[1]);
return 2;
}
//延迟
sleep(3);
read(fd, &sec, sizeof(sec));
printf("The second is %d\n",sec);
close(fd);
fd = -1;
return 0;
}
编译测试效果