现代操作系统-原理与实现(下)【银杏书-读书笔记】

上篇链接戳这里

第7章-进程间通信

多进程协助的目的

  1. 功能模块化

  2. 增强模块间隔离

  3. 提高应用的容错能力

进程间通信IPC

一个简单的进程间通信

包含接收者,发送者和一段共享内存和他们的消息。

而消息由分成接收者消息和发送者消息。
在这里插入图片描述
而消息肯定会包含头部(魔数+状态+长度)和数据域
在这里插入图片描述
一个最最简单的通信过程如下
发送者发起消息,设置状态为准备就绪,填好该填的,发到共享内存,接收者一直在轮询发送者消息的状态,如果为准备就绪就取走。而发送者,一直在轮询接收者消息的状态,如果接收者取完消息,把状态修改为准备就绪,说明接收者完成接收。为了实现好这通信就会由很多学问。

数据传递

有两种实现

  1. 基于共享内存的消息传递
  2. 操作系统辅助的消息传递
基于共享内存的消息传递

不需要操作系统干预,它为多个进程在虚拟地址空间映射了同一段物理内存

操作系统辅助的消息传递

内核提供接口,不需要建立共享内存等等操作。

基于共享内存操作系统辅助
优点有可能实现内存零拷贝抽象层次更高,简单。可以保证每一次通信接口的调用都是一个消息备发送或者接收
安全性更好,不会保护哎发送者和接收者进程的内存隔离性
多方通信的时候,共享内存过于复杂,操作系统辅助更好

控制流转移

通信发生的时候,内核将控制流从发生方切换到接收方。

共享内存方案中,不结合消息或者其他的通信机制,进程只能轮询检查,浪费计算资源。

而IPC的控制流转移利用内核对进程的运行状态和运行时间的控制来实现。
例如两个进程,一个发一个收,操作系统在一旁辅助
在这里插入图片描述

单向和双向

一般有三种

  • 只有单向
  • 只有双向
  • 单双向都可以

同步和异步

  • 同步IPC
  • 异步IPC

区别就是一直等还是不用等
在这里插入图片描述

超时机制

异步IPC可能会等很久消息都不来,就需要定义一个超时时间,时间到了必须返回一个超时错误的机制
但是实际中又很难估算到底要设置多少,一般都是两个选项永不返回立即返回

通信连接管理

实际上就是如何建立一个连接

  • 间接通信
  • 直接通信
直接通信

建立是自动的,通过便是的名称找到对方,一一对应。

间接通信

通过中间的一个邮箱来实现,邮箱是唯一的,用邮箱来交换信息。

权限检查

保证连接的安全性,能够通信之前一定要检查权限,能不能由Capability来刻画。
检查其权限能不能访问这个对象
对于SystemV IPC组件来说,检查都是基于文件的权限检查机制。
那么这种检查就依赖于进程的用户分组/文件的权限抽象来判定

命名服务

如何分发权限,通过命名服务来协调服务端和客户端之间的消息。
服务端把自己提供的服务告诉命令服务,客户端去命令服务上找当前的服务,选自己需要的,尝试获取。
命令服务一般跑在用户层,除了命令服务之外,还可以用继承的方式实现。
在这里插入图片描述

宏内核进程间通信

IPC组件

  • 管道
  • 消息队列
  • 信号量
  • 共享内存
  • 信号机制
  • 套接字机制
管道

单向的,一定是一端发送,一端接收
在这里插入图片描述
这个管道通常会有一定数量的缓冲区缓冲消息而且通信的数据抽象都是按字节流定义的,应用需要对数据解析

具体实现

管道本身也是一个文件,它创建的时候会返回一组文件描述符和一段内存作为数据的缓冲区,有点类似于FIFO。
一个进程输入数据,另一个进程就在输出端读到数据。

对于“读取空”的处理分成两种情况

  • 如果写端口一侧没有任何进程,这种返回EOF
  • 如果是没数据来,那么就阻塞/非阻塞
命名管道/匿名管道

