1. 进程0创建进程1
--- init --- main.c --- sched_init() | |- hd_init() | |- fork() --- int 0x80//系统调度中断 | |- init() --- setup() --- int 0x80 | |- pause() --- int 0x80 | |- kernel --- sched.c --- sched_init() --- set_system_gate(0x80,&system_call)//注册中断 | | |- sys_pause() --- schedule() | | |- schedule() --- switch_to() | | |- sleep_on() --- *p = current//p=&bh->b_wait | | | |- schedule() | | |- wake_up() --- (**p).state=0//p=&bh->b_wait,进程1就绪 | | | |- sched.h --- switch_to()//进程1切换到进程0后在sys_pause中循环 | | | |- system_call.s --- system_call() --- sys_call_table(,%eax,4) | | |- sys_fork() --- find_empty_process()//找到空闲任务结构task[nr] | | | |- copy_process()//复制父进程任务结构和页表 | | |- hd_interrupt() --- do_hd() | | | |- fork.c --- find_empty_process() --- 返回进程号nr, | | | | 并且找到最小的pid值赋值给last_pid | | |- copy_process() --- p=get_free_page()//开辟新的一页内存用于存放子进程任务结构 | | | |- *p=*current//复制父进程任务结构给子进程 | | | |- p->tss.eip=eip | | | |- p->tss.eax=0//这是子进程执行时,fork函数的返回值 | | | |- copy_mem(p)//建立页表映射,并复制父进程页表 | | | |- f->f_count++ | | | |- set_tss_desc()//将task[nr]的tss和ldt地址填入GDT | | | |- set_ldt_desc() | | | |- p->state = TASK_RUNNING//子进程改为就绪态 | | |- copy_mem() --- set_base(p->ldt[1]...)//子进程ldt,数据段和代码段基地址 | | |- set_base(p->ldt[2]...)//赋值为0x4000000*nr | | |- copy_page_tables()//建立页表映射,并复制父进程页表 | | | |- blk_drv --- hd.c --- sys_setup() --- hd_info[2]赋值 | | | |- hd[0]和hd[5]赋值 | | | |- bh=bread() | | | |- 判断硬盘信息有效 | | | |- 根据硬盘中的分区信息设置hd[1]~hd[4] | | | |- brelse(bh) | | | |- rd_load()//用软盘格式化虚拟盘 | | | |- mount_root()//加载根文件系统 | | |- hd_init() --- blk_dev[3].request_fn = do_hd_request | | | |- set_intr_gate(0x2E,&hd_interrupt); | | |- do_hd_request() --- INIT_REQUEST//CURRENT==NULL时返回 | | | |- hd_out(...,&read_intr) --- 控制硬盘开始读写, | | | | 完成后引发中断 | | | |- do_hd=read | | |- read_intr() --- port_read(...CURRENT->buffer) | | | //CURRENT=blk_dev[3].current_request | | | //CURRENT->buffer=bh->b_data | | |- end_request(1)//读取完成后执行到这里 | | |- do_hd_request() | | | |- ll_rw_block.c --- ll_rw_block() --- major=MAJOR(bh->b_dev) | | | |- make_request(major,bh) | | |- make_request() --- lock_buffer(bh) | | | |- 找到空闲请求req | | | |- req->buffer=bh->b_data | | | |- req->next=NULL | | | |- add_request(blk_dev[major],req) | | |- add_request() --- blk_dev[3]->current_request=req | | |- blk_dev[3]->request_fn | | | |- blk.h --- end_request() --- CURRENT->bh->b_uptodate = 1 | | | |- unlock_buffer(CURRENT->bh) | | | |- CURRENT = CURRENT->next//CURRENT=NULL | | |- unlock_buffer() --- bh->b_lock=0 | | |- wake_up(&bh->b_wait) | | | |- ramdisk.c --- rd_load() --- bh=breada() | |- 拷贝bh->b_data到s//s为超级块 | |- brelse(bh) | |- 计算虚拟块数 | |- 将软盘文件系统复制到虚拟盘 | |- ROOT_DEV=0x0101//虚拟盘设置为根设备 | |- include --- linux --- sys_call_table[] --- sys_fork() | |- sys_pause() | |- sys_setup() | |- mm --- memery.c --- copy_page_tables() --- from和to分别为父进程和子进程线性地址 | |- from_dir=(from>>20) & 0xffc //取出页目录偏移,然后乘上4 | |- to_dir=(to>>20) & 0xffc //等于在页目录中实际的地址 | |- size = (size+0x3fffff)) >> 22 | | //一个页表管理4MB内存,">>22"相当于"/4MB" | | //也就是将要拷贝的字节数转为要拷贝的页表数 | |- for( ; size-->0 ; from_dir++,to_dir++) { | | from_page_table=0xfffff000 & *from_dir; | | //取出父进程页表地址 | | to_page_table=get_free_page(); | | //申请一个页面作为子进程页表 | | *to_dir = to_page_table | 7; | | //将该页面放入子进程的页目录表 | | nr = (from==0)?0xA0:1024; | | for(;nr-->0;from_page_table++,to_page_table++) { | | this_page = *from_page_table; | | //取出父进程的页地址 | | this_page &= ~2;//设置为只读 | | *to_page_table = this_page; | | //放入子进程页表 | | 处理mem_map[] | | } | | } | |- invalidate()//刷新CR3页高速缓存 | |- fs --- buffer.c --- bread() --- bh=getblk() | | |- ll_rw_block(bh) | | |- wait_on_buffer(bh) | | |- if(bh->b_uptodate)//返回bh | |- getblk() --- get_hash_table() | | |- 遍历free_list,找到空闲bh | | |- remove_from_queues(bh) | | |- bh->b_dev=dev | | | bh->b_blocknr=block | | |- insert_into_queues(bh) | |- get_hash_table() --- find_buffer() | |- find_buffer() | |- remove_from_queues(bh) | |- insert_into_queues(bh) --- hash(...) = bh | |- wait_on_buffer() --- sleep_on(&bh->b_wait) | //等待读盘完成b_wait=NULL | |- super.c --- mount_root() --- 初始化super_block[8] | |- p=read_super(ROOT_DEV)//读取超级块 | |- mi=iget(ROOT_DEV,ROOT_INO)//读取根节点 | |- p->s_isup = p->s_imount = mi//挂接i节点 | |- current->pwd = mi | |- current->root = mi |- read_super() --- 从super_block[8]中申请一项 | |- s->s_dev = dev | |- lock_super(s) | |- bh = bread(dev,1) | |- 拷贝bh->b_data到s//s前半部分被填充 | |- s->s_imap[i]=bread() | |- s->s_zmap[i]=bread() |- iget() --- empty = get_empty_inode() | |- inode=empty | |- inode->i_dev = dev | |- inode->i_num = nr | |- read_inode(inode) |- read_inode() --- sb=get_super(inode->i_dev) |- bh=bread(inode->i_dev,block) |- 拷贝bh->b_data到inode//inode前半部分被填充
进程0创建进程1
1. fork()函数执行,触发0x80中断,系统由特权级3转换为特权级0,跳转到sys_fork执行。
2. sys_fork中首先使用find_empty_process()函数找到空闲的进程号nr,和最小的pid值,然后调用copy_process()函数复制父进程任务结构到给子进程。
3. copy_process()函数先将task[nr]指向新的一页内存,然后将父进程任务结构复制到task[nr]指向的内存;然后修改task[nr]的tss.eip=当前eip,tss.eax=0,tss.ldt = _LDT(nr);最后调用copy_mem()函数复制父进程页表到子进程。
4. copy_mem()函数中,设置新的数据段和代码段基址为nr*0x4000000,再将其填入当前进程的LDT[1]和LDT[2],然后调用copy_page_tables()函数开始复制页表。
5. copy_page_tables()函数建立的新的映射关系,并复制父进程的页表到子进程。首先申请一页内存作为子进程的页表,将页表首地址填入页目录表,然后将父进程的页表复制到子进程的页表中,最后写CR3寄存器,刷新MMU,这样父进程和子进程就共享同样的内存了。映射建立完后就可以直接使用线性地址寻址了。
6. 返回copy_process()函数,将填充好的task[nr]的tss和ldt的地址填充到GDT中相应的位置。
7. 将进程1改为状态改为就绪态,最终中断返回,fork返回子进程pid值。
8. 死循环中执行pause(),将当前进程(进程0)改为可中断等待状态,然后调用schedule()函数。
9. schedule()函数找到task[]中就绪态的进程只有进程1,于是切换到进程1。
10. 系统自动载入tss中的寄存器值到各寄存器,其中eip为进程0调用0x80中断后地址,exa=0,代码段基址在LDT[1]中,为0x4000000,这个线性地址是和进程0共享同一段内存的,因此代码段的物理地址是相同,因此跳转到进程0的eip的位置执行,也就是system_call函数的iret的位置。
11. 同样fork函数返回,返回值为exa,也就是0,执行init()函数。
读取硬盘信息
1. init()函数中执行setup()系统调用,触发0x80中断,系统转换到特权级0,执行sys_setup()函数。
2. sys_setup()函数中调用bread()读取硬盘信息到缓冲区bh,硬盘设备号是0x300和0x305,从0开始读取。
3. bread()函数中,首先使用getblk()函数在缓冲区中找到空闲的缓冲块。
4. getblk()函数遍历缓冲区数据结构,找到空闲的缓冲块,然后将缓冲块bh->b_dev=dev,返回缓冲区管理结构。
5. 返回bread()函数,调用ll_rw_block()函数从硬盘中读取数据到缓冲块。其中
major=MAJOR(bh->b_dev)=MAJOR(0x300)=3 blk_dev[major].request_fn=blk_dev[3].request_fn=do_hd_request
决定了硬盘该请求是硬盘读写请求。
6. bread()函数继续执行wait_on_buffer()函数等待硬盘读取完成,完成后返回bh
7. 返回sys_setup()函数,判断刚才读取的硬盘引导区内容是否有效,然后根据硬盘中的分区信息设置虚拟分区。
挂载根文件系统
1. sys_setup()函数中,继续调用rd_load()函数,读取软盘内容到内存的虚拟盘(4-6MB)中,并将根设备设置为虚拟盘(0x0101)。然后调用mount_root()函数挂载文件系统。
2. mount_root()函数中,先初始化file_table[64],然后初始化super_block[8],然后读取根文件系统(虚拟盘)的超级块到p,调用iget()函数读取根文件系统的根节点到mi。
3. iget()函数中,先在inode_table[32]中申请一个空闲的inode节点,然后将inode->i_dev = dev; inode->i_num = nr,nr为inode号,这里为1。然后调用read_inode()函数读取该节点。
4. read_inode()函数中首先读取inode->i_dev的超级块,然后和根据超级块的信息确定inode->i_num对应的块,最后使用bread()函数读取对应inode节点。
5. 最终返回iget()函数,将p->s_isup = p->s_imount = mi完成根文件系统的挂载。
6. 中断返回,最终返回sys_setup()函数。