定时器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; 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