Linux0.11 根文件系统挂载

Posted Liuqz2009

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux0.11 根文件系统挂载相关的知识,希望对你有一定的参考价值。

系列文章目录


Linux 0.11启动过程分析(一)
Linux 0.11 fork 函数(二)
Linux0.11 缺页处理(三)
Linux0.11 根文件系统挂载(四)
Linux0.11 文件打开open函数(五)
Linux0.11 execve函数(六)


文章目录


一、起源

在init/main.c文件中有init函数,其开头有如下代码:

static inline _syscall1(int, setup, void *, Bios)
 
void init(void) 
  int pid, i;
 
  setup((void *)&drive_info);
  //...

 
// 而_syscall1 在include/unistd.h中,定义为:
#define __NR_setup	0	/* used only by init, to get system going */
 
#define _syscall1(type,name,atype,a) \\
type name(atype a) \\
 \\
long __res; \\
__asm__ volatile ("int $0x80" \\
	: "=a" (__res) \\
	: "0" (__NR_##name),"b" ((long)(a))); \\
if (__res >= 0) \\
	return (type) __res; \\
errno = -__res; \\
return -1; \\

init 函数中,setup 函数实现了根文件系统挂载功能。setup 函数由宏 _syscall1(int, setup, void *, BIOS) 定义,其展开后为(可参考:Linux 0.11 fork 函数(二) ):

// __NR_##name 展开后为 __NR_setup, 而其又是一个宏定义,值为0
#define __NR_setup	0	/* used only by init, to get system going */
 
// 展开后
static inline int setup(void* BIOS) 
    long __res; 
    __asm__ volatile ("int $0x80" 
	    : "=a" (__res) 
	    : "0" (0),"b" ((long)(a))); 
    if (__res >= 0) 
	    return (type) __res; 
    errno = -__res; 
    return -1;   

二、系统调用

setup 函数是个系统调用(可参考:Linux 0.11 fork 函数(二) ),其响应函数 system_call 函数定义在 kernel/system_call.s 文件中。其最后会调用 sys_setup 函数。

三、sys_setup 之一函数读入超级块信息

    sys_setup 函数在 kernel/blk_drv/hd.c 文件中。其利用 boot/setup.s 程序提供的信息对系统中所含硬盘驱动器的参数进行了设置。然后读取硬盘分区表,并尝试把启动引导盘上的虚拟盘根文件系统映像文件复制到内存虚拟盘中,若成功则加载虚拟盘中的根文件系统,否则就继续执行普通根文件系统加载操作。

static struct hd_struct 
	long start_sect;    // 分区在硬盘中的起始物理(绝对)扇区
	long nr_sects;      // 分区中扇区总数
 hd[5*MAX_HD]=0,0,;
 
// 函数参数BIOS是由初始化程序 init/main.c 中 init 子程序设置为指向硬盘参数表结构的指针。
// 该硬盘参数表结构包含2个硬盘参数表的内容(共32字节),是从内存0x90080处复制而来。
int sys_setup(void * BIOS) 
    // ...
 
	for (i = NR_HD ; i < 2 ; i++) 
		hd[i*5].start_sect = 0;
		hd[i*5].nr_sects = 0;
	
    // 前面省略的代码,确定了系统中所含的硬盘个数NR_HD(跟踪发现为1个)。现在我们来读取每个
    // 硬盘上第1个山区中的分区表信息,用来设置分区结构数组hd[]中硬盘各分区的信息。
    // 首先利用bread函数读取硬盘上第1个数据块(第1个参数为硬盘设备号,第2个参数为所需读取的块号),
    // 若读取成功,则数据会被存放在缓冲块bh的数据区中。
    // 然后根据硬盘第1个扇区最后两个字节是否为0xAA55来判断扇区中数据的有效性,若有效则取分区
    // 表信息(位于扇区偏移0x1BE处)。将分区表信息放入到hd[]中。最后释放bh缓冲区。
    for (drive=0 ; drive<NR_HD ; drive++) 
		if (!(bh = bread(0x300 + drive*5,0))) 
			printk("Unable to read partition table of drive %d\\n\\r",
				drive);
			panic("");
		
		if (bh->b_data[510] != 0x55 || (unsigned char)
		    bh->b_data[511] != 0xAA) 
			printk("Bad partition table on drive %d\\n\\r",drive);
			panic("");
		
		p = 0x1BE + (void *)bh->b_data;
		for (i=1;i<5;i++,p++) 
			hd[i+5*drive].start_sect = p->start_sect;
			hd[i+5*drive].nr_sects = p->nr_sects;
		
		brelse(bh);
	
	if (NR_HD)
		printk("Partition table%s ok.\\n\\r",(NR_HD>1)?"s":"");
	rd_load();        // 尝试创建并加载虚拟盘
	mount_root();     // 安装根文件系统
	return (0);

1、bread函数

bread函数位于fs/buffer.c中。

struct buffer_head * bread(int dev,int block)

	struct buffer_head * 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;

ll_rw_block(READ,bh) 采用异步方式读取硬盘上的信息,此处使用 wait_on_buffer(bh) 进行同步等待(进入睡眠)读取磁盘信息结束。

1.1 getblk 函数

getblk 函数用于获取一块空闲缓冲区块。其执行流程图如下:

1.2 ll_rw_block 函数

    fs/buffer.c 中 bread 函数会调用 kernel/blk_drv/ll_rw_blk.c 中 ll_rw_block 函数。
    ll_rw_block 用来读写块设备中的数据。其为块设备创建块设备读写请求项,并插入到指定块设备请求队列中。实际的读写操作则是由设备的请求项处理函数 request_fn() 完成(硬盘为 do_hd_request(),软盘为 do_fd_request(),虚拟盘为 do_rd_request() )。
    若 ll_rw_block 为一个块设备建立起一个请求项,并通过测试块设备的当前请求项指针为空而确定设备空闲时,就会设置该新建的请求项为当前请求项,并直接调用 request_fn() 对该请求项进行操作。否则就会使用电梯算法将新建的请求项插入到该设备的请求项链表中等待处理。而当 request_fn() 结束对一个请求项的处理,就会把该请求项从链表中删除。
    由于 request_fn() 在每个请求项处理结束时,都有通过中断回调C函数(主要是 read_intrwrite_intr )再次调用 request_fn() 自身去处理链表中其余的请求项,因此,只要设备的请求项链表中有未处理的请求项存在,都会陆续地被处理,直到设备的请求项链表为空为止。

代码如下:

struct blk_dev_struct blk_dev[NR_BLK_DEV] = 
	 NULL, NULL ,		/* no_dev */
	 NULL, NULL ,		/* dev mem */
	 NULL, NULL ,		/* dev fd */
	 NULL, NULL ,		/* dev hd */
	 NULL, NULL ,		/* dev ttyx */
	 NULL, NULL ,		/* dev tty */
	 NULL, NULL 		/* dev lp */
;
 
// 该函数把已经设置好的请求项req添加到指定设备的请求项链表中。如果该设备的当前请求项指针为空,
// 则可以设置req为当前请求项并立即调用设备请求项处理函数。否则就把req请求项插入到该请求项链表中
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)) 
        // 当前没有请求,则直接调用请求函数,对于硬盘是 do_hd_request()
		dev->current_request = req;
		sti();
		(dev->request_fn)();
		return;
	
	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();

 
