五-3 Linux内核计时、延时函数与根本电磁照看计时器【转】

五-3 Linux内核计时、延时函数与根本电磁照看计时器【转】

 
        动态起首化 

  •    在linux模块中,大家能够选用
  •    MODULE_LICENSE(license)  
    //定义模块的license,一般为GPL,或有关公司的license
  •    MODULE_AUTHOLAND             //模块的小编
  •    MODULE_DESC中华VIPTION        //对模块程序的讲述,string
  •    MODULE_VERSION            //版本
  •    MODULE_DEVICE_TABLE       //模块程序所支撑的设施,string
  •    MODULE_ALIAS              //别名
  •    MODULE_PATiggoM(var,type)     //模块参数

转自:

五-三 Linux内核计时、延时函数与根本放大计时器 

计时

1、 
水源时钟

1.1   
水源通过电火花计时器(timer)中断来跟踪时间流

1.2   
硬件定时器以周期性的间距产生时间暂停,那个距离(即频率)由基本依据HZ来鲜明,HZ是1个与系统布局无关的常数。

1.3   
本条时辰间隔一般取一ms到10ms.

2、 
jiffies计算器

贰.1每一回当电火花计时器中断爆发时,内核内部通过一个陆11人的变量jiffies_6四做加一计数。

2.2驱动程序开垦者经常访问的是jiffies变量,它是jiffes_64的低32位。

2.3jiffies是unsigned
long型的变量,该变量被声称为volatile,那样可幸免编写翻译器对走访该变量的言辞的优化。

贰.四jiffies笔录了自近期1回Linux运行后到当前的日子间隔(即石英钟中断发生的次数)。驱动程序常使用jiffies来总结分歧事件间的流年间隔。

3、HZ

3.一 HZ表每秒中爆发的按时中断次数

3.2 HZ就代表1秒,HZ/2就代表0.5秒

4、使用jiffies计数器

  
#include<linux/jiffies.h>

  
Unsigned long  current_i,  stamp_1, 
stamp_half,  stamp_n;

  
Current_i = jiffies;  //读取当前的值

  
Stamp_1 = current_i+HZ;  //未来的1秒

  
Stamp_half = current_i +HZ/2 ; //现在的半秒

  
Stamp_n = current_i + n*HZ/1000; //未来的n毫秒

5、为了防御jiffies溢出导致难题,最佳使用宏相比

  
#include<linux/jiffies.h>

  
Int time_after(unsigned long a, unsigned long
b);//判定a代表的时日是或不是在b之后。

  
Int time_before(unsigned long a,unsigned long b);

  
Int time_after_eq(unsigned long a ,unsigned long b);

  
Int time_before_eq(unsigned long a,unsigned long b);

6、

(壹)用户空间的timeval

struct timeval{

 
time_t tv_sev;//秒、

 
suseconds_t tv_usec;//毫秒

}

(二)用户空间的timespec

 
struct timespec{

    
time_t tv_sec;//秒

    
long  tv_nsec;//纳秒

  
}

7、内核空间jiffies和用户空间的timeval、timevspec的改动。

 
#include<linux/time.h>

  
unsigned long timespec_to_jiffies(struct timespec* value);

void jiffies_to_timespec(unsigned long jiffie,struct timespec*
value);

unsigned long timeval_to_jiffies(struct timeval* value);

void jiffies_to_timeval(unsigned long jiffies, struct 
timeval* value);

八、获取当前些天子

  
#include<linux/time.h>
   void do_gettimeofday(struct timeval*  
tv);

  
struct timespec current_kernel_time(void);

九、使用jiffies延时(要是对延时的精度需要不是非常高时,用忙等待)

  
unsigned long j = jiffies + delay*HZ

  
while(jiffies<j){  //jiffies表当前的滴答值,是不停地走的。
  
/*do nothing*/

  
}

10、长延迟。

while(time_before(jiffies,end_time)){

  
Schedule();//在end_time之前,则调度

}

 

#include<linux/sched.h>
    signed long schedule_timeout(signed long
timeout);//使用jiffies表示的延期。先做超时,

名列前茅应用:

 set_current_state(TASK_INTE帕杰罗RUPTIBLE);//设置景况值,设置为可间歇的小憩

 schedule_timeout(delay);//延时

1一、短延时(忙等待延时,不爆发休眠)

#include<linux/delay> 

//以下三个延时函数均是忙等待函数,因此在延迟进度早上饭运转别的职务。不发生休眠的。

void ndelay(unsigned long nsecs); //延时纳秒

void udelay(unsigned long usecs);//延时皮秒

void mdelay(unsigned long msecs);//延时飞秒

1二、不用忙等待的延时格局(将调用进度休眠给定期间)

#include<linux/delay.h>

void msleep(unsigened int millisecs);//休眠millisecs毫秒
不行中断的休眠millisecs飞秒

unsigned long msleep_interruptible(unsigned int
millisecs);//可间歇的蛰伏

void ssleep(unsigned int seconds);//休眠seconds秒。

 

基础定时器:

壹三、放大计时器用于调节某些函数(电磁照拂计时器管理函数)在今后的某部特按期期实行。内核电磁料理计时器注册的处理函数只进行二遍—不是循环施行的。

14、内核停车计时器被公司成双向链表,并选取struct
time_list 结构描述。

#include<linux/timer.h>

