中科院 IP 网关 Linux 登录客户端版本 1.0 发布

愚人节和大家开了个 小玩笑,不过这次可不是玩笑了。CAS NET 正式发布版本 1.0,官方主页:http://share.solrex.org/casnet/

CAS Net 是中科院内部 IP 控制网关的 Linux 登录客户端,此软件完全使用 Python 语言写成,同时支持命令行和图形界面,使用简单,安装方便,实乃中国科学院 Linux 使用者居家旅行必备之良品 :)。

最新版本(1.0)特性:

1. 客户端同时具有命令行和图形界面,满足不同用户需要。
2. 可设置选项多,拥有较高扩展性。
3. 可保存用户设置,登录简单快捷。
4. 纯 Python 编程,修改简单,扩展性强,可移植到不同操作系统平台。
5. 开放源代码,确保程序无后门。

软件效果截图:

Ubuntu 7.10 系统下截图
Ubuntu 7.10 系统下截图

中科院 IP 网关 Linux 登录客户端版本 1pre 发布-注意日期

更多请访问官方主页:http://share.solrex.org/casnet/

CAS Net 是中科院内部 IP 控制网关的 Linux 登录客户端,此软件完全使用 Python 语言写成,同时支持命令行和图形界面,使用简单,安装方便,实乃中国科学院 Linux 使用者居家旅行必备之良品 :)。

最新版本(1 pre)特性:

  1. 客户端同时具有命令行和图形界面,满足不同用户需要。
  2. 可设置选项多,拥有较高扩展性。
  3. 可保存用户设置,登录简单快捷。
  4. 纯 Python 编程,修改简单,可移植到不同 Linux 平台。

感谢列表:

  • giv<goldolphin[at]163.com>: 命令行客户端脚本的原型作者

版权声明:

Copyright (C) 2008 Wenbo Yang<http://solrex.org> 祝大家节日快乐!

本软件遵从 GPL 协议<http://www.gnu.org/licenses/gpl.txt>,在此协议保护之下,您可以自由地使用、修改或分发本软件。

Ubuntu 7.10 系统下截图CentOS 5.1 系统下截图

POSIX 线程取消点的 Linux 实现

摘要:

这篇文章主要从一个 Linux 下一个 pthread_cancel 函数引起的多线程死锁小例子出发来说明 Linux 系统对 POSIX 线程取消点的实现方式,以及如何避免因此产生的线程死锁。

目录:

1. 一个 pthread_cancel 引起的线程死锁小例子
2. 取消点(Cancellation Point)
3. 取消类型(Cancellation Type)
4. Linux 的取消点实现
5. 对示例函数进入死锁的解释
6. 如何避免因此产生的死锁
7. 结论
8. 参考文献

1. 一个 pthread_cancel 引起的线程死锁小例子

下面是一段在 Linux 平台下能引起线程死锁的小例子。这个实例程序仅仅是使用了条件变量和互斥量进行一个简单的线程同步,thread0 首先启动,锁住互斥量 mutex,然后调用 pthread_cond_wait,它将线程 tid[0] 放在等待条件的线程列表上后,对 mutex 解锁。thread1 启动后等待 10 秒钟,此时 pthread_cond_wait 应该已经将 mutex 解锁,这时 tid[1] 线程锁住 mutex,然后广播信号唤醒 cond 等待条件的所有等待线程,之后解锁 mutex。当 mutex 解锁后,tid[0] 线程的 pthread_cond_wait 函数重新锁住 mutex 并返回,最后 tid[0] 再对 mutex 进行解锁。

1  #include <pthread.h>
2
3  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
4  pthread_cond_t   cond = PTHREAD_COND_INITIALIZER;
5
6  void* thread0(void* arg)
7  {
8    pthread_mutex_lock(&amp;mutex);
9    pthread_cond_wait(&amp;cond, &amp;mutex);
10   pthread_mutex_unlock(&amp;mutex);
11   pthread_exit(NULL);
12 }
13
14 void* thread1(void* arg)
15 {
16   sleep(10);
17   pthread_mutex_lock(&amp;mutex);
18   pthread_cond_broadcast(&amp;cond);
19   pthread_mutex_unlock(&amp;mutex);
20   pthread_exit(NULL);
21 }

22 int main()
23 {
24   pthread_t tid[2];
25   if (pthread_create(&amp;tid[0], NULL, &amp;thread0, NULL) != 0) {
26     exit(1);
27   }
28   if (pthread_create(&amp;tid[1], NULL, &amp;thread1, NULL) != 0) {
29     exit(1);
30   }
31   sleep(5);
32   pthread_cancel(tid[0]);
33
34   pthread_join(tid[0], NULL);
35   pthread_join(tid[1], NULL);
36
37   pthread_mutex_destroy(&amp;mutex);
38   pthread_cond_destroy(&amp;cond);
39   return 0;
40 }

看起来似乎没有什么问题,但是 main 函数调用了一个 pthread_cancel 来取消 tid[0] 线程。上面程序编译后运行时会发生无法终止情况,看起来像是 pthread_cancel 将 tid[0] 取消时没有执行 pthread_mutex_unlock 函数,这样 mutex 就被永远锁住,线程 tid[1] 也陷入无休止的等待中。事实是这样吗?

2. 取消点(Cancellation Point)

要注意的是 pthread_cancel 调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达 某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。pthread_cancel manual 说以下几个 POSIX 线程函数是取消点:

pthread_join(3)
pthread_cond_wait(3)
pthread_cond_timedwait(3)
pthread_testcancel(3)
sem_wait(3)
sigwait(3)

在中间我们可以找到 pthread_cond_wait 就是取消点之一。

但是,令人迷惑不解的是,所有介绍 Cancellation Points 的文章都仅仅说,当线程被取消后,将继续运行到取消点并发生取消动作。但我们注意到上面例子中 pthread_cancel 前面 main 函数已经 sleep 了 5 秒,那么在 pthread_cancel 被调用时,thread0 到底运行到 pthread_cond_wait 没有?

如果 thread0 运行到了 pthread_cond_wait,那么照上面的说法,它应该继续运行到下一个取消点并发生取消动作,而后面并没有取消点,所以 thread0 应该运行到 pthread_exit 并结束,这时 mutex 就会被解锁,这样就不应该发生死锁啊。

3. 取消类型(Cancellation Type)

我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。

POSIX 的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。

4. Linux 的取消点实现

下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。

以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:

145      /* Enable asynchronous cancellation.  Required by the standard.  */
146      cbuffer.oldtype = __pthread_enable_asynccancel ();
147
148      /* Wait until woken by signal or broadcast.  */
149      lll_futex_wait (&amp;cond-&gt;__data.__futex, futex_val);
150
151      /* Disable asynchronous cancellation.  */
152      __pthread_disable_asynccancel (cbuffer.oldtype);

我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。

这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。

5. 对示例函数进入死锁的解释

当 main 函数中调用 pthread_cancel 前,thread0 已经进入了 pthread_cond_wait 函数并将自己列入等待条件的线程列表中(lll_futex_wait)。这个可以通过 GDB 在各个函数上设置断点来验证。

当 pthread_cancel 被调用时,tid[0] 线程仍在等待,取消请求发生在 __pthread_disable_asynccancel 前,所以会被立即响应。但是 pthread_cond_wait 为注册了一个线程清理程序(glibc-2.6/nptl/pthread_cond_wait.c):

126  /* Before we block we enable cancellation.  Therefore we have to
127     install a cancellation handler.  */
128  __pthread_cleanup_push (&amp;buffer, __condvar_cleanup, &amp;cbuffer);

那么这个线程清理程序 __condvar_cleanup 干了什么事情呢?我们可以注意到在它的实现最后(glibc-2.6/nptl/pthread_cond_wait.c):

85  /* Get the mutex before returning unless asynchronous cancellation
86     is in effect.  */
87  __pthread_mutex_cond_lock (cbuffer-&gt;mutex);
88}

哦,__condvar_cleanup 在最后将 mutex 重新锁上了。而这时候 thread1 还在休眠(sleep(10)),等它醒来时,mutex 将会永远被锁住,这就是为什么 thread1 陷入无休止的阻塞中。

6. 如何避免因此产生的死锁

由于线程清理函数 pthread_cleanup_push 使用的策略是先进后出(FILO),那么我们可以在 pthread_cond_wait 函数前先注册一个线程处理函数:

void cleanup(void *arg)
{
  pthread_mutex_unlock(&amp;mutex);
}
void* thread0(void* arg)
{
  pthread_cleanup_push(cleanup, NULL);  // thread cleanup handler
  pthread_mutex_lock(&amp;mutex);
  pthread_cond_wait(&amp;cond, &amp;mutex);
  pthread_mutex_unlock(&amp;mutex);
  pthread_cleanup_pop(0);
  pthread_exit(NULL);
}

这样,当线程被取消时,先执行 pthread_cond_wait 中注册的线程清理函数 __condvar_cleanup,将 mutex 锁上,再执行 thread0 中注册的线程处理函数 cleanup,将 mutex 解锁。这样就避免了死锁的发生。

7. 结论

多线程下的线程同步一直是一个让人很头痛的问题。POSIX 为了避免立即取消程序引起的资源占用问题而引入的 Cancellation Points 概念是一个非常好的设计,但是不合适的使用 pthread_cancel 仍然会引起线程同步的问题。了解 POSIX 线程取消点在 Linux 下的实现更有助于理解它的机制和有利于更好的应用这个机制。

8. 参考文献

[1] W. Richard Stevens, Stephen A. Rago: Advanced Programming in the UNIX Environment, 2nd Edition.
[2] Linux Manpage

APUE, A Great Book

这两周是选课试听期,还没有正式开始上课,所以有点空闲就翻了翻 UINX 环境高级编程(Advanced Programming in the UNIX Environment, 2e),看了七八章,发现这本书真的是无愧于“UNIX 编程圣经”的称号。书中对编程中可能遇到的问题讲解得非常系统和详细,尤其当看到自己以前遇到过问题的地方时,简直就有一种顿悟的感觉,就想感叹一句“哦,原来如此!”。

我平常在写程序时,遇到问题总是求助于 Google。对那些讲编程技巧的书向来不怎么感冒(尤其是中国人写的),总觉得那种书根本不适合花时间仔细看一遍。这种问题驱动式的学习方式固然在解决某一特定问题时显得快捷高效,但是也往往受限于一叶障目不见泰山的困境。在解决了某一问题之后,对其它同类问题没有足够关注,导致再遇到类似问题时仍需要去搜索答案。

问题驱动式的学习方式会导致对问题的了解不够系统和深入,但如果仅仅拿本大部头慢慢翻完的话,又会枯燥无味,而且体会也不深。我觉得读编程书的最好方法就是,先有一定量的实践,再去看书,而且要保持对书中习题和代码的练习量。有时候不妨先看实例代码再看正文解释,如果代码看得懂,看作者的解释是否和自己理解一样;如果代码看不懂,就会加深对正文的注意度。而且有时候读那些入门级的教科书,不妨只看代码。

当然,在编程的时候,桌子上应该有几本经典图书当作手册来参考,不时地重读一下某些章节会很有好处。像 APUE,我就觉得非常适合作为案头书,做 Linux/Unix 开发的程序员买一本看看绝对不会失望。

Hello World 背后的真实故事

* 原作者:Antônio Augusto M. Fröhlich
* 原文链接http://www.lisha.ufsc.br/~guto/teaching/os/exercise/hello.html

* 译者:杨文博 <http://blog.solrex.org>
* 译文链接http://share.solrex.org/os/hello_cn.html
* 最后更新时间: 2008 年 2 月 28 日

我们计算机科学专业的大多数学生至少都接触过一回著名的 "Hello World" 程序。相比一个典型的应用程序——几乎总是有一个带网络连接的图形用户界面,"Hello World" 程序看起来只是一段很简单无趣的代码。不过,许多计算机科学专业的学生其实并不了解它背后的真实故事。这个练习的目的就是利用对 "Hello World" 的生存周期的分析来帮助你揭开它神秘的面纱。

源代码

让我们先看一下 Hello World 的源代码:

1. #include <stdio.h>
2. int main(void)
3. {
4. printf("Hello World!\n");
5. return 0;
6.
7. }

第 1 行指示编译器去包含调用 C 语言库(libc)函数 printf 所需要的头文件声明。

第 3 行声明了 main 函数,看起来好像是我们程序的入口点(在后面我们将看到,其实它不是)。它被声明为一个不带参数(我们这里不准备理会命令行参数)且会返回一个整型值给它的父进程(在我们的例子里是 shell)的函数。顺便说一下,shell 在调用程序时对其返回值有个约定:子进程在结束时必须返回一个 8 比特数来代表它的状态:0 代表正常结束,0~128 中间的数代表进程检测到的异常终止,大于 128 的数值代表由信号引起的终止。

从第 4 行到第 8 行构成了 main 函数的实现,即调用 C 语言库函数 printf 输出 "Hello World!\n" 字符串,在结束时返回 0 给它的父进程。

简单,非常简单!

编译

现在让我们看看 "Hello World" 的编译过程。在下面的讨论中,我们将使用非常流行的 GNU 编译器(gcc)和它的二进制辅助工具(binutils)。我们可以使用下面命令来编译我们的程序:

# gcc -Os -c hello.c

这样就生成了目标文件 hello.o,来看一下它的属性:

# file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

给出的信息告诉我们 hello.o 是个可重定位的目标文件(relocatable),为 IA-32(Intel Architecture 32) 平台编译(在这个练习中我使用了一台标准 PC),保存为 ELF(Executable and Linking Format) 文件格式,并且包含着符号表(not stripped)。

顺便:

# objdump -hrt hello.o
hello.o: file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000011 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000048 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000048 2**2
ALLOC
3 .rodata.str1.1 0000000d 00000000 00000000 00000048 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000033 00000000 00000000 00000055 2**0
CONTENTS, READONLY

SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .rodata.str1.1 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000011 main
00000000 *UND* 00000000 puts

RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000004 R_386_32 .rodata.str1.1
00000009 R_386_PC32 puts

这告诉我们 hello.o 有 5 个段:

(译者注:在下面的解释中读者要分清什么是 ELF 文件中的段(section)和进程中的段(segment)。比如 .text 是 ELF 文件中的段名,当程序被加载到内存中之后,.text 段构成了程序的可执行代码段。其实有时候在中文环境下也称 .text 段为代码段,要根据上下文分清它代表的意思。)

1. .text: 这是 "Hello World" 编译生成的可执行代码,也就是说这个程序对应的 IA-32 指令序列。.text 段将被加载程序用来初始化进程的代码段。

2. .data:"Hello World" 的程序里既没有初始化的全局变量也没有初始化的静态局部变量,所以这个段是空的。否则,这个段应该包含变量的初始值,运行前被装载到进程的数据段。

3. .bss: "Hello World" 也没有任何未初始化的全局或者局部变量,所以这个段也是空的。否则,这个段指示的是,在进程的数据段中除了上文的 .data 段内容,还有多少字节应该被分配并赋 0。

