操作系统原理、实现与实践课后习题参考答案(已完结)

习题二–系统接口 通向操作系统内核的大门

1.调用fork()的父子进程执行“同样”的代码,如何理解”同样“?
答:

fork()函数为系统调用,用于创建进程。创建的进程与原来进程几乎完全相同.
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同(毕竟要区分父子进程
pid)。相当于克隆了一个自己


2.
在这里插入图片描述
3.写出scanf("ID:%d:,&id)展开成read系统调用以后的形式。

4.写出syscall3(int,writr,int,fd,char *,buf,int,count)

int write(int fd,char * buf, int count)
{
long __res;
__asm(
"int 0x80"
:"=a"(__res):""(__NR_write),"b"((long)(fd)),"c"((long)(buf)),"d"((long)(count)));
if(__res>=0) retrun (int)__res;
errno = -__res;return -1;
)
}

5.实践项目2流程图(添加系统调用)

我们自顶向下考虑如何实现。以int iam(const char* name) 将字符串参数name的内容复制到内核中保存为例
应用端应该是我们编写的iam.c测试程序,其内部直接调用iam这一系统调用实现用户数据对内核数据的交互。
那么重点就在于iam这一系统调用如何实现: iam要利用宏 展开成一段包含int 0x80的代码,(syscall1)
操作系统解释执行int 0x80 通过查找idt表中和0x80对应的表项 跳到system_call函数执行 ;
system_call调用具体的内核函数实现上述操作,该内核函数的地址应该放于sys_call_table表中,内核函数的编写利用asm/segment.h提供的内核与用户信息交互函数即可完成(put_fs_bytes、 get_fs_bytes) 最后由于在内核中新加文件 所以需要修改makfile 。

习题三–多进程 操作系统最核心的视图

1.并发可提高CPU效率? 也可提高硬件工作效率?
是的,并发提高CPU效率(如利用等待磁盘响应时间去执行另外进程)
硬件工作效率提高与CPU类似,本质都是并发导致了执行总任务的时间减少,自然效率就提高了。
2.
(1)PID=4进程在执行 原因是PID=12进程的创建一定是由其它进程执行的 只能是PID=4
(2)PID=12的进程运行态切换到就绪态或阻塞态的过程缺失
(3)略
3.当前进程主动产生调度点:sys_pause()、sys_waitpid()、wake_up()
操作系统硬性加入调度点:sleep_on()、interruptible_sleep_on().

习题四–多进程 操作系统最核心的视图

1.内嵌汇编宏展开 Yield实现

#define Yield()
{
	next = Findnext();
	__asm__("push %eax"
	"push %ebx"
	"..." #其它通用寄存器
	"mov %esp,TCB[current].esp"
	"mov TCB[next].esp,%esp"
	"..." #其它通用寄存器现场恢复
	"pop %ebx"
	"pop %eax"
	)

}

将参数作为结构体传入进去即可

struct arg
{
	int i,
	int j

}arg1;
thread_create(sum,arg1);

不会哇
渴望大佬帮帮我!!
4.
(1) 存在的 PR的时常可认为是从小至大
(2)需要多变量 违背简单原则
5.
(1) 页内存的大小为4KB linux0.11 PCB和对应内核栈指针在同一页内存上 PCB在低内存处 内核栈在高地址处
(2) tss在该程序中的作用仅仅是在中断发生时能找到内核栈的位置,而ss0一直不变 使用init_task的即可。
6.
(1) 0
(2) 参数 与父进程保持一致
(3) ebp来自父进程 用户栈初始与父进程共用 待子进程需要改动后,再新建
7.
进程的FS寄存器实际上都是0X17,找到不同的用户态内存是因为两个进程查的LDT表不同,重置是因为段寄存器包含显示和隐式参数,隐式参数由LDT表决定,所以再更改LDT表后,需要重新修改段寄存器。

习题五–进程同步-多进程合理有序推进

