进程1的创建及执行
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()
函数。