对于长期使用Linux的童鞋来说,不说有没有打过补丁,至少这个词大家并不陌生,下面我们通过一个实例来说说:

前几天接触了TQ3358这块天嵌的ARM板子,想给它装个实时Linux并做测试,在自带的光盘中我找到了“Kernel_3.2_TQ3358_for_Linux_v1.2” 这样一个内核版本(从Makefile中我们可以看到这是个 3.2.0 版本的内核),我下载了实时补丁“ patch-3.2.6-rt13.patch ”(因为官方的内核3.2和3.2.6好像没什么区别,所以我们使用了这个补丁),并下载了Kernel-3.2.6 官方内核(用于在打补丁出问题时查看并判断)。

注:对于一个实时补丁对应的kernel.org官网上有两个文件我们需要下载,比如说我们的这个3.2.6版本,我们需要下载一个patch-3.2.6-rt13.patch.bz2和patches-3.2.6-rt13.tar.bz2,前者用于打补丁后者用于分析学习补丁。

所以在整个工作开始前我们有下面四个文件:

$ ls
Kernel_3.2_TQ3358_for_Linux_v1.2    linux-3.2.6    patch-3.2.6-rt13.patch      patches

1. 首先我们试打补丁

在打补丁时使用 --dry-run 参数就会试运行打补丁的过程(并没有真正的改变文件内容)!在内核文件 linux/Documentation/applying-patches.txt 中这样介绍:“--dry-run which causes patch to just print a listing of what would happen, but doesn't actually make any changes” (使用 --dry-run 会打印一系列在打补丁时要发生的事,但是不会真正的作任何改变!)

我们使用如下命令试打这个补丁并将输出记录到 /tmp/log 文件中:

patch  -p1 --dry-run <../patch-3.2.6-rt13.patch >/tmp/log

打开 /tmp/log 文件我们会发现里面又出现很多类似于下面这样的Hunk:

patching file kernel/sched.c
Hunk #1 succeeded at 190 (offset 1 line).
Hunk #2 succeeded at 943 (offset 1 line).
Hunk #3 succeeded at 1283 (offset 1 line).
Hunk #4 succeeded at 2571 (offset 1 line).
Hunk #5 succeeded at 2656 (offset 1 line).
Hunk #6 succeeded at 2833 (offset 1 line).
Hunk #7 succeeded at 2909 (offset 1 line).
Hunk #8 succeeded at 2925 (offset 1 line).
Hunk #9 succeeded at 3211 (offset 1 line).
......

那么对于这样的Hunk 我们的补丁会不会有什么影响呢?会不会因为有Hunk 打不上补丁呢?这就要我们根据每个Hunk指定的文件来判断。

2. 简介 Linux 补丁的原理

我们使用vim 打开 patch 文件,就会发现patch 文件中充满了这样的结构:

Index: linux-3.2/arch/x86/kernel/apic/apic.c
===================================================================
--- linux-3.2.orig/arch/x86/kernel/apic/apic.c
+++ linux-3.2/arch/x86/kernel/apic/apic.c
@@ -876,8 +876,8 @@void __irq_entry smp_apic_timer_interrup
     * Besides, if we don't timer interrupts ignore the global
     * interrupt lock, which is the WrongThing (tm) to do.
     */
-   exit_idle();
    irq_enter();
+   exit_idle();
    local_apic_timer_interrupt();
    irq_exit();

...

那么聪明的你应该大致能猜出来这是什么个结构:

首先使用 Index: linux-3.2/arch/x86/kernel/apic/apic.c 指明下面的这段修改是什么文件中的;

再用 “--- linux-3.2.orig/arch/x86/kernel/apic/apic.c
+++ linux-3.2/arch/x86/kernel/apic/apic.c” 这么两行标注一小段补丁,这两行称之为补丁头,---开头的那行 表示旧文件,+++开头的那行 表示新文件。感觉这两行和Index 那行有点冗余!

