怎样学习代码

阅读软件源代码是每个开发者的必由之路,尤其是内核开发者。因为内核开发在很大程度上并不是重新发明轮子,而是深入理解并尽量复用现有的内核设计框架,然后参照相似的功能模块去添加或改写某项需要的功能。在对内核

阅读软件源代码是每个开发者的必由之路,尤其是内核开发者。因为内核开发在很大程度上并不是重新发明轮子,而是深入理解并尽量复用现有的内核设计框架,然后参照相似的功能模块去添加或改写某项需要的功能。在对内核整体框架以及某些子系统融会贯通以后,才有可能站在巨人的肩膀上去改进框架本身,实现自主创新。就我的个人经验来说,阅读代码与编写代码的时间大概是6 : 4。

自由软件的开发与商业软件相比,有一个很大的不同就是文档相对比较缺乏。但同时有一种说法叫做“代码就是最好的文档”——只要你愿意,没什么学不会的。那么,像Linux内核这样动辄上千万行代码的浩大工程,该如何读懂,又从哪里读起?

为此,我梳理了自己在十余年Linux内核相关的工作经验,总结了一套可行的代码阅读方法:基于“广度优先”的大原则,“三部曲”具体实现,理解补丁文件。

➤广度优先原则

建议用“广度优先”的方式阅读代码。

1、学代码要有信心、恒心。2、学代码要由浅入深,从简单到复杂。

怎样学习代码

代码好比一棵树,“广度优先”就是说我们要先找到主干,然后搞清楚主干上有几根树枝,再去某条感兴趣的树枝上寻找有意义的叶子;而“深度优先”则是随便碰到一根树枝,就赶紧深入进去把所有的叶子给找出来。广度优先会让你有一种“会当临绝顶,一览众山小”的自信;而深度优先会让你有一种“不识庐山真面目,只缘身在此山中”的迷茫。

基于“广度优先”的大原则,我们更进一步来详细解析“三部曲”的具体实现方法。“三部曲”依次是:找准入口点,理清主脉络,顾名思义看功能。

➤找准入口点

“三部曲”的第一步是找准入口点。

Linux内核基本上由C语言和汇编语言组成,在C语言里面,大家都知道应用程序的入口点是main()函数,其实内核也是比较类似的。以相对独立的内核模块为例,在绝大多数情况下,如果模块的名字是modname,那么模块的入口函数就是modname_init()。万一不是,可以在相关文件目录里面用module_init进行搜索,因为模块入口一般会用module_init(mod_entry)或者类似的方式进行

那么内核本身的入口又在哪里呢?凭直觉,凭经验,总会跟start、init、entry之类的词汇有些关联。初始入口(第一入口)是体系结构相关的,在汇编语言代码里面;而通用入口(第二入口)是体系结构无关的,在C语言里面——实际上就是init/main.c里面的start_kernel()。顺便说一下,包括龙芯在内的所有MIPS处理器,第一入口都是arch/mips/ kernel/head.S里面的kernel_entry。CPU执行的第一条内核指令就在初始入口kernel_entry,而kernel_entry之后的代码在完成与体系结构高度相关的初始化以后,会跳转到通用入口start_kernel()执行。

➤理清主脉络

“三部曲”的第二步是理清主脉络。这一步的原则是“去粗取精”:去掉没用的,留下有用的。

零基础的人想要写代码首先需要进行一定的学习,了解一些基础的编程知识,选择适合自己的程序语言,之后通过不断的学习就可以写代码。从简单的、直接的几行十几行程序开始,比如计算器;到复杂的小工具,比如大数计算器。这个过。

眼不见为净,影响阅读的直接统统删除。当然了,我们并不是建议在原始代码库中直接删除,而是建议一边看代码一边做笔记,先把函数复制到笔记里面,然后再根据需要逐步删除,直到留下主脉络。

那么什么叫“有用”的,什么叫“没用”的?既然已经进入了内核源代码库,当然每一句都是有用的,这里所说的有用或者没用,仅仅指的是对理解主脉络有利还是不利。而“有利”或者“不利”又是分多个层次的。

1.代码 vs. 注释

注释是非常有用的,可以帮助我们理解代码,但是注释在很大程度上是用于了解细节的,而在对主脉络的了解上面,用处不大。所以首先考虑的就是去掉注释,包括//…和/*…*/格式的直接注释,以及endif格式的条件汇编。