4. .rodata: 这个段包含着被标记为只读 "Hello World!\n" 字符串。很多操作系统并不支持进程(运行的程序)有只读数据段,所以 .rodata 段的内容既可以被装载到进程的代码段(因为它是只读的),也可以被装载到进程的数据段(因为它是数据)。因为编译器并不知道你的操作系统所使用的策略,所以它额外生成了一个 ELF 文件段。

5. .comment:这个段包含着 33 字节的注释。因为我们在代码中没有写任何注释,所以我们无法追溯它的来源。不过我们将很快在下面看到它是怎么来的。

它也给我们展示了一个符号表(symbol table),其中符号 main 的地址被设置为 00000000,符号 puts 未定义。此外,重定位表(relocation table)告诉我们怎么样去在 .text 段中去重定位对其它段内容的引用。第一个可重定位的符号对应于 .rodata 中的 "Hello World!\n" 字符串,第二个可重定位符号 puts,代表了使用 printf 所产生的对一个 libc 库函数的调用。为了更好的理解 hello.o 的内容,让我们来看看它的汇编代码:

1. # gcc -Os -S hello.c -o -
2. .file "hello.c"
3. .section .rodata.str1.1,"aMS",@progbits,1
4. .LC0:
5. .string "Hello World!"
6. .text
7. .align 2
8. .globl main
9. .type main,@function
10. main:
11. pushl %ebp
12. movl %esp, %ebp
13. pushl $.LC0
14. call puts
15. xorl %eax, %eax
16. leave
17. ret
18. .Lfe1:
19. .size n,.Lfe1-n
20. .ident "GCC: (GNU) 3.2 20020903 (Red Hat Linux 8.0 3.2-7)"

从汇编代码中我们可以清楚的看到 ELF 段标记是怎么来的。比如,.text 段是 32 位对齐的(第 7 行)。它也揭示了 .comment 段是从哪儿来的(第 20 行)。因为我们使用 printf 来打印一个字符串,并且我们要求我们优秀的编译器对生成的代码进行优化(-Os),编译器用(应该更快的) puts 调用来取代 printf 调用。不幸的是,我们后面将会看到我们的 libc 库的实现会使这种优化变得没什么用。

那么这段汇编代码会生成什么代码呢?没什么意外之处:使用标志字符串地址的标号 .LCO 作为参数的一个对 puts 库函数的简单调用。

连接

下面让我们看一下 hello.o 转化为可执行文件的过程。可能会有人觉得用下面的命令就可以了:

# ld -o hello hello.o -lc
ld: warning: cannot find entry symbol _start; defaulting to 08048184

不过,那个警告是什么意思?尝试运行一下!

是的,hello 程序不工作。让我们回到那个警告:它告诉我们连接器(ld)不能找到我们程序的入口点 _start。不过 main 难道不是入口点吗?简短的来说,从程序员的角度来看 main 可能是一个 C 程序的入口点。但实际上,在调用 main 之前,一个进程已经执行了一大堆代码来“为可执行程序清理房间”。我们通常情况下从编译器或者操作系统提供者那里得到这些外壳程序(surrounding code,译者注:比如 CRT)。

下面让我们试试这个命令:

# ld -static -o hello -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -lgcc

现在我们可以得到一个真正的可执行文件了。使用静态连接(static linking)有两个原因:一,在这里我不想深入去讨论动态连接库(dynamic libraries)是怎么工作的;二,我想让你看看在我们库(libc 和 libgcc)的实现中,有多少不必要的代码将被添加到 "Hello World" 程序中。试一下这个命令:

# find hello.c hello.o hello -printf "%f\t%s\n"
hello.c 84
hello.o 788
hello 445506

你也可以尝试 "nm hello" 和 "objdump -d hello" 命令来得到什么东西被连接到了可执行文件中。

想了解动态连接的更多内容,请参考 Program Library HOWTO

装载和运行

在一个遵循 POSIX(Portable Operating System Interface) 标准的操作系统(OS)上,装载一个程序是由父进程发起 fork 系统调用来复制自己,然后刚生成的子进程发起 execve 系统调用来装载和执行要运行的程序组成的。无论何时你在 shell 中敲入一个外部命令,这个过程都会被实施。你可以使用 truss 或者 trace 命令来验证一下:

# strace -i hello > /dev/null
[????????] execve("./hello", ["hello"], [/* 46 vars */]) = 0
...
[08053d44] write(1, "Hello World!\n", 13) = 13
...
[0804e7ad] _exit(0) = ?

除了 execve 系统调用,上面的输出展示了打印函数 puts 中的 write 系统调用,和用 main 的返回值(0)作为参数的 exit 系统调用。

为了解 execve 实施的装载过程背后的细节,让我们看一下我们的 ELF 可执行文件:

# readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x80480e0
There are 3 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x55dac 0x55dac R E 0x1000
LOAD 0x055dc0 0x0809edc0 0x0809edc0 0x01df4 0x03240 RW 0x1000
NOTE 0x000094 0x08048094 0x08048094 0x00020 0x00020 R 0x4

Section to Segment mapping:
Segment Sections...
00 .init .text .fini .rodata __libc_atexit __libc_subfreeres .note.ABI-tag
01 .data .eh_frame .got .bss
02 .note.ABI-tag

输出显示了 hello 的整体结构。第一个程序头对应于进程的代码段,它将从文件偏移 0x000000 处被装载到映射到进程地址空间的 0x08048000 地址的物理内存中(虚拟内存机制)。代码段共有 0x55dac 字节大小而且必须按页对齐(0x1000, page-aligned)。这个段将包含我们前面讨论过的 ELF 文件中的 .text 段和 .rodata 段的内容,再加上在连接过程中生成的附加的段。正如我们预期,它被标志为:只读(R)和可执行(X),不过禁止写(W)。

第二个程序头对应于进程的数据段。装载这个段到内存的方式和上面所提到的一样。不过,需要注意的是,这个段占用的文件大小是 0x01df4 字节,而在内存中它占用了 0x03240 字节。这个差异主要归功于 .bss 段,它在内存中只需要被赋 0,所以不用在文件中出现(译者注:文件中只需要知道它的起始地址和大小即可)。进程的数据段仍然需要按页对齐(0x1000, page-aligned)并且将包含 .data 和 .bss 段。它将被标识为可读写(RW)。第三个程序头是连接阶段产生的,和这里的讨论没有什么关系。

如果你有一个 proc 文件系统,当你得到 "Hello World" 时停止进程(提示: gdb,译者注:用 gdb 设置断点),你可以用下面的命令检查一下是不是如上所说:

# cat /proc/`ps -C hello -o pid=`/maps
08048000-0809e000 r-xp 00000000 03:06 479202 .../hello
0809e000-080a1000 rw-p 00055000 03:06 479202 .../hello
080a1000-080a3000 rwxp 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0

第一个映射的区域是这个进程的代码段,第二个和第三个构成了数据段(data + bss + heap),第四个区域在 ELF 文件中没有对应的内容,是程序栈。更多和正在运行的 hello 进程有关的信息可以用 GNU 程序:time, ps 和 /proc/pid/stat 得到。

程序终止

当 "Hello World" 程序运行到 main 函数中的 return 语句时,它向我们在段连接部分讨论过的外壳函数传入了一个参数。这些函数中的某一个发起 exit 系统调用。这个 exit 系统调用将返回值转交给被 wait 系统调用阻塞的父进程。此外,它还要对终止的进程进行清理,将其占用的资源还给操作系统。用下面命令我们可以追踪到部分过程:

# strace -e trace=process -f sh -c "hello; echo $?" > /dev/null
execve("/bin/sh", ["sh", "-c", "hello; echo 0"], [/* 46 vars */]) = 0
fork() = 8321
[pid 8320] wait4(-1, <unfinished ...>
[pid 8321] execve("./hello", ["hello"], [/* 46 vars */]) = 0
[pid 8321] _exit(0) = ?
<... wait4 resumed> [WIFEXITED(s) && WEXITSTATUS(s) == 0], 0, NULL) = 8321
--- SIGCHLD (Child exited) ---
wait4(-1, 0xbffff06c, WNOHANG, NULL) = -1 ECHILD (No child processes)
_exit(0)

结束

这个练习的目的是让计算机专业的新生注意这样一个事实:一个 Java Applet 的运行并不是像魔法一样(无中生有的),即使在最简单的程序背后也有很多系统软件的支撑。如果您觉得这篇文章有用并且想提供建议来改进它,请发电子邮件给我

常见问题

这一节是为了回答学生们的常见问题。

* 什么是 "libgcc"? 为什么它在连接的时候被包含进来?
编译器内部的函数库,比如 libgcc,是用来实现目标平台没有直接实现的语言元素。举个例子,C 语言的模运算符 ("%") 在某个平台上可能无法映射到一条汇编指令。可能用一个函数调用实现比让编译器为其生成内嵌代码更受欢迎(特别是对一些内存受限的计算机来说,比如微控制器)。很多其它的基本运算,包括除法、乘法、字符串处理(比如 memory copy)一般都会在这类函数库中实现。

15 Minutes of Fame

短暂的荣光

——对 免费电子书《使用开源软件-自己动手写操作系统》 的一点点说明

我前几日发布了免费电子书《使用开源软件-自己动手写操作系统》,本以为只是在和我相关的一些圈子,主要是南京大学和南京大学毕业的 Linux 爱好者中得到传播。虽然我预计到了它会慢慢的扩散开来,但是从没有想过它在发布之初就产生了这么大的影响,我的个人主页和博客的点击量在这几日都迅速上升到日近千次访问。

从统计结果来看,JservsolidotLinuxGem 对此消息的分享功不可没。特别是对岸的 Jserv,繁体发布页的一千多次访问几乎都是他带来的(其实这也是我准备繁体发布页的原因,只可惜暂时没有精力发布繁体电子书)。在这里我对他们的无私帮助表示感谢。

再来说说这本电子书,其实我是将其当作对《自己动手写操作系统》一书的读书报告和扩展来写的。之所以将其发布出来,是想让更多人得益于我的共享,从零开始去理解一个操作系统,因为很多讲操作系统的书不会讲这些体验,了解了这些就能得到更完整的体验。

有很多人诟病本书的英文名“Write OS with Free Software”从语法上来讲有错误。我不得不承认我没有认真考虑这个名字,只是随手写了上去,所以可能下次发布的时候本书英文名会更改为“Write Your Own OS with Free and Open Source Software”,不知道各位可有更好提议?

我不敢说自己写这本书就是大公无私,回报开源社区,没有一点私心。私心是有的,不过只是为了出一点小名。现在的杨文博仅仅是无名小卒一个,有一些小小名气之后,于找工作交朋友都有益处。我花费了不少的气力在这本书上面,除了一点点名气并没有要求其它的什么,所以我欢迎中肯的批评和建议,而不是对本书毛病的横加指责。谁不想得到别人对自己工作的尊重呢?

我仍是研究生一年级学生,上个学期周五和周末都会去一间公司兼职来赚点生活费。因为作息和饮食一直不够规律引发了身体的一些零件出现问题,所以这个学期辞去了兼职工作专心学习。我有我的正常生活,网络仅仅是我生活的一小部分,这些小小名气也不能解决我的吃饭问题。我不保证电子书的更新发布速率会满足某些读者的要求,所以我只能说我尽力,非常感谢你们能喜欢这本书,很抱歉但请不要在这一点上指责我。

免费电子书《使用开源软件-自己动手写操作系统》Revision 1 发布

最后更新时间:2008年02月21日19点08分

免费电子书《使用开源软件-自己动手写操作系统》的官方主页为:http://share.solrex.org/WriteOS/ ,您可以到这里下载 pdf 格式电子书和随书源代码。

我叨叨了一个多月的这本书总算能发布第一个版本了,娘啊,费了老鼻子劲了。好多工作要花费在排版上,因为希望整得效果好点儿嘛!而且写了才知道,把自己知道的东西清楚地表述出来真不是件容易的事儿。啥也不说了,看图,还有目录。

免费电子书《使用开源软件-自己动手写操作系统》(无法看到此图,可能因为您无法连接国外网站)

写在前面的话i
序言vii
第一章计算机启动1
1.1 计算机启动过程. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 磁盘抽象物理结构. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2.1 硬盘. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 软盘. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.3 启动扇区. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 使用虚拟机. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.1 VirtualBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3.2 Bochs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 使用软盘镜像. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.1 制作软盘镜像. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.2 用软盘镜像启动虚拟机. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
第二章最小的“操作系统” 19
2.1 Hello OS world! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.1 Intel 汇编转化为AT&T(GAS) 汇编. . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.1.2 用连接脚本控制地址空间. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.1.3 用Makeˉle 编译连接. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.1.4 用虚拟机加载执行boot.img . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.2 FAT 文件系统. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.1 FAT12 文件系统. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.2 启动扇区与BPB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2.3 FAT12 数据结构. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2.4 FAT12 根目录结构. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.3 让启动扇区加载引导文件. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.1 一个最简单的loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.2 读取软盘扇区的BIOS 13h 号中断. . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.3 搜索loader.bin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.3.4 加载loader 入内存. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.3.5 向loader 转交控制权. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.3.6 生成镜像并测试. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.4 本章全部源代码. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Twiki 语法快速指南

最后更新时间:2008年01月14日01点00分

摘要:

这份文档主要给出了一些常用的 TWiki 文章编辑方法。TWiki 是一个广泛使用的开源 wiki 系统,通常被企业和组织用户用来共享知识等。更多介绍请看它的官方站点:http://twiki.org

这只是作者的一份编程笔记,其实与网上早期版本的 TWiki 文档中文翻译有些重复,需要更多内容请查看参考文章和链接。

目录

1. 基本语法
1.1 话题
1.2 标题和段落
1.3 字体
1.4 列表
1.4.1 无序号列表
1.4.2 带序号列表
1.5 表格
1.6 链接
1.6.1 词条链接
1.6.2 外部链接
1.6.3 页面内锚点
1.6.4 图片和附件链接
1.7
图标
2. 页面编辑技巧
3. 参考文章和链接

1. 基本语法

[1.1 话题]

Wiki 的精神就是用词条描述世界,所以 TWiki 也是这样,它内部对内容的管理是用一个一个 WikiWord 来分类的。WikiWord 就是像前面这种两个单词构成的连接在一起的词组,里面大小写交错。

TWiki 的话题(topic)推荐用 WikiWord 来建立,如果用户输入的新话题不是一个 WikiWord,那么建立新话题的按钮就不会被激活。但是 TWiki 允许用户使用非 WikiWord 建立词条,需要手动勾选上允许使用非 WikiWord 建立话题。

[1.2 标题和段落]

1.2.1 标题
TWiki 中可以使用分级标题,分级标题的语法如下:
---+
---++
即在行首三个"-"和一个"+"代表一级标题,三个"-"和两个"+"代表二级标题,以此类推。当用户使用规范的标题记号建立好话题之后,可以很方便地使用"%TOC%"标记建立一个标题目录。如果用户不想某个标题被包含,只需要在标记标记后加上两个感叹号"!!",比如:

---+!! 目录
%TOC%
这样目录这个标题就不会包含在自动建立的目录里。

1.2.2 段落
TWiki 的段落分隔和 LaTeX 有点儿类似,段落之间需要空一行。如果想输入不被 TWiki 格式化的原始文字(比如源程序等),需要用标签将这些段落包起来,主要有以下两种标签:
<verbatim></verbatim>
<pre></pre>