接下来是补丁内容:@@ -876,8 +876,8 @@ 用于表述补丁所要修改的代码位于这个文件中的第几行,void __irq_entry smp_apic_timer_interrup用于指定所要修改的代码在哪个函数里面。 如上面这个补丁在文件中的状况为:

 865 void __irq_entry smp_apic_timer_interrupt(struct pt_regs *regs)    // 补丁>所要修改的函数函数866 {...876      * Besides, if we don't timer interrupts ignore the global     // 补丁>起始位置,876行877      * interrupt lock, which is the WrongThing (tm) to do.878      */879     exit_idle();               // 现在exit_idle();是在irq_enter();之前的,>我们可以看到这个补丁所要做的其实就是把它放到irq_enter();后面去880     irq_enter();881     local_apic_timer_interrupt();882     irq_exit();883 884     set_irq_regs(old_regs);885 }886 887 int setup_profiling_timer(unsigned int multiplier)

Linux的补丁内容还包括三个部分:修改语句前三句 + 修改语句 + 修改语句后三句。

我们可以看到在正式修改的语句 -   exit_idle();之前有三句正常的代码,这是用来给补丁定位的,因为单纯只靠行数来确定补丁修改的位置是不够的,作为开源软件,Linux系统内的代码可能会被修改,所以得在补丁之前添加三句代码用来给补丁定位。

我们还能看到在+   exit_idle();语句的后面还有三句正常的代码,这是补丁结束的标志,也需要匹配以便检查我们的补丁是否合适!

3. 对Hunk的分析

好了,现在我们已经知道了Linux的补丁是如何运作的,那么我们可以根据上面使用 --dry-run 记录的打补丁记录来分析里面出现的所有Hunk,比如上面提到的:

patching file kernel/sched.c
Hunk #1 succeeded at 190 (offset 1 line).
Hunk #2 succeeded at 943 (offset 1 line).
Hunk #3 succeeded at 1283 (offset 1 line).
Hunk #4 succeeded at 2571 (offset 1 line).
Hunk #5 succeeded at 2656 (offset 1 line).
Hunk #6 succeeded at 2833 (offset 1 line).
Hunk #7 succeeded at 2909 (offset 1 line).
Hunk #8 succeeded at 2925 (offset 1 line).
Hunk #9 succeeded at 3211 (offset 1 line).
......

这很有可能就是在当前打补丁的文件中在前面有一个空白行,导致后面所有的补丁都有(offset 1 line) !

我们可以在补丁里面打开这个文件(kernel/sched.c)的补丁,我们可以看到第一个补丁是:

@@ -189,6 +189,7 @@ void init_rt_bandwidth(struct rt_bandwid

hrtimer_init(&rt_b->rt_period_timer,
            CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+   rt_b->rt_period_timer.irqsafe = 1;
    rt_b->rt_period_timer.function = sched_rt_period_timer;
 }

所以我们知道,这个补丁是靠 一个空白行加上hrtimer_init函数占用的两行作为定位,并且空白行位于第189行。

下面我们打开kernel/sched.c 打开这个发生Hunk 的文件,跳转到第189 行,我们可以看到:

 189     raw_spin_lock_init(&rt_b->rt_runtime_lock);190 191     hrtimer_init(&rt_b->rt_period_timer,192             CLOCK_MONOTONIC, HRTIMER_MODE_REL);193     rt_b->rt_period_timer.function = sched_rt_period_timer;194 }

这也应证了我们的猜想,补丁中用于定位的空白行应该在第189行,而在这个内核中的第189行并不是空白行,补丁自动上下找,在190~192这三行找到对应的定位,并试打补丁,前三句加后三句都正常,所以这个补丁虽然有 1 行的误差,但是正常了!所以 “ Hunk #1 succeeded at 190 (offset 1 line). ”提示这里虽然有Hunk但是是succeeded的!在这个文件的第一个补丁就有一个偏移,所以后面的所有的补丁都有一行偏移,这也解释了为什么这个文件那么多Hunk的原由!

同样,我们可以看到:

patching file kernel/fork.c
Hunk #2 succeeded at 212 (offset 16 lines).
Hunk #3 succeeded at 568 (offset 16 lines).
Hunk #4 succeeded at 1063 (offset 16 lines).
Hunk #5 succeeded at 1174 (offset 16 lines).
Hunk #6 succeeded at 1236 (offset 16 lines).