struct timer_list{

 
unsigned long expires;  //超时的jiffies

 
void (*function)(unsigned long);//超时管理函数

  
unsigned long data; //超时管理函数参数。

};

15、内核电火花计时器操作函数

1伍.1伊始化(初阶化停车计时器队列结构)

   
void init_timer(struct timer_list * timer);

   
struct timer_list TIMER_INITIALIZER(_function,_expires,_data);

一⑤.二加多放大计时器(运营电火花计时器,初始倒计时)

   
void add_timer(struct time_list *tiemer0:

一五.3刨除计时器(在停车计时器超时前将它删除。放大计时器超时后,系统会自行地将它删除)

一伍.4内决计时器使用模板

   
static struct timer_list  key_timer;//定义内核沙漏对象

   
static void key_timer_handle(unsigned ong data)//机械漏刻处理函数

   
{

     
……

     
//反应计时器管理函数具体举行代码

     
……

     
// 定时器参数的更新,重启电磁照望计时器

     
key_timer.expires = jiffies+ KEY_TIMER_DELAY;

   
  add_time(&key_time); 
 //赋新值,能够达成
循环

     
……

}

 

//设备驱动模块加载函数

static int__ init xxx_init(void)

{

 
……

 
//起始化内核沙漏

  init_time(&key_timer);

 
key_timer.function=&key_timer_handle;

 
key_timer.data = (unsigned long)key_desc;

 
key_timer.expires = jiffies+KEY_TIMER_DELAY;

 

 
//增加基本电磁照顾计时器(那是率先次运维)

 
add_time(&key_time);  

……

}

 

Static void__exit xxx_exit(void)

{
              ……

  
//删除放大计时器

  
del_timer(&key_timer);

  
……

}

16、内核沙漏与tasklet比较

16.1相同点:
在制动踏板期间运转,石英钟会在调整他们的同一个cpu上运营,软件中断的上下文,原子方式运维。(软件中断时展开硬件中断的同时执行某个异步职责的一种基础机制) 

 1陆.二不及的:不能够须求TASKLECT在给定的年华试行。

 

声称:本文非原创,整理自申嵌 

澳门新萄京 1

假诺是Arm平台的开拓板,则-C选项钦赐的职务(即内核源代码目录),当中保存有基本的顶层Makefile文件.

?

阻塞型驱动设计:

貌似的话,模块卸载函数实现与模块加载函数相反的效应:
比如模块加载函数注册了 XXX模块,则模块卸载函数应注销XXX。
若模块加载函数动体申请了内部存储器,则模块卸载函数应释放该内存。
若模块加载函数申请了硬件财富,则模块卸载函数应释放那个硬件财富。
若模块加载函数开启了硬件,则模块卸载函数应关闭硬件。

1
2
3
4
struct timespec {
 time_t tv_sec; /* seconds */
 long tv_nsec; /* nanoseconds */
};

[cpp] view
plain copy

(三)等待队列
    使用等待队列也能够兑现长延迟。
    在延迟之内,当前进度在守候队列中睡觉。
    进度在睡眠时,供给依据所等待的风云链接到某1个等候队列。
 
    a.申明等待队列
       
等待队列实际上就是多少个进程链表,链表中带有了守候某个特定事件的持有进度。 

1
2
3
4
5
6
7
obj-m := hello.o
KERNEL_BUILD := /lib/modules/$(shell uname -r)/build
all:
    make -C $(KERNEL_BUILD) M=$(shell pwd) modules
clean:
    -rm -rf *.o *.ko *.mod.c .*.cmd *.order *.symvers .tmpversions
KERNELBUILD :=/lib/modules/$(shell uname -r)/

key.c

Linux内核卸载模块函数一般以__exit标记注解,规范的模块卸载函数的款型如下:

 
    c.反应计时器的施行函数 
        超时管理函数的原型如下: 

  1. 暂停嵌套
  set_current_state(TASK_INTERRUPTIBLE); 
  schedule_timeout(2*HZ); /* 睡2秒 */ 
1
2
3
4
5
init_timer(&my_timer);
/* 填充数据结构 */
my_timer.expires = jiffies + delay;
my_timer.data = 0;
my_timer.function = my_function; /*定时器到期时调用的函数*/

澳门新萄京 2

void wait_event( 
       wait_queue_head_t q, 
       int condition); 

?

编写翻译运转能够看到:按一下开关 只打印3个OK6410 key0 down!

您大概感兴趣的稿子:

  • Linux内核漏洞浅析
  • Linux内核链表完毕进度
  • 浅谈Linux内核创制新进度的全经过
  • Linux内核模块和驱动的编制
  • SYN
    Cookie在Linux内核中的完毕
  • 一张图看尽Linux内核运行规律
  • Linux内核中红黑树算法的达成详解
  • Linux内核运行参数详解

b.

    第二步: 跳转到hand_ini处执行中断程序

build是编写翻译内核模块需求的Makefile的渠道,Ubuntu下是/lib/modules/2.6.3壹-14-generic/build

一、增多模块

同台到支付上,安装驱动模块 insmod key.ko

内核模块处理 Linux设备驱动会以内核模块的格局现身,由此学会编写Linux内核模块编制程序是上学linux设备驱动的先决条件。

 
    内核在<linux/timer.h>中提供了一密密麻麻管理停车计时器的接口。 
 
    a.创立放大计时器 

void work1_func(struct work_struct *work)
{
printk(KERN_WARNING”this is work1>\n”);
}

 
    b.等待函数
        进度经过调用上边函数能够在某些等待队列中休眠固定的年月: 

1
2
3
4
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait, 0, 2*HZ);
/*当前进程在等待队列wait中睡2秒 */