区别是 <verbatim></verbatim> 中间的代码以完全原始方式显示,<pre></pre> 中某些 HTML 标签依然起作用。

[1.3 字体和分隔线]

1.3.1 字体
TWiki 使用字体的方式比较像 HTML 的标签,就是在字符串两头加上某些标记。比如:
*Bold Font* 粗体
_Italic Font_ 斜体
__Bold Italic__ 粗斜体
=Fixed Font= 等宽字符
==Bold Fixed Font== 等宽粗体字符
最最需要注意的一点是:这些标记"*_="必须内侧与文字相连,外侧为空格,标记之间也不得有空格。

1.3.2 分割线
TWiki 的分割线是在行首输入连续的多于三个的减号"-",例如
----

[1.4 列表]

1.4.1 无序号列表
无序号列表的格式是:
*
*
即三个空格加"*"所进一层,六个空格加"*"缩进第二层,以此类推。

1.4.2 带序号列表
带序号列表的格式是:
1.
1.
即三个空格加"1"所进一层,六个空格加"1"缩进第二层,以此类推。注意,这里的"1"代表用阿拉伯数字编号列表,其它编号方式有"A"或"a"大小写字母标号,"I"或"i"大小写罗马字母编号。

注意:这里后面的小数点可要可不要,可以一直使用"1"编号,也可用"1,2,3"递增编号,效果无区别。

[1.5 表格]

表格的建立是用竖线"|"分隔,比如:
|T1|T2|T3|
|A1|A2|A3|
就建立了一个两行三列的列表。单元格内部的左右对齐是利用和竖线的距离实现的。

[1.6 链接]

1.6.1 词条链接
如果是规范的多词 WikiWord 话题,可以使用双方括号直接括起来,例如:[[my wiki topic]]就会直接引用 MyWikiTopic 词条;如果是非规范话题,或者引用说明和引用话题不一样,需要使用引用与说明分开的格式,例如:[[MyWikiTopic][my WIKI topic]]。

1.6.2 外部链接
外部链接可以直接使用类似与词条链接的方式来引用,例如:[[
http://blog.solrex.org][Solrex
的博客]] 。

