当前位置: 首页 > article >正文

操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之硬盘初始化

前言

硬盘软盘块设备上数据的读写操作是通过中断处理程序进行的。内核每次读写的数据量以一个逻辑块(1024 字节)为单位,而块设备控制器则是以扇区(512字节)为单位访问块设备。在处理过程中,内核使用了读写请求项等待队列来顺序地缓冲一次读写多个逻辑块的操作。

        具体的,当一个程序需要读取硬盘上的一个逻辑块时,就会向缓冲区管理程序提出申请。而请求读写的程序进程则进入睡眠等待状态。缓冲区管理程序首先在缓冲区中寻找以前是否已经读取过这块数据。如果缓冲区中已经有了,就直接将对应的缓冲区块头指针返回给程序并唤醒等待的进程。若缓冲区中还不存在所要求的数据块,则缓冲管理程序就会调用低级块读写函数 ll_rw_block(),向相应的块设备驱动程序发出一个读数据块的操作请求。该函数会为此创建一个请求结构项,并插入请求队列中。为了提高读写磁盘的效率,减小磁头移动的距离,内核代码在把请求项插入请求队列时,会使用电梯算法将请求项插入到磁头移动距离最小的请求队列位置处。(关于缓冲区概念及交互过程块设备操作请求过程将在后续另起文章介绍)
        若此时对应块设备的请求项队列为空,则表明此刻该块设备不忙。于是内核就会立刻向该块设备的控制器发出读数据命令。当块设备的控制器将数据读入到指定的缓冲块后,就会发出中断请求信号,并调用相应的读命令后处理函数,处理继续读扇区操作或者结束本次请求项的过程。例如对相应块设备进行关闭操作和设置该缓冲块数据已经更新标志,最后唤醒等待该块数据的进程。

        各种关于硬盘或者其他块设备的操作都离不开对关键数据结构的定义与初始化,本篇将简要地解读整理内核v0.11关于硬盘的初始化。

硬盘初始化 hd_init()

不外乎就是往某些 IO 端口上读写一些数据,表示开启它;然后再向中断向量表中添加一个中断,使得 CPU 能够响应这个硬件设备的动作;最后再初始化一些数据结构来管理。

void main(void) {
    ...
    hd_init(); 
    ...
}

//struct blk_dev_struct {
//    void (*request_fn)(void);
//    struct request * current_request;
//};
//extern struct blk_dev_struct blk_dev[NR_BLK_DEV];

void hd_init(void) {
    blk_dev[3].request_fn = do_hd_request;
    set_intr_gate(0x2E,&hd_interrupt);
    outb_p(inb_p(0x21)&0xfb,0x21);
    outb(inb_p(0xA1)&0xbf,0xA1); 
}

因为有很多块设备,所以 Linux 0.11 内核用了一个 blk_dev[] 来进行管理,每一个索引表示一个块设备

struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
    { NULL, NULL },     /* no_dev */
    { NULL, NULL },     /* dev mem */
    { NULL, NULL },     /* dev fd */
    { NULL, NULL },     /* dev hd */
    { NULL, NULL },     /* dev ttyx */
    { NULL, NULL },     /* dev tty */
    { NULL, NULL }      /* dev lp */
};

索引为 3 这个位置,就表示给硬盘 hd 这个块设备留的位置。并且每个块设备执行读写请求都有自己的函数实现,在上层看来都是一个统一函数 request_fn 接口,具体实现各有不同,对于硬盘来说,这个实现就是 do_hd_request 函数。

(这其实就是多态思想在 C 语言的体现。用C++熟悉的话就是,抽象基类接口指针 request_fn 指向派生类对象具体方法 do_hd_request 的感觉。)

接着又设置了一个新的中断,中断号是 0x2E,中断处理函数是 hd_interrupt,也即硬盘发生读写时,硬盘会发出中断信号给 CPU,之后 CPU 便会陷入中断处理程序,即执行 hd_interrupt 函数。

