linux内核学习(转1)

来源:互联网 发布:python 函数执行一次 编辑:程序博客网 时间:2024/06/05 04:37

这是我的第一篇关于linux kernel的东西,接触linux内核已经快一年的时间了,只是对内核有大体的了解,了解她实现的最基本的原理。现在想把我的学习的过程,或者是说对 linux内核的理解用文字的形式展现出来。一来检测自己的学习效果,再者,如果有初学linux内核的朋友,我认为这篇总结也可以给他们很好的指导。

写这篇文章花了我大量的时间,写之前以为很快就能完成的。不过后来我发现我错了,内核之庞大,之奥妙神奇,之复杂,不是一两句话就能说清道明的。而又想把每个模块尽量的讲明白,尽量把当初自己学习过程中的体会写出来,所以花了很长一段时间写。有几次想放弃写下去,不过后来还是坚持写完了全部文章,全文共近两万字。如果你是一个内核初学者,而且你还坚持看下去了,如果有难点,千万不要放弃,多看几遍你就能明白了。我会以我从初学内核的感受一步一步都写出来。尽量把我当初很难看明白的东西,然后我是怎么样最后理解这些东西的,都例举出来。在各个模块的讲解中,我把感觉非常重要的、最应该掌握的或是初学时难于理解的,都写出来,供大家参考。

 

Linux内核源代码到目前为止已经有几百万行代码了,我并没有过多的去看源代码(很大的原因是因为内核代码之庞大和它的复杂)。如果过多的追问这篇文章存在的价值,那就应该是我学习内核以来的想法了感受了。现在把它总结之后,就把内核的学习先放一放,还有更加重要的事情要去完成。由于是第一次写东西,语言并不简练,而且我感觉自己其实还有很多的东西要去学习,自己了解的也并不是很多。在写之后的内容的时候,我多次怀疑自己是否有能力写这样的总结,这样的怀疑是因为感觉自己还有很多不足。不过还是坚持写出来了,毕竟这是我的一个学习的见证,也是给我之后的在这方面的学习的一个新的起点。而且我感觉这样的一个总结给一个内核新手来说,还很不错的一个有引导性质的东西。

我打算按照内核各个功能模块来讲解linux内核,其中会涉及到内核的最核心的几个模块,linux内核的进程管理,进程调度,内存管理,进程地址空间,中断异常,系统调用,定时器中断,内核同步,虚拟文件系统,驱动程序,网络,等等。都是自己学习过程的总结,按照章节用文字的形式展现出来,供自己和linux内核初学者参考。并且把自己的学习心得总结出来,或者对前面阐述的东西进行补充。写这篇文章,首先会要更加细致的了解这些内核模块,毕竟看书和总结要求的知识面不是一样的。通过写这个总结,让自己和他人(如果有其他人能看到)得到收获。

 

文章的安排是这样的:首先我打算用一个适用于所有学习的倒金字塔结构(why?what?how?)来阐述前半部分内容。之后会再把我对内核各个模块的理解总结出来,不错,倒金字塔结构同样的适用于我的阐述。

1、为什么要有内核?内核存在的价值和意义。

2、什么是内核?她实现了什么样的功能。

3、内核是如何设计实现的?怎样实现对计算机系统的进行管理的。

4、内核各模块简介:对内核各个模块进行简单的介绍。

 

 

我认为关于为什么要有内核和什么是内核有着不可分割的联系,所以把他们放到一起,方便讲诉。

什么是linux内核?为什么要有内核?首先我们要先对内核有一定的了解,要不然再怎么说明为什么学习内核也是空话。内核是操作系统实现的最核心的部分,完成对系统的硬件资源的管理和分配,同时又管理着系统的其他的软件,对上层的应用程序提供访问硬件资源的服务接口。重点在于理解:管理软硬件,服务于上层应用程序。初学者可能对操作系统为什么要把硬件封装起来,而只对应用程序提供相应的服务接口这点感到疑惑:软件直接访问硬件不是更加快捷吗?或者说为什么要操作系统?