区别在于创建的方式。
匿名管道通过pipe命令创建,创建的同时进程会拿到读写的端口**(两个文件描述符)**。由于匿名,只能通过文件描述符来用。因此需要结合父子进程fork的场景下来用。
在这里插入图片描述
子进程通过fork也拿到了管道的文件描述符,父子进程都有管道的信息,是可以进行通信的。但是需要父子进程关闭多余的端口,避免通信出错。

命名管道就是管道有标识符
通过mkpipe命令创建,两个进程通过这个管道的名字识别,进行通信连接。
在这里插入图片描述

消息队列

唯一一个以消息作为数据抽象的通信方式,在内核中的表示是队列的数据结构。
每一个队列一个队列头,会有指针指向下一个。
消息队列的内容包含数据和类型,内核不需要知道类型。
这个一般弄过RTOS的肯定知道
很熟悉的内核/用户间传递消息的函数copy_from_user和copy_to_user函数就是传递消息的。

在Linux中,消息队列的空间是优先的,用完也是要释放的。如果数据量大,建议用共享内存!!!

信号量

概念性的不多写了,弄过RTOS的肯定知道。设计到同步原语章节再写。

共享内存

它的出现主要还是性能,因为可能做到0拷贝还是很好的,相比其他IPC来说。
它为多个进程在虚拟地址空间映射了同一段物理内存
这里以Linux的设计为例,共享内存的设计思想就像下面这样

在这里插入图片描述

信号机制

单向的事件通知能力,仅仅传输一个编号
一个进程可以随时发送出一个事件到特定的进程,线程,进程组。
一个进程会为一些特定的信号编号做回调处理函数

信号分类
  • 常用信号1-31
  • 实时信号32-64(POSIX标准引入的,用于实时场景)
信号发送

常见的就是kill函数和tgkill函数
内核会为每一个进程和线程准备一个信号事件等待队列,通过不同的系统调用与参数,来确定接收信号的目标进程或线程,将信号事件添加到其等待的队列上。

信号阻塞

当一个信号被阻塞之后,Linux不会触发其对应的回调函数,知道该信号被解除阻塞状态。它并不阻塞信号添加到等待队列之上,当进程解除某个信号的阻塞状态时,它才可以处理阻塞期间收到的信号。

信号的响应和处理

内核会检查一个状态位来判断是不是信号需要处理
三种处理的方式:

  1. 忽略
  2. 去用户自定义的处理函数
  3. 去系统默认的处理函数(如果用户没有为其注册处理函数)
信号的用户态与内核态的切换

在这里插入图片描述

  1. 用户态进程进入系统调用,内核处理完系统调用
  2. 发现信号来了,去信号处理函数,切换回用户信号处理函数
  3. 信号处理完以后,通过信号处理函数的sigreturn返回到内核,辅助内核恢复到被信号打断之前的上下文
  4. 直接恢复到之前的用户态(进系统调用之前的用户态)
    Linux会将系统调用的返回值和此前的用户上下文信息等保存在用户栈之上。
信号的嵌套与可重入

嵌套可能会导致最终处理结果与预期结果不一致的问题
所以要考虑可重入的问题

  1. 不使用静态数据,或者使用静态数据仅仅只读
  2. 尽量使用本地数据
  3. 注意全局变量的保护
  4. 不要调用不可重入的函数(如malloc)
套接字机制

即可用于本地,也可以用于网络通信
UDP和TCP
书上没有过多讲这个,感觉在unix网络的书籍会讲很多吧

微内核的IPC设计

在微内核设计下,一个应用程序获取系统服务通常需要通过IPC的方式实现

Mach

通过两种基本的抽象

  1. 端口
  2. 消息

设计实现了一种间接的通信IPC,通过端口进行通信消息

端口又细分为

  1. 发送者端口
  2. 接收者端口

通过权限系统来区分,发送者端口发送,接收者端口接收

端口在实现上可以看作消息队列的操作接口,一个入队,一个出队

此外,Mach的消息还可以传递端口,实现动态建立进程间通信的功能
举例就是A-B,A-C直接通信,但是B-C之间没有,需要A把端口发往B,B再发往C,然后建立联系

