Linux-0.11操作系统实验8理论-proc文件系统的实现

L28 生磁盘的使用

  • 磁盘定义

    磁盘利用了电流的磁效应,对一些电信号进行磁化,保存在磁盘中,用来表示一些信息。
    在这里插入图片描述
    ]
    每个盘面上都有磁道、扇区这2个概念。先从磁道说起,每个盘面上有多条圆环,也就是磁道,这些磁道中存储着磁盘的信息。每个磁道上又被分为若干个扇区,磁盘每次的读写数据的操作单位就是这个扇区。每个扇区的大小一般为512字节大小。

    磁盘是由多个盘片叠加在一起的,由于磁盘每次进行读写的开始都要移动机械的磁臂到对应的盘面、磁道上,这个过程很浪费时间。所以为了提高效率,把盘面与磁道抽象成另外2个概念,就是柱面、磁头。
    在这里插入图片描述
    其中,GAP 是间隔字段,用于起隔离作用。通常 GAP是 12 字节的 0。每个扇区地址场的地址字段存放着相关扇区的柱面号、磁头号(面号)和扇区号,因此通过读取地址场中的地址信息就可以唯一地确定一个扇区。

    上图中的cyl就是柱面的意思,就是图中蓝色区域的圆柱体侧面,也称为柱面。上图中的head的范围就是柱面的底部到顶部范围之间。这样一旦确定cylhead,就确定了哪个盘面和哪个磁道了,这样就可以找到对应的扇区了。为什么把盘面、磁道换成柱面、磁头查找会提高效率呢?根本原因是往往磁盘中盘面很多,每个盘面的承载信息有限,需要移动很多次磁臂才能记录信息。而用柱面的概念的话,就利用了每个盘面磁道的有限性,每次记载数据只需移动很少次的磁臂就可以搞定,提高了读写效率。sec表示扇区。

  • 磁盘使用
    在这里插入图片描述

    要往磁盘的某个扇区写一个字节,那么我们需要知道这个扇区对应的哪个柱面中的哪个磁头。知道了这些参数后,然后把这些参数传到磁盘控制器,磁盘控制器是 CPU 与驱动器之间的逻辑接口电路,它从 CPU 接收请求命令,向驱动器发送寻道、读/写和控制信号,并且控制和转换数据流形式。磁盘控制器再根据这些参数进行驱动磁盘写数据。下面看一段代码:

    // linux-0.11/kernel/blk_drv/hd.c
    void do_hd_request(void)
    {
    	...
    	// 前面blabla一大堆,就是要找出dev,nsect,
    	// sec,head,cyl,WIN_WRITE,&write_intr这些数据
    	// 然后传送这些参数到磁盘控制器
    	if (CURRENT->cmd == WRITE) {
    		hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
    	}
    	...
    }
    

    上面这段代码就是磁盘读写的请求函数,里面最后调用了hd_out函数,在调用之前传入了柱面号、磁头号、扇区号等核心参数。下面看下do_out函数:

    static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
    		unsigned int head,unsigned int cyl,unsigned int cmd,
    		void (*intr_addr)(void))
    {
    	register int port asm("dx");
    
    	if (drive>1 || head>15)
    		panic("Trying to write bad sector");
    	if (!controller_ready())
    		panic("HD controller not ready");
    	do_hd = intr_addr;
    	// 这些outb_p接口就是往外设传送数据的
    	// 也是cpu中磁盘驱动的核心代码
    	outb_p(hd_info[drive].ctl,HD_CMD);
    	port=HD_DATA;
    	outb_p(hd_info[drive].wpcom>>2,++port);
    	outb_p(nsect,++port);
    	outb_p(sect,++port);
    	outb_p(cyl,++port);
    	outb_p(cyl>>8,++port);
    	outb_p(0xA0|(drive<<4)|head,++port);
    	outb(cmd,++port);
    }
    

    上面这段代码就是磁盘驱动的核心代码。从这里不难看出,如果想读写磁盘,就传入磁盘的柱面、磁头、扇区、缓冲区等参数后,就可以读写磁盘啦。但是这样呢比较麻烦,在每次读写磁盘的时候。有一种简单的方法,就是把柱面、磁头、扇区包装成一个磁盘块的概念,接下来看下磁盘块。

  • 磁盘块封装CHS

    通过磁盘块(block)参数怎样计算出柱面号(c)、磁头号(h)、扇区号(s)呢?
    我们先来考虑一种情况,假如我们使用block参数读写磁盘,那么每次读写磁盘的block数可能有很多个,为了磁盘读写效率的提升,我们要保证block相邻的磁盘块可以快速读写。如何保证这样的效果呢?如果把相邻的block块读写近似映射成相邻的扇区号读写,那么达到上面的效果了。
    所以可以通过这样一个公式来推算chs
    block = c * (heads * sectors) + heads * sectors + s
    公式中的heads是一个磁盘的物理磁头总数量;公式中的sectors是一个磁盘盘面的物理扇区总数量。给出block,我们可以逆推出c、h、s这些参数。(s=block%sectors c=block/( heads * sectors))这样的话我们就可以通过一个block参数进行磁盘读写啦。还有一个block可以是若干个扇区的,这取决与程序怎样编写。如linux0.11的盘块就是2个扇区即1K

  • 磁盘的访问时间

    磁盘的访问时间 = 写入控制器时间 + 寻道时间 + 旋转时间 + 传输时间
    其中寻道时间(移动磁臂)大约占12ms到8ms。

  • 多进程通过队列使用磁盘

    通过传递block这个参数就可以在磁盘中指定的一些扇区进行读写操作,操作系统会有一个请求队列缓存各种进程的磁盘请求,进而涉及到磁盘调度算法。

  • 磁盘调度算法

    1. FCFS先来先服务
    2. SSFT短寻道优先,存在饥饿问题
    3. SCAN电梯算法,每个请求都有处理机会
  • 电梯算法详解

    相对保证了多个进程使用磁盘既准确又快速

    // linux-0.11/kernel/blk_drv/ll_rw_blk.c
    static void add_request(struct blk_dev_struct * dev, struct request * req)
    {
    	struct request * tmp;
    
    	req->next = NULL;
    	cli();
    	if (req->bh)
    		req->bh->b_dirt = 0;
    	if (!(tmp = dev->current_request)) {
    		dev->current_request = req;
    		sti();
    		(dev->request_fn)();
    		return;
    	}
    	// 调度算法的核心代码,分析下面代码,我们先要知道
    	// IN_ORDER是什么意思?可以看下文IN_ORDER的介绍
    	// 就可以知道是比较tmp与req中柱面号大小的
    	//下面从这段代码可以看出,当符合这两种情况时就跳出循环
    	// 并将req插入tmp和next之间。一种情况是当tmp的柱面号
    	// 小于req的柱面号,且req小于next的柱面号,这种其实
    	// 就相当于电梯上升时候的情况;还有一种情况是当tmp的柱面号
    	// 小于next的柱面号,且req小于next的柱面号,这种其实
    	// 就相当于电梯下降时候的情况。不管这两种任何一种情况,
    	// 下一步磁盘读写都会进入req这个对象上,否则就按照原有的
    	// 队列进行磁盘读写。这样的话就能更高效的使用磁盘
    	for ( ; tmp->next ; tmp=tmp->next)
    		if ((IN_ORDER(tmp,req) || 
    		    !IN_ORDER(tmp,tmp->next)) &&
    		    IN_ORDER(req,tmp->next))
    			break;
    	req->next=tmp->next;
    	tmp->next=req;
    	sti();
    }
    
    

    IN_ORDER的代码如下:

    // 核心思想是比较s1与s2中的sector大小,其实就是比较s1与s2中的柱面号的大小
    // 因为柱面的寻找是耗时最长的,所以要保证寻找柱面也即寻道的时间不能太长,
    // 就要在寻道上面做优化处理
    #define IN_ORDER(s1,s2) \
    ((s1)->cmd<(s2)->cmd || ((s1)->cmd==(s2)->cmd && \
    ((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
    (s1)->sector < (s2)->sector))))
    

