编写一个UNIX文件系统

简介:

近日有人求助,要写一个UNIX文件系统作为暑假作业。这种事情基本是学操作系统的必须要做的或者是做过的,毕竟文件系统是操作系统课程的一个重要组成部分。要实现这个UNIX文件系统,很多人就扎进了UNIX V6的的系统源码,以及《莱昂氏UNIX源代码分析》和《返璞归真:UNIX技术内幕》这两本书,很多人出来了,很多人在里面迷失了...最终忘了自己只是要实现一个UNIX文件系统而已。
       为何会迷失,因为代码不是自己写的,而且年代久远,编程理念不同了,作者为何那样写不一定就能理解,实际上对于任何别人写的代码,总是会有一些不易理解的地方,当然,如果作者水平超级高,那么代码也就相对容易理解。因此,写代码永远比读代码要容易!既然是要写一个文件系统,为何要用现成的UNIX V6代码呢?如果理解了UNIX文件的布局和结构,自己从零开始不参考任何现有的代码做一个也不是什么难事,最根本的是UNIX文件系统本身,至于说代码,仅仅是一个实现与用户操作的一个接口而已。如果代码是自己一点一点写的,那么你肯定能彻底明白每一行的每一个语句的精确含义,至于为何这么写,你当然及其明了!
       本文留下我仓促间几个小时写的一个类UNIX文件系统的代码,不是让别人看的,是为了自己留档,因为本文已经说了,看别人的代吗只能学习经验,不能理解本质,更何况,你看的还得是超级一流的代码,而我写的,则是超级垃圾的代码。我只想说,理解问题的本质要比代码重要得多,代码,码,并不是很多人想象中的那般重要!本文的实现基于Linux系统,即在Linux系统上编写一个用户态程序,实现UNIX文件的IO接口以及操作。
       我一向坚持的原则,那就是任何东西的根本性的,本质上的原理以及背后的思想都是及其简单的,所谓的复杂性都是优化与策略化的扩展带来的,正如TCP一样,UNIX的文件系统也不例外!我们必须知道,什么是最根本的,什么是次要的。对于UNIX文件系统,最根本的就是其布局以及其系统调用接口,一个处在最低层,一个在最上层开放给用户,如下所示:
系统调用接口:open,write,read,close...
文件系统布局:引导快,超级块,inode区表,数据块区
所有的在二者中间的部分都是次要的,也就是说那些东西不要也行,比如高速缓冲,高速缓存,VFS层,块层...因此在我的实现代码中,并没有这些东西,我所做到的,仅仅是UNIX文件系统所要求必须做到的最小集,那就是:
面对一个按照UNIX文件系统标准布局的“块设备”,可以使用open,read,write等接口进行IO操作。
在实现中,我用一个标准的Linux大文件来模拟磁盘块,这样块的操作基本都映射到了Linux标准的write,read等系统调用了。
首先定义必要的结构体:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//超级块结构
struct  filesys {
         unsigned  int  s_size;         //总大小                                          
         unsigned  int  s_itsize;         //inode表大小                                        
         unsigned  int  s_freeinodesize;     //空闲i节点的数量                          
         unsigned  int  s_nextfreeinode;     //下一个空闲i节点
         unsigned  int  s_freeinode[NUM];      //空闲i节点数组
         unsigned  int  s_freeblocksize;     //空闲块的数量         
         unsigned  int  s_nextfreeblock;     //下一个空闲块
         unsigned  int  s_freeblock[NUM];     //空闲块数组
         unsigned  int  s_pad[];         //填充到512字节  
};
//磁盘inode结构
struct  finode {
         int  fi_mode;             //类型:文件/目录
         int  fi_uid_unused;         //uid,由于和进程无关联,仅仅是模拟一个FS,未使用,下同
         int  fi_gid_unused;
         int  fi_nlink;             //链接数,当链接数为0,意味着被删除
         long  int  fi_size;         //文件大小
         long  int  fi_addr[BNUM];         //文件块一级指针,并未实现多级指针
         time_t   fi_atime_unused;     //未实现
         time_t   fi_mtime_unused;
};
//内存inode结构
struct  inode {
         struct  finode   i_finode;
         struct  inode    *i_parent;     //所属的目录i节点
         int      i_ino;             //i节点号
         int      i_users;         //引用计数
};
//目录项结构(非Linux内核的目录项)
struct  direct
{
         char  d_name[MAXLEN];         //文件或者目录的名字
         unsigned  short  d_ino;         //文件或者目录的i节点号
};
//目录结构
struct  dir
{
         struct  direct direct[DIRNUM];     //包含的目录项数组
         unsigned  short  size;         //包含的目录项大小    
};
//抽象的文件结构
struct  file {
         struct  inode *f_inode;         //文件的i节点
         int  f_curpos;             //文件的当前读写指针
};