// 创建请求项并插入请求队列中
// 参数major是主设备号,rw是指定命令,bh是存放数据的缓冲区头指针。
static void make_request(int major,int rw, struct buffer_head * bh)

	struct request * req;
	int rw_ahead;
 
/* WRITEA/READA is special case - it is not really needed, so if the */
/* buffer is locked, we just forget about it, else it's a normal read */
	if ((rw_ahead = (rw == READA || rw == WRITEA))) 
		if (bh->b_lock)
			return;
		if (rw == READA)
			rw = READ;
		else
			rw = WRITE;
	
	if (rw!=READ && rw!=WRITE)
		panic("Bad block dev command, must be R/W/RA/WA");
    // 在开始生成和添加相应的读写请求项前,我们再来看看此次是否有必要添加请求项。在两种情况下可以
    // 不必添加请求项。一是当命令是写,但缓冲区中的数据在读入之后并没有被修改过;二是当命令是读,
    // 但缓冲区中的数据已经是更新过的,即与块设备上的完全一样。
	lock_buffer(bh);
	if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) 
		unlock_buffer(bh);
		return;
	
repeat:
/* 我们不能让队列中全部是写请求项:读操作是优先的。请求队列的后三分之一空间仅
 * 用于读请求项。
 * 从后往前搜索一个空闲的请求项。request->dev为-1表示空闲。
 */
	if (rw == READ)
		req = request+NR_REQUEST;
	else
		req = request+((NR_REQUEST*2)/3);
