Chinaunix首页 | 论坛 | 博客
  • 博客访问: 537550
  • 博文数量: 120
  • 博客积分: 3030
  • 博客等级: 中校
  • 技术积分: 1445
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-05 01:00
文章存档

2011年(1)

2009年(2)

2008年(32)

2007年(33)

2006年(52)

我的朋友

分类: LINUX

2006-04-05 21:28:40

Linux 发展至今日,内核稳定版版本号已是 2.6.x,相应的这个内核源码也趋于复杂化,不利于刚开始看内核源码的新手。所以我选择 Linux0.11 版本,并随时记录心得笔记。

参考网站及书籍有:

  • 《Linux 0.11 源代码分析》
  • 《Linux 内核 0.11(0.95) 详细注释》

Essential - 基础知识

Interrupt - 中断

中断号可由 0-255 之间的数来表示。其中 int0-int31(0x00-ox1f) 中断由 Intel 公司固定设定或保留,称为软件中断(异常)。

Linux系统使用 int32-int47(0x20-0x2f) 来对应 IBM PC 中的两片级联的 8259A (从片的 INT 接主片的IR2) 发出的中断请求信号 IRQ0-IRQ15。并把系统调用 (system call) 中断设置为 int128 (0x80)。

Clocking - 定时

在 Linux0.11 中,时钟芯片 8253 被设置为每隔 10 毫秒就发出一个时钟中断信号 (IRQ0),然后调用时钟中断处理程序 (time_interrupt)。然后从被中断程序的段选择符中取得当前 CPL 值,作为调用的 do_timer() 的参数。

如果当前进程的时间片值减1后已经为 0,并且 CPL>0, do_timer() 就会调用 schedule() 函数切换到其他进程。但是如果CPL=0,那么 do_timer() 会立即退出,这保证了系统运行在内核态时不可被抢占。

Process - 进程

Linux0.11 最多允许有 64 个进程,这个最大值由 /include/linux/sched.h 文件中的 NR_TASKS 定义,除第一个进程是“手工”创建的以外,其他进程都是由它们的“创建者”通过 fork() 系统调用创建的。

Linux 的进程是抢占式的,被抢占的进程仍然处于 TASK_RUNNING 状态,只是暂时没有被 CPU 运行。

在进行进程调度时,基本按照下面的顺序:

schedule() 扫描进程数组选择剩余时钟滴答数最大的进程
                       ||
                       \/
若时钟数都用完则重新计算所有进程(包括睡眠进程)的时钟滴答数
                       ||
                       \/
选不出则运行进程 0,进程 0 调用 pause() 设置自己为可中断的睡眠状态再调用 schedule()

Memory - 内存分配

Linux 下的物理内存分布模式是:内核程序位于物理内存的开始部分;其后是高速缓冲区,用于缓冲读取块设备时的数据;最后是供所有程序分配使用的主内存区。对于含有 RAM 虚拟盘的系统,则在主内存前面还有一部分空间供 RAM 虚拟盘使用。

Segment Descriptor - 段描述符

数据段是不可执行的,但总是可读的。代码段总是不可写的,若要对代码段进行写入操作,则需要使用别名操作,即用一个可写的数据段描述符来描述该代码段,然后对此数据段进行写操作。

Startup - 启动部分

boot/bootsect.s

文件 bootsect.s 一开始是由 BIOS 启动程序装载到 0x7c000 字节处,然后它把自己移动到 0x90000 处,再将后面的2K字节大小的setup文件装入到0x90200处。

如何移动

这里涉及到了汇编知识。虽然查找资料也能看懂,这里顺便解释一下。看下面这段代码(L.46-62):

_start:
    mov ax,#BOOTSEG
    mov ds,ax
    mov ax,#INITSEG
    mov es,ax
    mov cx,#256
    sub si,si
    sub di,di
    rep
    movw
    jmpi    go,INITSEG
go: mov ax,cs
    mov ds,ax
    mov es,ax
! put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ! arbitrary value >>512

