朱文昊 Albert Zhu
朱文昊的中文博客--专注技术,向往自由
朱文昊的中文博客--专注技术,向往自由
2010年05月25日
假如生活欺骗了你,
不要悲伤,不要忧郁;
阴霾的日子需要镇静
相信吧,那快乐的时光即将来临。
心儿永远憧憬着未来,
而现在却总是令人伤怀;
一切都是瞬息,一切都会过去,
而那过去了的,将会变成美好的回忆。
更多 >
2010年05月15日
当用户打开PC的电源,BIOS开机自检,按BIOS中设置的启动设备(通常是硬盘)启动,接着启动设备上安装的引导程序lilo或grub开始引导Linux,Linux首先进行内核的引导,接下来执行init程序,init程序调用了rc.sysinit和rc等程序,rc.sysinit和rc当完成系统初始化和运行服务的任务后,返回init;init启动了mingetty后,打开了终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。
下面就将逐一介绍其中几个关键的部分:
第一部分:内核的引导(核内引导)
Red Hat9.0可以使用lilo或grub等引导程序开始引导Linux系统,当引导程序成功完成引导任务后,Linux从它们手中接管了CPU的控制权,然后CPU就开始执行Linux的核心映象代码,开始了Linux启动过程。这里使用了几个汇编程序来引导Linux,这一步泛及到Linux源代码树中的“arch/i386/boot”下的这几个文件:bootsect.S、setup.S、video.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、stdout和stderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。到此init()函数结束,内核的引导部分也到此结束了.
2010年05月14日
各位同仁:
IEEE CS Nanjing Chapter 5月18日13:30~15:40在南京大学蒙民伟楼404举行学术报告会,信息如下。欢迎参加!
报告一:
题目:Using Computers to Find Out the Truth
报告人:Professor Boi Faltings
ECCAI Fellow, Director of AI Lab
Faculty of Information and Communication Sciences
Swiss Federal Institute of Technology, Lausanne, Switzerland
时间:5月18日 13:30-14:30
地点:蒙民伟楼404会议室
摘要:One of the major problems for decision makers today is that they are far removed from the details that are often crucial for the success of their plans. On the other hand, the people who know these details are often not likely to report them truthfully, as it is not in their best interest to do so. The anonymity afforded by computing systems can help in this situation. I present several approaches to eliciting truthful information, in particular scoring rules, peer prediction methods and opinion polls.
简介:Boi Faltings is a full professor of computer science and director of the AI lab. His main research contributions are in the area of qualitative and case-based reasoning, constraint programming, distributed problem-solving, and recommender systems. He has co-founded 6 companies in e-commerce and computer security and acted as advisor to several other companies world-wide. Prof. Faltings has published over 300 refereed papers and graduated over 25 Ph.D. students, several of which have won national and international awards. Boi Faltings is a fellow of the European Coordinating Committee for Artificial Intelligence. He has served as head of the computer science department from 1996-1998 and as head of the Institute of Core Computing Sciences from 2005-2008. He serves or served as associate editor of several journals, in particular the AI Journal (2000-2008), JAIR (2004-2007), Annals of AI and Mathematics (2008-today), and as member of editorial boards (AI Communications, AI Magazine, Constraints, and others). He also regularly serves in conference committees (IJCAI, AAAI, ECAI, and others) and have been program (co-)chair of several workshops and conferences. He holds a Diploma from ETH Zurich and a Ph.D. from the University of Illinois at Urbana-Champaign.
报告二:
题目:User Experience and Technology Acceptance Issues in Recommender Systems
报告人:Dr. Pearl PU
更多 >
2010年05月13日
GNC CC 是一个功能非常强大的跨平台 C 编译器,它对 C 语言提供了很多扩展,这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把支持 GNU 扩展的 C 语言称为 GNU C。 Linux 内核代码使用了大量的 GNU C 扩展,以至于能够编译 Linux 内核的唯一编译器是 GNU CC,以前甚至出现过编译 Linux 内核要使用特殊的 GNU CC 版本的情
况。本文是对 Linux 内核使用的 GNU C 扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看gcc.info。文中的例子取自 Linux 2.4.18。
语句表达式
==========
GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。例如:
1 2 3 4 5 | ++++ include/linux/kernel.h 159: #define min_t(type,x,y) \ 160: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) ++++ net/ipv4/tcp_output.c 654: int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk)); |
复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。这里定义了一个安全的求最小值的宏,在标准 C 中,通常定义为:
1 | #define min(x,y) ((x) < (y) ? (x) : (y)) |
这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。
Typeof
======
使用前一节定义的宏需要知道参数的类型,利用 typeof 可以定义更通用的宏,不必事先知道参数的类型,例如:
1 2 3 4 5 6 | ++++ include/linux/kernel.h 141: #define min(x,y) ({ \ 142: const typeof(x) _x = (x); \ 143: const typeof(y) _y = (y); \ 144: (void) (&_x == &_y); \ 145: _x < _y ? _x : _y; }) |
这里 typeof(x) 表示 x 的值类型,第 142 行定义了一个与 x 类型相同的局部变量 _x 并初使化为 x,注意第 144 行的作用是检查参数 x 和 y 的类型是否相同。typeof 可以用在任何类型可以使用的地方,通常用于宏定义。
零长度数组
======
GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:
1 2 3 4 5 | ++++ include/linux/minix_fs.h 85: struct minix_dir_entry { 86: __u16 inode; 87: char name[0]; 88: }; |
结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要定义数组长度为 1,分配时计算对象大小比较复杂。
可变参数宏
==========
在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:
1 2 3 | ++++ include/linux/kernel.h 110: #define pr_debug(fmt,arg...) \ 111: printk(KERN_DEBUG fmt,##arg) |
这里 arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成 arg 的值,在宏扩展时替换 arg,例如:
1 | pr_debug("%s:%d",filename,line) |
扩展为
1 | printk("<7>" "%s:%d", filename, line) |
使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNU C 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样
1 | pr_debug("success!\n") |
扩展为
1 | printk("<7>" "success!\n") |
注意最后没有逗号。
标号元素
========
标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写 ‘[INDEX] =’,要指定一个范围使用[FIRST ... LAST] =’ 的形式,
例如:
1 2 | +++++ arch/i386/kernel/irq.c 1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL }; |
将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。
要指定结构元素,在元素值前写 ‘FIELDNAME:’,例如:
1 2 3 4 5 6 7 8 9 10 11 | ++++ fs/ext2/file.c 41: struct file_operations ext2_file_operations = { 42: llseek: generic_file_llseek, 43: read: generic_file_read, 44: write: generic_file_write, 45: ioctl: ext2_ioctl, 46: mmap: generic_file_mmap, 47: open: generic_file_open, 48: release: ext2_release_file, 49: fsync: ext2_sync_file, 50 }; |
将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,元素 read 初始化为 genenric_file_read,依次类推。我觉得这是 GNU C 扩展中最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。
Case 范围
=========
GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:
1 2 3 4 5 | ++++ arch/i386/kernel/irq.c 1062: case '0' ... '9': c -= '0'; break; 1063: case 'a' ... 'f': c -= 'a'-10; break; 1064: case 'A' ... 'F': c -= 'A'-10; break; case '0' ... '9': |
相当于
1 2 | case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': |
声明的特殊属性
==============
GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写
1 | __attribute__ (( ATTRIBUTE )) |
其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这里介绍最常用的:
* noreturn
属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:
1 2 3 | ++++ include/linux/kernel.h 47: # define ATTRIB_NORET __attribute__((noreturn)) .... 61: asmlinkage NORET_TYPE void do_exit(long error_code) |
ATTRIB_NORET;
* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)
属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以让编译器根据格式串检查参数类型。例如:
1 2 3 | ++++ include/linux/kernel.h? 89: asmlinkage int printk(const char * fmt, ...) 90: __attribute__ ((format (printf, 1, 2))); |
表示第一个参数是格式串,从第二个参数起根据格式串检查参数。
* unused
属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
* section (“section-name”)
属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的节中。
GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外信息,Linux 内核只使用了 __FUNCTION__。
1 2 3 4 5 6 7 8 9 10 11 12 | ++++ fs/ext2/super.c 98: void ext2_update_dynamic_rev(struct super_block *sb) 99: { 100: struct ext2_super_block *es = EXT2_SB(sb)->s_es; 101: 102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV) 103: return; 104: 105: ext2_warning(sb, __FUNCTION__, 106: "updating to rev %d because of new feature flag, " 107: "running e2fsck is recommended", 108: EXT2_DYNAMIC_REV); |
这里 __FUNCTION__ 将被替换为字符串 “ext2_update_dynamic_rev”。虽然__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__是被编译器替换的,不象 __FILE__ 被预处理器替换。
内建函数
========
GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以 __builtin 开始。
* __builtin_return_address (LEVEL)
内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:
1 2 3 4 | ++++ kernel/sched.c 437: printk(KERN_ERR "schedule_timeout: wrong timeout " 438: "value %lx from %p\n", timeout, 439: __builtin_return_address(0)); |
* __builtin_constant_p(EXP)
内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0。例如:
1 2 3 4 5 | ++++ include/asm-i386/bitops.h 249: #define test_bit(nr,addr) \ 250: (__builtin_constant_p(nr) ? \ 251: constant_test_bit((nr),(addr)) : \ 252: variable_test_bit((nr),(addr))) |
很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。
* __builtin_expect(EXP, C)
内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。例如:
1 2 3 4 5 6 7 8 | ++++ include/linux/compiler.h 13: #define likely(x) __builtin_expect((x),1) 14: #define unlikely(x) __builtin_expect((x),0) ++++ kernel/sched.c 564: if (unlikely(in_interrupt())) { 565: printk("Scheduling in interrupt\n"); 566: BUG(); 567: } |
这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。
2010年05月11日
将网站数据库备份后留在本地硬盘上,并不是一个可靠的方案。我就把压缩后的文件发送到电子邮件信箱里。Gmail的附件目前最大25兆,QQ邮箱是50兆,所以本方法不适合大型商业网站,个人独立博客比较合适的说。
下面贴出脚本,本方法需要先安装mutt邮件客户端。
请注意,有些老点的文章中给出的示例,在邮件地址前面没有 — 符号,这会造成新版本的mutt提示 No recipients specified. 只要加上 — 符号就可以了。
还有一个注意的地方,如果连续发送很多数据库文件并保存在Sent文件中,很快就会消耗大量的本地硬盘空间。让mutt默认不保存已发送邮件的方法是修改 /etc/Muttrc文件,或者在当前用户home目录下,修改或建立一个隐藏文件 .muttrc (注意这两个M的大小写), 修改或增加这样一行 set record=”/dev/null” (一般默认为 set record=”~/Sent”),就可以让Mutt不保存已发送邮件从而节省空间了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #!/bin/sh sendfiletogmail () { FilePath=$1 FileName=$2".bz2" mutt -s "Database Backup File ${FileName}" -a${FilePath}"/"${FileName} -- yourMailBox@gmail.com < "/path/to/emailbody.txt" } backup () { DBName=$@ BackupPath="/path/to/"${DBName} BackupName=${DBName}-`date "+[%z%Z]%Y-%m-%d-%H-%M-%S"`".sql" if [[ -d "${BackupPath}" ]] || mkdir ${BackupPath}; then if mysqldump -uroot -pYourPasswd \ --default-character-set=utf8 --opt --master-data=1 \ --single-transaction --flush-logs \ ${DBName} > ${BackupPath}"/"${BackupName}; then echo ${BackupName}"Backup success!" bzip2 -q -s ${BackupPath}"/"${BackupName}; sendfiletogmail ${BackupPath} ${BackupName} else echo ${BackupName}"Backup Failed@@@@@@@@@@@@@@@@@@@@@@@@" fi fi find ${BackupPath}"/" -ctime +7 -exec rm {} \; } #Call the function to backup database assigned below backup yourDBnamd_db |