的确,如果在加了操作系统这一层,可能会带来一些看上去很复杂的操作,但操作系统提供这样的接口有很多好处。首先是方便,方便了程序员的编程,程序员不需要过多的了解硬件的实现细节。好比在写C语言程序的时候,我们想在屏幕上输出了某些内容,我们会去调用printf()函数,而不是去对连接到屏幕的io接口做一些输出有关的汇编级编程操作。操作系统对应用程序提供了统一的接口函数,如果你需要访问硬件,调用它们就可以了。第二是安全,如果多个进程都想用同一个硬件,如果他们在没有任何的约束下,他们一旦并被调度了执行,那么他们都会去抢这个硬件资源。如果这个资源是像打印机那样不可以同时被占用,那么结果可想而知,肯定会去大乱子了。同时,操作系统内核还保证了应用程序不能访问内核空间,既内核所在的地址空间。那样做事不安全的,因为那样有可能会破坏内核的代码或者数据。还有一个重要的原因,那就是方便移植。针对硬件的语言是很不方便移植的,而如果用像C语言这样的高级程序,移植是非常方便的。以上几点也正是操作系统存在的价值,你参透了吗?

我在上李超老师的一节课中,他给了我们一个很好的比方:

如果把计算机系统比作是我们人类生活的大环境,那么计算机硬件资源(内核的管理和分配对象)就好比是我们地球上的土地资源,那么操作系统内核(管理计算硬件和软件,服务于上层应用程序)就可以比作是我们生活中的政府(管理地球的土地资源和人们的日常生活,服务于人类),那么应用程序(内核服务的对象)就可以比作是人类。希望这么多比喻,没有把你弄得头晕。

政府管理着地球上的各种资源,并为生活的人类服务。而在人类生活环境中,做很多什么事情的时候都是要政府给我们提供相应的服务。你不是可以随意的使用土地资源的,除非政府把它分配给了你。就像应用程序没有直接使用内存或者硬件的权力,除非它申请并得到了内核的批准;古代人类的通信方式比较慢,而且不方便。但自从有了政府提供的通信服务基站,使得人与人之间的相互通信变得方便快捷。内核同样提供方便的进程间通信机制,让应用程序之间更加好的协调完成任务。

说到这里相信大家对内核已经有了一个感性的认识:应用程序需要内核提供的服务接口才可以访问硬件资源,就好像人类需要政府提供的各种职能而生存着。

内核就是这样简单。

 

 

看到这边你应该对内核存在的意义有了一定的了解,哦,内核,你就是做了那些事情呀。可是你可能又产生了新的问题:内核是如何实现的呢?或者说操作系统是如何实现的呢?

很多操作系统原理的初学者(为什么这边说是操作系统原理初学者而不是内核初学者,是因为内核原理就是操作系统原理,这点你在上面的内容里面应该已经参透了),一定会对那些关于阐述原理的大堆大堆的段落感到反感:讨厌死了,我知道这些,我想听她是如何实现的,那才是我想要的重点。

那么内核是如何实现操作系统的各种功能的呢?这的确是非常有意思的,当你对他们了解之后。

在 linux内核中,内核是有各个不同的C语言函数组织(当然也会有一些涉及硬件操作要用到的汇编语句),他们相互的调用实现了操作系统各个最核心的功能。可能大家对这样的话并不是很了解,因为在大家心目中:操作系统应该不只是函数的组织和调用这么简单。但是我想说的是不管操作系统在你的心中有多么的强大,内核就只是函数的组织和调用,就这么简单。只不过这是一个工程庞大的函数群,有几百万行的代码组织,如此庞大。只不过这堆代码可以改变处理器的模式,有权利管理其他应用程序和系统资源。

如果你现在不能理解,不要急,在之后的学习中你会明白的。相信我,如果你深入学习,你会领略到内核的艺术所在。下面这幅图是一些主要内核函数的调用关系:(当内核在学习的过程中可以对照这个图看,查看他们之间的关系,对学习内核会有一定的帮助)

 

