首页 > 系统相关 >《Linux内核设计的艺术》——3.进程1

《Linux内核设计的艺术》——3.进程1

时间:2022-08-17 15:36:48浏览次数:68  
标签:调用 虚拟盘 文件系统 内核 Linux 进程 节点 block

0. 前言

现在已经有了处于特权3的进程0,将使用fork出进程1,之后的进程也使用fork。

void main()
{
   sti();
   move_to_user_mode(); // 切换到特权3
   if (!fork()) {
      init();       // 进程1进行init
   }
   for(;;) pause(); // 进程0循环进入可中断阻塞态
}

1. fork


fork函数使用汇编实现 int 80 中断, __NR_fork 是 sys_fork 在 sys_call_table 的偏移,
所以调用 sys_fork.
注意,中断使CPU硬件自动将 进程上下文(寄存器)压入 进程0的内核栈,这些压栈的数据将在 copy_process 函数中用于初始化进程的 TSS.
注意,EIP指向 指令 "int $0x80" 的下一行,即 if (__res >= 0)。这是中断返回后第一条执行的指令。进程1也是从这里开始执行。

1.1 sys_fork


注意 sys_fork,在调用 find_empty_process后,将其返回值(空闲的task[i]的编号)%eax 压栈,另外还将 TSS 压栈。所以 父进程的 TSS ,和 空闲的 task[i] 的编号 作为参数,调用 copy_process

1.2 find_empty_process


找到空闲的 task[i] ,返回编号

1.3 copy_process

工作内容如下:

  1. 为进程1创建 task_struct ,将进程0的task_struct 内容复制给进程1
    2)为进程1的task_struct,tss做个性设置
    3)为进程1创建第一个页表,将进程0的页表项内容赋给进程1的页表项
    4)进程1共享进程0的文件会话
    5)设置进程1的GDT项
    6)将进程1设置为就绪态,使其可以参与进程间的调度。

1.3.1 创建task_struct


copy_process有很多参数,都是 sys_fork 压栈的,主要为 新进程的task[i]的i,和父进程的TSS.
get_free_page获得空闲页面。


对于分页的疑惑可以参考进程0创建过程中对分页机制的创建。

copy_process后部分代码

用 *p = *current; 让子进程继承父进程的属性,然后再进程独特设置

注意:
task_struct 和 内核栈的关系

copy_process中对子进程 TSS的设置


注意

p->tss.eip = eip;
p->tss.eax = 0;

这样中断返回后,子进程执行 fork() 中 if(__res >= 0),且 __res 为0

1.3.2 设置进程1的分页管理

copy_process中对分页的设置

copy_mem

先获得 父进程的LDT,然后根据子进程的进程号,找到子进程的 LDT(在GDT中),
然后设置子进程的LDT, set_base(p->ldt[1], new_code_base); set_base(p->ldt[2], new_data_base);
最后调用 copy_page_tables 将父进程的页表 复制给子进程(用 父子进程的 LDT 基地址做参数)。

copy_page_tables

copy_page_tables会申请一个空闲的页面(基于内核的页表),作为子进程的页表空间,将父进程的页表复制给子进程,最后,用重置CR3的方法刷新页变换高速缓存。
注意:只复制了一个页表,因为一个页表有160个页表项,每个页表项对应一个页面,一个页面4KB,所以一个页表控制640KB,远大于进程0的 数据和代码占用空间。
实际上 copy_page_tables 会复制父进程全部的页表。

最终,进程0和进程1的页表,共同控制 640KB的空间。

1.3.3 进程1共享进程0的文件

1.3.4 设置进程1在GDT中的表项

这样内核就能通过 GDT 控制进程1的 LDT(程序和数据),TSS(上下文)

1.3.5 将进程1置为就绪态

1.4 fork返回

2. 内核第一次调度

Linux0.11一下情况发生调度
1)进程运行时间结束
进程在创建时,都被赋予了有限的时间片,以保证所有进程每次都只执行有限的时间。一旦进程的时间片被削减为0,就说明这个进程此次执行的时间用完了,立即切换到其他进程去执行,实现多进程轮流执行。
2)进程阻塞

2.1 pause

此时运行的是进程0,所以调用pause,
最终调用 sys_pause

进程状态变为 可中断态,并调用 schedule,

2.2 schedule