L4

这种系统优化了IPC
区分消息的大小,分为

  • 长消息
  • 短消息
短消息

用寄存器的参数传递的方式实现,这个比较依赖硬件,传递的数据少,但是可以0拷贝
更进一步的,出现了虚拟消息寄存器的方式扩展了寄存器比较依赖硬件,传递的数据少的问题。

长消息

再内核为进程开辟了一个临时缓冲区的方式,以两次拷贝完成。

第8章-同步原语

生产者-消费者模型

核心要义就是当缓冲区已经满了,生产者应该停止向缓冲区写入数据,避免数据覆盖。当缓冲区空时,消费者应该停止从缓冲区拿去数据,避免读到无效数据。

同步原语就是用来解决这种同步的问题而出现的

常见的同步原语语义

临界区问题

一个消费者,多个生产者的场景下,会出现程序的正确性依赖于执行顺序的情况,这种情景叫竞争冒险。出现的原因就是对共享资源的竞争,为了确保同一时刻下仅仅只有一个生产者对共享缓冲区进行访问,那么同一时刻下只允许最多一个线程访问,这个就是互斥访问。保证互斥访问共享资源的代码区域就叫做临界区,在这其中,线程可以安全的访问共享内存区域。

一个算法如何保证程序执行的正确性:

  1. 互斥访问【同一时刻仅一个线程执行临界区】
  2. 有限等待【有限时间内获得进入许可,不能无限等】
  3. 空闲让进【没用线程进临界区时,要选一个线程让其进入临界区】

简单的可以通过关闭中断的方式实现,因为关中断可以暂停调度器的工作,避免多个线程同时进临界区,短且有限时间的开关中断,让后续的线程可以进入。

皮特森算法

一种纯软件的方法,解决临界区的问题,能够在这两个线程同时执行时保证临界区的互斥性。

flag[2]是一个数组,表示线程0和线程1是否尝试进入临界区
turn是线程编号,对应的值也是0和1

一开始时flag数组被置为FALSE

//对于线程0
while(TRUE)
{
    flag[0]=TRUE;
    turn=1;/*要设置为对方的编号*/
    //线程1必须没有进(flag[1]=FALSE)或者现在轮到线程0进(turn=0),否则等待
    while(flag[1]==TRUE && turn==1);
    /*临界区部分*/
    flag[0]=FALSE;/*线程0没有进*/
    /*其他代码*/
}

//对于线程1
while(TRUE)
{
    flag[1]=TRUE;
    turn=0;
    while(flag[0]==TRUE && turn==0);
    /*临界区部分*/
    flag[1]=FALSE;
    /*其他代码*/
}

这个算法的限制是必须严格限制访存操作按程序顺序执行,但是现代CPU为了提升性能需要乱序执行,这样算法就无法在这样的CPU上执行了

原子操作

原子操作是指不可以被打断的一个或者一系列的操作

要么完成,要么不完成,不会执行到一半

常见的原子操作伪代码

  1. 比较与置换【CAS】
  2. 拿取并累加【FAA】
int CAS(int *addr,int expected,int new_value)
{
	int tmp = *addr;
	if(*addr == expected)
		*addr = new_value;
	return tmp;
}

int FAA(int *addr,int add_value)
{
	int tmp = *addr;
	*addr = tmp+add_value;
	return tmp;
}

根据不同的硬件,其实现的方式各有不同。

利用原子操作即可实现互斥锁,自旋锁,排号自旋锁等等【就是加了等待者FIFO队列的自旋锁】

书中介绍了一个叫条件变量的机制,这个有点像异步通知【挂起/唤醒的机制】的感觉。

一个线程可以停止使用CPU并挂起,等条件满足,其他线程会唤醒挂起的线程让它继续执行。能够有效避免无谓的空等待。

int empty_slot = 5;
int filled_slot=0;
struct cond empty_cond;//对应缓冲区无空位
struct lock empty_cnt_lock;//保护empty_slot的互斥锁
struct cond filled_cond;//对应缓冲区无数据
struct lock filled_cnt_lock;//保护filled_slot的互斥锁