也是四个参数:

澳门新萄京, 
不确定时间的延迟实行
(一)什么是不确按期期的延期
   
前面介绍的是明确时期的推迟实行,但在写驱动的进程中时常遇到这种气象:
    用户空间程序调用read函数从设备读数据,但设备中当前尚未发生多少。
   
此时,驱动的read函数私下认可的操作是跻身休眠,平昔等待到设备中有了数量停止。
 
    这种等待正是快要灭亡时的推移,经常选拔休眠机制来落实。
 
 
(2)休眠
    休眠是依附等待队列达成的,前边大家早就介绍过wait_event连串函数,
    但前日大家将不会有鲜明的蛰伏时间。
 
    当进度被置入休眠时,会被标志为特别情形并从调节器的周转队列中移走。
   
直到有些事件时有发生后,如设备接收到数量,则将经过重新设为运维态并跻身运维队列举行调节。
   
休眠函数的头文件是<linux/wait.h>,具体的完成函数在kernel/wait.c中。
 
    a.休眠的平整
        *世代不要在原子上下文中休眠
       
*当被提拔时,大家鞭长莫及知晓睡眠了稍稍时间,也不掌握醒来后是不是获得了大家必要的能源
        *只有知道有此外进度会在任何地方唤醒大家,否则过程不可能休眠
 
    b.等待队列的初阶化
        见前文
 
    c.休眠函数
       
linux最简便易行的睡眠方式为wait_event宏。该宏在实现休眠的同时,检查进程等待的基准。
 
        A. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <linux/init.h>
#include <linux/module.h>
  
  
static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__);
    return 0;
}
  
static void __exit hello_exit(void)
{
  
    printk(KERN_ALERT "Hello world! %s, %d\n", __FILE__, __LINE__);
}
  
module_init(hello_init);
module_exit(hello_exit);
  
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mikcy Liu");
MODULE_DESCRIPTION("A simple Module");
MODULE_ALIAS("a simple module");

那时期的函数就不细说了,照旧同样的章程不会就翻开内核代码!上面包车型大巴开关驱动实际上是不全面的,按一下会打字与印刷好些个少个按钮按下的音讯,这里运用方面介绍到的基石反应计时器知识优化方面包车型客车开关程序:

    #include <linux/sched.h> 
    signed long schedule_timeout(signed long timeout); 

?

这里KEYINT1是和GPN0相连,

未设定等级的,在<内核目录>/kernel/printk.c中定义

1
2
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(2*HZ); /* 睡2秒 */

 

    modinfo  hello.ko能够查阅模块新闻,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/init.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/delay.h>
  
static int __init test_init(void)
{
  set_current_state(TASK_INTERRUPTIBLE);
  schedule_timeout(5 * HZ);
  printk(KERN_INFO "Hello Micky\n");
  return 0;
}
  
static void __exit test_exit(void)
{
}
  
module_init(test_init);
module_exit(test_exit);
  
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Micky Liu");
MODULE_DESCRIPTION("Test for delay");

澳门新萄京 3

 
 (四)得到当前岁月
   
驱动程序中貌似无需精晓墙钟时间(也正是年月日的日子)。但驱动可能须求管理相对时间。
    为此,内核提供了八个结构体,都定义在<linux/time.h>: 

?

版权注明:本文为博主原创作品,未经博主允许不得转发。

    add_timer(&my_timer); 

 
            2. 

此间总计一下地方函数跳转的解析进程:

        B. 

(三)等待队列 
    使用等待队列也得以兑现长延迟。 
    在延迟时期,当前进程在伺机队列中睡觉。 
    进程在睡觉时,必要凭仗所等待的轩然大波链接到某1个等候队列。 
 
    a.证明等待队列 
       
等待队列实际上正是三个进度链表,链表中富含了等候某些特定事件的兼具进度。 

澳门新萄京 4

3、删除模块

make -C $(KERNEL_BUILD) M=$(shell pwd) modules 编写翻译内核模块。-C
将专门的学问目录转到KE冠道NEL_BUILD,调用该目录下的Makefile,并向这一个Makefile传递参数M的值是$(shell
pwd) modules。

下一场函数又跳到此地了:

    struct timer_list my_timer; 

?

 

       
在守候时期能够让出管理器,但系统不恐怕进入空闲格局(因为这么些进度一直在进行调节),不便于省电。
 
    b.超时函数 

1
2
#include <linux/time.h>
    struct timespec current_kernel_time(void);

在地点的基础上雄起雌伏优化,实现多开关驱动这里增添key伍开关!(结合上边的规律图部分)

  u64 j2; 
    j2 = get_jiffies_64(); 

   在模块所在目录下执行

澳门新萄京 5

 
    那四个延迟函数均是忙等待函数,在延迟经过中不能够运行其他职务。
 
(2)长延时
    a.在延迟届时前让出处理器 

?

澳门新萄京 6

make -C $(KERNEL_BUILD) M=$(shell pwd) modules 编写翻译内核模块。-C
将职业目录转到KE中华VNEL_BUILD,调用该目录下的Makefile,并向这几个Makefile传递参数M的值是$(shell
pwd) modules。

