zl程序教程

您现在的位置是:首页 >  其它

当前栏目

kernel jobs/rek

Kernel Jobs
2023-09-11 14:13:59 时间

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

Makefile有五部分

Makefile 顶层的Makefile文件.
.config 配置kernel后在目录根生成的配置文件.
arch/$(ARCH)/Makefile 架构Makefile文件.
scripts/Makefile.* 包含了与一般编译,模块生成,各种实用程序的编译,从内核树
删除目标文件和临时文件等任务相关的make规则.
kbuild Makefiles 内核源代码的各个子目录都包含了与特定驱动程序或子系统相
关的Makefile.

顶层的Makefile文件读取从配置过程生成的总配置文件.config.
顶层的Makefile主要负责编译vmlinux(常驻内核镜像)和模块(任何的模块文件).
顶层的Makefile通过递归的访问内核目录树的子目录来构建那些目标.
构建那些目标时需要访问的子目录列表依赖于内核的配置文件.config.
顶层的Makefile文本上包括了一个在arch/$(ARCH)/Makefile的架构Makefile文件,这个架
够Makefile文件向顶层的Makefile提供了具体的架构信息.

makefile的作用

在一个kernel中这样的大工程中,如何去编译这个工程?应该make,是的.为什么一条make命
令就能够做到?其实make命令的背后是许多的makefile在为make命令出谋划策,makefile作
为路线图指导make编译的每一个步骤.

make是一个命令工具,是一个解释makefile文件中指令的命令工具.make作为makefile这个
语法文件的解释器在工作.makefile的语法是一种在工程方面的编译方法,以告诉make命令
需要怎样去编译和链接目标程序.

makefile给这个大工程带来的是自动化编译kernel.

makefile规则介绍

目标文件... : 依赖文件...
命令
...
...

目标文件的生成依赖于其它的文件,按照规则执行相应的命令就能够生成目标文件所需的
依赖文件.

makefile规则中,如果使用一个没有依赖只有命令行的双冒号规则去更新一个文件,那么每
次执行make时,此规则的目标文件将会被无条件的更新(即此规则定义的命令会被无条件执
行).

makefile中的命令是和shell有紧密的关系的,除了一些隐含的规则和变量以及make中的函
数,都是shell中的命令,所以shell是make编译的基石.

通常我们在工程中会遇到,其中一个(makefile-A)需要使用另外一个(makefile-B)中所定
义的变量和规则.这时候用include(用来包含其它文件)并不保险,因为这两个文件可能存
在规则重复定义的问题.如下可解决问题:

sample GUNmakefile

foo:
frobnicate > foo
%:force
@$(MAKE) -f Makefile $@
force:;

makefile清除规则

清除当前目录中编译过程中产生的临时文件的规则

clean :
rm edit $(objects)

也可以如下这样写让清除工作不会出现任何差错

.PHONY : clean
clean :
-rm edit $(objects)

有两个不同点:
1)通过.PHONY特殊目标将clean目标声明为伪目标.避免当磁盘上存在一个名为clean文件
时,目标clean所在的规则的命令无法执行.
2)在命令之前使用"-",意思是忽略命令rm的执行错误.

这样的一个目标在makefile中,不能将其作为终极目标(makefile的第一个目标).因为我们
的初衷并不是在命令行上输入make以后执行删除动作,而是要创建或者更新程序.

make如何解析makefile文件

分为两个阶段

第一阶段:读取所有的makefile文件(包括指示符"include"指定的,以及命令行选项"-f"指
定的makefile文件),内建所有的变量,明确规则和隐含规则,并建立所有目标和依赖之间的
依赖关系结构链表.

第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新和创建,并使
用对应的规则来重建这些目标.

------------------------------makefile语法------------------------------------

这里不可能详细的介绍每一个细节,如果要这样的话,我们需要写上一本书了.这里只做大
致的介绍以方便我们使用.如果你是一个linux的shell用户理解这些并不难,这些东西和我
们通常使用的shell是浑然天成的. Let's Go>>

------------------------------makefile语法------------------------------------

GUN make可识别的指示符:

1)多行的定义
define VARIABLE
endef

2)条件判断的基本语法
ifdef VARIABLE 如果定义了变量
ifndef VARIABLE 如果没有定义变量
ifeq (A,B) 如果A和B相等
ifeq "A" "B" 如果A和B相等
ifeq 'A' 'B' 如果A和B相等
ifneq (A,B) 如果A和B不相等
ifneq "A" "B" 如果A和B不相等
ifneq 'A' 'B' 如果A和B不相等
else 条件分支
endif 条件分支结束

3)包含其他makefile文件
include FILE 包含其他的makefile文件
-include FILE 当所要包含的文件不存在时不会有错误提示
sinclude FILE 通-include相同,那个s代表"静默"
对于FILE可以使用通配符

4)通过override锁定定义变量
对于一个在makefile中使用常规方式(使用"=",":="或者"define")定义的变量,我们可以
通过命令行方式重新指定这个变量的值,如果我不希望命令行指定的变量值代替makefile
中变量定义,那么可以使用override来对这个变量进行声明.
override VARIABLE = VALUE 声明一个锁定的变量
override VARIABLE := VALUE 声明一个锁定的变量,可以覆盖之前的值
override VARIABLE += VALUE 对声明的锁定变量进行追加的值
override VARIABLE ?= VALUE 对声明的锁定变量如果没有被赋值使用此值
override define VARIABLE 声明多行锁定变量
endef 声明多行锁定变量结束

5)导入变量关键字(export)
如果需要在上层make执行的makefile变量传递给子make过程,那么这个在上层makefile文
件中的变量必须使用export指示符进行声明
export VARIABLE 需要将这层声明的变量传递给子make进程
unexport VARIABLE 不需要将这层声明的变量传递给子make进程

6)目录搜索(vpath VPATH)
通过变量VPATH可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在,make
会在此变量所制定的目录下去寻找这些依赖文件,而关键字vpath则更为灵活,可以为不同
类型的文件(由文件名区分)指定不同的搜索目录.
vpath PATTERN PATH 为所有符合模式"PATTERN"的文件指定搜索目录"PATH"
vpath PATTERN 清除之前符合模式"PATTERN"的文件设置的搜索路径.
vpath 清除所有已被设置的文件搜索路径.
vpath使用方法中的"PATTERN"需要包含模式字符"%"."%"意思是匹配一个或者多个字符.
例如,"%.h"表示所有以".h"结尾的文件.

GNU make函数:

函数的书写\((FUNCTION ARGUMENTS)或者\){FUNCTION ARGUMENTS}
对于用户自己的函数需要通过make的call函数来间接调用
在makefile中应该这样书写$(sort $(x))
参数和函数名之间可以使用若干个空格,函数的参数之间不能出现逗号和空格

1)文本处理函数

subst $(subst \((FROM)\)(TO)$(TEXT))
字符串替换函数
把字串TEXT中的FROM字串替换为TO.
返回值:替换后的新字符串.

patsubst $(patsubst \((PATTERN)\)(REPLACEMENT)$(TEXT))
模式替换函数
搜索TEXT中以空格分开的单词,将符合模式PATTERN替换为REPLACEMENT.
返回值:替换后的新字符串.