//生产者
void producer(void)
{
    int new_msg;
    while(TRUE)
    {
        new_msg = produce_new();
        
        lock(&empty_cnt_lock);
        /*进入临界区*/
        while(empty_slot==0)/*防护两个生产者竞争进入empty_slot--*/
            cond_wait(&empty_cond,&empty_cnt_lock);//条件变量等待
        
        empty_slot--;
        unlock(&empty_cnt_lock);
        /*退出临界区*/
        
        buffer_add_safe(new_msg);
        
        lock(&filled_cnt_lock);
        /*进入临界区*/
        filled_slot++;
        cond_signal(&filled_cond);/*加入这里仅限制filled_slot=0调用cond_signal,则只能唤醒等待的消费者中的一个,其他的就一直等*/
        unlock(&filled_cnt_lock);
        /*退出临界区*/
    }
    
    
}


//消费者
void consumer(void)
{
    int cur_msg;
    while(TRUE)
    {
     
        lock(&filled_cnt_lock);
        /*进入临界区*/
        while(filled_slot==0)/*可能有多个消费者在等,此时如果多个生产者到达*/
            cond_wait(&filled_cond,&filled_cnt_lock);//条件变量等待
        
        filled_slot--;
        unlock(&filled_cnt_lock);
        /*退出临界区*/
        
        cur_msg = buffer_remove_safe();//原子操作共享缓冲区
        
        lock(&filled_cnt_lock);
        /*进入临界区*/
        empty_slot++;
        cond_signal(&empty_cond);
        unlock(&empty_cnt_lock);
        /*退出临界区*/
        consumer_msg(cur_msg);
    }
    
    
}
信号量

又称PV原语
用来辅助控制多个线程访问有限数量的共享资源
一般提供三个操作

  1. 初始化
  2. wait
  3. signal
信号量的实现

value可以为正数也可以为负数
当没有线程等待时,其为正数或0,其表示剩余资源的数量
当有线程等待的时候,value为负数,其绝对值表示正在等待获取资源的线程数量

wakeup来表示有线程等待时的可用资源数量【应当唤醒的线程数量】,其只能为正数或者0

struct sem
{
	int value;//剩余资源的数量
	int wakeup;//有线程等待时的可用资源数量
	struct lock sem_lock;
	struct cond sem_cond;
}

void wait(struct sem *S)
{
	lock(&S->semlock);
	S->value--;
	if(S->value<0){
		do{
			cond_wait(&S->sem_cond,&S->sem_lock);
		}while(S->wakeup==0);//避免线程一直无法被唤醒
		S->wakeup--;
	}
	unlock(&S->sem_lock);	
}

void signal(struct sem *S)
{
	lock(&S->sem_lock);
	S->value++;
	if(S->value<=0){
		S->wakeup++;
		cond_signal(&S->sem_cond);
	}
	unlock(&S->sem_lock);	
}
对比

二元信号量,计数值只能在1或者0之间跳,它与互斥锁的差别在于

互斥锁有拥有者的概念,但是二元信号量没有。互斥锁由同一个线程加锁解锁,而信号量允许不同线程执行加锁和解锁。

计数信号量,计数值表示剩余可用的共享资源的数量

它允许多个线程通过,互斥锁则由同一个线程加锁解锁,保证多个线程对一个共享资源的互斥访问。信号量则协调多个线程对一系列共享资源的有序操作。

读写锁

多个读写线程的环境下,提供互斥访问并提高读者之间的并行度,减少性能损失。
这种锁,需要为读者和写者提供独立的加锁和解锁的接口。

读临界区只能保证读者与写者互斥,不保证读者之间的互斥

如果有写者在同一把读写锁保护的临界区内,读者就必须等待该写者退出临界区之后才能进入。

写临界区既不允许其他读者进入读写锁保护的读临界区,也不允许其他写者进入读写保护的写临界区

读写锁有两种实现

  1. 偏向读者的读写锁
  2. 偏向写者的读写锁

