哈工大李治军老师《操作系统》课程学习笔记

1 什么是操作系统

  • 是计算机硬件和应用之间的一层软件
  • CPU管理、内存管理、终端管理、磁盘管理、文件管理
  • 操作系统除了内核,还有一些其他组件,比如文本编辑器,编译器,用来与用户交互的程序、接口库、驱动程序等

2 操作系统boot

  • 启动盘由boot引导扇区、setup的4个扇区和system模块(OS代码)组成
  • boot位于0磁道0扇区(256字节)
  • boot代码bootsect.s使用汇编写的,目的是完整控制不出错,包括内存地址

(1)BIOS启动

  • CPU运行在实模式下

  • CS:IP指向0xFFFF0的BIOS ROM区

  • 检查RAM,外设

  • 将boot扇区读入0x7c00处并跳转

(2)boot引导

  • 将boot代码拷贝到0x9000处并跳转,挪动目的是腾出空间
  • 将setup4个扇区内容拷贝到0x9100
  • 显示"Loading system"和logo
  • 读入system模块
  • 转入setup执行

3 操作系统启动

3.1 setup

  • setup模块,即setup.s也是用汇编写的,完成OS启动前的设置

  • 读硬件参数存储到内存数据结构中,比如扩展内存后将大小放到0x9002

  • 将system移动到0地址

  • 初始化gdt表

  • 启动保护模式,跳转到0地址处

3.2 关于保护模式

  • 通过mov cr0, 1进入保护模式

  • CPU、寄存器、地址线从16位切换到32位模式,段寄存器仍为16位

  • 寻址方式改变,走另外一条电路,不再是CS*16+IP

  • CS里放的是段选择子,用于查gdt表项,取基址,再加偏移,查表是硬件实现的

  • 每个表项称为段描述符,包含4个部分,共8字节

  • intel8086汇编格式变为32位汇编代码AT&T

  • 使用idt来查中断向量

  • idt和gdt可以放在内存中任意位置,相关寄存器指向起始地址

3.3 system模块

  • 操作系统源码通过Makefile编译为image

  • system中的第一个模块是head.s,完成初始化idt、gdt的工作,通过ret main从进入main函数

  • main.c完成初始化工作,如trap_init、mem_init,推出后进入死循环

  • mem_init初始化了mem_map页表

4 操作系统接口

  • 用户使用计算机:命令行、图形按钮、应用程序

  • 命令行:shell程序在死循环中执行命令,命令也是一段程序

  • 图形按钮:硬件输入存到系统消息队列中,通过消息循环GetMessage获取消息并处理

  • 用户使用计算机的本质是通过程序,即重要函数和普通c代码

  • 本质都是c程序,调用了重要函数进入操作系统

  • 操作系统提供的这类重要函数,就是操作系统接口,也叫系统调用

5 系统调用的实现

  • 系统调用的作用:提供统一接口供上层使用;作为用户态通往内核态的大门

5.1 为什么要隔离用户态和内核态

  • 内核中存储重要数据,不让用户态访问
  • 用户态不能通过jmp到内核中,也不能通过mov指令访问内核数据

5.2 如何隔离用户态和内核态

  • CPU提供了特权环的机制来实现特权级检查(硬件实现检查)
  • CPL(当前特权级)和DPL(描述符特权级):只有CPL<=DPL时,指令才能被执行
  • 特权检查发生在访问/跳转到一个目标内存区域(段)之时
  • CPL位于CS段寄存器的最后两位,即当前指令所在段的特权级
  • DPL位是GDT表中的段描述符,即目标内存段的特权级
  • 普通的用户态指令不能访问/跳转内核态

5.3 系统调用如何进入内核态

  • 中断是进入内核的唯一方法

(1)系统调用的核心

  • 系统调用是一段包含中断的代码
  • 操作系统写中断处理,获取想掉程序的编号
  • 操作系统根据编号执行相应代码

(2)系统调用的实现

  • 应用程序调用printf->库函数printf->库函数write->系统调用write
  • 库函数write中调用了一个_syscall3的宏,传入write常量
  • syscall3宏中包含c语言内嵌汇编,包含 int0x80,通过寄存器传入参数和返回值存到eax,__NR_write系统调用号

(3)IDT的初始化

  • sched_init
  • set_system_gate设置中断处理,向IDT表中写入system_call偏移地址,段选择符和DPL=3
  • 由于DPL=3,所以当前指令能够执行,成功调用内核态函数system_call,并且CS段选择符被置为内核区域