strip $(strip $(STRINT)
去空格函数
去掉字串STRINT开头和结尾的空字符,并将其中多个连续空字符合并为一个空字符.
返回值:无前导和结尾空字符,使用单一空格分割的多单词字符串.

findstring $(findstring \((FIND)\)(IN))
查找字符串函数
搜索字串IN,查找FIND字串.
返回值:如果在IN之中存在FIND,则返回FIND,否则返回空.

filter $(filter \((PATTERN...)\)(TEXT))
过滤函数
过滤掉字串TEXT中所有不符合模式PATTERN的单词,保留所有符合此模式的单词.
返回值:空格分割的TEXT字串中所有符合模式PATTERN字串.

filter-out $(filter-out \((PATTERN...)\)(TEXT))
反过滤函数
与filter相反
返回值:空格分割的TEXT字串中所有不符合模式PATTERN的字串.

sort $(sort $(LIST))
排序函数
给字串LIST中的单词以首字母为准进行排序(升序),并去掉重复的单词.
返回值:空格分割的没有重复单词的字串.

word $(word \((N)\)(TEXT))
取单词函数
取字串TEXT中第N个单词.
返回值:返回字串TEXT中第N个单词.

wordlist $(wordlist \((S)\)(E)$(TEXT))
取字串函数
从字串TEXT中取出从S开始到E的单词串,S和E表示单词位置的数字.
返回值:字串TEXT中从第S到E的单词字串.

words $(words $(TEXT))
统计单词数目函数
计算字串TEXT中单词的数目.
返回值:TEXT字串中的单词数.

firstword $(firstword $(NAMES...))
取首单词函数
取字串NAMES...中的第一个单词.
返回值:字串NAMES...对的第一个单词.

2)文件名处理函数

dir $(dir $(NAMES...))
取目录函数
从文件名序列NAMES中取出各个文件名的目录部分.
返回值:空格分割的文件名序列NAMES...中每一个文件的目录部分.

notdir $(notdir $(NAMES...))
取文件名函数
从文件名序列NAMES...中取出非目录部分.
返回值:文件名序列NAMES...中每一个文件的非目录部分.

suffix $(suffix $(NAMES...))
取后缀函数
从文件名序列NAMES...中取出各个文件名的后缀.
返回值:以空格分割的文件名序列NAMES...中每一个文件的后缀序列.

basename $(basename $(NAMES...))
取前缀函数
从文件名序列NAMES...中取出各个文件名的前缀部分.
返回值:空格分割的文件名序列NAMES...中各个文件的前缀序列.如果没有前缀,返回空.

addsuffix $(addsuffix \((SUFFIX)\)(NAMES...))
加后缀函数
为NAMES...中的每一个文件名添加后缀SUFFIX.
返回值:以单空格分割的添加了后缀SUFFIX的文件名序列.

addprefix $(addprefix \((PREFIX)\)(NAMES...))
加前缀函数
为NAMES...中的每一个文件名添加前缀PREFIX.
返回值:以单空格分割的添加了前缀PREFIX的文件名序列.

join $(join \((LIST1)\)(LIST2))
单词连接函数
将字串LIST1和字串LIST2各单词进行对应连接.
返回值:单空格分割的合并后的字(文件名)序列.

wildcard $(wildcard $(PATTERN))
获取匹配模式文件名函数
列出当前目录下所有符合模式PATTERN格式的文件名.
返回值:空格分割的,存在当前目录下的所有符合模式PATTERN的文件名.

3)杂项函数

error $(error $(TEXT...))
产生致命错误,并提示TEXT...信息给用户,并退出make的执行.

warning $(warning $(TEXT...))
函数warning类似于函数error,区别在于它不会导致致命错误(make不退出).

shell $(shell $(....))
shell函数所实现的功能和shell中引用``相同.

origin $(origin VARIABLE)
函数origin的动作不是操作变量(它的参数).它只是获取此变量(参数)相关的信息,告诉我
们这个变量的出处(定义方式).
返回值:undefined(未定义)default(内嵌变量)environment(环境变量)
environment override(锁定环境变量)file(文件)command line(命令行定义)
override(锁定变量)automatic(自动化变量)

foreach $(foreach \((VAR)\)(LIST)$(TEXT))
循环函数
首先展开变量VAR和LIST的引用,而表达式TEXT中的变量引用不展开.执行时把LIST中使用
空格分割的单词依次取出赋值给变量VAR,然后执行TEXT表达式.重复直到LIST的最后一个
单词(为空时结束).

call $(call \((VARIABLE)\)(PARAM)$(PARAM))
call函数是唯一一个可以创建定制化参数函数的引用函数.使用这个函数可以实现对用户
自已定义函数引用.

if $(if \((CONDITION)\)(THEN-PART)[$(ELSE-PART)])
条件函数

eval
实现复杂通用的模板函数.

value $(value $(VARIABLE))
函数value提供了一种在不对变量进行展开的情况下获取变量值的方法.

GNU make的自动化变量:

模式规则中,规则的目标和依赖文件名代表了一类文件名;规则的命令是对所有这一类文件
重建过程的描述,显然,在命令中不能出现具体的文件名,否则模式规则失去意义.那么在模
式规则的命令行中该如何表示文件.为了解决这个问题,就需要使用'自动环变量',自动化
变量的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名.

\(@ 表示规则的目标文件名. \)% 当规则的目标文件是一个静态库文件时,代表静态库的一个成员名.
\(< 规则的第一个依赖文件名. %? 所有比目标文件更新的依赖文件列表,空格分割. \)^ 规则的所有依赖文件列表,使用空格分隔.
\(+ 类似\)^,但是它保留了依赖文件中重复出现的文件.
$* 表示文件中除后缀以外的部分.

GUN make中,还可以通过这七个自动化变量来获取一个完整文件名中的目录(D)部分和具体
文件名(F)部分.

\((@D) 表示目标文件的目录部分(不包括斜杠). \)(@F) 目标文件的完整文件名中除目录以外的部分(实际文件名).
\((*D) 表示目录部分. \)(*F) 表示文件名部分.
\((%D) 当以如archive(member)形式静态库为目标时,表示库文件中的目录部分. \)(%F) 当以如archive(member)形式静态库为目标时,表示库文件中的文件名部分.
\((<D) 表示规则中第一个依赖文件的目录部分. \)(<F) 表示规则中第一个依赖文件的文件名部分.
\((^D) 表示所有依赖文件的目录部分. \)(^F) 表示所有依赖文件的文件名部分.
\((+D) 表示所有依赖文件的目录部分(可存在重复文件). \)(+F) 表示所有依赖文件的文件部分(可存在重复文件).
\((?D) 表示被更新的依赖文件的目录部分. \)(?F) 表示被更新的依赖文件的文件名部分.

GNU make环境变量

MAKEFILES
如果在当前环境定义了一个MAKEFILES环境变量,make执行时首先将此变量的值作为需要读
入的Makefile文件,多个文件之间使用空格分开.

MAKEFILES_LIST
make程序在读取多个makefile文件时,包括由环境变量MAKEFILES指定,命令行指当前工作
下的默认的以及使用指示符include指定包含的,在对这些文件进行解析执行之前make读取
的文件名将会被自动依次追加到变量MAKEFILE_LIST的定义域中.

VPATH
这个变量已经在和关键字作比较时已经介绍过了.

SHELL
它作为一个变量,我们也可以在Makefile中明确地给它赋值(指出解释程序的名字,当明确
指定时需要使用完整的路径名.如/bin/sh),变量SHELL的默认值是/bin/sh.

MAKE
在使用make的递归调用时,在Makefile规则的命令行中应该使用变量MAKE来代替直接使用
make.