如果想学习代码,没有任何基础,你可以选择学习Python课程。Python是一种高级编程语言,易于理解,易于使用,适合0基础人员学习。是初学者的首选。Python是人工智能的首选编程语言。学习后,你可以从事多种工作。1、Office中自带。

2.程序流程 vs. 变量声明

去掉注释以后,纯粹的代码会变得清爽很多。但是如果依旧十分复杂,影响阅读,那么就可以删除一些变量

3.功能语句 vs. 调试语句

如果到了这里主脉络依旧不是十分清晰,那么可以开始考虑去掉各种调试和打印语句,比如printf()、printk()、debug()等。

4.正常流程 vs. 异常流程

异常处理是程序健壮性必不可少的部分,但是如果异常处理本身代码过于复杂,那它势必会影响可读性。因此在必要的时候,为了方便阅读,可以去掉返回值检查、try-catch中的catch子句等。

5.常见路径 vs. 罕见路径

新手学代码编程应该学习语法和基础理论。选择一种程序设计语言开始学习。有很多编程语言,如 python、 c、 c++、 Java等。不过,我建议新手朋友还是最好学习 python,因为 python具有更好的兼容性,然后代码简单,很适合作为一。

通常情况下,代码精简到这一步就已经比较容易理解了,但如果有必要,可以对switch-case、if-else等结构进行处理,只保留最常见的一种情况。

下面举一个实际的例子,代码来源是GXemul。这是一个模拟MIPS(包括龙芯)机器的模拟器软件,代码入口为main()。相比于Linux内核的庞大复杂,GXemul是一个设计精巧的应用程序,非常适合用来举例。(PS:这是一个长达106行的函数~)

int main(int argc,char *argv[]) { /*Setting constants:*/ const int constant_yes = 1; const int constant_true = 1; const int constant_no = 0; const int constant_false = 0; struct emul **emuls; char **diskimages = NULL; int n_diskimages = 0; int n_emuls; int i; progname = argv[0]; /*Initialize all emulator subsystems:*/ console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } /*Allocate space for a simple emul setup:*/ n_emuls = 1; emuls[0] = emul_new(NULL); if (emuls[0] == NULL) { fprintf(stderr,&34;); exit(1); } get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); if (!skip_srandom_call) { struct timeval tv; gettimeofday(&tv,NULL); srandom(tv.tv_sec ^ getpid() ^ tv.tv_usec); } /*Print startup message:*/ debug(&34;); debug(&34;); debug(&34;); if (emuls[0]->machines[0]->machine_type == MACHINE_NONE) { n_emuls --; } else { for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); } /*Simple initialization,from command line arguments:*/ if (n_emuls > 0) { /*Make sure that there are no configuration files as well:*/ for (i=1; i<argc; i++) if (argv[i][0] == &39;) { fprintf(stderr,&34; &34; &34; &34; &34;); exit(1); } /*Initialize one emul:*/ emul_simple_init(emuls[0]); } /*Initialize emulations from config files:*/ for (i=1; i<argc; i++) { if (argv[i][0] == &39;) { char tmpstr[50]; char *s = argv[i] + 1; if (strlen(s) == 0 && i+1 < argc && argv[i+1][0] != &39;) { i++; s = argv[i]; } n_emuls ++; emuls = realloc(emuls,sizeof(struct emul *) * n_emuls); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } /*Always allow slave xterms when using multiple emulations:*/ console_allow_slaves(1); /*Destroy the temporary emuls[0],since it will be overwritten:*/ if (n_emuls == 1) { emul_destroy(emuls[0]); } emuls[n_emuls - 1] = emul_create_from_configfile(s); snprintf(tmpstr,sizeof(tmpstr),&34;,n_emuls-1); } } if (n_emuls == 0) { fprintf(stderr,&34; &34; &34; &34; &34;,progname); exit(1); } device_set_exit_on_error(0); console_warn_if_slaves_are_needed(1); /*Run all emulations:*/ emul_run(emuls,n_emuls); /* *Deinitialize everything: */ console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

这是一个长达106行的函数,下面我们开始精简!

1、根据“三部曲”精简原则,我们首先去掉其中的注释。还剩下92行:

int main(int argc,char *argv[]) { const int constant_yes = 1; const int constant_true = 1; const int constant_no = 0; const int constant_false = 0; struct emul **emuls; char **diskimages = NULL; int n_diskimages = 0; int n_emuls; int i; progname = argv[0]; console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } n_emuls = 1; emuls[0] = emul_new(NULL); if (emuls[0] == NULL) { fprintf(stderr,&34;); exit(1); } get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); if (!skip_srandom_call) { struct timeval tv; gettimeofday(&tv,NULL); srandom(tv.tv_sec ^ getpid() ^ tv.tv_usec); } debug(&34;); debug(&34;); debug(&34;); if (emuls[0]->machines[0]->machine_type == MACHINE_NONE) { n_emuls --; } else { for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); } if (n_emuls > 0) { for (i=1; i<argc; i++) if (argv[i][0] == &39;) { fprintf(stderr,&34; &34; &34; &34; &34;); exit(1); } emul_simple_init(emuls[0]); } for (i=1; i<argc; i++) { if (argv[i][0] == &39;) { char tmpstr[50]; char *s = argv[i] + 1; if (strlen(s) == 0 && i+1 < argc && argv[i+1][0] != &39;) { i++; s = argv[i]; } n_emuls ++; emuls = realloc(emuls,sizeof(struct emul *) * n_emuls); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } console_allow_slaves(1); if (n_emuls == 1) { emul_destroy(emuls[0]); } emuls[n_emuls - 1] = emul_create_from_configfile(s); snprintf(tmpstr,sizeof(tmpstr),&34;,n_emuls-1); } } if (n_emuls == 0) { fprintf(stderr,&34; &34; &34; &34; &34;,progname); exit(1); } device_set_exit_on_error(0); console_warn_if_slaves_are_needed(1); emul_run(emuls,n_emuls); console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

2、删除注释以后仍然很长,所以我们开始第二次精简,去掉变量

int main(int argc,char *argv[]) { console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } emuls[0] = emul_new(NULL); if (emuls[0] == NULL) { fprintf(stderr,&34;); exit(1); } get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); if (!skip_srandom_call) { gettimeofday(&tv,NULL); srandom(tv.tv_sec ^ getpid() ^ tv.tv_usec); } debug(&34;); debug(&34;); debug(&34;); if (emuls[0]->machines[0]->machine_type == MACHINE_NONE) { n_emuls --; } else { for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); } if (n_emuls > 0) { for (i=1; i<argc; i++) if (argv[i][0] == &39;) { fprintf(stderr,&34; &34; &34; &34; &34;); exit(1); } emul_simple_init(emuls[0]); } for (i=1; i<argc; i++) { if (argv[i][0] == &39;) { if (strlen(s) == 0 && i+1 < argc && argv[i+1][0] != &39;) { i++; s = argv[i]; } n_emuls ++; emuls = realloc(emuls,sizeof(struct emul *) * n_emuls); if (emuls == NULL) { fprintf(stderr,&34;); exit(1); } console_allow_slaves(1); if (n_emuls == 1) { emul_destroy(emuls[0]); } emuls[n_emuls - 1] = emul_create_from_configfile(s); snprintf(tmpstr,sizeof(tmpstr),&34;,n_emuls-1); } } if (n_emuls == 0) { fprintf(stderr,&34; &34; &34; &34; &34;,progname); exit(1); } device_set_exit_on_error(0); console_warn_if_slaves_are_needed(1); emul_run(emuls,n_emuls); console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

3、现在看起来比较清爽了,但是仍然不够,因此我们进行第三轮精简,去掉各种调试和打印语句,还剩下52行:

int main(int argc,char *argv[]) { console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); emuls[0] = emul_new(NULL); get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); if (!skip_srandom_call) { gettimeofday(&tv,NULL); srandom(tv.tv_sec ^ getpid() ^ tv.tv_usec); } if (emuls[0]->machines[0]->machine_type == MACHINE_NONE) { n_emuls --; } else { for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); } if (n_emuls > 0) { for (i=1; i<argc; i++) if (argv[i][0] == &39;) { exit(1); } emul_simple_init(emuls[0]); } for (i=1; i<argc; i++) { if (argv[i][0] == &39;) { if (strlen(s) == 0 && i+1 < argc && argv[i+1][0] != &39;) { i++; s = argv[i]; } n_emuls ++; emuls = realloc(emuls,sizeof(struct emul *) * n_emuls); console_allow_slaves(1); if (n_emuls == 1) { emul_destroy(emuls[0]); } emuls[n_emuls - 1] = emul_create_from_configfile(s); } } if (n_emuls == 0) { exit(1); } device_set_exit_on_error(0); console_warn_if_slaves_are_needed(1); emul_run(emuls,n_emuls); console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

4、一般来说,超过一屏的函数或多或少都会影响可读性,因此我们需要进行第四轮精简,去掉各种异常处理语句,还剩下43行:

int main(int argc,char *argv[]) { console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); emuls[0] = emul_new(NULL); get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); if (!skip_srandom_call) { gettimeofday(&tv,NULL); srandom(tv.tv_sec ^ getpid() ^ tv.tv_usec); } if (emuls[0]->machines[0]->machine_type == MACHINE_NONE) { n_emuls --; } else { for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); } if (n_emuls > 0) { emul_simple_init(emuls[0]); } for (i=1; i<argc; i++) { if (argv[i][0] == &39;) { if (strlen(s) == 0 && i+1 < argc && argv[i+1][0] != &39;) { i++; s = argv[i]; } n_emuls ++; emuls = realloc(emuls,sizeof(struct emul *) * n_emuls); console_allow_slaves(1); if (n_emuls == 1) { emul_destroy(emuls[0]); } emuls[n_emuls - 1] = emul_create_from_configfile(s); } } emul_run(emuls,n_emuls); console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

对于一个熟练的开发者来说,该函数的逻辑精简到了这个状态以后已经比较清晰了(可以到此为止)。但如果是初次接触的话,还是相对显得有点复杂。

5、让我们来进行第五轮精简,去掉那些不常用的、罕见的代码路径,剩下18行:

int main(int argc,char *argv[]) { console_init(); cpu_init(); device_init(); machine_init(); timer_init(); useremul_init(); emuls = malloc(sizeof(struct emul *)); emuls[0] = emul_new(NULL); get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages); for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]); if (n_emuls > 0) emul_simple_init(emuls[0]); emul_run(emuls,n_emuls); console_deinit(); for (i=0; i<n_emuls; i++) emul_destroy(emuls[i]); return 0;}

这就是最终剩下的主脉络!非常清晰明了,那么,这个函数到底在干什么呢?让我们开始“三部曲”的第三步。

➤顾名思义看功能

前面我们提到,代码就像一棵树,因此本文选用一种树形视图来表示函数。依旧以GXemul为例,前面经过五轮精简的main()函数,稍作处理后可以按下面的方法表示:

main()|-- console_init();|-- cpu_init();|-- device_init();|-- machine_init();|-- timer_init();|-- useremul_init();|-- emuls[0] = emul_new(NULL);|-- get_cmd_args(argc,argv,emuls[0],&diskimages,&n_diskimages);|-- for (i=0; i<n_diskimages; i++) diskimage_add(emuls[0]->machines[0],diskimages[i]);|-- emul_simple_init(emuls[0]);|-- emul_run(emuls,n_emuls);||-- console_init_main(emuls[0]);||-- for (j=0; j<e->n_machines; j++) cpu_run_init(e->machines[j]);||-- timer_start();||-- for (j=0; j<e->n_machines; j++) machine_run(e->machines[j]);||-- timer_stop();||-- for (j=0; j<e->n_machines; j++) cpu_run_deinit(e->machines[j]);|\-- console_deinit_main();|-- console_deinit();\-- emul_destroy(emuls[i]);

其中main()函数为根节点,五轮精简后的每一行为根节点的下级节点,进一步展开感兴趣的下级节点(如树形视图中的emul_run()函数),可以得到更下一级的叶子节点。树形视图中的每行代码,甚至不需要看其实现细节,光靠“顾名思义”就能大概知道其功能了。比如console_init()是控制台初始化,cpu_init()是处理器初始化,device_init()是设备初始化,machine_init()是机器架构初始化,timer_init()是时钟初始化,useremul_init()是用户模拟器初始化,diskimage_add()是添加磁盘设备,emul_simple_init()是模拟机器的初始化,而emul_run()显然是核心中的核心,即模拟器运行的主事件循环(通过进一步展开的下级节点也证实了这一点)。