1.6.3 页面内锚点
在页面内可以定义锚点,这样可以使用链接在页面内跳来跳去。定义锚点的方法是在行首使用 #WikiWord,例如:
#FootNote Footnote is....
就定义了一个到该段的锚点。引用锚点和词条链接的方式也类似,例如:[[#FootNote][to footnote]]。如果引用别的页面的锚点,只需要在锚点前面加上该页面的话题名,例如:[[MyWikiTopic#FootNote][to another footnote]]。

1.6.4 图片和附件链接
如果引用在同一页面的附件或者图片(其实一般图片也是附件),链接的格式为:%ATTACHURL%/filename.extesion,比如:%ATTACHURL%/about.pdf;引用在不同页面的链接,需要在文件名前面加上该页面主题的名字,比如:%PUBURL%/%WEB%/MyWikiTopic/about.pdf

[1.7 图标]

TWiki 预定义了很多图标,直接在文中就可以使用,比如帮助的小 i 图标是:%H%,update 的图标是:%U%,new 的图标是:%N%。合理使用这些图标能增强文章的可读性。

2. 页面编辑技巧

[1] 建立话题时合理分级,有规律地规划父话题和子话题关系。
[2] 处理重复话题时使用 %INCLUDE{"XXX"}% 来包含已有的话题,比如我已经有了 PersonalComputer 话题,在建立 PC 话题时候,就应该直接在页面中使用 %INCLUDE{"PersonalComputer"}% 来避免冗余。
[3] 使用%TOC%自动创建目录:当编辑一篇比较长的文章时,建议使用标题标记建立分级标题,最后使用 %TOC% 在上方建立一个可索引目录。

[4] 合理使用字体和图标增加可读性。
[5] 合理使用 HTML 代码来加强页面排版功能。TWiki 可以直接支持 HTML 代码,为了格式的统一,一般不建议直接使用 HTML。但有些页面排版过于复杂,使用 HTML 可以直接达到要求。
[6] 使用注释的技巧:TWiki 没有装 footnote 插件时候是不支持注释链接的,但是可以通过一些技巧来实现。我们可以先在注释或者引用列表前建立一个锚点:
#FootNote

---+ Footnotes
1 aaa
1 bbb
当文中内容需要注释时,使用 HTML 和 TWiki 链接一起加一个上脚标:aaa<sup>[[[#FootNote][1]]]</sup>,这样 aaa 的右上角就可以出现一个方括号,里面是带到脚注链接的脚注编号 "1"。

3. 参考文章和链接

[1] 早期版本 TWiki 语法格式的中文翻译:
http://www.stlchina.org/twiki/bin/view.pl/TWiki/TextFormattingRules
[2] TWiki 官方语法文档:
http://twiki.org/cgi-bin/view/TWiki/TextFormattingRules

:tag: Wiki,TWiki

我的一些关于盗版的观点

前段时间我写了篇 扔掉你 Windows 操作系统中的盗版软件吧 的博客文章,那是因为我不得不使用 Windows 下的 QQ 来和我女友进行视频聊天,而我又不想在我的系统中再次塞满盗版软件,所以就尝试着完全使用免费软件来工作,结果我发现效果还很好。不使用盗版软件并没有影响我的工作,有时候反而能促进我的工作效率,所以我想打破“使用 Windows(正版) => 不得不使用盗版软件工作”这种神话。

我并不是一个无条件反对使用盗版的人。我认为版权保护的目的就是为了促进人类知识和智慧的发展,就像林肯说的那句话:The patent system added the fuel of interest to the fire of genius. (专利制度是为了用利益燃料来浇灌天才之火。) 而在当前的某些状况下,版权保护体系其实是在实现某些团体的利益,而阻碍了人类知识和智慧的发展。因为有很多后发国家,有很多落后社会,版权保护体系在它们和先进国家之间划分了一道巨大的数字鸿沟。从某种意义上来讲,这有点像垄断组织对资源的控制和对其它后起企业的挤压一样,虽然垄断组织也是从自由竞争中发展起来的,但是当它成为阻碍市场发展的力量时,它就应该被拆散。

所以说,我不反对“贫困却希望享有平等获得信息权利”的盗版行为(比如盗版音乐、电影、书籍),我也不反对“由需要而只能使用无法替代的应用”的盗版行为,但是我反对“了解到有替代的东西而不去使用它”的盗版行为,更反对“知道这一切却我行我素并不以使用盗版为耻”的盗版行为。总的来说我的观点是:如果一个人使用盗版,必须感到抱歉,认识到这是一种违法行为;如果某种东西无法替代,为了自身的发展,可以在很抱歉的情况下来使用它;如果某种东西可以替代,那么就应该采取行动避免使用盗版。

关于盗版不仅仅是道德层面的问题,还有一些其它的影响。这些警钟已经被敲过很多遍了,就比如关于 Windows:微软的新操作系统 Vista 引入了一些 DRM(Digital Right Management)的新特性,微软喜欢把这种概念叫做“可信计算”,有一个计划叫做“palladium”,后来改名叫做“NGSCB(下一代安全计算基础,Next Generation Secure Computing Base)”,意思是为了用户安全。但是关于这个说法,Richard Stallman 的一句话 说的很正确:

When Microsoft speaks of “security” in connection with palladium, they do not mean what we normally mean by that word: protecting your machine From things you do not want. They mean protecting your copies of data on your machine From access by you in ways others do not want.(当微软提到和 palladium 有关的“安全”时,它的意思不是我们所说的“安全”的通常意义:“保护你的机器,不做你不想做的事情”。他们的意思是:“保护你机器中的数据拷贝,在他人(和微软相关的企业或者组织)不允许的情况下,你不得访问。”)

那么这个的实际意义就是:你在 Windows 下用 QQ,不是你觉得“我相信 QQ,我要用。”你才能用,而是需要 Windows 说“我相信 QQ,你能用!”,你才能用。这才是可信计算的真正意思,可信计算组织要控制你访问自己系统资源的能力,所以不要相信什么带有安全芯片的笔记本电脑,那个安全和你的安全是不一样的。况且,任何国家生产的安全芯片,都是需要加入某些国家安全部门产品的。

这些大公司大企业(就是你正在侵犯他们版权的那些企业)并没有那么慷慨,盗版的代价迟早是要付出的。他们所做的是让你先陷入盗版的深渊,然后再剥夺你的自由权利,最终获取更大的控制权和更多的利益。Trusted computing 就是其中的一步,当每台电脑都被微软强制安上安全芯片后(这可能是由盗版用户的市场贡献给微软带来的特权),恐怕你运行自己写的程序也要先给微软发封电子邮件以得到许可了。

那么如何避免这种现象的发生呢?只需要提几个问题你就明白了:如果 Linux 占桌面市场操作系统份额 50% 以上,微软会/能这样做吗?如果 Loongson(龙芯) 在中国个人电脑芯片市场占统治地位,Intel 会/能这样做吗?如果世界前 10 名高性能计算机中 Lenovo 占一半以上,IBM 会/能这样做吗?

所以不使用盗版软件,也是你为后代人做的贡献,恐怕你不想听到你的孙子问你:“爷爷,为什么我爸爸要花掉收入的一半去买操作系统和其它软件的授权呢?”

扔掉你 Windows 中的盗版软件吧

——Windows 下常用盗版软件的替代免费软件列表

最后更新时间:2009年8月30日

本文不是一篇对开源软件或者 Linux 的宣传,本文仅仅针对那些在 Windows 下不愿意使用盗版软件,和不能忍耐寻找盗版软件注册码、注册机带来的麻烦的用户。本文的主要内容就是介绍一些用来替代 Windows 下流行盗版软件的开源和免费软件。请注意替代的免费软件并不一定比流行的盗版软件功能差,很多甚至比商业软件还要好。

当您看完这篇文章,我相信您完全可以把您 Windows 系统里安装的盗版软件清理干净而不影响您的任何工作。如果您仍然希望并且喜欢、享受做一个盗版软件用户的话,那也没有办法,但是请您记住,非常非常重要的一点:您已经不是一个历史清白的人,因为一打开电脑,您就在犯罪(侵犯知识产权,侵犯软件版权)。以后像“我是一个遵纪守法的好公民”这种话,想想再说吧!

目录:

1. 压缩解压缩软件
2. 文本编辑软件
3. 杀毒软件
4. 软件防火墙
5. 辞典软件
6. FTP 下载工具
7. 邮件客户端 & RSS 阅读器
8. 办公软件
9. 图片查看器
10. 集成开发环境
11. C/CPP 编译器
12. 科技文献排版
13. 图片编辑软件
14. 远程登录软件
15. 统计计算软件
16. 数学计算软件
17. 反汇编软件
18. 参考文献管理
19. 影音播放
20. 刻录工具

内容:

1. 压缩解压缩软件
流行盗版软件:WinRAR
替代开源软件:7-Zip http://www.7-zip.org

7-Zip 在 2007 年被 SourceForge 网站评为社区选择最佳项目奖,最佳技术设计奖。7-Zip 可以说是能完美替换 WinRAR(甚至更好),它可以解压所有流行压缩格式的压缩文件,可以解压或压缩 7z, ZIP, GZIP, BZIP2 和 TAR 压缩文件格式。注意,7-Zip 不可压缩为 rar 文件格式,这样做有版权问题。

2. 文本编辑软件
流行盗版软件:UltraEdit
替代开源软件:Notepad++ http://notepad-plus.sourceforge.net
文本比较软件:WinMerge http://winmerge.org

相信用过 UltraEdit 的人都会赞不绝口,因为它强大的语法高亮,十六进制编辑,文件对比等等功能令人爱不释手,但是可以的是,它是一个收费软件。那么开源社区有没有相应的替代品呢,答案是肯定的,Notepad++ 一定不会让你失望。更强大的语法高亮,更具扩展性的插件,除了文件对比功能还比不上 UE,我真的是挑不出来一点儿毛病,这篇文章就是在 Notepad++ 中写的,赶快去下载一个尝试一把吧。而且 Notepad++ 还带有很多插件,如果你的 Notepad++ 带有 Nppexec 插件,那么你就可以在 Notepad++ 中打开一个类似于命令行的窗口,想想用这个写 Latex 文档多爽吧,在侧栏中直接输入 latex xx.tex 就可以直接调用 latex 进行编译了,这总比打开一个新的命令行窗口要省事多了吧。

感谢 bbbush 推荐的开源软件 WinMerge,这是一个强大的文本比较和整合工具。

3. 杀毒软件
流行杀毒软件:Kaspersky, Norton, NOD32, Rising 等等太多了
替代开源软件:ClamWin http://www.clamwin.com
替代免费软件:AVG http://free.grisoft.com
替代免费软件:Avira AntiVir Personal http://www.free-av.com
替代免费软件:Avast Home Edition http://www.avast.com

相信大家对病毒都一直很头痛,杀毒软件这个东西,平常用不着,真到用着时候却发现起不了啥作用。上面推荐的杀毒软件中,第一款是开源软件,所以功能稍微弱一些,后面三款都是有商业公司支持的,包括收费版和个人用户免费版两种,所以软件的质量还是可以保证的。感谢 Abcx 推荐 Avira AntiVir. 我个人比较推荐 Avast,功能真的很强大,不比某些软件的商业版本少什么东西。

4. 软件防火墙
流行盗版软件:ZoneAlarm, 天网,瑞星 等等也太多了
替代免费软件:COMODO Firewall Pro http://www.personalfirewall.comodo.com

如果说 COMODO Firewall Pro 是“完美替代”瑞星防火墙,我都觉得侮辱它了。COMODO Firewall Pro 曾获得 Web User 杂志 2007 年金奖,它不仅仅是一个防火墙,而且还可以监视系统文件的更改,它监视文件范围之广泛简直都让我崩溃了,总之它是一个防火墙,但是包含了一些杀毒软件才有的功能,至于它的强大,用了才知道。用 COMODO Firewall Pro 和 Avast 可以帮你打造一个不逊于任何杀毒软件和防火墙组合的安全系统。COMODO 在 2009 后将杀毒功能也引入了防火墙功能中,它的安全套件目前显得比较臃肿,已经不太适合追求简约的用户使用了。

5. 辞典软件
流行盗版软件:金山词霸
替代开源软件:StarDict(星际译王) http://stardict.sourceforge.net
替代免费软件:灵格斯词霸 http://www.lingoes.cn
替代免费软件:谷歌金山词霸合作版 http://g.ciba.com

如果你用过 StarDict,我相信你不会再夸金山词霸好。StarDict 可以自定义辞典库,仅仅中英互译辞典你就有二十多种选择(其中包含金山词霸的辞典库哎),还有其它很多语言的辞典库,而且翻译中有很好玩的词义发散树。为什么它叫译王呢?因为它还有全文翻译功能,动心了吧,把金山词霸删了吧,换 StarDict,我保证你不会失望。

感谢 SteamedFish 推荐灵格斯词霸,这也是一款相当不错的辞典软件,尤其适合 Windows 下的用户习惯。灵格斯和星际译王最大的问题是没有解决好辞典的 license 问题,尤其是灵格斯词霸,这是它以后发展的隐忧。

2008年5月 Google 联合金山公司推出谷歌金山词霸合作版,虽然自带辞典少了点儿,但是有在线辞典功能。也许你想尝试一把!

6. FTP 下载工具
流行盗版软件:FlashFXP
替代开源软件:FileZilla http://filezilla-project.org
替代免费软件:UltraFXP http://www.click-now.net/html/UltraFXP.htm

FTP 下载工具,其实这个东西没什么讲头,大家用起来都差不多,只是换一个软件换一个使用方式而已。窗口布局,菜单内容变一变,基本功能都没差别。世事真的很多变,我以前强烈推荐 SmartFTP 在 2008 年 7 月全面转向了收费软件,这件事让我很伤心,因为这个软件我用的很舒服,sigh!不过幸好我们有开源软件 FileZilla,FileZilla 也支持 IPV6 协议以及使用代理服务器,唯一有点儿不舒服的是它的窗口分布有点儿乱,不过相信会很容易适应的。

7. 邮件客户端 & RSS 阅读器
流行盗版软件:Outlook, FeedDemon
替代开源软件:ThunderBird http://www.mozilla.com/thunderbird

ThunderBird,其实算是 Firefox 的兄弟,两者同是 mozilla 的开源项目。要说邮件客户端,ThunderBird 可能不是最优秀的,但是要说邮件客户端 + RSS 阅读器,恐怕直接支持两者的不太多吧。我之所以用它有一个很重要的理由,ThunderBird 是一款跨平台的阅读器,这样我就不用在 Windows 下和 Linux 下分别设置两次邮件客户端了,只需要把一个平台下的数据文件拷贝到另一平台下,就完全可以使用了。

8. 办公软件
流行盗版软件:MS Office
替代开源软件:OpenOffice http://www.openoffice.org
替代开源软件:AbiWord http://www.abisource.com
替代免费软件:WPS Office 2007 个人版http://www.wps.com.cn
替代免费软件:永中 Office 2009 个人版http://www.evermoresw.com/webch/product/personal.jsp

MS Office 我就不说了,有几个人不是盗版?正版 Office 2007 可是最便宜 400 多美元一套呢!用微软的口吻来说,您可是每年给微软带来好几百美元的直接经济损失啊(再加上Visual Studio, Windows 呢,上千美元了吧)!OpenOffice 现在虽然不敢说完美支持 MS Office 文档,但至少大部分兼容是没有问题的,而且 OpenOffice 文档可以直接保存成多种文件格式,像小气的微软在 2007 里还需要安装插件支持输出 pdf,太吝啬了吧。所以呢,OpenOffice 给你提供了替换 MS Office 的绝大部分功能,为什么还做一个盗版者呢?下载一个吧,至少可以当作一个 pdf 转换工具,要知道低于 2007 的 MS Office 可必须得安装 Adobe Acrobat 才能实现转换 pdf 功能,sigh,又是一个盗版软件!

对于中文用户,在这里我特别推荐金山公司的 WPS Office 套装个人版,对 MS Office 文档的支持到让人吃惊的地步,也没有 OpenOffice 一些启动速度慢等一些问题,非常适合一般用户使用;永中 Office 也在 2008 年 10 月 13 日宣布了个人版免费这一令人振奋的消息,这一消息使得 Linux 用户们有福了,因为永中不仅有 Windows 版,也有 Linux 版,对中文的支持要比 OpenOffice 好很多。但是从测试效果来看,Linux 版本对文档字体兼容性不如 Windows 下的 WPS,不知道 Windows 下的永中表现怎么样。

9. 图片查看器
流行盗版软件:ACDSee
替代开源软件:IrfanView http://www.irfanview.com

其实我对图片浏览软件并不感冒,因为 GFans 都是用 Picasa 嘛!但是看到很多人用 ACDSee 就让我很不爽,IrfanView 挺好的啊,而且速度更快。note:第一次看到有人用 IrfanView,是西门子公司 PSE 的工程师演示流程图。

10. 集成开发环境
流行盗版软件:MS Visual Studio, Intel C++, Turbo C, Delphi
替代开源软件:Eclipse(Almost Every Language) http://www.eclipse.org
替代开源软件:Code::Blocks (Only C/C++) http://www.codeblocks.org/
替代开源软件:NetBeans (Only JAVA) http://www.netbeans.org

如果你是个程序员,还没听说过 Eclipse?太老土了吧。先别说 eclipse 好不好,就拿现在大部分软件公司(尤其是JAVA软件开发公司)都在用 eclipse 来说,你也应该使用它。别给我说 Visual Studio 更顺手,我宁愿用 Notepad++ 写代码再调用命令行编译也不愿意用 Visual Studio 的代码编辑器(当然我这里指 Visual Studio 6.0,俺太土,就用过古董)。Eclipse 强大的插件功能,代码管理功能,自动补全功能,支持多种程序语言...好处我就不列举了(因为我发现自己总是经常不经意发现 Eclipse 的优点,点点惊喜太多了),自己用吧。

但是 Eclipse 最大的缺点就是“慢”!Code::Blocks 作为一个跨平台的 IDE,在速度上要比 Eclipse 好上很多。Code::Blocks 某些版本自带 C/C++ 编译器,如果您是个 C++ Programmer,您可以选择 Code::Blocks。

11. C/C++ 编译器
流行盗版软件:MS Visual Studio, Turbo C
替代开源软件:DevC++ http://www.bloodshed.net/devcpp.html

其实这个真的不好说,我没用过 DevC++,因为我基本上不在 windows 下写 C 代码了。但是 DevC++ 的编译器基础 GCC,我是太经常用了,我不相信你没听说过它,所以我就不解释了。为了写一段 hello world 满世界找注册码,找盗版光盘,值得吗?而且 VC6 对 C++ 标准的实现那么差,而 VS2003 以上动辄几个 G 的大小,相比而言 DevC++ 要比它们好很多。

12. 科技文献排版
流行盗版软件:MS Word
你应该使用的开源软件:LaTeX http://www.latex-project.org
中文 TeX 套装:CTeX http://www.ctex.org

如果我说 LaTex 是 MS Word 的替代软件,肯定有人会骂我:“有没有搞错?MS Word 能算科技文献排版软件?”所以对于那些习惯于用 word + Mathtype(哦,又是一个盗版软件!)写论文的同学,温习一下 LaTeX 的历史吧。你可以仔细看一下下载的 ieee, acm 论文,pdf 文件请看属性,有多少是用 word 的 adobe acrobat 插件生成的?又有多少是 TeX 相关程序或者 dvi, ghostscript 转化成的?当然了,中文的垃圾杂志就不要看了。如果是写中文文章,你可以更欢迎 CTeX 这一省去很多中文配置的套装,学了 LaTeX 你就会知道,什么叫做漂亮的科技文献排版!

13. 图片编辑软件
流行盗版软件:Photoshop
替代免费软件:Paint.net http://www.getpaint.net
替代开源软件:Gimp http://www.gimp.org

用 Linux 的人对 Gimp 不陌生,这是 Linux 下标准的图片编辑程序,这里指其 Windows 版本,Gimp 功能非常强大,但是编辑方式和习惯与 Photoshop 有些区别,可能需要稍微长点儿的学习周期。感谢 Cofyc 推荐 Paint.net,Paint.net 是一个更轻量级的图片编辑工具,功能比 Gimp 和 PS 都弱一点儿,但是编辑习惯和 PS 比较相似,用起来较为容易。有人会说,Photoshop 有这个,有那个,它们有吗?但是请问,你是专业的 PSer 吗?如果你是专业的 PSer,为什么不用苹果机再装个 Photoshop 进行处理呢?对于一般非专业人士的使用,我相信上面两款软件提供的功能足够你探索了。

14. 远程登录软件
流行盗版软件:SecureCRT
替代开源软件:Poderosa http://en.poderosa.org/

不喜欢 Putty 的黑色界面和难操作性?又不想使用盗版的 Secure CRT?这里像你隆重推荐终端登陆器 Poderosa,Poderosa 对 Telnet, SSH 各种登录方式均支持,还支持标签窗口,如果你电脑上还安装有 Cygwin,那么别再用那个黑黑的 Cygwin 启动窗口了,Poderosa 内建有对本地 Cygwin Shell 的支持,而且,Poderosa 支持插件扩展哦,你可以自己为它设置更多功能。

15. 统计计算软件
流行盗版软件:SAS
替代开源软件:R http://www.r-project.org/

大概学统计的学生都会很头痛使用什么软件做统计计算,SAS 那庞大的体积让人望而生畏,再加上用它的人不多,注册码和破解版也非常难找。其实你不必担心,R 软件在统计学领域的使用范围绝对要比 SAS 要广,因为不是每个教授都有钱买正版的 SAS 的,而且基于 R 开发的软件包也是多如牛毛,相信你一定能找到你需要的函数包,在这点上,SAS 要差一些。

16. 数学计算软件
流行盗版软件:Matlab, Mathematica
替代开源软件:GNU Octave+GNUPlot http://www.gnu.org/software/octave/ + http://www.gnuplot.info

虽然 Matlab 在数学计算领域非常流行,但是它的价钱也太贵了。这里我向您隆重推出 GNU Octave,一款开源数学计算软件,它兼容 Matlab 大部分语法,在它的发行版中您能找到很多 .m 文件,所以也许您写的 .m 文件都可以不加修改地在 Octave 上运行。如果需要画图功能,GNUPlot 会给您提供强大的画图工具支持。

17. 反汇编软件
流行盗版软件:Win32Dasm
替代免费软件:IDA Pro(旧版本) http://www.hex-rays.com/idapro/

其实 IDA Pro 更应该说是流行的盗版软件,但是 IDA Pro 的低版本是个人免费使用的,比如现在可以免费使用 IDA Pro 4.9 版,对于一般用户,旧的版本功能已经“非常非常非常”强大了。

18. 参考文献管理
流行盗版软件:Endnote
替代开源软件:JabRef http://jabref.sourceforge.net/

EndNote 是差不多 Windows 下文献管理的权威软件,某些学校可能会买下来这个软件给学生用。但是总有一些学校没钱,或者用户希望保持文献数据库在 Linux 下和 Windows 下的兼容性,那么 JabRef 可能是你最好的选择,因为 JabRef 使用 JAVA 语言编写,是跨平台的。尤其是对 bibtex 论文数据库的支持上,JabRef 已经相当完美了。

19. 影音播放
流行软件:暴风影音
替代开源软件:SMplayer http://smplayer.sourceforge.net/
替代免费软件:K-Lite Codec Pack http://www.codecguide.com/

其实暴风影音也是免费软件,但是暴风影音做的很多事情实在令人不敢恭维。因此我认为有必要推荐一下一些开源软件。SMplayer 是一个 mplayer 的前端,它将 mplayer 包装得更加用户友好,但是中文用户使用的话,有必要配置一下:字幕编码和文件缓存,否则在加载中文字幕或者高清电影时,会有一些问题。K-Lite Codec Pack 是著名的编解码库,很多播放器包括暴风影音、KMplayer 对媒体文件编解码都是使用的它。K-Lite Codec Pack 自带的 Media Player Classic 是经典的短小精悍型播放器,媒体加载速度极快,但是在控制功能上要少一些。

20. 刻录工具
流行盗版软件:Nero
替代免费软件:Nero Free Version http://www.nero.com/enu/downloads-nero9-free.php

本项内容是最奇怪的,盗版软件也是 Nero,免费软件也是 Nero。主要是因为 2009 年 Nero 推出了 Nero 9 的免费版本。我自己的刻录经验告诉我,很多看似很好的刻录软件都难以让人信任,很容易刻坏盘,而 Nero 是一款我足够信任的软件,它推出的免费版本包括了一般刻录时需要的基本功能,很值得使用。

本人的经验也就写到这里了,如果您有什么想补充的,您可以在本博客最底下的 CC 协议的基础上自由转载、修改或者添加内容。

雪和太阳

前天北京下了 2007 年冬天的第一场雪,不过意思意思就过去了,隔日又是阳光明媚。坐在教室中向外看去,忽然发现窗外的杨树叶子已经干净。印象中前次上课还是满满的绿叶呢,恍惚间觉得冬天才刚刚来到。

昨天上计算机网络课又睡了三个小时,而且梦中还在上课。结果老师说下课的时候,我迷茫机械地从教室走出,脚下直发软,觉得还在梦中,在一楼大厅里坐了一会儿才回味过来是真的下课了。我历来讨厌人多的教室,选的课程里就计算机网络人最多,所以这个也是听课听得最少的一门,也只有这一门课时最多,考得最晚,要到元月 23 号才考试。

圣诞节前会结束掉四门考试,隔一个月再考最后两门,所以南京之行又在计划中了 :-)。准备 26 号到南京,过了元旦假期吧,1 月 6 号左右回北京,多陪 xixi 几天。

现在在网上都不敢随便乱逛了,2G 的流量只够我和女友视频半个月,超过了还得交流量费,只好节省流量只聊天了。所以如果想限制自己上网的同学们,找个人视频吧!哦,那流量不限制的就算了~~~

今天才发现,原来 Ubuntu Linux 下使用摄像头都不用装驱动了,直接用 cheese 之类的视频管理软件打开就可以,真不错。那就意味着可以使用 skype 视频聊天了?有空试试。

在 Ubuntu 上部署 Snort 入侵检测系统

最后更新时间:2009年7月1日

摘要:

这份文档主要描述了我在 Ubuntu 7.10 上安装部署 Snort 入侵检测系统和 acid 基于 PHP 的网页入侵检测数据库分析控制台的过程。

目录

1. 介绍
2. 安装过程
3. 总结
4. 参考文章

1. 介绍

Snort 是一款非常优秀的开源主机入侵检测系统软件,可以用来对主机的网络状况进行记录、分析和报警,并且支持用户自定义规则库。Snort 在 Windows 平台和 Linux 平台上均可运行,详细介绍请访问 Snort 的官方网站:http://www.snort.org

Snort的默认记录是存放在 log 文本文件中,而为了观察监控方便起见,一般使用 acidbase 这个网页控制台来查看(好像 MySQL 的 phpmyadmin)。所以整个过程需要:安装 snort 和相应包;安装 LAMP(Linux, Apache, MySQL, PHP) 服务器;在MySQL数据库中建立好Snort数据库并配置 Snort 使其将 log 存放在 MySQL 数据库中;为基于 PHP 的入侵检测数据库分析控制台 (acidbase) 配置好数据库连接。

2. 安装过程

[安装LAMP,Snort和一些软件库]

由于 Ubuntu 是 Debian 系的 Linux,安装软件非常简单,而且 Ubuntu 在中国科技大学有镜像,在教育网和科技网下载速度非常快(2~6M/s),就省掉了出国下载安装包的麻烦,只需要一个命令即可在几十秒钟内安装好所有软件。这里使用 Ubuntu 默认命令行软件包管理器 apt 来进行安装。

$ sudo apt-get install libpcap0.8-dev libmysqlclient15-dev mysql-client-5.0 mysql-server-5.0 bison flex apache2 libapache2-mod-php5 php5-gd php5-mysql libphp-adodb php-pear pcregrep snort snort-rules-default

需要注意的是在安装 MySQL 数据库时会弹出设置 MySQL 根用户口令的界面,临时设置其为“test”。

[在 MySQL 数据库中为 Snort 建立数据库]

Ubuntu 软件仓库中有一个默认的软件包 snort-mysql 提供辅助功能,用软件包管理器下载安装这个软件包。

$ sudo apt-get install snort-mysql

安装好之后查看帮助文档:

$ less /usr/share/doc/snort-mysql/README-database.Debian

根据帮助文档中的指令,在 MySQL 中建立 Snort 的数据库用户和数据库。所使用的命令如下:

$ mysql –u root –p

在提示符处输入上面设置的口令 test

mysql> CREATE DATABASE snort;
mysql> grant CREATE, INSERT, SELECT, UPDATE on snort.* to snort@localhost;
mysql> grant CREATE, INSERT, SELECT, UPDATE on snort.* to snort;
mysql> SET PASSWORD FOR snort@localhost=PASSWORD('snort-db');
mysql> exit

以上命令的功能是在 MySQL 数据库中建立一个 snort 数据库,并建立一个 snort 用户来管理这个数据库,设置 snort 用户的口令为 snort-db。

然后根据 README-database.Debian 中的指示建立 snort 数据库的结构。

$ cd /usr/share/doc/snort-mysql
$ zcat create_mysql.gz | mysql -u snort -D snort -psnort-db

这样就为 snort 在 MySQL 中建立了数据库的结构,其中包括各个 snort 需要使用的表。

[设置 snort 把 log 文件输出到 MySQL 数据库中]

修改 Snort 的配置文件:/etc/snort/snort.conf

$ sudo vim /etc/snort/snort.conf

在配置文件中将 HOME_NET 有关项注释掉,然后将 HOME_NET 设置为本机 IP 所在网络,将 EXTERNAL_NET 相关项注释掉,设置其为非本机网络,如下所示:

#var HOME_NET any
var HOME_NET 192.168.0.0/16
#var EXTERNAL_NET any
var EXTERNAL_NET !$HOME_NET

将 output database 相关项注释掉,将日志输出设置到 MySQL 数据库中,如下所示:

output database: log, mysql, user=snort password=snort-db dbname=snort host=localhost
#output database: log, mysql

这样,snort 就不再向 /var/log/snort 目录下的文件写记录了,转而将记录存放在 MySQL 的snort数据库中。这时候可以测试一下 Snort 工作是否正常:

$ sudo snort -c /etc/snort/snort.conf

如果出现一个用 ASCII 字符画出的小猪,那么 Snort 工作就正常了,可以使用 Ctrl-C 退出;如果 Snort 异常退出,就需要查明以上配置的正确性了。

[测试 Web 服务器 Apache 和 PHP 是否工作正常]

配置 apache 的 php 模块,添加 msql 和 gd 的扩展。

$ sudo vim /etc/php5/apache2/php.ini
extension=msql.so
extension=gd.so

重新启动 apache

$ /etc/init.d/apache2 restart

在/var/www/目录下新建一个文本文件test.php

$ sudo vim /var/www/test.php

输入内容:

<?php
phpinfo();
?>

然后在浏览器中输入 http://localhost/test.php,如果配置正确的话,就会出现 PHP INFO 的经典界面,就标志着 LAMP 工作正常。

[安装和配置 acid-base]

安装 acid-base 很简单,使用 Ubuntu 软件包管理器下载安装即可:

$ sudo apt-get install acidbase

安装过程中需要输入 acidbase 选择使用的数据库,这里选 MySQL,根用户口令 test,和 acid-base 的口令(貌似也可以跳过不设置)。

将acidbase从安装目录中拷贝到www目录中,也可以直接在apache中建立一个虚拟目录指向安装目录,这里拷贝过来主要是为了安全性考虑。

sudo cp –R /usr/share/acidbase/ /var/www/

因为 acidbase 目录下的 base_conf.php 原本是一个符号链接指向 /etc/acidbase/ 下的base_conf.php,为了保证权限可控制,我们要删除这个链接并新建 base_conf.php 文件。

$ rm base_conf.php
$ touch base_conf.php

暂时将 /var/www/acidbase/ 目录权限改为所有人可写,主要是为了配置 acidbase 所用。

$ sudo chmod 757 acidbase/

现在就可以开始配置 acid-base 了,在浏览器地址栏中输入 http://localhost/acidbase,就会转入安装界面,然后就点击 continue 一步步地进行安装:

选择语言为 english,adodb 的路径为:/usr/share/php/adodb;选择数据库为 MySQL,数据库名为 snort,数据库主机为 localhost,数据库用户名为 snort 的口令为 snort-db;设置 acidbase 系统管理员用户名和口令,设置系统管理员用户名为 admin,口令为 test。然后一路继续下去,就能安装完成了。

安装完成后就可以进入登录界面,输入用户名和口令,进入 acidbase 系统。

这里需要将 acidbase 目录的权限改回去以确保安全性,然后在后台启动 snort,就表明 snort 入侵检测系统的安装完成并正常启动了:

$ sudo chmod 775 acidbase/
$ sudo snort -c /etc/snort/snort.conf -i eth0 –D

[检查入侵检测系统工作状况,更改入侵检测规则]

正常情况下在一个不安全的网络中,登录 acidbase 后一会儿就能发现网络攻击。如果没有发现网络攻击,可以添加更严格的规则使得正常的网络连接也可能被报攻击,以测试 Snort IDS 的工作正确性,比如在 /etc/snort/rules/web-misc.rules 的最后添加下面的话:

$ sudo vi /etc/snort/rules/web-misc.rules
alert tcp any :1024 -> $HTTP_SERVER 500:

这一行的意思是:对从任何地址小于 1024 端口向本机 500 以上端口发送的 tcp 数据包都报警。杀死 Snort 的后台进程并重新启动,就应该能检测到正常的包也被当作攻击了。

$ sudo kill `pgrep snort`
$ sudo snort –c /etc/snort/snort.conf –i eth0 -D

3. 总结

使用 Ubuntu 部署 Snort 入侵检测系统和网页控制台是相当容易的,因为 Ubuntu 提供了很方便的软件包安装功能,只是有时候定制性能太差,需要用户手动去寻找软件包的安装位置。

4. 参考文章
http://www.howtoforge.com/intrusion-detection-with-snort-mysql-apache2-on-ubuntu-7.10

使用最新 ALSA 驱动解决 Ubuntu Linux Intel 集成声卡问题

目前用户所抱怨的 Ubuntu 系列的声卡问题,基本上归结为几类:一,找不到声音设备;二,不发声;三,耳机和音箱同时发声;四、话筒没声。大部分这种问题都是由笔记本上 Intel 集成声卡驱动引起的,关于这个问题的具体描述和解决方案,可以查看下面两个页面:

BUG:
https://bugs.launchpad.net/ubuntu/+source/linux-source-2.6.22/+bug/131133

SOLUTION:
https://wiki.ubuntu.com/Gutsy_Intel_HD_Audio_Controller

其实大部分问题都可以通过自己动手编译安装最新 ALSA 驱动解决,解决方法上面两个链接中已经解释得很清楚了,我这里介绍一下我的思路:

第一,查看 ALSA 版本,如果最新,就不用重新安装了,仔细查看一下配置吧。

$ alsactl -v

如果打印出: alsactl version 1.0.20,那么 ALSA 已经是最新了。

第二,在 ALSA 官方网站 http://www.alsa-project.org 上,下载最新的 ALSA 驱动,怎么解压我就不说了吧。

$ wget ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.20.tar.bz2
$ wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.0.20.tar.bz2
$ wget ftp://ftp.alsa-project.org/pub/utils/alsa-utils-1.0.20.tar.bz2

第三,查看自己的内核版本和声卡解码芯片是否被支持。
查看支持的内核版本

$ less alsa-driver-1.0.15/SUPPORTED_KERNELS

查看自己声卡解码芯片(如果系统不能识别声卡,可能无法由下面两个查到,那么查看你电脑配置单吧)

$ tail -2 /proc/asound/oss/sndstat

$ head -1 /proc/asound/card0/codec#0

比如我的 DELL D630 就显示的是下面这个

Codec: SigmaTel STAC9205

在 alsa-driver-1.0.20/sound/Documentation/ALSA-Configuration.txt 中查找自己声卡解码芯片对应的 model 名字,比如我的 STAC9205 对应的就是:

STAC9205/9254
ref Reference board
dell-m42 Dell (unknown)
dell-m43 Dell Precision
dell-m44 Dell Inspiron

如果存在对应的 model,恭喜你可以继续安装了。

第四,准备好编译环境,安装 gcc, libc 等等工具,还需要下载 Linux header,这是编译 ALSA 驱动必须的。

$ sudo apt-get install build-essential linux-headers-2.x.xx-xx(你的内核版本)

第五,编译安装,一般的 ./configure, make, make install 流程。注意的是驱动编译时候需要 module 名字,就是第三步中找到的 modle 对应的 module 名字,去掉 snd-即可。比如我的 STAC9205 对应的 modle 是 dell-m44,对应的 module 是 snd-hda-intel,那么 configure 时候需要加上 --with-cards=hda-intel。编译安装时遇到问题请仔细阅读源码附带的 INSTALL。

$ cd alsa-driver-1.0.15
$ ./configure --with-cards=MODULE_NAME
$ make
$ sudo make install
$ cd ../alsa-lib-1.0.15
$ ./configure
$ make
$ sudo make install
$ cd ../alsa-utils-1.0.15
$ ./configure
$ make
$ sudo make install

第六,配置 ALSA。
如果以前系统中没有使用 ALSA 驱动,可能需要 alsaconf 来配置,还要将 ALSA 加入内核模块中,我没有经验;
如果系统原来已经有 ALSA 驱动,但是没有识别声卡或者声音驱动有问题。那么理论上只需要在两个地方做改动:

添加 /etc/modprobe.d/sound 文件,内容为:

alias snd-card-0 snd-MODULE_NAME
alias snd-slot-0 snd-MODULE_NAME

比如我的就是:

alias snd-card-0 snd-hda-intel
alias sound-slot-0 snd-hda-intel

在 /etc/modprobe.d/alsa-base 中最后一行添加:

options snd-MODULE_NAME model=MODEL_NAME

比如我的就是:

options snd-hda-intel model=dell-m44

第七,重新启动查看效果,可以使用 alsamixer 调节声音设置。

其它都好,话筒没声
如果您已经安装了 alsa 驱动,话筒没声的问题可以通过设置解决。命令行输入 alsamixer:
1. Digital 那列,设置为 Analog I。
2. 点击 tab 制表键,在第一个 Capture 上点空格,显示出来红色的 "Capture, LR"字样表示选中,并把音量设置为 0。
3. Digital 那列,将音量设置为 67,这样能减少杂音。

由于各种各样的问题,比如 ALSA 驱动重复安装,内核版本,内核模块添加不全等等,本文并不保证能完美解决声卡问题。本人不是声卡驱动专家,本文只是阐述我解决声卡问题的做法,如果您在本文指导下仍然不能解决声卡问题,请您到熟悉的论坛上寻求帮助。请尽量不要发邮件给我,谢谢。

Ubuntu 7.10 的一些小问题

这两天用 Ubuntu 7.10 和 Compiz-Fusion 遇到了几个小问题:

1. Firefox 相关

Ubuntu 自带的 Firefox 2.0.0.6 好像有很多问题,用起来总不舒服。很诡异的一点是,如果启用默认的 Ubuntu Extension for Firefox,无法打开 Windows Live Space 的博客页面。我起初还以为是 Feedburner 的统计图片无法显示造成的假死呢,但在卸载 Ubuntu Extension for Firefox 后一切正常,这就让人搞不明白了 Ubuntu team 做出来这个东西究竟是想干吗?

2. Nvidia 显卡相关

我使用的驱动是 Nvidia 官方最新的 for linux 驱动,单屏是没有任何问题的。但当使用双屏时,就是 TwinView,笔记本是 1280x800 的分辨率,外接 LCD 是 1280x1024 的分辨率,这时候笔记本屏幕会使用外接 LCD 的高度,造成的效果是在笔记本屏幕上看不到工具栏,因为工具栏在底部...所以只能用 Alt+Tab 切窗口。

2. Compiz-Fusion 相关

开启了 Compiz-Fusion 3D 特效之后,由于吸附作用,窗口的标题栏会不正常,经常会跑到桌面面板工具栏的后面,或者说不停的在尝试吸附,表现就是标题栏闪屏。

在使用双屏时,第一次桌面立方体旋转需要很长时间,像死机了一样,我经常先切到 console 再重新切回 X window,但第一次旋转以后的旋转就很正常了,和单屏没什么区别,就是两个独立的屏一起转。

另外,双屏还是很耗资源的,虽然我是 1.8G 双核 CPU, 1G 内存, 256M 显卡,在跑双屏时应用程序反应会稍微慢一点儿,不过并不很明显,还是在可以忍受的范围之内。感觉上在 Linux 上使用双屏没有在 Windows 上快,不过,嘿嘿,Windows 双屏只能算两个桌面,我的 Ubuntu 双屏可是 8 个桌面!!!

北京马拉松 2007 和 QQ

早上来上班的路上发现知春路上多了很多人,每个路口都有站得笔直的警察和军人,偶尔还有几个保安,沿路二三十米一个带着治安巡逻红袖标的老大妈。本来我还为自己闯红灯惴惴,但想了一下查交通违规用不着这么大场面吧!到公司后马上到很少访问的新浪网上看了看新闻,哦,原来知春路是 2007 年北京马拉松的经过路段。反正是首都,总有这样那样的事情,比其它城市热闹多了。

最近有些厌学情绪,我不知道是因为前几天熬夜太多了精神不好还是因为天气转凉要有个适应期。虽然没有学多少,但也没有闲着,我基本连静静思考问题的时间都没有。前两天还抽空装了一下 Ubuntu Linux,在很新的硬件上安装 Linux 果然是一种很痛苦的事情,我忽然怀念起自己原来台式机落后的 Gforce MX440 显卡了。虽然安装了 Ubuntu 7.10,不过我还没有怎么用,这几天回去以后还是 Windows 用的比较多。

虽然我喜欢给别人宣扬开源的理念,可我从来不是一个彻底的 Linux 用户,从未把 Windows 从自己电脑上删除(曾经考虑过,但没实施),因为 Windows 也有很多让我不得不用的理由:我的 Samsung 手机数据传输,只有 Win 驱动;更包括很多 Office 文件,超星文件的使用问题;在中国的环境下,Linux 还没有适合中国用户的好的音乐播放器,好的下载软件,还有,现在对我来说很重要的一点是:好的语音聊天软件。不要和我提 Skype,在中科院(科学网)加南京大学(教育网+代理服务器)的条件下,还没有任何语音聊天软件比 QQ 更稳定(QQ 也不是很稳定)。所以我的 QQ 签名档是:“如果我QQ在线,说明正在和女朋友聊天,请勿打扰。闲时请使用 Gtalk 和我联系。”这是说真的,我只有在和女朋友语音聊天时候才会上 QQ,我们文字聊天也是使用 Gtalk 的。所以求求您了别对我的 QQ 签名档感到很好奇专门为此点开和我聊几句,请勿打扰就是请勿打扰,除非您觉得和我的关系非同一般。

讲到 QQ,现在恐怕很多人都会想起“珊瑚虫”的案子,我对那个倒不是很在意,侵犯知识产权就是侵犯知识产权,不要扯上什么企业道德,总之侵权是犯法的,受害者就有权告,受害者告不告和要求怎样,和侵权人的行为性质没有关系。我想说的是最近发现 QQ 也在悄悄改变,包括:可以使用“自定义状态”,像 MSN 和 Gtalk 的状态一样,会覆盖原始签名档;可以把 QQ 号与一个 Email 帐户绑定,然后对别人而言在所有显示 QQ 号码的地方都变成了 Email;据说 QQ Hummer 蜂鸟计划中有 Linux 版,希望能提供语音聊天功能,那样我又能减去一条不得不用 Win 的原因。

最近我的脑子里面没有一点安安静静的想法,所以,生活记录到这里吧。

在 Dell Latitude D630 上安装 Ubuntu LInux 7.10

摘要

这份文档主要描述了我在自己的 Dell Latitude D630 上安装 7.10 的过程,Ubuntu 7.10 现在在我的笔记本电脑上运行正常(准确说基本功能工作正常,因为我未做太多测试)。

目录

1. 介绍
2. 安装
3. 总结
4. 计算机硬件信息
5. 参考文章

1. 介绍

我最近买了一台 Dell Latitude D630 笔记本电脑。除了原装的 Windows XP Home Edition, 我已经在这台笔记本上安装 RHEL5,然后又安装了 Ubuntu 7.10 Linux 操作系统,下面是我安装 Gutsy Gibbon(Ubuntu 7.10) 的过程介绍。

2. 安装

[ 安装基本操作系统 ]
# wget http://releases.ubuntu.com/7.10/ubuntu-7.10-alternate-i386.iso
# wget http://archive.ubuntu.com/ubuntu/dists/gutsy/main/installer-i386/current/images/hd-media/initrd.gz
# wget http://archive.ubuntu.com/ubuntu/dists/gutsy/main/installer-i386/current/images/hd-media/vmlinuz
# cp vmlinuz initrd.gz /media/sda5
# echo "title Gutsy Install Entry" >> /boot/grub/menu.lst
# echo " root (hd0,4)" >> /boot/grub/menu.lst
# echo "kernel /vmlinuz root=/dev/ram ramdisk_size=40000 devfs=mount,dall" >> /boot/grub/menu.lst
# echo "initrd /initrd.gz" >> /boot/grub/menu.lst
# reboot
以上下载地址可以换成任何 Ubuntu 镜像站点的下载地址。重新启动时在 Grub 的启动列表中选择 Gusty Install Entry,接下来就是标准的安装过程了。关于里面硬盘分区的位置问题(即 (hd0,4), sda5) 等这些名字,每个计算机有所不同,请根据自己计算机分区和硬盘情况相应修改。最简单的办法,把那两个文件放在某个分区根目录下,一个分区一个分区的试。

[ 修改更新源列表 ]
添加以下行到 /etc/apt/source.list 中:
deb http://ubuntu.cn99.com/ubuntu/ gutsy main restricted
deb http://ubuntu.cn99.com/ubuntu/ gutsy-updates main restricted
deb http://ubuntu.cn99.com/ubuntu/ gutsy universe
deb http://ubuntu.cn99.com/ubuntu/ gutsy-updates universe
deb http://ubuntu.cn99.com/ubuntu/ gutsy multiverse
deb http://ubuntu.cn99.com/ubuntu/ gutsy-updates multiverse
deb http://ubuntu.cn99.com/ubuntu/ gutsy-security multiverse
deb http://ubuntu.cn99.com/ubuntu/ gutsy-security main restricted
deb http://ubuntu.cn99.com/ubuntu/ gutsy-security universe

在其它行前加上注释符 #

[ 更新操作系统,安装工具软件包 ]
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install libc6 libc6-dev
$ sudo apt-get install gcc-4.2
$ sudo apt-get install g++-4.2
libc6 和 gcc 主要是为下面安装显卡驱动准备的。

[ 下载 nVidia 最新显卡驱动 ]
$ wget http://us.download.nvidia.com/XFree86/Linux-x86/100.14.09/NVIDIA-Linux-x86-100.14.09-pkg1.run

[ 修改 root 用户密码 ]
$ sudo passwd

[ 安装显卡驱动 ]
$ sudo reboot
重新启动时在 Grub 的启动列表中选择 Ubuntu 7.10, kernel 2.6.22-14-generic (recovery mode),输入 root 用户密码,运行 NVIDIA-Linux-x86-100.14.09-pkg1.run,按照指示进行。安装成功后重新启动系统,在系统启动前跳出的对话框里选择配置显示效果,选择 1280x800 分辨率的基本配置,并在下方的使用宽屏选项栏打上勾。

[ 解决 DVD±RW 驱动问题 ]
$ cp /etc/modules /etc/modules.bak
$ cp /etc/modules /tmp
$ echo piix >> /tmp/modules
$ sudo cp /tmp/modules /etc/modules
$ sudo reboot
这个我未曾尝试,因为还没有在 Linux 下刻录过光盘。

[ 解决声卡驱动问题 ]
因为 Dell Latitude D630 声卡版本比较新,用 Ubuntu 自带的 ALSA 驱动无法配置。解决办法是手工安装 ALSA 最新版本的 driver, lib 和 utils。详细步骤请参考:
http://blog.solrex.org/articles/solve-ubuntu-intel-soundcard-problem-with-latest-alsa-driver.html

[ 安装中文输入法 ]
首先在 System->Administration->Language Support 中选择安装中文支持,自动安装完中文支持以后:
$ sudo apt-get install scim-bridge-agent
$ sudo im-switch -s scim -z default
在 /etc/X11/xinit/xinput.d/scim 中找到以下两行并更改:
GTK_IM_MODULE=scim-bridge
QT_IM_MODULE=xim
如果 im-switch 不成功,可以手动将设置文件 copy 到 default 去
$ sudo cp /etc/X11/xinit/xinput.d/default /etc/X11/xinit/xinput.d/default.bak
$ sudo cp /etc/X11/xinit/xinput.d/scim /etc/X11/xinit/xinput.d/default
$ sudo reboot
如果重启后 Firefox 仍然不可以使用中文输入,可以在 Firefox 的启动脚本 /usr/lib/firefox/run-mozilla.sh 中最后 exit 前面加这么一句:
export GTK_IM_MODULE=xim

[ 安装 Windows 中文字体 ]
未在电脑上安装 Windows 操作系统者不需要此步骤,已安装者也可以选择跳过。我写了一个脚本做这个工作,可以下面链接下载。

$ wget http://share.solrex.org/scripts/install_win_CN_fonts_to_linux.sh
$ sudo sh install_win_CN_fonts_to_linux.sh
重新登录 X window

[ 安装 compiz-fusion 3D 桌面效果 ]
为了这一步的安装,你需要修改一下 xorg.conf 文件,请参考:http://compiz.org/NVidia。需要特别注意的是,如果选择了官方驱动,请不要安装任何 Ubuntu 软件仓库里的驱动。你可以从下面得到我的 xorg.conf 文件。
$ wget http://share.solrex.org/scripts/xorg.conf
$ sudo apt-get install compiz-fusion-plugins-* compizconfig-settings-manager
安装完成以后在 System->Preference->Appearance 中的 Visual Effects 中多出一项 Custom,选择以后,点右侧的 Preferences 配置效果。
注:最新版本的 Ubuntu 官方驱动已经支持 D630 的显卡,在选择 Custom选项时会自动安装驱动。安装完驱动后自动配置的 xorg.conf文件为:
http://share.solrex.org/scripts/xorg.conf.glx 

3. 总结

硬件信息 Linux 下状态 备注
Intel Core2 Duo T7100 @ 1.8 GHz 工作正常
14.1" WXGA TFT (1280x800) 工作正常
nVidia Corporation Quadro NVS 135M 工作正常 手工安装驱动程序
1 GB RAM (2 x 512 MB DDR2 533 MHz) 工作正常
120 GB HDD (Hitachi HTS722016K9A300) 工作正常
DVD±RW (PBDS DVD±RW DS-8W1P) 未验证 据说可以在 /etc/modules 增加 piix 解决.
Broadcom NetXtreme BCM5755M Gigabit Ethernet 工作正常
Broadcom Corporation BCM94311MCG wlan mini-PCI 未验证
Intel 82801H sound card (ICH8) 工作正常 Ubuntu 7.10 参见上面讲的处理方法.
Memory card reader 未验证
Internal 56k modem 未验证

4. 计算机信息

[ 所有 pci 设备 ]
$ lspci
00:00.0 Host bridge: Intel Corporation Mobile PM965/GM965/GL960 Memory Controller Hub (rev 0c)
00:01.0 PCI bridge: Intel Corporation Mobile PM965/GM965/GL960 PCI Express Root Port (rev 0c)
00:1a.0 USB Controller: Intel Corporation 82801H (ICH8 Family) USB UHCI Contoller #4 (rev 02)
00:1a.1 USB Controller: Intel Corporation 82801H (ICH8 Family) USB UHCI Controller #5 (rev 02)
00:1a.7 USB Controller: Intel Corporation 82801H (ICH8 Family) USB2 EHCI Controller #2 (rev 02)
00:1b.0 Audio device: Intel Corporation 82801H (ICH8 Family) HD Audio Controller (rev 02)
00:1c.0 PCI bridge: Intel Corporation 82801H (ICH8 Family) PCI Express Port 1 (rev 02)
00:1c.1 PCI bridge: Intel Corporation 82801H (ICH8 Family) PCI Express Port 2 (rev 02)
00:1c.5 PCI bridge: Intel Corporation 82801H (ICH8 Family) PCI Express Port 6 (rev 02)
00:1d.0 USB Controller: Intel Corporation 82801H (ICH8 Family) USB UHCI Controller #1 (rev 02)
00:1d.1 USB Controller: Intel Corporation 82801H (ICH8 Family) USB UHCI Controller #2 (rev 02)
00:1d.2 USB Controller: Intel Corporation 82801H (ICH8 Family) USB UHCI Controller #3 (rev 02)
00:1d.7 USB Controller: Intel Corporation 82801H (ICH8 Family) USB2 EHCI Controller #1 (rev 02)
00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev f2)
00:1f.0 ISA bridge: Intel Corporation 82801HEM (ICH8M) LPC Interface Controller (rev 02)
00:1f.1 IDE interface: Intel Corporation 82801HBM/HEM (ICH8M/ICH8M-E) IDE Controller (rev 02)
00:1f.2 IDE interface: Intel Corporation 82801HBM/HEM (ICH8M/ICH8M-E) SATA IDE Controller (rev 02)
00:1f.3 SMBus: Intel Corporation 82801H (ICH8 Family) SMBus Controller (rev 02)
01:00.0 VGA compatible controller: nVidia Corporation Quadro NVS 135M (rev a1)
03:01.0 CardBus bridge: O2 Micro, Inc. Cardbus bridge (rev 21)
03:01.4 FireWire (IEEE 1394): O2 Micro, Inc. Firewire (IEEE 1394) (rev 02)
09:00.0 Ethernet controller: Broadcom Corporation NetXtreme BCM5755M Gigabit Ethernet PCI Express (rev 02)
0c:00.0 Network controller: Broadcom Corporation BCM94311MCG wlan mini-PCI (rev 01)

以下省略了,只是写一下查看这些信息的命令
[ 电池信息 ]
$ less /proc/acpi/battery/BAT0/info
[ CPU 信息 ]
$ less /proc/cpuinfo
[ 内存信息 ]
$ less /proc/meminfo
[ 内核消息 ]
$ dmesg
[ 加载的内核模块 ]
$ lsmod
[ X.org 设置文件(显示及输入设置)]
$ less /etc/X11/xorg.conf

5. 参考文章

* HARDWARE Dell Latitude D630
* My Ubuntu Installation
* Ubuntu on my Dell Latitude D630

在双系统环境中使用Windows字体美化Linux中文字体

安装完 Linux 操作系统的中文支持后,看到那些难看的字体是不是很不爽,下面这个脚本就帮助你使用 Windows 字体来自动美化 Linux 中文字体。

下面的脚本使用 windows 自带字体美化 Linux 字体,主要是将软件中文字体使用宋体,终端使用黑体替换。脚本源程序可以在 http://share.solrex.org/scripts/install_win_CN_fonts_to_linux.sh 下载到。

首先确保 windows 系统所在分区被挂载到 /media 目录下将以下内容保存为脚本文件:install_win_CN_fonts_to_linux.sh

#!/bin/bash
check_answer()
{
    case $1 in
        y|Y|Yes|yes|YES)
            return 0;
            ;;
        n|N|No|no|NO)
            return 1;
            ;;
        *) echo "Answer either yes or no, default is yes"
            return 2
            ;;
    esac
}

