2007/3

在这个肮脏的世界,你是我的最爱

Thursday, 1. March 2007, 12:48:24


不是不能等你,也不是不再喜欢你了。只是觉得现在确实是该离开你的时候了,你已经有自己喜欢的人了,有一个能在你身边照顾你的人了,离开你虽然我还是不舍得但是终于放心了。不管你们之间出现了什么矛盾,我都希望你们还能和好如初,只要相爱就一定会在一起的。

关于我们之间的事,我觉得你做得对,换成我我也会这么做,你不用自责。你说过,我们之间有太多的不适合,我爱你只是一种习惯,我想你也是对的。生活也许本来就是这样,什么事都不可能十全十美,不能改变的事只能接受。三年了,我太累了,不想再纠缠于这种问题了。我想开始一种新的生活,开始不再想你。这两年离开你的日子里我也过得很充实,因为只要一想起那些和你在一起的点点滴滴,就能让我心里饱满很久很久。这两年我一直想让自己成为你的骄傲,好让你回到我身边,可我发现我错了,但现在看看又有什么关系呢?这些年来因为你我得到的甚至远比我失去的要多。所以,我还是得谢谢你。这辈子能认识你我就很满足了,也不能再奢求别的了。

关于未来,谁也说不准。中国的教育制度是一种失败,别太在意自己的学校。你可能很快就毕业了吧?希望你能找一个自己喜欢的工作(你那么喜欢孩子,怎么不考虑一下去幼儿园当老师呢?),哪怕它并不起眼,因为做自己喜欢做的事本身就是一种快乐,这是金钱换不来的。如果有人用金钱来衡量你,不要理他们。你是一个好姑娘,应该拥有属于你自己的幸福。

希望你能天天快乐!

探索内核bug的经历

Thursday, 8. February 2007, 12:38:09


04043196 王聪西安邮电学院计算机系

我们知道,当无符号数上溢时,它会安安静静地绕回,因此,当比较两个无符号数时,不得不考虑绕回的问题。很可能绝大多数情况下不会出现溢出的情况,但是一旦溢出而处理不当就会导致系统进入非预期状态。不幸的是,Linux内核中的kfifo并没有恰当地处理这一问题。

struct kfifo定义在include/linux/kfifo.h中,其成员如下:
struct kfifo {
unsigned char buffer;
unsigned int size;
unsigned int in;
unsigned int out;
spinlock_t
lock;
};
很明显,in和out两个成员都是无符号整型,这主要是为了下面的一个与操作方便。kfifo_put和kfifoget是不带锁的两个接口,分别向循环缓冲区中放数据和取数据,定义如下:
   118  unsigned int kfifo_put(struct kfifo fifo,
119 unsigned char
buffer, unsigned int len)
120 {
121 unsigned int l;
122
123 len = min(len, fifo-<size - fifo-<in + fifo-<out);

130 smp_mb();
131
132 / first put the data starting from fifo-<in to buffer end /
133 l = min(len, fifo-<size - (fifo-<in & (fifo-<size - 1)));
134 memcpy(fifo-<buffer + (fifo-<in & (fifo-<size - 1)), buffer, l);
135
136 / then put the rest (if any) at the beginning of the buffer /
137 memcpy(fifo-<buffer, buffer + l, len - l);

144 smp_wmb();
145
146 fifo-<in += len;
147
148 return len;
149 }

164 unsigned int
kfifo_get(struct kfifo fifo,
165 unsigned char
buffer, unsigned int len)
166 {
167 unsigned int l;
168
169 len = min(len, fifo-<in - fifo-<out);

176 smp_rmb();
177
178 / first get the data from fifo-<out until the end of the buffer /
179 l = min(len, fifo-<size - (fifo-<out & (fifo-<size - 1)));
180 memcpy(buffer, fifo-<buffer + (fifo-<out & (fifo-<size - 1)), l);
181
182 / then get the rest (if any) from the beginning of the buffer /
183 memcpy(buffer + l, fifo-<buffer, len - l);

190 smp_mb();
191
192 fifo-<out += len;
193
194 return len;
195 }
上面的两个函数在正常情况下可以保证in总是大于等于out,并且它们的差不会超过size。但是当in溢出,而out恰好又没有溢出时,不幸的情况就会发生,in会小于out!这对kfifo_get影响似乎不大,但对kfifo_put却是致命地影响!in绕回后会变成一个很小的正数,而out仍然是一个很大的正数,结果(fifo-<size - fifo-<in + fifo-<out)也会变成一个很大的正数。如果内核程序员恰好不小心把一个很大的len作为参数传递给了kfifo_put(kfifo_put也一样),就会出现指针越界,更严重的会让内核痛苦地oops!



下面一个粗糙的内核模块和用户程序可以展示这个bug。内核模块如下:

     1  #include >linux/kernel.hlinux/init.hlinux/module.hlinux/fs.hasm/uaccess.hlinux/err.hlinux/gfp.hlinux/spinlock.hlinux/kfifo.hlinux/string.h<