(tips:赵炯的内核注释书中是这样描述的:当硬盘操作完成或出错会发出此中断信号。)

_hd_interrupt:
    ...
    xchgl _do_hd,%edx
    ...
    
// 如果是读盘操作,这个 do_hd 是 read_intr
static void read_intr(void) {
    ...
    do_hd_request();
    ...
}

(操作系统就是一个靠中断驱动的死循环而已,如果不发生任何中断,操作系统会一直在一个死循环里等待。换句话说,让操作系统工作的唯一方式,就是触发中断。)

最后就是往几个 IO 端口上读写,其作用是允许硬盘控制器发送中断请求信号

硬盘的端口表如下:

所谓读硬盘,就是往除了第一个以外的后面几个端口写数据,告诉要读硬盘的哪个扇区,读多少。然后再从 0x1F0 端口一个字节一个字节的读数据,这就完成了一次硬盘读操作。

例如:

1. 在 0x1F2 写入要读取的扇区数

2. 在 0x1F3 ~ 0x1F6 这四个端口写入计算好的起始 LBA 地址

3. 在 0x1F7 处写入读命令的指令号

4. 不断检测 0x1F7 (此时已成为状态寄存器的含义)的忙位

5. 如果第四步骤为不忙,则开始不断从 0x1F0 处读取数据到内存指定位置,直到读完

操作系统中读写端口的核心如下:

static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
        unsigned int head,unsigned int cyl,unsigned int cmd,
        void (*intr_addr)(void)) {
    ...
    do_hd = intr_addr;
    outb_p(hd_info[drive].ctl,HD_CMD);
    port = 0x1f0;
    outb_p(hd_info[drive].wpcom>>2,++port);
    outb_p(nsect,++port);
    outb_p(sect,++port);
    outb_p(cyl,++port);
    outb_p(cyl>>8,++port);
    outb_p(0xA0|(drive<<4)|head,++port);
    outb(cmd,++port);
}

outb_p 方法,转换成汇编语言,就是 out 指令,往指定的硬盘 IO 端口上写数据,达到读或者写的目的。

由用户层写的各种 read / write 函数,即便是经过系统调用、文件系统、缓冲区管理等等过程,但只要是读写硬盘,最终都要调用到这个最底层的函数,殊途同归!

hd.c 中有很多读写硬盘的方法,这个在之后文件系统会使用到,将另外整理一篇进行展开!!!


http://www.kler.cn/a/515686.html

相关文章:

  • 基于springboot社区医院管理系统
  • 5.SQLAlchemy对两张有关联关系表查询
  • Spring MVC和Spring WebFlux的区别
  • STranslate 中文绿色版即时翻译/ OCR 工具 v1.3.1.120
  • Linux中关于glibc包编译升级导致服务器死机或者linux命令无法使用的情况
  • 高效安全文件传输新选择!群晖NAS如何实现无公网IP下的SFTP远程连接
  • 我谈概率论与数理统计的知识体系
  • Jenkins-获取build用户信息
  • Spring Bean Scope 全面解析:如何根据职责选择合适的作用范围?
  • STM32 GPIO工作模式
  • Stable diffusion 都支持哪些模型
  • 002-SpringBoot整合AI(Alibaba)
  • 总结4..
  • vim的介绍
  • Harmony Next 支持创建分身
  • HMV Challenges 022 Writeup
  • web前端5--css字体样式
  • 从浏览器层面看前端性能:了解 Chrome 组件、多进程与多线程
  • uniapp,编译运行报错“Error: listen EACCES: permission denied 0.0.0.0:5173“,解决方法
  • 微服务拆分
  • Linux终端之旅: 权限管理三剑客与特殊权限
  • 【玩转全栈】---基于YOLO8的图片、视频目标检测
  • 【算法笔记】力扣热题100(LeetCode hot-100)53. 最大子数组和 1.前缀和 2.动态规划
  • Kubernetes 网络插件断网恢复性能比较
  • Spring Boot中如何自定义属性配置
  • CSS 网络安全字体