/* find an empty request */
	while (--req >= request)
		if (req->dev<0)
			break;
/* if none found, sleep on new requests: check for rw_ahead */
	if (req < request) 
		if (rw_ahead) 
			unlock_buffer(bh);
			return;
		
		sleep_on(&wait_for_request);
		goto repeat;
	
/* fill up the request-info, and add it to the queue */
	req->dev = bh->b_dev;            // 设备号
	req->cmd = rw;                   // 命令(READ/WRITE)
	req->errors=0;                   // 操作时产生的错误次数
	req->sector = bh->b_blocknr<<1;  // 起始扇区。块号转换成扇区号(1块=2扇区)
	req->nr_sectors = 2;             // 本请求项需要读写的扇区数
	req->buffer = bh->b_data;        // 请求项缓冲区指针指向需读写的数据缓冲区
	req->waiting = NULL;             // 任务等待操作执行完成的地方
	req->bh = bh;                    // 缓冲块头指针
	req->next = NULL;                // 指向下一请求项
	add_request(major+blk_dev,req);  // 将请求项加入队列中,相当 (blk_dev[major], req)

 
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);

do_hd_request 函数

add_request 函数中 (dev->request_fn)(); 会调用相应的处理函数。对于硬盘此处调用的就是 do_hd_request 函数。其位于文件 kernel/blk_drv/hd.c 中。

void do_hd_request(void)

	int i,r = 0;
	unsigned int block,dev;
	unsigned int sec,head,cyl;
	unsigned int nsect;
 
	INIT_REQUEST;
	dev = MINOR(CURRENT->dev);
	block = CURRENT->sector;
	if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) 
		end_request(0);
		goto repeat;
	
	block += hd[dev].start_sect;
	dev /= 5;
	__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
		"r" (hd_info[dev].sect));
	__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
		"r" (hd_info[dev].head));
	sec++;
	nsect = CURRENT->nr_sectors;
	if (reset) 
		reset = 0;
		recalibrate = 1;
		reset_hd(CURRENT_DEV);
		return;
	
	if (recalibrate) 
		recalibrate = 0;
		hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
			WIN_RESTORE,&recal_intr);
		return;
		
	if (CURRENT->cmd == WRITE) 
		hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
		for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
			/* nothing */ ;
		if (!r) 
			bad_rw_intr();
			goto repeat;
		
		port_write(HD_DATA,CURRENT->buffer,256);
	 else if (CURRENT->cmd == READ) 
		hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
	 else
		panic("unknown hd-command");