L29 从生磁盘到文件

  • 从文件得到盘块号

    文件:建立字符流到盘块集合的映射关系,完成字符流位置算出盘块号

    连续结构实现文件映射,比较适合读写,只需要起始块号和块数

    链式结构实现文件映射,读写存储慢但是适合动态变化

    索引结构实现文件映射,inode存着文件信息,既适合读写又适合增长

    实际系统使用多级索引

L30 文件使用磁盘的实现

  • 代码分析:

  • 得到磁盘号代码:

    int do_execve(unsigned long * eip,long tmp,char * filename,
    	char ** argv, char ** envp)
    {
    	...
    	// 这里的关键代码是bread这个读磁盘的接口,我们发现函数
    	// bread的第二个参数是某个文件的设备号
    	if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
    		retval = -EACCES;
    		goto exec_error2;
    	}
    	...
    }
    struct buffer_head * bread(int dev,int block)
    {
    	struct buffer_head * bh;
    	// 这句代码就是获取携带磁盘块的指针,也就是获取bh
    	if (!(bh=getblk(dev,block)))
    		panic("bread: getblk returned NULL\n");
    	if (bh->b_uptodate)
    		return bh;
    	// 读写磁盘
    	ll_rw_block(READ,bh);
    	// 唤醒下一个磁盘读写请求队列,如果有的话。
    	wait_on_buffer(bh);
    	if (bh->b_uptodate)
    		return bh;
    	brelse(bh);
    	return NULL;
    }
    
  • 从上面看出进程获取block是通过文件系统的inode来获取的。下面当获取到block后,我们会进入ll_rw_block这个函数:

    void ll_rw_block(int rw, struct buffer_head * bh)
    {
    	unsigned int major;
    
    	if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
    	!(blk_dev[major].request_fn)) {
    		printk("Trying to read nonexistent block-device\n\r");
    		return;
    	}
    	// 这里会进入这句函数,来请求磁盘的读写
    	make_request(major,rw,bh);
    }
    
    static void make_request(int major,int rw, struct buffer_head * bh)
    {
    /* fill up the request-info, and add it to the queue */
    	req->dev = bh->b_dev;
    	req->cmd = rw;
    	req->errors=0;
    	req->sector = bh->b_blocknr<<1;
    	req->nr_sectors = 2;
    	req->buffer = bh->b_data;
    	req->waiting = NULL;
    	req->bh = bh;
    	req->next = NULL;
    	// 这个时候将解析好的req加入到磁盘读写请求队列中
    	add_request(major+blk_dev,req);
    }
    
  • 从上面可以看出我们加完req后,就唤醒另一个磁盘请求队列进程,然后睡眠自己。那么磁盘控制器是如何访问这些队列来读写磁盘呢?当然是通过磁盘中断。

    hd_interrupt:
    	pushl %eax
    	pushl %ecx
    	pushl %edx
    	push %ds
    	push %es
    	push %fs
    	movl $0x10,%eax  // 进入内核空间
    	mov %ax,%ds
    	mov %ax,%es
    	movl $0x17,%eax  // 重置fs
    	mov %ax,%fs
    	movb $0x20,%al
    	outb %al,$0xA0		# EOI to interrupt controller #1
    	jmp 1f			# give port chance to breathe
    1:	jmp 1f
    1:	xorl %edx,%edx
    	xchgl do_hd,%edx
    	testl %edx,%edx
    	jne 1f
    	movl $unexpected_hd_interrupt,%edx
    1:	outb %al,$0x20
    	// 中断处理函数do_hd入口
    	call *%edx		# "interesting" way of handling intr.
    	pop %fs
    	pop %es
    	pop %ds
    	popl %edx
    	popl %ecx
    	popl %eax
    	iret
    
  • 从上面可以看出执行do_hd这个函数指针,这个函数指针又会指向谁呢?从下面代码可以看出可以执行到read_intr。其实吧,这个do_hd可以指向磁盘读写的中断处理函数,接下来分析一下读的处理函数。

    static void read_intr(void)
    {
    	// 获取上次读磁盘的状态,看看有没有错误之类的
    	if (win_result()) {
    		bad_rw_intr();
    		do_hd_request();
    		return;
    	}
    	// 这个就是读的核心代码,向外设的HD_DATA数据端口读取数据
    	// 每次读取一个扇区512个字节的信息
    	port_read(HD_DATA,CURRENT->buffer,256);
    	CURRENT->errors = 0;
    	CURRENT->buffer += 512;
    	CURRENT->sector++;
    	// 如果没读完,那就继续读,知道读完为止
    	if (--CURRENT->nr_sectors) {
    		do_hd = &read_intr;
    		return;
    	}
    	// 读完后,唤醒另一个等待进程啦
    	end_request(1);
    	// 另一个等待进程继续做磁盘读写请求处理
    	do_hd_request();
    }
    
  • 至此一个进程的磁盘请求读写已经完成,后面的进程请求以此类推。

  • 总结:
    在这里插入图片描述