这样的偏移有十六行的总不能有十六个空白行吧?大家应该猜到了这样大的偏移应该是程序猿在这个文件里添加了一个函数或者类似的!

其实,在内核中这样的Hunk succeeded 都没多大问题,虽然有Hunk但是都正常了!但是如果是作产品级开发,我们不能放过任何一个Hunk,我们需要的是团队合作 + 分析patch log中所有的Hunk + 准确的记录(包括这个Hunk的原由,重不重要等)!

那么Hunk FAILED呢?我们在这个log中找到了这样几行:

patching file arch/arm/kernel/process.c
Hunk #1 FAILED at 214.
Hunk #2 succeeded at 612 (offset 121 lines).
1 out of 2 hunks FAILED -- saving rejects to file arch/arm/kernel/process.c.rej

这里有个Hunk FAILED,说明补丁的前三句和后三句定位出问题了,按照常理,我们先找到相应的文件补丁:

--- linux-3.2.orig/arch/arm/kernel/process.c
+++ linux-3.2/arch/arm/kernel/process.c
@@ -214,9 +214,7 @@ void cpu_idle(void)
        }
        leds_event(led_idle_end);
        tick_nohz_restart_sched_tick();
-       preempt_enable_no_resched();
-       schedule();
-       preempt_disable();
+       schedule_preempt_disabled();
    }
 }

我们发现当前这个补丁是中规中矩的“前三+所做修改+后三”的格式,那么为什么会失败呢?赶紧打开内核中的这个文件一探究静:打开arm/kernel/process.c文件,找到 cpu_idle函数,我们发现所要删除的前三句和补丁中的不一样而且位置也有所偏移:

254         }
255         tick_nohz_restart_sched_tick();
256         idle_notifier_call_chain(IDLE_END);
257         preempt_enable_no_resched();
258         schedule();
259         preempt_disable();
260     }
261 }

这应该是这个文件经过厂家修改,所以我们预先准备好的官方 kernel-3.2.6就用上了: 使用 vimdiff arch/arm/kernel/process.c ../linux-3.2.6/arch/arm/kernel/process.c查看两个文件之间的区别,并找到我们补丁对应的位置:

我们可以看到官方内核中的代码对应补丁是前三句后三句都匹配的,而这个内核中的是前三句完全不匹配,从而导致patch的失败!

对于这样的情况,我们要做的工作就是调查这个补丁对应的所有文档:包括整个补丁包中还有没有类似补丁;包括 patches 里对应的补丁说明;包括在 linux-stable-rt 的 git 仓库中使用git blame 查明是谁、为什么要做这个修改。

如果你自己是在闹不明白,可以上邮件列表或者直接给补丁的作者发邮件问清楚这个补丁是干什么的?为什么需要?为什么在实时内核中需要在普通内核中不需要?在这个文件中的这个补丁会引发多大的性能损失?...

在做嵌入式开发时、在做系统内核的调研时要多做查询、多作测试才能做好产品,才能做好学习!

4. 真正的打上补丁并对rej 文件处理

经过查资料 + 调研 + 测试,你会发现这个补丁是否对你有用,我在这说明这个补丁是对RT-Linux 很重要的补丁:

 1462 +/**1463 + * schedule_preempt_disabled - called with preemption disabled1464 + *1465 + * Returns with preemption disabled. Note: preempt_count must be 11466 + */1467 +void __sched schedule_preempt_disabled(void)1468 +{1469 +   __preempt_enable_no_resched();1470 +   schedule();1471 +   preempt_disable();1472 +}

我们可以看到在补丁中的 linux-3.2/kernel/sched.c 的实时补丁中使用schedule_preempt_disabled();函数将三句“    preempt_enable_no_resched();         schedule();         preempt_disable(); ” 封装在一起。那么这个封装对实时系统的抢占很重要么? 这是一个大的补丁,在这整个大的补丁中对于所有的这三句都进行替换,所以这是个对实时内核很重要的一个修改。(注:至此的判断已经足矣让我们在失败的补丁中确定这个修改的重要性!但是如果你想更好的学习内核,你需要继续找它的用途知道原理~本文不详述)