11
12 #define LFS_MAGIC 0x19860913
13 #define NFILES 2
14 #define TEST_BUF_LEN 64
15
16 static struct kfifo fifo;
17 static spinlock_t lock;
18 static char
buf;
19
20 static int lfs_open_file(struct inode inode, struct file filp)
21 {
22 if (inode-<i_ino < NFILES)
23 return -ENODEV;
24 return 0;
25 }
26
27 static ssize_t lfs_read_file(struct file filp, char buffer,
28 size_t count, loff_t offset)
29 {
30 int len;
31
32 len = kfifo_get(fifo, buf, count);
33 if (
offset < len)
34 return 0;
35 if (count < len - offset)
36 count = len -
offset;
37
38 if (copy_to_user(buffer, buf + offset, count))
39 return -EFAULT;
40
offset += count;
41 return count;
42 }
43
44 static ssize_t lfs_write_file(struct file filp, const char buffer,
45 size_t count, loff_t offset)
46 {
47 if (
offset != 0)
48 offset = 0;
49
50 if (count <= TEST_BUF_LEN)
51 count = TEST_BUF_LEN;
52
53 if (copy_from_user(buf, buffer, count))
54 return -EFAULT;
55
56 return (ssize_t) kfifo_put(fifo, (char
)buffer, count);
57 }
58
59 static int my_atoi(const char name)
60 {
61 int val = 0;
62
63 for (;; name++) {
64 switch (
name) {
65 case '0'…'9':
66 val = 10 val + (name - '0');
67 break;
68 default:
69 return val;
70 }
71 }
72 }
73
74 static int lfs_open_file2(struct inode inode, struct file filp)
75 {
76 if (inode-<i_ino < NFILES)
77 return -ENODEV;
78 filp-<private_data = fifo;
79 return 0;
80 }
81
82 static ssize_t lfs_read_file2(struct file filp, char buffer,
83 size_t count, loff_t offset)
84 {
85 int len;
86 struct kfifo
myfifo = (struct kfifo )filp-<private_data;
87
88 len =
89 snprintf(buf, TEST_BUF_LEN, "in=%u out=%un", myfifo-<in,
90 myfifo-<out);
91 if (
offset < len)
92 return 0;
93 if (count < len - offset)
94 count = len -
offset;
95
96 if (copy_to_user(buffer, buf + offset, count))
97 return -EFAULT;
98
offset += count;
99 return count;
100 }
101
102 static ssize_t lfs_write_file2(struct file filp, const char buffer,
103 size_t count, loff_t offset)
104 {
105 char
p = buf;
106 struct kfifo myfifo = (struct kfifo )filp-<private_data;
107
108 if (offset != 0)
109 return -EINVAL;
110
111 if (count <= TEST_BUF_LEN)
112 return -EINVAL;
113 memset(buf, 0, TEST_BUF_LEN);
114 if (copy_from_user(buf, buffer, count))
115 return -EFAULT;
116 p = strchr(buf, ' ');
117 if (!p)
118 return -EINVAL;
119
p++ = '';
120 myfifo-<in = my_atoi(buf);
121 myfifo-<out = my_atoi(p);
122 return count;
123 }
124
125 static struct file_operations lfs_file_ops = {
126 .open = lfs_open_file,
127 .read = lfs_read_file,
128 .write = lfs_write_file,
129 };
130
131 static struct file_operations lfs_file2_ops = {
132 .open = lfs_open_file2,
133 .read = lfs_read_file2,
134 .write = lfs_write_file2,
135 };
136
137 struct tree_descr myfiles[] = {
138 {NULL, NULL, 0},
139 {.name = "kfifo",
140 .ops = &lfs_file_ops,
141 .mode = S_IWUSR | S_IRUGO},
142 {.name = "debug",
143 .ops = &lfs_file2_ops,
144 .mode = S_IWUSR | S_IRUGO},
145 {"", NULL, 0}
146 };
147
148 static int lfs_fill_super(struct super_block sb, void data, int silent)
149 {
150 return simple_fill_super(sb, LFS_MAGIC, myfiles);
151 }
152
153 static int lfs_get_super(struct file_system_type fst,
154 int flags, const char
devname, void data,
155 struct vfsmount
mnt)
156 {
157 return get_sb_single(fst, flags, data, lfs_fill_super, mnt);
158 }
159
160 static struct file_system_type lfs_type = {
161 .owner = THIS_MODULE,
162 .name = "demofs",
163 .get_sb = lfs_get_super,
164 .kill_sb = kill_litter_super,
165 };
166
167 static int

init lfs_init(void)
168 {
169 spin_lock_init(&lock);
170 fifo = kfifo_alloc(TEST_BUF_LEN, GFP_KERNEL, &lock);
171 if (IS_ERR(fifo)) {
172 kfifo_free(fifo);
173 return -ENOMEM;
174 }
175 /
176
We just want the overflow comes soon.
177 You can, of course, let fifo-<out and fifo-<out
178
to be 0. And we can let them increase by 'fifo-<size'
179 in the user space quietly. Sooner or later, they will
180
overflow again like this.
181 */
182 fifo-<in = fifo-xiyou.wangcong@gmail.com<");
204 MODULE_DESCRIPTION("Show the bug of unsigned integer overflow in kfifo.");
205 MODULE_SUPPORTED_DEVICE("libfs filesystem");
用户程序代码:
     1  #include >sys/types.hsys/stat.hunistd.hfcntl.h 256;i++)
19 buf
=’0’;
20 /
21
I won’t check the return value of write.
22 And that’s the reason why I don’t use ‘echo’.
23
/
24 write(fd, buf, 256);
25 return 0;
26 }
27

—————————————————————————————————————