L31 目录与文件系统

  1. 从文件路径名/.../.../...fd找到FCB中的inode
  2. 再由inode找到盘块号
  3. 将盘块号加入电梯队列
  4. 根据电梯队列的盘块号算出c、h、s
  5. 使用outp发送到磁盘控制器
  6. 磁盘控制器驱动马达磁生电生成数据,使用磁盘
  • 通过路径名找到FCB中的inode

    首先找到根目录,根目录是一个文件,在FCB数组中有固定的位置,根据根目录中的索引项找到根目录对应的数据块,也就找到了根目录下的目录项、文件名、以及对应的FCB的地址,然后以此类推一层层找到最终的FCB的inode。
    在这里插入图片描述

  • 系统自举要格式化磁盘,具体的格式样子如下所示:
    在这里插入图片描述
    挂载使用磁盘mount就是去读取超级块,以便于找到根目录,从而实现磁盘文件系统的使用。

  • 完全映射下的磁盘使用
    在这里插入图片描述

L32 目录解析代码实现

  • open函数的核心就是找到 inode

  • get_dir完成真正的目录解析
    在这里插入图片描述

  • 从根目录开始进行目录解析。

  • iget读取inode,读磁盘一定要知道盘块号。

  • 开始目录解析—find_entry (&inode,name,…,&de)

