如何进行Linux Kernel 开发

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何进行Linux Kernel 开发相关的知识,希望对你有一定的参考价值。

参考技术A

    学习汇编语言、C语言,这两种语言是你进行Linux Kernel开发与维护的必备语言能力,这样你才有能力阅读与编写Linux Kernel的能力。

    下载Linux Kernel源代码,建议下载先前的版本,因为目前的新版本代码数量太庞大,技术太新,如果是进行Linux Kernel的开发的话,先从简单的版本0.11或者1.XX.XX版本开始,以前的版本中没有过多的新技术的代码,适合入门Linux Kernel的学习。

    当你熟悉了Linux Kernel了后,可以下载目前最新的版本Linux Kernel3.18版本的源代码,里面包含了很多的新技术的知识,方便你了解与学习~~~


这是一篇很重要的文档,它介绍了内核开发的方方面面。这篇文档已被加入到内核源码树的Documentation文档里(名字为HOWTO),你可以在最新的内核树里找到它。尽管已经有网友翻译过这篇文档,但是我还是决定自己再翻译一遍。翻译完之后,我的感触是如果依靠翻译来进行学习,速度太慢了。以后的技术文档直接看英文,适当的做做笔记即可。

 

山涛

 

-----------------------------------------------------

How to do Linux Kernel development

-----------------------------------------------------

关于如何进行Linux Kernel development,这篇文档是最值得你阅读的一篇。它指导你如何成为一名Linux内核开发者以及如何和Linux内核开发社区一同工作。尽管它不包含内核编程的技能方面的知识,但是本篇能够给你正确的指导去做内核开发。

 

如果这篇文档讲述的任何东西已经过时了的话,请给这篇文档的维护者发送你的更新。

 

Greg Kroah-Hartman greg@kroah.com

 

Introduction

-----------------

你想成为一名Linux内核开发者吗?或者你的老板曾经告诉你:去给某个设备写个Linux驱动程序。这篇文档的目标是,通过描述你进行开发时需要经历的一些流程规则去指导你如何与社区一起工作,教会你所需要的一切从而让你实现你的目标(成为一名合格的内核开发者,或者写出合格的令老板满意的驱动程序);这篇文档也会说明内核社区工作的风格和原因。

 

内核绝大部分代码是基于C语言编程,与体系结构有关的一小部分由汇编完成。很好的理解和掌握C语言,是内核开发的必备要求。汇编语言(不同的体系结构有不同的汇编语言)不是必需的,除非你计划做体系结构相关的底层开发。如果你想加强C语言的掌握,很好的参考资料如下:

- "The C Programming Language" by Kernighan and Ritchie [Prentice Hall]
- "Practical C Programming" by Steve Oualline [O'Reilly]

 

Linux内核是使用GNU C和GNU工具链完成的。尽管它遵循ISO C89标准,但是内核的编写也使用了许多的GNU C的扩展特性,这些特性不属于标准的一部分。内核的C编程环境自成体系,不依赖于C标准库,所以C标准的一部分特性没有被支持:例如Arbitrary long long divisions和浮点指针不被支持。有时你会很难理解内核基于GNU工具链的一些假定以及内核使用的一些GNU C扩展,不幸的是对于这类问题没有确定性的参考资料。如果你遇到这类问题,建议你查阅GCC的info pages来获取相关的信息(在Linux PC上,通过命令 info gcc可以获得信息)。

 

请记住你正在学习如何与已经存在的内核开发社区一起工作。内核开发社区由全球不同地方的开发人员组成,它以代码、风格、开发流程的高质量标准著称。这些高质量的标准使内核开发社区(这个组织非常大,地理位置非常分散)能够非常有效的进行。应当提早努力学习这些高质量标准(编程风格、代码要求以及开发流程),它们有很好的文档;不要期望内核开发社区别的开发人员会适应你自己的或者你公司的开发风格。

 

Legal Issues

------------------

Linux 内核代码基于GPL许可协议发布。请阅读内核源码树的主目录里的COPYING文件,它提供了GPL许可的详细描述。如果你有关于GPL许可的进一步问题,请联系一名律师,不要在Linux kernel mailing list里询问。Linux kernel mailing list里的开发人员不是律师,所以你不应当听取他们的任何关于法律事务的建议。

对于一些通常的关于GPL许可的问题和解答,请参考:

http://www.gnu.org/licenses/gpl-faq.html

 

Documentation

---------------------

Linux内核源码树里有大量的非常有用的文档用于学习,使你与内核社区相互促进和共同发展。当一个新的特性要加入到内核里,建议相关的文档也要加入到内核里,用于描述如何使用这个新特性;当一个内核的修改导致了内核提供给用户的接口发生了变化,建议你发送信息或者一个补丁给mtk-manpages@gmx.net,告诉manual pages的维护者用户接口的变化。

 

这里罗列了一些内核源码树里的需要阅读的文档:

 

README

这篇文档简要的介绍了Linux内核的背景,描述了配置和build内核需要什么。一个刚刚接触内核的新手应当从这里开始。(注:build kernel,就是编译内核源代码,生成可供系统使用的内核二进制文件(vmlinux/zImage)的过程。

 

Documentation/Changes

这篇文档给出了一个用于成功编译和运行内核的各种软件包的列表的最小集合。

 

Documentation/CodingStyle

这篇文档描述了Linux内核编码风格,和一些隐藏在背后的基本原理。所有的想加入内核的新代码应当遵循这篇文档的指导。绝大数的内核代码维护者只愿意接受那些符合这篇文档描述的风格的补丁,许多内核开发者也只愿意审查那些符合Linux内核编码风格的代码。

 

Documentation/SubmittingPatches

Documentation/SubmittingDrivers

这些文档清楚而又详细地告诉你如何成功的创建和向社区递交一个补丁,包括:

----邮件内容

----邮件格式

----发送者和接收者

遵循文档里提倡的规则并不一定保证你提交补丁成功(因为所有的补丁遭受详细而严格的内容和风格的审查),但是不遵循它们,提交补丁肯定不成功。

其他的一些非常优秀的描述如何正确的创建补丁的文档如下:

"The Perfect Patch"
http://www.zip.com.au/~akpm/linux/patches/stuff/tpp.txt
"Linux kernel patch submission format"
http://linux.yyz.us/patch-format.html
 

Documentation/stable_api_nonsense.txt

这篇文档描述了有意决定在内核里没有固定内核API的基本原因,包含下面的讨论主题:

---子系统的shim-layers(为了兼容性?)

---操作系统之间的驱动移植性

---减缓内核源码树的快速变化(或者说,防止快速变化)

这篇文档对于理解Linux的开发哲学非常关键,也对于从其他操作系统转移到Linux上的开发人员非常重要。

 

Documentation/SecurityBugs

如果你确知你在Linux Kernel里发现了security problem,请遵循这篇文档描述的步骤,帮助通知内核的开发者们并解决这类问题。

 

Documentation/ManagementStyle

这篇文档描述了Linux内核开发者们如何进行管理运作,以及运作方法背后的分享精神(shared ethos)。这篇文档对于那些内核开发新手们(或者那些好奇者)值得一读,因为它解决或解释了很多对于内核维护者独特行为的误解。

 

Documentation/stable_kernel_rules.txt

这篇文档描述了一个稳定的内核版本如何发布的规则,以及需要做些什么如果你想把一个修改加入到其中的一个版本。

 

Documentation/kernel-docs.txt

关于内核开发的外部文档列表。如果你在内核开发的内部文档中找不到你想要的资料,请参考这篇文档提供的资料链接。

 

Documentation/applying-patches.txt

这篇文档很好地描述了什么是补丁(patch),以及如何将它应用到内核的不同开发分支(branch)上。

 

内核里也有大量的由内核源码自动生成的文档。其中包括了内核内部API的全面描述,和如何处理好锁的规则。这些文档在Documentation/DocBook/下创建,格式可以是PDF、Postscritpt、HTML和man pages,在内核源码主目录下通过运行下面命令自动生成:

make pdfdocs

make psdocs

make htmldocs

make mandocs

 

附上出处链接:http://www.cppblog.com/flyonok/archive/2011/04/15/144316.html

linux kernel 没有输出信息 怎么调试

最近工作在调试usb虚拟串口,让其作为kernel启动的调试串口,以及user空间的输入输出控制台。
利用这个机会,学习下printk如何选择往哪个console输出以及user空间下控制台如何选择,记录与此,与大家共享,也方便自己以后翻阅。
Kernel版本号:3.4.55
依照我的思路(还是时间顺序)分了4部分,指定kernel调试console , kernel下printk console的选择 ,kernel下console的注册,user空间console的选择。

一 指定kernel调试console
首先看kernel启动时如何获取和处理指定的console参数。
kernel的启动参数cmdline可以指定调试console,如指定‘console=ttyS0,115200’,
kernel如何解析cmdline,我之前写了一篇博文如下:
http://blog.csdn.net/skyflying2012/article/details/41142801

根据之前的分析,cmdline中有console=xxx,start_kernel中parse_args遍历.init.setup段所有obs_kernel_param。
kernel/printk.c中注册了‘console=’的解析函数console_setup(注册了obs_kernel_param),所以匹配成功,会调用console_setup来解析,如下:

[cpp] view plain copy
static int __init console_setup(char *str)

char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
char *s, *options, *brl_options = NULL;
int idx;

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (!memcmp(str, "brl,", 4))
brl_options = "";
str += 4;
else if (!memcmp(str, "brl=", 4))
brl_options = str + 4;
str = strchr(brl_options, ',');
if (!str)
printk(KERN_ERR "need port name after brl=\n");
return 1;

*(str++) = 0;

#endif

/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9')
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);
else
strncpy(buf, str, sizeof(buf) - 1);

buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;

__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return 1;

__setup("console=", console_setup);

参数是console=的值字符串,如“ttyS0,115200”,console_setup对console=参数值做解析,以ttyS0,115200为例,最后buf=“ttyS”,idx=0,options="115200",brl_options=NULL。调用__add_preferred_console如下:

[cpp] view plain copy
/*
* If exclusive_console is non-NULL then only this console is to be printed to.
*/
static struct console *exclusive_console;

/*
* Array of consoles built from command line options (console=)
*/
struct console_cmdline

char name[8]; /* Name of the driver */
int index; /* Minor dev. to use */
char *options; /* Options for the driver */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
char *brl_options; /* Options for braille driver */
#endif
;

#define MAX_CMDLINECONSOLES 8

static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
static int selected_console = -1;
static int preferred_console = -1;
int console_set_on_cmdline;
EXPORT_SYMBOL(console_set_on_cmdline);
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options)

