珍贵的linux0.01内核

晨曦之光 发布于 2012/04/10 15:02
阅读 3K+
收藏 1

我最开始接触linux内核是2.4.16版本,然后就一直跟踪到了2.6.27版本,今天是2008年11月5号,一年前我好奇地想知道最开始的 linux内核是个什么样子,然后如何一步一步变成今天的这个样子,于是我就开始找原代码,呵呵,已经被命名为oldlinux了,然后看了一下内核,十 分小,李纳斯本人的注释写得也十分高笑,一切那么纯真,不出半天时间就可以通读全部代码,当然前提是你对x86平台十分熟悉,熟悉分段,熟悉分页,熟悉保 护模式。
0.01是最原始的版本了,它和现在linux的最大不同可能就是内存管理这一块了,至于进程调度则直接受内存管理的影响,至于算法嘛,那是个没完没了的 概念,说不定明天cfs调度算法就可能被淘汰,关键是框架。如果你有更好的进程调度算法,你可以很容易的更换现有的内核进程调度算法,但是0.01版本的 linux却是一个耦合性相当高的内核,不过那毕竟是李纳斯为x86专门写的。
0.01中,内核严重依赖了分段这种已经没有什么意义的技术,使intel害苦了当年的李纳斯啊,如果没有分段,那么李纳斯也不会给内核强加那么多的限制。有谁能证明分页不是一种障碍呢?
在分段模型中,段选择子中有段描述符索引和相应的特权环,linux0.01的全局描述符有256项,每个进程用两项,一个ldt,一个tss,还有中断 描述符,而且0.01内核强制每个进程的虚拟地址空间为64m,那么一共就可以拥有4g/64m=64个进程,所有进程共享一个页目录,区别之处就是用不 同的页目录项,因为有64m的内存,因此用16个页目录项,这种方式在当今很很让人想不通的,当然在嵌入式设备上还在用这种方式呢。内核将不同的进程映射 到不同的“线性空间”,然后再通过不同的页目录项映射到物理内存,这里有必要说一下线性空间的概念,intel的分页机制使得cpu拥有4g大小的线性空 间,从0到4g,而程序中用到的地址是虚拟地址而不是线性地址,比如进程0和进程1 的虚拟地址空间都是0到64m,但是由于所有进程共享这4g的线性空间,则进程0拥有0到63m的线性空间,而进程1拥有接下来64m到127m的线性空 间,分段机制把地址从虚拟空间映射到线性空间而分页机制将地址从线性空间映射到物理地址空间,0.01内核就是完全按照这种映射接力来完成的,不知道这是 不是intel的意愿。现在,不管是linux还是windows都是使用平坦模式来绕开了分段,这样每个进程都可以独享4g的虚拟地址空间 了,intel提供了一种机制可以使得cpu寻址4g空间,而且提供cr3页目录寄存器,这就是说intel的本意是让进程独享4g空间的,可是估计当时 李纳斯认为没有那需求,而且共享页表可以省去切换开销,所以就选择了这种现在看来很奇怪的方式。下面分析一些代码,看看一切多么的纯真:
1.schedule函数:

void schedule(void)

{

         int i,next,c;

         struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

//扫描所有进程,唤醒一切已经有信号附身的进程

         for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

                 if (*p) {

                         if ((*p)->alarm && (*p)->alarm < jiffies) {

                                         (*p)->signal |= (1<<(SIGALRM-1));

                                         (*p)->alarm = 0;

                                 }

                         if ((*p)->signal && (*p)->state==TASK_INTERRUPTIBLE)

                                 (*p)->state=TASK_RUNNING;

                 }

/* this is the scheduler proper: */

//原始的基于优先级的调度算法

         while (1) {

                 c = -1;

                 next = 0;

                 i = NR_TASKS;

                 p = &task[NR_TASKS];

                 while (--i) {

                         if (!*--p)

                                 continue;

                         if ((*p)->state == TASK_RUNNING && (*p)->counter > c)

                                 c = (*p)->counter, next = i;

                 }

                 if (c) break;

                 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

                         if (*p)

                                 (*p)->counter = ((*p)->counter >> 1) +

                                                 (*p)->priority;

         }

         switch_to(next);//这个函数中根本没有切换cr3,所以从这里也可以看出所有进程共享页目录

}

和2.6.27的对比一下,能吓人一大跳呢!这仅仅是一个开始
2.sleep_on函数

void sleep_on(struct task_struct **p)

{

         struct task_struct *tmp;

         if (!p)

                 return;

         if (current == &(init_task.task))

                 panic("task[0] trying to sleep");

         tmp = *p;

         *p = current;

         current->state = TASK_UNINTERRUPTIBLE;

         schedule();//直接调度

         if (tmp)

                 tmp->state=0;

}

呵呵,著名的list_head还没有出生,也就只能用这种方式了。0.01在慢慢成长着,虽然list_head没有出生,但是它已经是需求了,于是1.0代码中的相应函数出现了一个结构:

struct wait_queue {

         struct task_struct * task;

         struct wait_queue * next;

};

用 next指针将同类型的结构串在一起,当很多结构都要这么做的时候,list_head就呱呱坠地了,好可爱!linux内核就是从这个很不起眼的小东西 慢慢变成巨人的,这个内核很小巧,读了以后隐约能感觉到为什么linux没有把内存本身纳入虚拟内存管理范畴,一直到今天,李纳斯从一开始就认为,不管分 段也好还是分页也罢,很重要的意义就是提高安全性,而这种保护是针对用户进程的不是针对内核的,内核要做到快速管理这一切,因此他认为有必要尽量想办法在内核中避开分页带来的麻烦,比如操作页表时内核也要遵循页面映射原则,得先读cr3的虚拟地址,然后...于是就干脆让内核虚拟地址和物理地址来个一一线 性映射,这样操作会更加便捷,一旦出错,只怪操作系统本身就行了。就这样,从一个很简单的原则,linux演变成了很稳定很安全的系统,而windows 就不是这么想的,它尽量让一切都接受内存管理器的管理,这样不就更安全了吗?连内核都要页保护了,本意是好的,但是有必要吗?一个用户进程侵入别的进程地址空间页保护机制会阻止,内核还有侵入的别的什么空间的概念吗?更具讽刺意义的是,微软动不动就更改一个结构的虚拟空间地址,就是它自己造成的,怕什么, 它把很多内核结构也映射到了固定的虚存空间地址,还愁黑客们不黑它吗?


原文链接:http://blog.csdn.net/dog250/article/details/5303328
加载中
返回顶部
顶部