内核模块管理 Linux设备驱动会以内核模块的款型出现,因而学会编写Linux内核模块编程是学习linux设备驱动的先决条件。

expires: 超时也正是按时多久

    #include <linux/time.h> 
    void do_gettimeofday(struct timeval *tv); 

 
    使用方式: 

 

obj-m := hello.o 
KERNEL_BUILD := /lib/modules/$(shell uname -r)/build 
all: 
    make -C $(KERNEL_BUILD) M=$(shell pwd) modules 
clean: 
    -rm -rf *.o *.ko *.mod.c .*.cmd *.order *.symvers .tmpversions 
KERNELBUILD :=/lib/modules/$(shell uname -r)/
1
2
3
4
struct timeval {
 time_t tv_sec; /* seconds */
 suseconds_t tv_usec; /* microseconds */
};
  1. 暂停分层情势

    较老,但很盛行。选取秒和皮秒值,保存了196六年11月1五日0点以来的秒数 

?

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7. #include <linux/slab.h> /* for kmalloc */  
  8. #include<linux/uaccess.h> /* for copy_to_usr */  
  9. #include <linux/sched.h>  
  10.   
  11. #define GPNCON  0x7F008830  
  12. #define GPNDAT  0x7F008834  
  13.   
  14. unsigned int *gpio_data;  
  15.   
  16. struct work_struct *work壹;//定义一项工作  
  17.   
  18. struct timer_list key_timer; //定义1个电磁照拂计时器key_timer  
  19.   
  20. unsigned int key_num = 0;  
  21.   
  22. wait_queue_head_t key_q; //定义两个守候队列  
  23.   
  24. void work1_func(struct work_struct *work)  
  25. {  
  26.     //运维放大计时器 jiffies是全局变量,用来代表近些日子系统时间 一S=一千个滴答数  
  27.     mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S  
  28. }  
  29.   
  30. void key_timer_func(unsigned long data)  
  31. {  
  32.     unsigned int key_val;  
  33.       
  34.     key_val = readw(gpio_data)&0x0一; //只读取最终壹人  
  35.     if(key_val == 0)  
  36.     {  
  37.         //printk(KERN_WARNING”OK6410 key0 down!\n”);  
  38.         key_num = 1;  
  39.     }  
  40.       
  41.     key_val = readw(gpio_data)&0x20; //只读取最终1个人  
  42.     if(key_val == 0)  
  43.     {  
  44.         //printk(KERN_WARNING”OK6410 key5 down!\n”);  
  45.         key_num = 6;  
  46.     }  
  47.       
  48.     wake_up(&key_q);  
  49. }  
  50.   
  51. irqreturn_t key_int(int irq, void *dev_id)  
  52. {  
  53.     //一. 检查实验是还是不是发生了开关中断 这里能够不经常不做,因为那边没有应用共享中断  
  54.       
  55.     //2. 清除已经产生的按键中断 那几个是指硬件内处,开关CPU内部无需做拍卖  
  56.            
  57.     //三. 提交下半部  
  58.     schedule_work(work1);  
  59.       
  60.     //return 0;  
  61.     return IRQ_HANDLED;  
  62. }  
  63.   
  64. void key_hw_init(void) //按钮硬件开头化部分  
  65. {  
  66.     unsigned int *gpio_config;  
  67.     unsigned short data;  
  68.       
  69.     gpio_config = ioremap(GPNCON, 4);//将大要地址转化为虚拟地址  
  70.     data = readw(gpio_config);  
  71.     data &= ~0b110000000011; //先清零  
  72.     data |= 0b1000000000十;  //后两位设置成0b十  
  73.     writew(data, gpio_config);  
  74.       
  75.     gpio_data = ioremap(GPNDAT, 四);//将物理地址转化为虚拟地址  
  76.       
  77.     printk(KERN_WARNING”init …!\n”);  
  78. }  
  79.   
  80. int key_open(struct inode *node, struct file *filp)  
  81. {  
  82.     printk(KERN_WARNING”open …!\n”);  
  83.       
  84.     return 0;  
  85. }  
  86.   
  87. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)  
  88. {  
  89.     wait_event(key_q,key_num);//休眠 未有按下为0  
  90.       
  91.     //将key_value值重回给用户空间  
  92.     printk(KERN_WARNING”in kernel :key num is %d\n”,key_num);  
  93.     copy_to_user(buf, &key_num, 4); //buf为用户空间传过来的地方  
  94.       
  95.     key_num = 0;  
  96.       
  97.     return 4;  
  98. }  
  99.   
  100. struct file_operations key_fops =   
  101. {  
  102.     .open = key_open,  
  103.     .read = key_read,  
  104. };  
  105.   
  106. struct miscdevice key_miscdev = //定义二个misdevice结构  
  107. {  
  108.     .minor = 200,  
  109.     .name = “6410key”,  
  110.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  111. };  
  112.   
  113. static int key_init11(void)  
  114. {  
  115.     int err;  
  116.       
  117.     misc_register(&key_miscdev);//注册2个掺杂设备驱动装置  
  118.       
  119.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, “6410key”, 0)) < 0 )  
  120.     {  
  121.          printk(KERN_WARNING”err = %d\n”, err);  
  122.          goto irq_err;  
  123.     }  
  124.     if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, “6410key”, 0)) < 0 )  
  125.     {  
  126.          printk(KERN_WARNING”err = %d\n”, err);  
  127.          goto irq_err;  
  128.     }  
  129.   
  130.     key_hw_init();  
  131.       
  132.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);  
  133.     INIT_WORK(work1 , work1_func );  
  134.       
  135.     //初阶化计时器  
  136.     init_timer(&key_timer);  
  137.     key_timer.function = key_timer_func; //将定义的函数赋值给函数指针  
  138.       
  139.     //注册电火花计时器  
  140.     add_timer(&key_timer);  
  141.       
  142.     //初叶化2个等候队列  
  143.     init_waitqueue_head(&key_q);  
  144.       
  145.     return 0;  
  146.       
  147. irq_err:  
  148.         misc_deregister(&key_miscdev);    
  149.     return -1;  
  150. }  
  151.   
  152. static void key_exit(void)  
  153. {  
  154.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数权且用二个变量来代表(中断号)  
  155.     free_irq(S3C_EINT(五), 0);//注销中断 这里irqnumber参数目前用一个变量来表示(中断号)  
  156.       
  157.     misc_deregister(&key_miscdev);//注销贰个混合设备驱动  
  158.       
  159.     printk(KERN_WARNING”key up!”);  
  160. }  
  161.   
  162. module_init(key_init11);  
  163. module_exit(key_exit);  
  164. MODULE_LICENSE(“GPL”);  
  165. MODULE_DESCRIPTION(“key driver”);  
      wait_queue_head_t my_queue; 
      init_waitqueue_head(&my_queue); 