CURDIR
在make的递归调用中,需要了解一下变量CURDIR,此变量代表make的工作目录.当使用-C选
项进入一个子目录后,此变量将被重新赋值.

.SUFFIXES
可识别的后缀指的是特殊目标.SUFFIXES所有依赖的名字.通过给特殊目标SUFFIXES添加依
赖来增加一个可被识别的后缀.使用如下:
.SUFFIXES: #删除所有已定义的可识别后缀
.SUFFIXES: .c .o .h #重新定义

.LIBPATTERNS
变量.LIBPATTERNS就是告诉链接器在执行链接过程中对于出现-LNAME的文件如何展开.当
然也可以将此变量为空,取消链接器对-lNAME格式的展开.

GUN make隐含变量:

AR 函数库打包程序,可创建静态库.a文档,默认是ar.
AS 汇编程序.默认是as.
CC C编译程序.默认是cc.
CXX C++编译程序,默认是g++.
CO 从RCS中提取文件的程序.默认是co.
CPP C程序的预处理器(输出是标准输出设备).默认是$(CC) –E
FC 编译器和预处理Fortran和Ratfor源文件的编译器,默认是f77.
GET 从SCCS中提取文件程序.默认是get.
LEX 将Lex语言转变为C或Ratfor的程序.默认是lex.
PC Pascal语言编译器.默认是pc.
YACC Yacc文法分析器(针对C程序).默认命令是yacc.
YACCR Yacc文法分析器针对于Ratfor程序).默认是yacc –r
MAKEINFO 转换Texinfo源文件.texi到Info文件程序.默认是makeinfo.
TEX 从TeX源文件创建TeX DVI文件的程序.默认是tex.
TEXI2DVI 从Texinfo源文件创建TeX DVI文件的程序.默认是texi2dvi.
WEAVE 转换Web到TeX的程序.默认是weave.
CWEAVE 转换C Web到TeX的程序.默认是cweave.
TANGLE 转换Web到Passcal语言的程序.默认是tangle.
CTANGLE 转换C Web到C.默认是ctangle.
RM 删除命令默认是rm –f.

GUN make隐含命令参数的变量:

下面的是代表命令执行参数的变量.如果没有给出默认值则默认值为空.

ARFLAGS 执行AR命令的命令行参数.默认值是rv.
ASFLAGS 执行汇编器AS的命令行参数.(当明显的调用.s或.S文件时).
CFLAGS 执行CC编译器的命令行参数(编译.c源文件的选项).
CXXFLAGS 执行g++编译器的命令行参数(编译.cc源文件的选项).
COFLAGS 执行co的命令行参数(在RCS中提取文件的选项).
CPPFLAGS 执行C预处理器cc –E的命令行参数(C和Fortran编译器会用到)
FFLAGS Fotran语言编译器f77执行的命令行参数(编译Fortran源文件的选项).
GFLAGS SCCS "get"程序参数.
LDFLAGS 连接器参数.(如:ld)
LFLAGS Lex文法分析器参数
PFLAGS Pascal语言编译器参数.
RFLAGS Ratfor程序的Fortran编译器参数.
YFLAGS Yacc文法分析器参数.

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

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

readme

readme基本上是软件中介绍和使用软件向导的标准配置文档.就像他的名字一样"看看我".
通常取过来一个软件,解压之后我们怎么使用他呢?readme就起到这样的作用.

kernel readme翻译

Linux kernel release 4.x <http://kernel.org/>

这些是Linux版本4的发行说明。仔细的读这些,他将告诉你这些是关于什么的,解释如何
安装内核和在一些东西出错的的情况下能够做些什么事情。

LINUX是什么?

linux是一个unix系统的克隆,由Linus Torvalds在loosely-knit的网络黑客团队的援助
下编写出来的。它符合POSIX和单一UNIX规范。

他拥有你所期望的在一个现代完全成熟Unix的所有特性,包括真正的多任务,虚拟内存,
共享库,按需加载,共享的写时拷贝可执行文件,正确的内存管理和包括IPv4和IPv6的
网络。

他在这个GUN General Public License(GPL)下发布-看看这个随同的COPYING的更加详
细的文件。

他能够在什么样的硬件上运行?

尽管最开始是为了在32位的x86架构个人电脑(386或者更高),但是今天至少能够在像
Compaq Alpha AXP,Sun SPARC和UltraSPARC,Motorola 68000,PowerPC,PowerPC64,
ARM,Hitachi SuperH,Cell,IBM S/390,MIPS,HP PA-RISC,Inter IA-64,DEC VAX,
AMD x86-64,AXIS CRIS,Xtensa,Tilera TILE,AVR32,ARC和Renesas M32R架构上面
运行了。

Linux能够很容易的移植到大部分的32位和64位架构的机器上,只要他们有一个内存分页
管理单元(PMMU)和GNU C编译器(gcc)(这个GNU编译套件的部分,GCC)。Linux也能
够移植到一个没有PMMU的大多数的架构上面,这样的话功能可能会受到明显的限制。
Linux也可以移植到自己的上面。你能够将内核作为一个用户空间的应用程序来运行这个
他-这被叫做用户模式的Linux(UML)。

文档:

-在网络上和书籍中有很多的文档可以找到,既有Linux特有的也有通常UNIX的问题。我推
荐LDP(Linux文档工程)的任何Linux FTP站点的文档子目录。这个README不意味着是关
于系统的文档,关于系统的文档有更过更好的源可以找到。

-在Documentation/下的子目录有很多各种各样的README文件。那些包含了典型的对于一
些驱动安装内核特性的说明样例。看一下Documentation/00-INDEX,是包含每一个文件
的列表。请读这个Changes文件,他包含了那些由于升级你的内核所产生的问题的信息。

-这个Documentation/DocBook/子目录包含了一些对于内核开发者和内核用户的指导。那
些指导可以被渲染成如:PostScript,PDF,HTML和man文档等等的一定格式。安装后,
可以使用make psdocs,make pdfdocs,make htmldocs或make mandocs来渲染成相应要
求的格式。

安装内核源:

-假如你要安装整个代码源,把这个内核压缩文件放在你有权限的目录(例如:你的home
目录)中并解压他:

xz -cd linux-4.X.tar.xz | tar xvf -

用最新的内核版本号去替换这个X。

不要使用这个/usr/src/linux目录区域!这个区域通常有(也不完全)被库的头文件使
用的内核头文件。那个区域应该匹配库,而不是被那些内核怨妇碰巧使用到这个区域弄
的一团糟。

-你也能够通过打补丁来在4.x发行版之间进行升级。补丁是以xz格式来发布的。为了要打
补丁(获得最新的补丁),需要在内核源(linux-4.X)的顶层目录键入并执行:

xz -cd ../patch-4.x.xz | patch -p1