struct console_cmdline *c;
int i;

/*
* See if this tty is not yet registered, and
* if we have a slot free.
*/
for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
if (strcmp(console_cmdline[i].name, name) == 0 &&
console_cmdline[i].index == idx)
if (!brl_options)
selected_console = i;
return 0;

if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
if (!brl_options)
selected_console = i;
c = &console_cmdline[i];
strlcpy(c->name, name, sizeof(c->name));
c->options = options;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
c->brl_options = brl_options;
#endif
c->index = idx;
return 0;


kernel利用结构体数组console_cmdline[8],最多可支持8个cmdline传入的console参数。
__add_preferred_console将name idx options保存到数组下一个成员console_cmdline结构体中,如果数组中已有重名,则不添加,并置selected_console为最新添加的console_cmdline的下标号。
比如cmdline中有“console=ttyS0,115200 console=ttyS1,9600”
则在console_cmdline[8]数组中console_cmdline[0]代表ttyS0,console_cmdline[1]代表ttyS1,而selected_console=1.

二 kernel下printk console的选择
kernel下调试信息是通过printk输出,如果要kernel正常打印,则需要搞明白printk怎么选择输出的设备。
关于printk的实现原理,我在刚工作的时候写过一篇博文,kernel版本是2.6.21的,但是原理还是一致的,可供参考:
http://blog.csdn.net/skyflying2012/article/details/7970341
printk首先将输出内容添加到一个kernel缓冲区中,叫log_buf,log_buf相关代码如下:

[cpp] view plain copy
#define MAX_CMDLINECONSOLES 8

static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
static int selected_console = -1;
static int preferred_console = -1;
int console_set_on_cmdline;
EXPORT_SYMBOL(console_set_on_cmdline);

/* Flag: console code may call schedule() */
static int console_may_schedule;

#ifdef CONFIG_PRINTK

static char __log_buf[__LOG_BUF_LEN];
static char *log_buf = __log_buf;
static int log_buf_len = __LOG_BUF_LEN;
static unsigned logged_chars; /* Number of chars produced since last read+clear operation */
static int saved_console_loglevel = -1;

log_buf的大小由kernel menuconfig配置,我配置的CONFIG_LOG_BUF_SHIFT为17,则log_buf为128k。

printk内容会一直存在log_buf中,log_buf满了之后则会从头在开始存,覆盖掉原来的数据。
根据printk的实现原理,printk最后调用console_unlock实现log_buf数据刷出到指定设备。
这里先不关心printk如何处理log buf数据(比如添加内容级别),只关心printk如何一步步找到指定的输出设备,根据printk.c代码,可以找到如下线索。
printk->vprintk->console_unlock->call_console_drivers->_call_console_drivers->_call_console_drivers->__call_console_drivers
看线索最底层__call_console_drivers代码。如下:

[cpp] view plain copy
/*
* Call the console drivers on a range of log_buf
*/
static void __call_console_drivers(unsigned start, unsigned end)

struct console *con;

for_each_console(con)
if (exclusive_console && con != exclusive_console)
continue;
if ((con->flags & CON_ENABLED) && con->write &&
(cpu_online(smp_processor_id()) ||
(con->flags & CON_ANYTIME)))
con->write(con, &LOG_BUF(start), end - start);


for_each_console定义如下:

[cpp] view plain copy
/*
* for_each_console() allows you to iterate on each console
*/
#define for_each_console(con) \
for (con = console_drivers; con != NULL; con = con->next)
遍历console_drivers链表所有console struct,如果有exclusive_console,则调用与exclusive_console一致console的write,
如果exclusive_console为NULL,则调用所有ENABLE的console的write方法将log buf中start到end的内容发出。
可以看出,execlusive_console来指定printk输出唯一console,如果未指定,则向所有enable的console写。
默认情况下execlusive_console=NULL,所以printk默认是向所有enable的console写!
只有一种情况是指定execlusive_console,就是在console注册时,下面会讲到。
到这里就很明了了,kernel下每次printk打印,首先存log_buf,然后遍历console_drivers,找到合适console(execlusive_console或所有enable的),刷出log。
console_drivers链表的成员是哪里来的,谁会指定execulsive_console?接着来看下一部分,kernel下console的注册
参考技术A 内核中的bug也是多种多样的。它们的产生有无数的原因,同时表象也变化多端。从隐藏在源代码中的错误到展现在目击者面前的bug,其发作往往是一系列连锁反应的事件才可能出发的。虽然内核调试有一定的困难,但是通过你的努力和理解,说不定你会喜欢上这样的挑战。

以上是关于如何进行Linux Kernel 开发的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式 Linux开发Kernel移植——kernel内核配置和编译

linux kernel启动失败,如何分析问题所在?

linux kernel 没有输出信息 怎么调试

Linux Kernel系列一:开篇和Kernel启动概要

嵌入式 Linux开发Kernel移植——kernel内核简介

内核启动流程