所以我们下们进行正式的打补丁: patch  -p1  <../patch-3.2.6-rt13.patch >/tmp/log1   (去掉了 --dry-run 参数)

我们会在日志中发现与上面一样的Hunk FAILED:

patching file arch/arm/kernel/process.c
Hunk #1 FAILED at 214.
Hunk #2 succeeded at 612 (offset 121 lines).
1 out of 2 hunks FAILED -- saving rejects to file arch/arm/kernel/process.c.rej

因为我们已经确定了这个failed 可能对内核产生实时性能的影响,所以我们可以去 arch/arm/kernel/process.c 手动的将    preempt_enable_no_resched();      schedule();   preempt_disable();这三句去掉,添加上     schedule_preempt_disabled(); 。

5. patch学习总结

分析内核补丁是个漫长的过程,如果你是个新手,一个Hunk是需要很久的,整个log 文件可能会花上你一周甚至更久! 但是对于产品级的内核就要达到这样的精准度,字字句句斟酌斟酌再斟酌! 当你是个大牛时,你会发现前面的这个工作是非常重要的更是非常有价值的!

6. 试验自制个补丁

比如说我们目前有这么个Helloworld.c文件:

#include "stdio.h"
int main(int argc ,char **argv)
{
    printf("Hello World");
}

我想给它改成:

#include "stdio.h"
int main(int argc ,char **argv)
{
    printf("Hello World\n");
    return 0;
}

前面的helloworld程序命名hello.c,后面的命名为hello_new.c。

生成补丁需要用这个命令  :  diff -uN from-file to-file >to-file.patch  ,所以我们的这个补丁需要这么用: diff -uN hello.c hello_new.c >patch.test.patch

打开生成的patch.test.patch,我们可以看到:

--- helloworld.c    2013-11-26 20:35:48.736255687 +0800
+++ helloworld1.c   2013-11-26 20:36:10.340255754 +0800
@@ -1,5 +1,6 @@#include "stdio.h"int main(int argc ,char **argv){
-   printf("Hello World");
+   printf("Hello World\n");
+   return 0;}

恩,效果不错!!!了罢此文~~~

=====================================

引用资料:

【1】 Linux 内核文档: linux/Documentation/applying-patches.txt    & patch的 manpage(man patch)

【2】 RT_PREEMPT的维基主页 https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO

【3】 linux下patch命令使用详解---linux打补丁命令  http://www.linuxso.com/command/patch.html

【4】 补丁(patch)的制作与应用  http://linux-wiki.cn/wiki/zh-hans/%E8%A1%A5%E4%B8%81(patch)%E7%9A%84%E5%88%B6%E4%BD%9C%E4%B8%8E%E5%BA%94%E7%94%A8

======================================

注:本文来自 著名的实时系统专家、Safety专家 Nicholas Mc Guire 的课!我这只能算作学习总结,同时也是写给众多学习Linux的童鞋们学习之用,大家好好努力,与我同进步!