按照顺序,你应该可以做到的。你可能想要移除这个备份文件(some-file-name~或者是
some-file-name.orig),确保没有失败补丁(some-file-name#或some-file-name.rej)
如果有的话,你或者我犯了一个错误。

不像给4.x内核打补丁,要给4.x.y的内核打补丁(应该这是作为稳定的内核)不是增进
式的而是替代的应用到基础4.x内核的目录。例如,假如你的基本内核是4.0,打4.0.3的
补丁,你不用打4.0.1和4.0.2的补丁。类似的,假如你正在运行4.0.2版本的内核并且想
要跳到4.0.3,在你打4.0.3的补丁之前,你必须先打4.0.2的补丁(那就是patch -R)。
你能够在Documentation/applying-patches.txt中读到更多。

另一种选择方式,这个内核补丁脚本能够自动的进行这个过程。这取决于目前的内核版
本和应用发现的补丁。

linux/scripts/patch-kernel linux

在上面的命令行的第一个参数是内核源的地方。补丁从当前的目录中寻找,但是也可以
使用一个特殊的目录来作为第二个参数。

-确保没有陈旧的.o文件和依赖文件:

cd linux
make mrproper

你应该在这时将源代码正确的安装了。

软件需要

编译和运行4.x的内核需要将各种各样的软件包更新到新的版本。对于需要最小的版本号
和如何更新那些软件包可以参考Documentation/Changes。要意识到使用太久的软件包会
直接的引起很难追踪的错误,因此不要想着在编译过程中间出现明显的错误的时候再去
更新你的软件包。

为内核建立目录:

当你在编译内核的时候,所有的文件输出默认的都和内核的源代码存储在一起。通过使
用make O=output/dir可以让你为输出文件指定一个地方(包括.config)。
例如:

kernel source code:/usr/src/linux-4.X
build directory:/home/name/build/kernel

为了配置和编译内核,使用如下:

cd /usr/src/linux-4.X
make O=/home/name/build/kernel menuconfig
make O=/home/name/build/kernel
sudo make O=/home/name/build/kernel modules_install install

请注意:假如O=output/dir这个选项被使用,那么对于make的所有调用都要使用。

配置内核:

即使你升级了一个小的版本也不要跳过这个步骤。新的配置选项会在每个发行版上添加
并且假如配置文件没有如预期的被设置的话会引起奇怪的问题。假如你想用最小的工作
量将已经存在的配置文件应用到一个新的版本的话,使用make oldconfig,他会问你一
些关于新的配置选项的问题。

-其它配置命令是:

make config 文本界面。

make menuconfig 基于文本的彩色菜单,按钮和对话。

make nconfig 增强的基于文本的颜色的菜单。

make xconfig 基于(Qt)图形窗口的配置工具。

make gconfig 基于(GTK+)图形窗口的配置工具。

make oldconfig 对于你的已存在./.config文件的都使用默认选项,而新的选项询问。

make silentoldconfig 像上面,但避免让已回答的问题出现在屏幕上,此外更新依赖。

make olddefconfig 像上面,但给那些默认的值设置成新的符号却不给你提示。

make defconfig
从arch/\(ARCH/defconfig或arch/\)ARCH/configs/${PLATFORM}_defconfig的配置
中使用默认的配置符来创建一个./.config文件,但依赖与这个架构。

make \({PLATFORM}_defconfig 从arch/\)ARCH/configs/${PLATFORM}_defconfig的配置中使用默认的配置符来创
建一个./.config文件,使用make help能针对你的架构提供一个可使用的列表。

make allyesconfig 尽可能多的将配置符号设置成y来创建一个./.config文件。

make allmodconfig 尽可能多的将配置符号设置成m来创建一个./.config文件。

make allnoconfig 尽可能多的将配置符号设置成n来创建一个./.config文件。

make randconfig 随机的配置设置符号来创建一个./.config文件。

make localmodconfig
基于目前的配置选项和加载的模块(lsmod)来创建一个配置。禁止所有对已经
加载的模块没有必要的模块选项。

对于要给另一台机器创建一个localmodconfig配置,首先将那台机器的已加载模
块(lsmod)存储到一个文件中,然后将其作为一个LSMOD参数传递给他。

target$ lsmod > /tmp/mylsmod
target$ scp /tmp/mylsmod host:/tmp

host$ make LSMODE=/tmp/mylsmod localmodconfig

上面的对于跨平台编译也能很好的工作。

make localyesconfig
类似与localmodconfig,除了他将所有的模块选项都改为内建(=y)选项。

在Documentation/kbuild/kconfig.txt中,你能够找到关于内核配置工具更多的信息。

-关于make config的注意事项:

-使用没有必要的驱动将使内核变的更大,并且在某些情况下会导致问题:探测一个不存
在的控制卡可能混淆你的其他控制器。

-通过将CPU类型设置的比386更高来编译的这个内核不会在386平台上工作。在启动的时
候内核能够检测到这一点并放弃启动。

-即使是在那些从来都不使用数学模拟器的情况下,一个将数学模拟器编译进来的内核仍
然要使用协处理器。这个内核要稍微大一点,但是他仍能够在不同的机器上工作,并且
不用理睬他们是否拥有一个数学协处理器。

-这个kernel hacking配置细节通常会让内核变得更大或更慢(或都是),甚至通过配置
一个尝试终止那些有问题的代码来找到内核的问题(kmalloc())的手段会让内核变的
不稳定。因此,对于那些development,experimental或debugging的特性,你应该使用
n来回答配置内核的问题。

编译内核:

-确保你使用的至少是gcc 3.2版本的。可从Documentation/Changes找到更多的信息。

请注意你仍能够在这个内核上运行一个a.out的用户程序。

-通过make命令来创建一个压缩的内核镜像。假如你已经将lilo在makefiles文件中写入编
译到内核中,你也可以使用make install命令,但是你首先要核查一下你特殊的lilo安
装。

为了实际上的安装,你不得不使用root账户,但是正常编译的情况下不会使用他。不要
滥用root账户。

-假如你将内核的某些部分作为模块配置了,你将不得不使用make modules_install命令。

-内核的编译/构建输出细节:

通常情况下,会以相当安静的模式构建系统内核(但是不是完全沉默)。所以,有时你
或者内核的开发者需要看一下编译,链接,或命令,如他们已经执行的命令是否正确。
对于这种情况,使用verbose的构建模式。通过在make命令中使用V=1来做到。例如:

make V=1 all

为了让构建系统也输出每个目标重新构建的原因,要使用V=2。默认的是V=0。

-以防万一,最好在手边有一个内核的备份。这对于开发版内核显得尤其重要,因为每个
新特性包含了还没有被充分修正漏洞的新代码。也要确保给你对应的内核的模块也做一
个相应的备份。假如你在安装和你目前正在工作的版本号相同的内核,在你要使用如他
make modules_install命令之前,要确保给你的模块目录做一个备份。

在编译前,另外一种方法是使用内核的配置选项LOCALVERSION来附加一个唯一的后缀到
常规的内核版本。LOCALVERSION能够在General Setup菜单中设置。

-为了启动你的新内核,你需要拷贝内核镜像(例如.../linux/arch/i386/boot/bzImage
编译后的镜像)到你的启动引导器能够找到的地方。

-能够不在一个启动器(如LILO)的帮助下来直接的启动一个内核已经不再支持了。

假如你从一个硬盘驱动器上启动Linux,碰巧使用启动器LILO,他通过在/ect/lilo.conf
这个文件中来指定使用的内核镜像。这个内核镜像文件通常是/vmlinuz,/boot/vmlinuz,
/bzImage或者/boot/bzImage。为了要使用新的内核,保存一份老镜像的拷贝,然后将新
的镜像拷贝过来覆盖这个旧的镜像。之后,你必须回到LILO去更新这个加载地图!假如
你不做,不会启动新的镜像。

通过运行/sbin/lilo去重新安装LILO通常是有麻烦的。在新的镜像不能正常工作时,你
能够通过编辑/etc/lilo.conf去指定旧的内核镜像(譬如,/vmlinux.old)。通过查看
LILO文档能够得到更多的信息。

假如重新安装LILO的话,你需要对所有选项进行设置。关闭系统,重启,好好享受吧!

假如你需要在内核镜像方面改变像默认的根设备,图形模式,ramdisk大小等等时,使用
rdev这个程序(或者适当的改变LILO的启动选项)。没有必要通过改变那些参数就能够
行得通的方法,却南辕北辙的为此重新编译内核。

-重新启动,享用新的内核吧。

如果出错的怎么办:

-如果你发现了kernel漏洞的问题,请核查一下MAINTAINERS文件去看看是否有关于你遇到
麻烦的那部分内核维护人员,从那里可以获得援助。假如没有任何人去维护,那么最好
把那些漏洞信息用邮件的方式发送给我(torvalds@linux-foudation.org)和任何相关
的邮件列表或者新闻组。

-在那些漏洞报告中,×请×看看你讲的是哪个内核,怎么去抄写这个的问题,和你的设置
是什么(使用你的常识,动动脑子)。【译者注:这里作者的意思是在进行报告前,先
要做一下自己的功课,而不是犯一些低级的错误。】如果问题是新的,那么就告诉我。
假如是个你第一次注意到的一个老问题,也请告诉我。【译者注:这是作者的自我调侃】

-假如在漏洞结果中有如同下面的信息

unable to handle kernel paging request at address C0000010
Oops:0002
EIP: 0010:XXXXXXXX
eax:xxxxxxxx ebx:xxxxxxxx ecx:xxxxxxxx edx:xxxxxxxx
esi:xxxxxxxx edi:xxxxxxxx ebp:xxxxxxxx
ds:xxxx es:xxxx fs:xxxx gs:xxxx
Pid:xx,process nr:xx
xx xx xx xx xx xx xx xx xx xx

或者在你的屏幕或系统日志中出现了类似的信息,请×精确地×抄写他。那些转储对于你
可能很难理解,但是他包含了帮助解决问题的信息。上面的那些转储信息也是重要的:
他透漏了内核代码为什么出现了问题(像上面的那个例子就是一个内核的指针错误)。
更多的关于内核转储的常识信息可以从/Documentation/oops-tracing.txt找到。

-假如你将CONFIG_KALLSYMS选项编译进入了内核,你也能够报告转储信息,否则你将不得
不使用ksymoops程序来理解那些转储信息(但是使用CONFIG_KALLSYMS进行编译通常是更
好的办法)。可从ftp:ftp..kernel.org/pub/linux/utils/kernel/ksymoops/
获得这个使用程序。另一种可选方案就是用手去查看转储信息。【译者注:作者调侃】

-在解决像上面的转储信息时,假如你能够查到EIP的值意味着什么,那将非常有帮助。那
些十六进制的值对我或者其他任何人都没有任何用处:因为这些值依赖于你自己的内核
设置。你应该做的是从这个EIP那一行(不要理睬0010)获得那个十六进制码,在内核的
名字列表中去查找他看一看是那个内核的函数包含这个违规的地址。

为了找出内核的函数名字,你需要找到与你的内核表现症状相关的的系统二进制文件。
这个文件就是linux/vmlinux。为了能够提取到内核名字列表,并从崩溃的内核中找到与
EIP相匹配的,如下:

nm vmlinux | sort | less

这将会给你一个以内核地址做排序的升序列表,从这个列表中能够找到那个包含了违规
地址的内核函数。注意那些由内核除虫信息给出的地址,没有必要同函数地址去精确的
匹配(事实上,那非常的不可能)。因此,你不能仅是grep那个列表:这个列表只是给
了你每个内核函数起始的指针。应该寻找那个符合条件的函数:这个函数有一个比你正
在寻找的地址更低的开始地址,而不是那个你正寻找的那个地址后面紧随的,有比这个
地址更高的地址的函数。事实上,在你的问题报告中包含一点context的文本是一个好的
注意,围绕这个兴趣点写上几行。

假如你不能按照上述(你有一个预先编译的内核镜像或者类似)介绍做到的话,那么尽
可能告诉我你的设置将会有帮助。请详细的阅读关于REPORTING-BUGS文档的详细内容。

-另一种可选方案,你能够在一个正在运行的内核(只读:例如,你不能改变值和设置断
点)上使用gdb。首先要在编译内核的时候使用了-g选线,才能使用gdb;适当的编辑文
件arch/i386/Makefile,然后执行make clean。你也需要启用CONFIG_PROC_FS(通过命
令make config)。

在你用新的内核重新启动之后,执行gdb vmlinux /proc/kcore。现在你能够使用所有的
通常的gdb命令。这个命令能够查询你系统的崩溃点是*0xXXXXXXXX(用EIP的值去替换这
个XXXes值)。

目前,gdb一个没有运行的内核会失败的,因为gdb对于已被编译的内核不能够找到其起
始地址的偏移。

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

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

make help

编译和配置内核,我们应该仔仔细细的给内核每一个选项都进行配置吗?当然可以,这有
助于你深入了解整个内核的架构和细节,但是,实用主义者不会想要将时间浪费在永无尽
头的盘山公路上,不如利用隧道直达目的地。

内核目录树中有一个scritp的目录,里面有shell,python,perl等脚本程序,以及许多
的makefile(看看makefile的后缀名)可解析的编译配置文件,看看那些makefile的后缀
名就知道是做些什么的,当然还有少许的C程序及头文件,这个目录里面的程序都是为内
核的配置和编译工作的。

在内核源代码目录树的根部执行一下make help就会列出你能够执行的目标选项,这些给
你帮助的信息其实是make程序解析makefile文件中的@echo语法而回显的。你可以在源码
根目录的Makefile文件中找到,但是make help回显的部分内容是依赖于你自己平台的。

我的make help

Cleaning targets:
clean - Remove most generated files but keep the config and
enough build support to build external modules
mrproper - Remove all generated files + config + various backup files
distclean - mrproper + remove editor backup and patch files

Configuration targets:
config - Update current config utilising a line-oriented program
nconfig - Update current config utilising a ncurses menu based
program
menuconfig - Update current config utilising a menu based program
xconfig - Update current config utilising a Qt based front-end
gconfig - Update current config utilising a GTK+ based front-end
oldconfig - Update current config utilising a provided .config as base
localmodconfig - Update current config disabling modules not loaded
localyesconfig - Update current config converting local mods to core
silentoldconfig - Same as oldconfig, but quietly, additionally update deps
defconfig - New config with default from ARCH supplied defconfig
savedefconfig - Save current config as ./defconfig (minimal config)
allnoconfig - New config where all options are answered with no
allyesconfig - New config where all options are accepted with yes
allmodconfig - New config selecting modules when possible
alldefconfig - New config with all symbols set to default
randconfig - New config with random answer to all options
listnewconfig - List new options
olddefconfig - Same as silentoldconfig but sets new symbols to their
default value
kvmconfig - Enable additional options for kvm guest kernel support
xenconfig - Enable additional options for xen dom0 and guest kernel support
tinyconfig - Configure the tiniest possible kernel

Other generic targets:
all - Build all targets marked with [*]

  • vmlinux - Build the bare kernel
  • modules - Build all modules
    modules_install - Install all modules to INSTALL_MOD_PATH (default: /)
    firmware_install- Install all firmware to INSTALL_FW_PATH
    (default: $(INSTALL_MOD_PATH)/lib/firmware)
    dir/ - Build all files in dir and below
    dir/file.[oisS] - Build specified target only
    dir/file.lst - Build specified mixed source/assembly target only
    (requires a recent binutils and recent build (System.map))
    dir/file.ko - Build module including final link
    modules_prepare - Set up for building external modules
    tags/TAGS - Generate tags file for editors
    cscope - Generate cscope index
    gtags - Generate GNU GLOBAL index
    kernelrelease - Output the release version string (use with make -s)
    kernelversion - Output the version stored in Makefile (use with make -s)
    image_name - Output the image name (use with make -s)
    headers_install - Install sanitised kernel headers to INSTALL_HDR_PATH
    (default: ./usr)

Static analysers
checkstack - Generate a list of stack hogs
namespacecheck - Name space analysis on compiled kernel
versioncheck - Sanity check on version.h usage
includecheck - Check for duplicate included header files
export_report - List the usages of all exported symbols
headers_check - Sanity check on exported headers
headerdep - Detect inclusion cycles in headers
coccicheck - Check with Coccinelle.

Kernel selftest
kselftest - Build and run kernel selftest (run as root)
Build, install, and boot kernel before
running kselftest on it

Kernel packaging:
rpm-pkg - Build both source and binary RPM kernel packages
binrpm-pkg - Build only the binary kernel package
deb-pkg - Build the kernel as a deb package
tar-pkg - Build the kernel as an uncompressed tarball
targz-pkg - Build the kernel as a gzip compressed tarball
tarbz2-pkg - Build the kernel as a bzip2 compressed tarball
tarxz-pkg - Build the kernel as a xz compressed tarball
perf-tar-src-pkg - Build perf-4.2.6.tar source tarball
perf-targz-src-pkg - Build perf-4.2.6.tar.gz source tarball
perf-tarbz2-src-pkg - Build perf-4.2.6.tar.bz2 source tarball
perf-tarxz-src-pkg - Build perf-4.2.6.tar.xz source tarball

Documentation targets:
Linux kernel internal documentation in different formats:
htmldocs - HTML
pdfdocs - PDF
psdocs - Postscript
xmldocs - XML DocBook
mandocs - man pages
installmandocs - install man pages generated by mandocs
cleandocs - clean all generated DocBook files

Architecture specific targets (x86):

  • bzImage - Compressed kernel image (arch/x86/boot/bzImage)
    install - Install kernel using
    (your) ~/bin/installkernel or
    (distribution) /sbin/installkernel or
    install to $(INSTALL_PATH) and run lilo
    fdimage - Create 1.4MB boot floppy image (arch/x86/boot/fdimage)
    fdimage144 - Create 1.4MB boot floppy image (arch/x86/boot/fdimage)
    fdimage288 - Create 2.8MB boot floppy image (arch/x86/boot/fdimage)
    isoimage - Create a boot CD-ROM image (arch/x86/boot/image.iso)
    bzdisk/fdimage*/isoimage also accept:
    FDARGS="..." arguments for the booted kernel
    FDINITRD=file initrd for the booted kernel

    i386_defconfig - Build for i386
    x86_64_defconfig - Build for x86_64

    make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build
    make V=2 [targets] 2 => give reason for rebuild of target
    make O=dir [targets] Locate all output files in "dir", including .config
    make C=1 [targets] Check all c source with $CHECK (sparse by default)
    make C=2 [targets] Force check of all c source with $CHECK
    make RECORDMCOUNT_WARN=1 [targets] Warn about ignored mcount sections
    make W=n [targets] Enable extra gcc checks, n=1,2,3 where
    1: warnings which may be relevant and do not occur too often
    2: warnings which occur quite often but may still be relevant
    3: more obscure warnings, can most likely be ignored
    Multiple levels can be combined with W=12 or W=123

Execute "make" or "make all" to build all targets marked with [*]
For further info see the ./README file

make help翻译

清楚目标:
clean - 移除大部分生成文件,但是保留配置文件和足够支持构建外部模块。
mrproper - 移除所有文件(包括生成文件+配置文件+各种备份文件)。
distclean - mrproper + 移除编译备份和补丁文件。

配置目标:
config - 利用面向行的程序来更新目前的配置文件。
nconfig - 利用ncurses菜单的基本程序来更新目前的配置文件。
menuconfig - 利用菜单的基本程序来更新目前的配置文件。
xconfig - 利用Qt基本前端来更新目前的配置文件。
gconfig - 利用GTK+基本前段来更新目前的配置文件。
oldconfig - 利用一个已被提供的.config配置文件为基础来更新目前的配置。
localmodconfig - 通过禁用的模块不加载来更新目前的配置文件。
localyesconfig - 通过将本地模块转换为核心来更新目前的配置文件。
silentoldconfig - 同oldconfig一样,但是更安静,另外更新依赖。
defconfig - 利用来自于ARCH提供的默认配置新建一个配置。
savedefconfig - 保存目前的配置作为./defconfig(最小配置)。
allnoconfig - 通过将所有的选项都用no回答的方式来新建一个配置。
allyesconfig - 通过将所有的选项都用yes回答的方式来新建一个配置。
allmodconfig - 如果可能的话编译为模块来新建一个配置。
alldefconfig - 所有的符号都被设置成默认来新建一个配置。
randconfig - 随机回答所有的选项来新建一个配置。
listnewconfig - 列出新的选项。
olddefconfig - 同silentoldconfig一样,但将新的选项设置为默认值。
kvmconfig - 启用那些为了支持kvm guest功能的内核选项。
xenconfig - 启用那些为了支持xen dom0和guest功能的内核选项。
tinyconfig - 尽可能的配置最小的内核。

其它通用目标:
all - 构建所有使用[*]标记的目标。

  • vmlinux - 构建裸内核。
  • modules - 构架所有模块。
    modules_install - 安装所有的模块到INSTALL_MOD_PATH(默认:/)。
    firmware_install - 安装所有的固件到INSTAL_FW_PATH
    (默认:$(INSTALL_MOD_PATH)/lib/firmware))。
    dir/ - 在dir目录及以下来构建所有文件。
    dir/file.[oisS] - 仅仅构建指定的目标。
    dir/file.lst - 仅仅构建指定(源码/汇编)混合目标。
    (需要一个最近的binutils和最近的build(Sytem.map))
    dir/file.ko - 构建包括最终链接的模块。
    modules_prepare - 为构建另外的模块做准备。
    tags/TAGS - 为编辑者生成tags文件。
    cscope - 生成cscope索引。
    gtags - 生成GUN GLOBAL索引。
    kernelrelease - 输出释放版本字符串(使用make -s)。
    kernelversion - 输出存在Makefil中的版本(使用make -s)。
    image_name - 输出镜像名(使用make -s)。
    headers_install - 安装内核头文件到INSTALL_HDR_PATH(默认:./usr)。