LIN_FONTS_DIR=/usr/share/fonts/zh_CN/TrueType
LANG_SEL_FILE=/etc/fonts/language-selector.conf
SEARCH_DIR=/media

echo -n "This script is for installing Windows Chinese fonts to Linux on a dual-OS-installed computer. And the writer doesn't promise it will work on your system. Do you want to continue?(y/n)[y]:"
read ANS_STR
: ${ANS_STR:="y"}
check_answer $ANS_STR
ANS=$?
if [ $ANS -ne 0 ]; then
    echo "Exit."
    exit
fi

echo -n "There might be license problems, i.e. you have no permission to use Windows fonts in other program except Windows OS. Do you want to continue?(y/n)[y]:"
read ANS_STR
: ${ANS_STR:="y"}
check_answer $ANS_STR
ANS=$?
if [ $ANS -ne 0 ]; then
    echo "Exit."
    exit
fi

echo -n "You run this script with root account or "sudo" command?(y/n)[y]:"
read ANS_STR
: ${ANS_STR:="y"}
check_answer $ANS_STR
ANS=$?
if [ $ANS -ne 0 ]; then
    echo "Exit."
    exit
fi

echo -n "You mounted Windows System partation "C:\" to /media directory?(y/n)[y]:"
read ANS_STR
: ${ANS_STR:="y"}
check_answer $ANS_STR
ANS=$?
if [ $ANS -ne 0 ]; then
    echo "Exit."
    exit