谈谈Linux打补丁的原理以及如何判别打补丁的错误 --- 从补丁学内核相关推荐

  1. 谈谈Linux打补丁的原理以及如何判别打补丁的错误 --- 从补丁学内核

    补丁有几种方式: 1. 替换原有的EXE或DLL文件 2. 通过汇编码直接修改原来的EXE或DLL(豪杰就这么干过,太厉害了) 3. 修改注册表或INI文件以支持新格式文件 以上三种方法可单独使用或联 ...

  2. python程序只能使用源代码进行运行吗-谈谈 Python 程序的运行原理

    因为我的个人网站 restran.net 已经启用,博客园的内容已经不再更新.请访问我的个人网站获取这篇文章的最新内容,谈谈 Python 程序的运行原理 这篇文章准确说是『Python 源码剖析』的 ...

  3. 谈谈 Python 程序的运行原理

    谈谈 Python 程序的运行原理 这篇文章准确说是『Python 源码剖析』的读书笔记,整理完之后才发现很长,那就将就看吧. 1. 简单的例子 先从一个简单的例子说起,包含了两个文件 foo.py ...

  4. 从对我的质疑说起,谈谈Linux下的文件删除

    特特本来就是个刚毕业的小菜,很多知识都是靠着大家的指点才慢慢学会的.之前在一篇"纯属虚构"的文章 (鹅厂后台开发工程师的工作日常) 提到使用 rm 命令删除一个近 100 G 的 ...

  5. linux 查看日志_干货 | 名企高频考点之谈谈Linux日志查看方式都有哪些

    点击蓝字关注我哦 以下是本期干货视频视频后还附有文字版本哦 ▼<名企高频考点-谈谈Linux日志查看方式都有哪些>▼ ps:请在WiFi环境下打开,如果有钱任性请随意 0.概述 在我们面试 ...

  6. Linux存储保护,谈谈Linux中的存储保护

    谈谈Linux中的存储保护 以下讨论的内容是以i386平台为基础的 Linux将4G的地址划分为用户空间和内核空间两部分.在Linux内核的低版本中(2.0.X),通常0-3G为用户空间,3G-4G为 ...

  7. python程序运行原理_谈谈 Python 程序的运行原理

    因为我的个人网站 restran.net 已经启用,博客园的内容已经不再更新.请访问我的个人网站获取这篇文章的最新内容,谈谈 Python 程序的运行原理 这篇文章准确说是『Python 源码剖析』的 ...

  8. Linux 原生异步 IO 原理与使用

    目录 什么是异步 IO? Linux 原生 AIO 原理 Linux 原生 AIO 使用 什么是异步 IO? 异步 IO:当应用程序发起一个 IO 操作后,调用者不能立刻得到结果,而是在内核完成 IO ...

  9. Linux 下 TC 命令原理及详解<一>

    文章目录 1 前言 2 相关概念 3 使用TC 4 创建HTB队列 5 为根队列创建相应的类别 6 为各个类别设置过滤器 7 复杂的实例 Linux 下 TC 命令原理及详解<一> Lin ...

最新文章

  1. PostgreSQL11.3 创建用户和创建数据库
  2. linux cmake 多线程 错误 undefined reference to 'pthread_create'
  3. [提示]使用普通用户,通过sealos安装ks,默认还是要通过root用户才能正常使用kubectl等命令
  4. 使用sklearn进行数据预处理 —— 归一化/标准化/正则化
  5. 细说JavaScript对象(1):对象的使用和属性
  6. JavaScript——以简单的方式理解闭包
  7. 训练深度学习_深度学习训练tricks整理1
  8. 实体类blob类型_Mysql的数据类型和JPA的实体类
  9. 力扣回文字串的动态规划解法
  10. cocos 2d CCSprite 触摸识别 非常有用!!!!!
  11. Linux 初始化系统 systemd - journald 日志
  12. Atitit flowable使用总结 目录 1. flowable 1 1.1. 添加依赖 1 1.2. Flowable的启动接口 2 2. 还是使用简单流程来完成业务流程的学习, 2 2.1.
  13. coreseek mysql_coreseek 与 php mysql 的联合使用
  14. 人工智能降噪插件Topaz DeNoise AI
  15. pcm5102a解码芯片音质评测_鱼和熊掌兼得——一台可以换芯片的PCM1794解码评测(上)...
  16. html 调用es2015模块,ES 2015 Modules
  17. 救命啊!还是讨厌的中文问题
  18. 5-ipv6服务器之-dns
  19. macOS无法验证此App不包含恶意软件
  20. java double 类型_关于Java中的double类型数据

热门文章

  1. 回声消除中的LMS和NLMS算法与MATLAB实现
  2. 分布式 PostgreSQL 集群(Citus),官方快速入门教程
  3. 深圳软件测试培训:软件测试的需求评审
  4. PPP与PPPoE的学习
  5. 上次的问题解决啦,重新送上Go ORM 单元测试全流程讲解
  6. 特征工程:时间特征构造以及时间序列特征构造
  7. [收藏]好学若饥 谦卑若愚
  8. TK 技术学习日记(一)
  9. Qt如何改变鼠标形状
  10. 多生产者单消费者捆绑消费问题