静态分析
checkstack - 生成函数栈列表。
namespacecheck - 在已编译内核上进行名字空间分析。
versioncheck - 关于version.h使用的合理核查。
includecheck - 核查关于被包含在内的头文件是否重复。
export_report - 列出那些所有导出符号的使用方法。
headers_check - 关于导出头的完整性检查。
headerdep - 检测头的包含周期。
coccicheck - 使用Coccinelle进行核查。

内核自测
kselftest - 构建并运行内核自测(使用root运行)
在运行kselftest之前需要构建安装和启动内核。

内核打包:
rpm-pkg - 构建带有源代码的二进制RPM内核包。
binrpm-pkg - 只构建二进制内核包。
deb-pkg - 构建一个deb包格式的内核。
tar-pkg - 构建一个无压缩的tar包内核。
targz-pkg - 构建一个gzip压缩格式的tar包内核。
tarbz2-pkg - 构建一个bzip2压缩格式的tar包内核。
tarxz-pkg - 构建一个xz压缩格式的tar包内核。
perf-tar-src-pkg - 构建perf-4.2.6.tar源代码的tar包。
perf-targz-src-pkg - 构建perf-4.2.6.tar.gz源代码tar包。
perf-tarbz2-src-pkg - 构建perf-4.2.6.tar.bz2源代码tar包。
perf-tarxz-src-pkg - 构建perf-4.2.6.tar.xz源代码tar包。