?

queue_work(my_wq, work1);

static void __exit cleanup_function(void) {   
 //释放代码  
}  
module_exit(cleanup_function); 

?

澳门新萄京 7

   在模块所在目录下进行

除非当printk打字与印刷消息时的loglevel小于DEFAULT_CONSOLE_LOGLEVEL的值(优先级高于console
loglevel),这几个消息才会被打字与印刷到console上。

 

    init_timer(&my_timer); 
    /* 填充数据结构 */ 
    my_timer.expires = jiffies + delay; 
    my_timer.data = 0; 
    my_timer.function = my_function; /*定时器到期时调用的函数*/ 
1
2
3
4
static int __init initialization_function(void) {
   //初始化代码 
module_init(initialization_function);

澳门新萄京 8

    void my_timer_function(unsigned long data); 

诚如的话,模块卸载函数实现与模块加载函数相反的效果: 
假设模块加载函数注册了 XXX模块,则模块卸载函数应注销XXX。
若模块加载函数动体申请了内部存储器,则模块卸载函数应释放该内部存款和储蓄器。
若模块加载函数申请了硬件财富,则模块卸载函数应释放那么些硬件财富。 
若模块加载函数开启了硬件,则模块卸载函数应关闭硬件。

 

前者能够定义输出品级,在 <内核目录>/include/linux/kernel.h中

1
lsmod | grep hello

地点四个十分重要的分子(浅灰褐部分)

二、查看模块

1
void my_timer_function(unsigned long data);

澳门新萄京 9

 
    内核在<linux/timer.h>中提供了一层层管理电火花计时器的接口。
 
    a.创立反应计时器 

?

有了地方的根基,然后对从前的按键驱动进行改正!通过暂停分层来兑现开关驱动

  unsigned long timeout = jiffies + HZ/2; /* 0.5秒后超时 */ 
  ... 
  if(time_before(jiffies, timeout)){ 
    /* 没有超时,很好 */ 
  }else{ 
    /* 超时了,发生错误 */ 

?

 

   
在linux内核里,错误编码是3个负值,在<linux/errno.h>中定义,包罗-ENODEV、-ENOMEM之类的符号值。重临相应的失实编码是种蛮好的习于旧贯,因为唯有这么,用户程序才足以选拔perror等办法把它们调换来有意义的错误音讯字符串。

?

率先点:达成中断管理程序

头文件init.h包罗了宏_init和_exit,它们允许释放内核占用的内部存储器。
module_init()和hello_exit()是模块编制程序中最主旨也是必须的多少个函数。
module_init()是驱动程序初阶化的入口点。
hello_exit是模块的淡出和清理函数。此处可以做有所终止该驱动程序时有关的清理专门的职业。

 
 (4)获得当前时刻 
   
驱动程序中貌似无需精晓墙钟时间(也正是年月日的时日)。但驱动大概需求处理相对时间。 
    为此,内核提供了四个结构体,都定义在<linux/time.h>: 

这一行的命令功用是发生设备结点供应用程序访问 ,ok64十key为器材名字
c表示这一个是字符设备 混杂设备也是字符设备 十 是混杂字符设备的联结道具号
200是在驱动程序中定义的次设备号.

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/time.h> 
#include <linux/sched.h> 
#include <linux/delay.h> 

static int __init test_init(void) 
{ 
  set_current_state(TASK_INTERRUPTIBLE); 
  schedule_timeout(5 * HZ); 
  printk(KERN_INFO "Hello Micky\n"); 
  return 0; 
} 

static void __exit test_exit(void) 
{ 
} 

module_init(test_init); 
module_exit(test_exit); 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("Micky Liu"); 
MODULE_DESCRIPTION("Test for delay"); 

?

schedule_work

   
进程经过二秒后会被唤醒。假若不期望被用户空间打断,能够将经过情形设置为TASK_UNINTERRUPTIBLE。

?

开关计时器去抖:

   
unknown经常是指jiffies,known是亟需相比较的值(平时是二个jiffies加减后总括出的相对值)
 
    例: 

前者能够定义输出等第,在 <内核目录>/include/linux/kernel.h中

struct workqueue_struct *my_wq;//定义二个职业行列指针

b.

   
在linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。重回相应的不当编码是种格外好的习于旧贯,因为唯有那样,用户程序才方可采纳perror等格局把它们转变来有含义的错误音讯字符串。

void(*handler)(int , void *):中断管理函数

    del_timer(&my_timer); 

?

 

            q: 是等待队列头,注意是利用值传递。
            condition:
任性一个布尔表明式,在规则为真在此以前,过程会维持休眠。
            注意!进度须要通过唤醒函数才只怕被唤起,此时急需检查评定标准。
            若是条件满足,则被晋升的历程真正醒来;
            假设条件不满意,则经过继续安歇。
 
 
    d.唤醒函数
       
当大家的历程睡眠后,要求由别的的某部试行线程(只怕是另3个进度或暂停管理例程)唤醒。
        唤醒函数:
            #include <linux/wait.h>
            1.

        能够用来那多个已经初阶化但还没激活的计时器, 
        要是调用时电磁照应计时器未被激活则重返0,不然再次来到一。 
        一旦mod_timer重回,放大计时器将被激活。 
 
    f.删除计时器 

. IRQF_DISABLED(SA_INTE智跑RUPT) 火速中断

       
调用那五个函数后,进程会在加以的等待队列q上休眠,但会在逾期(timeout)到期时再次回到。
       
要是超时到期,则重回0,借使经过被此外交事务件唤醒,则赶回剩余的日子数。
        如若未有等待条件,则将condition设为0
 
        使用方法: 

基础时间管理 (一)内核中的时间概念 
    时间处理在linux内核中据有十二分关键的效率。 
    相对于事件驱动来说,内核中有大气函数是依附时间驱动的。 
    某些函数是周期施行的,比方每10阿秒刷新三回显示器; 
    某些函数是推后一按期间执行的,举例基本在500皮秒后进行某项任务。 
    要区分: 
    *纯属时间和相对时间 
    *周期性爆发的轩然大波和推迟推行的事件 
    周期性事件是由系统系统计时器驱动的 
 
(2)HZ值 
    内核必须在硬件停车计时器的拉拉扯扯下本事总结和保管时间。 
    机械漏刻发生中断的频率称为节拍率(tick rate)。 
   
在基础中钦赐了叁个变量HZ,内核初叶化的时候会依靠那么些值分明机械漏刻的节拍率。 
    HZ定义在<asm/param.h>,在i3八六平台上,最近应用的HZ值是一千。 
    也正是石英钟中断每秒产生1000次,周期为1微秒。即: 
    #define HZ 1000 
 
   
注意!HZ不是个稳固不改变的值,它是能够变动的,能够在内核源代码配置的时候输入。 
    分化的种类布局其HZ值是区别样的,举个例子arm就接纳拾0。 
    如若在驱动中要使用系统的制动踏板频率,直接利用HZ,而不要用十0或一千 
 
 
    a.理想的HZ值 
        i3八陆的HZ值平昔选择十0,直到贰.伍版后才改为一千。 
       
升高节拍率意味着时钟中断爆发的尤为频仍,中断管理程序也会更频仍地实行。 
 
        带来的益处有: 
        *根本电火花计时器能够以越来越高的功能和越来越高的正确度运营 
       
*借助测量时间的装置试行的系统调用,举个例子poll()和select(),运营的精度越来越高 
        *进步进程抢占的纯正度 
       
(缩小了调整延时,如若经过还剩二ms日子片,在拾ms的调治周期下,进程会多运营八ms。 
        由于推延了并吞,对于一些对时间需要从严的天职会产生潜移默化) 
 
        坏处有: 
        *节拍率要高,系统担任越重。 
        中断管理程序将攻陷越来越多的计算机时间。 
 
 (3)jiffies 
    全局变量jiffies用于记录系统运行以来发生的旋律的总的数量。 
   
运行时,jiffies开始化为0,此后历次石英钟中断管理程序都会加多该变量的值。 
    那样,系统运营后的运维时刻正是jiffies/HZ秒 
 
    jiffies定义于<linux/jiffies.h>中: 
    extern unsigned long volatile jiffies; 
 
    jiffies变量总是为unsigned long型。 
    因而在叁12人体系布局上是3三个人,而在陆十六位连串上是陆十四人。 
    对于3一个人的jiffies,如若HZ为1000,4玖.七天后会溢出。 
   
固然溢出的情景不普及,但先后在检查评定超时时仍旧也许因为回绕而导致错误。 
    linux提供了八个宏来相比节拍计数,它们能科学地拍卖节拍计数回绕。 
 
 

澳门新萄京 10

模块卸载函数

模块证明与叙述

那是内核源码里面找到的那几个函数用法示例,这里能够观望create_workqueue函数唯有3个参数,参数为办事行列的名字,再次来到的为创立好的一个专业行列指针,下边第多少个箭头所针对的有的正是那些指针的类型!

 
    b.起首化电磁照顾计时器 

模块编写翻译 首先看望Makefile文件:

那边看望OK6四十内核源码部分关于中断号的宏定义:

模块加载函数

?

澳门新萄京 11

    #include <linux/wait.h> 
    long wait_event_timeout(wait_queue_head_t q,condition, long timeout); 
    long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);

?

               
先事先注册中断程序,然后依据对应的间歇找到呼应的间歇管理程序

  struct timespec { 
   time_t tv_sec; /* seconds */ 
   long tv_nsec; /* nanoseconds */ 
  }; 

?

行事行列是壹种将职责推后推行的款式,他把推后的天职交由二个内核线程去施行。这样下半部会在经过上下文实践,它同意再一次调解以致睡觉。各类被推后的义务叫做“职业”,由那一个干活儿整合的种类称为职业行列

/* printk's without a loglevel use this.. */ 
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ 
 #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ 

        电火花计时器1旦激活就起初运转。 
 
    e.改变已激活的放大计时器的晚点时间 

  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/miscdevice.h> /* for struct miscdevice*/  
  4. #include <linux/interrupt.h>  
  5. #include <linux/fs.h> /* for iormap */  
  6. #include <linux/io.h>  
  7.   
  8. #define GPNCON 0x7F008830  
  9.   
  10. irqreturn_t key_int(int irq, void *dev_id)  
  11. {  
  12.     //一. 检验是还是不是发生了按钮中断 这里能够有时不做,因为此地未有动用共享中断  
  13.       
  14.     //2. 清除已经爆发的按钮中断 这么些是指硬件内处,开关CPU内部没有必要做拍卖  
  15.          //比如尽管是网卡驱动 将要管理  
  16.       
  17.     //三. 打字与印刷开关值  
  18.       
  19.     printk(KERN_WARNING”key down!\n”);  
  20.       
  21.     return 0;  
  22. }  
  23.   
  24. void key_hw_init(void) //开关硬件初阶化部分  
  25. {  
  26.     unsigned int *gpio_config;  
  27.     unsigned short data;  
  28.       
  29.     //第二步:设置GPNCON寄存器设置GPIO为输入  
  30.     gpio_config = ioremap(GPNCON, 肆);//将梗概地址转化为虚拟地址  
  31.     data = readw(gpio_config);  
  32.     data &= ~0b11; //先清零  
  33.     data |= 0b10;  //后两位设置成0b拾  
  34.     writew(data, gpio_config);  
  35.     printk(KERN_WARNING”init …!\n”);  
  36.     //第三步: 开关中断部分相应管理 注册中断 注销等等  
  37. }  
  38.   
  39. int key_open(struct inode *node, struct file *filp)  
  40. {  
  41.     printk(KERN_WARNING”open …!\n”);  
  42.       
  43.     return 0;  
  44. }  
  45.   
  46. struct file_operations key_fops =   
  47. {  
  48.     .open = key_open,  
  49. };  
  50.   
  51. struct miscdevice key_miscdev = //定义五个misdevice结构  
  52. {  
  53.     .minor = 200,  
  54.     .name = “key”,  
  55.     .fops = &key_fops,//这里key_fops是一个struct file_operations结构  
  56. };  
  57.   
  58. static int key_init(void)  
  59. {  
  60.     int err;  
  61.       
  62.     misc_register(&key_miscdev);//注册3个混合设备驱动装置  
  63.       
  64.     //开关初阶化 硬件开头化部分一般可一放在模块开头化部分只怕open函数中 这里位于模块开首化部分  
  65.     key_hw_init();  
  66.       
  67.     //由高电平变为低电平发生中断 ILX570QF_TRIGGER_FALLING  
  68.       
  69.       
  70.     if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, “key”, 0)) < 0 )//注册中断管理程序 多少个参数  
  71.     {  
  72.          printk(KERN_WARNING”err = %d\n”, err);  
  73.          goto irq_err;  
  74.     }  
  75.       
  76.     return 0;  
  77.       
  78. irq_err:  
  79.         misc_deregister(&key_miscdev);    
  80.     return -1;  
  81. }  
  82.   
  83. static void key_exit(void)  
  84. {  
  85.     free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数权且用1个变量来表示(中断号)  
  86.       
  87.     misc_deregister(&key_miscdev);//注销贰个错落设备驱动  
  88.       
  89.     printk(KERN_WARNING”key up!”);  
  90. }  
  91.   
  92.   
  93. module_init(key_init);  
  94. module_exit(key_exit);  
  95. MODULE_LICENSE(“GPL”);  
  96. MODULE_DESCRIPTION(“key driver”);  

 
    使用情势: 