虽然内核源代码看上去是那么的庞大,如野兽般不可驾驭。其实不然,内核在经过编译之后只有很少的几KB到几MB之间,是不是不可思议?这么庞大的内核源代码被编译成这么小的体积,这么小的内核却管理了如此庞大的计算机系统。在我心中,这就是艺术。

什么是艺术?艺术是一种文化现象,大多为满足主观与情感的需求,亦是日常生活进行娱乐的特殊方式。其根本在于不断创造新兴之美,借此宣泄内心的欲望与情绪,属浓缩化和夸张化的生活。文字、绘画、雕塑、建筑、音乐、舞蹈、戏剧、电影等任何可以表达美的行为或事物,皆属艺术。计算机学是一门艺术,经济管理学是一门艺术,发现美是一种艺术,人心美是一种艺术。

 

那为什么要学习linux内核?我认为学习一样知识,要问两个为什么。第一个是这个知识产生的价值,为什么要有这个知识?它是用来解决什么问题的?然后才是我们为什么要学习这样知识?学习了这样知识是否对自己有帮助?

那么理解内核的实现原理可以用来完成什么?我们是否真的有必要学习内核?内核是操作系统最核心的部分,理解她的实现对我们理解操作系统原理及实现有很大的帮助,应用程序的开发也会有很大的帮助。如果你想向嵌入式系统开发的方向发展,学习内核也会有很大的帮助,比如当我们了解到了内核提供的某些特定的函数之后,对于之后学习linux驱动程序的开发带来很大的帮助,驱动程序借助内核提供的函数实现其对硬件的封装。然后对配置内核也会有非常大的帮助,我们知道 linux内核是可裁减的,针对不同的硬件平台裁减内核,是内核更加小巧。在一本书上看到:linux内核是由世界上最厉害的程序员编写的,源代码就是最好的证明。我们不过多的评论这句话,但从这句话中我们可以看出,内核源代码的优秀,她的编码技巧和风格都是值得很多程序员学习的。如果对底层原理性的东西有一定的了解之后,会对之后的学习过程有个很好的支撑作用。如果你被内核艺术迷住了,那就了解和学习他吧。

就我自身而言,我感觉学习内核是非常好的,是非常有帮助的。学习可以对计算机系统学习有很大的帮助。学习过程同样是成长的过程,内核难于学习,可以磨砺一个人的心智。在学习内核的同时,同样也提升了我们的学习能力。21世纪社么最重要啊?显然是能力。当然我的意思不是说学了内核就一定对自己或者对自身的提高有帮助,因人而异吧。

当我学到现在,我就感觉内核的学习对我的帮助很大。这种帮助不只是体现在了对于内核的掌握上面,更加是我对整个计算机系统的认识提到了一个新的高度。然后我感觉我自己的能力也得到了进一步的提高,在内核的学习中,我一直在思考,思考内核的实现路径。经过这么长时间的思考之后,我发现我还是收获非常多的。真的能感觉到我在成长。

 

对也想学习内核的人,我感觉可以这样:

我的建议是首先要对C语言、汇编语言、操作系统基本原理、数据结构、硬件有一定的了解,再开始接触内核。(操作系统内核很庞大,而且要设计直接对硬件的管理,想要上手还有应该有一定的知识量储备来支撑。)比如说你要对编译、汇编、链接、加载、函数库有一定的了解,了解了它们也是同样很有用的。如果有做过linux程序设计的,那么就会用到一些内核提供的接口函数,那么在学习内核的时候,就会看到这些接口是如何实现的,对内核的学习会有很大的帮助。然后找一些内核的书籍看,先整体过一遍,在针对各个模块开始看。这边介绍我看过的几本书,个人感觉还是不错的。