文档目标:
Linux内核不同格式的内部内核:
htmldocs - HTML
pdfdocs - PDF
psdocs - Postscript
xmldocs - XML DocBook
mandocs - man pages
installmandocs - 安装被mandocs生成的手册格式文档
cleandocs - 清楚所有生成的DocBook文件

指定架构目标(x86):

  • bzImage - 压缩内核镜像(arch/x86/boot/bzImage)
    install - 使用(your) ~/bin/installkernel或者
    (distribution) /sbin/installkernel或者
    将内核安装到$(INSTALL_PATH)并运行lilo
    fdimage - 创建一个1.4MB启动软盘镜像(arch/x86/boot/fdimage)
    fdimage144 - 创建一个1.4MB启动软盘镜像(arch/x86/boot/fdimage)
    fdimage288 - 创建一个2.8MB启动软盘镜像(arch/x86/boot/fdimage)
    isoimage - 创建一个启动CD-ROM镜像(arch/x86/boot/image.iso)
    bzdisk/fdimage*/isoimage也接受:
    FDARGS="..." 启动内核的参数
    FDINITRD=file 启动内核的initrd

    i386_defconfig -为i386平台构建
    x86_64_defconfig -为x86_64平台构建

    make V=0|1 [targets] 0 => 安静构建(默认),1 => 输出细节的构建
    make V=2 [targets] 2 => 对于重新构建目标给出原因
    make O=dir [targets] 将所有输出的文件(包括.config)指定输出到dir
    make C=1 [targets] 使用\(CHECK核查所有的C源代码 make C=2 [targets] 强制使用\)CHECK核查所有的C源代码
    make RECORDMCOUNT_WARN=1 [targets] 关于忽略的mcount段给予警告
    make W=n [targets] 启用额外的gcc核查,n=1,2,3分别表示
    1:出现相关的警告并且不会过于频繁的出现
    2:经常发生的警告,但是可能仍然有效
    3:比较隐晦的警告,最容易被忽视的
    多个级别能够联合使用,如W=12或者W=123

