ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-08-17 15:30:18  阅读:126  来源: 互联网

标签:调用 虚拟盘 文件系统 内核 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
来源: https://www.cnblogs.com/yangxinrui/p/16564462.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有