(4)int0x80中断的处理

  • 在跳转中断处理程序时,DPL被强行置为3,成功跳转到内核态
  • 跳转后CPL被置为0,可以访问任何内存区域
  • 执行中断处理程序system_call

(5)system_call

  • call _sys_call_table(, %eax, 4),全局函数指针数组
  • 通过eax传入系统调用号,找到sys_write
  • 调用sys_write内核态函数

6 操作系统历史

  • 多进程图谱,CPU、内存,Unix的出现
  • 文件操作图谱,文件、磁盘、IO,DOS/Windows的出现

7 CPU管理

  • CPU的核心:取指执行
  • 单程序运行的问题:遇到IO指令后卡住,CPU利用率太低
  • 解决办法是在卡住后切到另一个程序运行,之后再切回来
  • 需要一个结构来保存程序运行中的信息(PC等寄存器)
  • 引出了进程的概念:运行中的程序,使用PCB来存放运行信息
  • 多进程并发执行是CPU管理的核心

8 多进程图像

  • main函数最后一行,创建初始化进程,启动shell/桌面,在其中启动其他进程

8.1 如何组织多进程

  • 操作系统用PCB组织多进程,将相应的PCB放到队列中

  • 正在执行的进程:运行态

  • 等待执行的进程:就绪态

  • 等待事件的进程:阻塞态

8.2 多进程如何交替

  • 启动磁盘读写后,将pCur设为阻塞态,放入阻塞队列
  • schedule函数中,通过getNext调度函数,从队列中找到下一个PCB
  • 通过操作pCur和pNew,切换到下一个进程

8.3 多进程的内存隔离

  • 进程之间相互访问内存会带来问题

  • 通过映射表实现进程内存隔离

  • 多进程管理连带形成内存管理

8.4 多进程合作

  • 如word和ppt放入打印机队列

  • 生产者进程往共享缓冲区中放内容,消费者进程取内容,会带来counter计算错误

  • 通过给counter上锁,合理的推进顺序,实现进程同步

9 用户线程

9.1 线程的引入

  • 进程=内存资源+指令序列,进程的切换分为两部分:指令切换和内存切换

  • 线程共享内存资源,切指令不切映射表,保留并发优点,降低进程的切换代价

  • 如浏览器线程:接收数据、显示文本、显示图片

9.2 用户线程的切换

  • 核心是线程创建Create和线程切换Yield

(1)Yield

  • 两个线程需要用两个栈,保证当前函数调用使用当前栈,不乱套

  • 栈切换需要用TCB保存栈顶指针

  • 不需要主动切换pc,因为pc已经在栈里了(不用jmp,直接返回)

(2)Create

  • 创建TCB,通过malloc分配内存
  • 创建线程栈,通过malloc分配内存
  • 将函数地址压栈
  • TCB保存栈顶指针

(3)组合

(4)用户线程和内核线程

  • 操作系统完全感知不到用户级线程,线程阻塞时CPU可能会切换到另一进程,而不是另一用户线程

  • 内核线程:ThreadCreate是系统调用,内核维护TCB,通过Schedule切换,系统决定调度,

  • 内核级线程由操作系统托管,并发性更好

10 内核级线程

  • 进程都是内核级的,需要分配内存资源,访问外设

10.1 多核

  • 多处理器是每个CPU都有一套Cache和MMU(映射),多核是多个CPU共用一套Cache和MMU

  • 只有内核级线程才能发挥多核特点(分配给另一个核心执行)

10.2 用户级和核心级线程的不同

  • ThreadCreate是系统调用,内核管理TCB,内核负责切换线程
  • 每个内核线程都有用户栈和内核栈
  • 切换时由两个栈变为两套栈,用户栈和内核栈都切
  • 关联:

10.3 内核线程切换的五段论

  • 线程S int中断引发中断处理,同时通过硬件进入内核栈,在内核执行代码
  • 中断处理函数中进行磁盘读,引发切换,schedule
  • 通过调度找到下一个线程T的TCB(TCB指向内核栈SP)
  • 通过TCB进行线程T的内核栈切换
  • 通过IRET返回线程T的用户栈

10.4 内核线程的创建

  • 创建TCB
  • 创建用户栈和内核栈并关联
  • TCB指向内核栈顶
  • TCB进入队列

11 内核级线程实现

  • 以fork为例,通过中断进入内核,复制了一份子进程后退出内核

