ELF加载器的原理与实现

ELF加载器

为什么需要ELF

隔离应用服务和内核服务
有了ELF,我们的应用程序都可以通过编译成ELF的方式从外部加载,系统内核部分只提供关键的内核服务(内存管理/中断/调度/IPC)和系统服务(文件/网络),用户服务程序都可以通过ELF的方式进行加载。同时库的同步更新可以在不更新内核的情况进行同步更新。

提供统一编程接口
我们通过提供统一的POSIX标准库,来为用户提供标准的编程接口,方便了应用程序开发人员进行标准开发

应用程序可以动态加载和卸载
通过ELF,用户可以动态的加载和卸载相关服务,增强了应用程序加载的灵活性

ELF加载方式

静态库-独立exec

我们把所有相关源代码进行编译,链接,最后生成可执行文件,这个文件不依赖于其他模块,是一个完整的可执行单元。
操作系统处理这类文件的流程是直接将elf的所有段拷贝到内存中,然后将PC指针指向entry就可以运行了,什么场景下会这样使用呢?

  • 简单的应用程序,不依赖于其他模块
  • 依赖于其他模块,其他模块以静态库的方式链接到应用程序

优点:程序是一个完整的可执行单元,不需要操作系统去进行重定向操作
缺点:每一个依赖于某个静态库的应用程序都会包含完整的静态库,这样每一个应用程序都会占用磁盘空间和内存空间,如果这样的应用程序有成千上万,那资源浪费很大

动态库-非独立exec

非独立exec我们这里只讲依赖于动态库的应用程序,一般情况我们在编写应用程序的时候,会用到很多库,这些库是其他工程师已经写好了,我们直接用就可以了,最典型的就是C和C++库,我们在写应用程序的时候只需要关注我们自己的业务功能就可以了。
外部库的存在方式以静态和动态2种,静态库在上面已经分析了,接下来我们着重分析动态库
如果我们的C库已经编译成了动态库,那么我们把他链接到我们的应用程序的时候,编译器只会对所需C库的符号进行分析,并把他记录到rel.dyn里,并不会真正的把动态库中的具体内容进行拷贝,所以这样编译出的应用程序就不包括动态库的内容,等到将应用程序真正加载到操作系统去运行的时候,我们操作系统中会有一个动态链接器dl去完成外部符号的链接与重定位
优点:
应用程序依赖的动态库在内存中只存在一份,当然数据段是每个应用程序私有的,这样会节约磁盘空间和内存空间
当我们要更新动态库时,只需要更新动态库文件即可,所依赖的应用程序不需要单独更新

缺点:
应用程序的启动会牺牲一部分启动时间
动态库更新需要考虑兼容性问题

静态库

编译方式

静态库将库源文件打包成.a,然后main链接到静态库,形成可执行文件exe,这里的链接实际上就是把.a的代码和数据联合main打包到exe里。

编译流程

在这里插入图片描述

编译脚本
$(CC) -MD -c $(CFLAGS)  %.c -o %.c.o
$(AR) cr libxxx.a %.c.o

动态库

编译方式

动态库以.so的方式呈现,然后main链接到动态库,形成可执行文件exe,注意这里的链接并非真正的链接,并没有把.so的代码和数据拷贝到exe里,只是标记了引用的外部符号表,所以exe的大小并没有变大。

编译流程

在这里插入图片描述

编译脚本
$(CC) -MD -c $(CFLAGS)  %.c -o %.c.o
$(CC) -shared -fPIC -nostartfiles -o libxxx.so %.c.o

ELF格式分析

一个可执行文件我们可以从两个视角去剖析:

  • 链接视图:section - 节
  • 执行试图:segment - 段

在这里插入图片描述

读取elf信息
readelf -a xxx > xxx.dump
section

一个elf文件,由多个section组成,这个可以linker.lds里指定节的名称和地址,一般来说一个elf是由多个section组成的,如下图:
在这里插入图片描述

从上图可以看出,此elf由11个段组成,其中最中要的段有:

  • text
  • rodata
  • data
  • bss

在这里插入图片描述

从section(节)的角度上,我们可以看出一个elf的基本组成,以及每个节的名称,类型,运行地址,大小,属性等。从上面的例子中我们可以得到:

  • text:代码节大小为0x55e0,运行地址为0x41000000,在文件中的偏移为0x10000(64K对齐)。text包含elf的所有指令,有些链接脚本把rodata也合并到此节
  • data:数据节大小为0x470,运行地址为0x410055e8,在文件中的偏移为0x155e8。data包含已经初始化的数据,这些数据必须像指令一样保存在文件中
  • bss:bss节大小为0x238,运行地址为0x41005a58,在文件中的偏移为0x15a58。因为bss是段是未初始化和初始化为0的节,所以在文件中实在是没有必要存储一堆0,只需要保存好bss大小,在elf加载的时候再手动清零即可,这样可以减少elf文件的空间
解析header

上面的section是我们通过readelf命令读取得到的,在elf里专门有区域来存储所有section的分布信息,我们通过header信息就可以轻松得到:
首先来看一下header结构:
在这里插入图片描述

重要的数据成员:

  • e_entry:程序的入口地址,一般为main函数的地址
  • e_phoff:程序头在文件中的偏移,用于解析segment信息
  • e_shoff:节头在文件中的偏移,用于解析section信息
  • e_phnum:程序头个数
  • e_shnum:节头个数
  • e_phentsize:一个程序头的大小,32位下默认固定为32字节
  • e_shentsize:一个节头的大小,32位下默认固定位40字节

程序头
在这里插入图片描述

节头
在这里插入图片描述

刚刚上面的section信息,我们就可以读取ELF文件的前128字节来解析ELF头部,根据section的偏移,再解析就可以得到section信息了,同样segment信息也是如此

segment

segment就是我们熟悉的段,我们一般在谈论一个程序加载的过程,我们希望用segment来描述,一个segment可以包含多个section,还是之前的那个elf文件,我们来分析一下他在段的视角下是如何组成的。
在这里插入图片描述

这个elf由2个段组成,我们只关注LOAD段,LOAD段表示此段是需要加载到内存运行的,我们可以看到LOAD段的大小分为了两部分,FileSiz和MemSiz,我们先放一下,我们先关注FileSiz,也就是LOAD段实际在文件中占用了多大空间,0x5a58,我们从section视图可以看到0x5a58刚好就是data节的结束地址。由此我们可以得到FileSiz,是text+rodata+data,我们再看MemSiz,MemSiz表示此段在内存中所占用的空间,前面我们说到bss段是不需要占用文件空间的,但是他需要占用内存空间,而且需要手动进行清0操作,所以我们可以看到MemSiz的大小为FileSiz+bss_size。再看此段的属性,为RWE说明此段可读可写可执行,包含了代码数据加BSS。

如何加载ELF

静态库加载

这里说的静态库加载指的是程序链接到静态库生成的exe文件的加载,静态库的加载相对动态库要简单很多,因为全部工作已经由编译器的连接器ld给我们完成了,我们要做的只是把ELF文件map到相应运行地址空间,然后把PC指向entry即可。

  1. map我们可以理解为copy,因为每一个elf都具有相同运行的地址空间,这个可以通过链接脚本指定,上面的elf的运行地址空间从0x41000000开始,很明显这个地址是个虚拟地址,也就是说,我们要把elf文件映射到这个地址空间上去。这里就需要解决一个问题,在多个ELF加载的过程中,需要把相同运行地址空间映射到不同的物理地址空间,这样才能保证每个ELF的代码和数据是相互独立的。这个概念和多进程有点类似哦。此处不多讲,这涉及到地址空间管理的问题,总之,需要内核提供这样一个接口。
  2. 究竟拷贝哪些内容呢?我们之前的segment现在可以出马了,segment里记录了我们需要拷贝的内容的全部信息,在静态库加载中,我们直接把属性为RWE的LOAD段拷贝到0x41000000,即可,需要注意的是,需要手动把bss清0。
  3. 将PC指向main
静态库加载流程图

在这里插入图片描述

动态库加载

这里说的动态库与加载指的是程序链接到动态库生成的exe文件的加载,动态库的加载比静态库要复杂很多,程序在链接阶段,只是给我们初步进行了一个假链接,我们需要自己在程序加载的时候再去链接动态库,简单来说,我们需要自己实现一个链接器linker,把主模块和动态库链接起来。

我们对比一下静态库和动态库在进程地址空间中的分布:
在这里插入图片描述

静态库很简单,直接把数据映射到text开始的地方就可以了,动态库的映射缺分成了2部分,动态库除了节省磁盘空间之外还具有共享属性,这个属性可以节约内存空间,尤其是在多个ELF加载同一个共享可以的同时,我们只需要映射一次即可。动态库的映射除了本身主模块的映射之外,还需要把共享库可以映射到进程的共享库中去,如上图,主模块被映射到0x41000000开始,共享库被映射到0x5D000000,我们要做的就是建立主模块和共享库之间的连接(主模块需要引用动态库中的函数和数据),也就是重定位工作,这也是我们实现链接器的关键。

共享库的关键:共享库的代码段是被所有ELF共享的,数据段对于每个ELF来说是相互独立的,linux下通过cow(写时拷贝)技术实现这一机制,从根本上来说,是通过缺页异常来实现。

实现自己的链接器

链接器最重要的工作就是重定位,主要包含主模块重定位和共享库重定位两项工作,后面我们都称为main_relocate和libary_relocate。这两者有何区别?

main relocate

main relocate主要完成的工作是把引用的外部函数和变量的地址更新到got.plt表中去,这样才能完成函数跳转工作
在这里插入图片描述

library relocate

library relocate主要处理的是共享库内部的函数跳转和变量引用,共享库最后被编译成位置无关,各个源文件之间的函数跳转(非static函数)和变量引用(非static变量)都需要重定位后才能进行加载运行,重定位的类型和CPU相关,ARM下的重定位类型主要有如下:

在这里插入图片描述

链接流程

我们先来梳理一下链接器的流程:
在这里插入图片描述

判断ELF是否需要链接器

我们读取一个包含共享库的elf文件:
在这里插入图片描述

在程序头中,我们发现了INTERP,证明这个elf是需要链接器去进行重新链接共享库。同时,一般包含了INTERP,同时也会有DYNAMIC段,从INTERP可以看出,ELF需要执行/usr/lib/ld.so.1来进行链接,在我们的系统中,我们的链接器(解释器)是被编译到系统服务中去的,所以就不用执行链接器本身的自举操作了,在Linux里,链接器本身还要完成自举操作,要复杂一些。
段类型:
在这里插入图片描述

查看ELF依赖哪些动态库

经过上面的链接器检查,如果检查出ELF确实依赖了一些库,那究竟依赖于哪些库呢?这个信息从哪里可以解析出来呢?我们的dynamic段该上场了,dynamic段里存储了相关信息,我们先截取看一下:
在这里插入图片描述

首先我们可以看到,在程序头中,我们可以看到新增加了DYNAMIC段,并标记了DYNAMIC的属性,大小,偏移等。
我们首先来看看dynamic段的数据结构:
在这里插入图片描述

我们再看看有哪些类型的动态节:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

类型确实是有点太多了,先大概扫一眼

我们要解析出ELF依赖于哪些库,需要借助 NEEDED 和 STRTAB,STRSZ 来解析,下面流程图表示了解析依赖库的流程:
在这里插入图片描述

之前的解析图中我们可以看出此ELF依赖于libdl.so和libmath.so,这个依赖信息是如何生成的呢?回想一下我们在之前提到的主模块在链接动态库的时候,我们说是个假链接,意思就是说只是给生成的exe文件打包上了相关依赖库和重定向信息,所以我称为假链接,我们看一下ld信息:

LINK:    elf_test 
arm-none-eabi-ld --warn-common --gc-sections -e main -T./linker.lds -L./libs/libdl -L./libs/libmath -Map elf_test.map -o elf_test --start-group elf_loader_test.c.o -ldl -lmath --end-group

其中的 -ldl -lmath,就会生成依赖信息

主模块调用共享库里的函数接口

主模块程序如下:

/*****************
 *elf_test.c
 ****************/
#include <dl.h>
int main()
{
    int sum = dl_sum(1, 2);
    return 0;
}

共享库程序如下:

/*****************
 *dl.c
 ****************/
#include <dl.h>
int dl_sum(int a, int b)
{
	return a+b;
}

把dl.c编译成动态库libdl.so:

$(CC) -MD -c $(CFLAGS)  dl.c -o dl.c.o
$(CC) -shared -fPIC -nostartfiles -o libld.so dl.c.o

在main中链接libdl.so:

$(LD) $(LDLAGS) -e main -T./linker.lds -L./libs/libdl -o elf_test elf_loader_test.c.o -ldl

linker.lds如下:

ENTRY(main);

SECTIONS{
    . = 0x41000000;
    .text :
    {
        *(.text .text.*)
        *(.rodata .rodata.*)
    }
	
    .data :
    {
        *(.data .data.*)
    }
	
    .bss :
    {
        *(.bss .bss.*)
    }
}

我们反汇编生成的exe文件:

$(OBJDUMP) -x -D -S elf_test > elf_test.dump

我们定位到我们调用dl_sum处:
在这里插入图片描述

因为dl_sum是动态库里的,代码自然也在动态库里,那么在exe里,他跳转到了0x4100014c这个地址处:
在这里插入图片描述

这个是plt表,最后他会跳转到一个新的地址上,那这个新地址是多少呢?

这些外部函数究竟该跳转到哪里?

main里调用的所有外部动态库函数,都存放在rel.dyn节里,我们来看一下:
在这里插入图片描述

我们来分析一下rel.dyn的数据结构:
在这里插入图片描述

r_info里保存着外部变量和函数的信息:

#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t))
// index为外部符号在动态符号表中的索引
index = ELF32_R_SYM(rel->r_info);
// type为外部符号表的类型:函数/变量/......
type = ELF32_R_TYPE(rel->r_info);

找到所需外部符号表的信息流程如下:
在这里插入图片描述

总结一下:根据rel.dyn节找到所有依赖外部动态库的符号,根据符号名搜索外部库找到符号真正的地址,然后进行重定向,重定向实际上就是,修改main中的.got.plt节把他的地址指向真正的地址就可以了。这样函数在跳转流程如下:
main->plt->rel.dyn->got.plt

从外部动态库搜索符号

main 中 用到了dl_sum符号,那么如何从外部动态库中得到该符号的真正地址呢?
搜索方式有2种:字符串匹配 和 hash匹配,在符号量大的情况下建议使用hash匹配,搜索效率会比较高,下面简要介绍一下字符串匹配的流程:
在这里插入图片描述

我们看一下dl_sum的在libdl.so的地址是多少:
在这里插入图片描述

注意:这里的地址是0x790,只是一个相对偏移地址,重定向的时候需要加上一个共享库的加载地址进行重定向。

未完成的工作

前面我们完成了main的重定向,我们还没有对动态库做任何处理,显然还有一步没有完成,就是把动态库的内容映射到进程的地址空间里,不然我们重定向后,还是无法正常跳转到dl_sum

我们同样readelf来看一下动态库的基本信息:
在这里插入图片描述

动态库也是一种类型的elf,他的type为DYN,其他信息的含义和exe并无区别

在这个动态库里包含了很多动态段,我们先放一下,如果你的动态库,没有涉及到数据段和函数间的引用的话,你只需要把代码段映射到进程的共享地址空间即可,如果你的动态库里涉及到了数据段和函数调用就需要再次对库进行重定位。

如何对共享库进行重定位
static函数和变量

静态函数和变量不需要进行重定位

非静态全局变量

全局变量在库中的访问,是通过间接访问得到:
源代码:

int dl_data1 = 0x3;
int get_dl_data()
{
	return dl_data1;
}

反汇编:

000006e0 <get_dl_data>:
 6e0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
 6e4:	e28db000 	add	fp, sp, #0
 6e8:	e59f201c 	ldr	r2, [pc, #28]	; 70c <get_dl_data+0x2c>
 6ec:	e08f2002 	add	r2, pc, r2
 6f0:	e59f3018 	ldr	r3, [pc, #24]	; 710 <get_dl_data+0x30>
 6f4:	e7923003 	ldr	r3, [r2, r3]
 6f8:	e5933000 	ldr	r3, [r3]
 6fc:	e1a00003 	mov	r0, r3
 700:	e28bd000 	add	sp, fp, #0
 704:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
 708:	e12fff1e 	bx	lr
 70c:	0001029c 	muleq	r1, ip, r2
 710:	00000014 	andeq	r0, r0, r4, lsl r0

在这里插入图片描述

从反汇编中,我们可以看到最后从0x109a4的地址中取出全部变量的地址,然后再ldr r3, [r3],得到全局变量的值。我们找到0x109a4的地址,发现其值为0,说明此地址是需要我们进行重定位的,我们找到dl_data真正的地址为0x109c8:
在这里插入图片描述

那么重定位就是把0x109a4地址处的值修改为0x109c8,这样就能正确访问全局变量了。
我们发现0x109a4是位于got节中的,所以我们得到,got节中应该保存着所有需要进行重定位的全局变量的地址。
在rel.dyn里记录下了所有变量所需重定向的信息:
在这里插入图片描述

非静态bss

非静态bss段的重定位和非静态的全部变量一样

全局函数

全局函数在库中的访问是通过延迟绑定来实现的:

// dl.c
int dl_test()
{
	return get_dl1_data();
}
// dl1.c
int dl1_data = 0x35;
int get_dl1_data()
{
	return dl1_data;
}

在这里插入图片描述
在这里插入图片描述

同样,最终会跳转到got表中去,我们需要对got表中的函数进行重定位:
再rel.plt节里记录了所有延迟绑定的函数:
在这里插入图片描述

Offset表示重定向的源地址,Sym.Value表示真实的函数地址

共享库重定位

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

R_ARM_ABS32,R_ARM_GLOB_DAT,R_ARM_JUMP_SLOT,都直接把offset修改为sym.value即可,R_ARM_RELATIVE比较特殊,需要把offset地址处的值修改为load_addr + [offset]

共享库重定位流程

在这里插入图片描述

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介   本书从只有二十行的引导扇区代码出发,一步一步地向读者呈现一个操作系统框架的完成过程。书中不仅关注代码本身,同时关注完成这些代码的思路和过程。本书不同于其他的理论型书籍,而是提供给读者一个动手实践的路线图。读者可以根据路线图逐步完成各部分的功能,从而避免了一开始就面对整个操作系统数万行代码时的迷茫和挫败感。书中讲解了大量在开发操作系统中需注意的细节问题,这些细节不仅能使读者更深刻地认识操作系统的核心原理,而且使整个开发过程少走弯路。本书分上下两篇,共11章。其中每一章都以前一章的工作成果为基础,实现一项新的功能。而在章的内部,一项大的功能被分解成许多小的步骤,通过完成每个小的步骤,读者可以不断获得阶段性的成果,从而让整个开发过程变得轻松并且有趣。   本书适合各类程序员、程序开发爱好者阅读,也可作为高等院校操作系统课程的实践参考书。 序   做真正 Hacker的乐趣──自己动手去实践   2004年我听编辑说有个年轻人写了本《自己动手写操作系统》,第一反应是不可能,恐怕是翻译稿,写这种书籍是要考作者硬功夫的,不但需要深入掌握操作系统原理,还需要实际动手写出原型。   历史上的 Linux就是这么产生的,Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的雏形。   我想中国有能力写出内核原型的程序员应该也有,但把这个题目写成一本书,感觉上不会有人愿意做这件事情,作者要花很多时间,加上主题比较硬,销售量不会太高,经济上回报有限。   但拿来文稿一看,整个编辑部大为惊艳,内容文笔俱佳,而且绝对原创,马上决定在《程序员》连载。2005年博文视点出版的第一版也广受好评。   不过有很多读者还是质疑:现在软件编程主要领域是框架和应用,还需要了解操作系统底层吗?   经过四年的磨练成长,于渊又拿出第二版的书稿《Orange'S:一个操作系统实现》,这本书是属于真正 Hacker的。我虽然已经有多年不写代码了,但看这本书的时候,让我又重新感受到做程序员的乐趣:用代码建设属于自己的系统,让电脑听从自己的指令,对系统的每个部分都了如指掌。   黑客(hacker)实际是褒义词,维基百科的解释是喜欢用智力通过创造性方法来挑战脑力极限的人,特别是他们所感兴趣的领域,例如软件编程或电气工程。个人电脑、软件和互联网等划时代的产品都是黑客创造出来的,如苹果的 Apple电脑、微软的 Basic解释、互联网的 Mosaic浏览。   回答前面读者的质疑,学软件编程并不需要看这本书,想成为优秀程序员和黑客的朋友,我强烈建议你花时间来阅读这本书,并亲自动手实践。正如于渊在本书结尾中所说“我们写自己的操作系统是出于一种好奇,或者说一种求知欲。我希望这样不停地‘过把瘾’能让这种好奇不停地延续”。   好奇心是动力的源泉,追究问题的本质是优秀黑客的必备素质,只有充分掌握了系统原理,才能在技术上游刃有余,才能有真正的创新和发展。中国需要更多真正的黑客,也希望更多的程序员能享受属于黑客的创造乐趣。   蒋涛   2009年 4月 作者自序   本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从一个简单的引导扇区开始,讲述一个操作系统成长的故事,以作读者参考之用。   本书面向实践,通过具体实例教读者开发自己的操作系统。书中的步骤遵循由小到大、由浅入深的顺序,跟随这些步骤,读者可以由一个最简单的引导扇区开始,逐渐完善代码,扩充功能,最后形成一个小的操作系统。   本书不仅介绍操作系统的各要素,同时涉及开发操作系统需要的各个方面,比如如何建立开发环境、如何调试以及如何在虚拟机中运行等。书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书中,而这是传统的操作系统实践书籍经常忽略的。总之,只要是开发自己的操作系统中需要的知识,书中都尽量涉及,以便于读者参考。   众所周知,一个成型的操作系统往往非常复杂。如果考虑到操作系统作为软硬件桥梁的特殊地位,那么它可能看上去比一般的软件系统更难理解,因为其核心部分往往包含许多直接针对CPU、内存和 I/O端口的操作,它们夹杂在一片代码汪洋之中,显得更加晦涩。   我们有许多源代码公开的操作系统,可供随时下载和阅读,看上去好像让实现一个供自己把玩的微型操作系统变得容易很多,但事实往往不尽人意,因为这些代码动辄上万甚至几十几百万行,而且细节之间经常互相关联,要理解它们着实不易。我们有许多容易得到的操作系统教程,但读来好像总觉得跟我们有隔膜,不亲近。造成这些的根本原因,在于学习者一开始就面对一个完整的操作系统,或者面对前辈们积累了几十年的一系列理论成果。而无论作者多么擅长写作,读者多么聪明,或者代码多么优秀,要一个初学者理清其中的头绪都将是非常困难的。   我并非在此危言耸听,因为这曾经是我的亲身体会。当然,如果只是为了考试,几本操作系统理论书籍就足够了,你不需要对细节那么清楚。但如果是出于兴趣呢?如果你是想编写自己的操作系统呢?你会发现理论书籍好像一下子变得无用武之地,你会发现任何一个细节上的理解错误都可能导致自己辛辛苦苦编写的代码运行异常甚至崩溃。   我经历过这一切!我曾经翻遍了一本《操作系统:设计与实现》,也没有找到实现一个操作系统应该从何处着手。并不是这些书不好,也不是前人的代码不优秀,而是作为一无所知的初学者,我们所不了解的不仅是高居庙堂的理论知识,还有让我们举步维艰的实践细节。   可能在这些教科书作者的眼里,操作的细节不属于课程的一部分,或者这些细节看上去太容易,根本不值一提,甚至作者认为这些属于所谓“经验”的一部分,约定俗成是由读者本人去摸索的。但是实际情况往往是,这些书中忽略掉的内容恰恰占去了一个初学者大部分的时间,甚至影响了学习的热情。   我至今仍记得当我开始编写自己的操作系统时所遭受的挫败感,那是一种不知道如何着手的无助的感觉。还好我坚持了下来,克服了各种困难,并完成了自己的操作系统雏形。   进而我想到,一定不只是我一个人对编写自己的操作系统怀有兴趣,也一定不只是我一个人在实践时遇到困难。或许我应该把自己的经历写下来,从而可以帮助跟我相似的后来者,就这样,我编写了本书的第一版,也就是《自己动手写操作系统》。我相信,如果你也对神奇的计算机世界充满好奇,并且希望通过自己编写操作系统的方式来了解背后发生的故事,那么你一定可以在这本书中得到一些帮助。而假如你真的因为我的书而重新燃起实践的热情,从而开始一段操作系统旅程,我将会感到非常高兴。   不过我得坦白,在写作《自己动手写操作系统》的时候,我并不敢期待它能引起多少反响,一方面因为操作系统并不是时尚的话题,另一方面我也是走在学习的路上,或许只是比读者早走了一小步而已。然而出乎我的意料,它面世后重印多次,甚至一度登上销量排行榜的榜首,这让我觉得它的确有一定的参考价值,我要借此机会感谢所有支持我的读者。   在我写作《自己动手写操作系统》的时候,并没有想过今天会有一个第二版。原因在于,我希望这本书是用来填补空白的,而不是重复去做别人已经做得很好的事情。所谓填补空白,具体说就是让像我一样的操作系统爱好者在读完本书之后,能够有信心去读其他比较流行的开源的操作系统代码,有能力从零开始自己动手写操作系统,而这个任务第一版已经完成了。   那么为什么我又写作了第二版呢?原因有几个方面。第一,虽然第一版未曾涉及的进程间通信、文件系统等内容在许多书中都有讲解,但阅读的时候还是感觉有语焉不详的通病,作者本人可能很清楚原委,但写得太简略,以至于读者看来未必清晰。第二,我自己想把这个圈画圆。第一版的书虽然完成了它的使命,但毕竟到书的结尾,读者看到的不是一个真正的操作系统,它没有文件系统,没有内存管理,什么也干不了。在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二版中,仍然试图把话说细一点,把自己的经验拿出来分享。而且我选择我能想到的最精简的设计,以便让读者不至于陷入太多细节而无法看到全貌。我想这是本书可能具有的价值所在──简化的易懂的设计,还有尽量详细的文字。   在这一版中,内容被划分成上下两篇。上篇基本上是第一版的修订,只是做了一个调整,那便是在兼顾 Windows和Linux两方面用户的基础上,默认在Linux下建立开发环境来编写我们的操作系统。至于这样做的原因,在本书第 2章有比较详细的说明。当然,开发环境毕竟是第二位的,书中讲述的内容以及涉及的代码跟第一版都是一致的。本书的下篇全部都是新鲜内容,主要是增加了进程间通信、文件系统和内存管理。跟第一版的做法相同,下篇仍然不仅关注结果,更加致力于将形成一个结果的过程呈现出来。与此同时,由于本书旨在分享和引路,所以尽可能地简化了设计,以便将最重要的部分凸显出来。读者将看到,一个操作系统文件系统和内存管理可以简陋到什么程度。简陋不是缺点,对于我们初学者而言,正是需要从简陋入手。换言之,如果你已经对实现一个操作系统有了一定的经验,那么这本书可能不适合你。这本书适合从来没有编写过操作系统的初学者。   本书的排版是我用L ATEX自己完成的。在排版中我花了一些工夫,因为我希望读者购买的首先是一本易于阅读且赏心悦目的书,其次才是编写操作系统的方法。另外,书中列出的代码均由我自己编写的程序自动嵌入L ATEX源文件,从而严格保证书和光盘的一致性,读者可以根据文件名和行号方便地找到光盘中   代码的准确位置。   此外,在第二版中还有一些小的变化。首先是操作系统的名字改变了,原因在于虽然我们的试验性   OS从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix(本意为 TryMinix)改成了新名字Orange ’S(这个名字来自于我的妻子 ,),以表示它们的不同。另外,书中的代码风格,有些地方也做了调整。   我想,虽然第二版有着这样那样的变化,但有一点没有变,那就是本书试图将我在编写自己操作系统的过程中的经验尽可能地告诉读者,同时尽可能将我当初的思路和编码过程呈现出来。很可能读者比我更聪明,有更好的解决问题的方法,但无论如何,我认为我自己的经验可以为读者所借鉴。如果真是如   此,我将会非常欣慰。   在第二版的编写过程中,我同样要感谢许多人。感谢我的父母和爷爷对我的爱,并希望爷爷不要为我担心,写书是件辛苦的事,但同时也使我收获良多。爸爸在第二版的最后阶段帮我订正文字,这本书里有你的功劳。我要感谢博文视点的各位朋友,感谢郭老师的理解和支持,感谢李玲的辛勤工作,感谢江立和李冰,你们的高效让我非常钦佩。我还要感谢孟岩老师,你给我的鼓励我一直记在心里。我要感谢我的挚友郭洪桥,不仅仅因为你在技术上给我的帮助,更加因为你在精神上给我的支持。感谢我的同事和朋友张会昌,你在技术上的广度和深度总令我钦佩。另外,在第一版中帮助我的人,我要再次谢谢你们,因为没有第一版,也就没有第二版。   在所有人中我最应该感谢和最想感谢的,是我的妻子黄丹红,感谢你给我的所有建议,还有你帮我画的图。尤其是,当这本书在我预想的时间内没有完成的时候,当我遇到困难迟迟不能解决的时候,你总在一旁给我鼓励,在你那里,我从来都能感觉到一种温暖,我深知,如果没有你的支持,我无法坚持下来将书写完。谢谢你,这本书同样属于你。   跟第一版相比,这本书涉及的内容触及操作系统设计的更多方面,而由于笔者的水平实在有限,难免有纰漏甚至错误。如果读者有任何的问题、意见或建议,请登录http://www.osfromscratch.org,让我们共同探讨,共同进步。   本书导读   这本书适合谁   本书是一本操作系统实践的技术书籍。对于操作系统技术感兴趣,想要亲身体验编写操作系统过程的实践主义者,以及Minix、Linux源代码爱好者,都可以在本书中得到实践中所需的知识和思路。   本书以“动手写”为指导思想,只要是跟“动手写”操作系统有关的知识,都作为介绍对象加以讨论,所以,从开发环境的搭建,到保护模式,再到IBMPC中有关芯片的知识,最后到操作系统本身的设计实现,都能在本文中找到相应介绍。所以如果你也想亲身实践的话,本书可以省去你在书店和互联网寻找相应资料的过程,使你的学习过程事半功倍。在读完本书后,你不但可以获得对于操作系统初步的感性认识,并且对 IBMPC的接口、IA架构之保护模式,以及操作系统整体上的框架都将会有一定程度的了解。   笔者相信,当你读完本书之后,如果再读那些纯理论性的操作系统书籍,所获得的体验将会完全不同,因为那些对你而言不再是海市蜃楼。   对于想阅读 Linux源代码的操作系统爱好者,本书可以提供阅读前所必要的知识储备,而这些知识储备不但在本书中有完整的涉及,而且在很多 Linux书籍中是没有提到的。   特别要提到的是,对于想通过阅读 Andrew S. Tanenbaum和 Albert S. Woodhull的《操作系统:设计与实现》来学习操作系统的读者,本书尤其适合作为你的引路书籍,因为它翔实地介绍了初学者入门时所必需的知识积累,而这些知识在《操作系统:设计与实现》一书中是没有涉及的,笔者本人是把这本书作为写操作系统的主要参考书籍之一,所以在本书中对它多有借鉴。   你需要什么技术基础   在本书中所用到的计算机语言只有两种:汇编和 C语言。所以只要你具备汇编和 C语言的经验,就可以阅读本书。除对操作系统常识性的了解(比如知道中断、进程等概念)之外,本书不假定读者具备其他任何经验。   如果你学习过操作系统的理论课程,你会发现本书是对于理论的吻合和补充。它是从实践的角度为你展现一幅操作系统画面。   书中涉及了 Intel CPU保护模式、Linux命令等内容,到时候会有尽可能清晰的讲解,如果笔者认为某些内容可以通过其他教材系统学习,会在书中加以说明。   另外,本书只涉及 Intel x86平台。   统一思想——让我们在这些方面达成共识   道篇   让我们有效而愉快地学习   你大概依然记得在你亲自敲出第一个“Hello world”程序并运行成功时的喜悦,那样的成就感助燃了你对编写程序浓厚的兴趣。随后你不断地学习,每学到新的语法都迫不及待地在计算机上调试运行,在调试的过程中克服困难,学到新知,并获得新的成就感。   可现在请你设想一下,假如课程不是这样的安排,而是先试图告诉你所有的语法,中间没有任何实践的机会,试问这样的课程你能接受吗?我猜你唯一的感受将是索然寡味。   原因何在?只是因为你不再有因为不断实践而获得的源源不断的成就感。而成就感是学习过程中快乐的源泉,没有了成就感,学习的愉快程度将大打折扣,效果于是也将变得不容乐观。   每个人都希望有效而且愉快的学习过程,可不幸的是,我们见到的操作系统课程十之八九令我们失望,作者喋喋不休地讲述着进程管理存储管理I/O控制调度算法,可我们到头来也没有一点的感性认识。我们好像已经理解却又好像一无所知。很明显,没有成就感,一点也没有。笔者痛恨这样的学习过程,也决不会重蹈这样的覆辙,让读者获得成就感将是本书的灵魂。   其实这本书完全可以称作一本回忆录,记载了笔者从开始不知道保护模式为何物到最终形成一个小小   OS的过程,这样的回忆录性质保证了章节的安排完全遵从操作的时间顺序,于是也就保证了每一步的可操作性,毫无疑问,顺着这样的思路走下来,每一章的成果都需要努力但又尽在眼前,步步为营是我   们的战术,成就感是我们的宗旨。   我们将从二十行代码开始,让我们最简单的操作系统婴儿慢慢长大,变成一个翩翩少年,而其中的每一步,你都可以在书中的指导下自己完成,不仅仅是看到,而是自己做到!你将在不断的实践中获得不断的成就感,笔者真心希望在阅读本书的过程中,你的学习过程可以变得愉快而有效。   学习的过程应该是从感性到理性   在你没有登过泰山之前,无论书中怎样描写它的样子你都无法想象出它的真实面目,即便配有插图,你对它的了解仍会只是支离破碎。毫无疑问,一千本对泰山描述的书都比不上你一次登山的经历。文学家的描述可能是华丽而优美的,可这样的描述最终产生的效果可能是你非去亲自登泰山不可。反过来想呢,假如你已经登过泰山,这样的经历产生的效果会是你想读尽天下描述泰山的书而后快吗?可能事实恰恰相反,你可能再也不想去看那些文字描述。   是啊,再好的讲述,又哪比得上亲身的体验?人们的认知规律本来如此,有了感性的认识,才能上升为理性的理论。反其道而行之只能是事倍功半。   如果操作系统是一座这样的大山,本书愿做你的导游,引领你进入它的门径。传统的操作系统书籍仅仅是给你讲述这座大山的故事,你只是在听讲,并没有身临其境,而随着这本书亲身体验,则好像置身于山门之内,你不但可以看见眼前的每一个细节,更是具有了走完整座大山的信心。   值得说明的是,本书旨在引路,不会带领你走完整座大山,但是有兴趣的读者完全可以在本书最终形成的框架的基础上容易地实现其他操作系统书籍中讲到的各种原理和算法,从而对操作系统有个从感性到理性的清醒认识。   暂时的错误并不可怕   当我们对一件事情的全貌没有很好理解的时候,很可能会对某一部分产生理解上的误差,这就是所谓的断章取义。很多时候断章取义是难免的,但是,在不断学习的过程中,我们会逐渐看到更多,了解更多,对原先事物的认识也会变得深刻甚至不同。   对于操作系统这样复杂的东西来说,要想了解所有的细节无疑是非常困难的,所以在实践的过程中,可能在很多地方,会有一些误解发生。这都没有关系,随着了解的深入,这些误解总会得到澄清,到时你会发现,自己对某一方面已经非常熟悉了,这时的成就感,一定会让你感到非常愉悦。   本书内容的安排遵从的是代码编写的时间顺序,它更像是一本开发日记,所以在书中一些中间过程不完美的产物被有意保留了下来,并会在以后的章节中对它们进行修改和完善,因为笔者认为,一些精妙的东西背后,一定隐藏着很多中间的产物,一个伟大的发现在很多情况下可能不是天才们刹那间的灵光一闪,背后也一定有着我们没有看到的不伟大甚至是谬误。笔者很想追寻前辈们的脚步,重寻他们当日的足迹。做到这一点无疑很难,但即便无法做到,只要能引起读者的一点思索,也是本书莫大的幸事。   挡住了去路的,往往不是大树,而是小藤   如果不是亲身去做,你可能永远都不知道,困难是什么。   就好像你买了一台功能超全的微波炉回家,研究完了整本说明书,踌躇满志想要烹饪的时候,却突然发现家里的油盐已经用完。而当时已经是晚上十一点,所有的商店都已经关门,你气急败坏,简直想摸起铁勺砸向无辜的微波炉。   研究说明书是没有错的,但是在没开始之前,你永远都想不到让你无法烹饪的原因居然是十块钱一瓶的油和一块钱一袋的更加微不足道的盐。你还以为困难是微波炉面板上密密麻麻的控制键盘。   其实做其他事情也是一样的,比如写一个操作系统,即便一个很小的可能受理论家们讥笑的操作系统雏形,仍然可能遇到一大堆你没有想过的问题,而这些问题在传统的操作系统书籍中根本没有提到。所以唯一的办法,便是亲自去做,只有实践了,才知道是怎么回事。   术篇   用到什么再学什么   我们不是在考试,我们只是在为了自己的志趣而努力,所以就让我们忠于自己的喜好吧,不必为了考试而看完所有的章节,无论那是多么的乏味。让我们马上投入实践,遇到问题再图解决的办法。笔者非常推崇这样的学习方法:   实践 →遇到问题 →解决问题 →再实践   因为我们知道我们为什么学习,所以我们才会非常投入;由于我们知道我们的目标是解决什么问题,所以我们才会非常专注;由于我们在实践中学习,所以我们才会非常高效。而最有趣的是,最终你会发现你并没有因为选择这样的学习方法而少学到什么,相反,你会发现你用更少的时间学到更多的东西,并且格外的扎实。   只要用心,就没有学不会的东西   笔者还清楚地记得刚刚下载完 Intel Architecture Software Developer Manual那三个可怕的 PDF文件时的心情,那时心里暗暗嘀咕,什么时候才能把这些东西读懂啊!可是突然有一天,当这些东西真的已经被基本读完的时候,我想起当初的畏惧,时间其实并没有过去多少。   所有的道理都是相通的,没有什么真正可怕,尤其是,我们所做的并非创造性的工作,所有的问题前人都曾经解决,所以我们更是无所畏惧,更何况我们不仅有书店,而且有互联网,动动手脚就能找到需要的资料,我们只要认真研究就够了。   所以当遇到困难时,请静下心来,慢慢研究,因为只要用心,就没有学不会的东西。   适当地囫囵吞枣   如果囫囵吞枣仅仅是学习的一个过程而非终点,那么它并不一定就是坏事。大家都应该听说过鲁迅先生学习英语的故事,他建议在阅读的过程中遇到不懂的内容可以忽略,等到过一段时间之后,这些问题会自然解决。   在本书中,有时候可能先列出一段代码,告诉你它能完成什么,这时你也可以大致读过,因为下面会有对它详细的解释。第一遍读它的时候,你只要了解大概就够了。    本书的原则   1.宁可啰嗦一点,也不肯漏掉细节   在书中的有些地方,你可能觉得有些很“简单”的问题都被列了出来,甚至显得有些啰嗦,但笔者宁可让内容写得啰嗦点,因为笔者自己在读书的时候有一个体验,就是有时候一个问题怎么也想不通,经过很长时间终于弄明白的时候才发现原来是那么“简单”。可能作者认为它足够简单以至于可以跳过不提,但读者未必那么幸运一下子就弄清楚。   不过本书到后面的章节,如果涉及的细节是前面章节提到过的,就有意地略过了。举个非常简单的例子,开始时本书会提醒读者增加一个源文件之后不要忘记修改Makefile,到后来就假定读者已经熟悉了这个步骤,可能就不再提及了。   2.努力做到平易近人   笔者更喜欢把本书称作一本笔记或者学习日志,不仅仅是因为它基本是真实的学习过程的再现,而且笔者不想让它有任何居高临下甚至是晦涩神秘的感觉。如果有一个地方你觉得书中没有说清楚以至于你没有弄明白,请你告诉我,我会在以后做出改进。 3.代码注重可读性但不注重效率   本书的代码力求简单易懂,在此过程中很少考虑运行的效率。一方面因为书中的代码仅仅供学习之用,暂时并不考虑实际用途;另一方面笔者认为当我们对操作系统足够了解之后再考虑效率的问题也不迟。   本书附带光盘说明   本书附带光盘中有本书用到的所有源代码。值得一提的是,其中不止包含完整的操作系统代码,还包含各个步骤的中间产物。换句话说,开发中每一步骤的代码,都可在光盘中单独文件夹中找到。举例说明,书的开篇介绍引导扇区,读者在相应文件夹中就只看到引导扇区的代码;第 9章介绍文件系统,在相应文件夹中就不会包含第 10章内存管理的代码。在任何一个步骤对应的文件夹中,都包含一个完整可编译运行的代码树,以方便读者试验之用。这样在学习的任何一个阶段,读者都可彻底了解阶段性成果,且不必担心受到自己还未学习的内容的影响,从而使学习不留死角。   在书的正文中引用的代码会标注出出自哪个文件。以“chapter5/b/bar.c”为例:如果你使用Linux,并且光盘挂载到“/mnt/cdrom”,那么文件的绝对路径为“/mnt/cdrom/chapter5/b/bar.c”;如果你使用Windows,并且光盘是 X:盘,那么文件的绝对路径为“X:nchapter5nbnbar.c”。 目 录   上 篇   第1章 马上动手写一个最小的“操作系统” 2   1.1 准备工作 2   1.2 十分钟完成的操作系统 3   1.3 引导扇区 4   1.4 代码解释 4   1.5 水面下的冰山 6   1.6 回顾 7   第2章 搭建你的工作环境 8   2.1 虚拟计算机Bochs 8   2.1.1 Bochs初体验 8   2.1.2 Bochs的安装 9   2.1.3 Bochs的使用 10   2.1.4 用Bochs调试操作系统 12   2.2 QEMU 15   2.3 平台之争:Windows还是*nix 16   2.4 GNU/Linux下的开发环境 20   2.5 Windows下的开发环境 22   2.6 总结 23   第3章 保护模式(Protect Mode) 25   3.1 认识保护模式 25   3.1.1 保护模式的运行环境 29   3.1.2 GDT(Global Descriptor Table) 31   3.1.3 实模式到保护模式,不一般的jmp 33   3.1.4 描述符属性 35   3.2 保护模式进阶 38   3.2.1 海阔凭鱼跃 38   3.2.2 LDT(Local Descriptor Table) 44   3.2.3 特权级概述 48   3.2.4 特权级转移 51   3.2.5 关于“保护”二字的一点思考 65   3.3 页式存储 65   3.3.1 分页机制概述 66   3.3.2 编写代码启动分页机制 67   3.3.3 PDE和PTE 68   3.3.4 cr3 71   3.3.5 回头看代码 72   3.3.6 克勤克俭用内存 73   3.3.7 进一步体会分页机制 81   3.4 中断和异常 87   3.4.1 中断和异常机制 87   3.4.2 外部中断 90   3.4.3 编程操作8259A 91   3.4.4 建立IDT 94   3.4.5 实现一个中断 95   3.4.6 时钟中断试验 96   3.4.7 几点额外说明 98   3.5 保护模式下的I/O 100   3.5.1 IOPL 100   3.5.2 I/O许可位图(I/O Permission Bitmap) 100   3.6 保护模式小结 101   第4章 让操作系统走进保护模式 102   4.1 突破512字节的限制 102   4.1.1 FAT12 103   4.1.2 DOS可以识别的引导盘 108   4.1.3 一个最简单的Loader 108   4.1.4 加载Loader入内存 109   4.1.5 向Loader交出控制权 116   4.1.6 整理boot.asm 116   4.2 保护模式下的“操作系统” 117   第5章 内核雏形 119   5.1 在Linux下用汇编写Hello World 119   5.2 再进一步,汇编和C同步使用 120   5.3 ELF(Executable and Linkable Format) 123   5.4 从Loader到内核 127   5.4.1 用Loader加载ELF 127   5.4.2 跳入保护模式 131   5.4.3 重新放置内核 137   5.4.4 向内核交出控制权 142   5.5 扩充内核 143   5.5.1 切换堆栈和GDT 144   5.5.2 整理我们的文件夹 148   5.5.3 Makefile 149   5.5.4 添加中断处理 155   5.5.5 两点说明 168   5.6 小结 169   第6章 进程 171   6.1 迟到的进程 171   6.2 概述 171   6.2.1 进程介绍 172   6.2.2 未雨绸缪——形成进程的必要考虑 172   6.2.3 参考的代码 173   6.3 最简单的进程 174   6.3.1 简单进程的关键技术预测 175   6.3.2 第一步——ring0→ring1 178   6.3.3 第二步——丰富中断处理程序 189   6.4 多进程 200   6.4.1 添加一个进程体 200   6.4.2 相关的变量和宏 200   6.4.3 进程表初始化代码扩充 202   6.4.4 LDT 203   6.4.5 修改中断处理程序 203   6.4.6 添加一个任务的步骤总结 206   6.4.7 号外:Minix的中断处理 207   6.4.8 代码回顾与整理 212   6.5 系统调用 220   6.5.1 实现一个简单的系统调用 222   6.5.2 get_ticks的应用 227   6.6 进程调度 232   6.6.1 避免对称——进程的节奏感 232   6.6.2 优先级调度总结 240   第7章 输入/输出系统 242   7.1 键盘 242   7.1.1 从中断开始——键盘初体验 242   7.1.2 AT、PS/2键盘 243   7.1.3 键盘敲击的过程 244   7.1.4 用数组表示扫描码 248   7.1.5 键盘输入缓冲区 251   7.1.6 用新加的任务处理键盘操作 253   7.1.7 解析扫描码 254   7.2 显示 263   7.2.1 初识TTY 264   7.2.2 基本概念 264   7.2.3 寄存 267   7.3 TTY任务 270   7.3.1 TTY任务框架的搭建 272   7.3.2 多控制台 277   7.3.3 完善键盘处理 281   7.3.4 TTY任务总结 288   7.4 区分任务和用户进程 289   7.5 printf 291   7.5.1 为进程指定TTY 292   7.5.2 printf()的实现 292   7.5.3 系统调用write() 294   7.5.4 使用printf() 296   下 篇   第8章 进程间通信 300   8.1 微内核还是宏内核 300   8.1.1 Linux的系统调用 302   8.1.2 Minix的系统调用 303   8.1.3 我们的选择 305   8.2 IPC 306   8.3 实现IPC 306   8.3.1 assert()和panic() 309   8.3.2 msg_send()和msg_receive() 313   8.3.3 增加消息机制之后的进程调度 321   8.4 使用IPC来替换系统调用get_ticks 322   8.5 总结 324   第9章 文件系统 325   9.1 硬盘简介 325   9.2 硬盘操作的I/O 端口 326   9.3 硬盘驱动程序 327   9.4 文件系统 337   9.5 硬盘分区表 338   9.6 设备号 344   9.7 用代码遍历所有分区 347   9.8 完善硬盘驱动程序 352   9.9 在硬盘上制作一个文件系统 355   9.9.1 文件系统涉及的数据结构 356   9.9.2 编码建立文件系统 358   9.10 创建文件 366   9.10.1 Linux下的文件操作 366   9.10.2 文件描述符(file descriptor) 367   9.10.3 open() 369   9.11 创建文件所涉及的其他函数 377   9.11.1 strip_path() 377   9.11.2 search_file() 378   9.11.3 get_inode()和sync_inode() 379   9.11.4 init_fs() 381   9.11.5 read_super_block()和get_super_block() 382   9.12 关闭文件 383   9.13 查看已创建的文件 384   9.14 打开文件 386   9.15 读写文件 387   9.16 测试文件读写 390   9.17 文件系统调试 393   9.18 删除文件 395   9.19 插曲:奇怪的异常 401   9.20 为文件系统添加系统调用的步骤 403   9.21 将TTY纳入文件系统 404   9.22 改造printf 411   9.23 总结 413   第10章 内存管理 414   10.1 fork 414   10.1.1 认识fork 414   10.1.2 fork前要做的工作(为fork所做的准备) 417   10.1.3 fork()库函数 421   10.1.4 MM 421   10.1.5 运行 427   10.2 exit和wait 427   10.3 exec 432   10.3.1 认识exec 433   10.3.2 为自己的操作系统编写应用程序 434   10.3.3 “安装”应用程序 436   10.3.4 实现exec 442   10.4 简单的shell 447   10.5 总结 449   第11章 尾声 451   11.1 让mkfs()只执行一次 451   11.2 从硬盘引导 455   11.2.1 编写硬盘引导扇区和硬盘版loader 455   11.2.2 “安装”hdboot.bin和hdldr.bin 461   11.2.3 grub 461   11.2.4 小结 463   11.3 将OS安装到真实的计算机 465   11.3.1 准备工作 465   11.3.2 安装Linux 466   11.3.3 编译源代码 466   11.3.4 开始安装 467   11.4 总结 467   参考文献 470 解密《一个操作系统实现》这本书 5 月 18 日见到了《 Orange'S :一个操作系统实现》的样书,多少有些激动。想一想前一版本《自己动手写操作系统》是那么畅销,这一本一定不能含糊。整个出版过程我能看到作者于渊为此付出的努力,还在自己排版的过程有深入体会,通过于渊的讲座也让博文视点的员工分享到他在排版过程中的很多心得。 应该有几万个朋友读过《自己动手写操作系统》了,本书的第 2 版《 Orange'S :一个操作系统实现》出来肯定有非常多的朋友想问,这两本书到底有何区别呢?就此博文视点对本书作者于渊进行了简单的采访。 * 提问:《 Orange'S :一个操作系统实现》与《自己动手写操作系统》明显区别在哪些方面? * 于渊:作为《自己动手写操作系统》(以下简称《自》)的第二版,《 Orange'S :一个操作系统实现》(以下简称“新版”)主要有以下变化: 1. 书中示例操作系统的名字改为 Orange'S 2. 书名改为《 Orange'S :一个操作系统实现》 3. 增加了有关 IPC 、 FS 、 MM 等内容 4. 将默认开发平台改为 GNU/Linux ,同时兼顾 Windows 5. 更改了排版工具,并使用技术手段增加书的可读性,比如代码行号的运用 6. 建立专门网站以服务读者 7. 建立专门讨论区供读者交流 读过《自己动手写操作系统》的读者一定知道,其中默认使用 Windows 作为开发平台,同时使用虚拟机来编译及运行自己的 OS ,在新版中这一点发生了变化(如上述第 4 条所述),具体的变化原因在书中第二章有详细的叙述。虽然开发平台是第二位的事情,但书中的默认平台却不免影响到叙述细节,所以,如果读者基于自己的原因坚持在 Windows 上开发(可能的原因或许有对 Linux 不熟悉、需要边开发操作系统边登录某些网上银行等等),则可能对读到的内容进行一点点额外加工。当然,所需的额外加工是少量的,而且在第二章中也有专门的文字介绍如何在两种平台下搭建工作环境。此外,如果读者不介意花钱,还可以同时购买《自己动手写操作系统》和新版,相互参照阅读。 * 提问:《 Orange'S :一个操作系统实现》与《自己动手写操作系统》相比是否有所增加吗?增加了多少内容量呢? 于渊:新版的内容是有增加的,新增文字约占整本书的三分之一,《 Orange'S :一个操作系统实现》新增代码则是《自己动手写操作系统》中代码的数倍。这些新增的内容,读者只能从新版中获得。目前并未有将新增内容单独成书的打算,所以读者即便仅想阅读第八章以后的内容,也需要购买整本《 Orange'S :一个操作系统实现》。已经购买了《自己动手写操作系统》的读者可能觉得有点浪费,但事实并不如此,因为《自己动手写操作系统》的内容经过了重新排版、修订和编辑(比如代码格式进行了重排,更方便与光盘中的文件对照阅读,以及其中所有的矢量图都用 pgf/TikZ 重新绘制等)笔者倾注的心血使得新版的感官已经大为不同,读者一看便知。 * 提问:在《自己动手写操作系统》大卖的时候,您是否想过会有第二版出版呢? * 于渊:坦白讲,我在写作《自》的时候,并没有想过今天会有一个第二版。原因在于,我希望这本书是用来填补空白的,而不是重复去做别人已经做得很好的事情。所谓填补空白,具体说就是让像我一样的操作系统爱好者在读完本书之后,能够有信心去读其他比较流行的开源的操作系统代码,有能力从零开始自己动手写操作系统,而这个任务第一版已经完成了。 * 提问:那么为什么又写作了第二版呢? * 于渊:原因有几个方面。第一,虽然第一版未曾涉及的进程间通信、文件系统等内容在许多书中都有讲解,但阅读的时候还是感觉有语焉不详的通病,作者本人可能很清楚原委,但写得太简略,以至于读者看来未必清晰。第二,我自己想把这个圈画圆。第一版的书虽然完成了它的使命,但毕竟到书的结尾,读者看到的不是一个真正的操作系统,它没有文件系统,没有内存管理,什么也干不了。在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS 编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二版中,仍然试图把话说细一点,把自己的经验拿出来分享。而且我选择我能想到的最精简的设计,以便让读者不至于陷入太多细节而无法看到全貌。我想这是本书可能具有的价值所在──简化的易懂的设计,还有尽量详细的文字。 * 提问:这本书为何不考虑用 WORD 排版? * 于渊:新版的排版是我用 LaTeX 自己完成的。在排版中我花了一些工夫,因为我希望读者购买的首先是一本易于阅读且赏心悦目的书,其次才是编写操作系统的方法。另外,书中列出的代码均由我自己编写的程序自动嵌入 LaTeX 源文件,从而严格保证书和光盘的一致性,读者可以根据文件名和行号方便地找到光盘中代码的准确位置。 * 提问:第二版还有哪些区别呢? Orange'S 这个名字很特别,有什么寓意吗? * 于渊:新版中还有一些小的变化。首先是操作系统的名字改变了,原因在于虽然我们的试验性 OS 从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix (本意为 TryMinix )改成了新名字 Orange'S (这个名字来自于我的妻子),以表示它们的不同。另外,书中的代码风格,有些地方也做了调整。 新版中,原先的叙述风格都尽量地得以贯彻,而在表现形式上,新版用了更多心思,我相信读者能在其中发现这些特点:关注动手细节,探寻代码背后的故事,结果与过程兼顾,内容与形式并重。加上专门为本书建立的网站和讨论区,我相信读者能更容易地阅读,更轻松地学习。
内容简介   本书从只有二十行的引导扇区代码出发,一步一步地向读者呈现一个操作系统框架的完成过程。书中不仅关注代码本身,同时关注完成这些代码的思路和过程。本书不同于其他的理论型书籍,而是提供给读者一个动手实践的路线图。读者可以根据路线图逐步完成各部分的功能,从而避免了一开始就面对整个操作系统数万行代码时的迷茫和挫败感。书中讲解了大量在开发操作系统中需注意的细节问题,这些细节不仅能使读者更深刻地认识操作系统的核心原理,而且使整个开发过程少走弯路。本书分上下两篇,共11章。其中每一章都以前一章的工作成果为基础,实现一项新的功能。而在章的内部,一项大的功能被分解成许多小的步骤,通过完成每个小的步骤,读者可以不断获得阶段性的成果,从而让整个开发过程变得轻松并且有趣。   本书适合各类程序员、程序开发爱好者阅读,也可作为高等院校操作系统课程的实践参考书。 序   做真正 Hacker的乐趣──自己动手去实践   2004年我听编辑说有个年轻人写了本《自己动手写操作系统》,第一反应是不可能,恐怕是翻译稿,写这种书籍是要考作者硬功夫的,不但需要深入掌握操作系统原理,还需要实际动手写出原型。   历史上的 Linux就是这么产生的,Linus Torvalds当时是一名赫尔辛基大学计算机科学系的二年级学生,经常要用自己的电脑去访问大学主机上的新闻组和邮件,为了方便读写和下载文件,他自己编写了磁盘驱动程序和文件系统,这成为了 Linux第一个内核的雏形。   我想中国有能力写出内核原型的程序员应该也有,但把这个题目写成一本书,感觉上不会有人愿意做这件事情,作者要花很多时间,加上主题比较硬,销售量不会太高,经济上回报有限。   但拿来文稿一看,整个编辑部大为惊艳,内容文笔俱佳,而且绝对原创,马上决定在《程序员》连载。2005年博文视点出版的第一版也广受好评。   不过有很多读者还是质疑:现在软件编程主要领域是框架和应用,还需要了解操作系统底层吗?   经过四年的磨练成长,于渊又拿出第二版的书稿《Orange'S:一个操作系统实现》,这本书是属于真正 Hacker的。我虽然已经有多年不写代码了,但看这本书的时候,让我又重新感受到做程序员的乐趣:用代码建设属于自己的系统,让电脑听从自己的指令,对系统的每个部分都了如指掌。   黑客(hacker)实际是褒义词,维基百科的解释是喜欢用智力通过创造性方法来挑战脑力极限的人,特别是他们所感兴趣的领域,例如软件编程或电气工程。个人电脑、软件和互联网等划时代的产品都是黑客创造出来的,如苹果的 Apple电脑、微软的 Basic解释、互联网的 Mosaic浏览。   回答前面读者的质疑,学软件编程并不需要看这本书,想成为优秀程序员和黑客的朋友,我强烈建议你花时间来阅读这本书,并亲自动手实践。正如于渊在本书结尾中所说“我们写自己的操作系统是出于一种好奇,或者说一种求知欲。我希望这样不停地‘过把瘾’能让这种好奇不停地延续”。   好奇心是动力的源泉,追究问题的本质是优秀黑客的必备素质,只有充分掌握了系统原理,才能在技术上游刃有余,才能有真正的创新和发展。中国需要更多真正的黑客,也希望更多的程序员能享受属于黑客的创造乐趣。   蒋涛   2009年 4月 作者自序   本书是《自己动手写操作系统》的第二版,通过一个具体的实例向读者呈现一个操作系统雏形的实现过程。有关操作系统的书籍资料可以找到很多,但是关注如何帮助读者实现一个试验性操作系统的书籍却不多见,本书便是从一个简单的引导扇区开始,讲述一个操作系统成长的故事,以作读者参考之用。   本书面向实践,通过具体实例教读者开发自己的操作系统。书中的步骤遵循由小到大、由浅入深的顺序,跟随这些步骤,读者可以由一个最简单的引导扇区开始,逐渐完善代码,扩充功能,最后形成一个小的操作系统。   本书不仅介绍操作系统的各要素,同时涉及开发操作系统需要的各个方面,比如如何建立开发环境、如何调试以及如何在虚拟机中运行等。书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书中,而这是传统的操作系统实践书籍经常忽略的。总之,只要是开发自己的操作系统中需要的知识,书中都尽量涉及,以便于读者参考。   众所周知,一个成型的操作系统往往非常复杂。如果考虑到操作系统作为软硬件桥梁的特殊地位,那么它可能看上去比一般的软件系统更难理解,因为其核心部分往往包含许多直接针对CPU、内存和 I/O端口的操作,它们夹杂在一片代码汪洋之中,显得更加晦涩。   我们有许多源代码公开的操作系统,可供随时下载和阅读,看上去好像让实现一个供自己把玩的微型操作系统变得容易很多,但事实往往不尽人意,因为这些代码动辄上万甚至几十几百万行,而且细节之间经常互相关联,要理解它们着实不易。我们有许多容易得到的操作系统教程,但读来好像总觉得跟我们有隔膜,不亲近。造成这些的根本原因,在于学习者一开始就面对一个完整的操作系统,或者面对前辈们积累了几十年的一系列理论成果。而无论作者多么擅长写作,读者多么聪明,或者代码多么优秀,要一个初学者理清其中的头绪都将是非常困难的。   我并非在此危言耸听,因为这曾经是我的亲身体会。当然,如果只是为了考试,几本操作系统理论书籍就足够了,你不需要对细节那么清楚。但如果是出于兴趣呢?如果你是想编写自己的操作系统呢?你会发现理论书籍好像一下子变得无用武之地,你会发现任何一个细节上的理解错误都可能导致自己辛辛苦苦编写的代码运行异常甚至崩溃。   我经历过这一切!我曾经翻遍了一本《操作系统:设计与实现》,也没有找到实现一个操作系统应该从何处着手。并不是这些书不好,也不是前人的代码不优秀,而是作为一无所知的初学者,我们所不了解的不仅是高居庙堂的理论知识,还有让我们举步维艰的实践细节。   可能在这些教科书作者的眼里,操作的细节不属于课程的一部分,或者这些细节看上去太容易,根本不值一提,甚至作者认为这些属于所谓“经验”的一部分,约定俗成是由读者本人去摸索的。但是实际情况往往是,这些书中忽略掉的内容恰恰占去了一个初学者大部分的时间,甚至影响了学习的热情。   我至今仍记得当我开始编写自己的操作系统时所遭受的挫败感,那是一种不知道如何着手的无助的感觉。还好我坚持了下来,克服了各种困难,并完成了自己的操作系统雏形。   进而我想到,一定不只是我一个人对编写自己的操作系统怀有兴趣,也一定不只是我一个人在实践时遇到困难。或许我应该把自己的经历写下来,从而可以帮助跟我相似的后来者,就这样,我编写了本书的第一版,也就是《自己动手写操作系统》。我相信,如果你也对神奇的计算机世界充满好奇,并且希望通过自己编写操作系统的方式来了解背后发生的故事,那么你一定可以在这本书中得到一些帮助。而假如你真的因为我的书而重新燃起实践的热情,从而开始一段操作系统旅程,我将会感到非常高兴。   不过我得坦白,在写作《自己动手写操作系统》的时候,我并不敢期待它能引起多少反响,一方面因为操作系统并不是时尚的话题,另一方面我也是走在学习的路上,或许只是比读者早走了一小步而已。然而出乎我的意料,它面世后重印多次,甚至一度登上销量排行榜的榜首,这让我觉得它的确有一定的参考价值,我要借此机会感谢所有支持我的读者。   在我写作《自己动手写操作系统》的时候,并没有想过今天会有一个第二版。原因在于,我希望这本书是用来填补空白的,而不是重复去做别人已经做得很好的事情。所谓填补空白,具体说就是让像我一样的操作系统爱好者在读完本书之后,能够有信心去读其他比较流行的开源的操作系统代码,有能力从零开始自己动手写操作系统,而这个任务第一版已经完成了。   那么为什么我又写作了第二版呢?原因有几个方面。第一,虽然第一版未曾涉及的进程间通信、文件系统等内容在许多书中都有讲解,但阅读的时候还是感觉有语焉不详的通病,作者本人可能很清楚原委,但写得太简略,以至于读者看来未必清晰。第二,我自己想把这个圈画圆。第一版的书虽然完成了它的使命,但毕竟到书的结尾,读者看到的不是一个真正的操作系统,它没有文件系统,没有内存管理,什么也干不了。在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二版中,仍然试图把话说细一点,把自己的经验拿出来分享。而且我选择我能想到的最精简的设计,以便让读者不至于陷入太多细节而无法看到全貌。我想这是本书可能具有的价值所在──简化的易懂的设计,还有尽量详细的文字。   在这一版中,内容被划分成上下两篇。上篇基本上是第一版的修订,只是做了一个调整,那便是在兼顾 Windows和Linux两方面用户的基础上,默认在Linux下建立开发环境来编写我们的操作系统。至于这样做的原因,在本书第 2章有比较详细的说明。当然,开发环境毕竟是第二位的,书中讲述的内容以及涉及的代码跟第一版都是一致的。本书的下篇全部都是新鲜内容,主要是增加了进程间通信、文件系统和内存管理。跟第一版的做法相同,下篇仍然不仅关注结果,更加致力于将形成一个结果的过程呈现出来。与此同时,由于本书旨在分享和引路,所以尽可能地简化了设计,以便将最重要的部分凸显出来。读者将看到,一个操作系统文件系统和内存管理可以简陋到什么程度。简陋不是缺点,对于我们初学者而言,正是需要从简陋入手。换言之,如果你已经对实现一个操作系统有了一定的经验,那么这本书可能不适合你。这本书适合从来没有编写过操作系统的初学者。   本书的排版是我用L ATEX自己完成的。在排版中我花了一些工夫,因为我希望读者购买的首先是一本易于阅读且赏心悦目的书,其次才是编写操作系统的方法。另外,书中列出的代码均由我自己编写的程序自动嵌入L ATEX源文件,从而严格保证书和光盘的一致性,读者可以根据文件名和行号方便地找到光盘中   代码的准确位置。   此外,在第二版中还有一些小的变化。首先是操作系统的名字改变了,原因在于虽然我们的试验性   OS从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix(本意为 TryMinix)改成了新名字Orange ’S(这个名字来自于我的妻子 ,),以表示它们的不同。另外,书中的代码风格,有些地方也做了调整。   我想,虽然第二版有着这样那样的变化,但有一点没有变,那就是本书试图将我在编写自己操作系统的过程中的经验尽可能地告诉读者,同时尽可能将我当初的思路和编码过程呈现出来。很可能读者比我更聪明,有更好的解决问题的方法,但无论如何,我认为我自己的经验可以为读者所借鉴。如果真是如   此,我将会非常欣慰。   在第二版的编写过程中,我同样要感谢许多人。感谢我的父母和爷爷对我的爱,并希望爷爷不要为我担心,写书是件辛苦的事,但同时也使我收获良多。爸爸在第二版的最后阶段帮我订正文字,这本书里有你的功劳。我要感谢博文视点的各位朋友,感谢郭老师的理解和支持,感谢李玲的辛勤工作,感谢江立和李冰,你们的高效让我非常钦佩。我还要感谢孟岩老师,你给我的鼓励我一直记在心里。我要感谢我的挚友郭洪桥,不仅仅因为你在技术上给我的帮助,更加因为你在精神上给我的支持。感谢我的同事和朋友张会昌,你在技术上的广度和深度总令我钦佩。另外,在第一版中帮助我的人,我要再次谢谢你们,因为没有第一版,也就没有第二版。   在所有人中我最应该感谢和最想感谢的,是我的妻子黄丹红,感谢你给我的所有建议,还有你帮我画的图。尤其是,当这本书在我预想的时间内没有完成的时候,当我遇到困难迟迟不能解决的时候,你总在一旁给我鼓励,在你那里,我从来都能感觉到一种温暖,我深知,如果没有你的支持,我无法坚持下来将书写完。谢谢你,这本书同样属于你。   跟第一版相比,这本书涉及的内容触及操作系统设计的更多方面,而由于笔者的水平实在有限,难免有纰漏甚至错误。如果读者有任何的问题、意见或建议,请登录http://www.osfromscratch.org,让我们共同探讨,共同进步。   本书导读   这本书适合谁   本书是一本操作系统实践的技术书籍。对于操作系统技术感兴趣,想要亲身体验编写操作系统过程的实践主义者,以及Minix、Linux源代码爱好者,都可以在本书中得到实践中所需的知识和思路。   本书以“动手写”为指导思想,只要是跟“动手写”操作系统有关的知识,都作为介绍对象加以讨论,所以,从开发环境的搭建,到保护模式,再到IBMPC中有关芯片的知识,最后到操作系统本身的设计实现,都能在本文中找到相应介绍。所以如果你也想亲身实践的话,本书可以省去你在书店和互联网寻找相应资料的过程,使你的学习过程事半功倍。在读完本书后,你不但可以获得对于操作系统初步的感性认识,并且对 IBMPC的接口、IA架构之保护模式,以及操作系统整体上的框架都将会有一定程度的了解。   笔者相信,当你读完本书之后,如果再读那些纯理论性的操作系统书籍,所获得的体验将会完全不同,因为那些对你而言不再是海市蜃楼。   对于想阅读 Linux源代码的操作系统爱好者,本书可以提供阅读前所必要的知识储备,而这些知识储备不但在本书中有完整的涉及,而且在很多 Linux书籍中是没有提到的。   特别要提到的是,对于想通过阅读 Andrew S. Tanenbaum和 Albert S. Woodhull的《操作系统:设计与实现》来学习操作系统的读者,本书尤其适合作为你的引路书籍,因为它翔实地介绍了初学者入门时所必需的知识积累,而这些知识在《操作系统:设计与实现》一书中是没有涉及的,笔者本人是把这本书作为写操作系统的主要参考书籍之一,所以在本书中对它多有借鉴。   你需要什么技术基础   在本书中所用到的计算机语言只有两种:汇编和 C语言。所以只要你具备汇编和 C语言的经验,就可以阅读本书。除对操作系统常识性的了解(比如知道中断、进程等概念)之外,本书不假定读者具备其他任何经验。   如果你学习过操作系统的理论课程,你会发现本书是对于理论的吻合和补充。它是从实践的角度为你展现一幅操作系统画面。   书中涉及了 Intel CPU保护模式、Linux命令等内容,到时候会有尽可能清晰的讲解,如果笔者认为某些内容可以通过其他教材系统学习,会在书中加以说明。   另外,本书只涉及 Intel x86平台。   统一思想——让我们在这些方面达成共识   道篇   让我们有效而愉快地学习   你大概依然记得在你亲自敲出第一个“Hello world”程序并运行成功时的喜悦,那样的成就感助燃了你对编写程序浓厚的兴趣。随后你不断地学习,每学到新的语法都迫不及待地在计算机上调试运行,在调试的过程中克服困难,学到新知,并获得新的成就感。   可现在请你设想一下,假如课程不是这样的安排,而是先试图告诉你所有的语法,中间没有任何实践的机会,试问这样的课程你能接受吗?我猜你唯一的感受将是索然寡味。   原因何在?只是因为你不再有因为不断实践而获得的源源不断的成就感。而成就感是学习过程中快乐的源泉,没有了成就感,学习的愉快程度将大打折扣,效果于是也将变得不容乐观。   每个人都希望有效而且愉快的学习过程,可不幸的是,我们见到的操作系统课程十之八九令我们失望,作者喋喋不休地讲述着进程管理存储管理I/O控制调度算法,可我们到头来也没有一点的感性认识。我们好像已经理解却又好像一无所知。很明显,没有成就感,一点也没有。笔者痛恨这样的学习过程,也决不会重蹈这样的覆辙,让读者获得成就感将是本书的灵魂。   其实这本书完全可以称作一本回忆录,记载了笔者从开始不知道保护模式为何物到最终形成一个小小   OS的过程,这样的回忆录性质保证了章节的安排完全遵从操作的时间顺序,于是也就保证了每一步的可操作性,毫无疑问,顺着这样的思路走下来,每一章的成果都需要努力但又尽在眼前,步步为营是我   们的战术,成就感是我们的宗旨。   我们将从二十行代码开始,让我们最简单的操作系统婴儿慢慢长大,变成一个翩翩少年,而其中的每一步,你都可以在书中的指导下自己完成,不仅仅是看到,而是自己做到!你将在不断的实践中获得不断的成就感,笔者真心希望在阅读本书的过程中,你的学习过程可以变得愉快而有效。   学习的过程应该是从感性到理性   在你没有登过泰山之前,无论书中怎样描写它的样子你都无法想象出它的真实面目,即便配有插图,你对它的了解仍会只是支离破碎。毫无疑问,一千本对泰山描述的书都比不上你一次登山的经历。文学家的描述可能是华丽而优美的,可这样的描述最终产生的效果可能是你非去亲自登泰山不可。反过来想呢,假如你已经登过泰山,这样的经历产生的效果会是你想读尽天下描述泰山的书而后快吗?可能事实恰恰相反,你可能再也不想去看那些文字描述。   是啊,再好的讲述,又哪比得上亲身的体验?人们的认知规律本来如此,有了感性的认识,才能上升为理性的理论。反其道而行之只能是事倍功半。   如果操作系统是一座这样的大山,本书愿做你的导游,引领你进入它的门径。传统的操作系统书籍仅仅是给你讲述这座大山的故事,你只是在听讲,并没有身临其境,而随着这本书亲身体验,则好像置身于山门之内,你不但可以看见眼前的每一个细节,更是具有了走完整座大山的信心。   值得说明的是,本书旨在引路,不会带领你走完整座大山,但是有兴趣的读者完全可以在本书最终形成的框架的基础上容易地实现其他操作系统书籍中讲到的各种原理和算法,从而对操作系统有个从感性到理性的清醒认识。   暂时的错误并不可怕   当我们对一件事情的全貌没有很好理解的时候,很可能会对某一部分产生理解上的误差,这就是所谓的断章取义。很多时候断章取义是难免的,但是,在不断学习的过程中,我们会逐渐看到更多,了解更多,对原先事物的认识也会变得深刻甚至不同。   对于操作系统这样复杂的东西来说,要想了解所有的细节无疑是非常困难的,所以在实践的过程中,可能在很多地方,会有一些误解发生。这都没有关系,随着了解的深入,这些误解总会得到澄清,到时你会发现,自己对某一方面已经非常熟悉了,这时的成就感,一定会让你感到非常愉悦。   本书内容的安排遵从的是代码编写的时间顺序,它更像是一本开发日记,所以在书中一些中间过程不完美的产物被有意保留了下来,并会在以后的章节中对它们进行修改和完善,因为笔者认为,一些精妙的东西背后,一定隐藏着很多中间的产物,一个伟大的发现在很多情况下可能不是天才们刹那间的灵光一闪,背后也一定有着我们没有看到的不伟大甚至是谬误。笔者很想追寻前辈们的脚步,重寻他们当日的足迹。做到这一点无疑很难,但即便无法做到,只要能引起读者的一点思索,也是本书莫大的幸事。   挡住了去路的,往往不是大树,而是小藤   如果不是亲身去做,你可能永远都不知道,困难是什么。   就好像你买了一台功能超全的微波炉回家,研究完了整本说明书,踌躇满志想要烹饪的时候,却突然发现家里的油盐已经用完。而当时已经是晚上十一点,所有的商店都已经关门,你气急败坏,简直想摸起铁勺砸向无辜的微波炉。   研究说明书是没有错的,但是在没开始之前,你永远都想不到让你无法烹饪的原因居然是十块钱一瓶的油和一块钱一袋的更加微不足道的盐。你还以为困难是微波炉面板上密密麻麻的控制键盘。   其实做其他事情也是一样的,比如写一个操作系统,即便一个很小的可能受理论家们讥笑的操作系统雏形,仍然可能遇到一大堆你没有想过的问题,而这些问题在传统的操作系统书籍中根本没有提到。所以唯一的办法,便是亲自去做,只有实践了,才知道是怎么回事。   术篇   用到什么再学什么   我们不是在考试,我们只是在为了自己的志趣而努力,所以就让我们忠于自己的喜好吧,不必为了考试而看完所有的章节,无论那是多么的乏味。让我们马上投入实践,遇到问题再图解决的办法。笔者非常推崇这样的学习方法:   实践 →遇到问题 →解决问题 →再实践   因为我们知道我们为什么学习,所以我们才会非常投入;由于我们知道我们的目标是解决什么问题,所以我们才会非常专注;由于我们在实践中学习,所以我们才会非常高效。而最有趣的是,最终你会发现你并没有因为选择这样的学习方法而少学到什么,相反,你会发现你用更少的时间学到更多的东西,并且格外的扎实。   只要用心,就没有学不会的东西   笔者还清楚地记得刚刚下载完 Intel Architecture Software Developer Manual那三个可怕的 PDF文件时的心情,那时心里暗暗嘀咕,什么时候才能把这些东西读懂啊!可是突然有一天,当这些东西真的已经被基本读完的时候,我想起当初的畏惧,时间其实并没有过去多少。   所有的道理都是相通的,没有什么真正可怕,尤其是,我们所做的并非创造性的工作,所有的问题前人都曾经解决,所以我们更是无所畏惧,更何况我们不仅有书店,而且有互联网,动动手脚就能找到需要的资料,我们只要认真研究就够了。   所以当遇到困难时,请静下心来,慢慢研究,因为只要用心,就没有学不会的东西。   适当地囫囵吞枣   如果囫囵吞枣仅仅是学习的一个过程而非终点,那么它并不一定就是坏事。大家都应该听说过鲁迅先生学习英语的故事,他建议在阅读的过程中遇到不懂的内容可以忽略,等到过一段时间之后,这些问题会自然解决。   在本书中,有时候可能先列出一段代码,告诉你它能完成什么,这时你也可以大致读过,因为下面会有对它详细的解释。第一遍读它的时候,你只要了解大概就够了。    本书的原则   1.宁可啰嗦一点,也不肯漏掉细节   在书中的有些地方,你可能觉得有些很“简单”的问题都被列了出来,甚至显得有些啰嗦,但笔者宁可让内容写得啰嗦点,因为笔者自己在读书的时候有一个体验,就是有时候一个问题怎么也想不通,经过很长时间终于弄明白的时候才发现原来是那么“简单”。可能作者认为它足够简单以至于可以跳过不提,但读者未必那么幸运一下子就弄清楚。   不过本书到后面的章节,如果涉及的细节是前面章节提到过的,就有意地略过了。举个非常简单的例子,开始时本书会提醒读者增加一个源文件之后不要忘记修改Makefile,到后来就假定读者已经熟悉了这个步骤,可能就不再提及了。   2.努力做到平易近人   笔者更喜欢把本书称作一本笔记或者学习日志,不仅仅是因为它基本是真实的学习过程的再现,而且笔者不想让它有任何居高临下甚至是晦涩神秘的感觉。如果有一个地方你觉得书中没有说清楚以至于你没有弄明白,请你告诉我,我会在以后做出改进。 3.代码注重可读性但不注重效率   本书的代码力求简单易懂,在此过程中很少考虑运行的效率。一方面因为书中的代码仅仅供学习之用,暂时并不考虑实际用途;另一方面笔者认为当我们对操作系统足够了解之后再考虑效率的问题也不迟。   本书附带光盘说明   本书附带光盘中有本书用到的所有源代码。值得一提的是,其中不止包含完整的操作系统代码,还包含各个步骤的中间产物。换句话说,开发中每一步骤的代码,都可在光盘中单独文件夹中找到。举例说明,书的开篇介绍引导扇区,读者在相应文件夹中就只看到引导扇区的代码;第 9章介绍文件系统,在相应文件夹中就不会包含第 10章内存管理的代码。在任何一个步骤对应的文件夹中,都包含一个完整可编译运行的代码树,以方便读者试验之用。这样在学习的任何一个阶段,读者都可彻底了解阶段性成果,且不必担心受到自己还未学习的内容的影响,从而使学习不留死角。   在书的正文中引用的代码会标注出出自哪个文件。以“chapter5/b/bar.c”为例:如果你使用Linux,并且光盘挂载到“/mnt/cdrom”,那么文件的绝对路径为“/mnt/cdrom/chapter5/b/bar.c”;如果你使用Windows,并且光盘是 X:盘,那么文件的绝对路径为“X:nchapter5nbnbar.c”。 目 录   上 篇   第1章 马上动手写一个最小的“操作系统” 2   1.1 准备工作 2   1.2 十分钟完成的操作系统 3   1.3 引导扇区 4   1.4 代码解释 4   1.5 水面下的冰山 6   1.6 回顾 7   第2章 搭建你的工作环境 8   2.1 虚拟计算机Bochs 8   2.1.1 Bochs初体验 8   2.1.2 Bochs的安装 9   2.1.3 Bochs的使用 10   2.1.4 用Bochs调试操作系统 12   2.2 QEMU 15   2.3 平台之争:Windows还是*nix 16   2.4 GNU/Linux下的开发环境 20   2.5 Windows下的开发环境 22   2.6 总结 23   第3章 保护模式(Protect Mode) 25   3.1 认识保护模式 25   3.1.1 保护模式的运行环境 29   3.1.2 GDT(Global Descriptor Table) 31   3.1.3 实模式到保护模式,不一般的jmp 33   3.1.4 描述符属性 35   3.2 保护模式进阶 38   3.2.1 海阔凭鱼跃 38   3.2.2 LDT(Local Descriptor Table) 44   3.2.3 特权级概述 48   3.2.4 特权级转移 51   3.2.5 关于“保护”二字的一点思考 65   3.3 页式存储 65   3.3.1 分页机制概述 66   3.3.2 编写代码启动分页机制 67   3.3.3 PDE和PTE 68   3.3.4 cr3 71   3.3.5 回头看代码 72   3.3.6 克勤克俭用内存 73   3.3.7 进一步体会分页机制 81   3.4 中断和异常 87   3.4.1 中断和异常机制 87   3.4.2 外部中断 90   3.4.3 编程操作8259A 91   3.4.4 建立IDT 94   3.4.5 实现一个中断 95   3.4.6 时钟中断试验 96   3.4.7 几点额外说明 98   3.5 保护模式下的I/O 100   3.5.1 IOPL 100   3.5.2 I/O许可位图(I/O Permission Bitmap) 100   3.6 保护模式小结 101   第4章 让操作系统走进保护模式 102   4.1 突破512字节的限制 102   4.1.1 FAT12 103   4.1.2 DOS可以识别的引导盘 108   4.1.3 一个最简单的Loader 108   4.1.4 加载Loader入内存 109   4.1.5 向Loader交出控制权 116   4.1.6 整理boot.asm 116   4.2 保护模式下的“操作系统” 117   第5章 内核雏形 119   5.1 在Linux下用汇编写Hello World 119   5.2 再进一步,汇编和C同步使用 120   5.3 ELF(Executable and Linkable Format) 123   5.4 从Loader到内核 127   5.4.1 用Loader加载ELF 127   5.4.2 跳入保护模式 131   5.4.3 重新放置内核 137   5.4.4 向内核交出控制权 142   5.5 扩充内核 143   5.5.1 切换堆栈和GDT 144   5.5.2 整理我们的文件夹 148   5.5.3 Makefile 149   5.5.4 添加中断处理 155   5.5.5 两点说明 168   5.6 小结 169   第6章 进程 171   6.1 迟到的进程 171   6.2 概述 171   6.2.1 进程介绍 172   6.2.2 未雨绸缪——形成进程的必要考虑 172   6.2.3 参考的代码 173   6.3 最简单的进程 174   6.3.1 简单进程的关键技术预测 175   6.3.2 第一步——ring0→ring1 178   6.3.3 第二步——丰富中断处理程序 189   6.4 多进程 200   6.4.1 添加一个进程体 200   6.4.2 相关的变量和宏 200   6.4.3 进程表初始化代码扩充 202   6.4.4 LDT 203   6.4.5 修改中断处理程序 203   6.4.6 添加一个任务的步骤总结 206   6.4.7 号外:Minix的中断处理 207   6.4.8 代码回顾与整理 212   6.5 系统调用 220   6.5.1 实现一个简单的系统调用 222   6.5.2 get_ticks的应用 227   6.6 进程调度 232   6.6.1 避免对称——进程的节奏感 232   6.6.2 优先级调度总结 240   第7章 输入/输出系统 242   7.1 键盘 242   7.1.1 从中断开始——键盘初体验 242   7.1.2 AT、PS/2键盘 243   7.1.3 键盘敲击的过程 244   7.1.4 用数组表示扫描码 248   7.1.5 键盘输入缓冲区 251   7.1.6 用新加的任务处理键盘操作 253   7.1.7 解析扫描码 254   7.2 显示 263   7.2.1 初识TTY 264   7.2.2 基本概念 264   7.2.3 寄存 267   7.3 TTY任务 270   7.3.1 TTY任务框架的搭建 272   7.3.2 多控制台 277   7.3.3 完善键盘处理 281   7.3.4 TTY任务总结 288   7.4 区分任务和用户进程 289   7.5 printf 291   7.5.1 为进程指定TTY 292   7.5.2 printf()的实现 292   7.5.3 系统调用write() 294   7.5.4 使用printf() 296   下 篇   第8章 进程间通信 300   8.1 微内核还是宏内核 300   8.1.1 Linux的系统调用 302   8.1.2 Minix的系统调用 303   8.1.3 我们的选择 305   8.2 IPC 306   8.3 实现IPC 306   8.3.1 assert()和panic() 309   8.3.2 msg_send()和msg_receive() 313   8.3.3 增加消息机制之后的进程调度 321   8.4 使用IPC来替换系统调用get_ticks 322   8.5 总结 324   第9章 文件系统 325   9.1 硬盘简介 325   9.2 硬盘操作的I/O 端口 326   9.3 硬盘驱动程序 327   9.4 文件系统 337   9.5 硬盘分区表 338   9.6 设备号 344   9.7 用代码遍历所有分区 347   9.8 完善硬盘驱动程序 352   9.9 在硬盘上制作一个文件系统 355   9.9.1 文件系统涉及的数据结构 356   9.9.2 编码建立文件系统 358   9.10 创建文件 366   9.10.1 Linux下的文件操作 366   9.10.2 文件描述符(file descriptor) 367   9.10.3 open() 369   9.11 创建文件所涉及的其他函数 377   9.11.1 strip_path() 377   9.11.2 search_file() 378   9.11.3 get_inode()和sync_inode() 379   9.11.4 init_fs() 381   9.11.5 read_super_block()和get_super_block() 382   9.12 关闭文件 383   9.13 查看已创建的文件 384   9.14 打开文件 386   9.15 读写文件 387   9.16 测试文件读写 390   9.17 文件系统调试 393   9.18 删除文件 395   9.19 插曲:奇怪的异常 401   9.20 为文件系统添加系统调用的步骤 403   9.21 将TTY纳入文件系统 404   9.22 改造printf 411   9.23 总结 413   第10章 内存管理 414   10.1 fork 414   10.1.1 认识fork 414   10.1.2 fork前要做的工作(为fork所做的准备) 417   10.1.3 fork()库函数 421   10.1.4 MM 421   10.1.5 运行 427   10.2 exit和wait 427   10.3 exec 432   10.3.1 认识exec 433   10.3.2 为自己的操作系统编写应用程序 434   10.3.3 “安装”应用程序 436   10.3.4 实现exec 442   10.4 简单的shell 447   10.5 总结 449   第11章 尾声 451   11.1 让mkfs()只执行一次 451   11.2 从硬盘引导 455   11.2.1 编写硬盘引导扇区和硬盘版loader 455   11.2.2 “安装”hdboot.bin和hdldr.bin 461   11.2.3 grub 461   11.2.4 小结 463   11.3 将OS安装到真实的计算机 465   11.3.1 准备工作 465   11.3.2 安装Linux 466   11.3.3 编译源代码 466   11.3.4 开始安装 467   11.4 总结 467   参考文献 470 解密《一个操作系统实现》这本书 5 月 18 日见到了《 Orange'S :一个操作系统实现》的样书,多少有些激动。想一想前一版本《自己动手写操作系统》是那么畅销,这一本一定不能含糊。整个出版过程我能看到作者于渊为此付出的努力,还在自己排版的过程有深入体会,通过于渊的讲座也让博文视点的员工分享到他在排版过程中的很多心得。 应该有几万个朋友读过《自己动手写操作系统》了,本书的第 2 版《 Orange'S :一个操作系统实现》出来肯定有非常多的朋友想问,这两本书到底有何区别呢?就此博文视点对本书作者于渊进行了简单的采访。 * 提问:《 Orange'S :一个操作系统实现》与《自己动手写操作系统》明显区别在哪些方面? * 于渊:作为《自己动手写操作系统》(以下简称《自》)的第二版,《 Orange'S :一个操作系统实现》(以下简称“新版”)主要有以下变化: 1. 书中示例操作系统的名字改为 Orange'S 2. 书名改为《 Orange'S :一个操作系统实现》 3. 增加了有关 IPC 、 FS 、 MM 等内容 4. 将默认开发平台改为 GNU/Linux ,同时兼顾 Windows 5. 更改了排版工具,并使用技术手段增加书的可读性,比如代码行号的运用 6. 建立专门网站以服务读者 7. 建立专门讨论区供读者交流 读过《自己动手写操作系统》的读者一定知道,其中默认使用 Windows 作为开发平台,同时使用虚拟机来编译及运行自己的 OS ,在新版中这一点发生了变化(如上述第 4 条所述),具体的变化原因在书中第二章有详细的叙述。虽然开发平台是第二位的事情,但书中的默认平台却不免影响到叙述细节,所以,如果读者基于自己的原因坚持在 Windows 上开发(可能的原因或许有对 Linux 不熟悉、需要边开发操作系统边登录某些网上银行等等),则可能对读到的内容进行一点点额外加工。当然,所需的额外加工是少量的,而且在第二章中也有专门的文字介绍如何在两种平台下搭建工作环境。此外,如果读者不介意花钱,还可以同时购买《自己动手写操作系统》和新版,相互参照阅读。 * 提问:《 Orange'S :一个操作系统实现》与《自己动手写操作系统》相比是否有所增加吗?增加了多少内容量呢? 于渊:新版的内容是有增加的,新增文字约占整本书的三分之一,《 Orange'S :一个操作系统实现》新增代码则是《自己动手写操作系统》中代码的数倍。这些新增的内容,读者只能从新版中获得。目前并未有将新增内容单独成书的打算,所以读者即便仅想阅读第八章以后的内容,也需要购买整本《 Orange'S :一个操作系统实现》。已经购买了《自己动手写操作系统》的读者可能觉得有点浪费,但事实并不如此,因为《自己动手写操作系统》的内容经过了重新排版、修订和编辑(比如代码格式进行了重排,更方便与光盘中的文件对照阅读,以及其中所有的矢量图都用 pgf/TikZ 重新绘制等)笔者倾注的心血使得新版的感官已经大为不同,读者一看便知。 * 提问:在《自己动手写操作系统》大卖的时候,您是否想过会有第二版出版呢? * 于渊:坦白讲,我在写作《自》的时候,并没有想过今天会有一个第二版。原因在于,我希望这本书是用来填补空白的,而不是重复去做别人已经做得很好的事情。所谓填补空白,具体说就是让像我一样的操作系统爱好者在读完本书之后,能够有信心去读其他比较流行的开源的操作系统代码,有能力从零开始自己动手写操作系统,而这个任务第一版已经完成了。 * 提问:那么为什么又写作了第二版呢? * 于渊:原因有几个方面。第一,虽然第一版未曾涉及的进程间通信、文件系统等内容在许多书中都有讲解,但阅读的时候还是感觉有语焉不详的通病,作者本人可能很清楚原委,但写得太简略,以至于读者看来未必清晰。第二,我自己想把这个圈画圆。第一版的书虽然完成了它的使命,但毕竟到书的结尾,读者看到的不是一个真正的操作系统,它没有文件系统,没有内存管理,什么也干不了。在第二版中,你将会看到,你已经可以通过交叉编译的方式为我们的实验性 OS 编写应用程序了,也就是说,它已经具备操作系统的基本功能,虽然仍然极其简陋,但第一个圈,毕竟是已经圆起来了。第三,实践类的操作系统书籍还是太少了,以至于你要想看看别人是怎么做的,除了读以《操作系统:设计与实现》为代表的极少数书籍之外,就是一头扎进源代码中,而结果有时相当令人气馁。我自己也气馁过,所以我在第二版中,仍然试图把话说细一点,把自己的经验拿出来分享。而且我选择我能想到的最精简的设计,以便让读者不至于陷入太多细节而无法看到全貌。我想这是本书可能具有的价值所在──简化的易懂的设计,还有尽量详细的文字。 * 提问:这本书为何不考虑用 WORD 排版? * 于渊:新版的排版是我用 LaTeX 自己完成的。在排版中我花了一些工夫,因为我希望读者购买的首先是一本易于阅读且赏心悦目的书,其次才是编写操作系统的方法。另外,书中列出的代码均由我自己编写的程序自动嵌入 LaTeX 源文件,从而严格保证书和光盘的一致性,读者可以根据文件名和行号方便地找到光盘中代码的准确位置。 * 提问:第二版还有哪些区别呢? Orange'S 这个名字很特别,有什么寓意吗? * 于渊:新版中还有一些小的变化。首先是操作系统的名字改变了,原因在于虽然我们的试验性 OS 从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix (本意为 TryMinix )改成了新名字 Orange'S (这个名字来自于我的妻子),以表示它们的不同。另外,书中的代码风格,有些地方也做了调整。 新版中,原先的叙述风格都尽量地得以贯彻,而在表现形式上,新版用了更多心思,我相信读者能在其中发现这些特点:关注动手细节,探寻代码背后的故事,结果与过程兼顾,内容与形式并重。加上专门为本书建立的网站和讨论区,我相信读者能更容易地阅读,更轻松地学习。
文件为doc版,可自行转成txt,在手机上看挺好的。 本资源来自网络,如有纰漏还请告知,如觉得还不错,请留言告知后来人,谢谢!!!!! 入门学习Linux常用必会60个命令实例详解 Linux必学的60个命令 Linux提供了大量的命令,利用它可以有效地完成大量的工作,如磁盘操作、文件存取、目录操作、进程管理、文件权限设定等。所以,在Linux系统上工作离不开使用系统提供的命令。要想真正理解Linux系统,就必须从Linux命令学起,通过基础的命令学习可以进一步理解Linux系统。 不同Linux发行版的命令数量不一样,但Linux发行版本最少的命令也有200多个。这里笔者把比较重要和使用频率最多的命令,按照它们在系统中的作用分成下面六个部分一一介绍。 ◆ 安装和登录命令:login、shutdown、halt、reboot、install、mount、umount、chsh、exit、last; ◆ 文件处理命令:file、mkdir、grep、dd、find、mv、ls、diff、cat、ln; ◆ 系统管理相关命令:df、top、free、quota、at、lp、adduser、groupadd、kill、crontab; ◆ 网络操作命令:ifconfig、ip、ping、netstat、telnet、ftp、route、rlogin、rcp、finger、mail、 nslookup; ◆ 系统安全相关命令:passwd、su、umask、chgrp、chmod、chown、chattr、sudo ps、who; ◆ 其它命令:tar、unzip、gunzip、unarj、mtools、man、unendcode、uudecode。 本文以Mandrake Linux 9.1(Kenrel 2.4.21)为例,介绍Linux下的安装和登录命令。 immortality按:请用ctrl+f在本页中查找某一部分的内容或某一命令的用法。 -------------------------------------------------------------------------------- Linux必学的60个命令(1)-安装与登陆命令 login 1.作用 login的作用是登录系统,它的使用权限是所有用户。 2.格式 login [name][-p ][-h 主机名称] 3.主要参数 -p:通知login保持现在的环境参数。 -h:用来向远程登录的之间传输用户名。 如果选择用命令行模式登录Linux的话,那么看到的第一个Linux命令就是login:。 一般界面是这样的: Manddrake Linux release 9.1(Bamboo) for i586 renrel 2.4.21-0.13mdk on i686 / tty1 localhost login:root password: 上面代码中,第一行是Linux发行版本号,第二行是内核版本号和登录的虚拟控制台,我们在第三行输入登录名,按“Enter”键在Password后输入账户密码,即可登录系统。出于安全考虑,输入账户密码时字符不会在屏幕上回显,光标也不移动。 登录后会看到下面这个界面(以超级用户为例): [root@localhost root]# last login:Tue ,Nov 18 10:00:55 on vc/1 上面显示的是登录星期、月、日、时间和使用的虚拟控制台。 4.应用技巧 Linux 是一个真正的多用户操作系统,可以同时接受多个用户登录,还允许一个用户进行多次登录。这是因为Linux和许多版本的Unix一样,提供了虚拟控制台的访问方式,允许用户在同一时间从控制台(系统的控制台是与系统直接相连的监视和键盘)进行多次登录。每个虚拟控制台可以看作是一个独立的工作站,工作台之间可以切换。虚拟控制台的切换可以通过按下Alt键和一个功能键来实现,通常使用F1-F6 。 例如,用户登录后,按一下“Alt+ F2”键,用户就可以看到上面出现的“login:”提示符,说明用户看到了第二个虚拟控制台。然后只需按“Alt+ F1”键,就可以回到第一个虚拟控制台。一个新安装的Linux系统允许用户使用“Alt+F1”到“Alt+F6”键来访问前六个虚拟控制台。虚拟控制台最有用的是,当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作,关闭这个程序。 shutdown 1.作用 shutdown命令的作用是关闭计算机,它的使用权限是超级用户。 2.格式 shutdown [-h][-i][-k][-m][-t] 3.重要参数 -t:在改变到其它运行级别之前,告诉init程序多久以后关机。 -k:并不真正关机,只是送警告信号给每位登录者。 -h:关机后关闭电源。 -c:cancel current process取消目前正在执行的关机程序。所以这个选项当然没有时间参数,但是可以输入一个用来解释的讯息,而这信息将会送到每位使用者。 -F:在重启计算机时强迫fsck。 -time:设定关机前的时间。 -m: 将系统改为单用户模式。 -i:关机时显示系统信息。 4.命令说明 shutdown 命令可以安全地将系统关机。有些用户会使用直接断掉电源的方式来关闭Linux系统,这是十分危险的。因为Linux与Windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失,使系统处于不稳定的状态,甚至在有的系统中会损坏硬件设备(硬盘)。在系统关机前使用 shutdown命令,系统管理员会通知所有登录的用户系统将要关闭,并且login指令会被冻结,即新的用户不能再登录。 halt 1.作用 halt命令的作用是关闭系统,它的使用权限是超级用户。 2.格式 halt [-n] [-w] [-d] [-f] [-i] [-p] 3.主要参数说明 -n:防止sync系统调用,它用在用fsck修补根分区之后,以阻止内核用老版本的超级块覆盖修补过的超级块。 -w:并不是真正的重启或关机,只是写wtmp(/var/log/wtmp)纪录。 -f:没有调用shutdown,而强制关机或重启。 -i:关机(或重启)前,关掉所有的网络接口。 -f:强迫关机,不呼叫shutdown这个指令。 -p: 当关机的时候顺便做关闭电源的动作。 -d:关闭系统,但不留下纪录。  4.命令说明 halt 就是调用shutdown -h。halt执行时,杀死应用进程,执行sync(将存于buffer中的资料强制写入硬盘中)系统调用,文件系统写操作完成后就会停止内核。若系统的运行级别为0或6,则关闭系统;否则以shutdown指令(加上-h参数)来取代。  reboot 1.作用 reboot命令的作用是重新启动计算机,它的使用权限是系统管理者。 2.格式 reboot [-n] [-w] [-d] [-f] [-i] 3.主要参数 -n: 在重开机前不做将记忆体资料写回硬盘的动作。 -w: 并不会真的重开机,只是把记录写到/var/log/wtmp文件里。 -d: 不把记录写到/var/log/wtmp文件里(-n这个参数包含了-d)。 -i: 在重开机之前先把所有与网络相关的装置停止。 install 1.作用 install命令的作用是安装或升级软件或备份数据,它的使用权限是所有用户。 2.格式 (1)install [选项]... 来源 目的地 (2)install [选项]... 来源... 目录 (3)install -d [选项]... 目录... 在前两种格式中,会将复制至或将多个文件复制至已存在的,同时设定权限模式及所有者/所属组。在第三种格式中,会创建所有指定的目录及它们的主目录。长选项必须用的参数在使用短选项时也是必须的。 3.主要参数 --backup[=CONTROL]:为每个已存在的目的地文件进行备份。 -b:类似 --backup,但不接受任何参数。 -c:(此选项不作处理)。 -d,--directory:所有参数都作为目录处理,而且会创建指定目录的所有主目录。 -D:创建前的所有主目录,然后将复制至 ;在第一种使用格式中有用。 -g,--group=组:自行设定所属组,而不是进程目前的所属组。 -m,--mode=模式:自行设定权限模式 (像chmod),而不是rwxr-xr-x。 -o,--owner=所有者:自行设定所有者 (只适用于超级用户)。 -p,--preserve-timestamps:以文件的访问/修改时间作为相应的目的地文件的时间属性。 -s,--strip:用strip命令删除symbol table,只适用于第一及第二种使用格式。 -S,--suffix=后缀:自行指定备份文件的。 -v,--verbose:处理每个文件/目录时印出名称。 --help:显示此帮助信息并离开。 --version:显示版本信息并离开。 mount 1.作用 mount命令的作用是加载文件系统,它的用权限是超级用户或/etc/fstab中允许的使用者。 2.格式 mount -a [-fv] [-t vfstype] [-n] [-rw] [-F] device dir 3.主要参数 -h:显示辅助信息。 -v:显示信息,通常和-f用来除错。 -a:将/etc/fstab中定义的所有文件系统挂上。 -F:这个命令通常和-a一起使用,它会为每一个mount的动作产生一个行程负责执行。在系统需要挂上大量NFS文件系统时可以加快加载的速度。 -f:通常用于除错。它会使mount不执行实际挂上的动作,而是模拟整个挂上的过程,通常会和-v一起使用。 -t vfstype:显示被加载文件系统的类型。 -n:一般而言,mount挂上后会在/etc/mtab中写入一笔资料,在系统中没有可写入文件系统的情况下,可以用这个选项取消这个动作。 4.应用技巧 在Linux 和Unix系统上,所有文件都是作为一个大型树(以/为根)的一部分访问的。要访问CD-ROM上的文件,需要将CD-ROM设备挂装在文件树中的某个挂装点。如果发行版安装了自动挂装包,那么这个步骤可自动进行。在Linux中,如果要使用硬盘、光驱等储存设备,就得先将它加载,当储存设备挂上了之后,就可以把它当成一个目录来访问。挂上一个设备使用mount命令。在使用mount这个指令时,至少要先知道下列三种信息:要加载对象的文件系统类型、要加载对象的设备名称及要将设备加载到哪个目录下。 (1)Linux可以识别的文件系统 ◆ Windows 95/98常用的FAT 32文件系统:vfat ; ◆ Win NT/2000 的文件系统:ntfs ; ◆ OS/2用的文件系统:hpfs; ◆ Linux用的文件系统:ext2、ext3; ◆ CD-ROM光盘用的文件系统:iso9660。 虽然vfat是指FAT 32系统,但事实上它也兼容FAT 16的文件系统类型。 (2)确定设备的名称 在Linux 中,设备名称通常都存在/dev里。这些设备名称的命名都是有规则的,可以用“推理”的方式把设备名称找出来。例如,/dev/hda1这个 IDE设备,hd是Hard Disk(硬盘)的,sd是SCSI Device,fd是Floppy Device(或是Floppy Disk?)。a代表第一个设备,通常IDE接口可以接上4个IDE设备(比如4块硬盘)。所以要识别IDE硬盘的方法分别就是hda、hdb、hdc、 hdd。hda1中的“1”代表hda的第一个硬盘分区 (partition),hda2代表hda的第二主分区,第一个逻辑分区从hda5开始,依此类推。此外,可以直接检查 /var/log/messages文件,在该文件中可以找到计算机开机后系统已辨认出来的设备代号。 (3)查找挂接点 在决定将设备挂接之前,先要查看一下计算机是不是有个/mnt的空目录,该目录就是专门用来当作挂载点(Mount Point)的目录。建议在/mnt里建几个/mnt/cdrom、/mnt/floppy、/mnt/mo等目录,当作目录的专用挂载点。举例而言,如要挂载下列5个设备,其执行指令可能如下 (假设都是Linux的ext2系统,如果是Windows XX请将ext2改成vfat): 软盘 ===>mount -t ext2 /dev/fd0 /mnt/floppy cdrom ===>mount -t iso9660 /dev/hdc /mnt/cdrom SCSI cdrom ===>mount -t iso9660 /dev/sdb /mnt/scdrom SCSI cdr ===>mount -t iso9660 /dev/sdc /mnt/scdr 不过目前大多数较新的Linux发行版本(包括红旗 Linux、中软Linux、Mandrake Linux等)都可以自动挂装文件系统,但Red Hat Linux除外。 umount 1.作用 umount命令的作用是卸载一个文件系统,它的使用权限是超级用户或/etc/fstab中允许的使用者。 2.格式 unmount -a [-fFnrsvw] [-t vfstype] [-n] [-rw] [-F] device dir 3.使用说明 umount 命令是mount命令的逆操作,它的参数和使用方法和mount命令是一样的。Linux挂装CD-ROM后,会锁定CD—ROM,这样就不能用CD- ROM面板上的Eject按钮弹出它。但是,当不再需要光盘时,如果已将/cdrom作为符号链接,请使用umount/cdrom来卸装它。仅当无用户正在使用光盘时,该命令才会成功。该命令包括了将带有当前工作目录当作该光盘中的目录的终端窗口。 chsh 1.作用 chsh命令的作用是更改使用者shell设定,它的使用权限是所有使用者。 2.格式 chsh [ -s ] [ -list] [ --help ] [ -v ] [ username ] 3.主要参数 -l:显示系统所有Shell类型。 -v:显示Shell版本号。 4.应用技巧 前面介绍了Linux下有多种Shell,一般缺省的是Bash,如果想更换Shell类型可以使用chsh命令。先输入账户密码,然后输入新Shell类型,如果操作正确系统会显示“Shell change”。其界面一般如下: Changing fihanging shell for cao Password: New shell [/bin/bash]: /bin/tcsh 上面代码中,[ ]内是目前使用的Shell。普通用户只能修改自己的Shell,超级用户可以修改全体用户的Shell。要想查询系统提供哪些Shell,可以使用chsh -l 命令,见图1所示。 图1 系统可以使用的Shell类型 从图1中可以看到,笔者系统中可以使用的Shell有bash(缺省)、csh、sh、tcsh四种。 exit 1.作用 exit命令的作用是退出系统,它的使用权限是所有用户。 2.格式 exit 3.参数 exit命令没有参数,运行后退出系统进入登录界面。 last 1.作用 last命令的作用是显示近期用户或终端的登录情况,它的使用权限是所有用户。通过last命令查看该程序的log,管理员可以获知谁曾经或企图连接系统。 2.格式 1ast[—n][-f file][-t tty] [—h 节点][-I —IP][—1][-y][1D] 3.主要参数 -n:指定输出记录的条数。 -f file:指定用文件file作为查询用的log文件。 -t tty:只显示指定的虚拟控制台上登录情况。 -h 节点:只显示指定的节点上的登录情况。 -i IP:只显示指定的IP上登录的情况。 -1:用IP来显示远端地址。 -y:显示记录的年、月、日。 -ID:知道查询的用户名。 -x:显示系统关闭、用户登录和退出的历史。 动手练习 上面介绍了Linux安装和登录命令,下面介绍几个实例,动手练习一下刚才讲过的命令。 1.一次运行多个命令 在一个命令行中可以执行多个命令,用分号将各个命令隔开即可,例如: #last -x;halt 上面代码表示在显示系统关闭、用户登录和退出的历史后关闭计算机。 2.利用mount挂装文件系统访问Windows系统 许多Linux发行版本现在都可以自动加载Vfat分区来访问Windows系统,而Red Hat各个版本都没有自动加载Vfat分区,因此还需要进行手工操作。 mount 可以将Windows分区作为Linux的一个“文件”挂接到Linux的一个空文件夹下,从而将Windows的分区和/mnt这个目录联系起来。因此,只要访问这个文件夹就相当于访问该分区了。首先要在/mnt下建立winc文件夹,在命令提示符下输入下面命令: #mount -t vfat /dev/hda1 /mnt/winc 即表示将Windows的C分区挂到Liunx的/mnt/winc目录下。这时,在/mnt/winc目录下就可以看到Windows中C盘的内容了。使用类似的方法可以访问Windows系统的D、E盘。在Linux系统显示Windows的分区一般顺序这样的:hda1为C盘、hda5为D盘、 hda6为E盘……以此类推。上述方法可以查看Windows系统有一个很大的问题,就是Windows中的所有中文文件名或文件夹名全部显示为问号 “?”,而英文却可以正常显示。我们可以通过加入一些参数让它显示中文。还以上面的操作为例,此时输入命令: #mount -t vfat -o iocharset=cp936 /dev/hda1 /mnt/winc 现在它就可以正常显示中文了。 3.使用mount加挂闪盘上的文件系统 在Linux下使用闪盘非常简单。Linux对USB设备有很好的支持,当插入闪盘后,闪盘被识别为一个SCSI盘,通常输入以下命令: # mount /dev/sda1 /usb 就能够加挂闪盘上的文件系统。 小知识 Linux命令与Shell 所谓Shell,就是命令解释程序,它提供了程序设计接口,可以使用程序来编程。学习Shell对于Linux初学者理解Linux系统是非常重要的。 Linux系统的Shell作为操作系统的外壳,为用户提供了使用操作系统的接口。Shell是命令语言、命令解释程序及程序设计语言的统称,是用户和 Linux内核之间的接口程序。如果把Linux内核想象成一个球体的中心,Shell就是围绕内核的外层。当从Shell或其它程序向Linux传递命令时,内核会做出相应的反应。Shell在Linux系统的作用和MS DOS下的COMMAND.COM和Windows 95/98 的 explorer.exe相似。Shell虽然不是系统核心的一部分,只是系统核心的一个外延,但它能够调用系统内核的大部分功能。因此,可以说 Shell是Unux/Linux最重要的实用程序。 Linux中的Shell有多种类型,其中最常用的是Bourne Shell(sh)、C Shell(csh)和Korn Shell(ksh)。大多数Linux发行版本缺省的Shell是Bourne Again Shell,它是Bourne Shell的扩展,简称bash,与Bourne Shell完全向后兼容,并且在Bourne Shell的基础上增加了很多特性。bash放在/bin/bash中,可以提供如命令补全、命令编辑和命令历史表等功能。它还包含了很多C Shell和Korn Shell中的优点,有灵活和强大的编程接口,同时又有很友好的用户界面。Linux系统中200多个命令中有40个是bash的内部命令,主要包括 exit、less、lp、kill、 cd、pwd、fc、fg等。 -------------------------------------------------------------------------------- Linux必学的60个命令(2)-文件处理命令 Linux 系统信息存放在文件里,文件与普通的公务文件类似。每个文件都有自己的名字、内容、存放地址及其它一些管理信息,如文件的用户、文件的大小等。文件可以是一封信、一个通讯录,或者是程序的源语句、程序的数据,甚至可以包括可执行的程序和其它非正文内容。 Linux文件系统具有良好的结构,系统提供了很多文件处理程序。这里主要介绍常用的文件处理命令。 file 1.作用 件内容判断文件类型,使用权限是所有用户。 2.格式 file通过探测文 file [options] 文件名 3.[options]主要参数 -v:在标准输出后显示版本信息,并且退出。 -z:探测压缩过的文件类型。 -L:允许符合连接。 -f name:从文件namefile中读取要分析的文件名列表。 4.简单说明 使用file命令可以知道某个文件究竟是二进制(ELF格式)的可执行文件, 还是Shell Script文件,或者是其它的什么格式。file能识别的文件类型有目录、Shell脚本、英文文本、二进制可执行文件、C语言源文件、文本文件、DOS的可执行文件。 5.应用实例 如果我们看到一个没有后缀的文件grap,可以使用下面命令: $ file grap grap: English text 此时系统显示这是一个英文文本文件。需要说明的是,file命令不能探测包括图形、音频、视频等多媒体文件类型。 mkdir 1.作用 mkdir命令的作用是建立名称为dirname的子目录,与MS DOS下的md命令类似,它的使用权限是所有用户。 2.格式 mkdir [options] 目录名 3.[options]主要参数 -m, --mode=模式:设定权限,与chmod类似。 -p, --parents:需要时创建上层目录;如果目录早已存在,则不当作错误。 -v, --verbose:每次创建新目录都显示信息。 --version:显示版本信息后离开。 4.应用实例 在进行目录创建时可以设置目录的权限,此时使用的参数是“-m”。假设要创建的目录名是“tsk”,让所有用户都有rwx(即读、写、执行的权限),那么可以使用以下命令: $ mkdir -m 777 tsk grep 1.作用 grep命令可以指定文件中搜索特定的内容,并将含有这些内容的行标准输出。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。 2.格式 grep [options] 3.主要参数 [options]主要参数: -c:只输出匹配行的计数。 -I:不区分大小写(只适用于单字符)。 -h:查询多文件时不显示文件名。 -l:查询多文件时只输出包含匹配字符的文件名。 -n:显示匹配行及行号。 -s:不显示不存在或无匹配文本的错误信息。 -v:显示不包含匹配文本的所有行。 pattern正则表达式主要参数: \:忽略正则表达式中特殊字符的原有含义。 ^:匹配正则表达式的开始行。 $: 匹配正则表达式的结束行。 \:到匹配正则表达式的行结束。 [ ]:单个字符,如[A]即A符合要求 。 [ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。 。:所有的单个字符。 * :有字符,长度可以为0。 正则表达式是Linux/Unix系统中非常重要的概念。正则表达式(也称为“regex”或“regexp”)是一个可以描述一类字符串的模式(Pattern)。如果一个字符串可以用某个正则表达式来描述,我们就说这个字符和该正则表达式匹配(Match)。这和DOS中用户可以使用通配符 “*”代表任意字符类似。在Linux系统上,正则表达式通常被用来查找文本的模式,以及对文本执行“搜索-替换”操作和其它功能。 4.应用实例 查询DNS服务是日常工作之一,这意味着要维护覆盖不同网络的大量IP地址。有时IP地址会超过2000个。如果要查看nnn.nnn网络地址,但是却忘了第二部分中的其余部分,只知到有两个句点,例如nnn nn..。要抽取其中所有nnn.nnn IP地址,使用[0-9 ]\{3 \}\.[0-0\{3\}\。含义是任意数字出现3次,后跟句点,接着是任意数字出现3次,后跟句点。 $grep '[0-9 ]\{3 \}\.[0-0\{3\}\' ipfile 补充说明,grep家族还包括fgrep和egrep。fgrep是fix grep,允许查找字符串而不是一个模式;egrep是扩展grep,支持基本及扩展的正则表达式,但不支持\q模式范围的应用及与之相对应的一些更加规范的模式。 dd 1.作用 dd命令用来复制文件,并根据参数将数据转换和格式化。 2.格式 dd [options] 3.[opitions]主要参数 bs=字节:强迫 ibs=及obs=。 cbs=字节:每次转换指定的。 conv=关键字:根据以逗号分隔的关键字表示的方式来转换文件。 count=块数目:只复制指定的输入数据。 ibs=字节:每次读取指定的。 if=文件:读取内容,而非标准输入的数据。 obs=字节:每次写入指定的。 of=文件:将数据写入,而不在标准输出显示。 seek=块数目:先略过以obs为单位的指定的输出数据。 skip=块数目:先略过以ibs为单位的指定的输入数据。 4.应用实例 dd命令常常用来制作Linux启动盘。先找一个可引导内核,令它的根设备指向正确的根分区,然后使用dd命令将其写入软盘: $ rdev vmlinuz /dev/hda $dd if=vmlinuz of=/dev/fd0 上面代码说明,使用rdev命令将可引导内核vmlinuz中的根设备指向/dev/hda,请把“hda”换成自己的根分区,接下来用dd命令将该内核写入软盘。 find 1.作用 find命令的作用是在目录中搜索文件,它的使用权限是所有用户。 2.格式 find [path][options][expression] path指定目录路径,系统从这里开始沿着目录树向下查找文件。它是一个路径列表,相互用空格分离,如果不写path,那么默认为当前目录。 3.主要参数 [options]参数: -depth:使用深度级别的查找过程方式,在某层指定目录中优先查找文件内容。 -maxdepth levels:表示至多查找到开始目录的第level层子目录。level是一个非负数,如果level是0的话表示仅在当前目录中查找。 -mindepth levels:表示至少查找到开始目录的第level层子目录。 -mount:不在其它文件系统(如Msdos、Vfat等)的目录和文件中查找。 -version:打印版本。 [expression]是匹配表达式,是find命令接受的表达式,find命令的所有操作都是针对表达式的。它的参数非常多,这里只介绍一些常用的参数。 —name:支持统配符*和?。 -atime n:搜索在过去n天读取过的文件。 -ctime n:搜索在过去n天修改过的文件。 -group grpoupname:搜索所有组为grpoupname的文件。 -user 用户名:搜索所有文件属主为用户名(ID或名称)的文件。 -size n:搜索文件大小是n个block的文件。 -print:输出搜索结果,并且打印。 4.应用技巧 find命令查找文件的几种方法: (1)根据文件名查找 例如,我们想要查找一个文件名是lilo.conf的文件,可以使用如下命令: find / -name lilo.conf find命令后的“/”表示搜索整个硬盘。 (2)快速查找文件 根据文件名查找文件会遇到一个实际问题,就是要花费相当长的一段时间,特别是大型Linux文件系统和大容量硬盘文件放在很深的子目录中时。如果我们知道了这个文件存放在某个目录中,那么只要在这个目录中往下寻找就能节省很多时间。比如smb.conf文件,从它的文件后缀“.conf”可以判断这是一个配置文件,那么它应该在/etc目录内,此时可以使用下面命令: find /etc -name smb.conf 这样,使用“快速查找文件”方式可以缩短时间。 (3)根据部分文件名查找方法 有时我们知道只某个文件包含有abvd这4个字,那么要查找系统中所有包含有这4个字符的文件可以输入下面命令: find / -name '*abvd*' 输入这个命令以后,Linux系统会将在/目录中查找所有的包含有abvd这4个字符的文件(其中*是通配符),比如abvdrmyz等符合条件的文件都能显示出来。 (4) 使用混合查找方式查找文件 find命令可以使用混合查找的方法,例如,我们想在/etc目录中查找大于500000字节,并且在24小时内修改的某个文件,则可以使用-and (与)把两个查找参数链接起来组合成一个混合的查找方式。 find /etc -size +500000c -and -mtime +1 mv 1.作用 mv命令用来为文件或目录改名,或者将文件由一个目录移入另一个目录中,它的使用权限是所有用户。该命令如同DOS命令中的ren和move的组合。 2.格式 mv[options] 源文件或目录 目标文件或目录 3.[options]主要参数 -i:交互方式操作。如果mv操作将导致对已存在的目标文件的覆盖,此时系统询问是否重写,要求用户回答“y”或“n”,这样可以避免误覆盖文件。 -f:禁止交互操作。mv操作要覆盖某个已有的目标文件时不给任何指示,指定此参数后i参数将不再起作用。 4.应用实例 (1)将/usr/cbu中的所有文件移到当前目录(用“.”表示)中: $ mv /usr/cbu/ * . (2)将文件cjh.txt重命名为wjz.txt: $ mv cjh.txt wjz.txt  ls 1.作用 ls命令用于显示目录内容,类似DOS下的dir命令,它的使用权限是所有用户。 2.格式 ls [options][filename] 3.options主要参数 -a, --all:不隐藏任何以“.” 字符开始的项目。 -A, --almost-all:列出除了“ . ”及 “.. ”以外的任何项目。 --author:印出每个文件著作者。 -b, --escape:以八进制溢出序列表示不可打印的字符。 --block-size=大小:块以指定的字节为单位。 -B, --ignore-backups:不列出任何以 ~ 字符结束的项目。 -f:不进行排序,-aU参数生效,-lst参数失效。 -F, --classify:加上文件类型的指示符号 (*/=@| 其中一个)。 -g:like -l, but do not list owner。 -G, --no-group:inhibit display of group information。 -i, --inode:列出每个文件的inode号。 -I, --ignore=样式:不印出任何符合Shell万用字符的项目。 -k:即--block-size=1K。 -l:使用较长格式列出信息。 -L, --dereference:当显示符号链接的文件信息时,显示符号链接所指示的对象,而并非符号链接本身的信息。 -m:所有项目以逗号分隔,并填满整行行宽。 -n, --numeric-uid-gid:类似-l,但列出UID及GID号。 -N, --literal:列出未经处理的项目名称,例如不特别处理控制字符。 -p, --file-type:加上文件类型的指示符号 (/=@| 其中一个)。 -Q, --quote-name:将项目名称括上双引号。 -r, --reverse:依相反次序排列。 -R, --recursive:同时列出所有子目录层。 -s, --size:以块大小为序。 4.应用举例 ls 命令是Linux系统使用频率最多的命令,它的参数也是Linux命令中最多的。使用ls命令时会有几种不同的颜色,其中蓝色表示是目录,绿色表示是可执行文件,红色表示是压缩文件,浅蓝色表示是链接文件,加粗的黑色表示符号链接,灰色表示是其它格式文件。ls最常使用的是ls- l,见图1所示。 图1 使用ls-l命令 文件类型开头是由10个字符构成的字符串。其中第一个字符表示文件类型,它可以是下述类型之一:-(普通文件)、d(目录)、l(符号链接)、b(块设备文件)、c(字符设备文件)。后面的9个字符表示文件的访问权限,分为3组,每组3位。第一组表示文件属主的权限,第二组表示同组用户的权限,第三组表示其他用户的权限。每一组的三个字符分别表示对文件的读(r)、写(w)和执行权限(x)。对于目录,表示进入权限。s表示当文件被执行时,把该文件的UID 或GID赋予执行进程的UID(用户ID)或GID(组 ID)。t表示设置标志位(留在内存,不被换出)。如果该文件是目录,那么在该目录中的文件只能被超级用户、目录拥有者或文件属主删除。如果它是可执行文件,那么在该文件执行后,指向其正文段的指针仍留在内存。这样再次执行它时,系统就能更快地装入该文件。接着显示的是文件大小、生成时间、文件或命令名称。 diff 1.作用 diff命令用于两个文件之间的比较,并指出两者的不同,它的使用权限是所有用户。 2.格式 diff [options] 源文件 目标文件 3.[options]主要参数 -a:将所有文件当作文本文件来处理。 -b:忽略空格造成的不同。 -B:忽略空行造成的不同。 -c:使用纲要输出格式。 -H:利用试探法加速对大文件的搜索。 -I:忽略大小写的变化。 -n --rcs:输出RCS格式。 cmp 1.作用 cmp(“compare”的缩写)命令用来简要指出两个文件是否存在差异,它的使用权限是所有用户。 2.格式 cmp[options] 文件名 3.[options]主要参数 -l: 将字节以十进制的方式输出,并方便将两个文件中不同的以八进制的方式输出。 cat 1.作用 cat(“concatenate”的缩写)命令用于连接并显示指定的一个和多个文件的有关信息,它的使用权限是所有用户。 2.格式 cat [options] 文件1 文件2…… 3.[options]主要参数 -n:由第一行开始对所有输出的行数编号。 -b:和-n相似,只不过对于空白行不编号。 -s:当遇到有连续两行以上的空白行时,就代换为一行的空白行。 4.应用举例 (1)cat命令一个最简单的用处是显示文本文件的内容。例如,我们想在命令行看一下README文件的内容,可以使用命令: $ cat README  (2)有时需要将几个文件处理成一个文件,并将这种处理的结果保存到一个单独的输出文件。cat命令在其输入上接受一个或多个文件,并将它们作为一个单独的文件打印到它的输出。例如,把README和INSTALL的文件内容加上行号(空白行不加)之后,将内容附加到一个新文本文件File1 中: $ cat README INSTALL File1 (3)cat还有一个重要的功能就是可以对行进行编号,见图2所示。这种功能对于程序文档的编制,以及法律和科学文档的编制很方便,打印在左边的行号使得参考文档的某一部分变得容易,这些在编程、科学研究、业务报告甚至是立法工作中都是非常重要的。 图2 使用cat命令/etc/named.conf文件进行编号 对行进行编号功能有-b(只能对非空白行进行编号)和-n(可以对所有行进行编号)两个参数: $ cat -b /etc/named.conf ln 1.作用 ln命令用来在文件之间创建链接,它的使用权限是所有用户。 2.格式 ln [options] 源文件 [链接名] 3.参数 -f:链结时先将源文件删除。 -d:允许系统管理者硬链结自己的目录。 -s:进行软链结(Symbolic Link)。 -b:将在链结时会被覆盖或删除的文件进行备份。 链接有两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link)。默认情况下,ln命令产生硬链接。 硬连接指通过索引节点来进行的连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件才会被真正删除。 与硬连接相对应,Lnux系统中还存在另一种连接,称为符号连接(Symbilc Link),也叫软连接。软链接文件有点类似于Windows的快捷方式。它实际上是特殊文件的一种。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。 动手练习 上面我们介绍了Linux文件处理命令,下面介绍几个实例,大家可以动手练习一下刚才讲过的命令。 1.利用符号链接快速访问关键目录 符号链接是一个非常实用的功能。假设有一些目录或文件需要频繁使用,但由于Linux的文件和目录结构等原因,这个文件或目录在很深的子目录中。比如, Apache Web服务文档位于系统的/usr/local/httpd/htdocs中,并且不想每次都要从主目录进入这样一个长的路径之中(实际上,这个路径也非常不容易记忆)。 为了解决这个问题,可以在主目录中创建一个符号链接,这样在需要进入该目录时,只需进入这个链接即可。 为了能方便地进入Web服务(/usr/local/httpd/htdocs)文档所在的目录,在主目录下可以使用以下命令: $ ln -s /usr/local/httpd/htdocs gg 这样每次进入gg目录就可访问Web服务的文档,以后如果不再访问Web服务的文档时,删除gg即可,而真正的Web服务的文档并没有删除。 2.使用dd命令将init.rd格式的root.ram内容导入内存 dd if=/dev/fd0 of=floppy.fd dd if=root.ram of=/dev/ram0 # 3.grep命令系统调用 grep是Linux/Unix中使用最广泛的命令之一,许多Linux系统内部都可以调用它。 (1)如果要查询目录列表中的目录,方法如下: $ ls -l | grep '∧d' (2)如果在一个目录中查询不包含目录的所有文件,方法如下: $ ls -l | grep '∧[∧d]' (3)用find命令调用grep,如所有C源代码中的“Chinput”,方法如下: $find /ZhXwin -name *.c -exec grep -q -s Chinput {} \;-print -------------------------------------------------------------------------------- Linux必学的60个命令(3)-系统管理命令 Linux必学的系统管理命令 对于Linux系统来说,无论是中央处理、内存、磁盘驱动、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心。熟悉了Linux常用的文件处理命令以后,这一讲介绍对系统和用户进行管理的命令。 df 1.作用 df命令用来检查文件系统的磁盘空间占用情况,使用权限是所有用户。 2.格式 df [options] 3.主要参数 -s:对每个Names参数只给出占用的数据块总数。 -a:递归地显示指定目录中各文件及子目录中各文件占用的数据块数。若既不指定-s,也不指定-a,则只显示Names中的每一个目录及其中的各子目录所占的磁盘块数。 -k:以1024字节为单位列出磁盘空间使用情况。 -x:跳过在不同文件系统上的目录不予统计。 -l:计算所有的文件大小,对硬链接文件则计算多次。 -i:显示inode信息而非块使用量。 -h:以容易理解的格式印出文件系统大小,例如136KB、254MB、21GB。 -P:使用POSIX输出格式。 -T:显示文件系统类型。 4.说明 df命令被广泛地用来生成文件系统的使用统计数据,它能显示系统中所有的文件系统的信息,包括总容量、可用的空闲空间、目前的安装点等。 超级权限用户使用df命令时会发现这样的情况:某个分区的容量超过了100%。这是因为Linux系统为超级用户保留了10%的空间,由其单独支配。也就是说,对于超级用户而言,他所见到的硬盘容量将是110%。这样的安排对于系统管理而言是有好处的,当硬盘被使用的容量接近100%时系统管理员还可以正常工作。 5.应用实例 Linux支持的文件系统非常多,包括JFS、ReiserFS、ext、ext2、ext3、ISO9660、XFS、Minx、vfat、MSDOS等。使用df -T命令查看磁盘空间时还可以得到文件系统的信息: #df -T 文件系统 类型 容量 已用 可用 已用% 挂载点 /dev/hda7 reiserfs 5.2G 1.6G 3.7G 30% / /dev/hda1 vfat 2.4G 1.6G 827M 66% /windows/C /dev/hda5 vfat 3.0G 1.7G 1.3G 57% /windows/D /dev/hda9 vfat 3.0G 2.4G 566M 82% /windows/E /dev/hda10 NTFS 3.2G 573M 2.6G 18% /windows/F /dev/hda11 vfat 1.6G 1.5G 23M 99% /windows/G 从上面除了可以看到磁盘空间的容量、使用情况外,分区的文件系统类型、挂载点等信息也一览无遗。 top 1.作用 top命令用来显示执行中的程序进程,使用权限是所有用户。 2.格式 top [-] [d delay] [q] [c] [S] [s] [i] [n] 3.主要参数 d:指定更新的间隔,以秒计算。 q:没有任何延迟的更新。如果使用者有超级用户,则top命令将会以最高的优先序执行。 c:显示进程完整的路径与名称。 S:累积模式,会将己完成或消失的子行程的CPU时间累积起来。 s:安全模式。 i:不显示任何闲置(Idle)或无用(Zombie)的行程。 n:显示更新的次数,完成后将会退出top。 4.说明 top命令是Linux系统管理的一个主要命令,通过它可以获得许多信息。这里我们结合图1来说明它给出的信息。 图1 top命令的显示 在图1中,第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。第二行显示的是所有启动的进程、目前运行的、挂起 (Sleeping)的和无用(Zombie)的进程。第三行显示的是目前CPU的使用情况,包括系统占用的比例、用户使用比例、闲置(Idle)比例。第四行显示物理内存的使用情况,包括总的可以使用的内存、已用内存、空闲内存、缓冲区占用的内存。第五行显示交换分区使用情况,包括总的交换分区、使用的、空闲的和用于高速缓存的大小。第六行显示的项目最多,下面列出了详细解释。 PID(Process ID):进程标示号。 USER:进程所有者的用户名。 PR:进程的优先级别。 NI:进程的优先级别数值。 VIRT:进程占用的虚拟内存值。 RES:进程占用的物理内存值。 SHR:进程使用的共享内存值。 S:进程的状态,其中S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数。 %CPU:该进程占用的CPU使用率。 %MEM:该进程占用的物理内存和总内存的百分比。 TIME+:该进程启动后占用的总的CPU时间。 Command:进程启动的启动命令名称,如果这一行显示不下,进程会有一个完整的命令行。 top命令使用过程中,还可以使用一些交互的命令来完成其它参数的功能。这些命令是通过快捷键启动的。 :立刻刷新。 P:根据CPU使用大小进行排序。 T:根据时间、累计时间排序。 q:退出top命令。 m:切换显示内存信息。 t:切换显示进程和CPU状态信息。 c:切换显示命令名称和完整命令行。 M:根据使用内存大小进行排序。 W:将当前设置写入~/.toprc文件中。这是写top配置文件的推荐方法。 可以看到,top命令是一个功能十分强大的监控系统的工具,对于系统管理员而言尤其重要。但是,它的缺点是会消耗很多系统资源。 5.应用实例 使用top命令可以监视指定用户,缺省情况是监视所有用户的进程。如果想查看指定用户的情况,在终端中按“U”键,然后输入用户名,系统就会切换为指定用户的进程运行界面,见图2所示。 图2 使用top命令监视指定用户 free 1.作用 free命令用来显示内存的使用情况,使用权限是所有用户。 2.格式 free [-b|-k|-m] [-o] [-s delay] [-t] [-V] 3.主要参数 -b -k -m:分别以字节(KB、MB)为单位显示内存使用情况。 -s delay:显示每隔多少秒数来显示一次内存使用情况。 -t:显示内存总和列。 -o:不显示缓冲区调节列。 4.应用实例 free命令是用来查看内存使用情况的主要命令。和top命令相比,它的优点是使用简单,并且只占用很少的系统资源。通过-S参数可以使用free命令不间断地监视有多少内存在使用,这样可以把它当作一个方便实时监控。 #free -b -s5 使用这个命令后终端会连续不断地报告内存使用情况(以字节为单位),每5秒更新一次。 quota 1.作用 quota命令用来显示磁盘使用情况和限制情况,使用权限超级用户。 2.格式 quota [-g][-u][-v][-p] 用户名 组名 3.参数 -g:显示用户所在组的磁盘使用限制。 -u:显示用户的磁盘使用限制。 -v:显示没有分配空间的文件系统的分配情况。 -p:显示简化信息。 4.应用实例 在企业应用中磁盘配额非常重要,普通用户要学会看懂自己的磁盘使用情况。要查询自己的磁盘配额可以使用下面命令(下例中用户账号是caojh): #quota caojh Disk quotas for user caojh(uid 502): Filesystem blocks quota limit grace files quota limit grace /dev/hda3 58 200000 400000 41 500 1000 以上显示ID号为502的caojh账号,文件个数设置为500~1000个,硬盘空间限制设置为200MB~400MB。一旦磁盘配额要用完时,就需要删除一些垃圾文件或向系统管理员请求追加配额。 at 1.作用 at命令用来在指定时刻执行指定的命令序列。 2.格式 at [-V] [-q x] [-f file] [-m] time 3.主要参数 -V:显示标准错误输出。 -q:许多队列输出。 -f:从文件中读取作业。 -m:执行完作业后发送电子邮件到用户。 time:设定作业执行的时间。time格式有严格的要求,由小时、分钟、日期和时间的偏移量组成,其中日期的格式为MM.DD.YY,MM是分钟,DD是日期,YY是指年份。偏移量的格式为时间+偏移量,单位是minutes、hours和days。 4.应用实例 #at -f data 15:30 +2 days 上面命令表示让系统在两天后的17:30执行文件data中指明的作业。 lp 1.作用 lp是打印文件的命令,使用权限是所有用户。 2.格式 lp [-c][-d][-m][-number][-title][-p] 3.主要参数 -c:先拷贝文件再打印。 -d:打印队列文件。 -m:打印结束后发送电子邮件到用户。 -number:打印份数。 -title:打印标题。 -p:设定打印的优先级别,最高为100。 4.应用实例 (1)使用lp命令打印多个文件 #lp 2 3 4 request id is 11 (3 file(s)) 其中2、3、4分别是文件名;“request id is 11 (3 file(s)) ”表示这是第11个打印命令,依次打印这三个文件。 (2)设定打印优先级别 #lp lp -d LaserJet -p 90 /etc/aliases 通过添加“-p 90”,规定了打印作业的优先级为90。它将在优先级低于90的打印作业之前打印,包括没有设置优先级的作业,缺省优先级是50 useradd 1.作用 useradd命令用来建立用户帐号和创建用户的起始目录,使用权限是超级用户。 2.格式 useradd [-d home] [-s shell] [-c comment] [-m [-k template]] [-f inactive] [-e expire ] [-p passwd] [-r] name 3.主要参数 -c:加上备注文字,备注文字保存在passwd的备注栏中。  -d:指定用户登入时的启始目录。 -D:变更预设值。 -e:指定账号的有效期限,缺省表示永久有效。 -f:指定在密码过期后多少天即关闭该账号。 -g:指定用户所属的群组。 -G:指定用户所属的附加群组。 -m:自动建立用户的登入目录。 -M:不要自动建立用户的登入目录。 -n:取消建立以用户名称为名的群组。 -r:建立系统账号。 -s:指定用户登入后所使用的shell。 -u:指定用户ID号。 4.说明 useradd可用来建立用户账号,它和adduser命令是相同的。账号建好之后,再用passwd设定账号的密码。使用useradd命令所建立的账号,实际上是保存在/etc/passwd文本文件中。 5.应用实例 建立一个新用户账户,并设置ID: #useradd caojh -u 544 需要说明的是,设定ID值时尽量要大于500,以免冲突。因为Linux安装后会建立一些特殊用户,一般0到499之间的值留给bin、mail这样的系统账号。 groupadd 1.作用 groupadd命令用于将新组加入系统。 2.格式 groupadd [-g gid] [-o]] [-r] [-f] groupname 3.主要参数 -g gid:指定组ID号。 -o:允许组ID号,不必惟一。 -r:加入组ID号,低于499系统账号。 -f:加入已经有的组时,发展程序退出。 4.应用实例 建立一个新组,并设置组ID加入系统: #groupadd -g 344 cjh 此时在/etc/passwd文件中产生一个组ID(GID)是344的项目。 kill 1.作用 kill命令用来中止一个进程。 2.格式 kill [ -s signal | -p ] [ -a ] pid ... kill -l [ signal ] 3.参数 -s:指定发送的信号。 -p:模拟发送信号。 -l:指定信号的名称列表。 pid:要中止进程的ID号。 Signal:表示信号。 4.说明 进程是Linux系统中一个非常重要的概念。Linux是一个多任务的操作系统,系统上经常同时运行着多个进程。我们不关心这些进程究竟是如何分配的,或者是内核如何管理分配时间片的,所关心的是如何去控制这些进程,让它们能够很好地为用户服务。 Linux 操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性。交互进程是由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。批处理进程和终端没有联系,是一个进程序列。监控进程(也称系统守护进程)时Linux系统启动时启动的进程,并在后台运行。例如,httpd是著名的 Apache服务的监控进程。 kill命令的工作原理是,向Linux系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核就可以对进程标识号指定的进程进行操作。比如在top命令中,我们看到系统运行许多进程,有时就需要使用kill中止某些进程来提高系统资源。在讲解安装和登陆命令时,曾提到系统多个虚拟控制台的作用是当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作关闭这个程序。此时使用的命令就是kill,因为kill是大多数Shell内部命令可以直接调用的。 5.应用实例 (1)强行中止(经常使用杀掉)一个进程标识号为324的进程: #kill -9 324 (2)解除Linux系统的死锁 在Linux 中有时会发生这样一种情况:一个程序崩溃,并且处于死锁的状态。此时一般不用重新启动计算机,只需要中止(或者说是关闭)这个有问题的程序即可。当 kill处于X-Window界面时,主要的程序(除了崩溃的程序之外)一般都已经正常启动了。此时打开一个终端,在那里中止有问题的程序。比如,如果 Mozilla浏览程序出现了锁死的情况,可以使用kill命令来中止所有包含有Mozolla浏览的程序。首先用top命令查处该程序的PID,然后使用kill命令停止这个程序: #kill -SIGKILL XXX 其中,XXX是包含有Mozolla浏览的程序的进程标识号。 (3)使用命令回收内存 我们知道内存对于系统是非常重要的,回收内存可以提高系统资源。kill命令可以及时地中止一些“越轨”的程序或很长时间没有相应的程序。例如,使用top命令发现一个无用 (Zombie) 的进程,此时可以使用下面命令: #kill -9 XXX 其中,XXX是无用的进程标识号。 然后使用下面命令: #free 此时会发现可用内存容量增加了。 (4)killall命令 Linux下还提供了一个killall命令,可以直接使用进程的名字而不是进程标识号,例如: # killall -HUP inetd crontab 1.作用 使用crontab命令可以修改crontab配置文件,然后该配置由cron公用程序在适当的时间执行,该命令使用权限是所有用户。 2.格式 crontab [ -u user ] 文件 crontab [ -u user ] { -l | -r | -e } 3.主要参数 -e:执行文字编辑来设定时程表,内定的文字编辑是vi。 -r:删除目前的时程表。 -l:列出目前的时程表。 crontab 文件的格式为“M H D m d cmd”。其中,M代表分钟(0~59),H代表小时(0~23),D代表天(1~31),m代表月(1~12),d代表一星期内的天(0~6,0为星期天)。cmd表示要运行的程序,它被送入sh执行,这个Shell只有USER、HOME、SHELL三个环境变量。 4.说明 和at命令相比,crontab命令适合完成固定周期的任务。 5.应用实例 设置一个定时、定期的系统提示: [cao @www cao]#crontab -e 此时系统会打开一个vi编辑。 如果输入以下内容:35 17 * * 5 wall "Tomorrow is Saturday I will go CS",然后存盘退出。这时在/var/spool/cron/目录下会生产一个cao的文件,内容如下: # DO NOT EDIT THIS FILE - edit the master and reinstall. # (/tmp/crontab.2707 installed on Thu Jan 1 22:01:51 2004) # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $) 35 17 * * 5 wall "Tomorrow is Saturday I will play CS " 这样每个星期五17:35系统就会弹出一个终端,提醒星期六可以打打CS了!显示结果见图3所示。 图3 一个定时、定期的系统提示 动手练习 1.联合使用kill和top命令观察系统性能的变化 首先启动一个终端运行top命令,然后再启动一个终端使用kill命令,见图4所示。 图4 观察kill命令对top终端的影响 这时利用上面介绍的kill命令来中止一些程序: #kill SIGKILL XXX 然后再看top命令终端的变化,包括内存容量、CPU使用率、系统负载等。注意,有些进程是不能中止的,不过学习Linux命令时可以试试,看看系统有什么反应。 2.使用at和halt命令定时关机 首先设定关机时间是17:35,输入下面代码: #at 17:35 warning: commands will be executed using (in order) a) $SHELL b) login shell c) /bin/sh at>halt `-i -p at> job 6 at 2004-01-01 17:35 此时实际上就已经进入Linux系统的Shell,并且编写一个最简单程序:halt -i -p。上面Shell中的文本结束符号表示按“Ctrl+D”组合键关闭命令,提交任务退出Shell。“Job 6 at 2004-01-01 17:35”表示系统接受第6个at命令,在“2004-01-01 17:35”时执行命令:先把所有网络相关的装置停止,关闭系统后关闭电源。 3.用crontab命令实现每天定时的病毒扫描 前面已经介绍了一个简单的crontab命令操作,这里看一些更重要的操作。 (1)建立一个文件文件名称自己设定,假设为caoproject: #crontab -e (2)文件内容如下: 05 09 * * * antivir 用vi编辑后存盘退出。antivir是一个查杀Linux病毒的软件,当然需要时先安装在系统中。 (3)使用crontab命令添加到任务列表中: #crontab caoproject 这样系统内所有用户在每天的9点05分会自动进行病毒扫描。 4.用kill使修改的配置文件马上生效 Windows用户一般都知道,重要配置文件修改后往往都要重新启动计算机才能使修改生效。而Linux由于采用了模块化设计,可以自己根据需要实时设定服务。这里以网络服务inetd为例介绍一些操作技巧。 inetd 是一个监听守护进程,监听与提供互联网服务进程(如rlogin、telnet、ftp、rsh)进行连接的要求,并扩展所需的服务进程。默认情况下, inetd监听的这些daemon均列于/etc /inetd.conf文件中。编辑/etc/inetd.conf文件,可以改变inetd启动服务守护进程的选项,然后驱使inetd以 SIGHUP(signal 1)向当前的inetd进程发送信号,使inetd重读该文件。这一过程由kill命令来实现。 用vi或其它编辑修改inetd.conf后,首先使用下面命令: #ps -ef |grep inetd 上面代码表明查询inetd.conf的进程号(PID),这里假设是1426,然后使用下面命令: # kill -1426 inetd 这样配置文件就生效了。 这一讲介绍的系统管理命令都是比较重要的,特别是crontab命令和quota命令使用起来会有一定难度,需要多做一些练习。另外,使用kill命令要注意“-9“这个参数,练习时最好不要运行一些重要的程序。 -------------------------------------------------------------------------------- Linux必学的60个命令(4)-网络操作命令 Linux必学的60个命令:网络操作命令 因为Linux系统是在Internet上起源和发展的,它与生俱来拥有强大的网络功能和丰富的网络应用软件,尤其是TCP/IP网络协议的实现尤为成熟。 Linux的网络命令比较多,其中一些命令像ping、 ftp、telnet、route、netstat等在其它操作系统上也能看到,但也有一些Unix/Linux系统独有的命令,如ifconfig、 finger、mail等。Linux网络操作命令的一个特点是,命令参数选项和功能很多,一个命令往往还可以实现其它命令的功能。 ifconfig 1.作用 ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。 2.格式 ifconfig -interface [options] address 3.主要参数 -interface:指定的网络接口名,如eth0和eth1。 up:激活指定的网络接口卡。 down:关闭指定的网络接口。 broadcast address:设置接口的广播地址。 pointopoint:启用点对点方式。 address:设置指定接口设备的IP地址。 netmask address:设置接口的子网掩码。 4.应用说明 ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令: #fconfig eth0 210.34.6.89 netmask 255.255.255.128 broadcast 210.34.6.127 该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机所有激活接口的信息。带有“-a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机重新启动以后将会丢失。 如果要暂停某个网络接口的工作,可以使用down参数: #ifconfig eth0 down ip 1.作用 ip是iproute2软件包里面的一个强大的网络配置工具,它能够替代一些传统的网络管理工具,例如ifconfig、route等,使用权限为超级用户。几乎所有的Linux发行版本都支持该命令。 2.格式 ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]] 3.主要参数 OPTIONS是修改ip行为或改变其输出的选项。所有的选项都是以-字符开头,分为长、短两种形式。目前,ip支持如表1所示选项。 OBJECT是要管理者获取信息的对象。目前ip认识的对象见表2所示。 表1 ip支持的选项 -V,-Version 打印ip的版本并退出。 -s,-stats,-statistics 输出更为详尽的信息。如果这个选项出现两次或多次,则输出的信息将更为详尽。 -f,-family 这个选项后面接协议种类,包括inet、inet6或link,强调使用的协议种类。如果没有足够的信息告诉ip使用的协议种类,ip就会使用默认值inet或any。link比较特殊,它表示不涉及任何网络协议。 -4 是-family inet的简写。 -6 是-family inet6的简写。 -0 是-family link的简写。 -o,-oneline 对每行记录都使用单行输出,回行用字符代替。如果需要使用wc、grep等工具处理ip的输出,则会用到这个选项。 -r,-resolve 查询域名解析系统,用获得的主机名代替主机IP地址 COMMAND 设置针对指定对象执行的操作,它和对象的类型有关。一般情况下,ip支持对象的增加(add)、删除(delete)和展示(show或list)。有些对象不支持这些操作,或者有其它的一些命令。对于所有的对象,用户可以使用help命令获得帮助。这个命令会列出这个对象支持的命令和参数的语法。如果没有指定对象的操作命令,ip会使用默认的命令。一般情况下,默认命令是list,如果对象不能列出,就会执行help命令。 ARGUMENTS 是命令的一些参数,它们倚赖于对象和命令。ip支持两种类型的参数:flag和parameter。flag由一个关键词组成;parameter由一个关键词加一个数值组成。为了方便,每个命令都有一个可以忽略的默认参数。例如,参数dev是ip link命令的默认参数,因此ip link ls eth0等于ip link ls dev eth0。我们将在后面的详细介绍每个命令的使用,命令的默认参数将使用default标出。 4.应用实例 添加IP地址192.168.2.2/24到eth0网卡上: #ip addr add 192.168.1.1/24 dev eth0 丢弃源地址属于192.168.2.0/24网络的所有数据报: #ip rule add from 192.168.2.0/24 prio 32777 reject ping 1.作用 ping检测主机网络接口状态,使用权限是所有用户。 2.格式 ping [-dfnqrRv][-c][-i][-I][-l][-p][-s][-t] IP地址 3.主要参数 -d:使用Socket的SO_DEBUG功能。 -c:设置完成要求回应的次数。 -f:极限检测。 -i:指定收发信息的间隔秒数。 -I:网络界面使用指定的网络界面送出数据包。 -l:前置载入,设置在送出要求信息之前,先行发出的数据包。 -n:只输出数值。 -p:设置填满数据包的范本样式。 -q:不显示指令执行过程,开头和结尾的相关信息除外。 -r:忽略普通的Routing Table,直接将数据包送到远端主机上。 -R:记录路由过程。 -s:设置数据包的大小。 -t:设置存活数值TTL的大小。 -v:详细显示指令的执行过程。 ping 命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览查看一个网页,但是却无法 ping通,这是因为一些网站处于安全考虑安装了防火墙。另外,也可以在自己计算机上试一试,通过下面的方法使系统对ping没有反应: # echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all netstat 1.作用 检查整个Linux网络状态。 2.格式 netstat [-acCeFghilMnNoprstuvVwx][-A][--ip] 3.主要参数 -a--all:显示所有连线中的Socket。 -A:列出该网络类型连线中的IP相关地址和网络类型。 -c--continuous:持续列出网络状态。 -C--cache:显示路由配置的快取信息。 -e--extend:显示网络其它相关信息。 -F--fib:显示FIB。 -g--groups:显示多重广播功能群组组员名单。 -h--help:在线帮助。 -i--interfaces:显示网络界面信息表单。 -l--listening:显示监控中的服务的Socket。 -M--masquerade:显示伪装的网络连线。 -n--numeric:直接使用IP地址,而不通过域名服务。 -N--netlink--symbolic:显示网络硬件外围设备的符号连接名称。 -o--timers:显示计时。 -p--programs:显示正在使用Socket的程序识别码和程序名称。 -r--route:显示Routing Table。 -s--statistice:显示网络工作信息统计表。 -t--tcp:显示TCP传输协议的连线状况。 -u--udp:显示UDP传输协议的连线状况。 -v--verbose:显示指令执行过程。 -V--version:显示版本信息。 -w--raw:显示RAW传输协议的连线状况。 -x--unix:和指定“-A unix”参数相同。 --ip--inet:和指定“-A inet”参数相同。 4.应用实例 netstat 主要用于Linux察看自身的网络状况,如开启的端口、在为哪些用户服务,以及服务的状态等。此外,它还显示系统路由表、网络接口状态等。可以说,它是一个综合性的网络状态的察看工具。在默认情况下,netstat只显示已建立连接的端口。如果要显示处于监听状态的所有端口,使用-a参数即可: #netstat -a Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 *:32768 *:* LISTEN tcp 0 0 *:32769 *:* LISTEN tcp 0 0 *:nfs *:* LISTEN tcp 0 0 *:32770 *:* LISTEN tcp 0 0 *:868 *:* LISTEN tcp 0 0 *:617 *:* LISTEN tcp 0 0 *:mysql *:* LISTEN tcp 0 0 *:netbios-ssn *:* LISTEN tcp 0 0 *:sunrpc *:* LISTEN tcp 0 0 *:10000 *:* LISTEN tcp 0 0 *:http *:* LISTEN ...... 上面显示出,这台主机同时提供HTTP、FTP、NFS、MySQL等服务。 telnet 1.作用 telnet表示开启终端机阶段作业,并登入远端主机。telnet是一个Linux命令,同时也是一个协议(远程登陆协议)。 2.格式 telnet [-8acdEfFKLrx][-b][-e][-k][-l][-n][-S][-X][主机名称IP地址] 3.主要参数 -8:允许使用8位字符资料,包括输入与输出。 -a:尝试自动登入远端系统。 -b:使用别名指定远端主机名称。 -c:不读取用户专属目录里的.telnetrc文件。 -d:启动排错模式。 -e:设置脱离字符。 -E:滤除脱离字符。 -f:此参数的效果和指定“-F”参数相同。 -F:使用Kerberos V5认证时,加上此参数可把本地主机的认证数据上传到远端主机。 -k:使用Kerberos认证时,加上此参数让远端主机采用指定的领域名,而非该主机的域名。 -K:不自动登入远端主机。 -l:指定要登入远端主机的用户名称。 -L:允许输出8位字符资料。 -n:指定文件记录相关信息。 -r:使用类似rlogin指令的用户界面。 -S:服务类型,设置telnet连线所需的IP TOS信息。 -x:假设主机有支持数据加密的功能,就使用它。 -X:关闭指定的认证形态。 4.应用说明 用户使用telnet命令可以进行远程登录,并在远程计算机之间进行通信。用户通过网络在远程计算机上登录,就像登录到本地机上执行命令一样。为了通过 telnet登录到远程计算机上,必须知道远程机上的合法用户名和口令。虽然有些系统确实为远程用户提供登录功能,但出于对安全的考虑,要限制来宾的操作权限,因此,这种情况下能使用的功能是很少的。 t
实验三 移植U-Boot-1.3.1 实验 【实验目的】 了解 U-Boot-1.3.1 的代码结构,掌握其移植方法。 【实验环境】 1、Ubuntu 7.0.4发行版 2、u-boot-1.3.1 3、FS2410平台 4、交叉编译 arm-softfloat-linux-gnu-gcc-3.4.5 【实验步骤】 一、建立自己的平台类型 (1)解压文件 #tar jxvf u-boot-1.3.1.tar.bz2 (2)进入 U-Boot源码目录 #cd u-boot-1.3.1 (3)创建自己的开发板: #cd board #cp smdk2410 fs2410 –a #cd fs2410 #mv smdk2410.c fs2410.c #vi Makefile (将 smdk2410修改为 fs2410) #cd ../../include/configs #cp smdk2410.h fs2410.h 退回 U-Boot根目录:#cd ../../ (4)建立编译选项 #vi Makefile smdk2410_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0 fs2410_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t fs2410 NULL s3c24x0 arm: CPU的架构(ARCH) arm920t: CPU的类型(CPU),其对应于 cpu/arm920t子目录。 fs2410: 开发板的型号(BOARD),对应于 board/fs2410目录。 NULL: 开发者/或经销商(vender),本例为空 s3c24x0: 片上系统(SOC) (5)编译 #make fs2410_config; #make 本步骤将编译 u-boot.bin文件,但此时还无法运行在FS2410开发板上。 二、修改 cpu/arm920t/start.S文件,完成 U-Boot的重定向 (1)修改中断禁止部分 # if defined(CONFIG_S3C2410) ldr r1, =0x7ff /*根据 2410 芯片手册,INTSUBMSK 有 11位可用 */ ldr r0, =INTSUBMSK Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) str r1, [r0] # endif (2)修改时钟设置(这个文件要根据具体的平台进行修改) (3)将从Nor Flash启动改成从 NAND Flash启动 在文件中找到 195-201 代码,并在 201行后面添加如下代码: 195 copy_loop: 196 ldmia r0!, {r3-r10} /* copy from source address [r0] */ 197 stmiar1!, {r3-r10} /* copy to target address [r1] */ 198 cmp r0, r2 /* until source end addreee [r2] */ 199 ble copy_loop 200 #endif /* CONFIG_SKIP_RELOCATE_UBOOT */ 201 #endif #ifdef CONFIG_S3C2410_NAND_BOOT @ reset NAND mov r1, #NAND_CTL_BASE ldr r2, =0xf830 @ initial value str r2, [r1, #oNFCONF] ldr r2, [r1, #oNFCONF] bic r2, r2, #0x800 @ enable chip str r2, [r1, #oNFCONF] mov r2, #0xff @ RESET command strb r2, [r1, #oNFCMD] mov r3, #0 @ wait nand1: add r3, r3, #0x1 cmp r3, #0xa blt nand1 nand2: ldr r2, [r1, #oNFSTAT] @ wait ready tst r2, #0x1 beq nand2 ldr r2, [r1, #oNFCONF] orr r2, r2, #0x800 @ disable chip str r2, [r1, #oNFCONF] @ get read to call C functions (for nand_read()) ldr sp, DW_STACK_START @ setup stack pointer mov fp, #0 @ no previous frame, so fp=0 @ copy U-Boot to RAM ldr r0, =TEXT_BASE mov r1, #0x0 mov r2, #0x30000 bl nand_read_ll tst r0, #0x0 beq ok_nand_read Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)bad_nand_read: loop2: b loop2 @ infinite loop ok_nand_read: @ verify mov r0, #0 ldr r1, =TEXT_BASE mov r2, #0x400 @ 4 bytes * 1024 = 4K-bytes go_next: ldr r3, [r0], #4 ldr r4, [r1], #4 teq r3, r4 bne notmatch subs r2, r2, #4 beq stack_setup bne go_next notmatch: loop3: b loop3 @ infinite loop #endif @ CONFIG_S3C2410_NAND_BOOT (4)在 “ _start_armboot: .word start_armboot ”后加入: .align 2 DW_STACK_START: .word STACK_BASE+STACK_SIZE-4 三、创建 board/fs2410/nand_read.c 文件,加入读 NAND Flash 的操作。 #include #define __REGb(x) (*(volatile unsigned char *)(x)) #define __REGi(x) (*(volatile unsigned int *)(x)) #define NF_BASE 0x4e000000 # if defined(CONFIG_S3C2410) #define NFCONF __REGi(NF_BASE + 0x0) #define NFCMD __REGb(NF_BASE + 0x4) #define NFADDR __REGb(NF_BASE + 0x8) #define NFDATA __REGb(NF_BASE + 0xc) #define NFSTAT __REGb(NF_BASE + 0x10) #define BUSY 1 inline void wait_idle(void) { int i; while(!(NFSTAT & BUSY)) for(i=0; i<10; i++); } /* low level nand read function */ int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size) { int i, j; Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) { return -1; /* invalid alignment */ } /* chip Enable */ NFCONF &= ~0x800; for(i=0; i<10; i++); for(i=start_addr; i > 9) & 0xff; NFADDR = (i >> 17) & 0xff; NFADDR = (i >> 25) & 0xff; wait_idle(); for(j=0; j NFCONF = conf; } Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)static inline void NF_Cmd(u8 cmd) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->NFCMD = cmd; } static inline void NF_CmdW(u8 cmd) { NF_Cmd(cmd); udelay(1); } static inline void NF_Addr(u8 addr) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->NFADDR = addr; } static inline void NF_WaitRB(void) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); while (!(nand->NFSTAT & (1<NFDATA = data; } static inline u8 NF_Read(void) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); return(nand->NFDATA); } static inline u32 NF_Read_ECC(void) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); return(nand->NFECC); } static inline void NF_SetCE(NFCE_STATE s) { S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); switch (s) { case NFCE_LOW: nand->NFCONF &= ~(1<NFCONF |= (1<NFCONF |= (1<<12); } extern ulong nand_probe(ulong physadr); static inline void NF_Reset(void) { int i; NF_SetCE(NFCE_LOW); NF_Cmd(0xFF); /* reset command */ for(i = 0; i < 10; i++); /* tWB = 100ns. */ NF_WaitRB(); /* wait 200~500us; */ NF_SetCE(NFCE_HIGH); } static inline void NF_Init(void) { #if 0 #define TACLS 0 #define TWRPH0 3 #define TWRPH1 0 #else #define TACLS 0 #define TWRPH0 4 #define TWRPH1 2 #endif #if defined(CONFIG_S3C2440) NF_Conf((TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)); NF_Cont((1<<6)|(1<<4)|(1<<1)|(1<<0)); #else NF_Conf((1<<15)|(0<<14)|(0<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<NFCONF = (1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<> 20); } #endif (2)配置GPIO 和 PLL 根据开发板的硬件说明和芯片手册,修改GPIO 和 PLL的配置。 六、修改 include/configs/fs2410.h 头文件 (1)加入命令定义 /* Command line configuration. */ #include #define CONFIG_CMD_ASKENV #define CONFIG_CMD_CACHE #define CONFIG_CMD_DATE #define CONFIG_CMD_DHCP #define CONFIG_CMD_ELF #define CONFIG_CMD_PING #define CONFIG_CMD_NAND #define CONFIG_CMD_REGINFO #define CONFIG_CMD_USB #define CONFIG_CMD_FAT (2)修改命令提示符 #define CFG_PROMPT "SMDK2410 # " -> #define CFG_PROMPT "FS2410# " (3)修改默认载入地址 #define CFG_LOAD_ADDR 0x33000000 -> #define CFG_LOAD_ADDR 0x30008000 (4)加入 Flash环境信息 #define CFG_ENV_IS_IN_NAND 1 #define CFG_ENV_OFFSET 0X30000 #define CFG_NAND_LEGACY //#define CFG_ENV_IS_IN_FLASH 1 #define CFG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */ (5)加入Nand Flash设置(在文件结尾处) /* NAND flash settings */ #if defined(CONFIG_CMD_NAND) #define CFG_NAND_BASE 0x4E000000 /* NandFlash控制在SFR区起始寄存地址 */ #define CFG_MAX_NAND_DEVICE 1 /* 支持的最在Nand Flash数据 */ #define SECTORSIZE 512 /* 1页的大小 */ #define NAND_SECTOR_SIZE SECTORSIZE Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)#define NAND_BLOCK_MASK 511 /* 页掩码 */ #define ADDR_COLUMN 1 /* 一个字节的Column地址 */ #define ADDR_PAGE 3 /* 3字节的页块地址!!!!!*/ #define ADDR_COLUMN_PAGE 4 /* 总共4字节的页块地址!!!!! */ #define NAND_ChipID_UNKNOWN 0x00 /* 未知芯片的ID号 */ #define NAND_MAX_FLOORS 1 #define NAND_MAX_CHIPS 1 /* Nand Flash命令层底层接口函数 */ #define WRITE_NAND_ADDRESS(d, adr) {rNFADDR = d;} #define WRITE_NAND(d, adr) {rNFDATA = d;} #define READ_NAND(adr) (rNFDATA) #define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));} #define WRITE_NAND_COMMAND(d, adr) {rNFCMD = d;} #define WRITE_NAND_COMMANDW(d, adr) NF_CmdW(d) #define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);} #define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);} /* the following functions are NOP's because S3C24X0 handles this in hardware */ #define NAND_CTL_CLRALE(nandptr) #define NAND_CTL_SETALE(nandptr) #define NAND_CTL_CLRCLE(nandptr) #define NAND_CTL_SETCLE(nandptr) /* 允许 Nand Flash写校验 */ #define CONFIG_MTD_NAND_VERIFY_WRITE 1 (6)加入Nand Flash启动支持(在文件结尾处) /* Nandflash Boot*/ #define STACK_BASE 0x33f00000 #define STACK_SIZE 0x8000 /* NAND Flash Controller */ #define NAND_CTL_BASE 0x4E000000 #define bINT_CTL(Nb) __REG(INT_CTL_BASE + (Nb)) /* Offset */ #define oNFCONF 0x00 #define CONFIG_S3C2410_NAND_BOOT 1 /* Offset */ #define oNFCONF 0x00 #define oNFCMD 0x04 #define oNFADDR 0x08 #define oNFDATA 0x0c #define oNFSTAT 0x10 #define oNFECC 0x14 #define rNFCONF (*(volatile unsigned int *)0x4e000000) #define rNFCMD (*(volatile unsigned char *)0x4e000004) Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)#define rNFADDR (*(volatile unsigned char *)0x4e000008) #define rNFDATA (*(volatile unsigned char *)0x4e00000c) #define rNFSTAT (*(volatile unsigned int *)0x4e000010) #define rNFECC (*(volatile unsigned int *)0x4e000014) #define rNFECC0 (*(volatile unsigned char *)0x4e000014) #define rNFECC1 (*(volatile unsigned char *)0x4e000015) #define rNFECC2 (*(volatile unsigned char *)0x4e000016) #endif (7)加入 jffs2的支持 /*JFFS2 Support */ #undef CONFIG_JFFS2_CMDLINE #define CONFIG_JFFS2_NAND 1 #define CONFIG_JFFS2_DEV "nand0" #define CONFIG_JFFS2_PART_SIZE 0x4c0000 #define CONFIG_JFFS2_PART_OFFSET 0x40000 /*JFFS2 Support */ (8)加入 usb的支持 /* USB Support*/ #define CONFIG_USB_OHCI #define CONFIG_USB_STORAGE #define CONFIG_USB_KEYBOARD #define CONFIG_DOS_PARTITION #define CFG_DEVICE_DEREGISTER #define CONFIG_SUPPORT_VFAT #define LITTLEENDIAN /* USB Support*/ 七、修改 include/linux/mtd/nand.h头文件 屏蔽如下定义: #if 0 /* Select the chip by setting nCE to low */ #define NAND_CTL_SETNCE 1 /* Deselect the chip by setting nCE to high */ #define NAND_CTL_CLRNCE 2 /* Select the command latch by setting CLE to high */ #define NAND_CTL_SETCLE 3 /* Deselect the command latch by setting CLE to low */ #define NAND_CTL_CLRCLE 4 /* Select the address latch by setting ALE to high */ #define NAND_CTL_SETALE 5 /* Deselect the address latch by setting ALE to low */ #define NAND_CTL_CLRALE 6 /* Set write protection by setting WP to high. Not used! */ #define NAND_CTL_SETWP 7 /* Clear write protection by setting WP to low. Not used! */ Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)#define NAND_CTL_CLRWP 8 #endif 八、修改 include/linux/mtd/nand_ids.h 头文件 在该文件中加入开发板的 NAND Flash型号 {"Samsung K9F1208U0B", NAND_MFR_SAMSUNG, 0x76, 26, 0, 4, 0x4000, 0}, 九、修改 common/env_nand.c文件 我们使用了早期的Nand读写方式,因此做出下列移植: (1) 加入函数原型定义 extern struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE]; extern int nand_legacy_erase(struct nand_chip *nand, size_t ofs, size_t len, int clean); /* info for NAND chips, defined in drivers/nand/nand.c */ extern nand_info_t nand_info[CFG_MAX_NAND_DEVICE]; (2) 修改saveenv函数 注释//if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE)) 加入:if (nand_legacy_erase(nand_dev_desc + 0, CFG_ENV_OFFSET, CFG_ENV_SIZE, 0)) 注释//ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr); 加入:ret = nand_legacy_rw(nand_dev_desc + 0,0x00 | 0x02, CFG_ENV_OFFSET, CFG_ENV_SIZE, &total, (u_char*)env_ptr); (3) 修改env_relocate_spec函数 注释//ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr); 加入:ret = nand_legacy_rw(nand_dev_desc + 0, 0x01 | 0x02, CFG_ENV_OFFSET, CFG_ENV_SIZE, &total, (u_char*)env_ptr); 十、修改 common/cmd_boot.c 文件,添加内核启动参数设置 (1) 首先添加头文件#include (2) 修改do_go函数。具体修改为: int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { #if defined(CONFIG_I386) DECLARE_GLOBAL_DATA_PTR; #endif ulong addr, rc; int rcode = 0; ///////////////////////////////////////////////////////////////////////// char *commandline = getenv("bootargs"); struct param_struct *my_params=(struct param_struct *)0x30000100; memset(my_params,0,sizeof(struct param_struct)); my_params->u1.s.page_size=4096; my_params->u1.s.nr_pages=0x4000000>>12; memcpy(my_params->commandline,commandline,strlen(commandline)+1); Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)/////////////////////////////////////////////////////////////////////// if (argc usage); return 1; } addr = simple_strtoul(argv[1], NULL, 16); printf ("## Starting application at 0x%08lX ...\n", addr); /* * pass address parameter as argv[0] (aka command name), * and all remaining args */ #if defined(CONFIG_I386) /* * x86 does not use a dedicated register to pass the pointer * to the global_data */ argv[0] = (char *)gd; #endif #if !defined(CONFIG_NIOS) //////////////////////////////////////////////////////////////////// __asm__( "mov r1, #193\n" "mov ip, #0\n" "mcr p15, 0, ip, c13, c0, 0\n" /* zero PID */ "mcr p15, 0, ip, c7, c7, 0\n" /* invalidate I,D caches */ "mcr p15, 0, ip, c7, c10, 4\n" /* drain write buffer */ "mcr p15, 0, ip, c8, c7, 0\n" /* invalidate I,D TLBs */ "mrc p15, 0, ip, c1, c0, 0\n" /* get control register */ "bic ip, ip, #0x0001\n" /* disable MMU */ "mov pc, %0\n" "nop\n" : :"r"(addr) ); ////////////////////////////////////////////////////////// rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]); #else /* * Nios function pointers are address >> 1 */ rc = ((ulong (*)(int, char *[]))(addr>>1)) (--argc, &argv[1]); #endif if (rc != 0) rcode = 1; Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) printf ("## Application terminated, rc = 0x%lX\n", rc); return rcode; } 其中用//括起来的代码是要添加的代码。否则在引导LINUX 内核的时候会出现一个 Error: a 或无法传递内核启动参数的错误。其原因是平台号或启动参数没有正确传入内核。 十一、交叉编译 U-BOOT #make distclean #make fs2410_config export PATH=$PATH:/home/linux/crosstool/gcc-3.4.5-glibc-2.3.6/arm-softfloat-linux-gnu/bin: #make CROSS_COMPILE= arm-softfloat-linux-gnu- 生成的 u-boot.bin 即为我们移植后的结果。下载到开发板上运行! Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)U-Boot简介 U-Boot,全称 Universal Boot Loader,是遵循 GPL 条款的开放源码项目。从 FADSROM、 8xxROM、PPCBOOT 逐步发展演化而来。其源码目录、编译形式与 Linux 内核很相似,事 实上,不少U-Boot源码就是相应的 Linux内核源程序的简化,尤其是一些设备的驱动程序, 这从U-Boot源码的注释中能体现这一点。 但是U-Boot不仅仅支持嵌入式Linux系统的引导, 当前,它还支持 NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS嵌入式操作系统。其目 前要支持的目标操作系统是OpenBSD, NetBSD, FreeBSD,4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS。这是 U-Boot中 Universal的一层含义,另外一层含义则是 U-Boot除了支持 PowerPC系列的处理外,还能 支持 MIPS、 x86、ARM、NIOS、XScale等诸多常用系列的处理。这两个特点正是U-Boot 项目的开发目标,即支持尽可能多的嵌入式处理和嵌入式操作系统。就目前来看,U-Boot 对 PowerPC 系列处理支持最为丰富,对 Linux 的支持最完善。其它系列的处理和操作 系统基本是在2002年11 月PPCBOOT改名为U-Boot后逐步扩充的。 从PPCBOOT向U-Boot 的顺利过渡,很大程度上归功于 U-Boot 的维护人德国 DENX 软件工程中心 Wolfgang Denk[以下简称 W.D]本人精湛专业水平和持着不懈的努力。当前,U-Boot 项目正在他的领 军之下,众多有志于开放源码 BOOT LOADER移植工作的嵌入式开发人员正如火如荼地将 各个不同系列嵌入式处理的移植工作不断展开和深入, 以支持更多的嵌入式操作系统的装 载与引导。 选择 U-Boot的理由: ① 开放源码; ② 支持多种嵌入式操作系统内核,如 Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS; ③ 支持多个处理系列,如 PowerPC、ARM、x86、MIPS、XScale; ④ 较高的可靠性和稳定性; ④ 较高的可靠性和稳定性; ⑤ 高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等; ⑥ 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、 RTC、键盘等; ⑦ 较为丰富的开发调试文档与强大的网络技术支持; U-Boot主要目录结构 - board 目标板相关文件,主要包含 SDRAM、FLASH 驱动; - common 独立于处理体系结构的通用代码,如内存大小探测与故障检测; - cpu 与处理相关的文件。如 mpc8xx子目录下含串口、网口、LCD 驱动及中断初始化等 文件; - driver 通用设备驱动,如 CFI FLASH 驱动(目前对INTEL FLASH 支持较好) Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)- doc U-Boot的说明文档; - examples可在 U-Boot下运行的示例程序;如 hello_world.c,timer.c; - include U-Boot头文件;尤其 configs子目录下与目标板相关的配置头文件是移植过程中经 常要修改的文件; - lib_xxx 处理体系相关的文件,如 lib_ppc, lib_arm目录分别包含与 PowerPC、ARM体系 结构相关的文件; - net 与网络功能相关的文件目录,如 bootp,nfs,tftp; - post 上电自检文件目录。尚有待于进一步完善; - rtc RTC驱动程序; - tools 用于创建 U-Boot S-RECORD 和 BIN 镜像文件的工具; U-Boot支持的主要功能 U-Boot可支持的主要功能列表 系统引导 支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统 支持 NFS挂载、从 FLASH 中引导压缩或非压缩系统内核; 基本辅助功能 强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适 合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲; 支持目标板环境参数多种存储方式,如 FLASH、NVRAM、EEPROM; CRC32校验,可校验 FLASH 中内核、RAMDISK 镜像文件是否完好; 设备驱动 串口、 SDRAM、 FLASH、 以太网、 LCD、 NVRAM、 EEPROM、 键盘、 USB、 PCMCIA、 PCI、RTC等驱动支持; 上电自检功能 SDRAM、FLASH 大小自动检测;SDRAM故障检测;CPU型号; 特殊功能 XIP内核引导; 移植前的准备 (1)、首先读读 uboot自带的 readme文件,了解了一个大概。 (2)、看看 common.h,这个文件定义了一些基本的东西,并包含了一些必要的头文件。再 看看 flash.h,这个文件里面定义了 flash_info_t为一个 struct。包含了 flash的一些属性定义。 并且定义了所有的 flash 的属性,其中,AMD 的有:AMD_ID_LV320B,定义为“#define AMD_ID_LV320B 0x22F922F9” 。 (3)、对于“./borad/at91rm9200dk/flash.c”的修改,有以下的方面: “void flash_identification(flash_info_t *info)”这个函数的目的是确认 flash的型号。注意的 是,这个函数里面有一些宏定义,直接读写了 flash。并获得 ID 号。 (4)、修改: ”./board/at91rm9200dk/config.mk”为 TEXT_BASE=0x21f80000 为 TEXT_BASE=0x21f00000 (当然,你应该根据自己的板子来 修改,和一级boot的定义的一致即可)。 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)(5)、再修改”./include/configs/at91rm9200dk.h”为 修改 flash和 SDRAM的大小。 (6)、另外一个要修改的文件是: ./borad/at91rm9200dk/flash.c。这个文件修改的部分比较的多。 a. 首先是OrgDef的定义,加上目前的 flash。 b. 接下来,修改”#define FLASH_BANK_SIZE 0x200000”为自己flash的 容量 c. 在修改函数 flash_identification(flash_info_t * info)里面的打印信息,这部分将在 u-boot 启动的时候显示。 d. 然后修改函数 flash_init(void)里面对一些变量的赋值。 e. 最后修改的是函数 flash_print_info(flash_info_t * info)里面实际打印的函数信息。 f. 还有一个函数需要修改,就是: “flash_erase” ,这个函数要检测先前知道的 flash类型是 否匹配,否则,直接就返回了。把这里给注释掉。 (7)、接下来看看 SDRAM的修改。 这个里面对于“SIZE”的定义都是基于字节计算的。 只要修改”./include/configs/at91rm9200dk.h”里面的 “#define PHYS_SDRAM_SIZE 0X200000”就可以了。注意,SIZE 是以字节为单位的。 (8)、还有一个地方要注意 就是按照目前的设定,一级 boot 把 u_boot 加载到了 SDRAM 的空间为:21F00000 -> 21F16B10,这恰好是 SDRAM的高端部分。另外,BSS为 21F1AE34。 (9)、编译后,可以写入 flash了。 a. 压缩这个 u-boot.bin “gzip –c u-boot.bin > u-boot.gz” 压缩后的文件大小为: 43Kbytes b. 接着把 boot.bin和 u-boot.gz 烧到 flash里面去。 Boot.bin大约 11kBytes,在 flash的 0x1000 0000 ~ 0x1000 3fff U-Boot移植过程 ① 获得发布的最新版本 U-Boot源码,与 Linux内核源码类似,也是 bzip2 的压缩格式。可 从 U-Boot的官方网站 http://sourceforge.net/projects/U-Boot上获得; ② 阅读相关文档,主要是 U-Boot 源码根目录下的 README 文档和 U-Boot 官方网站的 DULG ( The DENX U-Boot and Linux Guide ) 文档 http://www.denx.de/twiki/bin/view/DULG/Manual。尤其是DULG 文档,从如何安装建立交叉 开发环境和解决 U-Boot移植中常见问题都一一给出详尽的说明; Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) ③ 订阅 U-Boot 用户邮件列表 http://lists.sourceforge.net/lists/listinfo/u-boot-users。在移植 U-Boot 过程中遇有问题 , 在参考相关文档和搜 索 U-Boot-User 邮 件 档 案 库 http://sourceforge.net/mailarchive/forum.php?forum_id=12898 仍不能解决的情况下,第一时间 提交所遇到的这些问题,众多热心的 U-Boot开发人员会乐于迅速排查问题,而且很有可能, W.D本人会直接参与指导; ④ 在建立的开发环境下进行移植工作。绝大多数的开发环境是交叉开发环境。在这方面, DENX 和 MontaVista 均提供了完整的开发工具集; ⑤ 在目标板与开发主机间接入硬件调试。 这是进行U-Boot移植应当具备且非常关键的调 试工具。因为在整个 U-Boot的移植工作中,尤其是初始阶段,硬件调试是我们了解目标板 真实运行状态的唯一途径。 在这方面, W.D 本人和众多嵌入式开发人员倾向于使用 BDI2000。 一方面,其价格不如 ICE 调试昂贵,同时其可靠性高,功能强大,完全能胜任移植和调 试 U-Boot。另外,网上也有不少关于 BDI2000调试方面的参考文档。 ⑥ 如果在参考开发板上移植 U-Boot,可能需要移除目标板上已有的 BOOT LOADER。可以 根据板上 BOOT LOADER的说明文档,先着手解决在移除当前 BOOT LOADER的情况下, 如何进行恢复。以便今后在需要场合能重新装入原先的BOOT LOADER。 U-Boot移植方法 当前,对于 U-Boot的移植方法,大致分为两种。一种是先用 BDI2000创建目标板初始运行 环境,将 U- Boot镜像文件 u-boot.bin下载到目标板 RAM中的指定位置,然后,用 BDI2000 进行跟踪调试。其好处是不用将 U-Boot 镜像文件烧写到 FLASH 中去。但弊端在于对移植 开发人员的移植调试技能要求较高,BDI2000 的配置文件较为复杂。另外一种方法是用 BDI2000先将 U-Boot 镜像文件烧写到 FLASH 中去,然后利用GDB和 BDI2000进行调试。 这种方法所用 BDI2000的配置文件较为简单,调试过程与 U-Boot移植后运行过程相吻合, 即 U-Boot先从 FLASH 中运行,再重载至 RAM中相应位置,并从那里正式投入运行。唯一 感到有些麻烦的就是需要不断烧写 FLASH。 但考虑到 FLASH 常规擦写次数基本为 10万次 左右,作为移植 U-Boot,不会占用太多的次数,应该不会为 FLASH 烧写有什么担忧。同时, W. D本人也极力推荐使用后一种方法。笔者建议,除非U-Boot移植资深人士或有强有力的 技术支持,建议采用第二种移植方法。 U-Boot移植主要修改的文件 从移植 U-Boot最小要求-U-Boot能正常启动的角度出发,主要考虑修改如下文件: ① .h头文件,如 include/configs/RPXlite.h。可以是 U-Boot源码中已有的目标板头 文件,也可以是新命名的配置头文件;大多数的寄存参数都是在这一文件中设置完成的; ② .c文件, 如board/RPXlite/RPXlite.c。 它是SDRAM的驱动程序, 主要完成SDRAM Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)的 UPM表设置,上电初始化。 ③ FLASH的驱动程序, 如board/RPXlite/flash.c, 或common/cfi_flash.c。 可在参考已有FLASH 驱动的基础上,结合目标板 FLASH 数据手册,进行适当修改; ④ 串口驱动,如修改cpu/mpc8xx/serial.c串口收发芯片使能部分。 U-Boot移植要点 ① BDI2000 的配置文件。如果采用第二种移植方法,即先烧入 FLASH 的方法,配置项只 需很少几个,就可以进行 U-Boot的烧写与调试了。对 PPC 8xx系列的主板,可参考DULG 文档中 TQM8xx 的配置文件进行相应的修改。下面,笔者以美国 Embedded Planet 公司的 RPXlite DW 板为例,给出在嵌入式Linux交叉开发环境下的 BDI2000参考配置文件以作参 考: ; bdiGDB configuration file for RPXlite DW or LITE_DW ; -------------------------------------------- [INIT] ; init core register WSPR 149 0x2002000F ;DER : set debug enable register ; WSPR 149 0x2006000F ;DER : enable SYSIE for BDI flash program WSPR 638 0xFA200000 ;IMMR : internal memory at 0xFA200000 WM32 0xFA200004 0xFFFFFF89 ;SYPCR [TARGET] CPUCLOCK 40000000 ;the CPU clock rate after processing the init list BDIMODE AGENT ;the BDI working mode (LOADONLY | AGENT) BREAKMODE HARD ;SOFT or HARD, HARD uses PPC hardware breakpoints [HOST] IP 173.60.120.5 FILE uImage.litedw FORMAT BIN LOAD MANUAL ;load code MANUAL or AUTO after reset DEBUGPORT 2001 START 0x0100 [FLASH] CHIPTYPE AM29BX8 ;;Flash type (AM29F | AM29BX8 | AM29BX16 | I28BX8 | I28BX16) CHIPSIZE 0x400000 ;;The size of one flash chip in bytes BUSWIDTH 32 ;The width of the flash memory bus in bits (8 | 16 | 32) WORKSPACE 0xFA202000 ; RAM buffer for fast flash programming FILE u-boot.bin ;The file to program FORMAT BIN 0x00000000 ERASE 0x00000000 BLOCK ERASE 0x00008000 BLOCK ERASE 0x00010000 BLOCK Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)ERASE 0x00018000 BLOCK [REGS] DMM1 0xFA200000 FILE reg823.def ② U-Boot 移植参考板。这是进行 U-Boot 移植首先要明确的。可以根据目标板上 CPU、 FLASH、SDRAM的情况,以尽可能相一致为原则,先找出一个与所移植目标板为同一个或 同一系列处理的 U-Boot 支持板为移植参考板。如 RPXlite DW 板可选择 U-Boot 源码中 RPXlite 板作为 U-Boot 移植参考板。对 U-Boot 移植新手,建议依照循序渐进的原则,目标 板文件名暂时先用移植参考板的名称,在逐步熟悉 U-Boot 移植基础上,再考虑给目标板重 新命名。在实际移植过程中,可用 Linux 命令查找移植参考板的特定代码,如 grep –r RPXlite ./ 可确定出在 U-Boot中与 RPXlite板有关的代码,依此对照目标板实际进行屏蔽或 修改。同时应不局限于移植参考板中的代码,要广泛借鉴U-Boot 中已有的代码更好地实现 一些具体的功能。 ③ U-Boot烧写地址。 不同目标板, 对 U-Boot在 FLASH 中存放地址要求不尽相同。 事实上, 这是由处理中断复位向量来决定的,与主板硬件相关,对 MPC8xx 主板来讲,就是由硬 件配置字(HRCW)决定的。也就是说,U-Boot烧写具体位置是由硬件决定的,而不是程序设 计来选择的。程序中相应 U-Boot 起始地址必须与硬件所确定的硬件复位向量相吻合;如 RPXlite DW 板的中断复位向量设置为 0x00000100。因此, U-Boot 的 BIN 镜像文件必须烧 写到 FLASH 的起始位置。 事实上, 大多数的 PPC系列的处理中断复位向量是 0x00000100 和 0xfff00100。这也是一般所说的高位启动和低位启动的 BOOT LOADER 所在位置。可通 过修改 U-Boot 源码.h 头文件中 CFG_MONITOR_BASE 和 board//config.mk中的 TEXT_BASE 的设置来与硬件配置相对应。 ④ CPU寄存参数设置。根据处理系列、类型不同,寄存名称与作用有一定差别。必 须根据目标板的实际,进行合理配置。一个较为可行和有效的方法,就是借鉴参考移植板的 配置,再根据目标板实际,进行合理修改。这是一个较费功夫和考验耐力的过程,需要仔细 对照处理各寄存定义、参考设置、目标板实际作出选择并不断测试。MPC8xx处理较 为关键的寄存设置为 SIUMCR、PLPRCR、SCCR、BRx、ORx。 ⑤ 串口调试。能从串口输出信息,即使是乱码,也可以说 U-Boot移植取得了实质性突破。 依据笔者调试经历,串口是否有输出,除了与串口驱动相关外,还与 FLASH 相关的寄存 设置有关。因为 U-Boot 是从 FLASH 中被引导启动的,如果 FLASH 设置不正确,U-Boot 代码读取和执行就会出现一些问题。因此,还需要就FLASH 的相关寄存设置进行一些参 数调试。同时,要注意串口收发芯片相关引脚工作波形。依据笔者调试情况,如果串口无输 出或出现乱码,一种可能就是该芯片损坏或工作不正常。 ⑥ 与启动 FLASH 相关的寄存 BR0、OR0 的参数设置。应根据目标板 FLASH 的数据手 册与 BR0 和 OR0 的相关位含义进行合理设置。这不仅关系到 FLASH 能否正常工作,而且 与串口调试有直接的关联。 ⑦ 关于 CPLD 电路。目标板上是否有 CPLD 电路丝毫不会影响 U-Boot 的移植与嵌入式操 作系统的正常运行。事实上,CPLD 电路是一个集中将板上电路的一些逻辑关系可编程设置 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)的一种实现方法。其本身所起的作用就是实现一些目标板所需的脉冲信号和电路逻辑,其功 能完全可以用一些逻辑电路与 CPU口线来实现。 ⑧ SDRAM的驱动。串口能输出以后,U-Boot移植是否顺利基本取决于 SDRAM的驱动是 否正确。与串口调试相比,这部分工作更为核心,难度更大。 MPC8xx 目标板 SDRAM 驱 动涉及三部分。一是相关寄存的设置;二是 UPM表;三是 SDRAM上电初始化过程。任 何一部分有问题,都会影响 U- Boot、嵌入式操作系统甚至应用程序的稳定、可靠运行。所 以说,SDRAM 的驱动不仅关系到 U-Boot 本身能否正常运行,而且还与后续部分相关,是 相当关键的部分。 ⑨ 补充功能的添加。在获得一个能工作的 U-Boot后,就可以根据目标板和实际开发需要, 添加一些其它功能支持。如以太网、LCD、NVRAM 等。与串口和 SDRAM 调试相比,在 已有基础之上,这些功能添加还是较为容易的。大多只是在参考现有源码的基础上,进行一 些修改和配置。 另外,如果在自主设计的主板上移植 U-Boot,那么除了考虑上述软件因素以外,还需要排 查目标板硬件可能存在的问题。如原理设计、PCB 布线、元件好坏。在移植过程中,敏锐 判断出故障态是硬件还是软件问题,往往是关系到项目进度甚至移植成败的关键,相应难度 会增加许多。 下面以移植 u-boot 到 44B0开发板的步骤为例,移植中上仅需要修改和硬件相关的部分。在 代码结构上: 1) 在 board 目录下创建 ev44b0ii 目录,创建 ev44b0ii.c 以及 flash.c,memsetup.S,u-boot.lds 等。不需要从零开始,可选择一个相似的目录,直接复制过来,修改文件名以及内容。我在 移植 u-boot 过程中,选择的是 ep7312 目录。由于 u-boot 已经包含基于 s3c24b0 的开发板 目录,作为参考,也可以复制相应的目录。 2) 在 cpu 目录下创建 arm7tdmi 目录,主要包含 start.S, interrupts.c 以及 cpu.c,serial.c几个文 件。同样不需要从零开始建立文件,直接从arm720t 复制,然后修改相应内容。 3) 在 include/configs 目录下添加 ev44b0ii.h,在这里放上全局的宏定义等。 4) 找到 u-boot 根目录下 Makefile 修改加入 ev44b0ii_config : unconfig @./mkconfig $(@:_config=) arm arm7tdmi ev44b0ii 5) 运行 make ev44bii_config,如果没有错误就可以开始硬件相关代码移植的工作 u-boot 的体系结构 1) 总体结构 u-boot 是一个层次式结构。从上图也可以看出,做移植工作的软件人员应当提供串口驱动 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)(UART Driver),以太网驱动(Ethernet Driver),Flash 驱动(Flash 驱动),USB 驱动(USB Driver)。目前,通过 USB 口下载程序显得不是十分必要,所以暂时没有移植 USB 驱动。 驱动层之上是 u-boot 的应用,command 通过串口提供人机界面。我们可以使用一些命令做 一些常用的工作,比如内存查看命令 md。 Kermit 应用主要用来支持使用串口通过超级终端下载应用程序。TFTP 则是通过网络方式 来下载应用程序,例如uclinux 操作系统。 2) 内存分布 在 flash rom 中内存分布图 ev44b0ii 的 flash 大小 2M(8bits),现在将 0-40000 共 256k 作为 u-boot 的存储空间。由于 u-boot 中有一些环境变量,例如 ip 地址,引导文件名等,可在命 令行通过 setenv 配置好,通过 saveenv 保存在 40000-50000(共 64k)这段空间里。如果存在 保存好的环境变量,u-boot 引导将直接使用这些环境变量。正如从代码分析中可以看到, 我们会把 flash 引导代码搬移到 DRAM 中运行。下图给出 u-boot 的代码在 DRAM 中的位 置。引导代码 u-boot 将从 0x0000 0000 处搬移到 0x0C700000 处。特别注意的由于 ev44b0ii uclinux 中断向量程序地址在 0x0c00 0000 处,所以不能将程序下载到0x0c00 0000 出,通 常下载到 0x0c08 0000 处。 2) start.S 代码结构 1) 定义入口 一个可执行的 Image 必须有一个入口点并且只能有一个唯一的全局入口,通常这个入口放 在 Rom(flash)的 0x0 地址。例如 start.S 中的 .globl _start _start: 值得注意的是你必须告诉编译知道这个入口, 这个工作主要是修改连接脚本文件 (lds)。 2) 设置异常向量(Exception Vector) 异常向量表,也可称为中断向量表,必须是从 0 地址开始,连续的存放。如下面的就包括 了复位(reset),未定义处理(undef),软件中断(SWI),预去指令错误(Pabort),数据错误 (Dabort), 保留,以及 IRQ,FIQ 等。注意这里的值必须与 uclinux 的 vector_base 一致。这就是说如果 uclinux 中 vector_base(include/armnommu/proc-armv/system.h) 定 义 为 0x0c00 0000, 则 HandleUndef 应该在 0x0c00 0004。 b reset //for debug ldr pc,=HandleUndef ldr pc,=HandleSWI ldr pc,=HandlePabort ldr pc,=HandleDabort b . ldr pc,=HandleIRQ ldr pc,=HandleFIQ ldr pc,=HandleEINT0 /*mGA H/W interrupt vector table*/ ldr pc,=HandleEINT1 ldr pc,=HandleEINT2 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)ldr pc,=HandleEINT3 ldr pc,=HandleEINT4567 ldr pc,=HandleTICK /*mGA*/ b . b . ldr pc,=HandleZDMA0 /*mGB*/ ldr pc,=HandleZDMA1 ldr pc,=HandleBDMA0 ldr pc,=HandleBDMA1 ldr pc,=HandleWDT ldr pc,=HandleUERR01 /*mGB*/ b . b . ldr pc,=HandleTIMER0 /*mGC*/ ldr pc,=HandleTIMER1 ldr pc,=HandleTIMER2 ldr pc,=HandleTIMER3 ldr pc,=HandleTIMER4 ldr pc,=HandleTIMER5 /*mGC*/ b . b . ldr pc,=HandleURXD0 /*mGD*/ ldr pc,=HandleURXD1 ldr pc,=HandleIIC ldr pc,=HandleSIO ldr pc,=HandleUTXD0 ldr pc,=HandleUTXD1 /*mGD*/ b . b . ldr pc,=HandleRTC /*mGKA*/ b . b . b . b . b . /*mGKA*/ b . b . ldr pc,=HandleADC /*mGKB*/ b . b . b . b . b . /*mGKB*/ b . Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)b . ldr pc,=EnterPWDN 作为对照:请看以上标记的值: .equ HandleReset, 0xc000000 .equ HandleUndef,0xc000004 .equ HandleSWI, 0xc000008 .equ HandlePabort, 0xc00000c .equ HandleDabort, 0xc000010 .equ HandleReserved, 0xc000014 .equ HandleIRQ, 0xc000018 .equ HandleFIQ, 0xc00001c /*the value is different with an address you think it may be. *IntVectorTable */ .equ HandleADC, 0xc000020 .equ HandleRTC, 0xc000024 .equ HandleUTXD1, 0xc000028 .equ HandleUTXD0, 0xc00002c .equ HandleSIO, 0xc000030 .equ HandleIIC, 0xc000034 .equ HandleURXD1, 0xc000038 .equ HandleURXD0, 0xc00003c .equ HandleTIMER5, 0xc000040 .equ HandleTIMER4, 0xc000044 .equ HandleTIMER3, 0xc000048 .equ HandleTIMER2, 0xc00004c .equ HandleTIMER1, 0xc000050 .equ HandleTIMER0, 0xc000054 .equ HandleUERR01, 0xc000058 .equ HandleWDT, 0xc00005c .equ HandleBDMA1, 0xc000060 .equ HandleBDMA0, 0xc000064 .equ HandleZDMA1, 0xc000068 .equ HandleZDMA0, 0xc00006c .equ HandleTICK, 0xc000070 .equ HandleEINT4567, 0xc000074 .equ HandleEINT3, 0xc000078 .equ HandleEINT2, 0xc00007c .equ HandleEINT1, 0xc000080 .equ HandleEINT0, 0xc000084 3) 初始化 CPU 相关的 pll,clock,中断控制寄存 依次为关闭 watch dog timer,关闭中断,设置 LockTime,PLL(phase lock loop),以及时钟。 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)这些值(除了LOCKTIME)都可从 Samsung 44b0 的手册中查到。 ldr r0,WTCON //watch dog disable ldr r1,=0x0 str r1,[r0] ldr r0,INTMSK ldr r1,MASKALL //all interrupt disable str r1,[r0] /***************************************************** * Set clock control registers * *****************************************************/ ldr r0,LOCKTIME ldr r1,=800 // count = t_lock * Fin (t_lock=200us, Fin=4MHz) = 800 str r1,[r0] ldr r0,PLLCON /*temporary setting of PLL*/ ldr r1,PLLCON_DAT /*Fin=10MHz,Fout=40MHz or 60MHz*/ str r1,[r0] ldr r0,CLKCON ldr r1,=0x7ff8 //All unit block CLK enable str r1,[r0] 4) 初始化内存控制 内存控制,主要通过设置 13 个从 1c80000 开始的寄存来设置,包括总线宽度, 8 个内存 bank,bank 大小,sclk,以及两个 bank mode。 /***************************************************** * Set memory control registers * *****************************************************/ memsetup: adr r0,SMRDATA ldmia r0,{r1-r13} ldr r0,=0x01c80000 //BWSCON Address stmia r0,{r1-r13} 5) 将 rom 中的程序复制到 RAM 中 首先利用 PC 取得 bootloader 在 flash 的起始地址,再通过标号之差计算出这个程序代 码的大小。这些标号,编译会在连接(link)的时候生成正确的分布的值。取得正 确信息后,通过寄存(r3 到 r10)做为复制的中间媒介,将代码复制到 RAM 中。 relocate: /* * relocate armboot to RAM */ Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)adr r0, _start /* r0 <- current position of code */ ldr r2, _armboot_start ldr r3, _armboot_end sub r2, r3, r2 /* r2 <- size of armboot */ ldr r1, _TEXT_BASE /* r1 <- destination address */ add r2, r0, r2 /* r2 baudrate) + 0.5) -1 )计算得出。这可以在手 册中查到。其他的函数包括发送,接收。这个时候没有中断,是通过循环等待来判断是否动 作完成。 例如,接收函数: while(!(rUTRSTAT0 & 0x1)); //Receive data read return RdURXH0(); 2. 时钟部分 实现了延时函数 udelay。 这里的 get_timer 由于没有使用中断,是使用全局变量来累加的。 3. flash 部分 flash 作为内存的一部分,读肯定没有问题,关键是 flash 的写部分。 Flash 的写必须先擦除,然后再写。 unsigned long flash_init (void) { int i; u16 manId,devId; //first we init it as unknown,even if you forget assign it below,it's not a problem for (i=0; i < CFG_MAX_FLASH_BANKS; ++i){ flash_info[i].flash_id = FLASH_UNKNOWN; flash_info[i].sector_count=CFG_MAX_FLASH_SECT; } /*check manId,devId*/ _RESET(); _WR(0x555,0xaa); _WR(0x2aa,0x55); _WR(0x555,0x90); manId=_RD(0x0); _WR(0x555,0xaa); _WR(0x2aa,0x55); _WR(0x555,0x90); Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)devId=_RD(0x1); _RESET(); printf("flashn"); printf("Manufacture ID=%4x(0x0004), Device ID(0x22c4)=%4xn",manId,devId); if(manId!=0x0004 && devId!=0x22c4){ printf("flash check faliluren"); return 0; }else{ for (i=0; i = CFG_FLASH_BASE //onitor protection ON by default flash_protect(FLAG_PROTECT_SET, CFG_MONITOR_BASE, CFG_MONITOR_BASE+monitor_flash_len-1, &flash_info[0]); #endif */ flash_info[0].size =PHYS_FLASH_SIZE; return (PHYS_FLASH_SIZE); } flash_init 完成初始化部分,这里的主要目的是检验flash 的型号是否正确。 int flash_erase (flash_info_t *info, int s_first, int s_last) { volatile unsigned char *addr = (volatile unsigned char *)(info->start[0]); int flag, prot, sect, l_sect; //ulong start, now, last; u32 targetAddr; u32 targetSize; /*zyy note:It is required and can't be omitted*/ rNCACHBE0=( (0x2000000>>12)<>12); //flash area(Bank0) must be non-cachable area. rSYSCFG=rSYSCFG & (~0x8); //write buffer has to be off for proper timing. if ((s_first s_last)) { if (info->flash_id == FLASH_UNKNOWN) { printf ("- missingn"); } else { printf ("- no sectors to erasen"); Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)} return 1; } if ((info->flash_id == FLASH_UNKNOWN) || (info->flash_id > FLASH_AMD_COMP)) { printf ("Can't erase unknown flash type - abortedn"); return 1; } prot = 0; for (sect=s_first; sectprotect[sect]) { prot++; } } if (prot) { printf ("- Warning: %d protected sectors will not be erased!n", prot); } else { printf ("n"); } l_sect = -1; /* Disable interrupts which might cause a timeout here */ flag = disable_interrupts(); /* Start erase on unprotected sectors */ for (sect = s_first; sectprotect[sect] == 0) {/* not protected */ targetAddr=0x10000*sect; if(targetAddr<0x1F0000) targetSize=0x10000; else if(targetAddr<0x1F8000) targetSize=0x8000; else if(targetAddr<0x1FC000) targetSize=0x2000; else targetSize=0x4000; F29LV160_EraseSector(targetAddr); l_sect = sect; if(!BlankCheck(targetAddr, targetSize)) printf("BlankCheck Errorn"); } } /* re-enable interrupts if necessary */ if (flag) enable_interrupts(); Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)/* wait at least 80us - let's wait 1 ms */ udelay (1000); /* *We wait for the last triggered sector */ if (l_sect > 16) & 0xffff; low=swap_16(low); high=swap_16(high); tempPt=(volatile u16 *)dest; _WR(0x555,0xaa); _WR(0x2aa,0x55); _WR(0x555,0xa0); *tempPt=high; Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)_WAIT(); _WR(0x555,0xaa); _WR(0x2aa,0x55); _WR(0x555,0xa0); *(tempPt+1)=low; _WAIT(); return 0; } wirte_word 则想 flash 里面写入 unsigned long 类型的 data, 因为flash 一次只能写入16bits, 所以这里分两次写入。 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com)u-boot源码分析——启动第一阶段 分析代码当然要从上电后执行的第一条指令开始看起咯, 那第一条指令在哪呢? 还是以 smdk2410 为 例,我们看它的链接脚本: 文件 board/smsk2410/u-boot.lds: …… ENTRY(_start) //指明入口地址(见汇编指令) SECTIONS { . = 0x00000000; //入口地址为 0x00000000,硬件决定的 . = ALIGN(4); //按 4 字节对齐,即按字对齐(32 位) .text: //文本段,即代码段 { cpu/arm920t/start.o (.text) //确定启动后执行的第一个文件 *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } …… } 由这个文件可知第一个执行的文件是 cpu/arm920t/start.S,那第一条指令(_start)很可能就在这个文件中 了。我们看这个文件: cpu/arm920t/start.S: .globl _start /*这 8 行为中断向量表,参考arm书籍可确定这段代码的编写方法*/ _start: b reset //复位向量,CPU上电后执行的第一条语句 ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq //中断向量 ldr pc, _fiq //快速中断向量 /*.word为伪指令,变量替换*/ _undefined_instruction: .word undefined_instruction _software_interrupt: .word software_interrupt Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) _prefetch_abort: .word prefetch_abort _data_abort: .word data_abort _not_used: .word not_used _irq: .word irq _fiq: .word fiq S3C2410的 CPU规定开机后的 PC寄存地址为 0,即从 0 地址开始执行指令,因此我们必须把我们的 复位代码放在 0 地址处才能正常开机。 ARM核也规定启动地址处的 32个字节必须存放异常向量跳转表,里面保存有中断,异常等的处理函数 地址。当系统产生中断时,必定会跳到这里来开始处理中断。具体可参考 ARM方面的书籍。 由 u-boot.lds可知入口地址为_start, 即开机后从_start处开始执行指令。所以第一条指令就是: b reset //跳转到 reset处进行复位处理 cpu/arm920t/start.S: // CPU上电后跳转到此处,CPU进入 SVC32模式,这样可以拥有特权操作,参考 ARM书籍 /* the actual reset code */ reset: mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0 /* turn off the watchdog */ //CPU上操作 watchdog相关的寄存地址,可参考CPU的 datasheet,这里用到的地址都是实地址, //因为还没为 MMU等部件进行初始化,也没切换操作模式呢。 #if defined(CONFIG_S3C2400) # define pWTCON 0x15300000 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ # define CLKDIVN 0x14800014 /* clock divisor register */ #elif defined(CONFIG_S3C2410) # define pWTCON 0x53000000 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */ # define INTSUBMSK 0x4A00001C # define CLKDIVN 0x4C000014 /* clock divisor register */ #endif #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) ldr r0, =pWTCON mov r1, #0x0 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) str r1, [r0] //关闭 watchdog,具体寄存含义可参考 CPU手册 /* * mask all IRQs by setting all bits in the INTMSK - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0] //关闭所有的中断 # if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0] //关闭所有的中断 # endif /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ //设置 HCLK 为 FCLK/2, PCLK 为 FCLK/4, FCLK 为 CPU产生 clock,HCLK 为 AHB总线上的设备产生 //clock, PCLK 为 APB总线上的设备产生 clock,具体参考 s3c2410的 datasheet ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */ /* * we do sys-critical inits only at reboot, * not when booting from ram! */ //做系统相关的重要初始化,这些初始化代码只在系统重起的时候执行, // CONFIG_SKIP_LOWLEVEL_INIT 可以看 README. #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit //可以先看这段代码在转回来接着看后面的复位过程。 #endif //内存配置完后,可以进行重定位操作了 #ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: /* 重定位 u-boot到 RAM中*/ adr r0, _start /* r0 = flash中的代码的起始地址*/ ldr r1, _TEXT_BASE /* r1= 代码在 RAM中的起始地址 */ cmp r0, r1 /* 看是否 u-boot就在 RAM中运行*/ beq stack_setup /*如果在 RAM中则无需重定位*/ /*开始重定位,即把u-boot从 flash中搬到 RAM 中去运行*/ ldr r2, _armboot_start /*r2 = flash中代码的起始地址,看_armboot_start的定义*/ ldr r3, _bss_start /*r3 = bss段的起始地址,_bss_start可在 u-boot.lds中查看。*/ Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) sub r2, r3, r2 /* r2 = 需要重定位的字节数*/ add r2, r0, r2 /* r2 = flash中 RO,RW 内容的结束地址 */ //开始把代码从 flash中搬运到 RAM中 copy_loop: ldmia r0!, {r3-r10} /*获取从 r0开始的代码,存入 r3—r10*/ stmia r1!, {r3-r10} /*把 r3—r10 的内容存入r1 所在位置,即 RAM中*/ cmp r0, r2 /*copy所有代码 */ ble copy_loop #endif /* CONFIG_SKIP_RELOCATE_UBOOT */ /*设置栈地址*/ stack_setup: ldr r0, _TEXT_BASE /*upper 128 KiB: relocated uboot*/ sub r0, r0, #CFG_MALLOC_LEN /*malloc分配内存的区域,大小以板子的配置而定,smdk2410的在 include/configs/smdk2410.h中定义*/ sub r0, r0, #CFG_GBL_DATA_SIZE /* 存放 bdinfo的区域,定义同上*/ #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //保留中断所需的区域 #endif sub sp, r0, #12 /* 保留 12 字节给 abort-stack, 并设好堆栈*/ //bss段内容清 0 clear_bss: ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 ble clbss_l #if 0 /* try doing this stuff after the relocation */ ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] /* mask all IRQs by setting all bits in the INTMR - default*/ mov r1, #0xffffffff ldr r0, =INTMR str r1, [r0] /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) str r1, [r0] /* END stuff after relocation */ #endif ldr pc, _start_armboot //跳转到_start_armboot处执行。 _start_armboot: .word start_armboot 总结 reset这块代码,主要完成了一下几个部分: 1. 重要部分的初始化工作,如禁止中断,关闭 watchdog,初始化 memory控制等 2. 重定位boot loader 到 ram 3. 设置好堆栈 4. 跳转到第 2阶段执行 完成这些后,此时内存的分布情况如下: 这个图代表的是 u-boot自己在内存的情况, 和上面的图不一样, 这里的_TEXT_BASE 就是 0x33F8’ 0000 接着看 CPU_init_critical cpu/arm920t/start.S: /* ************************************************************************** * CPU_init_critical registers * 设置 cache,TLB,MMU等寄存 * 设置内存操作的时序 * ************************************************************************* */ cpu_init_crit: /* * flush v4 I/D caches */ /*使 cache和 TLB无效,可以参考 data sheet*/ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* 使指令 cache和数据 cache无效 */ mcr p15, 0, r0, c8, c7, 0 /* 使 TLB无效 */ /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 /*读出 c1 控制寄存的值*/ bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM),小端对齐,关闭数据 cache,关 //闭错误检测,关闭MMU orr r0, r0, #0x00000002 @ set bit 2 (A) Align, 使能错误检测 orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache, 使能指令 cache mcr p15, 0, r0, c1, c0, 0 /*设置 c1 控制寄存*/ /*可以参考 data sheet*/ Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) /* * before relocating, we have to setup RAM timing * because memory timing is board-dependend, you will * find a lowlevel_init.S in your board directory. */ //在把 u-boot 重定位到 RAM 前,我们必须先把 RAM 的时序设置好,内存时序是依板子而定的, 所以这里的初始化应该由我们提供,一般在我们的板子所在目录下有个 lowlevel_init.S来负责这件事情。 特定板子的目录还记得吗, 呵呵回到上面在看看。 mov ip, lr bl lowlevel_init mov lr, ip mov pc, lr //类似于函数返回 cpu_init_crit主要是使能了 instruction cache,关闭了 MMU等部件,但是好像在 u-boot后面的代码里没有 看见打开 MMU 的操作,我猜测可能是留到了 OS 启动的时候再打开了吧,data cache 在第二阶段的 board_init下被使能。 接着看 lowlevel_init。以 smdk2410位例 board/smdk2410/lowlevel_init.S _TEXT_BASE: .word TEXT_BASE .globl lowlevel_init lowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ // 内存控制的配置, 配置完后就可以使用内存了 ldr r0, =SMRDATA //在下面定义 ldr r1, _TEXT_BASE sub r0, r0, r1 // ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4 0: ldr r3, [r0], #4 str r3, [r1], #4 //设置内存配置寄存,可以对着datasheet来看这里的设置,包括时序位宽等 等, 使用一个循环来配置所有的寄存 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr .ltorg /* the literal pools origin */ //这些就是要被设置进内存配置寄存的值, SMRDATA: Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON< <20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0x32 .word 0x30 .word 0x30 这部分代码主要是设置 memory的时序,位宽等参数 Create PDF files without this message by purchasing novaPDF printer (http://www.novapdf.com) U-BOOT源码分析及移植 本文从以下几个方面粗浅地分析 u-boot并移植到 FS2410 板上: 1、u-boot工程的总体结构 2、u-boot的流程、主要的数据结构、内存分配。 3、u-boot的重要细节,主要分析流程中各函数的功能。 4、基于 FS2410板子的u-boot移植。实现了 NOR Flash和 NAND Flash启动,网络功能。 这些认识源于自己移植 u-boot过程中查找的资料和对源码的简单阅读。下面主要以 smdk2410为分析对 象。 一、u-boot工程的总体结构: 1、源代码组织 对于 ARM而言,主要的目录如下: board 平台依赖 存放电路板相关的目录文件,每一套板子对 应一个目 录。如 smdk2410(arm920t) cpu 平台依赖 存放 CPU 相关的目录文件,每一款 CPU 对应一个目 录,例如:arm920t、 xscale、i386 等目录 lib_arm 平台依赖 存放对 ARM 体系结构通用的文件,主要用于实现 ARM平台通用的函数,如软件浮点。 common 通用 通用的多功能函数实现,如环境,命令,控制台相关的函数实 现。 include 通用 头文件和开发板配置文件,所有开发板的配置文件都在 configs目录下 lib_generic 通用 通用库函数的实现 net 通用 存放网络协议的程序 drivers 通用 通用的设备驱动程序,主要有以太网接口的驱动,nand 驱 动。 ....... 2.makefile简要分析 所有这些目录的编译连接都是由顶层目录的 makefile 来确定的。 在执行 make之前,先
### 回答1: 在 Linux 中,ELF 格式文件是可执行文件、共享库和内核模块的标准格式。ELF 格式文件由许多不同的段组成,其中之一就是 text 段。text 段通常包含可执行代码和常量。 要实现 Linux ELF 格式文件 text 段感染,您需要执行以下步骤: 1. 使用诸如 objdump 或 readelf 等工具来分析目标文件ELF 格式,以确定 text 段的起始地址和长度。 2. 使用 mmap 函数将目标文件映射到进程的地址空间中。 3. 使用 mprotect 函数将 text 段的访问权限设置为可写。 4. 使用 memcpy 函数或直接内存操作来复制感染代码到 text 段中。 5. 使用 mprotect 函数将 text 段的访问权限恢复为只读。 6. 使用 munmap 函数解除对目标文件的映射。 7. 使用 execve 函数加载并执行感染后的目标文件。 请注意,ELF 格式文件 text 段感染可能会对系统造成严重危害,并且在大多数情况下都是不合法的。 ### 回答2: 实现Linux ELF格式文件text段感染需要先了解ELF文件的结构和特点。ELF文件是一种可执行文件格式,包含了多个段(section)和段头表(section header table)。其中,text段是用来存放可执行代码的区域。 要实现text段感染,一种常见的方法是通过在被感染的ELF文件中添加恶意代码,使之在执行时将恶意代码复制到其他可执行文件或进程中。 具体实现过程如下: 1. 打开被感染的ELF文件,读取其ELF头和段头表; 2. 找到text段的起始地址和大小; 3. 分配一段内存,用于存放恶意代码; 4. 将恶意代码写入分配的内存中; 5. 修改text段的大小,使其能够容纳恶意代码; 6. 将恶意代码复制到text段中; 7. 修改ELF头和段头表中相应的字段,更新text段的起始地址和大小; 8. 将修改后的ELF文件保存。 需要注意的是,实现text段感染可能会破坏原始ELF文件的结构和完整性,使其无法正常执行。此外,恶意代码的编写需要遵循法律规定,切勿用于非法用途。 总结来说,实现Linux ELF格式文件text段感染的关键步骤包括找到text段的位置和大小、分配内存存放恶意代码、修改text段的大小和内容,以及更新段头表和ELF头中的相关字段。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值