【偏向读者的读写锁】
允许读者直接进入,那么写者会被一直阻塞到直到没有任何读者,可能陷入无限等待。
【偏向写者的读写锁】
允许写者进入,再允许读者进入,但是这样会减少读者的并行性。

RCU【Read-Copy Update】

减少读写锁的性能开销,它允许多个读者同时进入读临界区,同时写者不会阻塞到读者,且读者不需要额外用同步原语保护读临界区。

由于现代的CPU都提供了地址对齐的读写原子操作,即要么全部都赋值完毕,要么全部都没有赋值。
利用这一点就可以实现订阅/发布机制,实现对指针的读写原子性,让写者能够原子地更新任意大小的数据。

读者要么读到旧的指针,要么读到新的指针,可以遍历到节点。通过这个机制,可以让写者原子地更新任何大小的数据。

订阅/发布实际上是一次读操作/写操作,涉及到访存操作顺序的问题。我们需要保证第一步与第二步的访存顺序,保证顺序的操作打包成两个接口,便于开发者使用。

宽限期

描述从写者更新指针到最后一个可能观察到旧数据的读者离开的这一段时间,写者必须区分出有可能观察到旧数据的读者,而读者也必须标记自己的读临界区开始与结束的位置。

通过订阅/发布机制来更新数据以及使用宽限期机制来回收失效数据,RCU允许读者在有写者并发写入共享数据时,也无须任何耗时的同步操作,不被阻塞地随意读取共享数据。

死锁

为何死锁
  1. 互斥访问
  2. 持有并占用
  3. 资源非抢占
  4. 循环等待
  5. 特殊场景【中断中用了锁】
如何恢复

当资源分配与线程等待出现了“环路”,则表示出现了死锁
解决的方法就可以找到环中任意的线程,将其终止释放占有的资源,直到可以恢复正常运行。
或者可以让环上所有线程回退到死锁前的某个状态。

如何预防
  1. 避免互斥访问
  2. 不允许持有并等待
  3. 允许资源被抢占
  4. 避免循环等待,按照顺序获取资源
如何避免

可以采用银行家算法,具体看书

活锁

两个线程都无法成功地同时获取两把锁并进入临界区
解决的方法可以让线程在获取失败后等待随机时间后再次尝试

优先级反转

由于同步导致执行顺序违反预先设定的优先级,具体场景描述看书。
为了避免,可以采取
不可抢占临界区协议【得到锁就不可以被其他线程抢】
优先级继承协议【高优先级线程等待锁时,会使锁的持有者继承其优先级,避免锁被低优先级的任务打断】
即时优先级置顶协议【将优先级置为可能竞争该锁的线程中的最高优先级】
原生优先级置顶协议【在真的有其他线程竞争该资源的时候,将优先级置为可能竞争该锁的线程中的最高优先级】

第9章-文件系统

文件
一个有名字的字符序列

文件数据
序列的内容

文件元数据
描述文件数据的属性等其他信息

文件名
区分不同的文件,保存在目录中

目录
每个文件名和对应的文件地址或编号组成一个目录项,一个或者多个目录项组成一个目录

文件系统
实现文件接口并扶负责管理文件数据和元数据的系统

块设备
操作系统将存储设备抽象成一个个存储块,在逻辑上划分成固定的大小(一般为512bytes或4KB),每个块对应一个地址,叫块号

Linux的文件系统架构图
在这里插入图片描述

inode与文件

结构图类似页表那样,这里去看书。
一个文件系统所能支持的文件带线啊哦依赖于文件数据组织方式的限制。

文件名和目录

目录是连接inode号和文件名的一个中介。
目录本身作为一种特殊的文件,可以结构化的组织文件系统中的文件,其中的每一个目录项记录着文件的文件信息(文件名和inode号)
在这里插入图片描述
.一个目录项包含上述的信息。
对目录来说,主要的操作包含查找,遍历,增加和删除目录项。

硬链接和软链接

硬链接

在linux一个文件可以对应多个文件名,通过In file link 指令可以为文件file创建一个名字叫link的链接,也就是硬链接。它并不会创建一个新的inode,而是找到目标文件的inode,随后在目录增加一个指向此inode的新目录项,此目录项与文件地位一致。