树形视图是一种自顶向下鸟瞰全局的宏观视图,但有时候我们特别希望能有一种方法能够清晰地表述某个很深的函数调用,因此作为补充,本文推荐一种叫链式视图的表示法。比如Radeon显卡驱动中的模式设置函数drm_crtc_helper_set_mode(),在首次模式设置中从驱动入口函数开始一路向下的调用链展示如下:

这是一条很长的调用链,非常具有典型意义。

在很多解析源代码的书籍中,都会使用流程图来描述代码逻辑。然而,流程图虽然直观,但是其描述能力有限(尤其是缺乏树形源代码视图的层次化表达能力),往往很难精确描述一个函数的执行过程。而一个费尽心机画出来的精确的流程图,往往又会因为其复杂性而失去了直观的功能。并且,单靠流程图并不能完全理解源代码,而是需要将源代码与流程图两相对照。

因此用精简版的源代码(即树形视图和链式视图)来代替流程图,一方面可以快速理解多级函数的复杂调用关系,另一方面可以不需要在源代码和流程图之间反复切换。

企业回软件测试的基础知识(黑盒测试,白盒测试,单元测试,系统测试) 软件测试的基本工具(测试管理工具,自动化测试工具,性能测试工具) 其他: 一定的编程知识是需要的 还需要数据库,中间件,网络协议 CMI等软件工程的理论也是重要的。想了解更。

➤理解补丁文件

阅读软件源代码,尤其是Linux内核源代码时,不可避免地要接触补丁文件,那么什么是补丁文件呢?其实,补丁文件就是一个变更集,它描述了源代码从旧版本到新版本之间的差异变化,或者更一般地说,描述了源代码从一个状态变到另一个状态的差异(不一定是从旧版本到新版本)。

如果用数学方法来表达,就是:

公式1:源代码差异 = 源代码状态B – 源代码状态A

也可以反过来表达:

公式2:源代码状态A + 源代码差异 = 源代码状态B

举个具体的例子,假设当前目录下有两个子目录linux-4.4.1和linux-4.4.2,分别是Linux-4.4.1版本和Linux-4.4.2版本的源代码顶级目录。那么可以用diff命令来执行公式1的过程,导出一个变更集(即源代码差异)到补丁文件kernel.patch:

diff -Naurp linux-4.4.1 linux-4.4.2 > kernel.patch

接下来可以先进入linux-4.4.1目录,用patch命令来执行公式2的过程,通过应用补丁文件kernel.patch将Linux-4.4.1的源代码状态变成跟Linux-4.4.2一致:

patch -p1 < kernel.patch

上面是对补丁文件的正向应用,使源代码状态A变成源代码状态B。实际上补丁文件还可以反向应用,使源代码状态从B变成源代码状态A。比如先进入linux-4.4.2目录,然后通过反向应用补丁文件kernel.patch将Linux-4.4.2的源代码状态变成跟Linux-4.4.1一致:

patch -Rp1 < kernel.patch

利用两个目录来保存两个版本的内核源代码,使用diff和patch命令来操作补丁文件的做法是一种非常原始的方法。通常在内核开发中我们推荐使用Git做版本管理工具。Git可以记录源代码变化的版本历史,可以回滚到任意一个历史状态,也可以导出两个版本之间的变更集(即源代码差异)。

图1是一个Git历史记录的示例(用git log命令查看)。

图1 Linux内核源代码的Git历史记录示例

图中用节点和线条来描述历史演进关系,每个节点是代表一个完整的源代码状态(即某一个版本的完整源代码)。在Git的术语里面一个版本节点称之为一个commit,用一个40位十六进制数的哈希值来表达。Git里面有分支的概念,历史记录是允许分叉合并的,也就是说可以有多条历史线同时演进。

在Git里面我们有更先进的方法导出和应用补丁文件(commit1和commit2代表两次commit的散列值,散列值也叫哈希值,即Hash的音译):

导出补丁(公式1):git diff commit1 commit2 > kernel.patch

应用补丁(公式2):git apply kernel.patch