1.(1) 如果是队列的话 P1执行后放到队尾 此时P2是队首,那么当下一次出现调度时 会执行P2,不过P1就会不得执行。如果是全部唤醒 根据优先级调度的话 若P1优先级优于P2优先级 则永远得不到执行
(2)死锁吗?不过我也不知道为啥会死锁唉 等大佬解答
2.

司机:
while(1)
{
	P(关闭车门指令);
	//注意因为这里不是共享缓存区 所以不需要mutex 即不互斥进入
	司机操作

	V(停靠完毕指令);
}
售票员:
while(1)
{
	P(停靠完毕指令);

	售票员操作

	V(关闭车门指令);
}

因为生产者是产生的随机数,所以每个消费者在读取一个字符时 应该一次性读取缓冲区的所有数据len(从0到\0) 然后把第一个符合自己调节的字符取出,在向缓冲区写入剩下的len-1个字符 这样即可实现题目要求。
4.略
5.
分配号码相当于标记 而选择号码最小的进程进入临界区相当于轮换

使用choosing数组是必须的。假设不使用choosing数组,那么就可能会出现这种情况:设进程i的优先级高于进程j(即i<j),两个进程获得了相同的排队登记号(Number数组的元素值相等)。进程i在写Number[i]之前,被优先级低的进程j抢先获得了CPU时间片,这时进程j读取到的Number[i]为0,因此进程j进入了临界区. 随后进程i又获得CPU时间片,它读取到的Number[i]与Number[j]相等,且i<j,因此进程i也进入了临界区。这样,两个进程同时在临界区内访问,可能会导致数据腐烂(data corruption)。算法使用了choosing数组变量,使得修改Number数组的元素值变得“原子化”,解决了上述问题。
6.多米诺骨牌效应 死锁越来越多 直至崩溃
7死锁的必要条件是循环等待
8.死锁避免 银行家算法
9.无限遍历 并且每一个进程执行完 可利用资源是增大的 如果有安全序列 则一定能找到
10.安全序列 work
P2: 5 3 2
P4:7 4 3
P5:7 4 5
P1:7 5 5
P3:10 5 7

习题六–内存管理-给程序执行提供一个舞台

1.根据下图所示情况,给出执行指令"mov 1,[300]"时要做的地址转换工作,给出该指令的执行效果。
答:首先虚拟内存是分段的,mov 1,[300]指令在cs段的偏移40处, 所以该条指令的虚拟地址是180KB+40。根据此去查页目录表 继续查页表 即可得实际物理地址。
2.针对三种分区适配算法:最佳适配、最差适配和最先适配,分别给出一个适合该种适配算法的工作场景,简要论述原因。
答:
最佳适配:选择能够满足要求且最不浪费(即尺寸最小)的空闲区域。最佳适配会让剩余空闲分区越来愈小,将来分配给其他请求的可能性也就越来越小。最佳适配的场景应该是请求分区尺寸较大 或者较小的场景,因为它浪费(表面)最小,分区较大。
最差适配:每次选择满足请求大小且尺寸最大的空闲区域 这会导致出现很多中等大小的空闲区域,适用于中等内存大小的请求。
最先适配:在空闲内存区域表中找到第一个满足要求的分区即可。运算速度快。
3.为了避免内存空间的浪费,页面尺寸应该设置的较小,为什么?但是为什么不是设置的越小越好,比如将页尺寸设置为1个字节。
答:因为分页之后,内存最大的浪费空间也就是1页(一般是4kb) 这是可以接受的。但也不能太小,太小的话页表过大,存储页表的存储空间和查表的速度都要受到影响。
4.根据请添加图片描述