上面的代码执行过程是:

  1. 把 BOOTSEG 的值 (0x07c0) 放入 ds,把 INITSEG 的值 (0x9000) 放入 es 中。
  2. 把 si,di 中的值清0。
  3. 汇编指令 rep 的作用是使 rep 后的串指令重复执行,每执行一次 cs=cs-1,直至 cs=0 时退出 rep。这里的意思就是执行 movw 指令256次(cx的值)。
  4. 而 movw 每执行一次,就执行:[di] = [si],将位于 ds 段的由 si 所指出的存储单元的字节传送到位于 es 段的由 di 所指出的存储单元,再修改 si 和 di,从而指向下一个元素。
  5. 这样的话就把 0x07c0:0x0000 至后 256 字节的内容移到了 0x9000 中 :0x0000 去。
load_setup:
    mov dx,#0x0000          ! drive 0, head 0
    mov cx,#0x0002          ! sector 2, track 0
    mov bx,#0x0200          ! address = 512, in INITSEG
    mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors
    int 0x13                ! read it
    jnc ok_load_setup       ! ok - continue
    mov dx,#0x0000
    mov ax,#0x0000          ! reset the diskette
    int 0x13
    j   load_setup

在把 setup.s 文件装入内存时,使用了 13 号中断。在中断时, ah=0x02 表示读磁盘内容到内存, al=SETUPLEN=4 为要读出的扇区数量。

总的说来,就是读 第0号驱动器 (硬盘的话 dx 第7位要为1)第0号磁头第0号磁道第2号扇区,数据缓冲区地址为 es:bx=0x9000:0x0200 。如果这段执行出错则 cf=1 ,复位磁盘后再跳到装入 setup.s 的地方继续尝试装入。

作为一个引导程序,一定要设置 boot_flag 标记:

boot_flag:
    .word 0xAA55

因为 BIOS 只有在发现了 0xAA55 这个标记后才能确定这个引导程序是正确的。

boot/setup.s

根据 Linus 在 setup.s 文件头的注释, setup.s 是负责从 BIOS 获取系统信息,并且存放在系统内存的适当位置。

使我感到奇怪的是,在复制硬盘参数表时。Linux 内核先取得第一块硬盘和第二块硬盘的信息,再检查第二块硬盘是否存在,若不存在则要将第二个参数表清零。那为什么不先检查再进行复制呢?这样如果第二块不存在话,不是能节省一次复制操作吗?

在对中断控制器 8259A 进行初始化部分,对以下代码:

mov al,#0x11        ! initialization sequence
out #0x20,al        ! send it to 8259A-1

《Linux0.11源代码分析》中说设置主控电路的 ICW1 为电平触发,级联模式。但是据《现代微机原理及接口技术》P155 上面说,D3 为 0 应该是边沿触发方式才对。

在对 ICW2 设置时,

mov al,#0x20        ! start of hardware int's (0x20)
out #0x21,al

由于 ICW2 的低 3 位用于 8 位机,所以只要对高 5 位进行设置,而低3位对应 8259A 外部引脚 IR7-IR0 的编码。这里设置的高 5 位为 00010D。因为 IR(i) 的中断类型号 = ICW2 的高 5 位 + i,所以中断类型号是从 0x20 开始的。

代码

.word 0x00eb,0x00eb

的作用与教科书上的

jmp short $+2

差不多,都是因为 I/O 的端口延时。 .word 伪指令表示其后每个数据项占 2 个字节。

boot/head.s

和该目录下其他两个汇编语言文件不同, head.s 使用的是 AT&T 格式的汇编语言,需要用 GNU as 来编译。该文件会被编译连接在 system 模块的最前面部分,主要进行硬件设备的探测设置和内存管理页面的初始设置工作。

init/main.c

系统进入保护模式后,就开始执行系统初始化程序 init/main.c ,这其中 /include/asm/system.h 中的宏 move_to_user_module 会把 main.c 从内核态移动到用户态。在这个移动过程中,该宏手工在堆栈中构建中断返回指令所需要的内容,这样当运行 iret 时将导致 CPU 从特权级 0 跳到特权级 3 上。
阅读(2664) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~