这两个git命令导出和应用的补丁称之为简单格式补丁,它与diff/patch命令所操作的补丁具有相同的格式。但git还可以操作更加强大的正规格式补丁(commit1和commit2代表两次commit的散列值):

应用补丁(公式2):git am kernel_patch_dir/*.patch

这两个命令中,如果commit1和commit2相邻,就会导出一个补丁;如果不相邻,就会导出一系列补丁。这些补丁保存在目录kernel_patch_dir中,按版本从早到晚(从旧到新)的顺序,以0001-xxx-yyy.patch,0002-xxx-yyy.patch的格式逐个命名。正规格式补丁导出以后可以直接以电子邮件的形式发送出去,而应用正规格式补丁的同时会自动提交到代码库。图2是一个正规格式补丁的具体示例。

图2 一个正规格式的补丁文件内容

一个正规格式的补丁内容包括4大部分:头部信息、描述信息、正文区和脚注区。图2中补丁的头部信息指的是前4行,包含了作为电子邮件的Commit编号、发送人、补丁日期和邮件标题(邮件标题同时也是Commit标题)。描述信息指的是图中第一个空白行以后,第一个分割线之前的部分,包括补丁内容描述(补丁内容描述同时也是Commit描述)和作者签名(Signed-of-by开头的两行,如有必要还可以加上审查者签名Reviewed-by、确认者签名Acked-by、报告者签名Reported-by、测试者签名Tested-by等)。接下来从第一个分割线开始到最后一个分割线之前的部分都是正文区,这是最重要的一部分,即补丁的主体部分(简单格式补丁只有正文部分)。最后的脚注区就是git的版本号标识。

那么图2中这个补丁究竟包含了什么信息呢?现在我们可以知道了:

dev_info.gart_page_size = AMDGPU_GPU_PAGE_SIZE;

同时又在原来的位置增加了一行代码(实质上就是修改了一行代码):

dev_info.gart_page_size = max((int)PAGE_SIZE,AMDGPU_GPU_PAGE_SIZE);

实际的内核开发过程中,代码补丁往往比这个例子要复杂很多,但是其原理是相同的。理解了补丁文件的原理,在阅读源代码及其变更历史的时候就会如虎添翼。例如,我们已经知道Linux-3.15版本的内核加入了龙芯3A的支持,但是如果直接查看Linux-3.15的完整源代码,你会发现相关的代码延伸非常广泛,遍及到多个子系统,几十个目录,上百个文件。面对这种情况,想要在一个早期的内核版本上移植相同的功能,简直无从下手。那么,如何才能“干净利落”又“完整无缺”地分离出那些跟龙芯3号有关的一项项功能呢?答案就是:查看git记录,导出系列补丁,然后按顺序逐个分析解读。在理解这一系列补丁的基础上,如果你需要在一个早期的内核版本(如Linux-3.12)上添加龙芯3号的支持,并不会是一件非常困难的事情。

结语

怎样学习代码

基于“广度优先”的大原则,首先找准代码的入口点,从源头开始梳理代码框架;其次理清主脉络,通过精简代码,只保留源码最核心的内容;最终可根据函数名了解各个模块的功能,厘清代码的实现逻辑,同时为了使得阅读更加方便,还介绍了树形视图和链式视图。通过结合具体的案例,本文详细解析了“三部曲”的具体实现方法,并且介绍了补丁文件的阅读方式。

这一套方法不仅贯穿在我的实际工作中,也是《用“芯”探核:基于龙芯的linux内核探索解析》一书中绝大部分代码解析的方法,使得本书更方便读者阅读和理解。

上一篇 2022年12月18 21:29
下一篇 2023年01月23 08:42

相关推荐

  • 逆矩阵怎么求,3x3矩阵怎么求逆矩阵

    元旦假期即将到来,股市休市,闲钱放着也是闲着,利用闲钱来捡钱啦!所谓每逢佳节发红包,受流动性影响,节假日资金价格可能走高,操作一把国债逆回购这个无风险收益,挣点过节红包,算是股民的一个小福利。结合20

    2023年02月04 262
  • 雅思准考证怎么打印,雅思准考证6页都要打印嘛

    什么时候打印准考证雅思考试—纸笔考生在笔试日期前10天打印准考证雅思考试—机考考生在笔试日期前3天打印准考证注意事项直接打黑白打印即可。雅思官方考试需要两份准考证,打印一张中文一张英文的准考证就行了,

    2023年02月07 280
  • 打印标题行怎么设置,excel表里面每行带标题打印

    如果一张工作表中的数据过多,需要分多页打印,那么默认只会在第一页显示表格的标题行。如下图所示:如果想要在每一页中都显示标题行,可以设置多页时始终显示标题行。选择【页面布局】选项卡,点击【页面设置】组的

    2023年01月22 297
  • 是怎么回事,那咋回事

    近日,市民林先生手拎月饼袋走进了大涌旗山派出所难道是要给警察送月饼?林先生的举动先是把民警愣住了然而,了解真相后的民警却收下了月饼袋这究竟是怎么回事呢?还有一个重要原因,就是窝料一下子打的太多,鱼都在

    2023年01月10 262
  • 怀疑的时代需要怎样的信仰,在怀疑的时代依然需要信仰

    在物质文明飞速发展的时代,颓废、享乐之风盛行,“丧文化”大行其道,在怀疑的时代依然需要信仰,“怀疑一切”理论甚嚣尘上。然而正如卢新宁所言:“在这个怀疑的时代,我们依然需要信仰。”青年人应摒弃消沉之心,

    2022年12月30 229
  • 怎样制定学习计划

    古语说:&34;这就是说,凡事都要有个计划,学习当然也不例外。如何帮助孩子合理地制订学习计划呢?让我们一起往下看吧!Part1制定基本的计划要点第一:要尽可能与课堂学习内容相结合;1、进行自我分析;2

    2022年12月27 220
  • 青少年叛逆期家长怎样与之沟通,孩子厌学带他去三个地方

    青春期的孩子,越管越反。父母要发自内心地尊重和支持孩子,爱与真诚,才是我们通往青春期孩子内心唯一的路。应该怎么和青春期的孩子相处?这是困扰了很多父母的难题。这一时期,这些半大的孩子们从身体到心理都在急

    2022年12月31 227
  • 怎样祷告,一个完整的祈祷过程

    婚礼于每个人而言都是充满期待的,基督徒也不列外,但是基督徒对于婚姻是不同的,他们不仅仅是进行婚礼仪式,还是表示着在神面前的誓约,那么基督徒的婚礼流程是怎么样的?婚前有什么讲究?深圳好百年婚礼策划中心一

    2023年01月01 209
  • 孤独的句子,一个人孤独的短句伤感

    1、我试着销声匿迹,却发现真的无人问津2、我从未觉得孤独,说得浪漫些,我完全自由3、有一天,我看了44次日落4、雾起挡炊烟,烟樟叶挂青檐,回首看人间,吃了嚼了咽。5、这个世界充斥着谎言和奴性,孤独的荒

    2022年12月10 282
  • 要怎么做,干那啥事,应该怎么做

    想要把一件事情做成,没有详尽的计划方案以及方法是不行的。如果没有计划、没有方案、没有策略、没有规划,不懂方法,那就是无头苍蝇,到处乱撞,能做成什么样自己也没有把握。想要做成一件事情,就要有方法,那具体

    2023年02月08 295
  • 间隔号怎么打,手机间隔号怎么打

    一、论文中序号的要求(一)正文层次标题序号间隔号输入方法如下:一、使用输入法直接输入1.SCIM的智能拼音:izd2.QQ拼音输入法等输入法:在中文模式下按Esc键的下面~・就可以了。3.微软拼音输入

    2023年02月05 257
  • 三线格怎么做,word怎么制作三线表

    科技论文的核心内容是实验测量和计算数据,word怎么制作三线表,表格作为一种最直观的表现形式,具有鲜明、清晰的特点。其中,“三线表”以其形式简洁、功能分明、阅读方便而在科技论文中被推荐使用。“三线表”

    2023年02月01 271
  • ps三角形怎么画,ps三角形工具在哪里

    前面两篇写了背景色.路面.分割线.云朵绘制方法.这篇写下图的绿色的山绘制方法.画这个绿色的山.可以用的工具很多.我就写其中一个工具.用多边形套索工具.ps三角形工具在哪里,按照下图画一个三角形.第三点

    2023年01月18 293
关注微信