记录一次内核热补丁制作流程

Posted 行木辛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一次内核热补丁制作流程相关的知识,希望对你有一定的参考价值。

最近接到下游的一个需求,要为适配某网卡修改内核的patch制作热补丁。内核热补丁有较多的约束限制,包括不支持修改数据结构,不允许删除函数内部静态局部变量,不支持头文件修改等等。本次要修改的patch包含了头文件的修改,数据结构的变动,如果要制作热补丁就需要将这些变动尽量移动到.c文件中。本次patch涉及两处数据结构的变化,分别如下:

+++ b/include/net/bonding.h
@@ -173,7 +173,8 @@ struct slave {
     u8     backup:1,   /* indicates backup slave. Value corresponds with
                   BOND_STATE_ACTIVE and BOND_STATE_BACKUP */
            inactive:1, /* indicates inactive slave */
-           should_notify:1; /* indicateds whether the state changed */
+           should_notify:1, /* indicates whether the state changed */
+           should_notify_link:1; /* indicates whether the link changed */
     u8     duplex;
     u32    original_mtu;
     u32    link_failure_count;
@@ -2249,6 +2271,12 @@ struct netdev_notifier_changeupper_info {
         struct net_device *upper_dev; /* new upper dev */
         bool master; /* is upper dev master */
         bool linking; /* is the nofication for link or unlink */
+     void *upper_info; /* upper dev info */
+};

    一个是在结构体中新增一个位域成员,patch中对这个位域成员有读取和写的操作。为保持结构体不变,可以通过位运算从当前字节中获取到该位置的值。对应的获取和写这个位的操作可以用宏来实现,如下所示:

#define SET_SHOULD_NOTIFY_LINK_BIT(pslave, value) \
        (value) == 1 ? (*(char *)(&(pslave)->new_link + 1) |= 0x8) : (*(char *)(&(pslave)->new_link + 1) &= 0x7)
 
#define GET_SHOULD_NOTIFY_LINK_BIT(pslave)  ((*(char *)(&(pslave)->new_link + 1) & 0x8) >> 3)
 

    另外一个结构体是新增了一个指向void类型的指针,搜索该patch所有引用这个结构体的地方,发现所有使用该结构体时,在函数中传的参数为该结构体的第一个成员,而不是整个结构体。如下所示。

void netdev_upper_dev_unlink(struct net_device *dev,
                 struct net_device *upper_dev)
{
        struct netdev_notifier_changeupper_info changeupper_info;   
        changeupper_info.upper_dev = upper_dev;
        changeupper_info.master = master;
        changeupper_info.linking = true;
+      changeupper_info.upper_info = upper_info;
+      ret = call_netdevice_notifiers_info(NETDEV_PRECHANGEUPPER, dev,
+                       &changeupper_info.info);

这样就可以通过新增一个与netdev_notifier_changeupper_info 结构体相同的结构体定义, 将新增的成员upper_info传入即可,并且可以保持与原有的代码保持兼容,上述修改可以改为:

void netdev_upper_dev_unlink(struct net_device *dev,
                 struct net_device *upper_dev)
{
        struct netdev_notifier_changeupper_info_ext changeupper_info;    
        changeupper_info_ext.upper_dev = upper_dev;
        changeupper_info_ext.master = master;
        changeupper_info_ext.linking = true;
+      changeupper_info_ext.upper_info = upper_info;
+      ret = call_netdevice_notifiers_info(NETDEV_PRECHANGEUPPER, dev,
+                       &changeupper_info_ext.info);

保持原有结构体netdev_notifier_changeupper_info 不变,新增一个netdev_notifier_changeupper_info_ext结构体。

     若要不修改结构体的定义,那么就要满足在使用这个新增成员时,可以通过原有结构体找到该成员,并且要保证所有调用的函数参数接口不变化,一般来讲要保证这些比较困难,但是netdev_notifier_changeupper_info这个结构体的原有定义,以及原有的使用方式,可扩展型很好,就比较方便修改为结构体不变化的形式,满足制作内核热补丁的条件。

     解决了这两个问题后,用这个patch制作热补丁,但热补丁没有制作成功,经过专家定位,主要问题在于,同一个patch中的修改涉及到编译到内核模块的,和编译到内核的。需要把它们进行拆分,要每个patch可以单独编译通过,完成独立的一块功能。并且还要保证修改的头文件不能又被内核模块调用,又被内核调用,否则头文件也要拆分到.c中。后面经过了各种拆分,把大量的在头文件中,或者在.c文件中定义的函数,或函数实现,挪到了引用的.c中,费了较大力气才把这个补丁给做出来了,由于拆分的代码太多,有太多的函数挪用,就给这个修改带来了较大的功能和可靠性的风险。 

    所以制作热补丁的方案还需要继续研究,如果后面能有更好的制作热补丁的方式,可以支持结构体变化,支持头文件变化,那么对内核的修改和调试将会大大缩短开发时间。

以上是关于记录一次内核热补丁制作流程的主要内容,如果未能解决你的问题,请参考以下文章

热补丁的全面调研

内核热补丁,真的安全么?

Linux内核热补丁方案对比

内核热补丁,真的安全么?

android中的热更新

[硬货分享] Linux 4.1 内核热补丁成功实践