do_hd_request() 是硬盘请求项的操作函数。其操作流程如下:

  • 首先判断当前请求项是否存在,若当前请求项指针为空,则说明目前硬盘块设备已经没有待处理的请求项,因此立刻退出程序。这是在宏 INIT_REQUEST 中执行的语句。否则就继续处理当前请求项。
  • 对当前请求项中指明的设备号和请求的盘起始扇区号的合理性进行验证;
  • 根据当前请求项提供的信息计算请求数据的磁盘磁道号、磁头号和柱面号;
  • 如果复位标志(reset)已被设置,则也设置硬盘重新校正标志(recalibrate),并对硬盘执行复位操作,向控制器重新发送"建立驱动器参数"命令(WIN_SPECIFY)。该命令不会引发硬盘中断;
  • 如果重新校正标志被置位的话,就向控制器发送硬盘重新校正命令(WIN_RESTORE),并在发送
    之前预先设置好该命令引发的中断中需要执行的 C 函数(recal_intr()),并退出。recal_intr() 函数的主要作用是:当控制器执行该命令结束并引发中断时,能重新(继续)执行本函数。
  • 如果当前请求项指定是写操作,则首先设置硬盘控制器调用的 C 函数为 write_intr(),向控制器发送写操作的命令参数块,并循环查询控制器的状态寄存器,以判断请求服务标志(DRQ)是否置位。若该标志置位,则表示控制器已"同意"接收数据,于是接着就把请求项所指缓冲区中的数据写入控制器的数据缓冲区中。若循环查询超时后该标志仍然没有置位,则说明此次操作失败。 于是调用 bad_rw_intr() 函数,根据处理当前请求项发生的出错次数来确定是放弃继续当前请求项还是需要设置复位标志,以继续重新处理当前请求项。
  • 如果当前请求项是读操作,则设置硬盘控制器调用的 C 函数为 read_intr(),并向控制器发送读盘操作命令。
hd_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(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(

Linux挂载根文件系统

NFS根文件系统挂载

  **这里只是记录自己使用NFS挂载根文件系统时出现的错误,并不涉及技术细节**

 

  开发板:Smart210

  Bootloader: u-boot-2012-10

  Linux: Linux3.10.46

 

  刚开始时在uboot中设置的参数如下:

setenv bootargs root=/dev/nfs nfsroot=192.168.10.101:/home/weirdo/Share/rootfs_rtm_210 ip=192.168.10.120:192.168.10.101:192.168.10.1:255.255.255.0:
          weirdo:eth0:off console=ttySAC0,115200

  在使用bootm 0x20008000 后,终端开始打印各种启动信息,最后停留在dm9000的答应输出中,输出的信息只是显示dm9000的up 和down,始终不可以进入Linux提供的终端。百思不得其解,最后看了一个视频时突然发现是自己参数设置错误导致启动失败。

 

  解释一下ip参数:

    * param1: 开发板ip

    * param2: 宿主机ip

    * param3: 网关

    * param4:网络掩码

    param5: 宿主机名称

    * param6: 指定网卡

    * param7: 不清楚(因该是网卡状态)

  这里后面的3个参数中param5可以随便给,不会影响启动,我这里给的参数比较全面,是因为在我的u-boot中param*参数没有设定完全,这里特别的指定。如果你的参数(param*)在环境中设置的比较完整,也可以直接使用如下的形式:

    ip = 开发板ip

 

  错误原因:缺少init=/linuxrc参数

  修改bootargs启动参数为如下:

setenv bootargs root=/dev/nfs init=/linuxrc nfsroot=192.168.10.101:/home/weirdo/Share/rootfs_rtm_210 
          ip=192.168.10.120:192.168.10.101:192.168.10.1:255.255.255.0:           weirdo:eth0:off console=ttySAC0,115200

   再次启动开发板挂载Linux,成功挂载!

以上是关于Linux0.11 根文件系统挂载的主要内容,如果未能解决你的问题,请参考以下文章

Linux 0.11启动过程分析

bochs运行早期linux0.11和linux0.98内核

Linux0.11内核系列—2.系统调用机制分析

Linux 0.11-加载根文件系统-32

Linux 0.11-加载根文件系统-32

嵌入式Linux开发-根文件系统NFS网络挂载