1
add_timer(&my_timer);

    三.三 注销管理
当设备不再须求动用中断时(日常在使得卸载时),应当把它们注销,使用函数:

M=选项让该makefile在结构modules目的在此之前重返到模块源代码目录。然后modules指标指向obj-m变量中设定的模块

?

二.二. 上边去内核源码中搜寻一下init_work那么些函数的用法:

唯有当printk打字与印刷消息时的loglevel小于DEFAULT_CONSOLE_LOGLEVEL的值(优先级高于console
loglevel),这么些音讯才会被打字与印刷到console上。

1
del_time_sync(&my_timer);

 

        反应计时器一旦激活就最先运维。
 
    e.更动已激活的机械漏刻的晚点时间 

   
在smp系统中,确定保障重返时,全体的放大计时器管理函数都退出。不能够在暂停上下文使用。 
    

快/慢速成人中学学断的珍视分歧在于:快捷中断保险中断管理的原子性(不被卡住),而慢速成中学断则不保障。换句话说,也正是“开启中断”标识位(管理器IF)在运作高效中断管理程序时是倒闭的,因而在劳动该中断时,不会被其余类别的中断打断;而调用慢速成中学断处理时,别的类其他行车制动器踏板仍是可以够得到服务。

  struct timeval { 
   time_t tv_sec; /* seconds */ 
   suseconds_t tv_usec; /* microseconds */ 
  }; 

 
        wake_up会唤醒等待在给定queue上的全体进程。 
        而wake_up_interruptible唤醒那多少个进行可暂停休眠的进度。 
       
