2006年08月22日

预处理器(Preprocessor

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

我在这想看到几件事情:

?; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

?; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

?; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

?; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。

#define MIN(A,B) ((A <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:

?; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

?; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。

?; 懂得在宏中小心地把参数用括号括起来

?; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b);

3. 预处理器标识#error的目的是什么?

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

死循环(Infinite loops

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

while(1)

{

?}

 

一些程序员更喜欢如下方案:

for(;

{

?}

 

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。

第三个方案是用 goto

Loop:

goto Loop;

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations

5. 用变量a给出下面的定义

a) 一个整型数(An integer

b)一个指向整型数的指针( A pointer to an integer

c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an integer

d)一个有10个整型数的数组( An array of 10 integers

e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers

f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers

g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer

h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer

答案是:

a) int a; // An integer

b) int *a; // A pointer to an integer

c) int **a; // A pointer to a pointer to an integer

d) int a[10]; // An array of 10 integers

e) int *a[10]; // An array of 10 pointers to integers

f) int (*a)[10]; // A pointer to an array of 10 integers

g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

Static

6. 关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

?; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

?; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

?; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

Const

7关键字const有什么含意?

我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正确回答这个问题,我将问他一个附加的问题:

下面的声明都是什么意思?

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

/******/

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

?; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

?; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

?; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

?; 并行设备的硬件寄存器(如:状态寄存器)

?; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

?; 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。

?; 一个参数既可以是const还可以是volatile吗?解释为什么。

?; 一个指针可以是volatile 吗?解释为什么。

?; 下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

下面是答案:

?; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

?; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

?; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此ab可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

