Linux 内核定时器采用系统时钟来实现,使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统在启动的时候会将 jiffies 初始化为 0。Linux内核定时器的运行没有周期性,到达计时终点后会自动关闭。所以要想实现周期性定时,就要在定时处理函数中重新开启定时器。并且Linux提供多种函数使得秒,毫秒等时间与系统节拍数相互转换。
此次实验将利用Linux内核定时器来实现具有重置功能的秒计时器。由于此次实验不涉及设备树配置和具体的硬件操作,因此在Ubuntu虚拟机上即可进行验证,具体内容可以参考:没有开发板如何在Ubuntu中体验Linux驱动开发(无需下载和编译Linux内核)
第一步,驱动程序编写
在字符设备驱动的框架下,追加定时器的初始化及注册等操作,实现秒计时并通过使用原子变量来记录定时的秒数,以防出现竞争。并在此基础上添加信号量,互斥锁等操作进行共享资源的保护。
<头文件包含>
struct mutex mutex_test;
struct device_test{
dev_t dev_num;
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test;
struct class *class;
struct device *device; //设备
int sec; //计时的秒数
};
atomic64_t v = ATOMIC_INIT(0); //定义并初始化原子类型变量 v
static struct device_test dev1;
static void function_test(struct timer_list *t);//定时器功能函数
DEFINE_TIMER(timer_test,function_test);
static void function_test(struct timer_list *t)
{
atomic64_inc(&v);
dev1.sec = atomic64_read(&v);
//printk("the sec is %d\n",dev1.sec);
mod_timer(&timer_test,jiffies_64 + msecs_to_jiffies(1000));//重新开启定时器
}
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;
dev1.sec = 0;
atomic64_set(&v,0);
add_timer(&timer_test);
printk("timer add successed\n");
mutex_lock(&mutex_test);
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
if(copy_to_user(buf,&dev1.sec,sizeof(dev1.sec))){
printk("copy_to_user error \n");
return -1;
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
del_timer(&timer_test);
mutex_unlock(&mutex_test);
return 0;
}
/*文件操作集*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE,
.open = cdev_test_open,
.read = cdev_test_read,
.release = cdev_test_release,
};
static int __init timer_dev_init(void)
{
int ret;
mutex_init(&mutex_test); //初始化互斥锁
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("major is %d \r\n", dev1.major);
printk("minor is %d \r\n", dev1.minor);
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
return 0;
/*错误处理*/
err_device_create:
class_destroy(dev1.class);
err_class_create:
cdev_del(&dev1.cdev_test);
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1);
err_chrdev:
return ret;
}
static void __exit timer_dev_exit(void) //驱动出口函数
{
unregister_chrdev_region(dev1.dev_num, 1);
cdev_del(&dev1.cdev_test);
device_destroy(dev1.class, dev1.dev_num);
class_destroy(dev1.class);
printk("closed byebye\n");
}
module_init(timer_dev_init);
module_exit(timer_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("tester");
Makefile使用之前的Makefile即可,注意更改文件名!
第二步,测试程序编写
测试程序主要实现从0开始循环打印从内核空间传来的定时的秒数。
<头文件包含>
int main(int argc,char *argv[])
{
int fd; //定义文件描述符
int count; //定义接收秒数的变量
fd = open("/dev/test",O_RDWR);
while(1)
{
read(fd,&count,sizeof(count));
sleep(1);
printf("num is %d\n",count);
}
close(fd);
return 0;
}
第三步,运行测试
编译驱动程序及测试程序,得到驱动模块和可执行测试文件。
加载驱动并成功打印出了主设备号和次设备号。
运行测试程序,可以看到定时器成功添加的打印信息以及从0开始每隔一秒打印的定时秒数。
新开一个终端,同样运行该测试程序,内核会抛出段错误的警告,说明互斥锁的存在下,此次资源的访问是非法的。
总结:使用Linux内核定时器实现了一个可重置的秒定时器,并通过原子变量进行定时器定时计数值的保存以及使用互斥锁进行资源的保护,成功验证了Linux下的共享内存保护机制。