基于Linux内核时钟的简单闹钟应用


技术路线

在本实例中,我们采用应用程序以及内核模块的结合以达到使用动态定时器定时的目的。
其中使用了应用程序中调用了两个系统调用函数,而系统调用函数使用模块的方式进行导入。下面分别对应用程序和内核模块功能进行介绍:

应用程序

应用程序部分,首先接受来自用户输入的参数,并且对命令进行检测,如果有误,对用户进行提醒,如果正确,将调用第一个系统调用函数,将用户输入的定时时间传入内核函数中,进行定时器的定时,并且在后面通过轮询去检测定时完成标志,其中检测的函数也是一个系统调用函数,当定时完成标志被置一后,调用声音驱动函数,发出闹铃的响声并提示用户。

模块设计

模块设计部分,主要是方便应用程序对内核的动态定时器进行管理。其中在导入模块的时候,为了修改内存中的表项,首先修改寄存器的保护位,然后修改映射在内存中的系统调用表,把空闲的系统调用表项指向自己写的模块函数,并且保留原始的系统调用表,方便卸载模块的时候恢复系统调用表。模块中包含两个系统调用函数,其中一个接收来自应用程序中用户输入的参数,根据此参数设置定时器的基本定时,并在定时器的回调函数中打印倒计时,当到达定时时间的时候将计时完成标志置位。而第二个系统调用函数则是向应用程序返回计时完成标志,以达到通知应用程序的目的。


总体功能以及设计

获取到命令行中用户输入的定时参数,要能够对参数进行检查以及对输错的参数进行提示等,引导用户了解命令的使用方法。在正确得到用户输入后,解析命令,计时开始,并将用户设定计时信息输出在命令行中,方便用户进行确认。在系统内核日志中打印倒计时,在计时完成后,响起铃声,通知用户,并在命令行中输出完成信息。

流程图

在这里插入图片描述


代码

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内核定时器

猜你喜欢

转载自blog.csdn.net/qq_37429313/article/details/114263484