fi

echo "Searching for windows's fonts directory..."
echo "That may take a few minutes, please wait."
WIN_FONTS_DIR=`echo $(find $SEARCH_DIR -name simsun.ttc -type f) | sed s/simsun.ttc//g`
if [ -d $WIN_FONTS_DIR ]; then
    while true; do
        echo "Windows fonts dir found! Your windows fonts directory is: "
        echo -n ""$WIN_FONTS_DIR" Is it right?(y/n)[y]:"
        read ANS_STR
        : ${ANS_STR:="y"}
        check_answer $ANS_STR
        ANS=$?
        if [ $ANS -eq 0 ]; then
            break
        elif [ $ANS -eq 1 ]; then 
            echo "Please enter path to your windows fonts dir?"
            read ANS_STR
            if [ -d $ANS_STR ]; then
                WIN_FONTS_DIR=$ANS_STR
                break
            else
                echo "Your input is not a dir. Exit."
                exit
            fi
        else
            echo "Unrecognized input."
        fi
    done
else
    echo "Can not find windows fonts directory. "
    echo -n "Please enter path to your windows fonts dir:"
    read ANS_STR
    if [ -d $ANS_STR ]; then
        WIN_FONTS_DIR=$ANS_STR
    else
        echo "Your input is not a dir. Exit."
        exit
    fi