1 #! /bin/bash
2 #bugshow.sh
3 #Author: WANG Cong, XIPT. >xiyou.wangcong@gmail.com<
4 #Usage: ./bugshow.sh install yourmodule_name.ko
5 # OR ./bugshow.sh uninstall your_module_name
6
7 if [ $# != "2" ]; then
8 echo "Usage: ./bugshow.sh install your_module_name.ko"
9 echo "OR ./bugshow.sh uninstall your_module_name"
10 exit -1
11 fi
12 action="$1"
13 if [ "$action" = "install" ]; then
14 module=${!#}
15 /sbin/insmod $module
16 mkdir -p /mnt/libfs
17 mount -t demofs none /mnt/libfs
18 if find ./ -name bugshow.c
19 then
20 gcc -Wall -o bugshow bugshow.c
21 else
22 echo "Can't find bugshow.c!"
23 exit -2
24 fi
25 ./bugshow
26 cat /mnt/libfs/debug
27 ./bugshow
28 cat /mnt/libfs/debug
29 elif [ "$action" = "uninstall" ]; then
30 module=${!#}
31 umount none
32 rmdir /mnt/libfs
33 /sbin/rmmod $module
34 else
35 echo "Bad usage!"
36 exit -3
37 fi
38 exit 0
上面的模块是仔细编写的(虽然没有考虑竞争;-p),所以bug不会导致很严重的问题,只是无法向kfifo中继续写入数据。这个bug影响到所有使用kfifo的内核版本,从2.6.10到2.6.20。



一个简单的补丁如下:

—- kernel/kfifo.c.orig 2007-02-07 19:42:51.000000000 +0800
+++ kernel/kfifo.c 2007-02-07 19:43:31.000000000 +0800
@@ -24,6 +24,7 @@
#include >linux/slab.hlinux/err.hlinux/kfifo.hlinux/compiler.h<

/* kfifo_init - allocates a new FIFO using a preallocated buffer
@@ -120,6 +121,12 @@ unsigned int kfifo_put(struct kfifo f
{
unsigned int l;

+ /
If only fifo-<in overflows, let both overflow!/
+ if (unlikely(fifo- fifo-<out)) {
+ fifo-<out += fifo-<size;
+ fifo-<in += fifo-<size;
+ }
+
len = min(len, fifo-<size - fifo-<in + fifo-<out);

/

@@ -166,6 +173,12 @@ unsigned int
kfifo_get(struct kfifo f
{
unsigned int l;

+ /
If only fifo-<in overflows, let both overflow!/
+ if (unlikely(fifo- fifo-<out)) {
+ fifo-<out += fifo-<size;
+ fifo-<in += fifo-<size;
+ }
+
len = min(len, fifo-<in - fifo-<out);

/

后经过Andrew的指点,发现这不是一个bug。我一开始被/proc接口搞晕了,得出了错误的结论。
教训:千万不用使用老的/proc接口!


不公开: 考试只不过是一种欺骗

Friday, 2. February 2007, 06:11:15

王聪@西邮

考试成绩出来了。一看到成绩就想笑,因为我的成绩破具讽刺意味。

选取经典的三门成绩,列举如下:

模拟电路 83
计算方法 91
数据结构 63

不知道的人一看还以为,这小子学习还可以,基础课不错,可惜计算机专业课差了点。而事实正好相反。

在这个学期中,模拟电路和计算方法这两门课我听课的次数加起来还不到10节,一般上这两门课除了睡觉就是看计算机书;作业除了第一次是自己做的,其余均是直接或间接抄的答案。而且,考计算方法时,我提前40分钟交的卷子,因为我做完又检查了两遍,实在是无聊了!(知道我当时在想什么吗?出这种题考我们,你当我们是二啊?!老子一年花5500¥就接受这种教育啊?!)那我怎么还能考这么高呢?很简单,因为我考试前用三天的时间突击了一下,把老师划的所谓的传说中的重点全都勾好看了一遍,把以前的卷子也都做了一下,把可能出的题型仔细研究了一下。

结论一:要想在考试中取得好成绩,务必一定必须熟悉+研究这门课考试的详细情况!即使你对这门课厌倦无比(比如我们伟大的D要开设的马克思,邓*,毛泽东*。在我眼中,模电也可以和它们相提并论),只要你有了老师划的考试范围和复习重点,照样拿高分!!照样是“好好学习,天天向上”+品学兼优的三好学生!!其它一概不用管,成绩几乎可以遮盖一切,请尽管放心!!

再看看形成鲜明对比的数据结构。首先,虽然我喜欢数据结构这门课,但我讨厌系里选用的数据结构教材,我认为那本书的编写者没有责任心,书中的代码不堪入目,理论也多“参考”其它书籍。即使是像教授们宣称的那样“它是使用类C语言编写”,它的代码也很难让我接受。我在学期一开始就从书中找出二十多处错误(同类型的错误只计算一次),自然也不会花冤枉钱买这等烂书。然后就是作业,平时作业我基本上全是自己独立完成的,因为我要单独给老师交电子版。最后,到期末时,老师划的重点什么的我一概没划,题几乎也一个没做,因为我从大一就开始自学数据结构,也编写了不少数据结构的代码(用C,C++,Perl都写过),我读过一些Linux内核的源代码,见过的数据结构比课本上的要复杂的多(不信你就看一下Linux源代码中的rbtree和pidhash),怎么说也得优秀吧?结果呢,刚好及格!(或许我还应该庆幸,差点就不及格呀!)

结论二:如果你喜欢一门课,很可能你并不会在它的考试中取得好成绩。因为你喜欢的是课程本身而不是考试,甚至你会因为太喜欢这门课而拒绝让你恶心的考试(它侮辱了你的智商),所以你就失去了取得高分的法宝!一门课不管你学得多么好,只要是考试不考或者不是考试的重点,你也别想考好!

从上面我们可以看出,考试是一个坏东西,或许是人间至恶,没有一样坏的东西会长存。


看看恶心的考试让我们多少未来的科学家们放弃了学习自己喜欢的东西,想想为什么我们一年又一年地培养了那么多高分状元但至今仍拿不到诺贝尔奖,有多少人在绞尽脑汁地拼命地把自己变成做题机器来获取所谓的名利和认可,又有多少才华横溢的少年因为不适应这考试制度而被埋没:教育部欺骗了我们!

希特勒说过:“越大的谎言就越容易有人相信。”教育部用欺骗和诱惑迷住了我们的双眼和头脑。我在这种欺骗中存活了十几年,不能不说这是一个奇迹。我想我是接受不到新的教育了,可我希望以后的孩子们能够接受,我希望能在2030年的某一天看到孩子们都能够学他们喜欢的知识,他们的才华不再用考试成绩来衡量,而是用他们与生俱来的创造力;他们能够快快乐乐地在科学的世界里探索,而不再受政治和金钱的制约。

教育上的欺骗是最大的欺骗。我不想自己受骗以后再继续去骗别人,那样的话骗局永远不会结束。我希望有更多的人能够识破这种骗局,并有勇气和决心去改变它。只有这样,以后的孩子们才会有希望,我们的国家和民族才会有希望。

关于《三体》

Friday, 26. January 2007, 08:46:03


我是在刚刚读完《银河系漫游指南》又重拾《三体》的,于是一口气把它也读完了。之前在杂志和网络上陆陆续续读过一些,《三体》给我的总体印象只是一般,情节和想像根本就没法和国外的优秀科幻相比,不过仍然有一些值得提起的地方。

《三体》中成功塑造了三体文明以及人类对它们疯狂的向往。小说中揭露了人类自私和丑陋的一面,但认识到这一点的人对此却无能为力,只好求助于偶然间联系上的外星三体文明,却没想到三体文明也只是一个自私自利的物种,当它们得知人类的消息后就开始准备占领地球来为自己取得生存之地。

人性本来就是丑陋还是宇宙间的生命皆如此?人类的命运最终会如何?这是作者留给我们的大问题,这没有绝对答案,也没人能知道答案。这警告着我们:可怜的人类,好自为之吧!

和《银河系漫游指南》相比,两者都是嘲笑人类的无知和愚蠢,但差距无疑是很明显的。无论在情节处理还是在语言叙述上,《三体》都明显落后于《银河系漫游指南》,这似乎也是国内科幻的通病。虽然《三体》的一些想像之处也并不落后,但是总体上就明显落后了,它没能像国外那些优秀科幻那样,让读者从头一路吃惊到底;情节上似乎更明显,一些处理不尽如人意,弥合不足,让人读着多多少少有些牵强。不过在国内,《三体》仍然是一部不错的科幻小说。

这学期结束了

Wednesday, 17. January 2007, 15:13:32


终于全部考完了!终于可以放松了!这该死的考试总算过去了,终于又可以学自己想学的东西了!真的感觉很轻松啊~!:cool:

考试完了就意味着马上就要回家了,赶紧做一些临走前的准备。把宿舍里的脏衣物都洗了,晾干的衣服也该收了;把老妈嘱咐了多次的衣服带上,不穿的衣服也整理出来,该留的留,该扔的扔;把期末复习用的书都搁好,复习资料都可以扔了,把寒假要读的书带上……电脑里积累了一个学期的资料,也该备份了,这样带回去也方便。仔细一看,还真不少,刻了整整两张光盘~!:rolleyes:

也给自己备份个好心情,下学期来了继续学习。回家放松一下,和家人好好过个年,和朋友同学好好聚一聚。:D

下学期再见吧!:jester:

拿到Intel官方手册

Tuesday, 16. January 2007, 07:41:56

哈!今天终于拿到Intel公司寄来的手册啦~!!:D

上个周五收到Intel客服部门(在印度,外包)的电子邮件,说我订的手册已经开始邮寄了,快递公司是FedEx(联邦快递)。上FedEx公司的网站上一查,发现手册已经开始ship,下周一能到。:rolleyes: 结果,昨天就接到我们导员的电话,问我是不是从美国Intel公司订过东西,快递公司的人到了我们老校区,让我打过电话去问问。他这么一说我才意识到Intel免费发放的手册到了:eyes: 。大约三个星期以前,听说Intel公司又开始在全球范围内发放Intel CPU的手册了,我怀着侥幸的心理去Intel公司那里试试订一份看看,结果去真的订到了,免费的!好幸运啊~!:lol:

不过从昨天接到消息到今天拿到手册,还遇到了一个小麻烦,那就是FedEx西安分公司不在我们大学城这里服务,也就是说我得自己过去拿!:mad: 给那FedEx公司的人打电话问了一下,他们公司在高薪区,那离我们很远而且公交车很少。我先坐600到了小寨,在那里又转34到了高薪区。结果我在高新路就下车了,害得我走了N站路,问了N个人才找到FedEx公司!真不容易啊~~

不过进了FedEx公司,里面的服务人员态度不错,最后总算是拿到手册了,打开一看,哈!果然是两本System Programming Guide,爽~~:raider: 坐在回去的公交车上,我又看了看包裹上的英文,发现自己居然把地址中的Engineering给错拼成Engeering了!惭愧死了!:ko: 不过我回来又看了看邮件,发现包裹上的地址和我当初留的地址还不一样,Intel的客服公司也弄错了,我留的邮编是710121,结果他们弄成了 710021;我写的我们学校的全名Xi’an Institute of Post and Telecommunications,结果他们弄成了Xi’an Institute of Post。:eek:

书拿到了,以后得好好学习。再批评Intel的时候就得注意点儿了~:devil:

这个世界是平的~!

走近Linux内核

Monday, 15. January 2007, 13:54:04





# 走近Linux内核

作者:王聪

>xiyou.wangcong@gmail.com<


>
>
> 不要理会任何一个告诉你内核开发是困难,特别或者不同的人。它是一个大的程序,而且bug修复或驱动编写是一个最佳起点。它也没有什么魔力,也不是使用只有留着络腮胡的老手才能读懂的语言编写。
>
> ──Alan Cox



## 简介

这篇文章是专门写给那些对Linux内核感兴趣,却又不知道如何着手去读懂那么多代码的内核新手。也许你刚刚了解Linux,又急于探索Linux的内部秘密;也许你是一个Linux开发者,熟悉应用程序的开发,又雄心勃勃准备向内核世界进发。那么这篇文章正是你需要的,它会带你走进内核的世界,伴你渡过危险的沼泽。通过分享我们自己的经历,希望有更多的人能够加入到Linux内核开发者行列。

内核开发向来被视为非常神秘的工作,仿佛只有传说中的留着长长的络腮胡的黑客们才能从事它。其实不然,Linux内核的开发和其它大型项目没有多少差别,只不过它的调试确实有点特别,需要一些特别的技巧。不要恐慌(Don’t Panic!),只要你下功夫,你也能参与内核的开发,它的确是一件非常好玩的事。

## 需要准备什么

当然,你首先要有一台可供支配的电脑,最好装有Linux。如果可以,最好再有一台专门供你调试代码的机器,因为没人能保证调试内核的过程中不会让你的文件系统崩溃。或者,至少有一块专门给调试内核使用的硬盘。

最好还有一个固定的互联网接口,毕竟Linux内核开发是在网络上进行的,而且你也会经常在互联网上搜索一些有用的信息。

如果你是一位超级geek的话,再准备一根双机串口线,它能帮助你从一台机器上聆听另一台机器上内核运行中的抱怨。嗯,有点像是外科医生给病人听诊,这看起来很酷,不是吗?

如果你准备在一台非计算机设备上调试你的内核(这没什么奇怪的,Linux早已经被移植到千奇百怪的系统上),那么你还需要准备相应的硬件,或者它的模拟器,或者其它一些工具。如果你有在非计算机设备上调试Linux内核的经验,请在这里自由添加相应的内容。

## 开始

我们假设上面的东西你都准备好了,整装待发,现在可以正式进军内核了。当然了,如果你对Linux上的开发已经很熟悉了,你可以安全地跳过这一节。好了,出发,水手们!

### 1. 精通C语言编程

不是我们一味推崇C语言,而是C语言的的确确太适合做内核开发了。C语言的诞生源于编写Unix内核代码,它精练的设计哲学确实做到了这一点。甚至有人这样评价C语言──“它联合了汇编的所有威力。如果你还不懂C,赶快去学吧。

如果你是一名编程新手,不推荐用C作为你的入门语言,原因如下:

编程新手最需要了解的是编程的概念和对编程的基本认识,而过多的接触C语言往往会把你引出这一目的,会让你把注意力集中到一些奇怪的语言特性上,而不是编程语言本身。

编程新手往往对计算机了解不够深刻,不清楚计算机的内部结构,而C语言恰恰就是和计算机内存/编码/CPU打交道,最起码,调试那些隐晦的错误时如此。(想想你是不是没有把一个指向指针的指针的指针指向正确的位置。)

学好C语言需要下很大的功夫,最起码不能低于两年。(当然如果你不打算学好那得另说了。)

所以,最好先学一门比较简单的编程语言作为铺垫。不妨试一下Python,它比Java还要简单。当然了,这并非绝对,因人而异。如果你真的决定开始学习C语言,那么推荐的入门书籍仍然是K&R《The C Programming Language。过去这么多年了,它仍然被奉为入门的首选,可见其有多么经典。

不过仅仅了解C的语法,能编写一些小的程序是远远不够的。你必须能够熟练地操纵C语言,了解它的一些缺陷和陷阱,让它变成你的利器。有句话说得好:“C语言就像一把刻刀,简单,锋利,并且在技师手中非常有用。和任何锋利的工具一样,C会伤到那些不能驾驭它的人。读一读《C

Traps and Pitfalls
》和《Expert C Programming》吧,它们能让你有一个大的提升,成为一名C语言高手。

如果碰巧你是一位C++的推崇者,那么下面的一些引用或许能说服你开发Linux内核不使用C++(摘自LKML FAQ)。

> Linus2004年说:
>
> In fact, in Linux we did try C++ once already, back in 1992.
>
> It sucks. Trust me - writing kernel code in C++ is a BLOODY STUPID IDEA.
>
>
他认为:
>
> C++
编译器是不可靠的,1992年的时候更糟,有一些基础性的东西没有改变:
>
> (不知道怎么翻译这个词好)的,对内核来说它更是broken
>
> 任何一个喜欢把内存分配藏到你背后的编译器或者语言,都不是你编写内核的好的选择。
>
> 你可以用C来编写OO代码,而不用C++的一些废话
>
> Andrew D. Balsa如是说:
>
> Linux
一开始的时候gcc还没有很好的C++实现,而且当时C程序员比C++程序员要多。
>
> Richard E. Gooch
解释到:
>
>
Linux诞生之前,也有内核在g++下编译,但是人们抱怨它的性能。据证明,把C代码放到g++下编译会产生更糟糕的代码。它不应该有差别,但的的确确有!

### 2. 熟悉常用的工具

#### 掌握至少一种编辑器

掌握一种你喜欢的编辑器是非常有必要的,它能为你节省很多时间。在Linux上,最著名的编辑器莫过于emacsvi了。一些内核开发者介绍,他们大多数人就是在使用这两种编辑器中的一种。可能一开始你并不能适应这种环境,没关系,熟练之后你的效率会有质的飞跃。

>pvi


在内核开发中使用vi有如下好处:

1.
占用内存少,加载速度快;

2.
高度可定制化,经配置的vi可以很好地重映射命令按键;

3.
可以自动补齐一些函数名和变量名;

4.
可以和ctags很好地配合使用,通过ctrl+]能很快定位函数的定义位置。

了解更多的vi使用说明,请参考:《Learning the vi Editor, Fifth Edition》, L.Lamb, 1990, O’Reilly

emacs

emacs的优点如下:

1. Lisp
扩展可以大幅度提高效率;

2.
etags的良好整合;

3.
按键组合更具指导性。

更多的emacs使用知识请参考:《Learning GNU Emacs, Third Edition

, Debra Cameron, James Elliott and

Marc Loy, 2004, O’Reilly


#### 熟悉Linux命令行工具

内核开发者一般都在命令行下面工作,是的,图形界面有时会让事情变得更糟,对内核开发尤为如此。你应该能在命令行下面进行一些简单的工作,比如:编译程序,提交补丁,控制版本,收发邮件等。一些常用的命令行技巧会有帮助,所以也不妨去了解一些shell编程知识。下面仅介绍一些常用的:

gcc

gccGNU提供的优秀的软件之一,其性能不亚于任何商业编译器。它具有惊人的可移植性,而它也号称其最优化功能非常强大,所以Linux内核就是采用了gcc作为编译器。gcc的选项这里无法一一列举,只能介绍一些常用的选项。

-o filename
:这可能是你最常用的一个选项了。它用来产生以指定名字的可执行输出文件。如果不指定文件名,gcc产生默认的a.out

-c:
只编译不链接,产生以.o结尾的目标文件。当然它也可以同时编译多个文件。

> $ gcc -c hello.c

-Idir: 要求gcc在搜寻头文件时除了默认的目录/usr/include,也要到指定的目录dir中去找。

比如,编译test.c时要用到/home/myname/test.h,我们可以:

$gcc

-I/home/myname test.c -o test


-On: 最优化选项。后面的n越大最优化程度越大。一般最常用的是-O2-O0是不优化,这也是默认的。

$gcc -O2 -o foo

foo.c


-Wall/-w:

-Wall
将打开所有警告,而-w将关闭所有警告。在编译C程序时,强烈建议你加上-Wall选项,因为gcc给出的很多警告都是相当有用的。当然还有折中的选择,具体请参阅man手册。

-W:
打开一些额外的警告。

-S:
用来产生相应的汇编源文件,默认的文件名是filename.s

make

make是一个通用的工程管理工具,它可以自动化编译,极大地提高了软件开发的效率。make的使用是根据预先设定的规则来运行的,这些设定的规则记录在一个文件中,即makefile

make
命令的格式是:

> make [ -f makefile ] [ option ] …
>
> target …


-c dir :将指定目录设为make开始运行后的工作目录。

-f filename
:将指定的文件作为makefile

-k
:使make尽可能地编译,即使命令调用返回一个错误,make也不会停止运行。这个功能很有用,例如,当需要移植的时候;你可以创建尽可能多的目标文件,然后可以在不用等待中间文件创建的同时,移植到不能创建的文件处。

makefile
的书写规则是一个大的话题,我们在这里难以展开论述。这里>http://www.wlug.org.nz/MakefileHowto<有一篇介绍makefile书写的Howto,不妨仔细读一下。要对make进行更深入的了解,请参考《Managing

Projects with GNU make
一书。



grep

grep用来在文件中查找一个给定的字符串模式,比如可以快速定位函数或结构体定义。它还可以自动匹配一些正则表达式,非常强大。比如:你要搜索taskstruct的定义, 你可以:

> $grep -r
>
> task_struct | less


-r选项或许是你需要的,它可以帮你扫描源代码树中的所有源代码文件。查看grepman手册来了解更多信息。



*diff/patch

如果你在维护包含很多源文件的一个大型项目,每次更新后都推出完整的源程序是不太可行的,所以最好就用patch程序来更新原来的程序。这样,当你的程序升级时,你只要推出源文件对应的patch文件,其他人就能通过执行patch来使用那个patch文件,以获得最新版本的程序。

比如我们有一个程序foo,我们把它更新为bar,我们可以用diff程序这样产生patch文件:

> $diff -u foo bar
>
> < foo.patch
-u参数指定使用特殊的diff输出格式,否则得到的patch格式怪异,一般人都没法看懂。foo.patch就包含了描述foobar不同的信息,我们将用这个文件来更新。

提示:一定要注意diff命令后面的文件名顺序,一定是旧的文件在前,新的文件在后!



接下来,我们用patch来更新:

$patch foo >

foo.patch


使用过某个patch文件后,若要再次使用该patch文件,patch程序会产生警告信息:询问是否以-R方式执行,-R将还原文件。这样很好,防止进行了误操作。当然,有时你想更新的是整个目录中的所有源文件,比如foobar两个目录,我们可以:

$diff -cr foo

bar <foo.patch

$patch -p0 > foo.patch


diff
-c参数表示输出上下文格式,-r参数表示递归的比较两个目录。 -p0告诉patch程序被更新的文件的路径名并没改变。

diffstat
是一个很有用的工具,它可以列出patch 所引起的变更的统计(加入或移出的代码行)。输出关于patch的信息,执行:

$diffstat -p1

foo.patch


更多选项请参阅patchdiff的使用手册。

### 3. 良好的英文阅读能力和表达能力

不管是在Linux内核中,还是在Linux大多数应用程序中,文档基本上都是用英语写成的,到目前为止,大多数还没有对应的汉语翻译。而且Linux内核黑客之间也是用英语交流的,虽然他们可能来自世界各地。熟练地驾驭英文肯定让你受益非浅。

引用ESR《如何成为一名黑客》一文中的话:

> 当前英语有着比其他语言丰富得多的技术词汇,因此是一个对于工作来说相当好的工具。基于类似的原因,英文技术书籍的翻译通常不令人满意(如果有翻译的话)。
>
> “Linus Torvalds
,一个芬兰人,用英语注释他的代码(很明显这对他来说不是凑巧)。他流利的英语成为他能够管理全球范围的Linux开发人员社区的重要因素。这是一个值得学习的例子。

如果你不懂实用性的英语,赶快去学习吧。

## 进阶

如果你能来到这里,那说明你已经是一名合格的Unix/Linux程序员了。但是,如果你要成为一名内核程序员,还需要下面的一些知识。

### 1. 理解操作系统原理

内核是操作系统的核心和灵魂,要理解内核的原理和运作,必须具备基本的操作系统知识。操作系统课上的一些理论性的东西很有帮助,比如信号量的定义和使用,死锁的预防,内存页面的置换等等。列举如下:

1. 进程管理

进程的定义和PCB,进程和线程的区别,进程的三个基本状态及它们之间的转换关系,进程的同步,竞争和死锁,进程间通信

2. 内存管理

分页式管理,分段式管理,虚拟内存的概念,页面置换算法,内存分配算法

3. 设备管理

中断的概念,中断处理,I/O控制方式,缓冲区管理,设备驱动,磁盘调度和高速缓存

4. 文件管理

文件的概念,文件的管理,文件系统

5. 系统调用

系统调用的概念,系统调用的处理,系统调用类型

关于操作系统理论方面的书籍,推荐Andrew S. Tanenbaum编写的《Operating Systems: Design and Implementation》一书。

### 2. 一些硬件知识

由于内核本身的特殊性,一些时候你不得不和计算机硬件打交道,尤其是CPU。以i386为例说明:

常用寄存器,常见指令

实模式和保护模式

分段和分页机制

TSS和任务管理

中断机制

时钟机制

高速缓存

关于Intel CPU最权威的资料莫过于Intel的官方手册,它们可以在Intel网站上免费下载: http://www.intel.com/products/processor/manuals/index.htm。其实你只要读一下System Programming Guide就够了。

如果你在做嵌入式Linux或者Linux设备驱动,相关的硬件知识更是必须的,可惜这里无法一一列举。

### 3. 其它

你还需要足够的耐心和一些运气,毕竟内核调试不像用户程序那么容易,一些bug让整个社区好几天都食不甘味了。用户程序的调试经验或许有用,但用处不大。这就更要求你对内核本身有足够多的了解了,实际上,你对内核越是了解,bug就越容易清除。

最后,你可能还需要一些想像力。没错,Linux内核开发者有着丰富的想像力,他们经常使用一些神来之笔来解决令旁人苦恼的问题,或者为内核添加一项新奇的功能来让内核运行更流畅。

## 推荐相关书籍:

《Linux内核完全剖析》

讲述Linux 0.11版的源代码,是一个不错的起点。

Understanding the Linux Kernel, 3rd Edition

中文版是《深入理解Linux内核》,第三版即将上市。第三版主要是讲解2.6版的内核,保持了前两版的风格,是深入学习内核的必读书籍。

_Linux Kernel Development, 2nd Edition


中文版是《Linux内核开发》。其内容朴实,风格简练,便于整体把握Linux内核架构。当了解操作系统原理后,便可阅读这本书。

The Linux Kernel Primer - A Top Down Approach For x86 and PowerPC Architectures

中文版是《Linux内核编程》,此书最大的特色是能够把x86ppc两个平台结合起来讲述内核,从用户程序讲起,深入到内核,而且每章后面还配有练习题。

Linux Device Drivers, 3rd Edition

中文译本是《Linux设备驱动程序》,该书是设备驱动开发的最好工具书。其中的例子对于内核开发者来讲是最好的启蒙教材。

《Linux操作系统原理与应用》

陈老师写的一本不错的内核书籍,从原理、设计思想的角度对Linux操作系统的核心内容进行全面的阐述。

《Linux内核情景分析》

本书采取类似于英语教学中行之有效的情景会话的教学方法,全面深入地剖析了Linux 2.4.0内核源代码,并对Linux核心的独特优点和需要进一步改进的问题作了精辟的评述。 全书分上下两册。

## 一些有用的链接:

KernelNewbies

KernelTrap

Linux Weekly News

Linux内核之旅

Kernel Links

The Linux Kernel

What is the Kernel

## 结束语

讲了这么多了,现在该轮到你了。如果你对上面的知识还有没全部掌握,那么不妨从你不熟悉的地方入手,一步步去了解和熟悉Linux内核。如果你是一位高手,对Linux内核有初步的了解和认识,不妨去动手写一些驱动程序或者内核模块,尝试对Linux内核做一些自己的修改。如果可以,加入Linux内核邮件列表,看看里面的内核黑客在做些什么,试着帮他们测试新的内核,修复一些bug

祝你在Linux内核世界走得更远!

My answers on yahoo!

Sunday, 14. January 2007, 14:58:40


今天闲来无事,索性去yahoo上回答问题去了。我的解答如下:

编程入门是学C语言好,还是C++?或者别的?

都不好!
C不适合作入门语言,原因如下:

· 编程新手最需要了解的是编程的概念和对编程的基本认识,而过多的接触C语言往往会把你引出这一目的,会让你把注意力集中到一些奇怪的语言特性上,而不是编程语言本身。

· 编程新手往往对计算机了解不够深刻,不清楚计算机的内部结构,而C语言恰恰就是和计算机内存/编码/CPU打交道,最起码,调试那些“隐晦”的错误时如此。(想想你是不是没有把一个指向指针的指针的指针指向正确的位置。)

· 学好C语言需要下很大的功夫,最起码不能低于两年。(当然如果你不打算学好那得另说了。)

而C++被奉最难的计算机语言,因为它很大,你不可能一次就把它学完(实际上三次也不够)。

入门语言不妨试一下Python,它比Java还要简单。当然了,Java也可以。

C中char *a[8]所占储存空间字节数为什么是32不是8?

char a[8]才是字符数组,里面的元素都是char类型,所以大小应该是sizeof(char)8=8
而你说的char
a[8]是字符指针数组,里面的元素是指向char的指针,所以大小是sizeof(char)8。这个结果在32位机器上是32,在64位机器上是64。

什么叫堆?堆和栈有什么区别?和堆栈有什么区别?

在计算机科学中,堆(heap)这个词至少有两个意思。在数据结构中,它是一种二叉树,便于排序,便于建立优先队列。在操作系统中,它是内存的一个区域,用来存放全局变量和malloc分配的空间,一般是由低地址向高地址生长。

而栈(stack)也有两个意思。在数据结构中,栈就是所有先进后出(FILO)的结构,栈和递归密切相关。而栈的另一个意思和上面堆的第二个意思相对应,它也是内存的一个区域,一般是从高地址向低地址生长,用来存放函数的局部变量和函数调用时保存的一些内容。

二维以上的数组是不是特殊的广义表?一维数组和线性表,广义表有什么关系???

二维数组应该也是一种广义表,你可以把它当成“表的表”,二维以上的类似。广义表是数组上面的一种抽象,数组只是它的一个特例。

FIFO缓冲区是什么意思?

FIFO缓冲区是一种先进先出的环行缓冲区,在Linux内核中使用了此数据结构,用于免锁的生产者/消费者任务。在这个算法中,一个生产者将数据放入数组的结尾,而消费者从数组的另一端移走数据。具体实现请参见Linux内核源代码中的include/linux/kfifo.h。

Google hacks

Saturday, 13. January 2007, 15:55:10

Do you know google hacks?

Google的一些使用技巧,简单总结一下:

intext:

这个就是把网页中的正文内容中的某个字符串做为搜索条件。比如: intext:西邮 。(注意,冒号后面和前面都没有空格!下面的也是如此。)

intitle:

在网页标题里搜索。

inurl:

在url中搜索要找的字符串,比如: inurl:xiyou 。

link:

搜索指向该链接的网页,比如: link:http://www.xiyou.edu.cn

filetype:

指定搜索文件的类型,比如我要找mp3歌曲,可以在后面加上filetype:mp3限制。非常好用的一个功能。

site:

搜索指定的网站,也非常好用,比如: site:xiyou.edu.cn 。

movie:

非常coole的一个功能,可以搜索放映的电影,试试 movie:美丽心灵 。

上面的组合可以产生更强大的功能,比如我要在西邮网站上搜索doc文件中包含“计算机系”的文件,我可以键入:site:xiyou.edu.cn filetype:doc 计算机系 。

还有一些操作符也很有用,列举一下:

+

把后面的词也纳入搜索范围。

-

忽略某个词。

OR

两者中的一个。

“”

精确匹配,不需要google做自己的解释。对比: 分别google一下{西邮 王聪}和{“西邮 王聪”}看看结果。

..

范围操作符,比如2..10就是搜索2到10之间的数字。

另外,google还可以当计算器使用。 ;-p 比如,输入3+2,google会告诉你结果5,输入sqrt(9)会得到3。

怎样?Google是不是很cool啊?

寒假读书计划

Friday, 12. January 2007, 14:49:18



这次寒假放得时间长,大约放42天。回家也没法经常上网,决定在家潜心学习编程,多读一些书。下面列举一下寒假要读的一些书:

1. 《Linux设备驱动》

非常经典的Linux内核入门书籍,决定寒假要把它仔细读完一遍,这样开学来了就可以向内核进军了。

2. 《Unix编程艺术》

ESR的经典之作,讲述Unix的历史,文化和艺术,实在是值得收藏,利用寒假这么长的时间把它仔细读完。

3. 《具体数学》

DEK的名著,经典之作,想深入了解算法一定得读一下此书,起码可以给你打个良好的数学基础。

由于寒假还要过年过节,家里事情也很多,所以能读完上面那三本就很不错了,呵呵。下面再列举几本,以备读完了之后无事可做,当然了,下面的这些书也是以后要读的:

4. 《TCP/IP详解》

W. Richard Steven的经典之作,详细讲述了TCP/IP协议。不过这书有三卷,能啃完这三本书可真不容易。

5. 《计算机编程艺术》

DEK的牛著,算法方面的圣经,不可不读,不过这四卷都很厚,所以也很贵,而且涉及大量数学,只能仰望一下了。

6. 《FreeBSD设计与实现》

关于BSD内核方面的经典之作,也是不能错过的优秀书籍。

7. 《编译器原理──技术与工具》

编译理论方面入门的超级经典之作,一定要把阅读此书提上日程。