内核入门级的书籍可以是《Linux Kernel Development》,翻译到国内的书籍叫《Linux内核设计与实现》,目前有第二版了。这本书主要起到了很好的提纲挈领的作用,是内核入门的好书。先把这本书整体过一遍,对内核有个大致的了解之后,再针对你所感兴趣或者是想研究的地方,再次看各个章节。这个时候你就应该找一些更加详细的书籍来做参考对比了,毕竟每本书都会讲到不同的地方。针对某个模块,几本书一起看,会过理解有帮助。这时可以参考《Understanding The Linux Kernel》(中文名:《深入理解Linux内核》)和《Professional Linux Kernel Architecture》(中文名:《深入Linux内核构架》),这两本书较之《Linux内核设计与实现》,多了很多的细节。在看书的同时,建议你去网上(www.kernel.org)下载到linux内核的源码,可以用Source Insight这个软件去组织和查看内核这个庞大的项目。这个软件方便我们很好的阅读内核源代码。如果你感觉你真的对内核的代码实现,或者想深入了解她的实现,那就看源码吧,毕竟源码才是最好的老师。当然如果你的编程功底不是很好,那有这样两本书可以帮助你更好的阅读源代码。一本是《Linux内核完全剖析——基于0.12内核》,另一本是《Linux内核源代码情景分析》。这两本书都是非常不错的,对于想深入了解源代码是由非常大的帮助的。

插句嘴:内核源代码真的很难看,我感觉最重要的原因是因为内核的庞大,跳来跳去,还有一个原因是要对编程语言非常理解。如果只是为了更好的编程,或者为了编写设备驱动,那么能了解她的实现和提供的接口就可以了。这里所说的提供的接口没有强调是对“上层”或者说“应用程序”提供的接口。因为内核提供的函数接口不只是给应用程序调用,同时内核也在用这样的接口。最普遍的例子就是设备驱动,驱动程序运行在内核空间中,调用内核提供的各种功能接口。所以学习内核的实现的原理,以及她所提供的接口不失为一个好办法。我想毕竟不是每个人都有天赋能开发内核的,起码我就不适合——我感觉太难了。当然不是说源码不看,而是要有针对性的看,看个别重要的模块还是蛮好的。

 

 

接下来我就想把内核的各个模块做一个简单的介绍,这边先给出一个关于内核的架构,有助于我们的了解内核。每个模块都是我精心安放的,至于什么会那样放置,以后你就会明白的。

 

 

在理解上幅图和讲解各个模块之前,有必要交代下让初学者对头痛的东西,那就是用户空间和内核空间。操作系统内核是独立于普通应用程序的,她一般处于系统态,拥有受到保护的内存空间和访问硬件设备的所有权限。这种系统态和被保护起来的内存空间,统称为内核空间。相应的,应用程序在用户空间执行。它们只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,不能直接访问硬件,还有一些其他的使用限制。内核态和用户态使用了不同的地址空间,不同的保护级别。那么为什么要这样分开呢?是为了安全。因为如果应用程序和内核在同一个保护级别,那么应用程序就有可能有意或者不小心进入了内核空间,破坏了内核空间的代码和数据。处理器硬件提供了这样的保护级别,内核代码和数据被放到了内核空间,而应用程序的地址空间(进程地址空间,稍后讲解)则由内核管理分配。因为内核只相信她自己。我们上面说到用户空间没有直接访问硬件的权力,如果应用程序要访问硬件(此时还在用户空间),那么请你调用一个系统调用吧。之后硬件会自动的进入内核空间,并完成此次系统调用要完成的操作。也就是说系统调用是一个陷入的动作,使程序进入了内核空间。我们说内核运行在进程上下文中,或者内核代表进程执行动作。懂了吗?

这边我例举一个例子:你需要在屏幕上输出:hello world!这样几个字符。因为你没有直接访问硬件的权力,所以你会调用printf()函数(实事是你也只会这么调用)。这显然是一个库函数,printf()封装了一个系统调用,编译器和链接加载器会帮我们完成,实事上printf()做了很多事情:数据的缓冲和格式化等操作,然后再执行的末期通过write()系统调用把处理后的最终数据输出到屏幕上面。在系统调用之前,一直是出于用户态的,系统调用之后,陷入了内核,内核代表这个程序完成对硬件的访问操作,之后退出内核态,继续做printf()之后的事情。

