Little Deamon.

timer_list结构体

字数统计: 870阅读时长: 3 min
2019/07/02 Share

定时器timer_list的变化

dummynet编译的时候报了这么个错误。timer_list是linux kernel中的定时器结构体。

在2.3版本之后的定时器一般默认指的是动态定时器,只需要初始化一个超时时间,激活后在到期的时候就会触发设置的函数。定时器可以在运行之后就撤销,不必周期运行。

linux4.15之前

1
2
3
4
5
6
7
struct timer_list {
struct hlist_node entry; //定时器链表入口
unsigned long expires;//以jiffies为单位的超时值
void (*function)(unsigned long);//超时后的处理函数
unsigned long data;//传给函数的参数
u32 flags;
}

这是linux4.14中的定时器结构体(精简)

expires字段包含计时器的到期时间(以jiffies为单位);超时到期后将会调用function。我们可以手动填写timer_list结构,但更常见的是使用setup_timer()宏:void setup_timer(timer, function, data);

这个api存在很多问题,data字段不必要的膨胀了timer_list结构体,同时作为一个无符号整形,可以抵御任何类型的类型检查,例如这个值被转换成指针的情况并不少见。在比较新的内核api中更多的是放弃数据字段,而是将指针传递给相关的结构体。

linux4.15

1
2
3
4
5
6
struct timer_list {
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;
};

在4.15版本的内核中,timer_list正式舍弃的旧的timer_list结构体,也就是正式取消了data字段。而如何向函数传递参数,这里用到的是和内核链表同样的思想。将timer_list结构体嵌入一个更大的结构中,然后用container_of()的内核宏可以取得更大结构体中data字段。

代码说明

尝试用一段简单的代码demo来解释这个行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>

#define offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(PTR,TYPE,MEMBER) ({ \
const typeof(((TYPE *)0)->MEMBER) *__mptr=(PTR); \
(TYPE *) ((char *)__mptr - offsetof(TYPE,MEMBER)); })

struct timer_list_old{
void (* function)(unsigned long);
unsigned long data;
};

struct timer_list_new{
void (* function)(struct timer_list_new *);
};

struct use_timer_new{
unsigned long data;
struct timer_list_new timer;
};

void PrintDataNew(struct timer_list_new * temp)
{
struct use_timer_new *ptimer = container_of(temp, struct use_timer_new, timer);
printf("new data = %ld\n", ptimer->data);
}

void PrintDataOld(unsigned long data)
{
printf("old data = %ld\n", data);
}


int main()
{
struct use_timer_new tnew;
tnew.timer.function = (void *)PrintDataNew;
tnew.data = 123;


struct timer_list_old told;
told.function = (void *)PrintDataOld;
told.data = 456;


tnew.timer.function(&tnew.timer);
told.function(told.data);


}

在这里我们假设我们只有指向timer_list的指针。老的方法,是直接取出timer_list中的data传入timer_list中的注册的function。新的方法向function传入指向自己的指针。然后通过container_of()宏,取得指向包含timer_list结构体的大结构体的指针。

container_of()宏的作用是通过一个结构里某个成员的地址,结构的类型,成员的字段名取得这个结构的地址。简单拿来说,只要知道结构的实现,某个成员的地址,就可以获得结构的地址。

最后至于dummynet当然是弃用了,其中的逻辑也不是修改一个结构体的用法就能修正的,作为一个年久失修的内核模块项目未来空闲了可以来弄弄。至于替代品自然是选用了tc,虽然只能模拟上行丢包,但只需要在双端都配置上tc,也可以模拟收发丢包。

sudo tc qdisc replace dev wlp58s0 root netem loss 10%

关于timer_list修改的延伸阅读
https://lwn.net/Articles/735887/
timer: Prepare to change timer callback argument type

CATALOG
  1. 1. 定时器timer_list的变化
    1. 1.1. linux4.15之前
    2. 1.2. linux4.15
    3. 1.3. 代码说明