(1)切换

  • schedule

  • linux0.1使用TSS(PCB的一部分)而不是内核栈进行切换

  • 通过ljmp一条指令跳转切换,过慢(不能使用指令流水)

  • ljmp:把当前CPU的寄存器现场放到TSS中,根据TSS(n)找到新TSS,覆盖到CPU的寄存器上

  • EIP也通过TSS覆盖,没有用到栈传递

(2)创建

  • _sys_fork中调用了_copy_process,将当前所有寄存器作为参数传入
  • 首先申请一页内存p作为PCB和TSS
  • 在PCB上方创建内核栈并用tss->esp0指向栈顶
  • 和父进程共用一个用户栈,并用tss->esp指向栈顶
  • tss的eip和cs通过传入参数赋值,eax置为0(作为子进程的返回值)

(3)执行目标代码

  • 子进程调用exec(cmd)函数
  • 调用sys_execve系统调用,进入内核态
  • 调用do_execve,将内核栈的ret_eip置为cmd程序的入口地址(从可执行文件的head处读取),SS:SP处被置为新的用户栈顶
  • iret返回后,执行目标程序代码

12 操作系统的那棵“树”

  • 操作系统的内核:CPU管理和内存管理

(1)一个实例,屏幕上交替打A和B

  • 父进程进入内核,将两个子进程的PCB填写好,并修改tss-eip为printf("A")和printf("B")

  • 父进程wait后阻塞,进入内核,调用schedule,调度策略选择next,switch_to(next),目标tss覆盖当前CPU寄存器,切换到子进程

  • 子进程间利用时钟中断,进入内核,分配时间片counter,为0调用schedule切换(调度点)

13 CPU调度策略

  • 前台任务关注响应时间(从操作到响应),后台任务关注周转时间(从任务进入到结束)

  • 响应时间小,切换次数多,系统内耗大,吞吐量小

  • IO约束性任务(前台任务),优先级应该高,CPU约束性任务(后台任务)

(1)SJF算法

  • 短作业优先,平均周转时间最小

(2)RR算法

  • 按时间片来轮转调度,减小响应时间

(3)优先级算法

  • 前台任务队列用轮转调度

  • 后台用短作业

  • 之间用优先级调度,照顾前台进程

  • 优先级调度可能造成进程饥饿,还需综合考虑

15 一个实际的schedule函数

  • counter既可以作为时间片,进行轮转调度,并且时间片是收敛的(除以2+p),保证响应时间的界
  • counter还可以作为优先级,找到优先级最高的任务,并且优先级可以动态调整(在时间片都为0后,除以2加初值,就绪队列counter不变,阻塞队列即经过IO的进程counter会变大),照顾了前台进程
  • 后台进程一直按照counter轮转,近似SJF调度

16 进程同步与信号量

  • 进程间合理有序的推进:需要停下来等待,根据信号判断继续执行(让进程走走停停)

  • 信号量用来判断可供使用的资源数量

  • 信号量的定义:

  • V{s.value++; if(s.value <= 0){wakeup}}
  • 操作PCB,P和V做成了系统调用
  • 用信号量解决生产者—消费者问题

  • 思路:空闲单元为0时生产者停下来(p(empty)),消费者对应增加(V(empty));生产内容为0时消费者停下 来(p(full)),生产者对应增加(V(full))
  • empty表示空闲单元数量,full表示生产内容的数量
  • mutex表示只能让一个进程修改缓冲区
  • 再次思考:一个信号量的含义是表示能让对应的进程能够使用多少次

17 对信号量的临界区保护

  • 错误由多个进程并发操作共享数据(信号量)引起

  • 解决办法:给共享变量上锁,临界区是成对出现的,一方进入,另一方不能进入对应临界区

  • 关键是写进入区和退出区的代码

  • 基本原则:互斥进入;有空让近;有限等待

  • Peterson算法:标记+轮转

  • 简单的:使用cli和sti来上锁,防止另一进程被调度(仅限单核CPU)

  • 另一简单的:硬件原子指令法,不能用信号量来上锁(信号量本身可能被打断),但可以用一条指令来上锁

18 信号量的代码实现

(1)用户态程序

