日期:2022-10-13
版本号:V1.0
作者:snow
一、基础概念
二、多进程文件管理
2.1、多进程操作同一文件
PA PB PC=有效写入/读取数据
M=缓冲块数据
文件原始数据X,写入数据Y1、Y2……
文件最终数据Z
2.1.1 读写读
原始内容X,
进程A读取->进程B写入Y1->进程C读取
按以下步骤A读取到Y1,B写入Y1,C读取到X,文件存储Y1
(1)进程A搜索到文件file的i节点,将其登记到内核的inode中,标记引用次数为1
(2)进程A在内核的的filetable申请空闲项,然后将file登记到该项中
(3)进程A在自身栈中定义文件管理指针filep中,指向内核文件管理项
(4)进程A通过待读取的设备号和块号的哈希值否存在缓冲块
(5)进程A判断当前缓冲块不存在,申请一个空闲缓冲块,并将目标设备号和数据块号登记到缓冲块中(缓冲块初始化bh->b_uptodate置0,bh->b_dirty置0)
(6)进程A对缓冲块加锁,并请求通过硬件接口将硬盘中数据块到缓冲块中
(7)进程A等待缓冲块解锁(bh->b_uptodate置1)(将自身挂起)
(8)进程B搜索到文件file的i节点,发现其已存在内核的inode中,标记引用次数加1=2
(9)进程B在内核的的filetable申请空闲项,然后将file登记到该项中
(10)进程B在自身栈中定义文件管理指针filep中,指向内核文件管理项
(11)进程B通过待写入的设备号和块号的哈希值否存在缓冲块
(12)进程B判断当前已存在待写入的缓冲块
(13)进程B判断当前缓冲块处于加锁状态(且未读取完成)
(14)进程B重复发起缓冲块读取命令
(15)等待缓冲块解锁(将缓冲块当前的等待进程A备份至本地,然后将缓冲块的等待进程重新设置为进程B)
(16)调度器将进程C调度
(17)进程C搜索到文件file的i节点,发现其已存在内核的inode中,标记引用次数加1=3
(18)进程C在内核的的filetable申请空闲项,然后将file登记到该项中
(19)进程C在自身栈中定义文件管理指针filep中,指向内核文件管理项
(20)进程C通过待读取的设备号和块号的哈希值否存在缓冲块
(21)进程C判断当前已存在待读取的缓冲块
(22)等待缓冲块解锁(将缓冲块当前的等待进程B备份至本地,然后将缓冲块的等待进程重新设置为进程C)
(23)缓冲块读取完成,缓冲块解锁,将bh->b_uptodate置1,唤醒缓冲块当前的等待进程C(将进程C就绪)M=X
(24)进程C将目标缓冲块之前的等待进程B设置为就绪态
(25)进程C将缓冲块数据拷贝至用户区(PA=X)
(26)标记缓冲块引用次数减1=2
(27)进程C时间片用完/执行结束/被终止
(28)调度器将进程B调度
(29)进程B将目标缓冲块之前的等待进程A设置为就绪态
(30)进程B将用户数据写入至缓冲块(M=Y1,PB=Y1)
(31)将缓冲块bh->b_dirty置1
(32)等待update进程将缓冲数据写入硬盘
(33)标记缓冲块引用次数减1=1
(34)进程B时间片用完/执行结束/被终止
(35)调度器将进程A调度
(36)进程A将缓冲块的数据拷贝到用户数据区中(PA=Y1)
(37)进程A将缓冲块引用次数减1=0
(38)进程A时间片用完/执行结束/被终止
(39)update进程将缓冲数据写入硬盘,bh->b_dirty置0,Z=Y1
2.1.2 写读写
文件原始X
进程A写入Y1->进程B读取->进程C写入Y2
按以下步骤B读取到Y2,文件存储Y1
(1)进程A搜索到文件file的i节点,将其登记到内核的inode中,标记引用次数为1
(2)进程A在内核的的filetable申请空闲项,然后将file登记到该项中
(3)进程A在自身栈中定义文件管理指针filep中,指向内核文件管理项
(4)进程A通过待写入的设备号和块号的哈希值否存在缓冲块
(5)进程A判断当前不存在,申请一个空闲缓冲块,并将目标设备号和数据块号登记到缓冲块中(bh->b_uptodate置0,bh->b_dirty置0)
(6)缓冲块加锁,等待update进程将硬盘的数据块读取到缓冲块中(完成后bh->b_uptodate置1)
(7)调度器将进程B调度
(8)进程B搜索到文件file的i节点,发现其已存在内核的inode中,标记引用次数加1=2
(9)进程B在内核的的filetable申请空闲项,然后将file登记到该项中
(10)进程B在自身栈中定义文件管理指针filep中,指向内核文件管理项
(11)进程B通过待读取的设备号和块号的哈希值否存在缓冲块
(12)进程B判断当前已存在待读取的缓冲块
(13)进程B发现缓冲块bh->b_uptodate为0
(14)等待缓冲块解锁(将缓冲块当前的等待进程A备份至本地,然后将缓冲块的等待进程重新设置为进程B)
(15)调度器将进程C调度
(16)进程C搜索到文件file的i节点,发现其已存在内核的inode中,标记引用次数加1=3
(17)进程C在内核的的filetable申请空闲项,然后将file登记到该项中
(18)进程C在自身栈中定义文件管理指针filep中,指向内核文件管理项
(19)进程C通过待读取的设备号和块号的哈希值否存在缓冲块
(20)进程C判断当前已存在待读取的缓冲块
(21)进程C发现缓冲块bh->b_uptodate为0
(22)等待缓冲块解锁(将缓冲块当前的等待进程B备份至本地,然后将缓冲块的等待进程重新设置为进程C)
(23)由update进程硬盘数据块读取到缓冲块完成,缓冲块解锁(bh->b_uptodate置1)M=X
(24)调度器唤醒进程C
(25)进程C将之前缓冲块的等待进程B置为就绪
(26)进程C将用户数据写入到缓冲块中,M=Y2,PC=Y2
(27)将缓冲块bh->b_dirty置1,
(28)缓冲块引用次数减1=2
(29)由update进程后台将缓冲块数据写入硬盘数据块(写入完成后bh->b_dirty置0)
(30)进程C时间片用完/执行结束/被终止
(31)调度器唤醒进程B
(32)进程B将之前缓冲块的等待进程A置为就绪
(33)进程B发现缓冲块读取完成(bh->b_uptodate置1)
(34)进程B将缓冲块中的内容拷贝至用户区中,标记缓冲块引用次数减1=1,PB=Y2
(35)进程B时间片用完/执行结束/被终止
(36)调度器唤醒进程A
(37)进程A将用户数据写入到缓冲块相应位置,M=Y1,PA=Y1
(38)将缓冲块bh->b_dirty置1,
(39)缓冲块引用次数减1=0
(40)由update进程后台将缓冲块数据写入硬盘数据块(写入完成后bh->b_dirty置0,bh->b_uptodate置1)
(41)进程A时间片用完/执行结束/被终止
(42)update进程写入硬盘数据块完成(写入完成后bh->b_dirty置0,bh->b_uptodate置1)Z=Y1
2.2、多进程操作多文件
本节主要介绍硬件同步请求项、缓冲块不足时的处理基址,场景如下:
进程A写入文件P1,进程B写入文件P2,进程C读取文件P3
初始请求项队列为空(2/3为读写请求项,1/3读请求项),缓冲块均为空
读写数据量远大于缓冲块空间,缓冲块数量远大于请求项队列数量,当前请求项队列中正占用用较多数量
(1)进程A申请一个数据块,并将用户数据写入到缓冲块中,并将缓冲块置为脏状态
(2)update进程后台申请一个读写请求项与该缓冲块连接,并开始将缓冲块写入到硬盘数据块
(3)重复步骤1~2直至没有可用缓冲块
(4)调用sync_dev主动进行数据同步
(5)从现在的缓冲块队列中,找出一个数据块->设备号与文件P1相同的设备号的缓冲块X
(6)发现缓冲块X处于脏状态(引用次数为0=空闲+需要被写入到硬盘)
(7)将缓冲块X加锁,且为数据块申请一个请求项
(8)清除缓冲块X的脏状态
(9)调用硬盘请求项处理函数进行数据写入同步
(10)调用硬盘读写接口进行写入
(11)重复步骤(5)~(9)直至没有在(7)无法申请到空闲(读写)请求项
(12)进程A由于等待(读写)请求项被挂起
(13)调度器调度进程B
(14)进程B项缓冲块申请一个缓冲块(由于进程A对可用的缓冲块都写入了数据=空闲+脏状态+未加锁,然后在缓冲块不够时调用了sync_dev强制同步将部分缓冲块加锁并清除了脏状态,且此时没有缓冲块完成硬盘写入并解锁,故此时对于系统而言可用优先级最高的是不脏+加锁的缓冲块)(空闲缓冲块优先级:不脏+未加锁>不脏+加锁>脏+未加锁>脏+加锁)
(15)进程B发现申请的缓冲处于加锁状态,进程挂起,等待缓冲块解锁
(16)调度器调度进程C
(17)进程C项缓冲块申请一个缓冲块(此时没有缓冲块完成硬盘写入并解锁,故此时对于系统而言可用优先级最高的是不脏+加锁的缓冲块,同步骤。同时进程C申请的缓冲块与B申请的为同一个缓冲块)
(18)进程C发现申请的缓冲处于加锁状态,加入该缓冲块的等待队列,并成为其中的对首进程,然后挂起,等待缓冲块释放
(19)硬盘完成某个缓冲的写入工作,产生硬盘中断
(20)解锁缓冲块
(21)唤醒当前缓冲块的等待进程的队首进程C
(22)唤醒请求项等待进程的队首进程A
(23)进程调度器选择就绪队列中剩余时间片较多的进程C执行(假定此时C剩余时间片较多)
(24)进程C唤醒缓冲块之前的等待进程的队首进程B
(25)进程C将申请的缓冲块引用次数+1=1(不再是空闲缓冲块)
(26)进程C申请一个读取空闲请求项(由于该缓冲块的解锁必然伴随一个请求的工作完成,故此时一定能申请到一个空闲请求项,假设这里优先申请到的是读请求项)(注:读写请求项可以被读请求与写请求申请,读请求项只能由读请求申请)
(27)进程C将读请求项与该缓冲块绑定
(28)进程C将缓冲块加锁
(29)进程C将读请求项插入到请求项队列中
(30)进程C等待缓冲块读取完成,等待缓冲块解锁
(31)进程调度器将B执行(假设此时进程B的剩余时间片比A多)
(32)进程B发现缓冲块依然处于加锁状态,则加入该缓冲块的等待队列,并成为其中的对首进程,然后挂起,等待缓冲块释放
(33)进程调度器将A执行(假设此时上述缓冲块还没有完成)
(34)进程A参照步骤(5)~(10)执行,直至系统没有空余的请求项
(35)进程A由于没有可用的请求,参照步骤(12)挂起
(36)重复上述步骤,即硬盘执行完一个操作后,释放一个请求项并解锁缓冲块,解锁缓冲块后唤醒等待缓冲块的进程队列使用缓冲块,释放请求项则唤醒请求项的等待进程队列,被唤醒的请求项与缓冲块等待进程由不断反复使用这些缓冲块和请求项,请求项又反复去同步硬盘数据
(37)某个进程执行完毕,释放相应缓冲块
三、IPC通信管理
管道可以理解为将一个内存页面抽象为一个队列,然后让A、B两个进程共同操作这个队列,其中一个进程往这个队列写入数据,另一个进程从队列中获取数据,以实现进程间IPC通信。
3.1、父进程创建管道
3.1.1 申请内核文件管理项
(1)定义两个文件句柄fd[0]&fd[1]
(2)在内核文件管理表中申请两个空闲项,并将引用次数均记为1
(3)将两个文件句柄分别指向这两个空闲项
(4)在进程文件管理结构中,申请两个文件句柄指针,内核文件管理项连接
3.1.2 创建管道文件i节点
(1)创建一个i节点
(2)在内存中申请一个空闲页面
(3)将空闲页面的地址复制给i节点i_size(在块设备文件中该变量对应文件大小)
(4)i节点的引用次数标记为2
(5)i节点的属性设置为-管道型i节点
3.1.3 内核文件管理项与i节点连接
(1)fd[0]&fd[1]的i节点均均指向管道文件i节点
(2)fd[0]&fd[1]的指针偏移清0
(3)fd[0]的操作属性设置为read
(4)fd[0]的操作属性设置为write
3.2、父进程创建子进程
父进程创建一个子进程,由于子进程继承了父进程的所有内容,包括fd[0]&fd[1]、以及管道文件内存页面所对应页目录项以及页表。
意味着此时父进程与子进程均可以在道中进行读写,下面将以父进程写入数据,子进程读取为例。
3.3、管道数据读写
(1)父进程创建了子进程,并将子进程就绪
(2)父进程将要写入的数据写入管道
(3)将管道的等待进程进程设置为就绪(当前子进程没有执行,没有进程等待该管道)
(4)重复2~3直至管道写满(基于循环队列式访问),管道空间不足,进程挂起,等待管道空间释放
(5)调度器将子进程执行
(6)子进程从循环队列中读取数据
(7)唤醒正在等待管道的父进程
(8)重复(6)、(7)直至时间片结束/管道被读取完毕/被中止(此处假设管道读取完毕,则子进程挂起等待管道写入入)
(9)调度器将父进程运行
(10)父进程查看当前管道使用情况,若可以使用,则向管道中写入数据
(11)唤醒当前管道的等待进程子进程
(12)重复(11)~(12)直至写完或者空间不足(此处假设父进程写完)
(13)释放写入文件管理项
(14)父进程时间片结束/被挂起/执行完毕
(15)调度器启动子进程
(16)子进程读取管道完成
(17)子进程时间片结束/被挂起/执行完毕
(18)释放读取文件管理项
四、用户进程页面管理
当shell的子进程切换为执行目标文件的用户进程后,即真正开始执行自己的代码。
4.1、代码申请新页面
由于用户程序再首次执行以及执行完某个页面的代码时,通过逻辑地址地址到线性地址的映射,发现映射过程目标页面不存在或无效时,就需要将代码加载到一个可用的空闲页面中。
(1)发生缺页中断
(2)检查当前执行程序是否有效(是否需要加载代码)
(3)检查逻辑地址是否有效(超过进程代码文件大小)
(4)检查是否可与其他进程共享进程代码(的页面)(一个程序代码多个实例进程运行)
(5)申请一个空闲页面
(6)将空闲页面登记到页目录项(页表)中
(7)从读取一个页面的数据到该页面中
4.2、栈空间申请新页面
随着函数的执行,每个函数都会为自身局部变量申请一定的物理内存(在数据段的数据页面中,但是每个页面只能存放4KB的数据)
(1)待写入的数据(逻辑地址)大于当前栈顶指针所对应页面的剩余空间
(2)进入缺页中断
(3)申请一个空闲页面
(4)将空闲页面的地址映射为逻辑地址(在页目录项&页表中登记)
4.3、栈空间释放页面
在执行完成某个函数后,函数的局部变量应从栈空间中释放,若释放栈空间导致页面不在有可用数据,应释放相应的页面。
但实际并未立刻释放(代码和数据都具有局部性原理,当前使用的数据后续会大概率会再次使用->当前的函数会大概率会重复执行->避免每次执行都去申请页面)
故在linux0.11中无因为栈空间的释放进一步去释放相应的页面
4.4、用户进程释放页面
app执行完自己的业务程序后,会调用内核的exit()函数进行退出。一个进程的退出分为两部分,一是由进程自身完成的内存页面和相关资源(文件)释放,二是由shell进程释放APP使用的进程管理结构并解除进程槽的占用。
根据当前进程的LDT第1项释放代码段所有页面(页面使用次数减一)
根据当前进程的LDT第2项释放数据段所有页面(页面使用次数减一)
4.5、页面共享
在进程创建以及执行多个进程执行相同代码时,会直接将已有的页表项拷贝给目标进程,同时已有的页表项引用次数会加一次。同时为了避免多个进程在同一个页面写入不同的数据导致混乱,在引用次数大于1时,该页面会被置为“只读模式”
但随着进程的执行,进程可能会在数据段页面中进行压栈、出栈以及修改等操作,这时候就会触发写页保护。原理同创建新页面
(1)申请空闲页面
(2)将原有页面拷贝至新页面
(3)原页面引用次数减一
(4)更新进程页表(指向新页面)
(5)操作新页面
(6)原页面被访问,触发写页保护
(7)当前引用次数为1,解除当前页保护
(8)操作原页面
五、信号机制
信号可以理解为一种软中断信号,由一个进程发出信号,另一个进程接收到此信号后中断当前执行的代码,先响应该信号后,然后在回到原来的位置继续执行(原理与硬件中断+注册回调函数基本一样的机制)
每个进程的进程管理结构中都设置了用以接收信号的数据成员signal,linux可通过kill- l输出当前支持的信号编码
5.1、信号的基本介绍
5.1.1 信号分类
不可靠信号:也称为非实时信号,信号值取值区间为1~31,不支持排队,比如发送多次相同的信号时,后续发送的信号会覆盖之前的信号(只管发,不管之前的处理了没)
可靠信号:也称为实时信号,信号值取值区间为32~64,支持排队,信号发送多少次,接收进程就会接收到多少次(等之前的处理了再发),由用户定义信号实际意义
5.1.2 非实时信号编码
编号 | 英文 | 中文 | 备注 | 产生方式 |
1 | SIGHUP | 挂起 |
| |
2 | SIGINT | 中断 |
| |
3 | SIGQUIT | 退出 |
| |
4 | SIGILL | 非法指令 |
| |
5 | SIGTRAP | 断点或陷阱指令 |
| |
6 | SIGABRT | abort发出的信号 |
| abort |
7 | SIGBUS | 非法内存访问 |
| |
8 | SIGFPE | 浮点异常 |
| |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 | kill |
10 | SIGUSR1 | 用户信号1 |
| |
11 | SIGSEGV | 无效内存访问 |
| |
12 | SIGUSR2 | 用户信号2 |
| |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 |
| 写入的管道没有进程读 |
14 | SIGALRM | alarm发出的信号 |
| |
15 | SIGTERM | 终止信号 |
| |
16 | SIGSTKFLT | 栈溢出 |
| |
17 | SIGCHLD | 子进程退出 | 默认忽略 | |
18 | SIGCONT | 进程继续 |
| |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 | |
20 | SIGTSTP | 进程停止 |
| |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 |
| |
22 | SIGTTOU | 进程停止,后台进程想终端写数据时 |
| |
23 | SIGURG | I/O有紧急数据到达当前进程 | 默认忽略 | |
24 | SIGXCPU | 进程的CPU时间片到期 |
| |
25 | SIGXFSZ | 文件大小的超出上限 |
| |
26 | SIGVTALRM | 虚拟时钟超时 |
| |
27 | SIGPROF | profile时钟超时 |
| |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 | |
29 | SIGIO | I/O相关 |
| |
30 | SIGPWR | 关机 | 默认忽略 | |
31 | SIGSYS | 系统调用异常 |
|
5.1.3 信号产生方式
(1)硬件方式
硬件产生相关输入冰岛相应寄存器/内存发生变化时,由内核直接将相关信号发送出来
终端输入:ctrl+c =2 ctrl+z=20 ctrl+\=3
硬件异常:除0=6 非法访问,段错误=11 总线错误=7
(2)软件方式
当某个进程检测到某种软件条件发生并需要将其通知有关进程的时,调用内核相关函数产生信号
5.2、信号的注册
每个进程都有32个信号接收槽指针,该指针分别对应一个信号值的处理(但其中9号不由用户设置),同时还有一个blocked成员作为信号屏蔽掩码(0~31个bit位就分别对应1个信号,置为1则代表不响应该信号),信号的注册主要有三个参数:
(1)信号编码id,对应会使用的信号接收槽指针
(2)信号处理函数,后续通过注册回调机制使用
(3)重载值,信号处理函数调用参数
5.3、信号的发送
(1)检查信号接收进程PID是否合法
(2)检查信号编码id是否合法
(3)将接收进程的signal的对边信号值置1
5.4、信号的激活
linux进程调度器执行时,会遍历所有进程是否有接收到信号,然后根据需要唤醒对应的进程区响应相关的信号
(1)遍历当前所有的进程,,若满足(2)~(4),则使该进程处于就绪态
(2)若当前进程接收到信号
(3)该信号被标记为需要处理(不屏蔽)
(4)若接收该信号的进程当前状态位可中断等待状态
5.5、信号的接收处理
(1)执行到A处被(主)动中断
(2)当进程被再次被调度(因接收到信号或其他)
(3)将信号值入用户栈
(4)调用do_signal函数进行信号预处理
(5)信号值以及A处中断现场环境从用户栈弹出到内核空间
(6)将do_signal的返回函数设置(eip)设置为用户信号处理函数
(7)将A处的现场值中部分参数及返回地址值入用户栈
(8)将现场数据恢复函数地址入用户栈
(9)将信号处理函数所需参数入用户栈
(10)系统调用返回,正式进入用户信号处理函数
(11)将用户栈中信号处理函数的参数出栈
(12)用户信号基本处理
(13)再次激活该信号值的处理函数
(14)信号处理函数执行完毕,现场数据恢复函数地址弹出给ip
(15)将A处的现场值中部分参数及返回地址弹出给数据恢复函数
(16)数据恢复函数将A处的现场值及返回地址进行恢复
(17)数据恢复函数返回,系统从A处继续执行
第六章、撒花完结
参考资料:很多很多
致谢:很多很多
感想:很多很多
2022年10月31日