阅读Linux0.11——main.c

目录:

1、前言
1、main.c功能描述
2、main.c代码分析

一、前言

 这个前言的目的就是解释清楚理解main运行的一些基本的概念。如下:

1、Linux0.11也是有CPU负载均衡的的

  很神奇吧,最开始Linus已经考虑了CPU负载均衡这一点了,先别急着惊叹,我来跟你说说是怎么一回事。
 基本上现在的Linux里,都有0号进程,进程调度的时候,如果检测到就绪队列为空,就会一直执行0号进程,因此我们也把0号进程称为闲逛进程。当然我们现代操作系统所实现的负载均衡并不只是执行进程0,因为我们目前都是多核CPU,有可能这一个CPU上没有等待执行的进程的,别的CPU上还有呢?这个不是我们今天的重点,这部分的分析,我在前面写进程调度源码分析的时候已经说过了,这个不再赘述。

 但是当时Linus实现的CPU负载均衡就只是把进程0放在CPU上执行。
 进程0是在什么时候创建的呢?我们前面分析boot的时候提过,boot执行完了以后把控制权交给了init模块(也就是开始执行main.c),这时候是在0特权级下运行的,0特权级也就是最高特权级。main.c执行完了内核各个模块的初始化工作以后,需要切换到3特权级下,等切换完了以后main.c就是以进程0的身份运行的。
 我们知道Linux创建进程是用函数fork创建的,执行的操作基本上就是复制父进程的各个资源,例如:子进程复制父进程的栈空间,子进程复制父进程的数据段等等。进程0是没有父进程的,所以进程0是怎么创建的呢?我们在本文第三个部分——分析源码的时候会讲,这里暂且不提。这里只需要记住,进程0负责创建进程1,进程1创建进程2和第一个进程组。

1、进程0 不能使用栈空间

 这个没什么要理解的,我直接告诉你进程0在创建进程1也就是我们常说的init进程之前,不能使用栈空间,下面是解释。
 前面有一篇讲请求分页机制的博文里面讲过:从内核空间fork的进程,并没有写时复制机制(COW),是创建的时候就直接分配的。所以进程1创建的时候,是直接复制了进程0的用户栈空间,为了避免进程0的栈中有进程1并不想要的多余的信息,在fork之前,进程0不能用自己的栈,这也就意味着,进程0不能调用函数。所有的函数调用都是触发中断来实现的。

2、_exit和exit的区别

3、TSS和特权级转移

 在讲TSS之前我们先说一说特权级这个问题。计算机的特权级都是为了限制非法访问的,比如说低特权级的进程访问了高特权级的资源。特权级有4个,分别是0,1,2,3,数字越小特权级越高。在Linux中内核的特权级最高是0,用户特权级最小是3。我们常常会有这样的进程,在用户特权级下运行,但是调用了一个系统调用,需要陷入内核。这时候就需要特权级切换。

 TSS(Task State Segment),任务状态段,用于任务切换的时候保存现场信息的数据结构。TSS段有很多的字段,主要可以分为:寄存器保存区域、堆栈指针保存区域和其他的预留区域等等。说起来很复杂,本文并不全部介绍,只是说几个跟我们主题有关的字段——堆栈指针保存区域。
 每一个TSS保存的堆栈不能超过四个,这是因为同一个任务在同一个特权级下最多只能有一个堆栈,因为只有四个特权级,所以最多只能有四个堆栈(ss0,sp0,ss1,sp1,ss2,sp2,ss3,sp3)。

 我们前面说了特权级切换,特权级切换有两种情况:
* 低特权级向高特权级切换,就像我们刚刚举得系统调用例子
* 高特权级向低特权级切换,我们本文遇到的情况。
 低特权级向高特权级切换的时候,CPU自己把当前特权级的堆栈指针(ss和sp)压入高特权级所对应的栈中。比如说我们刚刚的例子中,是从特权级3切换到特权级0,则特权级3的堆栈指针信息压入TSS中的ss0和sp0中。
 所以如果执行iret或retf指令从低特权级切换到高特权级的时候,只需要从高特权的堆栈中拿出低特权级对应的堆栈信息即可。

一、main.c功能描述

 setup.s程序读出了系统的一些硬件信息,存在主存里。main.c程序读这些参数,然后调用函数通过分页机制管理主存、完成硬件初始化、创建0号进程等等,整个内核的初始化是main.c函数来完成的。
 我们知道新建进程都是调用fork函数实现的,那下面我们看看第一个进程是怎么创建出来的。

二、main.c代码分析

 下面我们分析main.c的代码:

static inline _syscall0(int,fork)          
static inline _syscall0(int,pause)          
static inline _syscall1(int,setup,void *,BIOS)              
static inline _syscall0(int,sync)

 这几行代码以内嵌宏代码的形式实现了fork、pause、setup和sync函数的调用,我们前面说过了进程0不能使用栈空间,所以意味着进程0在不能直接调用函数。_syscall0通过触发0x80(系统调用)中断实现了函数的调用。

ROOT_DEV = ORIG_ROOT_DEV;
    drive_info = DRIVE_INFO;
    memory_end = (1<<20) + (EXT_MEM_K<<10);             
    memory_end &= 0xfffff000;                       
    if (memory_end > 16*1024*1024)                  
        memory_end = 16*1024*1024;
    if (memory_end > 12*1024*1024)
        buffer_memory_end = 4*1024*1024;
    else if (memory_end > 6*1024*1024)
        buffer_memory_end = 2*1024*1024;
    else                                    /
        buffer_memory_end = 1*1024*1024;
    main_memory_start = buffer_memory_end;      
#ifdef RAMDISK
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif

先得到根设备的设备号,然后设置内存大小,内存大小忽略不到4k字节的内容(一页大小是4k字节)。如果内存大小超过了16M字节则按16M字节计。

相关文章
相关标签/搜索