之所以叫做类UNIX文件系统,是因为我并没有去精确确认当时的UNIX文件系统的超级块以及inode表的结构,只是大致的模仿其布局,比如超级块中字段,以及字段的顺序可能和标准的UNIX文件系统并不完全一致。但是不管怎么说,当时的UNIX文件系统基本就是这个一个样子。另外,可以看到file结构体内容及其少,因为本质上,我只是想表示“一个inode节点相对于一个读写者来说,就是一个file”,仅此而已。接下来就是具体的实现了,我的方式是自下而上的,这样做的好处在于便于今后的扩展。那么首先要完成的就是i节点的分配和释放了,我的实现中,是将文件i节点映射到了内存i节点,这样或许违背了我的初衷,我不是说过不要那么多“额外”的东西来扰乱视听的吗?是的,然而比起那些所谓的额外的优化,我更不喜欢频繁的调用read和write。反正,只要自己能控制住局面即可。
       在实现中,还有一个大事就是内存的分配与释放,这些也不是本质的,记住,要实现的仅仅是一个UNIX文件系统,其它的能绕开则绕开!显然malloc,free等也是我们要绕开的,于是我基本都使用预分配空间的东西-全局数组。以下是全局变量:

1
2
3
4
5
6
7
8
9
10
//内存i节点数组,NUM为该文件系统容纳的文件数
struct  inode g_usedinode[NUM];
//ROOT的内存i节点
struct  inode *g_root;
//已经打开文件的数组
struct  file* g_opened[OPENNUM];
//超级块
struct  filesys *g_super;
//模拟二级文件系统的Linux大文件的文件描述符
int  g_fake_disk = -1;