执行make或者make all来构建用[*]标记的所有构建目标。
更多的信息,可以查看这个./README文件

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

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

内核任务

内核是在硬件与软件之间的一个中间层。其作用是将应用程序的请求传递给硬件,并充当
底层驱动程序,对系统中的各种设备和组件进行寻址。

从应用程序的视角来看,应用程序朱旭发送传输数据的命令。实际的工作如何完成与应用
程序是不相干的,因为内核抽象了相关的细节。应用程序与硬件本身没有联系,至于内核
有联系,内核是应用程序所知道的层次结构中最底层。

当若干程序在统一系统中并发运行时,也可以将内核视为资源管理程序。在这种情况下,
内核负责将可用共享资源(包括CPU时间,磁盘空间,网络连接等)分配到各个系统进程,
同时还需要保证系统的完整性。

内核种类

微内核:只有最基本的功能直接有中央内核(即微内核)实现。所有其他的功能都委托给
一些独立进程,这些进程通过明确定义的通信接口与中心内核通信。如,独立进程可能负
责实现各种文件系统,内存管理等。

宏内核:内核的全部代码,包括所有子系统(如内存管理,文件系统,设备驱动程序)都
打包到一个文件中。内核中的每个函数都可以访问内核中所有其他部分。linux便是采用
宏内核的方式,但在系统运行中,模块可以插入到内核代码中,也可以移除,这使得可以
向内核动态添加功能,弥补了宏内核的一些缺陷。

进程

由于Linux是多任务系统,它支持(看上去)并发执行的若干进程。系统中同时真正在运
行的进程数目最多不超过CPU数目,因此内核会按照短的时间间隔在不同的进程之间切换
(用户是注意不到的)。内核借助于CPU的帮助,负责进程之间进行切换。内核还必须确
定如何在在现存进程之间共享CPU时间。重要进程得到的CPU时间多一点,次要进程得到的
少一点,确定那个进程运行多长时间的过程称为调度。

linu对进程采用了一种层次系统,每个进程都依赖于一个父进程。内核启动init程序作为
第一进程,该进程负责进一步的系统初始化操作,因此init是进程树的根,所有的进程都
直接或间接起源自该进程。

创建新进程的两种机制:fork和exec

fork可以创建当前进程的一个副本,父进程和子进程只有PID(进程ID)不同。linux使用
了一种叫做写时复制(copy on write)的级数来是fork操作更高效,主要的原理是将内
存复制操作延迟到父进程或子进程向某内存页面写入数据之前,在只读访问的情况下父进
程和子进程可以公用同一内存页。

exec是将一个新程序加载到当前进程的内存中并执行。旧程序的内存页将刷出,其内容将
替换为新的数据。然后开始执行新程序。

线程:本质上一个进程可能有若干线程组成,这些线程共享同样的数据和资源,但可能执
行程序中不同的代码路径。线程通常也称为轻量级进程。

linux用clone方法创建线程。其工作方式类似与fork,但启用了精确的检查,以确认哪些
资源与父进程共享,哪些资源为线程独立创建。这种细粒度的资源分配扩展了一般的线程
概念,在一定成都上允许线程与进程之间的连续转换。

命名空间:linux中对命名空间的支持被集成到了许多子系统中。这使得不同的进程可以
看到不同的系统视图。如linux进程ID中有唯一表示的全局变量,启用命名空间之后,以
前的全局资源可以具有不同的分组。每个命名空间可以包含一个特定的PID集合,或可以
提供文件系统的不同视图,在某个命名空间中挂载的卷不会传播到其他命名空间中。现在
最大的用处莫过于在容器上的应用了,人们不必在为每个用户准备一台物理计算机,而是
通过称为容器的命名空间建立系统的多个视图。从容器内部看来这是一个完整的linux系
统,而且与其他容器没有交互,这样事实上一台物理机器可以同时运转许多这个的容器实
例,有助于更有效地使用资源。与完全虚拟化解决方案(如KVM)相比,计算机上只需要
运行一个内核来管理所有的容器。

内存管理

地址空间的最大长度与实际可用的物理内存数量无关,这被称为虚拟地址空间。

2^10byte=1Kib 2^20byte=1Mib 2^30byte=1Gib
由于内存区域是通过指针寻址,因此对于32位系统是232byte=4Gib,对64位是264byte。

