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
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值