fi

echo "Entering $WIN_FONTS_DIR"
cd $WIN_FONTS_DIR

echo "Creating Linux fonts directory: $LIN_FONTS_DIR..."
mkdir -p $LIN_FONTS_DIR
if [ $? -ne 0 ]; then
    echo "Creating Linux fonts dir failed. Exit."
    exit
fi

echo "Copying windows fonts to linux fonts dir... sim*|tahoma|tohomabd.ttf"
cp sim* SimSun18030.ttc tahoma.ttf tahomabd.ttf $LIN_FONTS_DIR
if [ $? -ne 0 ]; then
    echo "Copying windows fonts to linux fonts dir failed. Exit."
    exit
fi

echo "Changing font files' access permissions.."
chmod 644 $LIN_FONTS_DIR/*

echo "Entering $LIN_FONTS_DIR"
cd $LIN_FONTS_DIR

echo "Creating an index of scalable font files in $LIN_FONTS_DIR for X..."
mkfontscale
if [ $? -ne 0 ]; then
    echo "Creating an index of scalable font files for X failed. Exit."
    exit
fi

echo "Creating an index of X font files in $LIN_FONTS_DIR"
mkfontdir
if [ $? -ne 0 ]; then
    echo "Creating an index of X font files in $LIN_FONTS_DIR failed. Exit."
    exit
fi

echo "Creating an index of FreeType font files in $LIN_FONTS_DIR"
fc-cache $LIN_FONTS_DIR
if [ $? -ne 0 ]; then
    echo "Creating an index of FreeType font files in $LIN_FONTS_DIR failed. Exit."
    exit
fi

echo "Backing up $LANG_SEL_FILE"
if [ -f $LANG_SEL_FILE ]; then
    cp $LANG_SEL_FILE $LANG_SEL_FILE.bak
fi

echo "Generating new $LANG_SEL_FILE... for language select"
echo '
<fontconfig>
  <alias>
    <family>serif</family>
      <prefer>
        <family>Bitstream Vera Serif</family>
        <family>SimSun</family>
        <family>DejaVu Serif</family>
        <family>AR PL ShanHeiSun Uni</family>
        <family>AR PL ZenKai Uni</family>
      </prefer>
  </alias>
  <alias>
    <family>sans-serif</family>
      <prefer>
        <family>Bitstream Vera Sans</family>
        <family>SimSun</family>
        <family>DejaVu Sans</family>
        <family>AR PL ShanHeiSun Uni</family>
        <family>AR PL ZenKai Uni</family>
      </prefer>
  </alias>
  <alias>
    <family>monospace</family>
      <prefer>
        <family>DejaVu Sans Mono</family>
        <family>Bitstream Vera Sans Mono</family>
        <family>SimHei</family>
    </prefer>
  </alias>

<match target="font" >
  <test name="family" compare="contains" >
    <string>Song</string>
    <string>Sun</string>
    <string>Kai</string>
    <string>Ming</string>
  </test>
  <test compare="more_eq" target="pattern" name="weight" >
    <int>180</int>
  </test>
  <edit mode="assign" name="embolden" >
    <bool>true</bool>
  </edit>
</match>

<match target="font" >
  <test name="family" compare="contains" >
    <string>Song</string>
    <string>Sun</string>
    <string>Kai</string>
    <string>Ming</string>
  </test>
  <edit name="globaladvance">
    <bool>false</bool>
  </edit>
  <edit name="spacing">
    <int>0</int>
  </edit>
  <edit name="hinting">
    <bool>true</bool>
  </edit>
  <edit name="autohint">
    <bool>false</bool>
  </edit>
  <edit name="antialias" mode="assign">
    <bool>true</bool>
  </edit>
  <test name="pixelsize" compare="less_eq">
    <int>18</int>
  </test>
  <edit name="antialias" mode="assign" >
    <bool>false</bool>
  </edit>
</match>

<match target="pattern">
  <test name="family">
    <string>SimSun</string>
    <string>SimSun-18030</string>
    <string>AR PL ShanHeiSun Uni</string>
    <string>AR PL New Sung</string>
    <string>MingLiU</string>
    <string>PMingLiU</string>
  </test>
  <edit binding="strong" mode="prepend" name="family">
    <string>Tahoma</string>
    <string>Verdana</string>
  </edit>
</match>

<match target="pattern">
  <test name="family">
    <string>宋体</string>
  </test>
  <edit name="family" mode="assign">
    <string>SimSun</string>
  </edit>
</match>
<match target="pattern">
  <test name="family">
    <string>新宋体</string>
  </test>
  <edit name="family" mode="assign">
    <string>SimSun</string>
  </edit>
</match>
<match target="pattern">
  <test name="family">
    <string>仿宋_GB2312</string>
  </test>
  <edit name="family" mode="assign">
    <string>FangSong_GB2312</string>
  </edit>
</match>
<match target="pattern">
  <test name="family">
    <string>楷体_GB2312</string>
  </test>
  <edit name="family" mode="assign">
    <string>KaiTi_GB2312</string>
  </edit>
</match>
<match target="pattern">
  <test name="family">
    <string>黑体</string>
  </test>
  <edit name="family" mode="assign">
    <string>SimHei</string>
  </edit>
</match>
</fontconfig> ' > $LANG_SEL_FILE
echo "Finished. Please quit your X session and relogin."
echo "If you have problems on start X(sometimes happened on Ubuntu 7.04.), please delete $LIN_FONTS_DIR and copy the backuped $LANG_SEL_FILE.bak to $LANG_SEL_FILE. Then do "fc-cache" with no options. That may work."

执行:
sudo sh ./install_win_CN_fonts_to_linux.sh

登出 Xwindow,再重新登录即可。

Hacking 《自己动手写操作系统》Chapter 4

——Writing x86 PC Bootloader With Free Software 2

本文内容已被整理为一本电子书,请到这里下载

强烈建议随《自己动手写操作系统》这本书读本篇文章,基础知识:x86 汇编指令,AT&T 汇编格式,FAT12 文件系统格式。

由于《自己动手写操作系统》第三章讲的是保护模式,示例代码全是 DOS 可执行代码,和直接的 Bootloader 没有太大关系,所以这次继续下来从第四章开始。

前两章中写的 Bootloader 其实严格意义上只算是一个 Boot sector(扇区),因为它只是简单地打印了一个字符。第四章的 Bootloader 实现了从软盘中读写一个文件并加载运行的功能。

总共有以下几个文件:boot.S loader.S solrex_x86_boot.ld solrex_x86_dos.ld Makefile

下面解释每个文件的作用:
boot.S:
它生成的 .bin 文件就是软盘镜像的第一个扇区。由上一篇文章提供的 boot.S 只有启动功能,生成的软盘镜像文件只能作为启动器而不能作为可读写软盘来使用,因为在那个镜像里面没有将软盘格式化。其实格式化说白了就是在软盘的第一个扇区添加一些头信息,这样系统就知道如何处理这张软盘,在 boot.S 中的 Floppy header 就是做这个工作的。而在软盘的前 3 个 byte 是一句跳转指令,这样在启动时候系统会跳过 floppy header,也解决了如何启动的问题。这样这张软盘既可以作为启动盘,又可以作为文件盘使用了。因为启动扇区只有 512 个字节,不可能依赖它作为启动功能,这样我们可以将另一个程序 LOADER.BIN 放入软盘,启动后,从软盘读取它进入内存并执行。

所以 boot.S 代码的主要内容是,首先将软盘主目录复制到内存中,并在其中搜索 LOADER.BIN,搜索到以后,将 LOADER.BIN 加载入内存并执行。

loader.S:
就是一个普通的打印程序,在屏幕第一行中间打印一个 'L'

solrex_x86_boot.ld:
boot.S 的连接脚本,将 boot.S 代码段连接到 0x7c00 的位置(x86 PC 操作系统启动的位置)。

solrex_x86_dos.ld:
loader.S 的连接脚本,将 loader.S 代码段连接到 0x1000 的位置,和普通 DOS 程序一样。

Makefile:
不说了,大家都知道。

这个 hacking 系列我准备继续写下去,示例代码可以从我的个人主页 http://solrex.org 打包下载,包括《自己动手写操作系统》的部分源代码,以 .asm 为后缀。对 Intel(MASM,NASM,TASM) 和 AT&T(GAS)汇编语言语法在 x86 平台的实现如何转换感兴趣的朋友,可以对比阅读 .asm 和 .S 文件。

[solrex@NJU-CAS Solrex]$ more boot.S loader.S solrex_x86_boot.ld solrex_x86_dos.ld Makefile
::::::::::::::
boot.S
::::::::::::::
.code16
.set BaseOfStack, 0x7c00 /* Stack base address, inner */
.set BaseOfLoader, 0x9000 /* Section loading address of LOADER.BIN */
.set OffsetOfLoader, 0x0100 /* Loading offset of LOADER.BIN */
.set RootDirSectors, 14 /* Root directory sector count */
.set SecNoOfRootDir, 19 /* 1st sector of root directory */
.set SecNoOfFAT1, 1 /* 1st sector of FAT1 */
.set DeltaSecNo, 17 /* BPB_(RsvdSecCnt+NumFATs*FATSz) -2 */
/* Start sector of file space =*/
.text
/* Floppy header of FAT12 */
jmp LABEL_START /* Start to boot. */
nop /* nop required */
BS_OEMName: .ascii "WB. YANG" /* OEM String, 8 bytes required */
BPB_BytsPerSec: .2byte 512 /* Bytes per sector */
BPB_SecPerCluse: .byte 1 /* Sector per cluse */
BPB_ResvdSecCnt: .2byte 1 /* Reserved sector count */
BPB_NumFATs: .byte 2 /* Number of FATs */
BPB_RootEntCnt: .2byte 224 /* Root entries count */
BPB_TotSec16: .2byte 2880 /* Total sector number */
BPB_Media: .byte 0xf0 /* Media descriptor */
BPB_FATSz16: .2byte 9 /* FAT size(sectors) */
BPB_SecPerTrk: .2byte 18 /* Sector per track */
BPB_NumHeads: .2byte 2 /* Number of magnetic heads */
BPB_HiddSec: .4byte 0 /* Number of hidden sectors */
BPB_TotSec32: .4byte 0 /* If TotSec16 equal 0, this works */
BS_DrvNum: .byte 0 /* Driver number of interrupt 13 */
BS_Reserved1: .byte 0 /* Reserved */
BS_BootSig: .byte 0x29 /* Boot signal */
BS_VolID: .4byte 0 /* Volume ID */
BS_VolLab: .ascii "Solrex 0.01" /* Volume label, 11 bytes required */
BS_FileSysType: .ascii "FAT12 " /* File system type, 8 bytes required */

/* Initial registers. */
LABEL_START:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $BaseOfStack, %sp

/* Clear screen */
mov $0x0600,%ax /* %ah=6, %al=0 */
mov $0x0700,%bx /* Black white */
mov $0,%cx /* Top left: (0,0) */
mov $0x184f,%dx /* Bottom right: (80,50) */
int $0x10 /* BIOS int 10h, ah=6: Initialize screen */

/* Display "Booting**" */
mov $0,%dh
call DispStr /* Display string(index 0)*/

/* Reset floppy */
xor %ah,%ah
xor %dl,%dl /* %dl=0: floppy driver 0 */
int $0x13 /* BIOS int 13h, ah=0: Reset driver 0 */

/* Find LOADER.BIN in root directory of driver 0 */
movw $SecNoOfRootDir, (wSectorNo)

/* Read root dir sector to memory */
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmpw $0,(wRootDirSizeForLoop) /* If searching in root dir */
jz LABEL_NO_LOADERBIN /* can find LOADER.BIN ? */
decw (wRootDirSizeForLoop)
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader*/
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov (wSectorNo),%ax /* %ax <- sector number in root */
mov $1,%cl
call ReadSector
mov $LoaderFileName,%si /* %ds:%si -> LOADER BIN */
mov $OffsetOfLoader,%di /* BaseOfLoader<<4+100*/
cld
mov $0x10,%dx

/* Search for "LOADER BIN", FAT12 save file name in 12 bytes, 8 bytes for
file name, 3 bytes for suffix, last 1 bytes for '20'. If file name is
less than 8 bytes, filled with '20'. So "LOADER.BIN" is saved as:
"LOADER BIN"(4f4c 4441 5245 2020 4942 204e).
*/
LABEL_SEARCH_FOR_LOADERBIN:
cmp $0,%dx /* Read control */
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec %dx
mov $11,%cx

LABEL_CMP_FILENAME:
cmp $0,%cx
jz LABEL_FILENAME_FOUND /* If 11 chars are all identical? */
dec %cx
lodsb /* %ds:(%si) -> %al*/
cmp %es:(%di),%al
jz LABEL_GO_ON
jmp LABEL_DIFFERENT /* Different */

LABEL_GO_ON:
inc %di
jmp LABEL_CMP_FILENAME /* Go on loop */

LABEL_DIFFERENT:
and $0xffe0,%di /* Go to head of this entry */
add $0x20,%di
mov $LoaderFileName,%si /* Next entry */
jmp LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
addw $1,(wSectorNo)
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

/* Not found LOADER.BIN in root dir. */
LABEL_NO_LOADERBIN:
mov $2,%dh
call DispStr /* Display string(index 2) */
jmp . /* Infinite loop */

/* Found. */
LABEL_FILENAME_FOUND:
mov $RootDirSectors,%ax
and $0xffe0,%di /* Start of current entry, 32 bytes per entry */
add $0x1a,%di /* First sector of this file */
mov %es:(%di),%cx
push %cx /* Save index of this sector in FAT */
add %ax,%cx
add $DeltaSecNo,%cx /* LOADER.BIN's start sector saved in %cl */
mov $BaseOfLoader,%ax
mov %ax,%es /* %es <- BaseOfLoader */
mov $OffsetOfLoader,%bx /* %bx <- OffsetOfLoader */
mov %cx,%ax /* %ax <- Sector number */

/* Load LOADER.BIN's sector's to memory. */
LABEL_GOON_LOADING_FILE:
push %ax
push %bx
mov $0x0e,%ah
mov $'.',%al /* Char to print */
mov $0x0f,%bl /* Front color: white */
int $0x10 /* BIOS int 10h, ah=0xe: Print char */
pop %bx
pop %ax

mov $1,%cl
call ReadSector
pop %ax /* Got index of this sector in FAT */
call GetFATEntry
cmp $0x0fff,%ax
jz LABEL_FILE_LOADED
push %ax /* Save index of this sector in FAT */
mov $RootDirSectors,%dx
add %dx,%ax
add $DeltaSecNo,%ax
add (BPB_BytsPerSec),%bx
jmp LABEL_GOON_LOADING_FILE

