基于Linux内核时钟的简单闹钟应用
目录
- 技术路线
- 应用程序
- 模块设计
- 总体功能以及设计
- 流程图
- 代码
- Makefile
- timer.c
- alarm.c
- 编译过程及编译结果
- 清理工程
- 编译工程
- 运行或测试结果
- 导入模块
- 调用应用程序
- 参考文章
技术路线
在本实例中,我们采用应用程序以及内核模块的结合以达到使用动态定时器定时的目的。
其中使用了应用程序中调用了两个系统调用函数,而系统调用函数使用模块的方式进行导入。下面分别对应用程序和内核模块功能进行介绍:
应用程序
应用程序部分,首先接受来自用户输入的参数,并且对命令进行检测,如果有误,对用户进行提醒,如果正确,将调用第一个系统调用函数,将用户输入的定时时间传入内核函数中,进行定时器的定时,并且在后面通过轮询去检测定时完成标志,其中检测的函数也是一个系统调用函数,当定时完成标志被置一后,调用声音驱动函数,发出闹铃的响声并提示用户。
模块设计
模块设计部分,主要是方便应用程序对内核的动态定时器进行管理。其中在导入模块的时候,为了修改内存中的表项,首先修改寄存器的保护位,然后修改映射在内存中的系统调用表,把空闲的系统调用表项指向自己写的模块函数,并且保留原始的系统调用表,方便卸载模块的时候恢复系统调用表。模块中包含两个系统调用函数,其中一个接收来自应用程序中用户输入的参数,根据此参数设置定时器的基本定时,并在定时器的回调函数中打印倒计时,当到达定时时间的时候将计时完成标志置位。而第二个系统调用函数则是向应用程序返回计时完成标志,以达到通知应用程序的目的。
总体功能以及设计
获取到命令行中用户输入的定时参数,要能够对参数进行检查以及对输错的参数进行提示等,引导用户了解命令的使用方法。在正确得到用户输入后,解析命令,计时开始,并将用户设定计时信息输出在命令行中,方便用户进行确认。在系统内核日志中打印倒计时,在计时完成后,响起铃声,通知用户,并在命令行中输出完成信息。
流程图
代码
Makefile
1. #obj-m表示编译生成可加载模块
2. obj-m:=timer.o
3. #需要编译的模块源文件地址
4. PWD:= $(shell pwd)
5. CC:= gcc
6. #指定内核源码的位置
7. KERNELDIR:= /lib/modules/$(shell uname -r)/build
8. #关闭gcc优化选项,避免插入模块出错
9. EXTRA_CFLAGS= -O0
10.
11. all:
12. make -C $(KERNELDIR) M=$(PWD) modules
13. $(CC) alarm.c -o alarm
14. clean:
15. make -C $(KERNELDIR) M=$(PWD) clean
16. rm -rf alarm
17. sudo modprobe pcspkr
timer.c
1. #include <linux/kernel.h>
2. #include <linux/init.h>
3. #include <linux/unistd.h>
4. #include <linux/time.h>
5. #include <asm/uaccess.h>
6. #include <linux/sched.h>
7. #include <linux/kallsyms.h>
8. #include <linux/module.h>
9. #include <linux/timer.h>
10. #include <linux/jiffies.h>
11. /* 系统调用号 */
12. #define __NR_syscall 335
13. #define __NR_pausecall 336
14. /*struct timer*/
15. struct timer_list mytimer;
16. unsigned long * sys_call_table;
17. unsigned int clear_and_return_cr0(void);
18. void setback_cr0(unsigned int val);
19. static void B_timer(unsigned long arg);
20. static int myfunc(int timecnt);
21. /* 用来存储cr0寄存器原来的值 */
22. int orig_cr0;
23. /*定义一个函数指针,用来保存一个系统调用*/
24. unsigned long *sys_call_table = 0;
25. /*timeok*/
26. unsigned char timeok = 0;
27. static int (*anything_saved)(void);/*定义一个函数指针,用来保存一个系统调用*/
28. unsigned int n_timecnt;
29. //为了修改内存中的表项,还要修改寄存器的保护位
30. /*
31. * 设置cr0寄存器的第17位为0
32. */
33. unsigned int clear_and_return_cr0(void)
34. {
35. unsigned int cr0 = 0;
36. unsigned int ret;
37. /* 前者用在32位系统。后者用在64位系统,本系统64位 */
38. //asm volatile ("movl %%cr0, %%eax" : "=a"(cr0));
39. /* 将cr0寄存器的值移动到rax寄存器中,同时输出到cr0变量中 */
40. asm volatile ("movq %%cr0, %%rax" : "=a"(cr0));
41. ret = cr0;
42. /* 将cr0寄存器的值移动到rax寄存器中,同时输出到cr0变量中 */
43. cr0 &= 0xfffeffff;
44. //asm volatile ("movl %%eax, %%cr0" :: "a"(cr0));
45. /* 读取cr0的值到rax寄存器,再将rax寄存器的值放入cr0中 */
46. asm volatile ("movq %%rax, %%cr0" :: "a"(cr0));
47. return ret;
48. }
49.
50. /* 读取val的值到rax寄存器,再将rax寄存器的值放入cr0中 */
51. void setback_cr0(unsigned int val)
52. {
53. //asm volatile ("movl %%eax, %%cr0" :: "a"(val));
54. asm volatile ("movq %%rax, %%cr0" :: "a"(val));
55. }
56. //一秒到了后,调用的函数,进行定时参数的检查,若到达定时器指定时间
57. //将定时完成标志位置1
58.
59. static void B_timer(unsigned long arg)
60. {
61. if(n_timecnt > 0)
62. {
63. printk("time cnt is %d\n",n_timecnt);
64. n_timecnt = n_timecnt - 1;
65. mod_timer(&mytimer, jiffies + HZ);
66. }
67. else
68. {
69. printk("It is on time\n");
70. timeok = 1;
71. }
72. }
73.
74. static int myfunc(int timecnt)
75. {
76. /****************time set**********************/
77. //初始化内核定时器
78. timeok = 0;
79. init_timer(&mytimer);
80. //指定定时时间到后的回调函数
81. mytimer.function = B_timer;
82. add_timer(&mytimer);
83. //基本定时为1秒
84. mod_timer(&mytimer, jiffies + HZ);
85. n_timecnt = timecnt;
86. printk("Time start!!!\n");
87. return timecnt;
88. }
89. //系统调用函数,获取定时器状态信息
90. static char Askfunc(void)
91. {
92. return timeok;
93. }
94.
95. static void Init_syscall(void)
96. {
97. //修改映射在内存中的系统调用表,把空闲的系统调用表项指向自己写的模块函数
98. /* 获取系统调用服务首地址 */
99. sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");
100. //printk("sys_call_table: 0x%p\n", sys_call_table);
101. /* 保存原始系统调用 */
102. anything_saved = (int(*)(void))(sys_call_table[__NR_syscall]);
103. /* 设置cr0可更改 */
104. orig_cr0 = clear_and_return_cr0();
105. /* 更改原始的系统调用服务地址 */
106. sys_call_table[__NR_syscall] = (unsigned long)&myfunc;
107. sys_call_table[__NR_pausecall] = (unsigned long)&Askfunc;
108. /* 设置为原始的只读cr0 */
109. setback_cr0(orig_cr0);
110. }
111. //导入模块的时候调用的函数
112. static int __init mytimer_init(void)
113. {
114. printk("Insmod Ok !\n");
115. Init_syscall();
116. return 0;
117. }
118. //卸载模块的时候调用的函数
119. static void __exit mytimer_exit(void)
120. {
121. del_timer(&mytimer);
122. /* 设置cr0中对sys_call_table的更改权限 */
123. orig_cr0 = clear_and_return_cr0();
124. /* 设置cr0可更改 */
125. sys_call_table[__NR_syscall] = (unsigned long)anything_saved;
126. /* 恢复原有的中断向量表中的函数指针的值 */
127. setback_cr0(orig_cr0);
128. /* 恢复原有的cr0的值 */
129. printk("rmmod OK !\n");
130. }
131. //module function
132. module_init(mytimer_init);
133. module_exit(mytimer_exit);
134. //
135.
136. //相关信息
137. MODULE_LICENSE("GPL");
138. MODULE_AUTHOR("willpower");
139. MODULE_DESCRIPTION("Demo for timer");
alarm.c
1. #include <syscall.h>
2. #include <stdio.h>
3. #include <linux/unistd.h>
4. #include <stdlib.h>
5. #include <fcntl.h>
6. #include <string.h>
7. #include <unistd.h>
8. #include <sys/ioctl.h>
9. #include <sys/types.h>
10. #include <linux/kd.h>
11. /*input alarm -t 30 or pause like alarm -h (like 30 )*/
12. int timecnt,pausetime;
13. //sounds
14. void Buzzer(void);
15. //命令解析函数
16. char parse_command_line(char **argv, int *timecnt)
17. {
18. char *arg0 = *(argv++);
19. while ( *argv )
20. {
21. //检查参数是否含有-t
22. if ( !strcmp( *argv,"-t" ))
23. { /*time*/
24. //将输入的数字字符串转换为整形
25. int time = atoi ( *( ++argv ) );
26. //检查输入定时器参数是否在正确范围内
27. if ( ( time <= 0 ) || ( time > 65535 ) )
28. {
29. //输出错误提示
30. fprintf ( stderr, "Bad parameter: time must be from 1..65535\n" );
31. exit (1) ;
32. }
33. else
34. {
35. *timecnt = time;
36. argv++;
37. }
38. return 1;
39. }
40. else
41. {
42. //command error output help
43. fprintf(stderr, "Bad parameter: %s\n", *argv);
44. printf("Please enter again!\n");
45. printf("You can use -t for timecnt");
46. exit(1);
47. }
48. }
49. }
50. int main(int argc, char **argv)
51. {
52. //parse_conmand
53. if(parse_command_line(argv, &timecnt) == 1)
54. {
55. printf("timecnt is %d\n", timecnt);
56. //系统调用,传入定时事件timecnt
57. timeset(timecnt);
58. while(1)
59. {
60. //对定时完成标志做检查
61. if(timeget() == 1)
62. {
63. //定时事件到达,响起铃声
64. Buzzer();
65. break;
66. }
67. }
68. //输出完成调用提示
69. printf("timecnt action is successful!\n");
70. }
71. else
72. {
73. printf("Error!");
74. }
75. return 0;
76. }
77. void Buzzer(void)
78. {
79. int console_fd;
80. int i;
81. //open dev
82. if ( ( console_fd = open ( "/dev/console", O_WRONLY ) ) == -1 )
83. {
84. //Error
85. fprintf(stderr, "Failed to open console.\n");
86. perror("open");
87. exit(1);
88. }
89. for(i = 0; i < 20; i++)
90. {
91. int magical_fairy_number = 1190000/3000;
92. /* 开始发声 */
93. ioctl(console_fd, KIOCSOUND, magical_fairy_number);
94. /*等待... */
95. usleep(1000*100);
96. /* 停止发声*/
97. ioctl(console_fd, KIOCSOUND, 0);
98. /* 等待... */
99. usleep(1000*100);
100. /* 重复播放*/
101. }
102. }
编译过程及编译结果
清理工程
可以看到只有alarm.c、timer.c以及Makefile文件,其中~结尾的是备份文件。
编译工程
可以看到生成了可执行文件alarm,以及可导入模块timer.ko文件
运行或测试结果
导入模块
可以看到模块被成功导入
调用应用程序
其中调用的格式为alarm -t cnt 使用-t参数设置定时的时间。
成功调用后,会在系统内核日志中打印倒计时,方便用户进行检查。
到时间后,会响起铃声提醒用户进行查看。
应用程序会对输入参数做检查
参考文章
[1] Linux下的声音编程方法
[2] zusi_csdn. Linux驱动开发1-内核入门之hello模块
[3] cbl709. linux 内核定时器编程
[4] Shell命令控制蜂鸣器发声
[5] Kunaly. Linux内核定时器timer_list的使用
[6] Linux内核定时器
基于Linux内核时钟的简单闹钟应用相关推荐
- 孙玄辜教授:基于Linux内核的时间轮算法设计实现【附代码】
文章目录 1.时间轮算法基本思想 2.定时器的添加 3.定时器到期处理 孙玄:毕业于浙江大学,现任转转公司首席架构师,技术委员会主席,大中后台技术负责人(交易平台.基础服务.智能客服.基础架构.智能运 ...
- Linux内核时钟系统和定时器实现
1. Linux内核时钟系统和定时器实现 Linux 2.6.16之前,内核只支持低精度时钟,内核定时器的工作方式: 系统启动后,会读取时钟源设备(RTC, HPET,PIT-),初始化当前系统时间: ...
- YOCTO项目介绍:通过提供模版、工具和方法帮助开发者创建基于linux内核的定制系统
目录 YOCTO项目介绍 配置内核 build配套 Yocto ,是一个开源社区.它通过提供模版.工具和方法帮助开发者创建基于linux内核的定制系统,支持ARM, PPC, MIPS, x86 (3 ...
- linux流量控制的基本原理,基于Linux内核的BT流量控制的原理与实现.pdf
基于Linux内核的BT流量控制的原理与实现 黄山学院学报 2010 年 第 卷第 期 黄山学院学报 Vo1.12,NO.3 12 3 年 月 Journal of Huangshan Univers ...
- linux内核下网络驱动流程,基于Linux内核驱动的网络带宽测速方法与流程
本发明涉及一种测速方法,尤其是一种网络带宽测速方法. 背景技术: :电信运营商为客户提供一定带宽的Internet接入:为了检验带宽是否达标,一般均由客户使用个人电脑在网页上直接测速.但是随着智能网关 ...
- Linux 驱动开发 三十五:Linux 内核时钟管理
参考: linux时间管理,时钟中断,系统节拍_u010936265的博客-CSDN博客_系统节拍时钟中断 Linux内核时钟系统和定时器实现_anonymalias的专栏-CSDN博客_linux内 ...
- Mac强大Git客户端Tower 基于Linux 内核开发的Git客户端
你是否需要一款简单易用的Git客户端呢?快来试试Tower for Mac吧!Tower mac版是运行在mac os系统上的基于Linux 内核开发的Git客户端.Tower可以让Git更简单高效地 ...
- 直播回顾:如何基于Linux内核构建起商用密码基础设施?| 龙蜥技术
编者按:本文整理自龙蜥大讲堂技术解读,分享主题为<构建商用密码操作系统>,直播视频回放已上线至龙蜥社区官网(文末阅读原文直接跳转):首页-支持-视频,欢迎观看. 作者张天佳,来⾃阿⾥云操作 ...
- [arm驱动]linux内核时钟
<[arm驱动]linux内核时钟>涉及内核驱动函数四个,内核结构体一个,分析了内核驱动函数一个:可参考的相关应用程序模板或内核驱动模板一个,可参考的相关应用程序模板或内核驱动一个 一.内 ...
最新文章
- AutoScaling 弹性伸缩附加与分离RDS实例
- 发个招聘的信息来激励自己
- Github上删除fork的仓库
- Linux搭建PHP环境(LAMP)
- C#中文件及文件夾的遍历
- Spring(1)-IOC
- 斯坦福2019秋季课程:图机器学习资料全公开
- 一步步实现SDDC-Edge负载均衡
- Vue登录注册,并保持登录状态 1
- jQuery 第八课 —— 数据交互的升级
- 三维点云数据处理软件-图像重建点云或LiDAR扫描点云
- 【3dmax千千问】食住玩初学3dmax插件神器第24课:3dmax自学渲染效果图教程|疯狂模渲大师、室内设计师、效果图绘图员都应该如何认识VRAY或扫描线CORONA渲染器及其VR核心算法的作用?
- 如何导出mysql数据库
- E盾网络验证企业版个人版离线版防破解加密易语言源码加密对接好的自绘界面5
- 小程序图片裁剪组件基于image-cropper(修改版)
- 任天堂如何通过旧技术赢得胜利
- CATIA二次开发过程中几个问题
- fig-tlo_PHP-FIG的替代方案:各种愿景的利弊
- CoreXY运动结构工作原理
- 解决C#读取文本文件乱码