对任意一个硬链接的读,写,元数据操作都会影响到指向同一inode的其他硬链接。但是删除操作,只有当所有指向这个inode的全部硬链接都被删除时,这个inode以及其对应的数据才会被删除。

软链接

通过In -s file slink 指令可以为文件file创建一个名字叫slink的软链接,它保存的是一个字符串,表示一个文件路径,所对应的文件为目标文件

符号链接文件本身只支持读取操作。

读取操作只需要找到符号链接文件的inode,并返回其中保存的路径就可以。

修改符号链接文件的内容,一般需要先删除原文件,再使用新的路径创建一个名字相同的符号链接文件

比较

均允许应用程序使用一个新的路径访问已有文件(目标文件)

访问对象是符号链接时,文件系统读取符号链接中保存的路径,并继续解析,最终找到目标文件。

访问对象是硬链接时,其直接通过硬链接的目录项访问到目标文件的inode.

符号链接

  1. 有自己的inode,有自己的权限,时间,等等元数据。且删除符号链接本身,不会影响到其目标文件。
  2. 用户可以存放任意的目标路径,即使目标路径不存在。
  3. 只有在跟随符号链接进行路径解析时,符号链接中的路径才会真正使用
  4. 不受文件系统边界的限制,即使在一个文件系统中,可以创建一个指向其他文件系统的符号链接。

硬链接

  1. 与目标文件地位一致,共享同一个inode
  2. 删除任意一个,应用程序依然可以通过另外一个对文件数据进行操作
  3. 无法创建一个指向不存在的文件,且指向的目标文件不能是目录。

存储布局

文件系统以特定的存储布局将文件数据和元数据保存再存储设备中。
基本的布局包含:

  1. 超级块
  2. 块分配信息
  3. inode分配信息
  4. inode表
  5. 文件数据块

超级块
记录整个文件系统的全局元数据,其中包含魔数信息,区分不同的文件系统,操作系统通过读取魔数知道是何种文件系统。
块分配信息
标记文件数据块区域中各个数据块的使用情况。每比特表示一个块的使用情况。
inode分配信息与inode表
以数组形式保存了整个文件系统的inode结构,文件系统使用inode在此表中的索引对不同的inode进行区分。对于inode表的大小,各个文件系统不同,所以能够保存的最大文件数量也不同。
文件数据块
用来保存文件的数据

虚拟文件系统VFS

对多种文件系统进行管理,允许多种文件系统在同一操作系统上运行。虚拟文件系统要求不同的文件系统提供对应的方法,利用这些方法进行统一转换为VFS的内存数据结构,向应用层提供服务。

VFS定义的内存数据结构

包含以下部分

  1. VFS中的超级块
  2. VFS中的inode
  3. VFS中的文件数据管理
  4. VFS中的目录项
VFS定义的文件系统调用路径

封装也是搞得函数指针那套
在这里插入图片描述

文件描述符

调用系统函数操作文件之后,会返回一个叫文件描述符的东西。

实际上它是一个整数,由于VFS为每一个进程维护一个文件描述符的表,这个表保存着一组文件描述结构,它记录着每一个打开的文件的各种信息。VFS解析正确要操作的文件并找到inode后,VFS分配文件描述符和对应的文件描述结构,把inode记录到这个文件描述结构里,最后把这个文件描述符返回到应用层。
在这里插入图片描述

文件描述符的作用就是在APP和内核之间建立认证的关系

页缓存,直接IO与内存映射

由于文件的访问具体时间局部性,当文件的一部分被访问之后,大概率会再次被使用。应用程序再次需要读取这些数据的时候,就可以从此前保存在内存的数据中读取,不用再访问存储。而且一个文件请求中的多次修改如果均在同一个存储块的话,那么可以将多次修改合并,减少对存储的访问。

一个文件写请求返回到应用程序之后,允许其修改的数据暂时不要写回到存储,而是允许文件系统暂时将修改保存到内存之中,再后台慢慢写回到内存里,可是牺牲了可靠性。