LABEL_FILE_LOADED:
mov $1,%dh
call DispStr /* Display string(index 1) */

/*******************************************************************
Jump to LOADER.BIN's start address in memory.
*/
jmp $BaseOfLoader,$OffsetOfLoader
/*******************************************************************/

/* ==================================================================
Variable table
*/
wRootDirSizeForLoop: .2byte RootDirSectors
wSectorNo: .2byte 0 /* Sector number to read */
bOdd: .byte 0 /* odd or even? */

/* ==================================================================
String table
*/
LoaderFileName: .asciz "LOADER BIN" /* File name */
.set MessageLength,9
BootMessage: .ascii "Booting**" /* index 0 */
Message1: .ascii "Loaded in" /* index 1 */
Message2: .ascii "No LOADER" /* index 2 */

/* ==================================================================
Routine: DispStr
Action: Display a string, string index stored in %dh
*/
DispStr:
mov $MessageLength, %ax
mul %dh
add $BootMessage,%ax
mov %ax,%bp /* String address */
mov %ds,%ax
mov %ax,%es
mov $MessageLength,%cx /* String length */
mov $0x1301,%ax /* ah = 0x13, al = 0x01(W) */
mov $0x07,%bx /* PageNum 0(bh = 0), bw(bl= 0x07)*/
mov $0,%dl /* Start row and column */
int $0x10 /* BIOS INT 10h, display string */
ret

/* ==================================================================
Routine: ReadSector
Action: Read %cl Sectors From %ax sector(floppy) to %es:%bx(memory)
Assume sector number is 'x', then:
x/(BPB_SecPerTrk) = y,
x%(BPB_SecPerTrk) = z.
The remainder 'z' PLUS 1 is the start sector number;
The quotient 'y' devide by BPB_NumHeads(RIGHT SHIFT 1 bit)is cylinder
number;
AND 'y' by 1 can got magnetic header.
*/
ReadSector:
push %ebp
mov %esp,%ebp
sub $2,%esp /* Reserve space for saving %cl */
mov %cl,-2(%ebp)
push %bx /* Save bx */
mov (BPB_SecPerTrk), %bl /* %bl: the devider */
div %bl /* 'y' in %al, 'z' in %ah */
inc %ah /* z++, got start sector */
mov %ah,%cl /* %cl <- start sector number */
mov %al,%dh /* %dh <- 'y' */
shr $1,%al /* 'y'/BPB_NumHeads */
mov %al,%ch /* %ch <- Cylinder number(y>>1) */
and $1,%dh /* %dh <- Magnetic header(y&1) */
pop %bx /* Restore %bx */
/* Now, we got cylinder number in %ch, start sector number in %cl, magnetic
header in %dh. */
mov (BS_DrvNum), %dl
GoOnReading:
mov $2,%ah
mov -2(%ebp),%al /* Read %al sectors */
int $0x13
jc GoOnReading /* If CF set 1, mean read error, reread. */
add $2,%esp
pop %ebp
ret

/* ==================================================================
Routine: GetFATEntry
Action: Find %ax sector's index in FAT, save result in %ax
*/
GetFATEntry:
push %es
push %bx
push %ax
mov $BaseOfLoader,%ax
sub $0x0100,%ax
mov %ax,%es /* Left 4K bytes for FAT */
pop %ax
movb $0,(bOdd)
mov $3,%bx
mul %bx /* %dx:%ax = %ax*3 */
mov $2,%bx
div %bx /* %dx:%ax/2 */
cmp $0,%bx /* remainder %dx = 0 ? */
jz LABEL_EVEN
movb $1,(bOdd)

LABEL_EVEN:
xor %dx,%dx /* Now %ax is the offset of FATEntry in FAT */
mov (BPB_BytsPerSec),%bx
div %bx /* %dx:%ax/BPB_BytsPerSec */
push %dx
mov $0,%bx
add $SecNoOfFAT1,%ax /* %ax <- FATEntry's sector */
mov $2,%cl /* Read 2 sectors in 1 time, because FATEntry */
call ReadSector /* may be in 2 sectors. */
pop %dx
add %dx,%bx
mov %es:(%bx),%ax
cmpb $1,(bOdd)
jnz LABEL_EVEN_2
shr $4,%ax

LABEL_EVEN_2:
and $0x0fff,%ax

LABEL_GET_FAT_ENTRY_OK:
pop %bx
pop %es
ret

.org 510 /* Skip to address 0x510. */
.2byte 0xaa55 /* Write boot flag to 1st sector(512 bytes) end */
::::::::::::::
loader.S
::::::::::::::
.code16
.text
mov $0xb800,%ax
movw %ax,%gs
mov $0xf,%ah
mov $'L',%al
mov %ax,%gs:((80*0+39)*2)
jmp .
::::::::::::::
solrex_x86_boot.ld
::::::::::::::
SECTIONS
{
. = 0x7c00;
.text :
{
_ftext = .;
} = 0
}
::::::::::::::
solrex_x86_dos.ld
::::::::::::::
SECTIONS
{
. = 0x0100;
.text :
{
_ftext = .;
} = 0
}
::::::::::::::
Makefile
::::::::::::::
CC=gcc
LD=ld
OBJCOPY=objcopy

CFLAGS=-c
TRIM_FLAGS=-R .pdr -R .comment -R.note -S -O binary

LDFILE_BOOT=solrex_x86_boot.ld
LDFILE_DOS=solrex_x86_dos.ld
LDFLAGS_BOOT=-e c -T$(LDFILE_BOOT)
LDFLAGS_DOS=-e c -T$(LDFILE_DOS)

all: boot.img LOADER.BIN

boot.bin: boot.S
$(CC) $(CFLAGS) boot.S
$(LD) boot.o -o boot.elf $(LDFLAGS_BOOT)
$(OBJCOPY) $(TRIM_FLAGS) boot.elf $@

LOADER.BIN: loader.S
$(CC) $(CFLAGS) loader.S
$(LD) loader.o -o loader.elf $(LDFLAGS_DOS)
$(OBJCOPY) $(TRIM_FLAGS) loader.elf $@

boot.img: boot.bin
dd if=/dev/zero of=emptydisk.img bs=512 count=2880
dd if=boot.bin of=boot.img bs=512 count=1
dd if=emptydisk.img of=boot.img skip=1 seek=1 bs=512 count=2879
rm emptydisk.img

# You must have the authority to do mount, or you must use "su root" or
# "sudo" command to do "make copy"
copy: boot.img LOADER.BIN
mkdir -p /tmp/floppy;
mount -o loop boot.img /tmp/floppy/ -o fat=12;
cp LOADER.BIN /tmp/floppy/;
umount /tmp/floppy/;
rm -rf /tmp/floppy/;

clean:
@rm -f *.o *.elf *.bin *.BIN

distclean:
@rm -f *.img

在 Linux 上制作数据软盘或者 CD 镜像

当在 Linux 下和虚拟机共享文件时,我发现一个问题,并不是所有的虚拟机都支持(或很好的支持)在主机和虚拟机操作系统之间共享文件,这样在需要文件共享时候就会遇到很多问题。不过因为几乎所有操作系统都支持读写软盘和光盘,就可以使用一种比较迂回的办法解决这个问题:建立软盘或者光盘镜像文件,虚拟机使用读写软/光驱的方式打开它,Linux 主机直接挂载它,这样就可以将这个文件作为共享目录使用。不过当虚拟机的操作系统足够强大的时候,完全可以使用虚拟网络传输。

这样就涉及到如何建立一个数据软/光盘镜像。我们平常所挂载的光盘镜像,基本是用于只读的目的,而且是别人建立好的。如何使用 Linux 命令来建立一个数据软/光盘镜像呢?

建立光盘镜像很简单,使用下面命令即可:

genisoimage -o data.iso /data/yourdir/*

这就能直接将 /data/yourdir/ 下的所有文件建立成一个光盘镜像。但是使用光盘镜像唯一一个不足就是,它是只读的,所以文件的共享只能从一个方向进行(如果虚拟机上的系统不支持烧录CD的话 ^_^)。

建立软盘镜像:

前面在 Writing x86 PC Bootloader With Free Software 一文中提到了如何制作启动软盘镜像,但是这个软盘镜像仅仅能用来启动电脑,而不能用来存储数据。因为它没有被格式化,所以不能挂载,因此最主要的任务是要将它格式化。

首先,生成空白软盘镜像:
dd if=/dev/zero of=data.img bs=512 count=2880

使用 losetup 命令,将 data.img 作为 loop device 使用:
sudo losetup /dev/loop0 data.img

然后,格式化这个 loop device:
sudo mkfs.msdos /dev/loop0

检查文件系统:
sudo fsck.msdos /dev/loop0

删除 loop device:
sudo losetup -d /dev/loop0

这时候,data.img 已经格式化完成,可以作为一个软盘镜像使用,比如用 sudo mount -o loop data.img mountdir/ 挂载到 mountdir 上。设置一定的权限之后,使用虚拟机打开这个软盘镜像,就可以把这个镜像当作共享目录来使用,虽然很可怜的是这个文件只有 1.44 M 大小,但在某些时候已经能满足需要了。

Fedora 和 Ubuntu 上的段错误

LilyBBS discussion: Segmentation Fault on Fedora and Ubuntu

昨天和别人在小百合 LinuxUnix 版发帖讨论 Segmentation Fault 的问题,整理如下:

flyDutchMan 根据自己存在段错误的程序在 Fedora 和 Ubuntu 上的运行结果,认为 Fedora 和 Ubuntu 对段错误的处理方式不同,他的观点是(原文链接:[href: http://bbs.nju.edu.cn/vd83468/bbscon?board=LinuxUnix&file=M.1185514732.A&num=6528 ] ):

“Ubuntu认为段错误是很严重的错误,它的做法是当即中断程序。而Fedora对待段错误是比较宽容的,在Fedora中即使检测到某个进程正在对不属于它的地址空间进行操作,他仍然会完成这次“非法”的操作,并且继续执行后面的操作,只是在终端上打印出“Segment Fault”的错误。所以在这个程序中,虽然发生了段错误,Fedora仍然能运行到connect(),是整个程序顺利跑起来。”

并给出了一个 demo:

#include <stdio.h>

#define IP_ADDR_LENGTH 16
#define UMP_FUNC_NUM 6

int main() {
  char addr[IP_ADDR_LENGTH];
  addr[0] = '';
  strcat(addr, "172.16.64.181" );

  char (*taskpath)[IP_ADDR_LENGTH];

  printf("the address is %dn", &amp;taskpath[0]);

  int a;
  int b;

  sprintf(taskpath[0], "%s", addr);

  printf("taskpath[0]: %sn", taskpath[0]);
  printf("finish!n");
  fflush(stdout);

  return 0;
}

“在Ubuntu下运行,系统不会输出“ finish! ”这句,而是在输出taskpath[0]的地址后直接终止程序。注意,上面的int a; int b;声明不能省略,也不能赋值!因为如果省略或赋值,就不会产生Segment Fault!(赋值的话系统就会把这两个变量分配到Stack中,这就与对Heap操作的taskpath没有冲突了)”

我的回复是(原文链接:[href: http://bbs.nju.edu.cn/vd83468/bbscon?board=LinuxUnix&file=M.1185532985.A&num=6538 ]):

首先,我的观点,没有所谓 Fedora 和 Ubuntu 对段错误处理的不同。因为它们都是使用 Linux kernel,而内存管理只要 Kernel 的版本一样,我认为不会有不同的处理方式。

其次,我想纠正上文中的一个说法(可能有些讨人嫌哈,不过一些东西还是说清楚点儿好,因为 ls 用这个来解释自己的程序):

> 赋值的话系统就会把这两个变量分配到Stack中,这就与对Heap操作的taskpath没有冲突了

无论你赋值与否,这两个变量都是存在在 stack 中的; taskpath 也不是对 heap 进行操作,它只是存在于 stack 上的一个指针变量。

> 因为如果省略或赋值,就不会产生Segment Fault!

在我的系统中,都会产生段错误。

最后,我来给出我对这个问题的解释:

就上文的 demo 程序来说, a, b, addr, taskpath 都是存在于 stack 上的,这个很清楚,调试过 C 语言程序的人应该都知道,我就不解释了。

1. 为什么会出现段错误?

因为 taskpath 是一个野指针,在使用之前没有被赋值,所以 taskpath 会指向任何位置,对一个随机的位置进行写操作,显然会出现段错误。

2. 为什么同一个程序,定义不定义 a, b 会影响段错误出来的时间点?

虽然上面说 taskpath 会指向任何位置,但是这个说法并不完全正确。因为大家知道,taskpath 是在 stack 上的一个变量,而 stack 呢,是一直在重复使用的一个区域。要明确这一个概念,在操作系统中执行一个可执行文件,程序并不是从 main 开始的,它要先执行一段代码,也就是平常所说的 crt(c runtime)。这个一般是由 lib 提供的,其中要调用一些库函数,所以呢,在 main 执行之前, stack 被 crt 用过(这是最关键的一点)。

因为 stack 使用完是会被释放的,这也就是在调用函数时 function prologue 和 epilogue 所干的事情,开辟栈空间和恢复栈空间,主要动作就是移动栈指针。那么 taskpath 所占的位置很有可能被 crt 用过(不是一定),那么如果被 crt 写过,比如被 crt 用做保留 ebp 或者什么其他的寄存器,它的值就是确定的(在一定程度上说)。

如果 crt 在 taskpath 这个位置上保存过寄存器的值,尤其是 ebp 或者 esp,那么很有可能 taskpath 就指向此程序栈空间的某个位置。那么写 taskpath 指向的内存产生的段错误就没那么 critical,或者说操作系统对它的指针在自己栈空间中的操作比较容忍,就不会立即停止程序的运行。但是如果 crt 没有在这个位置上进行操作,那么这个位置就可能是某个垃圾地址,比如说操作系统自己的内存空间,那么写 taskpath 指向的内存就会造成很严重的后果,操作系统会立马检查出来终止它的运行。

我在 Ubuntu 7.04 下使用 gcc 4.1.2 编译、调试并反汇编的结果显示:两个程序唯一的不同是 taskpath 在堆栈上的位置,当定义 int a, b; 时,taskpath 是 $ebp -40 而这个地址没有被操作过;当不定义 int a, b; 时, taskpath 是 $ebp - 32,这个地址曾经被 crt 使用过。所以按照上面的解释,系统报段错误的时间不一样。

如果熟悉 GDB 的话,可以很容易的用调试证明这一点。计算出 crt 入口的 $ebp 和 main 中 $ebp 的差,以此计算出 taskpath 保存的位置,在上面设置 watch point,从 _init 执行到 main,看其中有没有对 taskpath 所在位置进行写操作。

3. 为什么不同的操作系统,结果不一样?
这个就比较简单了。kernel 不一样,可能内存管理的方式不一样。使用的 lib 或者 gcc 不一样,可能引起 crt 的汇编结果不一样。这两个都能导致同样的程序报错的时间不一样。

所以,不是 Fedora 或者 Ubuntu 能不能容忍段错误,没有 OS 容忍段错误,不同只是在产生段错误够不够 crucial 需要得到立即处理。