试行中,约定做法是在运用wait_event时使用wake_up,而使用wait_event_interruptible时使用wake_up_interruptible。

 澳门新萄京 12澳门新萄京 13

   lsmod命令实际上读取并分析/proc/modules文件,也足以cat
/proc/modules文件

1
2
3
4
5
6
#include <linux/wait.h>
struct __wait_queue_head {
  spinlock_t lock;
  struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

用法示例:

    wait_queue_head_t wait; 
    init_waitqueue_head(&wait); 
    wait_event_interruptible_timeout(wait, 0, 2*HZ); 
    /*当前进程在等待队列wait中睡2秒 */ 
1
2
3
4
5
6
7
8
#define KERN_EMERG   "<0>"  /* system is unusable          */
#define KERN_ALERT   "<1>"  /* action must be taken immediately   */
#define KERN_CRIT    "<2>"  /* critical conditions         */
#define KERN_ERR    "<3>"  /* error conditions           */
#define KERN_WARNING  "<4>"  /* warning conditions          */
#define KERN_NOTICE   "<5>"  /* normal but significant condition   */
#define KERN_INFO    "<6>"  /* informational            */
#define KERN_DEBUG   "<7>"  /* debug-level messages         */

然后mknod /dev/ok6410key  c   10  200 

水源时间管理 (1)内核中的时间概念
    时间管理在linux内核中占领拾一分重大的功用。
    相对于事件驱动来说,内核中有大气函数是基于时间驱动的。
    某个函数是周期实行的,比方每10纳秒刷新一回显示屏;
    有个别函数是推后一按期间实行的,比方基本在500皮秒后实行某项职务。
    要区分:
    *相对时间和对即刻间
    *周期性发生的事件和延缓试行的事件
    周期性事件是由系统系统电磁关照计时器驱动的
 
(2)HZ值
    内核必须在硬件机械漏刻的提携下手艺揣度和管制时间。
    电磁照看计时器发生中断的频率称为节拍率(tick rate)。
   
在根本中钦命了三个变量HZ,内核开首化的时候会依靠那个值明确测量时间的装置的节拍率。
    HZ定义在<asm/param.h>,在i38六平台上,近来利用的HZ值是一千。
    也正是时钟中断每秒发生一千次,周期为一飞秒。即:
    #define HZ 1000
 
   
注意!HZ不是个固定不改变的值,它是足以改造的,可以在内核源代码配置的时候输入。
    差别的系统布局其HZ值是不雷同的,比如arm就选取100。
    要是在使得中要利用系统的间歇频率,直接利用HZ,而不要用十0或一千
 
 
    a.理想的HZ值
        i38陆的HZ值一向采纳十0,直到二.5版后才改为1000。
       
升高节拍率意味着石英钟中断发生的尤为频仍,中断管理程序也会更频仍地实施。
 