1.sd=sem_open("empty")
typedef struct {
	char name[20];
	int value;
	task_struct * queue;
} semtable[20];
sys_sem_open(char * name)
{
	在semtable中寻找name对应的结构体,没找到则创建,返回下标;
}
2.sem_wait(sd)
sys_sem_wait(int sd){
	cli();
	if(semtable[sd].value-- < 0){
		设置自己为阻塞;
		加入semtable[sd].queue;
		schedule();
	}
	sti();
}
  • 信号量在内核中,涉及到pcb切换
  • task_struct(tss)就是linux的pcb,tcb是线程用的

(2)内核态程序

  • bread启动磁盘读后睡眠,等待磁盘读完后通过磁盘中断将其唤醒,也是利用了信号量同步

  • while和if的区别:if是将阻塞队列中第一个唤醒,while是全部唤醒

19 死锁处理

(1)死锁的成因

  • 资源互斥使用,一旦占有别人无法使用
  • 进程占有了一些资源不释放,再去申请其他资源
  • 各自占有的资源和申请的资源形成了环路等待

(2)死锁的处理方法

  • 死锁预防:破坏死锁出现的条件
  • 死锁避免:检测每个资源请求,如果造成死锁就拒绝
  • 死锁检测+恢复:检测到死锁出现时,让一些进程回滚,让出资源
  • 死锁忽略:就好像没有死锁一样,如重启

20 内存使用与分段

(1)重定位

  • 修改程序中的地址

  • 编译时重定位:只能放到固定位置

  • 载入时重定位,进程交换内存后(阻塞后换入换出),载入地址被修改,不能满足换入换出的需求

  • 运行时重定位:每执行一条指令都要从逻辑地址计算出物理地址(地址翻译),基址放到PCB中,进程运行时赋值给基地址寄存器

(2)分段

  • 每个段特点不同,不是将整个程序,而是分段放入内存

  • 段表:用来保存每个段的地址,包含段号,段基址,长度和段属性

  • 操作系统的段表叫GDT,每个进程的段表叫LDT

  • 载入时将LDT赋值给PCB,进程运行时将PCB赋值给LDT,计算各段指令的物理地址

21 内存分区与分页

  • 内存管理:编译时程序分段,在内存中找到空闲分区,磁盘读写载入内存并建立LDT

(1)内存分区

  • 将程序的各个段载入到相应的内存分区

  • 可变分区算法:最先适配、最差适配、最佳适配

  • 分区用于虚拟内存,分页用于物理内存

(2)内存分页

  • 可变分区内存碎片造成的问题

  • 将每个段分成4K的页

  • 按照给出的逻辑地址计算出第几页和页内偏移,通过页表找到对应页框,计算出物理地址

  • 页表:保存页框和页号的对应关系 ,CR3寄存器指向页表

22 多级页表和快表

(1)问题

  • 为了提高内存空间利用率,页应该小,页表项就多,页表就大
  • 程序只用到很小部分逻辑页,大部分页表项用不到
  • 如果只存放用到的页表项,页表就需要比较查找,费时,所以页表项必须连续存放

(2)多级页表

  • 通过页目录表(章)和页表(节)形成多级页表查找

  • 通过一个顶级页表为真正用到的页表提供索引,用不到的二级页表不放入内存

  • 既保证连续,又能减小内存空间占用

  • 每个页4k,每个二级页表有1k个表项

(2)快表

  • 多级页表增加了访存的次数,用时多

  • TLB是一组快速存储寄存器,保存经常用到的页表项,通过硬件实现查找

  • 快表未命中时,通过多级页表补充查找

  • TLB页表项可以很少的原因:程序访问内存的局部性(程序多为循环和顺序结构)

23 段页结合的实际内存管理

  • 操作系统实现了虚拟内存,从用户角度看,实现了段,从物理内存上看,实现了页

  • 使用内存 需要经过两层地址翻译

  • 一个实际的段、页式内存管理

23.1 载入(以fork为例)

(1)分配虚存,建段表

  • set_base(p->ldt[1], new_data_base)

(2)分配内存,建页表

		* 虚拟地址作为页号来查找

  • 父子进程用同一个物理页,子进程按照虚拟地址换算来建立新的顶级页表,并分配内存建立新的二级页表,并拷贝父进程的二级页表

23.2 使用(以*p=7为例)

  • 查段表和页表,通过MMU硬件完成地址转换
  • 父子进程公用一块物理页并设置为只读,写入7时重新分配一块物理页(写时复制)
  • 父子进程通过段表和页表进行内存分离