地址转移过程:根据段表和偏移地址找到虚拟地址,根据偏移地址计算页一级目录号、页二级目录号和页号,CR3寄存器指向页一级目录表的位置,即基址 该基址+页一级目录号4(4字节)即为页一级目录号的地址 查看可得对应的页二级目录号的地址 同样+页二级目录号4可得对应页表的地址 同样+页号*4 可得对应物理页的地址 +偏移 就是最终物理地址。
5.4ns 20ns 0.9
6.逻辑地址是段内偏移 根据段号 查段表可得虚拟地址 虚拟地址经过多级目录的转换 可得物理地址
7.逻辑地址300 对应虚拟地址[DS:300] 根据图6.18 是2300 /100 页号是23 页内偏移是 0 23对应页框号根据页表可查是10 所以最终的物理地址是1000.
8.因为linux0.11 虚拟地址不重叠 所以可共用一个页表。因为页表的实质就是完成虚拟内存到物理内存的映射。linux2.6不可 因为每一个进程对应于0-4G的内存空间。
9.因为子进程也映射到该物理内存空间 父子进程在fork之后就相互独立了,父进程释放资源,子进程并不需要释放,所以相等于一个保护作用吧,至于为什么不会释放,是因为mem_map[this_page]==0时才会认为时空闲内存,进程释放资源只是-1,不是赋0.
10.写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种通用优化策略。其核心思想是,如果有多个调用者(Callers)同时访问相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
通俗易懂的
讲,写入时复制技术就是不同进程在访问同一资源的时候,只有更新操作,才会去复制一份新的数据并更新替换,否则都是访问同一个资源。
11.

#include <stdio.h>
#include <sys/wait.h>
int main()
{
    int *p;
    int pid;
    p = (int *)malloc(sizeof(int));


    *p = 7;
    printf("parent %d : %d\n",pid,*p);
    fflush(stdout);
    if(!(pid =fork()))
    {
        printf("child %d : %d\n",pid,*p);
        fflush(stdout);
        *p = 8;
        printf("child %d : %d\n",pid,*p);
        fflush(stdout);
    }
    else
    {
        wait(NULL);
        printf("parent %d : %d\n",pid,*p);
        fflush(stdout);
    }

    
    return 0;
}

结果:
在这里插入图片描述

习题七–换入/换出-用磁盘和时间来换取一个规整的虚拟内存

该章小结:
对于32位操作系统而言,其虚拟内存是规整的(4G),当物理内存为4G时,可以完美映射。但是物理内存常常小于4G,那么就会出现有较大的虚拟内存没有映射到物理内存空间中。对于用户而言,我们coding时是对于可以随便访问整个虚拟内存的,那么就会出现我们访问的虚拟内存不在物理内存中(在磁盘中)就需要换入换出操作。
换入:磁盘上的东西放入内存中,并与虚拟内存关联(填写页表)
换出:物理内存的东西放入磁盘中,并与虚拟内存解除关联。

请求调页伪代码:(换入的一种策略 还有请求调段策略)

 void do_no_page(unsigned long error_code, unsigned long address) 
  { 
	  address &= 0xfffff000; //得虚拟页号 	
	  page = get_free_page(); //获得空闲页框 	
	  bread_page(page,current->executable->i_dev,nr);//启动磁盘读写来读取虚拟页号对应页面得内容		
	  put_page(page,adddress);//将虚拟页面与物理页框关联  
  } 

换入的前提是物理内存中有空闲单元,但事实并非如此,经常需要做的是,先执行页面换出,从现有已映射的物理内存页框中换出一页,在腾出空闲页框进行bread_page和put_page。
页面换出策略:
1.**FIFO:**选择最先进入的页面进行淘汰(效率低(由于程序局部性的存在)
2.**OPT:**最优置换算法:选择未来最远使用的页面进行淘汰(不现实)
3.**LRU算法:**最近最少使用页面置换算法,选择未来最远被访问的页面淘汰,实际等同于选择历史上最近最少使用的页面进行淘汰。
准确实现LRU需要:
1)基于访问时间戳的LRU页面置换算法
基于从开机就对于每个虚拟页也关联时间戳,时间戳记录每次请求时的时间(递增+1)发生缺页时找到最小的时间戳即可。这样被换出的虚拟页是历史上最近最少被使用的(访问时间距离当前最久)但是需要额外空间消耗
2)基于页面栈的LRU页面置换算法
用页面栈实现页增加了额外的内存空间访问请求(页面栈的维护)