还有一种办法可以进入内核空间,那就是中断。就只有这两种办法。这边是有点饶头的,不过当深入学习之后就可以慢慢理解了。慢慢来。

 

 

在学习内核之前我还希望大家对list_head这个双向链表数据结构对深入的了解,这对之后的学习有大的帮助。这个双向链表不同于我们C语言中一般的双向链表,这个结构实现的非常巧妙。你同样可以把他用在你的应用程序中,但是不能直接的引用,因为内核空间的数据或者函数是不可以直接调用的(你也不可能调用的到)。把这个数据结构放到开头,大家就应该知道它的重要性了。

 

·进程管理 

进程是占用了一定的系统资源的一个程序的实体,或者说是正处于执行期的程序。进程不仅仅是一段可执行的代码,它还包括一些其他系统资源,如打开的文件、挂起的信号、内核内部数据、处理器的状态、地址空间以及一个或多个执行线程,当然还包括用来存放全局变量的数据段。记住,进程不只是一段可执行代码,更是一组系统资源的集合。在这重点理解为什么要有这些资源,如果这点都不了解,下面的内容你也是看不懂的。

每个进程都有它的进程控制块(PCB),在linux系统中,用进程描述符task_struct这个结构体来描述一个进程。它概括进程的所有完整的信息,所以这个结构体非常大,大约有 1.7KB,可以说是相当的大了(要知道,内核分给每个进程的内核栈只有8KB)。每产生一个进程或者线程分配一个进程描述符。请不要为内核产生这么大的一个结构体所花的时间担心,因为稍后我们会讲到slab分配器,一种快速分配内核数据结构的高速数据缓存池。Linux不特别区分线程和进程,线程被看做特殊的进程,线程共享进程的资源,就这样。那为什么要给线程也分配一个进程描述符?因为需要线程并发的执行任务,只有有了进程描述符,线程才可以作为一个调度的实体了。关于调度,稍后会讲到。

关于进程的状态的一些说明:有趣的是,linux并没有设置任务就绪这个状态,task_running既是就绪状态,又是执行状态。因需要等待资源的情况不同,linux有两个任务挂起状态。当资源到了,进程会再次被选择并执行。那么谁选中它们呢?是内核。那么内核是怎么选得处理器的执行权力并选中一个进程的呢?为什么要有两种任务挂起的状态呢?我希望大家在看书的时候,经常提出一些问题。关于调度,我们还是下面再讲解吧。

接下来是关于进程的创建和删除。一个进程被产生出来,是为了完成某个任务。当task结束之后,它也就没有存在的价值了,那么释放它得到的资源。那么进程由谁创建的呢?内核和应用程序都可以创建。内核直接调用创建的函数do_fork(),而应用程序只能通过fork()系统调用来创建一个进程,当然fork()最终还是调用了do_fork()。当创建一个进程时,要给进程分配一个进程描述符(直接拷贝父进程的,当然之后会做相应的修改),分配一个内核栈(当进程进入内核态时,访问的是内核栈。那么这个栈的切换是怎么完成的呢?很简单,就是改变堆栈指针)。之后进程调用exec()函数族,完成进程地址空间的分配,分配一个用户态堆栈,并对代码的加载。虽然让人伤感,但进程终归是要终结的。此时调用进程退出exit()函数族,释放一些资源,做一些善后操作。这是每个进程都需要做的事情,有些人产生疑问:我编的C程序中一般都没有这个函数呀。那是因为C语言的编译器在编译的时候已经帮你加上了。她还帮我们做了很多事情,所以编译器真是个好东西呀。

关于进程管理就想说这么多,那么你需要知道什么呢?Linux是如何进行进程抽象的。当内核访问一个任务,她需要获得指向其tsak_struct的指针。实际上,内核中大部分处理进程的代码都是通过task_struct进行的。所以找到当前的进程描述符的速度很重要,所以产生了thread_info这个数据结构,使得更加方便的查找到进程描述符。