24 内存换入—请求调页

  • 虚拟内存是实现分段分页的关键,内存换入换出是实现虚拟内存的关键

  • 虚拟地址就是线性地址

  • 用换入换出实现大内存,请求的时候才换入并建立映射

  • 换入的核心—请求调页(通过缺页中断从磁盘读入到内存空间页)

  • 处理缺页中断
do_no_page:
1.page=get_free_page();     //获取物理空闲页
2.bread_page(page, current->executable->i_dev, )      //从磁盘设备读入到物理页
3.put_page(page, address);      //虚拟地址和物理页关联
put_page{
	page_table[(address>>12)&0x3ff] = page|7;       //页表
}

25 内存换出

  • 在get_free_page中实现,内存有限,不能总是获得新的页,需要按照算法选择一页淘汰,换出到磁盘
  • 算法目标:降低缺页次数
  • FIFO页面置换:缺页次数高
  • MIN算法:选最远将使用的页淘汰,是最优反感,但理论不可行,不知道后来的页框

25.1 LRU算法

  • 选最近最长一段时间没有使用的页淘汰

(1)实现1

  • 每页维护一个时间戳,选数值最小的页换出

  • 每次地址访问都要打时间戳,时间代价太高

(2)实现2

  • 维护一个页码栈,每次地址访问都要修改栈,同样时间代价高

25.2 LRU的近似实现

  • 将时间计数变为是和否,记录最近一段时间有没有使用
  • 只用修改一位,可以放到页表中,查表时顺便写入
  • clock算法,缺页的时候转指针,为0则换出,访问页时置1
  • 缺页很少时,退化为FIFO,即很长一段时间内页都使用过
  • 解决办法:定时清除R位,再来一个快速扫描指针

26 IO与显示器

26.1 外设工作的三个部分

  • CPU向外设发读写指令,外设控制器对寄存器进行读写(in/out)
  • 操作系统提供文件视图,使得对寄存器的读写方便
  • 中断处理

26.2 操作外设的程序

int fd = open(“/dev/xxx”);
for (int i = 0; i < 10; i++) {
write(fd,i,sizeof(int));
}
close(fd);
  • 不论什么设备都是用open、read、write、close,操作系统为用户提供统一的接口
  • 不同的设备对应不同的设备文件,根据设备文件找到控制器的地址、内容格式等等

26.3 显示器的输出

(1)找到设备

printf(“Host Name: %s”, name);
->write(1, buf, ...);
->sys_write(fd, buf, ...);     //通过fd找到文件的索引
sys_write{
	struct file *file;
	file = current->filp[fd];     //current进程
	inode = file->f_inode;        //显示器信息保存在inode中
}
  • fd=1的filp从父进程shell fork拷贝而来
void init(void)
{open(“dev/tty0”,O_RDWR,0);dup(0);dup(0);     //dup用来复制一个文件描述符,并指向同一个设备
execve("/bin/sh",argv,envp)}
  • open系统调用
int sys_open(const char* filename, int flag)
{ i=open_namei(filename,flag,&inode);     //inode保存设备信息
cuurent->filp[fd]=f;       // 第一个空闲的fd
f->f_mode=inode->i_mode; f->f_inode=inode;
f->f_count=1; return fd; }

(2)准备好后,向屏幕输出

  • inode->i_zone[0]获取设备号,crw_table是字符设备接口,从中找到tty
  • tty_write是输出的核心函数,往write_q缓冲区队列中写
  • tty->write,在tty_struct中,从缓冲区队列中往屏幕上写,内部用到mov ax, pos,往显存中写字符
  • 统一编址用pos,独立编制用out
  • 写设备驱动,就是包装out,编写驱动函数并注册到tty_struct中

27 键盘

  • 从键盘中断开始,从0x60端口读扫描码,调用key_table
  • key_table:函数表,包含do_self等
  • do_self专门用于处理可显示字符,从key_map中获取arscii码
  • 将arscii码放入read_q缓冲队列中,供程序使用

28 生磁盘的使用

28.1 磁盘的基本知识

  • 扇区(基本单位)、磁道和柱面

  • IO过程

    • 控制器
    • 寻道:磁头移动到相应磁道
    • 旋转:旋转磁盘到相应扇区
    • 传输:磁生电(读缓冲区),电生磁(写缓冲区)
  • 要往控制器中写柱面、磁头、扇区和缓存位置(通过out)

28.2 从盘块号读写磁盘(一层抽象)

  • 磁盘驱动负责从block计算CHS

  • block编址方式要满足相邻的盘块可以快速读出,即将block相邻的区域放到相邻扇区中