位操作(Bit manipulation

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置abit 3,第二个清除a bit 3。在以上两个操作中,要保持其它位不变。

对这个问题有三种基本的反应

?; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。

?; bit fieldsBit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。

?; #defines bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0×1 << 3)

static int a;

void set_bit3(void) {

a |= BIT3;

}

void clear_bit3(void) {

a &= ~BIT3;

}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=&=~操作。

访问固定的内存位置(Accessing fixed memory locations

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;

ptr = (int *)0x67a9;

*ptr = 0xaa55;

A more obscure approach is:

一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)

{

double area = PI * radius * radius;

printf("\nArea = %f", area);

return area;

}

这个函数有太多的错误了,以至让人不知从何说起了:

?; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。

?; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。

?; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

?; 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

*****

代码例子(Code examples

12 . 下面的代码输出是什么,为什么?

void foo(void)

{

unsigned int a = 6;

int b = -20;

(a+b > 6) ? puts("> 6") : puts("<= 6");

}

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 >6。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

13. 评价下面的代码片断:

unsigned int zero = 0;

unsigned int compzero = 0xFFFF;

/*1’s complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。

到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧…

动态内存分配(Dynamic memory allocation

14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?

这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:

下面的代码片段的输出是什么,为什么?

char *ptr;

if ((ptr = (char *)malloc(0)) ==

NULL)

else

puts("Got a null pointer");

puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

Typedef

:

15 Typedef C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *

typedef struct s * tPS;

以上两种情况的意图都是要定义dPS tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;

tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.

上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;

c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12

如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。

好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。

Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com

References

?; Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.

?; Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

 

 

本文以RedHat9.0i386平台为例,剖析了从用户打开电源直到屏幕出现命令行提示符的整个Linux启动过程。并且介绍了启动中涉及到的各种文件。

  阅读Linux源代码,无疑是深入学习Linux的最好方法。在本文对Linux启动过程的介绍中,我们也尝试从源代码的视角来更深入的剖析Linux 的启动过程,所以其中也简单涉及到部分相关的Linux源代码,Linux启动这部分的源码主要使用的是C语言,也涉及到了少量的汇编。而启动过程中也执行了大量的shell(主要是bash shell)所写脚本。为了方便读者阅读,笔者将整个Linux启动过程分成以下几个部分逐一介绍,大家可以参考下图:

当用户打开PC 的电源,BIOS开机自检,按BIOS中设置的启动设备(通常是硬盘)启动,接着启动设备上安装的引导程序lilogrub开始引导Linux Linux首先进行内核的引导,接下来执行init程序,init程序调用了rc.sysinitrc等程序,rc.sysinitrc当完成系统初始化和运行服务的任务后,返回initinit启动了mingetty后,打开了终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。

 

下面就将逐一介绍其中几个关键的部分:

第一部分:内核的引导(核内引导)

  Red Hat9.0可以使用lilogrub等引导程序开始引导Linux系统,当引导程序成功完成引导任务后,Linux从它们手中接管了CPU的控制权,然后CPU就开始执行Linux的核心映象代码,开始了Linux启动过程。这里使用了几个汇编程序来引导Linux,这一步泛及到Linux源代码树中的“arch/i386/boot”下的这几个文件:bootsect.Ssetup.Svideo.S等。

  其中 bootsect.S是生成引导扇区的汇编源码,它完成加载动作后直接跳转到setup.S的程序入口。setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到特别内存中,以便以后这些参数被保护模式下的代码来读取。此外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到 0×100000

  那么0×100000这个内存地址中存放的是什么代码?而这些代码又是从何而来的呢?

  0×100000这个内存地址存放的是解压后的内核,因为Red Hat提供的内核包含了众多驱动和功能而显得比较大,所以在内核编译中使用了“makebzImage”方式,从而生成压缩过的内核,在RedHat中内核常常被命名为vmlinuz,在Linux的最初引导过程中,是通过"arch/i386/boot/compressed/"中的head.S利用 misc.c中定义的decompress_kernel()函数,将内核vmlinuz解压到0×100000的。

  当CPU跳到 0×100000时,将执行"arch/i386/kernel/head.S"中的startup_32,它也是vmlinux的入口,然后就跳转到 start_kernel()中去了。start_kernel()"init/main.c"中的定义的函数,start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。start_kernel()函数中,做了大量的工作来建立基本的Linux核心环境。如果顺利执行完 start_kernel(),则基本的Linux核心环境已经建立起来了。

  在start_kernel()的最后,通过调用init ()函数,系统创建第一个核心线程,启动了init过程。而核心线程init()主要是来进行一些外设初始化的工作的,包括调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。并完成文件系统初始化和root文件系统的安装。

  当 do_basic_setup()函数返回init()init()又打开了/dev/console设备,重定向三个标准的输入输出文件stdin stdoutstderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。到此init()函数结束,内核的引导部分也到此结束了。

 

第二部分:运行init

 

  init的进程号是1,从这一点就能看出,init进程是系统所有进程的起点,Linux在完成核内引导以后,就开始运行init程序,。init程序需要读取配置文件/etc/inittabinittab是一个不可执行的文本文件,它有若干行指令所组成。在Redhat系统中,inittab的内容如下所示(以“###"开始的中注释为笔者增加的)

  #

  # inittab This file describes how the INIT process should set up

  # the system in a certain run-level.

  #

  # Author: Miquel van Smoorenburg, <miquels@drinkel.nl.mugnet.org>

  # Modified for RHS Linux by Marc Ewing and Donnie Barnes

  #

 

  # Default runlevel. The runlevels used by RHS are:

  # 0 – halt (Do NOT set initdefault to this)

  # 1 – Single user mode

  # 2 – Multiuser, without NFS (The same as 3, if you do not havenetworking)

  # 3 – Full multiuser mode

  # 4 – unused

  # 5 – X11

  # 6 – reboot (Do NOT set initdefault to this)

  #

  ###表示当前缺省运行级别为5(initdefault)

  id:5:initdefault:

 

  ###启动时自动执行/etc/rc.d/rc.sysinit脚本(sysinit)

  # System initialization.

  si::sysinit:/etc/rc.d/rc.sysinit

 

  l0:0:wait:/etc/rc.d/rc 0

  l1:1:wait:/etc/rc.d/rc 1

  l2:2:wait:/etc/rc.d/rc 2

  l3:3:wait:/etc/rc.d/rc 3

  l4:4:wait:/etc/rc.d/rc 4

  ###当运行级别为5时,以5为参数运行/etc/rc.d/rc脚本,init将等待其返回(wait)

  l5:5:wait:/etc/rc.d/rc 5

  l6:6:wait:/etc/rc.d/rc 6

 

  ###在启动过程中允许按CTRL-ALT-DELETE重启系统

  # Trap CTRL-ALT-DELETE

  ca::ctrlaltdel:/sbin/shutdown -t3 -r now

 

  # When our UPS tells us power has failed, assume we have a few minutes

  # of power left. Schedule a shutdown for 2 minutes from now.

  # This does, of course, assume you have powerd installed and your

  # UPS connected and working correctly.

  pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"

 

  # If power was restored before the shutdown kicked in, cancel it.

  pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"

 

  ###2345级别上以ttyX为参数执行/sbin/mingetty程序,打开ttyX终端用于用户登录,

  ###如果进程退出则再次运行mingetty程序(respawn)

  # Run gettys in standard runlevels

  1:2345:respawn:/sbin/mingetty tty1

  2:2345:respawn:/sbin/mingetty tty2

  3:2345:respawn:/sbin/mingetty tty3

  4:2345:respawn:/sbin/mingetty tty4

  5:2345:respawn:/sbin/mingetty tty5

  6:2345:respawn:/sbin/mingetty tty6

 

  ###5级别上运行xdm程序,提供xdm图形方式登录界面,并在退出时重新执行(respawn)

  # Run xdm in runlevel 5

  x:5:respawn:/etc/X11/prefdm -nodaemon

 

 以上面的inittab文件为例,来说明一下inittab的格式。其中以#开始的行是注释行,除了注释行之外,每一行都有以下格式:

 

  id:runlevel:action:process

 

  对上面各项的详细解释如下:

 

  1. id

 

  id是指入口标识符,它是一个字符串,对于gettymingetty等其他login程序项,要求idtty的编号相同,否则getty程序将不能正常工作。

 

  2. runlevel

 

  runlevelinit所处于的运行级别的标识,一般使用06以及Ss016运行级别被系统保留:其中0作为shutdown动作,1作为重启至单用户模式,6为重启;Ss意义相同,表示单用户模式,且无需inittab文件,因此也不在inittab中出现,实际上,进入单用户模式时,init直接在控制台(/dev/console)上运行/sbin/sulogin。在一般的系统实现中,都使用了2345几个级别,在 Redhat系统中,2表示无NFS支持的多用户模式,3表示完全多用户模式(也是最常用的级别),4保留给用户自定义,5表示XDM图形登录方式。7 9级别也是可以使用的,传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值,以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功才会执行。

 

  3. action

 

  action是描述其后的process的运行方式的。action可取的值包括:initdefaultsysinitbootbootwait等:

 

  initdefault是一个特殊的action值,用于标识缺省的启动级别;当init由核心激活以后,它将读取inittab中的 initdefault项,取得其中的runlevel,并作为当前的运行级别。如果没有inittab文件,或者其中没有initdefault项, init将在控制台上请求输入runlevel

 

  sysinitbootbootwaitaction将在系统启动时无条件运行,而忽略其中的runlevel

 

  其余的action(不含initdefault)都与某个runlevel相关。各个action的定义在inittabman手册中有详细的描述。

 

  4. process

 

  process为具体的执行程序。程序后面可以带参数。

 

第三部分:系统初始化

 

  在init的配置文件中有这么一行:

 

  si::sysinit:/etc/rc.d/rc.sysinit

 

  它调用执行了/etc/rc.d/rc.sysinit,而rc.sysinit是一个bash shell的脚本,它主要是完成一些系统初始化的工作,rc.sysinit是每一个运行级别都要首先运行的重要脚本。它主要完成的工作有:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。

 

  rc.sysinit约有850多行,但是每个单一的功能还是比较简单,而且带有注释,建议有兴趣的用户可以自行阅读自己机器上的该文件,以了解系统初始化所详细情况。由于此文件较长,所以不在本文中列出来,也不做具体的介绍。

 

  当rc.sysinit程序执行完毕后,将返回init继续下一步。

 

第四部分:启动对应运行级别的守护进程

 

  在rc.sysinit执行后,将返回init继续其它的动作,通常接下来会执行到/etc/rc.d/rc程序。以运行级别3为例,init将执行配置文件inittab中的以下这行:

 

  l5:5:wait:/etc/rc.d/rc 5

 

  这一行表示以5为参数运行/etc/rc.d/rc/etc/rc.d/rc是一个Shell脚本,它接受5作为参数,去执行 /etc/rc.d/rc5.d/目录下的所有的rc启动脚本,/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些链接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。而这些rc启动脚本有着类似的用法,它们一般能接受 startstoprestartstatus等参数。

  /etc/rc.d/rc5.d/中的rc启动脚本通常是KS开头的链接文件,对于以以S开头的启动脚本,将以start参数来运行。而如果发现存在相应的脚本也存在K打头的链接,而且已经处于运行态了( /var/lock/subsys/下的文件作为标志),则将首先以stop为参数停止这些已经启动了的守护进程,然后再重新运行。这样做是为了保证是当 init改变运行级别时,所有相关的守护进程都将重启。

  至于在每个运行级中将运行哪些守护进程,用户可以通过chkconfigsetup中的"System Services"来自行设定。常见的守护进程有:

  amd:自动安装NFS守护进程

  apmd:高级电源管理守护进程

  arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库

  autofs:自动安装管理进程automount,与NFS相关,依赖于NIS

  crondLinux下的计划任务的守护进程

  namedDNS服务器

  netfs:安装NFSSambaNetWare网络文件系统

  network:激活已配置网络接口的脚本程序

  nfs:打开NFS服务

  portmapRPC portmap管理器,它管理基于RPC服务的连接

  sendmail:邮件服务器sendmail

  smbSamba文件共享/打印服务

  syslog:一个让系统引导时起动syslogklogd系统日志守候进程的脚本

  xfsX Window字型服务器,为本地和远程X服务器提供字型集

Xinetd:支持多种网络服务的核心守护进程,可以管理wuftpsshdtelnet等服务

 

  这些守护进程也启动完成了,rc程序也就执行完了,然后又将返回init继续下一步。

 

第五部分:建立终端

 

  rc执行完毕后,返回init。这时基本系统环境已经设置好了,各种守护进程也已经启动了。init接下来会打开6个终端,以便用户登录系统。通过按Alt+Fn(n对应1-6)可以在这6个终端中切换。在inittab中的以下6行就是定义了6个终端:

  1:2345:respawn:/sbin/mingetty tty1

  2:2345:respawn:/sbin/mingetty tty2

  3:2345:respawn:/sbin/mingetty tty3

  4:2345:respawn:/sbin/mingetty tty4

  5:2345:respawn:/sbin/mingetty tty5

  6:2345:respawn:/sbin/mingetty tty6

  从上面可以看出在2345的运行级别中都将以respawn方式运行mingetty程序,mingetty程序能打开终端、设置模式。同时它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户的身份。

 

第六部分:登录系统,启动完成

 

  对于运行级别为5的图形方式用户来说,他们的登录是通过一个图形化的登录界面。登录成功后可以直接进入KDEGnome等窗口管理器。而本文主要讲的还是文本方式登录的情况:

  当我们看到mingetty的登录界面时,我们就可以输入用户名和密码来登录系统了。

  Linux的账号验证程序是loginlogin会接收mingetty传来的用户名作为用户名参数。然后login会对用户名进行分析:如果用户名不是root,且存在/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。只有/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。 /etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。

  在分析完用户名后,login将搜索/etc/passwd以及/etc/shadow来验证密码以及设置账户的其它信息,比如:主目录是什么、使用何种shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为/bin/bash

  login程序成功后,会向对应的终端在输出最近一次登录的信息(/var/log/lastlog中有记录),并检查用户是否有新邮件( /usr/spool/mail/的对应用户名目录下)。然后开始设置各种环境变量:对于bash来说,系统首先寻找/etc/profile脚本文件,并执行它;然后如果用户的主目录中存在.bash_profile文件,就执行它,在这些文件中又可能调用了其它配置文件,所有的配置文件执行后后,各种环境变量也设好了,这时会出现大家熟悉的命令行提示符,到此整个启动过程就结束了。

希望通过上面对Linux启动过程的剖析能帮助那些想深入学习Linux用户建立一个相关Linux启动过程的清晰概念,进而可以进一步研究Linux接下来是如何工作的。

 

 

 

  1.坚持看CCTV-1新闻联播。

  要想把握经济命脉,必须关注政局,新闻联播图文并茂,有声有色,着实为中国商人的最佳晴雨表;你可以不看财经报道,也可以不看焦点访谈,如果你不是做石油和外汇的,甚至你都可以不去管类似9.11事件和中东局势。

  2.不要轻易相信合约或合同

   哪怕合约让你的律师看过了,公证处公证了都不要轻易相信,甚至当你的客户把钱已经汇入你指定的账户以后你都必须确认,这笔钱你能不能拿出来,能不能动, 而合约以外的涉及到利益冲突的任何口头承诺与解释你都必须当他是放屁,无论香还是臭,在对方兑现承诺以前都不要沉湎其中,更无论对方是谁,哪怕那是你交了 十年的朋友甚至是和你上了床的女人都必须如此。

  3.你自己必须守信,一诺千斤,但对不守信的人例外!!

  你确认 你一定能够做到的事情你才可以承诺,但不要夸大其辞;你如果想一直做个商人,那么你必须树立自己的信誉!虽然你可以不在乎外界对你的争议甚至你也可以制造 争议但你不能失去信誉,否则你就不是一个商人而是一个骗子;信誉具体包括你如果和别人约了2:00见面,那么你绝对不可以1:50以前或者2:01以后出 现,如遇交通堵塞或意外事件,那你必须及时通知对方,除非你出了车祸遇到空难昏迷不醒或者已经死亡,否则你都没有理由爽约、早到太早或迟到太迟,而你的涵 养则体现在对待对方不守时不守承诺的态度与包容等方面;而一旦当你确认对方是在为了利益而一再欺骗你,那么你对对方做出的一切行为都不过分,甚至你可以将 计就计,反过来给他画一个饼!

  4.你能赢得起但你可能输不起的生意最好不做!

  在做任何生意以前,你都必须考虑 清楚,如果你输了,那么你是否输得起,而不是去考虑你如果赢了会怎样怎样,输不起的事情你最好别做!而考虑输的范围时你也不要只考虑钱财方面,作为半个商 人,有些东西你永远都输不起,包括你爱的女人,你的家人,你的江湖地位甚至你的信誉;所以你必须在做任何生意以前全面考虑清楚你究竟输得起输不起,如果输 得起,那么OK,你去义无返顾的DO IT吧!

  5.不要先期投入太多,给自己留够底牌

  不要把自己手里所有的牌全部亮出来,因为牌局随时会中途停止,而对方也随时会出新的牌,不到最后关键时刻,最好不要亮出你手里最有分量的牌,最后的赢家才是真正的赢家!

  6.天下无事不可为,但商人有所为也有所不为

  《菜根潭》上有句话说的好“毋以善小而不为,毋以恶小而为之”,说的是做人的道理,而生意也是如此:“不要因为利润少就不去做,也不要因为风险小就去做”;而同样在中国,违背法律的事情可以做,但违背道义的事情则坚决不能做。

  7.慎重选择合作伙伴

   无论是团队,还是个人,很多时候我们都渴望有能够和我们一起联手打天下的黄金搭档,但亲密战友是一定要慎重慎重再慎重的选择的,慎重是对彼此而言并非只 针对单方,而亲密战友一定要符合下面这些前提条件才可以成为你的亲密战友;其一,他和你一定需要在一个战壕里一起战斗过至少一年;其二,在你没有负他的前 提他对你所说的每一句话他自己都能负责任;其三,他必须是个实在而且能塌实干事的人;其四,他考虑得更多的是你们之间共同的利益(无论是短期的还是长期 的),而这个共同利益高于个人利益;其五,关键时刻他没有躲开更没有出卖你或者大家在他能获得比合作利益还大的更大利益的前提下,五点缺一不可,否则彼此 之间的合作不会长久。

  8.不要在你的团队里有你家庭成员的影子

  无论是你老婆还是你父母,都不可以在以你为核心 之一的商业团队里有太多插手,因为以你为核心之一的团队接受的是你,而不是你的家庭成员,在你的团队全体成员主动接受并邀请你的家庭成员成为你们团队一员 以前,无论你的家庭成员是谁,有多大的本事,或者可以给你们的团队带来多大的前进帮助,都不能成为你让你的家庭成员成为团队一员的理由;而当在你团队(三 人或三人以上)里的异性成员一旦与你上了床,那么你必须考虑让对方立刻离开这个团队,要么她(他)另谋高就,要么她(他)成为你的专职情人或者太太(老 公),总之,她(他)已经不能继续留在这个团队,所以在团队里不要随便考虑和异性上床或者动情。

  9.不要与和你有利益冲突的女人上床

   无论谁会占谁的便宜,或者大家之间可以同时并行两种关系,都不应该与之上床,更无论这个女人有多性感,多煽情;这样的女人包括:与你有生意往来的女人, 在你手下工作的女人或者是你手下与同事的家眷,甚至是管理着你的政府机构、职能部门里的女公务员,原因有二:其一,这样的女人可以让你死都不知道是怎么死 的,其二你虽然是半个商人,但另一半也不是出卖肉体的男妓。

  10.不要给你的女人讲你的商业细节

  无论这个女人 是你包了的娼妓、二奶,还是你众多情人之中的一个更或者是你已经结婚多年的妻子,你都应该不和她们谈你的商业细节:第一,你谈了可能她们也不懂;第二,你 谈的商业细节里面会有能让你入狱的内容;第三,那涉及到商业机密;无论你有多少信任你的女人,都不应该谈太多和你有关的商业内容;还有一个无须单列的商规 就是千万不要在夜总会歌厅等色情场所给小姐和应招女郎发名片,一定要记住!

  11.你可以行贿但不要做污点证人到法庭去举证

   大头小尾的发票最好别开,营业税以及附加的教育基金等你该交多少交多少,增值税你更别动脑子(记得找上家要税票正常抵扣就可以了),除此以外关税你掂量 掂量自己的分量,具体情况具体对待,企业所得税是可以全部避掉一分钱都不交的,但最好给地税点面子,少交点别一分钱都不交,交多交少你自己看着办,而个人 所得税呢,视当地政府给纳税人办了多少实事是否把咱纳税人真当纳税人人看待的具体情况而造工资表吧。

  12.不要偷税漏税但要学会合理避税
    
  13.你可以利用新闻记者但不要相信记者

  你可以给记者一些钱或礼品,但你不能告诉记者很多你的底细更不能相信记者会给你保守商业秘密,甚至你不能和记者靠的太近且必须学会面对记者而设防,无论你面对是是否是漂亮风骚的女记者还是能发内参的大腕记者都如此。
    
  14.不要摆大,哪怕你真的是老大

 切记天外有天,不要在任何场合摆大,哪怕你真的很大,而当对方是个摆大而且肤浅的人,你如果想灭掉他,那么最好随便找块砖头砸他一个跟头,然后你走你的路!但切记,这个砖头一定不是你自己的砖头,而且这个砖头最好和你自己没什么关系。

  15.保持中立,不要卷入政治派系纷争

 在任何派系纷争面前都应该保持中立,尤其是在政治派系纷争面前,事实证明,把商业利益的希望寄托在有派系纷争可能的任何一方面都是危险而且不明智的,在派系纷争之间,你必须冷静的保持中立。
    
  16.不要太在乎金钱与利益得失

  切记有所得就有所失,而有所失就有所得的古训,钱没有了还可以再赚,天下自然有得是你赚不完的钱和商业机会,所以何妨在金钱与利益面前大度一些呢?更何况,更大的商业机会正在等着你去把握呢?基本上你应该没有时间计较一时的得失才对,哪怕你有的是时间去品茗赏色。
    
  17.不要过多用金钱粉饰自己

  虽然面子对你而言很重要,但相对于你自己的人格魅力而言,有没有名车,带游泳池的别墅,高尔夫以及你的服饰,甚至发型这些都会显得微不足道;当然你可以按 自己的喜欢穿一双“内联升”的布鞋,甚至可以在有时间的时候飞到异国他乡去看一场你喜欢的球队的主场或客场比赛,更甚至你可以在很多人面前抽你自己喜欢抽 的劣质香烟!
    
  18.资本决定发言权,但你不应该轻易让别人知道你有多大的发言权

  不要羡慕别人的成 功,更不要鄙夷别人的失败,你首要应该做的是学会分析和总结现象背后的本质,找出别人失败或者成功的全部原因,取其长,补其短,做你自己该做的事情;而国 外类似微软等那些故事,离你实在太遥远,你大可以不去管他,所以一定意义上,现有的MBA对在中国做生意的你而言并没有多大实际意义!

  19. 总结别人的成败得失,但国外的案例你可以不用理会

  不管收受你贿赂的官员多么无耻,多么不够意思,你只可以行贿而绝不可以去做污点证人到法庭举证;除非 你以后连半个商人都不想做了;而如果是你主动行贿,对方收受了而且也为你办了事情,那你更不应该由此来当做能抓住对方小辫子的罪证要挟对方继续为你顶风开 绿灯,也无论对方是政府官员还是执法人员还是银行官员甚至办事人员的;最基本的,请客送礼吃饭洗桑拿甚至大到澳门游,这些感情投入都千万不要单独记小黑 账,而财务账面上的处理也一定要不留痕迹。

  20.不要用黑白道的规矩去解决商业上的冲突

  商业永远是商业,而 商业是有商业自身的游戏规则在的,所以哪怕你确实是黑道人物,也不能随便利用黑道规矩去解决商业冲突,同样,即将你有很好的白道背景与资源,你也不能轻易 利用这些资源来解决你在商业中的冲突!既然选择了商人,那你必须遵守商业中的一切游戏规则,愿赌就得服输!

  21.在能把握全局的前提下,不要追求事必躬亲

  不要把自己搞的没有时间与朋友交流,更不要让自己没有时间去泡女人和INTERNET,最要紧的是不要让自己没有时间放松与思考,所以,应该学会让别人去 帮你打点生意,处理业务,虽然,业务的核心部分你自己必须牢牢把握;同样,把事情交给别人去做的风险你要考虑清楚并能够预防,以免你把事情交给别人去做以 后,你自己又成了一名忙碌的救火队员,记住消防和救火是有区别的,消防的口号是“消防结合,预防为主”。

  22.给自己留条后路,预防众叛亲离你

   可以在沉寂江湖多年以后重整旗鼓,但你不可以倒下以后就不再起来,因为你是一个男人!所以你必须给自己留一条属于你自己的后路,后路包括藏起一个存钱 罐,虽然里面只有几块钱但你将来就是要靠这几块钱东山再起;后路也包括一栋法律意义上并不在你名下的房子(你可以有个地方一个人疗伤,恢复元气),更包括 一个并不经常来往的但很仗义而且你也给过他很多帮助的朋友(他可以在关键时候收留你,陪你喝酒,而这样的朋友一生中你能遇到一个也已经很幸运了);如果实 在没有后路,那么你就必须有去露宿街头沿街乞讨的心理准备,但那时候你只可以去向陌生人伸手,而绝对不要向你过去帮助过的还欠着你很多债务或者人情但装做 不认识你的人低头。

  关心一下你的属于你的资本和你能控制的资本是正确的,甚至你该关心的包括你的坏帐和现金收益以及现金流量, 但这些不应该让太多人知道;而当你以外的获得一笔巨大财富的时候,你更应该学会别把这个消息告诉别人,包括已经和你患难很久的妻子,否则她可能会要求离婚 并分割你的这笔不小的资产!记住,男人的金钱应该和女人的年龄一样永远属于秘密,哪怕有一天连女人的年龄都已经不是秘密的时候,你的金钱也应该还是秘密; 除非在你临死时即将捐献你的全部家产时,你都绝对不可告诉别人!