        带来的功利有:
        *水源计时器能够以越来越高的频率和更加高的正确度运转
       
*依傍放大计时器试行的类别调用,譬如poll()和select(),运转的精度越来越高
        *巩固进程抢占的正确度
       
(减弱了调治延时,假诺经过还剩2ms年华片,在10ms的调节周期下,进程会多运转八ms。
        由于推延了抢占,对于某个对时间要求严刻的职责会发生影响)
 
        坏处有:
        *节拍率要高,系统担负越重。
        中断管理程序将攻下更加多的Computer时间。
 
 (3)jiffies
    全局变量jiffies用于记录系统运转以来发出的韵律的总的数量。
   
运维时,jiffies初阶化为0,此后历次时钟中断管理程序都会扩大该变量的值。
    那样,系统运转后的运行时刻正是jiffies/HZ秒
 
    jiffies定义于<linux/jiffies.h>中:
    extern unsigned long volatile jiffies;
 
    jiffies变量总是为unsigned long型。
    因而在三十三个人体系布局上是三十八个人,而在陆10人种类上是陆拾位。
    对于30个人的jiffies,假如HZ为一千,4九.柒天后会溢出。
   
纵然溢出的状态不常见,但顺序在检查测试超时时依然大概因为回绕而形成错误。
    linux提供了六个宏来比较节拍计数,它们能科学地管理节拍计数回绕。 
 
 

    modinfo  hello.ko能够查看模块信息,如下所示

    第一步:苏醒现场,

内核模块中用来出口的函数式内核空间的printk()而非用户空间的printf(),printk()的用法和printf()相似,但前者可定义输出品级。printk()可看作1种最基本的木本调节和测试手腕

    较老,但很盛行。选用秒和皮秒值,保存了1九陆七年3月二二日0点来讲的秒数 

停顿号偏移 个中前面包车型大巴3四个中断号是留住用户程序作为软中断来行使, 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图