在给出实现代码之前,要说明的是,在删除文件的时候,我并没有实现文件块区以及i节点的清除操作,众所周知,那样很耗时,和很多实现一样,我只是记录了一些信息,表示这个文件块或者inode字段是可以随时覆盖的。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//同步i节点,将其写入“磁盘”
void  syncinode( struct  inode *inode)
{
         int  ino = -1, ipos = -1;
         ino = inode->i_ino;
     //ipos为inode节点表在文件系统块中的偏移
         ipos = IBPOS + ino* sizeof ( struct  finode);
     //从模拟块的指定偏移位置读取inode信息
         lseek(g_fake_disk, ipos, SEEK_SET);
         write(g_fake_disk, ( void  *)&inode->i_finode,  sizeof ( struct  finode));
}
//同步超级块信息
int  syncsuper( struct  filesys *super)
{
         int  pos = -1, size = -1;
         struct  dir dir = {0};
         pos = BOOTBSIZE;
         size = SUPERBSIZE;
         lseek(g_fake_disk, pos, SEEK_SET);
         write(g_fake_disk, ( void  *)super, size);
         syncinode(g_root);
         breadwrite(g_root->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 1);
         breadwrite(g_root->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 0);
}
//关键的将路径名转换为i节点的函数,暂不支持相对路径
struct  inode *namei( char  *filepath,  char  flag,  int  *match,  char  *ret_name)
{
         int  in = 0;
         int  repeat = 0;
         char  *name = NULL;
         char  *path =  calloc (1, MAXLEN*10);
         char  *back = path;
         struct  inode *root = iget(0);
         struct  inode *parent = root;
         struct  dir dir = {0};
         strncpy (path, filepath, MAXLEN*10);
         if  (path[0] !=  '/' )
                 return  NULL;
         breadwrite(root->i_finode.fi_addr[0], &dir,  sizeof ( struct  dir), 0, 1);
         while ((name= strtok (path,  "/" )) != NULL) {
                 int  i = 0;
                 repeat = 0;
                 *match = 0;
                 path = NULL;
                 if  (ret_name) {
                         strcpy (ret_name, name);
                 }
                 for  (; i<dir.size; i++) {
                         if  (! strncmp (name, dir.direct[i].d_name,  strlen (name))) {
                                 parent = root;
                                 iput(root);
                                 root = iget(dir.direct[i].d_ino);
                                 root->i_parent = parent;
                                 *match = 1;
                                 if  (root->i_finode.fi_mode == MODE_DIR) {
                                         memset (&dir, 0,  sizeof ( struct  dir));
                                         breadwrite(root->i_finode.fi_addr[0], &dir,  sizeof ( struct  dir), 0, 1);
                                 else  {
                                         free (back);
                                         return  root;
                                 }
                                 repeat = 1;
                         }
                 }
                 if  (repeat == 0) {
                         break ;
                 }
         }
         if  (*match != 1) {
                 *match = 0;
         }
         if  (*match == 0) {
                 if  (ret_name) {
                         strcpy (ret_name, name);
                 }
         }
         free (back);
         return  root;
}
//通过i节点号获取内存i节点的函数
struct  inode *iget( int  ino)
{
         int  ibpos = 0;
         int  ipos = 0;
         int  ret = 0;
     //倾向于直接从内存i节点获取
         if  (g_usedinode[ino].i_users) {
                 g_usedinode[ino].i_users ++;
                 return  &g_usedinode[ino];
         }
         if  (g_fake_disk < 0) {
                 return  NULL;
         }
     //实在不行则从模拟磁盘块读入
         ipos = IBPOS + ino* sizeof ( struct  finode);
         lseek(g_fake_disk, ipos, SEEK_SET);
         ret = read(g_fake_disk, &g_usedinode[ino],  sizeof ( struct  finode));
         if  (ret == -1) {
                 return  NULL;
         }
         if  (g_super->s_freeinode[ino] == 0) {
                 return  NULL;
         }
     //如果是一个已经被删除的文件或者从未被分配过的i节点,则初始化其link值以及size值
         if  (g_usedinode[ino].i_finode.fi_nlink == 0) {
                 g_usedinode[ino].i_finode.fi_nlink ++;
                 g_usedinode[ino].i_finode.fi_size = 0;
                 syncinode(&g_usedinode[ino]);
         }
         g_usedinode[ino].i_users ++;
         g_usedinode[ino].i_ino = ino;
         return  &g_usedinode[ino];
}
//释放一个占有的内存i节点
void  iput( struct  inode *ip)
{
         if  (ip->i_users > 0)
                 ip->i_users --;
}
//分配一个未使用的i节点。注意,我并没有使用超级块的s_freeinodesize字段,
//因为还会有一个更好更快的分配算法
struct  inode* ialloc()
{
         int  ino = -1, nowpos = -1;
         ino = g_super->s_nextfreeinode;
         if  (ino == -1) {
                 return  NULL;
         }
         nowpos = ino + 1;
         g_super->s_nextfreeinode = -1;
     //寻找下一个空闲i节点,正如上述,这个算法并不好
         for  (; nowpos < NUM; nowpos++) {
                 if  (g_super->s_freeinode[nowpos] == 0) {
                         g_super->s_nextfreeinode = nowpos;
                         break ;
                 }
         }
         g_super->s_freeinode[ino] = 1;
         return  iget(ino);
}
//试图删除一个文件i节点
int  itrunc( struct  inode *ip)
{
         iput(ip);
         if  (ip->i_users == 0 && g_super) {
                 syncinode(ip);
                 g_super->s_freeinode[ip->i_ino] = 0;
                 g_super->s_nextfreeinode = ip->i_ino;
                 return  0;
         }
         return  ERR_BUSY;
}
//分配一个未使用的磁盘块
int  balloc()
{
         int  bno = -1, nowpos = -1;
         bno = g_super->s_nextfreeblock;
         if  (bno == -1) {
                 return  bno;
         }
         nowpos = bno + 1;
         g_super->s_nextfreeblock = -1;
         for  (; nowpos < NUM; nowpos++) {
                 if  (g_super->s_freeblock[nowpos] == 0) {
                         g_super->s_nextfreeblock = nowpos;
                         break ;
                 }
         }
         g_super->s_freeblock[bno] = 1;
         return  bno;
}
//读写操作
int  breadwrite( int  bno,  char  *buf,  int  size,  int  offset,  int  type)
{
         int  pos = BOOTBSIZE+SUPERBSIZE+g_super->s_itsize + bno*BSIZE;
         int  rs = -1;
         if  (offset + size > BSIZE) {
                 return  ERR_EXCEED;
         }
         lseek(g_fake_disk, pos + offset, SEEK_SET);
         rs = type ? read(g_fake_disk, buf, size):write(g_fake_disk, buf, size);
         return  rs;
}
//IO读接口
int  mfread( int  fd,  char  *buf,  int  length)
{
         struct  file *fs = g_opened[fd];
         struct  inode *inode = fs->f_inode;
         int  baddr = fs->f_curpos;
         int  bondary = baddr%BSIZE;
         int  max_block = (baddr+length)/BSIZE;
         int  size = 0;
         int  i = inode->i_finode.fi_addr[baddr/BSIZE+1];
         for  (; i < max_block+1; i ++,bondary = size%BSIZE) {
                 size += breadwrite(inode->i_finode.fi_addr[i], buf+size, (length-size)%BSIZE, bondary, 1);
         }
         return  size;
}
//IO写接口
int  mfwrite( int  fd,  char  *buf,  int  length)
{
         struct  file *fs = g_opened[fd];
         struct  inode *inode = fs->f_inode;
         int  baddr = fs->f_curpos;
         int  bondary = baddr%BSIZE;
         int  max_block = (baddr+length)/BSIZE;
         int  curr_blocks = inode->i_finode.fi_size/BSIZE;
         int  size = 0;
         int  sync = 0;
         int  i = inode->i_finode.fi_addr[baddr/BSIZE+1];
     //如果第一次写,先分配一个块
         if  (inode->i_finode.fi_size == 0) {
         int  nbno = balloc();
                 if  (nbno == -1) {
                         return  -1;
                 }
                 inode->i_finode.fi_addr[0] = nbno;
                 sync = 1;
         }
     //如果必须扩展,则再分配块,可以和上面的合并优化
         if  (max_block > curr_blocks) {
                 int  j = curr_blocks + 1;
                 for  (; j < max_block; j++) {
             int  nbno = balloc();
                     if  (nbno == -1) {
                             return  -1;
                     }
                         inode->i_finode.fi_addr[j] = nbno;
                 }
                 sync = 1;
         }
         for  (; i < max_block+1; i ++,bondary = size%BSIZE) {
                 size += breadwrite(inode->i_finode.fi_addr[i], buf+size, (length-size)%BSIZE, bondary, 0);
         }
         if  (size) {
                 inode->i_finode.fi_size += size;
                 sync = 1;
         }
         if  (sync) {
                 syncinode(inode);
         }
         return  size;
}
//IO的seek接口
int  mflseek( int  fd,  int  pos)
{
         struct  file *fs = g_opened[fd];
         fs->f_curpos = pos;
         return  pos;
}
//IO打开接口
int  mfopen( char  *path,  int  mode)
{
         struct  inode *inode = NULL;
         struct  file *file = NULL;
         int  match = 0;
         inode = namei(path, 0, &match, NULL);
         if  (match == 0) {
                 return  ERR_NOEXIST;
         }
         file = ( struct  file*) calloc (1,  sizeof ( struct  file));
         file->f_inode = inode;
         file->f_curpos = 0;
         g_opened[g_fd] = file;
         g_fd++;
         return  g_fd-1;
}
//IO关闭接口
void  mfclose( int  fd)
{
         struct  inode *inode = NULL;
         struct  file *file = NULL;
         file = g_opened[fd];
         inode = file->f_inode;
         iput(inode);
         free (file);
}
//IO创建接口
int  mfcreat( char  *path,  int  mode)
{
         int  match = 0;
         struct  dir dir;
         struct  inode * new  = NULL;
         char  name[MAXLEN] = {0};;
         struct  inode *inode = namei(path, 0, &match, name);
         if  (match == 1) {
                 return  ERR_EXIST;
         }
         breadwrite(inode->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 1);
         strcpy (dir.direct[dir.size].d_name, name);
         new  = ialloc();
         if  ( new  == NULL) {
                 return  -1;
         }
         dir.direct[dir.size].d_ino =  new ->i_ino;
         new ->i_finode.fi_mode = mode;
         if  (mode == MODE_DIR) {
         //不允许延迟分配目录项
                 int  nbno = balloc();
                 if  (nbno == -1) {
                         return  -1;
                 }
                 new ->i_finode.fi_addr[0] = nbno;
         }
         new ->i_parent = inode;
         syncinode( new );
         dir.size ++;
         breadwrite(inode->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 0);
         syncinode(inode);
         iput(inode);
         syncinode( new );
         iput( new );
         return  ERR_OK;
}
//IO删除接口
int  mfdelete( char  *path)
{
         int  match = 0;
         struct  dir dir;
         struct  inode *del = NULL;
         struct  inode *parent = NULL;
         char  name[MAXLEN];
         int  i = 0;
         struct  inode *inode = namei(path, 0, &match, name);
         if  (match == 0 || inode->i_ino == 0) {
                 return  ERR_NOEXIST;
         }
         match = -1;
         parent = inode->i_parent;
         breadwrite(parent->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 1);
         for  (; i < dir.size; i++) {
                 if  (! strncmp (name, dir.direct[i].d_name,  strlen (name))) {
                         del = iget(dir.direct[i].d_ino);
                         iput(del);
                         if  (itrunc(del) == 0) {
                                 memset (dir.direct[i].d_name, 0,  strlen (dir.direct[i].d_name));
                                 match = i;
                                 break ;
                         else  {
                                 return  ERR_BUSY;
                         }
                 }
         }
         for  (i = match; i < dir.size - 1 && match != -1; i++) {
                 strcpy (dir.direct[i].d_name, dir.direct[i+1].d_name);
         }
         dir.size--;
         breadwrite(parent->i_finode.fi_addr[0], ( char  *)&dir,  sizeof ( struct  dir), 0, 0);
         return  ERR_OK;
}
//序列初始化接口,从模拟块设备初始化内存结构
int  initialize( char  *fake_disk_path)
{
         g_fake_disk = open(fake_disk_path, O_RDWR);
         if  (g_fake_disk == -1) {
                 return  ERR_NOEXIST;
         }
         g_super = ( struct  filesys*) calloc (1,  sizeof ( struct  filesys));
         lseek(g_fake_disk, BOOTBSIZE, SEEK_SET);
         read(g_fake_disk, g_super,  sizeof ( struct  filesys));
         g_super->s_size = 1024*1024;
         g_super->s_itsize = INODEBSIZE;
         g_super->s_freeinodesize = NUM;
         g_super->s_freeblocksize = (g_super->s_size - (BOOTBSIZE+SUPERBSIZE+INODEBSIZE))/BSIZE;
         g_root =  iget(0);
     //第一次的话要分配ROOT
         if  (g_root == NULL) {
                 g_root = ialloc();
                 g_root->i_finode.fi_addr[0] = balloc();
         }
         return  ERR_OK;
}

下面是一个测试程序:

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
int  main()
{
         int  fd = -1,ws = -1;
         char  buf[16] = {0};
         initialize( "bigdisk" );
         mfcreat( "/aa" , MODE_FILE);
         fd = mfopen( "/aa" , 0);
         ws = mfwrite(fd,  "abcde" , 5);
         mfread(fd, buf, 5);
         mfcreat( "/bb" , MODE_DIR);
         mfcreat( "/bb/cc" , MODE_FILE);
         fd = mfopen( "/bb/cc" , 0);
         ws = mfwrite(fd,  "ABCDEFG" , 6);
         mfread(fd, buf, 5);
         mflseek(0, 4);
         ws = mfwrite(0,  "ABCDEFG" , 6);
         mflseek(0, 0);
         mfread(0, buf, 10);
         mfclose(0);
         mfdelete( "/aa" );
         fd = mfopen( "/aa" , 0);
         mfcreat( "/aa" , MODE_FILE);
         fd = mfopen( "/aa" , 0);
         syncsuper(g_super);
}

这个文件系统实现得超级简单,除去了很多额外的非本质的东西,并且也绕开了烦人的内存管理问题!于是,我的这个实现也就显示了UNIX文件系统的本质。那么再看一下,还有什么东西虽然是额外的,但是却是必不可少或者起码说是很有意思的?答案很显然,那就是空闲块或者空闲inode的组织以及分配算法,然而这个算法可以单独抽象出来。




 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1282346



相关文章
|
存储 算法 Oracle
服务器数据恢复-UNIX类文件系统数据恢复可能性分析
服务器数据恢复环境: 基于UNIX系统,软件层级的数据灾难。 服务器故障: 1、存储结构出错。 2、删除数据。 3、文件系统格式化。 4、其他原因导致的数据丢失。
|
监控 关系型数据库 Unix
|
SQL Oracle 关系型数据库
在UNIX裸设备和文件系统之间移动ORACLE
一、关于裸设备 1.1 什么是裸设备(RAW DEVICE)        裸设备是指未创建文件系统的磁盘分区(raw partition)或逻辑卷(raw logical volume),应用程序直接通过一 个字符设备驱动程序对它进行访问。
1062 0
|
6月前
|
Unix Shell Linux
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
69 2
|
27天前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。
|
3月前
|
Unix Linux 程序员
Unix:Linux的“逗趣祖师爷”与它的不凡传承
在科技长河中,Unix犹如一颗恒星,既是历史见证者也是未来的启发者。1969年,因程序员肯·汤普森想在他的PDP-7上玩“Space Travel”游戏,意外创造了Unix,以简洁优雅的代码改变了操作系统的世界。进入90年代,林纳斯·托瓦兹受Unix启发,开发了开源免费的Linux,像是Unix调皮的孙子,不仅继承其精髓还增添了开放共享的精神。Unix与Linux之间的传承,就像是智者与追蝶孩童的故事,充满了岁月的智慧与新生的活力,提醒我们科技传奇往往源于不起眼的小事。下次使用Linux时,不妨会心一笑吧!
53 0