Linux驱动开发详解(4.0)-- Timer相关

学习宋宝华Linux驱动开发详解(基于kernel 4.0)ch10 中断 timer中实践代码笔记。

编程环境:
ubuntu 16.04
Kernel 4.15.0-generic

内核定时器

软件意义上的定时器最终由硬件定时器实现。Top Half:内核在时钟中断发生后检测各定时器是否到期。Bottom Half:到期后的定时器处理函数将作为软中断在底半部执行。

数据结构:timer_list

书中版本采用的kernel 4.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//from kernel v4.4
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(unsigned long); //这里中断处理函数的参数是unsigned long
unsigned long data;
u32 flags;
int slack;

#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

而内核环境4.15对timer.h进行了更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//from kernel v4.15
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *); //这里传入的是timer_list实例
u32 flags;

#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};

timer_list初始化

初始化timer_list的时候最终执行的都是_init_timer这个函数,但是这个函数在两个版本中的定义也发生了修改。

1
2
3
4
5
6
//from v4.4
#define __init_timer(_timer, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_flags), #_timer, &__key); \
} while (0)
1
2
3
4
5
6
//from v4.15
#define __init_timer(_timer, _fn, _flags) \
do { \
static struct lock_class_key __key; \
init_timer_key((_timer), (_fn), (_flags), #_timer, &__key);\
} while (0)
  • 在宋宝华版本的second.c中在second_open中使用了init_timer(struct timer_list *timer_list)这个函数进行初始化(v4.4),但这个函数在v4.15被删除了。
  • 同样,在timer_list结构体中,handler function传入的参数也进行了改变。

在内核编译的过程中要注意。编译报错tips:

  • Implicit declaration of function: 通常出现在:1.没有把函数所在的c文件生成.o文件; 2.在函数所属的c文件中定义了,但是没有在.h文件中声明
  • assignment from incompatible pointer type: 通常出现在函数传入的参数指针类型不一致的情况。

内核模块命令行参数传递

在user mode中,我们可以通过main(int argc, char *argv[])来进行命令行参数传递,在运行可执行文件的时候跟上命令行参数来配置相关变量。
在kernel mode中,通过module_param来进行设置命令行参数module_param(name, type, perm)。

在原版宋宝华second.c文件中,second_major设置为了248。但查看系统内置驱动cat /proc/devices可以看到248被watchdog所占用,所以在安装insmod的时候出现了报错Device or resource busy.

这时候我们要通过命令行参数来修改second_major的主设备号到260,或为0使得系统为second分配一个没有使用过的设备号。

  • 查看模块信息,modinfo second.ko。看到param: second_major:int
  • 安装模块 insmod second.ko second_major=0 (使系统为second分配一个没有使用过的设备号)
  • 查看安装好的模块 cat /proc/devices 可以看到设备号被分到了244

安装设备(文件)在/dev

使用mknod命令在/dev中进行实体设备(文件)安装。一个设备只有在定义了正确的主设备号才能被链接到正确的设备驱动中。
mknod [name] [type: c-char b-block] [major] [minor]

使用shell脚本从/proc/devices中获取驱动的主设备号

awk '{if($2=="your-driver") {print $1}}' /proc/devices
awk是一个强大的文本编辑命令。
所以命令为:
mknod /dev/second c $(awk '{if($2=="second") print $1} /proc/devices') 0

测试second设备

运行second_test可执行文件,可以看到实际上user就是去读取了设备文件中的counter。而counter是在每一次内核时钟中断到来的时候由second_timer_handler进行处理。

定时器的到期时间往往是在当前jiffies的数目上加上一个时延,如果是HZ,表示1s。

查看kernel msg cat /proc/kmsg 不会打印之前已经打印过的log
dmesg 会将从开机到现在所有的log都打印出来