操作系统全图

操作系统是管理硬件的软件,管理CPU利用多进程结构交替执行,CPU充分忙碌,执行过程中会有计算指令,也会有类似*p=7的指令,即将7放入内存,就要进行重定位和地址翻译,填写段表页表,内存就使用起来了;也会执行到open,read等函数操作文件,就会进行目录解析,算出磁盘块,发到电梯队列,进行磁盘的使用,进而IO以及文件系统就使用起来了;整个操作系统就在多进程带动下,就使得文件系统,IO操作等开始同时使用,操作系统也就运行起来了。
在这里插入图片描述
完结撒花!!!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
! ! SYS_SIZE is the number of clicks (16 bytes) to be loaded. ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current ! versions of linux ! SYS_SIZE 是要加载的节数(16 字节为1 节)。0x3000 共为 1 2 3 4 5 6 0x7c00 0x0000 0x90000 0x10000 0xA0000 system 模块 代码执行位置线路 0x90200 ! 0x30000 字节=192 kB(上面Linus 估算错了),对于当前的版本空间已足够了。 ! SYSSIZE = 0x3000 ! 指编译连接后system 模块的大小。参见列表1.2 中第92 的说明。 ! 这里给出了一个最大默认值。 ! ! bootsect.s (C) 1991 Linus Torvalds ! ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves ! iself out of the way to address 0x90000, and jumps there. ! ! It then loads 'setup' directly after itself (0x90200), and the system ! at 0x10000, using BIOS interrupts. ! ! NOTE! currently system is at most 8*65536 bytes long. This should be no ! problem, even in the future. I want to keep it simple. This 512 kB ! kernel size should be enough, especially as this doesn't contain the ! buffer cache as in minix ! ! The loader has been made as simple as possible, and continuos ! read errors will result in a unbreakable loop. Reboot by hand. It ! loads pretty fast by getting whole sectors at a time whenever possible. ! ! 以下是前面这些文字的翻译: ! bootsect.s (C) 1991 Linus Torvalds 版权所有 ! ! bootsect.s 被bios-启动子程序加载至0x7c00 (31k)处,并将自己 ! 移到了地址0x90000 (576k)处,并跳转至那里。 ! ! 它然后使用BIOS 中断将'setup'直接加载到自己的后面(0x90200)(576.5k), ! 并将system 加载到地址0x10000 处。 ! ! 注意! 目前的内核系统最大长度限制为(8*65536)(512k)字节,即使是在 ! 将来这也应该没有问题的。我想让它保持简单明了。这样512k 的最大内核长度应该 ! 足够了,尤其是这里没有象minix 中一样包含缓冲区高速缓冲。 ! ! 加载程序已经做的够简单了,所以持续的读出错将导致死循环。只能手工重启。 ! 只要可能,通过一次取取所有的扇区,加载过程可以做的很快的。 .globl begtext, begdata, begbss, endtext, enddata, endbss ! 定义了6 个全局标识符; .text ! 文本段; begtext: .data ! 数据段; begdata: .bss ! 堆栈段; begbss: .text ! 文本段; SETUPLEN = 4 ! nr of setup-sectors ! setup 程序的扇区数(setup-sectors)值; BOOTSEG = 0x07c0 ! original address of boot-sector ! bootsect 的原始地址(是段地址,以下同); INITSEG = 0x9000 ! we move boot here - out of the way ! 将bootsect 移到这里 -- 避开; SETUPSEG = 0x9020 ! setup starts here ! setup 程序从这里开始; SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). ! system 模块加载到0x10000(64 kB)处; ENDSEG = SYSSEG + SYSSIZE ! where to stop loading ! 停止加载的段地址; ! ROOT_DEV: 0x000 - same type of floppy as boot. ! 根文件系统设备使用与引导时同样的软驱设备; ! 0x301 - first partition on first drive etc ! 根文件系统设备在第一个硬盘的第一个分区上,等等; ROOT_DEV = 0x306 ! 指定根文件系统设备是第2 个硬盘的第1 个分区。这是Linux 老式的硬盘命名 ! 方式,具体值的含义如下: ! 设备号=主设备号*256 + 次设备号(也即dev_no = (major<>512 ! 由于代码段移动过了,所以要重新设置堆栈段的位置。 ! sp 只要指向远大于512 偏移(即地址0x90200)处 ! 都可以。因为从0x90200 地址开始处还要放置setup 程序, ! 而此时setup 程序大约为4 个扇区,因此sp 要指向大 ! 于(0x200 + 0x200 * 4 + 堆栈大小)处。 ! load the setup-sectors directly after the bootblock. ! Note that 'es' is already set up. ! 在bootsect 程序块后紧根着加载setup 模块的代码数据。 ! 注意es 已经设置好了。(在移动代码时es 已经指向目的段地址处0x9000)。 load_setup: ! 68--77 行的用途是利用BIOS 中断INT 0x13 将setup 模块从磁盘第2 个扇区 ! 开始读到0x90200 开始处,共读4 个扇区。如果读出错,则复位驱动器,并 ! 重试,没有退路。INT 0x13 的使用方法如下: ! 读扇区: ! ah = 0x02 - 读磁盘扇区到内存;al = 需要读出的扇区数量; ! ch = 磁道(柱面)号的低8 位; cl = 开始扇区(0-5 位),磁道号高2 位(6-7); ! dh = 磁头号; dl = 驱动器号(如果是硬盘则要置位7); ! es:bx ??指向数据缓冲区; 如果出错则CF 标志置位。 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 ok_load_setup: ! Get disk drive parameters, specifically nr of sectors/track ! 取磁盘驱动器的参数,特别是每道的扇区数量。 ! 取磁盘驱动器参数INT 0x13 调用格式和返回信息如下: ! ah = 0x08 dl = 驱动器号(如果是硬盘则要置位7 为1)。 ! 返回信息: ! 如果出错则CF 置位,并且ah = 状态码。 ! ah = 0, al = 0, bl = 驱动器类型(AT/PS2) ! ch = 最大磁道号的低8 位,cl = 每磁道最大扇区数(位0-5),最大磁道号高2 位(位6-7) ! dh = 最大磁头数, dl = 驱动器数量, ! es:di -?? 软驱磁盘参数表。 mov dl,#0x00 mov ax,#0x0800 ! AH=8 is get drive parameters int 0x13 mov ch,#0x00 seg cs ! 表示下一条语句的操作数在cs 段寄存器所指的段中。 mov sectors,cx ! 保存每磁道扇区数。 mov ax,#INITSEG mov es,ax ! 因为上面取磁盘参数中断改掉了es 的值,这里重新改回。 ! Print some inane message ! 在显示一些信息('Loading system ...'回车换行,共24 个字符)。 mov ah,#0x03 ! read cursor pos xor bh,bh ! 读光标位置。 int 0x10 mov cx,#24 ! 共24 个字符。 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 ! 指向要显示的字符串。 mov ax,#0x1301 ! write string, move cursor int 0x10 ! 写字符串并移动光标。 ! ok, we've written the message, now ! we want to load the system (at 0x10000) ! 现在开始将system 模块加载到0x10000(64k)处。 mov ax,#SYSSEG mov es,ax ! segment of 0x010000 ! es = 存放system 的段地址。 call read_it ! 读磁盘上system 模块,es 为输入参数。 call kill_motor ! 关闭驱动器马达,这样就可以知道驱动器的状态了。 ! After that we check which root-device to use. If the device is ! defined (!= 0), nothing is done and the given device is used. ! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending ! on the number of sectors that the BIOS reports currently. ! 此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0) ! 就直接使用给定的设备。否则就需要根据BIOS 报告的每磁道扇区数来 ! 确定到底使用/dev/PS0 (2,28) 还是 /dev/at0 (2,8)。 ! 上面一行中两个设备文件的含义: ! 在Linux 中软驱的主设备号是2(参见第43 行的注释),次设备号 = type*4 + nr,其中 ! nr 为0-3 分别对应软驱A、B、C 或D;type 是软驱的类型(2??1.2M 或7??1.44M 等)。 ! 因为7*4 + 0 = 28,所以 /dev/PS0 (2,28)指的是1.44M A 驱动器,其设备号是0x021c ! 同理 /dev/at0 (2,8)指的是1.2M A 驱动器,其设备号是0x0208。 seg cs mov ax,root_dev ! 将根设备号 cmp ax,#0 jne root_defined seg cs mov bx,sectors ! 取上面第88 行保存的每磁道扇区数。如果sectors=15 ! 则说明是1.2Mb 的驱动器;如果sectors=18,则说明是 ! 1.44Mb 软驱。因为是可引导的驱动器,所以肯定是A 驱。 mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 ! 判断每磁道扇区数是否=15 je root_defined ! 如果等于,则ax 中就是引导驱动器的设备号。 mov ax,#0x021c ! /dev/PS0 - 1.44Mb cmp bx,#18 je root_defined undef_root: ! 如果都不一样,则死循环(死机)。 jmp undef_root root_defined: seg cs mov root_dev,ax ! 将检查过的设备号保存起来。 ! after that (everyting loaded), we jump to ! the setup-routine loaded directly after ! the bootblock: ! 到此,所有程序都加载完毕,我们就跳转到被 ! 加载在bootsect 后面的setup 程序去。 jmpi 0,SETUPSEG ! 跳转到0x9020:0000(setup.s 程序的开始处)。 !!!! 本程序到此就结束了。!!!! ! 下面是两个子程序。 ! This routine loads the system at address 0x10000, making sure ! no 64kB boundaries are crossed. We try to load it as fast as ! possible, loading whole tracks whenever we can. ! ! in: es - starting address segment (normally 0x1000) ! ! 该子程序将系统模块加载到内存地址0x10000 处,并确定没有跨越64KB 的内存边界。我们试图尽快 ! 地进行加载,只要可能,就每次加载整条磁道的数据。 ! 输入:es – 开始内存地址段值(通常是0x1000) sread: .word 1+SETUPLEN ! sectors read of current track ! 当前磁道中已读的扇区数。开始时已经读进1 扇区的引导扇区 ! bootsect 和setup 程序所占的扇区数SETUPLEN。 head: .word 0 ! current head !当前磁头号。 track: .word 0 ! current track !当前磁道号。 read_it: ! 测试输入的段值。必须位于内存地址64KB 边界处,否则进入死循环。清bx 寄存器,用于表示当前段内 ! 存放数据的开始位置。 mov ax,es test ax,#0x0fff die: jne die ! es must be at 64kB boundary ! es 值必须位于64KB 地址边界! xor bx,bx ! bx is starting address within segment ! bx 为段内偏移位置。 rp_read: ! 判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),如果不是就 ! 跳转至下面ok1_read 标号处继续读数据。否则退出子程序返回。 mov ax,es cmp ax,#ENDSEG ! have we loaded all yet? ! 是否已经加载了全部数据? jb ok1_read ret ok1_read: ! 计算和验证当前磁道需要读取的扇区数,放在ax 寄存器中。 ! 根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算如果全部读取这些未读扇区,所 ! 读总字节数是否会超过64KB 段长度的限制。若会超过,则根据此次最多能读入的字节数(64KB – 段内 ! 偏移位置),反算出此次需要读取的扇区数。 seg cs mov ax,sectors ! 取每磁道扇区数。 sub ax,sread ! 减去当前磁道已读扇区数。 mov cx,ax ! cx = ax = 当前磁道未读扇区数。 shl cx,#9 ! cx = cx * 512 字节。 add cx,bx ! cx = cx + 段内当前偏移值(bx) ! = 此次读操作后,段内共读入的字节数。 jnc ok2_read ! 若没有超过64KB 字节,则跳转至ok2_read 处执行。 je ok2_read xor ax,ax ! 若加上此次将读磁道上所有未读扇区时会超过64KB,则计算 sub ax,bx ! 此时最多能读入的字节数(64KB – 段内读偏移位置),再转换 shr ax,#9 ! 成需要读取的扇区数。 ok2_read: call read_track mov cx,ax ! cx = 该次操作已读取的扇区数。 add ax,sread ! 当前磁道上已经读取的扇区数。 seg cs cmp ax,sectors ! 如果当前磁道上的还有扇区未读,则跳转到ok3_read 处。 jne ok3_read ! 读该磁道的下一磁头面(1 号磁头)上的数据。如果已经完成,则去读下一磁道。 mov ax,#1 sub ax,head ! 判断当前磁头号。 jne ok4_read ! 如果是0 磁头,则再去读1 磁头面上的扇区数据。 inc track ! 否则去读下一磁道。 ok4_read: mov head,ax ! 保存当前磁头号。 xor ax,ax ! 清当前磁道已读扇区数。 ok3_read: mov sread,ax ! 保存当前磁道已读扇区数。 shl cx,#9 ! 上次已读扇区数*512 字节。 add bx,cx ! 调整当前段内数据开始位置。 jnc rp_read ! 若小于64KB 边界值,则跳转到rp_read(156 行)处,继续读数据。 ! 否则调整当前段,为读下一段数据作准备。 mov ax,es add ax,#0x1000 ! 将段基址调整为指向下一个64KB 段内存。 mov es,ax xor bx,bx ! 清段内数据开始偏移值。 jmp rp_read ! 跳转至rp_read(156 行)处,继续读数据。 ! 读当前磁道上指定开始扇区和需读扇区数的数据到es:bx 开始处。参见第67 行下对BIOS 磁盘读中断 ! int 0x13,ah=2 的说明。 ! al – 需读扇区数;es:bx – 缓冲区开始位置。 read_track: push ax push bx push cx push dx mov dx,track ! 取当前磁道号。 mov cx,sread ! 取当前磁道上已读扇区数。 inc cx ! cl = 开始读扇区。 mov ch,dl ! ch = 当前磁道号。 mov dx,head ! 取当前磁头号。 mov dh,dl ! dh = 磁头号。 mov dl,#0 ! dl = 驱动器号(为0 表示当前驱动器)。 and dx,#0x0100 ! 磁头号不大于1。 mov ah,#2 ! ah = 2,读磁盘扇区功能号。 int 0x13 jc bad_rt ! 若出错,则跳转至bad_rt。 pop dx pop cx pop bx pop ax ret ! 执行驱动器复位操作(磁盘中断功能号0),再跳转到read_track 处重试。 bad_rt: mov ax,#0 mov dx,#0 int 0x13 pop dx pop cx pop bx pop ax jmp read_track /* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don't have to worry about it later. */ ! 这个子程序用于关闭软驱的马达,这样我们进入内核后它处于已知状态,以后也就无须担心它了。 kill_motor: push dx mov dx,#0x3f2 ! 软驱控制卡的驱动端口,只写。 mov al,#0 ! A 驱动器,关闭FDC,禁止DMA 和中断请求,关闭马达。 outb ! 将al 中的内容输出到dx 指定的端口去。 pop dx ret sectors: .word 0 ! 存放当前启动软盘每磁道的扇区数。 msg1: .byte 13,10 ! 回车、换行的ASCII 码。 .ascii "Loading system ..." .byte 13,10,13,10 ! 共24 个ASCII 码字符。 .org 508 ! 表示下面语句从地址508(0x1FC)开始,所以root_dev ! 在启动扇区的第508 开始的2 个字节中。 root_dev: .word ROOT_DEV ! 这里存放根文件系统所在的设备号(init/main.c 中会用)。 boot_flag: .word 0xAA55 ! 硬盘有效标识。 .text endtext: .data enddata: .bss endbss:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值