于是有了fsync函数,用于保证某个已打开的文件的所有修改全部写入存储中。

如果修改的地方都是同一块区域,则写在内存里,内存不够就调用这个一次性写入存储里。

页缓存

读缓存和写缓存合并起来管理,以内存页为单位,将存储设备中存储的位置映射到内存中。

在这里插入图片描述

直接I/O和缓存I/O
某些应用对数据持久化有要求,操作系统便会把页缓存的使用权给应用层,可以选要不要用页缓存。不用的话就直接I/O操作,否则就是缓存I/O。

本章后面关于FAT和NTFS的介绍不打算写了~~~

第10章-设备管理

设备的连接-总线

  1. AMBA总线【ARM的片上总线】
  2. PCI总线

可编程IO与寄存器

寄存器包含:

  1. 控制寄存器
  2. 状态寄存器
  3. 输入/输出寄存器

访问寄存器的两种方式
4. 内存映射I/O 【MMIO】【将寄存器映射内存空间且独立地址】
5. 端口映射I/O 【PMIO】【提供专门的端口操作指令】

DMA

发起DMA的可以是处理器/IO设备/DMA控制器
第三方DMA
需要DMA控制器参与,它与处理器和内存在同一条总线,相当于一个中间者,连接起处理器和外设
第一方DMA
允许设备直接获取总线的控制权进行DMA操作,多个设备希望同时DMA时,总线控制器需要仲裁,决定优先级,同一时刻仅一个使用。

IOMMU设备地址翻译

设备与内存之间的输入输出内存管理单元(IOMMU)将总线地址翻译成物理地址
由于设备本身的限制,设备可以访问的内存可能小于物理内存的总大小。
在这种情况下,应该要在设备DMA能够访问到的内存区域之内和之外分别分配一个内存缓冲区,进行数据搬运
另外可以使用IOMMU进行地址翻译,它的存在免除了DMA缓冲区内各个内存页必须物理连续的限制,同时IOMMU本身限定了可以访问的物理内存的范围。

设备的识别

设备树

描述计算机的硬件信息的数据结构,包含了CPU的名称,内存,总线,设备等信息。

ACPI

x86用的设备识别方法,在设备和操作系统之间的一层抽象

设备的中断

ARM的中断控制器

  1. ICOLL
  2. GIC

在GIC中,中断类型有三类

  1. 软件生成中断
  2. 私有设备中断
  3. 共享设备中断

中断优先级,中断号,中断状态,中断响应过程用过MCU肯定都知道

Linux的中断处理

这个不想再重复写了,看自己写的imx6ull的笔记
我的imx6ull学习笔记
这本书多介绍了一种方式CMWQ[并发可管理工作队列],想更多了解自行百度

Linux的驱动模型

platform驱动
这个也不想再重复写了,看自己写的imx6ull的笔记
我的imx6ull学习笔记

Linux的用户态驱动

sysfs,将内核中的设备信息和驱动信息以文件的形式提供给用户程序用
这个不展开了,想更多了解自行百度

第11章-系统虚拟化

简单写写吧,实际工作也不大用的到~
包括:

  1. CPU虚拟化
  2. 内存虚拟化
  3. IO虚拟化

CPU虚拟化

如果每一条敏感指令在用户态执行时能够触发下陷,那么虚拟机监控器就可以代替虚拟机,以一种安全的方式模拟引起下陷的结果,这种模拟的方法就叫做下陷与模拟

可虚拟化架构/不可虚拟化架构

所有敏感指令都是特权指令,都可以触发下陷。

实现CPU虚拟化

实现CPU虚拟化有以下几种方法:

  1. 解释执行
  2. 动态二进制翻译
  3. 扫描和翻译
  4. 半虚拟化
  5. 硬件虚拟化
    不具体展开了~

内存虚拟化/IO虚拟化

感觉太深奥,随便翻了下,实际工作也不大用的到。


就这样吧,这本书就看完啦
20年双十一开始看
21年5月16日看完
工作再忙,也要保持学习~~
THE END

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值