28.3 多个进程通过队列使用磁盘(第二层抽象)

  • 磁盘调度的目的是减少寻道时间
  • FCFS
  • SSTF(最短寻道):存在饥饿问题
  • SCAN(SSTF+中途不回折):每个请求都有处理机会

  • C-SCAN(电梯算法,真实用的):SCAN+直接移动到另一端,保证两端请求都能很快处理

28.4 生磁盘(raw disk)的使用整理

  • 读:进程发出请求后睡眠,磁盘将数据准备到缓冲内存,通过中断唤醒进程
  • 写:通过out往CHL写

29 从生磁盘到文件

  • 第三层抽象,从盘块号到文件
  • 用户眼中的文件是字符序列,磁盘上的文件是盘块集合
  • 文件的关键是建立字符流到盘块集合的映射关系

  • 连续存放字符
  • 映射关系:根据文件名找到FCB,FCB中存放起始块和块数
  • 不适合动态增长

(2)链式结构

  • 读取慢,增长快

(3)索引结构

  • index,linux下FCB为inode
  • 找到文件对应的inode,并找到索引块(专门用来存放索引的盘块),从中找到目标字符序列对应的盘块号
  • 多级索引(可以表示很大的文件,很小的文件也能高效访问)

30 文件使用磁盘的实现

  • 一个盘块占两个扇区
  • 本节是磁盘到文件的核心代码实现

(1)sys_write

  • 处理字符流中的一段,写到磁盘上
int sys_write(int fd, const char* buf, int count) {
	struct file *file = current->filp[fd];
    struct m_inode *inode = file->inode;
    if(S_ISREG(inode->i_mode))
    	return file_write(inode, file, buf, count);
}
  • file中放字符流的某一段(如200-212),从file中取出200,从count获得个数12,根据inode得到盘块号,对buf进行读写
  • file中有一个读写指针file->f_pos,顺序后移,fseek就是修改它的

(2)file_write

(3)m_inode

  • inode是文件抽象,形成统一文件视图
  • 数据文件的inode用来映射盘块
  • 设备文件的inode存放主从设备号信息
  • i_mode是文件类型
  • i_zone是映射关系或者主从设备号
  • 根据文件名找到inode

(4)文件视图

  • 路1:根据inode找到盘块号
  • 路2:根据inode找到一堆函数,包装了out
  • 在用户看来都是字符流

(5)实践项目—实现proc文件

  • S_ISPROC
  • proc_read,按照file->f_pos和count将task_struct内容拷贝到buf中,形成字符流

31 目录与文件系统

  • 磁盘的最后一层抽象,整个磁盘变成一颗目录树
  • 文件,抽象为一个磁盘块集合;文件系统,抽象整个磁盘

(1)目录树

  • 引入目录树,表示清晰
  • 关键是根据树状结构产生的路径名,找到对应文件的inode

(2)树状目录的完整实现

  • FCB和对应的数据盘块是在磁盘上的
  • 目录项包含FCB的编号和字符串,通过字符串匹配编号,通过编号找到FCB位置
  • 根目录FCB编号固定为0,根据根目录的izone索引项找到数据块,文件里面保存目录项

(3)文件系统自举

  • 磁盘需要按特定形式格式化

  • 盘块位图:表明盘块空闲情况
  • i节点位图:创建/删除了哪些inode
  • 引导块:引导扇区
  • 超级块:各个部分位置信息,mount就是读超级块,挂载到根目录位置

(5)全部映射下磁盘的使用

32 目录解析代码实现

(1)open

  • 关键是弄懂sys_open
  • open的作用是按照文件名,在PCB中通过一个指针数组指向对应inode,fd就是数组下标,形成一个链,write是利用链
  • 核心是解析路径

(2)get_dir

(3)根目录

inode=current->root;
//shell进程初始化时指向了根目录的inode,其他进程fork出来
void mount_root(void)//在fs/super.c中
{
	mi=iget(ROOT_DEV,ROOT_INO));   //#define ROOT_INO 1
	current->root = mi; 
}

(4)iget

  • 从inode编号找到inode位置

(5)find_entry

  • 从inode->i_zone中取出盘块号,读数据,获得目录项,匹配字符串后返回inode编号
posted @ 2022-10-03 20:43  z5onk0  阅读(1045)  评论(0编辑  收藏  举报