linux将虚拟地址空间划分为两个部分,分别称为内核空间和用户空间。
0到TASK_SIZE是用户进程的虚拟地址范围。
TASK_SIZE到232或264是内核空间。
TASK_SIZE是一个特定与计算机体系结构的常数,如对于32位系统能够寻址的虚拟地址空
间为4Gib,TASK_SIZE常数是3Gib处,用户空间为0-3Gib,内核空间为3-4Gib。这种划分
与可用的内存数量无关。由于地址空间虚拟化的结果,每个用户进程都认为自身有3Gib的
内存,各个系统进程的用户空间是完全彼此分离的,而虚拟地址空间顶部的内核空间总是
同样的,这样内核的地址空间总是不会和用户地址空间混在一起,这样用户态和内核态之
间的分离的这种机制可防止进程无意间修改彼此的数据而造成相互干扰。

从用户状态到核心态的切换通过系统调用的特定手段完成。
普通进程只能借助于系统调用向内核发出请求,内核首先检查进程是否允许执行想要的操
作,然后代表进程执行所需的操作,接下来返回到用户状态。

虚拟地址如何映射到物理地址上呢?一一对应,显然不行。
内核将地址空间划分为很多等长的部分,这些部分称之为页。这样虚拟地址空间和物理地
址空间都能够用同样大小的页来表示,通过页来产生一一对应的关系,这样虚拟地址空间
就能和物理地址空间映射到一起。由于内核负责将虚拟地址空间映射到物理地址空间,因
此可以决定那些内存区域在进程之间共享,那些不共享,这样在内核的管控下,内核空间
和用户空间的进程内存仍然是分离的,不会造成混乱。

用来将虚拟地址空间映射到物理地址空间的数据结构称为页表。为减少页表的大小并容许
忽略不需要的区域,linux将虚拟地址划分成几部分,采用四级页表的管理方式,如同用
树形的数据结构可以快速寻找到自己所需要的内存块,如果你知道算法是怎么一回事的话,
这里应该很容易理解,如果有困难的话,自己做一下功课,这里不打算展开讲。

每次访问内存时,必须逐级访问多个数组才能将虚拟地址转换为物理地址,CPU则使用如
下的方法来加速该过程:
1)CPU中有一个专门的部分称为MMU(Memory Management Unit,内存管理单元),该单
元优化了内存访问操作。
2)地址转换中出现最频繁的那些地址,保存到称为地址转换后备缓冲器(TLB)的CPU高
速缓存中。无需访问内存中的页表即可从高速缓存直接获得地址数据。

内核在实现设备驱动程序时直接使用了内存映射。外设的输入/输出可以映射到虚拟地址
空间的区域中。对相关内存区域的读写会由系统重定向到设备,因而大大简化了驱动程序
的实现。

伙伴系统:内核中很多时候要求分配连续页。为快速检测内存中的连续区域,内核使用了
一种叫做伙伴系统。
系统中的空闲内存块总是两两分组,每组中的两个内存块称为伙伴。伙伴的分配可以是彼
此独立的。但如果两个伙伴都是空闲的,内核会将其合并为一个更大的内存块,作为下一
层上某个内存块的伙伴。
内核对所有大小相同的伙伴,都防止到同一个列表中管理。
例如:如果系统想在需要8个页帧,则将16个页帧组成的块拆分为两个伙伴。其中一块用
与满足应用程序的请求,而剩余的8个页帧则放置到对应8页大小内存块的列表中。

slab缓存:伙伴系统作为一种在使用比页帧大的内存的一种解决办法,但是,内核本身经
常需要比完整页帧小的多的内存块时就是一个问题了。这时,内核将伙伴系统提供的页划
分为更小的部分,还对频繁使用的小对象实现了一个一般性的缓存(slab缓存)。
slub则是slab的一个替代品,但是slub完全兼容slab。slub的效率更高更简单。

页面交换:页面交换通过利用磁盘空间作为扩展内存,从而增大了可用的内存。在内核需
要更多内存时,不经常使用的页可以写入硬盘,如果再需要访问相关数据,内核会将相应
的页切换回内存。

页面回收用于将内存映射被修改的内容与底层的块设备同步,有时也称为数据回写。

系统调用

系统调用是用户进程与内核交互的方法。传统的系统调用按不同类别分组,如下:

进程管理:创建新进程,查询信息,调试。
信号:发送信号,定时器以及相关处理机制。
文件:创建,打开和关闭文件,从文件读取和向文件写入,查询信息和状态。
目录和文件系统:创建,删除和重命名目录,查询信息,链接,变更目录。
保护机制:读取和变更UID/GID,命名空间的处理。
定时器函数:定时器函数和统计信息。

设备驱动

设备驱动程序用于与系统连接的输入/输出装置通信,如硬盘,声卡,各种接口等,UNIX
中的一切皆为文件(everything is a file),对外设的访问可利用/dev目录下的设备文
件来完成,程序对设备的处理完全类似与常规的文件。设备驱动程序的任务在与支持应用
程序经由设备文件与设备通信。就是,是的能够按适当的方式在设备上读取/写入数据。

字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。此类设备
支持按字节/字符来读写数据。调制解调器是典型的字符设备。

块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型
的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据,另外,数据的读写只
能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。

编写块设备的驱动程序比字符设备要复杂得多,因为内核为提高系统性能广泛地使用了缓
存机制。

网络

网卡也可以通过设备驱动程序控制,但在内核中属于特殊状况,因为网卡不能利用设备文
件访问。原因在于在网络通信期间,数据打包到了各种协议层中。在接收到数据时,内核
必须针对个协议层的处理,对数据进行拆包与分析,然后才能将有效数据传递给应用程序。
在发送数据时,内核必须首先根据各个协议层的要求打包数据,然后才能发送。

为支持通过文件接口处理网络连接,linux使用了源于BSD的套接字抽象。套接字可以看作
应用程序,文件接口,内核的网络实现之间的代理。

文件系统

文件系统使用目录结构组织存储数据,并将其他元信息(例如所有者,访问权限等)与实
际数据关联起来。

linux系统中支持各种各样的文件系统,这些文件系统的功能各不相同,这得益于linux提
供一个额外的软件层(VFS,Virtual Filesystem,虚拟文件系统),将各种底层文件系
统的的具体特性与应用层隔离开来。

VFS既是向下的接口(所有文件系统都必须实现该接口),同时也是向上的接口(用户进
程通过系统调用最终能够访问文件系统功能)。

模块和热插拔

模块用于在运行时动态地向内核添加功能,如设备驱动程序,文件系统,网络协议等,实
际上内核的任何子系统几乎都可以模块化。这消除了宏内核与微内核相比的一个缺陷。模
块还可以在运行时从内核卸载。

模块对于开发新功能的调试有很大的帮助,如果没有模块特性的话,为了添加和修改代码
后的新功能,要重新将这个宏内核编译一边,对于编译过内核的开发者来说,这多么的浪
费时间应该是不言而喻的。

模块的本质上不过是普通的程序,只是在内核空间而不是用户空间执行而已。模块必须提
供某些代码某些代码段在模块初始化(和终止)时执行,以便向内核注册和注销模块。另
外,模块代码与普通内核代码的权利(和义务)都是相同的,可以像编译到内核中的代码
一样,访问内核所有的函数和数据。

对于热插拔而言,模块在本质上是必需的。某些总线允许在系统运行时连接设备,而无需
系统重启。在系统检测到新设备时,通过加载对应的模块,可以将必要的驱动添加到内核
中。

模块特性使得内核可以支持种类繁多的设备,而内核自身的大小却不会发生膨胀。在检测
到连接的硬件后,只需要加载必要的模块,多余的驱动程序无需加入到内核。

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