本文参考书籍
1.操作系统真相还原
2.Linux内核完全剖析:基于0.12内核
3.x86汇编语言 从实模式到保护模式
4.Linux内核设计的艺术
ps:基于x86硬件的pc系统
Linux0.12初始化续
在上文中主要分析了读取硬盘数据到内存中,通过将硬盘中的数据读入到内存中缓存后,然后如果其他进程需要读取同样的数据时,根据内存是否有更新,来获取内存数据,或者将内存块数据同步到磁盘。
本文继续分析setup初始化函数中的初始化过程,虚拟盘加载。
Linux0.12虚拟盘初始化
继续分析的sys_setup函数如下;
int sys_setup(void * BIOS)
{
...
rd_load(); // 加载虚拟盘
init_swapping(); // 内存交换初始化
mount_root(); // 安装根文件系统
return (0);
}
此时rd_load()函数位于kernel/blk_drv/ramdisk.c中;
void rd_load(void)
{
struct buffer_head *bh; // 缓冲块头部
struct super_block s; // 文件超级快结构
int block = 256; /* Start at block 256 */ // 开始于256盘块
int i = 1;
int nblocks; // 文件系统盘块总数
char *cp; /* Move pointer */
if (!rd_length) // 检测虚拟盘的长度,该值在rd_init中被初始化
return;
printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
(int) rd_start);
if (MAJOR(ROOT_DEV) != 2) // 如果根设备不是软盘设备则退出
return;
bh = breada(ROOT_DEV,block+1,block,block+2,-1); // 读跟文件系统的基本参数,从盘块257,256,258读取
if (!bh) { // 如果读取不到则报错
printk("Disk error while looking for ramdisk!\n");
return;
}
*((struct d_super_block *) &s) = *((struct d_super_block *) bh->b_data); // 缓冲区的磁盘超级快复制到s变量中
brelse(bh); // 释放该数据块数据
if (s.s_magic != SUPER_MAGIC) // 判断读取的硬盘是否是硬盘魔数
/* No ram disk image present, assume normal floppy boot */
return;
nblocks = s.s_nzones << s.s_log_zone_size; // 获取盘总数
if (nblocks > (rd_length >> BLOCK_SIZE_BITS)) { // 文件系统的数据块总数大于内存虚拟盘所能容纳的总数则返回
printk("Ram disk image too big! (%d blocks, %d avail)\n",
nblocks, rd_length >> BLOCK_SIZE_BITS);
return;
}
printk("Loading %d bytes into ram disk... 0000k",
nblocks << BLOCK_SIZE_BITS);
cp = rd_start; // 虚拟盘内存开始头部位置
while (nblocks) { // 循环将文件系统映像文件加载到虚拟盘上
if (nblocks > 2) // 如果需要加载的磁盘快数大于2
bh = breada(ROOT_DEV, block, block+1, block+2, -1); // 调用预读函数breada
else
bh = bread(ROOT_DEV, block); // 否则就单块读取
if (!bh) {
printk("I/O error on block %d, aborting load\n",
block);
return; // 如果读取出错则返回
}
(void) memcpy(cp, bh->b_data, BLOCK_SIZE); // 从高速缓冲区复制到内存起始位置的指定位置处
brelse(bh); // 释放高速缓冲块
printk("\010\010\010\010\010%4dk",i); // 打印缓冲了多少块数据
cp += BLOCK_SIZE; // 虚拟盘指针前移
block++;
nblocks--;
i++;
}
printk("\010\010\010\010\010done \n"); // 整个跟文件系统加载完毕后,显示done
ROOT_DEV=0x0101; // 设置当前根设备号
}
通过高速缓冲块读取硬盘数据上文已经分析过了流程了,此时在读取根文件数据时,会使用到预读操作breada,该函数主要操作如下;
struct buffer_head * breada(int dev,int first, ...)
{
va_list args;
struct buffer_head * bh, *tmp;
va_start(args,first);
if (!(bh=getblk(dev,first))) // 获取可变参数列表第1个参数,然后读取参数
panic("bread: getblk returned NULL\n");
if (!bh->b_uptodate) // 如果更新标志未置位,则发出读设备请求
ll_rw_block(READ,bh);
while ((first=va_arg(args,int))>=0) { // 依次读取剩下参数的数据,如果最后一位参数小于0则停止循环
tmp=getblk(dev,first); // 从高速缓冲区中获取
if (tmp) { // 如果获取到
if (!tmp->b_uptodate) // 更新标志位未置位
ll_rw_block(READA,bh); // 发送读请求
tmp->b_count--; // 由于读取后并不引用,该处在获取时会计数加1,此时加1
}
}
va_end(args);
wait_on_buffer(bh); // 等待第一个高速缓冲区数据返回
if (bh->b_uptodate) // 如果数据有效则返回
return bh;
brelse(bh); // 否则释放该缓冲块
return (NULL); // 返回空
}
依次将数据进行读取,成功后返回第1块的缓冲区头指针。
等到预读了相应数据后,将文件系统的数据加载到虚拟盘指定的内存起始位置,当执行完成后,虚拟盘的加载就完成了。
Linux0.12根文件系统挂载
此时会执行到mount_root()函数,
void mount_root(void) // 读取根文件系统超级快
{
int i,free;
struct super_block * p;
struct m_inode * mi;
if (32 != sizeof (struct d_inode)) // 判断硬盘i节点结构是否是32字节
panic("bad i-node size");
for(i=0;i<NR_FILE;i++) // 初始化文件表数组,系统同时最多打开64个文件
file_table[i].f_count=0; // 初始化文件表
if (MAJOR(ROOT_DEV) == 2) { // 提示插入根文件系统
printk("Insert root floppy and press ENTER");
wait_for_keypress(); // 等待键盘输入
}
for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) { // 总共可以有8个超级块
p->s_dev = 0; // 初始化超级块表
p->s_lock = 0;
p->s_wait = NULL;
}
if (!(p=read_super(ROOT_DEV))) // 读取超级块
panic("Unable to mount root");
if (!(mi=iget(ROOT_DEV,ROOT_INO))) // 取得文件系统的跟i节点在内存i节点表中的指针
panic("Unable to read root i-node");
mi->i_count += 3 ; /* NOTE! it is logically used 4 times, not 1 */
p->s_isup = p->s_imount = mi;
current->pwd = mi;
current->root = mi;
free=0;
i=p->s_nzones;
while (-- i >= 0)
if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data)) // 统计设备空闲的块总数
free++;
printk("%d/%d free blocks\n\r",free,p->s_nzones);
free=0;
i=p->s_ninodes+1; // 统计空闲i节点数
while (-- i >= 0)
if (!set_bit(i&8191,p->s_imap[i>>13]->b_data)) // 根据i节点位图相应位的占用情况统计空闲节点数
free++;
printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}
该函数主要初始化文件表数组和超级块表,然后读取根文件系统超级块,并取得文件系统根i节点,最后统计并显示根文件系统上的空闲块数和空闲i节点数。
其中,在调用read_super时,
static struct super_block * read_super(int dev) // 读取指定设备的超级块
{
struct super_block * s;
struct buffer_head * bh;
int i,block;
if (!dev) // 如果传入设备号为空则返回
return NULL;
check_disk_change(dev); // 检查是否更换过盘
if (s = get_super(dev)) // 如果超级块已经存在超级块表中,则直接返回,否则在超级块数组中找出一个空项
return s;
for (s = 0+super_block ;; s++) { // 在超级块数组中,查找一个空项
if (s >= NR_SUPER+super_block)
return NULL; // 如果没有找到则返回为空
if (!s->s_dev) // 如果找到则停止循环
break;
}
s->s_dev = dev; // 找到后指定设备中的文件系统进行初始化
s->s_isup = NULL;
s->s_imount = NULL;
s->s_time = 0;
s->s_rd_only = 0;
s->s_dirt = 0;
lock_super(s); // 锁定该超级块
if (!(bh = bread(dev,1))) { // 读取超级块位于块设备的超级块信息
s->s_dev=0; // 如果读取失败置空返回空
free_super(s); // 对超级块解锁
return NULL;
}
*((struct d_super_block *) s) =
*((struct d_super_block *) bh->b_data); // 将高速缓冲块的数据复制到数组中
brelse(bh); // 释放该高速缓冲块
if (s->s_magic != SUPER_MAGIC) { // 根文件系统魔数检查
s->s_dev = 0;
free_super(s); // 如果不是则对超级块解锁
return NULL;
}
for (i=0;i<I_MAP_SLOTS;i++)
s->s_imap[i] = NULL; // 初始化超级块中的i节点位图
for (i=0;i<Z_MAP_SLOTS;i++)
s->s_zmap[i] = NULL; // 初始化超级块中逻辑块位图
block=2; // 从设备2号块读取数据
for (i=0 ; i < s->s_imap_blocks ; i++)
if (s->s_imap[i]=bread(dev,block)) // 读取设备中的i节点位图
block++;
else
break;
for (i=0 ; i < s->s_zmap_blocks ; i++)
if (s->s_zmap[i]=bread(dev,block)) // 读取设备中的逻辑块位图
block++;
else
break;
if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) { // 如果读出的位图块数不等于位图应该占有的逻辑块数
for(i=0;i<I_MAP_SLOTS;i++) // 释放位图占用的高速缓冲块
brelse(s->s_imap[i]);
for(i=0;i<Z_MAP_SLOTS;i++) // 释放逻辑快占用的高速缓冲块
brelse(s->s_zmap[i]);
s->s_dev=0;
free_super(s); // 解锁超级块
return NULL; // 返回为空
}
s->s_imap[0]->b_data[0] |= 1; // 防止文件系统分配0号i节点
s->s_zmap[0]->b_data[0] |= 1;
free_super(s); // 解锁超级块并返回
return s;
}
经过超级块的数据读取,然后将超级块返回,此时在继续调用iget()函数;
struct m_inode * iget(int dev,int nr) // 取得一个i节点
{
struct m_inode * inode, * empty;
if (!dev) // 判断是否是设备号为0 的设备
panic("iget with dev==0");
empty = get_empty_inode(); // 从节点表中取一个空闲i节点备用
inode = inode_table;
while (inode < NR_INODE+inode_table) { // 循环遍历inode_table数组
if (inode->i_dev != dev || inode->i_num != nr) { // 如果当前iJ节点的设备号不等于指定设备号或者节点号不等于指定的节点号则继续循环
inode++;
continue;
}
wait_on_inode(inode); // 等待该缓冲块
if (inode->i_dev != dev || inode->i_num != nr) { // 等待被唤醒后,有可能被修改则继续检查
inode = inode_table;
continue;
}
inode->i_count++; // 找到则该节点的引用计数加1
if (inode->i_mount) { // 检查是否是另一文件系统的安装点
int i;
for (i = 0 ; i<NR_SUPER ; i++) // 检查是否是另一系统的安装点
if (super_block[i].s_imount==inode)
break;
if (i >= NR_SUPER) { // 如果i大于超级块数组数则不是
printk("Mounted inode hasn't got sb\n");
if (empty)
iput(empty);
return inode; // 返回该inode节点
}
iput(inode);
dev = super_block[i].s_dev; // 获取对应的超级块取得设备号
nr = ROOT_INO; // 设置i节点号
inode = inode_table; // 继续循环
continue;
}
if (empty) // 如果找到则放弃申请的空闲节点
iput(empty);
return inode; // 返回找到的i节点指针
}
if (!empty) // 如果获取的空闲节点为空则返回为空
return (NULL);
inode=empty;
inode->i_dev = dev; // 设置i节点的设备
inode->i_num = nr; // 设置i节点号
read_inode(inode); // 读取节点数据,将超级块的头部数据读入inode中
return inode; // 返回inode
}
从设备上读取指定节点号的i节点内容到i节点表中,并返回该节点指针,首先会在高速缓冲区中的i节点表中查找,若找到则进过相关处理后返回,否则从设备上读取指定i节点信息放入i节点表中,并返回该i节点指针。
其中input函数就是放回i节点;
void iput(struct m_inode * inode) // 放回一个i节点
{
if (!inode) // 判断inode是否有效
return;
wait_on_inode(inode); // 等待inode节点解锁
if (!inode->i_count) // 如果节点引用计数为0则表示已经空闲,此时就报错
panic("iput: trying to free free inode");
if (inode->i_pipe) { // 如果该节点是管道
wake_up(&inode->i_wait); // 唤醒等待的进程
wake_up(&inode->i_wait2);
if (--inode->i_count) // 节点引用计数减一,如果还有引用则返回
return;
free_page(inode->i_size); // 否则就释放该页面
inode->i_count=0; // 复位该节点的引用计数、已修改标志和管道标志
inode->i_dirt=0;
inode->i_pipe=0;
return;
}
if (!inode->i_dev) { // 如果该节点设备号为0
inode->i_count--; // 则节点引用减1
return;
}
if (S_ISBLK(inode->i_mode)) { // 如果是块设备的i节点,则刷新该块
sync_dev(inode->i_zone[0]);
wait_on_inode(inode); // 等待节点解锁
}
repeat:
if (inode->i_count>1) { // 如果节点的引用计数大于1,则减1返回
inode->i_count--;
return;
}
if (!inode->i_nlinks) { // 如果i节点的连接数为0
truncate(inode); // 删除该节点
free_inode(inode); // 释放该节点
return;
}
if (inode->i_dirt) { // 如果该节点已修改,则回写该节点
write_inode(inode); /* we can sleep - so do again */
wait_on_inode(inode);
goto repeat;
}
inode->i_count--; // 此时节点引用计数值为1、链接数不为0,此时把节点引用减1并返回
return;
}
在iget函数中,还会执行到read_inode函数;
static void read_inode(struct m_inode * inode) // 读取指定i节点信息
{
struct super_block * sb;
struct buffer_head * bh;
int block;
lock_inode(inode); // 锁定该i节点
if (!(sb=get_super(inode->i_dev))) // 取得该节点所在的设备超级块
panic("trying to read inode without dev");
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
(inode->i_num-1)/INODES_PER_BLOCK; // 计算该节点所在的设备逻辑块号
if (!(bh=bread(inode->i_dev,block))) // 读取指定块到高速缓冲区
panic("unable to read i-node block");
*(struct d_inode *)inode =
((struct d_inode *)bh->b_data) // 将读取数据复制到inode中
[(inode->i_num-1)%INODES_PER_BLOCK];
brelse(bh); // 释放高速缓冲块
if (S_ISBLK(inode->i_mode)) { // 如果是块设备文件还需要设置节点的最大文件长度
int i = inode->i_zone[0];
if (blk_size[MAJOR(i)])
inode->i_size = 1024*blk_size[MAJOR(i)][MINOR(i)];
else
inode->i_size = 0x7fffffff;
}
unlock_inode(inode); // 解锁该inode
}
该函数主要是将设备上的数据读入到inode中。
至此,sys_setup函数整个执行的流程已经分析完成,本次设计到的相关inode内容在后续文章中会继续介绍。