4.clock算法(对LRU算法的近似)
用一位二进制数R位表示访问位(0/1)近似模拟时间戳 1)SCR算法 将分配给进程的所有页框组织成环形线性表,产生缺页时,从当前的线性表指针(一直停在上一次缺页处理完以后的位置)处开始进行环形扫描,若扫描到的虚拟页面R位为1,则将其修改为0,指针向后移动,如果发现为0,就将该页淘汰换出。因此也称第二次机会置换算法。
可知SCR换出的是最近(上次缺页以来的这段时间)未被访问过的页面,而LRU换出的是最近最少被访问的界面。SCR的问题在于其对于“最近”的定义模拟是“上次缺页以来的这段时间"
这个模拟并不准确,因为这段时间很长,长到所有页面在这段时间内都会被访问,这样SCR就退化为FIFO算法。通过扫描指针的出现,定期扫描所有页面置为0,换出指针只负责换出(找到R为0的页面)
这样把”最近“这段时间近似模拟为”自从上一次定期扫描以来"
页框个数与全局置换
这一节关注的问题是操作系统应该分配给进程多少个物理页框。由于局部性的存在,os保证分配给该进程的物理页框数量大于其局部即可。存在工作集模型(WSS)和全局空闲物理页框链表两种处理方法。

1.请求调段实现换入
当内存访问发生时,由于段页式内存管理机制,根据段号在段表中查询虚拟地址基址,当此段在页表中无映射时,需要将硬盘中对应的段调入,缺段中断发生,并填写映射表。请求调页每次消耗时间少,并且页是空间利用的最小单位(4kb) 可避免浪费
2.缺页后需要通过磁盘读写执行换入/换出工作,CPU利用率低,等待。
3.反证法。当淘汰马上要用到的页时,徒增消耗。
4.初始:A->D->B A->D->B->D A->B->D A->B->D->A(环状) D->A->B 所以共4次。
5.略
6.略
7.系统颠簸,不行。将工作集模型时间间隔调大(增大分配给每进程的物理页框)
8.系统颠簸,每个进程都要消耗内存资源,造成磁盘IO读取频繁。关机…
9.中断引起的挂起
10.略

习题八–设备驱动-从文件试图到out指令

在这里插入图片描述
对于显示器来讲:
printf(库函数)->write->sys_wrie(系统调用)->rw_char(字符设备处理函数)->rw_ttyx(根据设备号判断是否是终端设备 终端设备处理函数)->tty_write(将用户内存中的内容写入write_q)->write_q->con_write(汇编代码 执行mov ax [pos]指令)向光标处显示字符
对于键盘来讲:
keyboard_interrupt(0x21中断)-> inb 0x60,al(从键盘0x60端口获得按键扫描码)->do_self->read_q(缓冲队列)->secondary->wake_up(唤醒进程)->tty_read(将缓冲区内容写入用户内存)->rw_ttyx(终端对应的读写函数)->rw_char(字符设备处理函数)->sys_read(系统调用)->read(函数)->scanf(库函数)
进程通过文件读接口read发起一个设备读操作,接下来该进程在文件视图路线中阻塞,等待设备准备好缓冲区。设备开始工作,工作完成以后会中断cpu(0x21中断),操作系统在设备中断处理时,将设备上的内容放入内存缓冲区(中,并唤醒阻塞等待的进程,醒来的进程从缓冲区取出内容进行处理。
文件操作是用户发起的(scanf) 系统调用为read发起设备读操作,设备中断是设备动作发起的,由操作系统的中断处理函数负责处理,两条线通过该机制连接。

1.文件视图的好处:
1)提供统一接口,方便编程
2)忽视底层硬件差异 屏蔽细节
3)可根据外设各自特点实现缓冲机制、排队调度等、提高外设的工作效率。
2.提高工作效率
因为高速设备和低速设备间不对等,在设备执行动作时,cpu可执行其他工作。
减少CPU对磁盘的读写次数、
提高CPU的执行效率
合并读写
3.放入write_q前就执行转换工作。