schedule先遍历 task[64],检查进程是否收到信号(alarm , signal 字段),若收到则 将 状态从 可中断态 变为 就绪态。
再次遍历 task[64],根据进程状态和时间片,找出就绪态,且counter最大的进程,执行 switch_to(next),调度进程。


switch_to

此时会发生任务切换,将进程1的TSS数据和LDT代码段,数据段描述符数据 复制给 CPU的各个寄存器,实现从特权0的内核代码切换到特权3的进程1代码执行。

注意:
进程0通过 pause调用 int80 中断,进入内核态,并调用 switch_to,切换到进程1,但进程0没有返回。

3. 进程1执行

根据 copy_process时,当时设置进程1的 tss.eip,指向 if(__res>=0),
所以进程1从 fork() 的 if(__res>=0) 开始执行。
由于 copy_process时,设置 p->tss.eax = 0,所以 __res为0


所以进程1 调用 init()

3.1 init

init先调用 setup

3.2 setup

setup也通过int80中断调用 sys_setup,
注意,进程0的进程还没有返回。

sys_setup为安装硬件文件系统做准备
1)根据机器系统数据设置硬盘参数
2)读取硬盘引导块
3)从引导块中获取信息

根据机器系统数据drive_info(柱面,磁头数,扇区数)设置内核的hd_info

读取引导块到缓冲区
引导块中有硬盘的 分区表,根据分区表可以引导处其他信息。
一个硬盘只有一个引导块,一个引导块有两个扇区,但真正有用的只有一个扇区。
使用 bread读取那个扇区

3.2.1 bread


先使用getblk获得对应的缓冲块,再读取。

3.2.1.1 getblk

getblk用 设备号,和块号 做哈希参数,获得缓冲块


由于还没有读取过,所以没有和 dev, blk 对应的缓冲块,所以 从 空闲列表中分配

一定能获得空闲的缓冲块

将获得缓存块,加入哈希表

对缓存块进行设置

remove_from_queues(bh); 将缓冲块从空闲表中删除

3.2.1.2 ll_rw_block

break函数 通过 getblk获得缓存块后,调用ll_rw_block,将缓冲块和请求项挂接

图中黑色部分为缓存块数组,在内核前部分已经完成格式化。

make_request会对缓存块进行加锁,
然后申请一个空的请求项,和缓冲块挂接。

make_request




lock_buffer

add_request
add_request调用 dev->request_fn, request_fn 是前期绑定的回调,这里是 do_hd_request

do_hd_request
先通过对当前请求项数据成员的分析,解析出需要操作的磁头、扇区、柱面、操作多少个扇区……之后,建立硬盘读盘必要的参数,将磁头移动到0柱面,如图
3-22中第二步所示;之后,针对命令的性质(读/写)给硬盘发送操作命令。现在是读操作(读硬盘的引导块),所以接下来要调用hd_out()函数来下
达最后的硬盘操作指令。注意看最后两个实参,WIN_READ表示接下来要进行读操作,read_intr()是读盘操作对应的中断服务程序,所以要提取
它的函数地址,准备挂接,这一动作反映在图3-22中的第三步。请注意,这是通过hd_out()函数实现的,读盘请求就挂接read_intr();如
果是写盘,那就不是read_intr(),而是write_intr()了。

.... 中间为分析磁盘参数

hd_out


hd_out:
do_hd = intr_addr; // 将磁盘中断处理方法和中断服务程序绑定

在硬盘中断时

xchgl_do_hd, %edx 会调用绑定的回调函数

3.2.2 ll_rw_block返回

hd_out最后下发硬盘命令,
硬盘开始将引导块中的数据不断读入它的缓存中,同时,程序也返回了,将会沿着前面调用的反方向,即hd_out()函数、do_hd_request()
函数、add_request()函数、make_request()函数、ll_rw_block()函数,一直返回bread()函数中。

3.2.3 wait_on_buffer

由于硬盘数据没有读完,所以调用wait_on_buffer,挂起等待

sleep_on 将进程1设置为不可中断态,然后调用 schedule

进入schedule函数后,切换到进程0去执行,
此时进程0的EIP指向 cmpl%%ecx, _last_task_used_match\n\t

最后进程0会进入 for(;

标签:调用,虚拟盘,文件系统,内核,Linux,进程,节点,block
From: https://www.cnblogs.com/yangxinrui/p/16564462.html

相关文章