习题九–文件系统-一个从磁盘到文件再到文件系统的漫长抽象:

第一层抽象:从扇区到磁盘块请求(由block号推到stctor(扇区号)进而计算C\H\S)
第二层抽象:多个进程产生的磁盘请求队列(磁盘请求的电梯队列)
第三层抽象:从磁盘请求到高速缓存(内核内存缓存、有内容的缓存块由散列表(线性探测)组织、空闲缓存块由空闲链接组织) 磁盘读写请求时,先到高速缓存查,有内容直接返回或写入 没有内容则申请空闲缓存,将磁盘内容写到空闲缓存,用户直接操作高速缓存。
第四层抽象:文件,连续字符流,建立字符流和盘块号之间的映射(多级索引存储结构(原因是字符流连续,而盘块号不连续),由pos计算 inode索引)
第五层抽象:文件系统(目录树的解析)

1.方案1需要的寻道时间和旋转时间为0,是因为在读磁盘时,旋转才能产生电信号,因此读完就到下一个扇区了,不需要移动等。
因此,方案2需要移动一次磁头,旋转一次磁盘(0号读完 磁头在0号扇区的末尾,需要移到前面)。方案3不需移动磁头,只需旋转一次磁盘即可。
2.位图 。程序略 参考问7 选出符合文件大小的连续空闲扇区即可。
3.SSTF的公平性问题,会在中间柱面来回摆动
4.208
5.本质是修改file->f_pos

int sys_lseek(unsigned int fd,off_t offset, int origin)
{
	struct file * file;
	int tmp;

	if (fd >= NR_OPEN || !(file=current->filp[fd]) || !(file->f_inode)
	   || !IS_SEEKABLE(MAJOR(file->f_inode->i_dev)))
		return -EBADF;
	if (file->f_inode->i_pipe)
		return -ESPIPE;
	switch (origin) {
		case 0:
			if (offset<0) return -EINVAL;
			file->f_pos=offset;
			break;
		case 1:
			if (file->f_pos+offset<0) return -EINVAL;
			file->f_pos += offset;
			break;
		case 2:
			if ((tmp=file->f_inode->i_size+offset) < 0)
				return -EINVAL;
			file->f_pos = tmp;
			break;
		default:
			return -EINVAL;
	}
	return file->f_pos;
}

6.count
7.new_block(inode->i_dev)

int new_block(int dev)
{
	struct buffer_head * bh;
	struct super_block * sb;
	int i,j;

	if (!(sb = get_super(dev)))
		panic("trying to get new block from nonexistant device");
	j = 8192;
	for (i=0 ; i<8 ; i++)
		if ((bh=sb->s_zmap[i]))//s_zmap 逻辑块位图在内存中的数组 最多是8个
			// find_first_zero返回第一个bit是0的偏移
			if ((j=find_first_zero(bh->b_data))<8192)//bh->b_data该逻辑块位图对应的字符数组
				break;
	if (i>=8 || !bh || j>=8192)
		return 0;
	if (set_bit(j,bh->b_data))
		panic("new_block: bit already set");
	bh->b_dirt = 1;
	// j的逻辑块号(刚刚只是偏移)
	j += i*8192 + sb->s_firstdatazone-1;
	if (j >= sb->s_nzones)
		return 0;
	//高速缓存区申请
	if (!(bh=getblk(dev,j)))
		panic("new_block: cannot get block");
	if (bh->b_count != 1)
		panic("new block: count is != 1");
	clear_block(bh->b_data);
	bh->b_uptodate = 1;
	bh->b_dirt = 1;
	brelse(bh);
	return j;
}

8.两个位图置0 ,其它不动。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值