声明:

参考《linux内核完全剖析基于linux0.11》--赵炯    节选

1、功能描述

该程序主要用于执行低层块设备读 / 写操作,是本章所有块设备与系统其它部分的接口程序。其它程
序通过调用该程序的低级块读写函数 ll_rw_block() 来读写块设备中的数据。该函数的主要功能是为块设
备创建块设备读写请求项,并插入到指定块设备请求队列中。实际的读写操作则是由设备的请求项处理
函数 request_fn() 完成。对于硬盘操作,该函数是 do_hd_request() ;对于软盘操作,该函数是 do_fd_request() ;
对于虚拟盘则是 do_rd_request() 。若 ll_rw_block() 为一个块设备建立起一个请求项,并通过测试块设备的
当前请求项指针为空而确定设备空闲时,就会设置该新建的请求项为当前请求项,并直接调用 request_fn()
对该请求项进行操作。否则就会使用电梯算法将新建的请求项插入到该设备的请求项链表中等待处理。
而当 request_fn() 结束对一个请求项的处理,就会把该请求项从链表中删除。
        由于 request_fn() 在每个请求项处理结束时,都会通过中断回调 C 函数(主要是 read_intr() 和
write_intr() )再次调用 request_fn() 自身去处理链表中其余的请求项,因此,只要设备的请求项链表(或
者称为队列)中有未处理的请求项存在,都会陆续地被处理,直到设备的请求项链表是空为止。当请求
项链表空时, request_fn() 将不再向驱动器控制器发送命令,而是立刻退出。因此,对 request_fn() 函数的
循环调用就此结束。参见下图所示。

对于虚拟盘设备,由于它的读写操作不牵涉到上述与外界硬件设备同步操作,因此没有上述的中断
处理过程。当前请求项对虚拟设备的读写操作完全在 do_rd_request() 中实现。

2.代码注释

linux/kernel/blk_drv/ll_rw_blk.c 程序
/*
* linux/kernel/blk_dev/ll_rw.c
*
* (C) 1991 Linus Torvalds
*//*
* This handles all read/write requests to block devices
*/
/*
* 该程序处理块设备的所有读/写操作。
*/
#include <errno.h> // 错误号头文件。包含系统中各种出错号。(Linus 从 minix 中引进的)
#include <linux/sched.h> // 调度程序头文件,定义了任务结构 task_struct、初始任务 0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
#include <linux/kernel.h> // 内核头文件。含有一些内核常用函数的原形定义。
#include <asm/system.h> // 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。#include "blk.h" // 块设备头文件。定义请求数据结构、块设备数据结构和宏函数等信息。/*
* The request-struct contains all necessary data
* to load a nr of sectors into memory
*/
/*
* 请求结构中含有加载 nr 扇区数据到内存中的所有必须的信息。
*/
struct request request[NR_REQUEST];/*
* used to wait on when there are no free requests
*/
/* 是用于在请求数组没有空闲项时进程的临时等待处 */
struct task_struct * wait_for_request = NULL;/* blk_dev_struct is:
* do_request-address
* next-request
*/
/* blk_dev_struct 块设备结构是:(kernel/blk_drv/blk.h,23)
* do_request-address // 对应主设备号的请求处理程序指针。
* current-request // 该设备的下一个请求。
*/
// 该数组使用主设备号作为索引。实际内容将在各种块设备驱动程序初始化时填入。例如,硬盘
// 驱动程序进行初始化时(hd.c,343 行),第一条语句即用于设置 blk_dev[3]的内容。
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {{ NULL, NULL },   /* no_dev */    // 0 - 无设备。{ NULL, NULL },  /* dev mem */   // 1 - 内存。{ NULL, NULL },   /* dev fd */    // 2 - 软驱设备。{ NULL, NULL },     /* dev hd */    // 3 - 硬盘设备。{ NULL, NULL },     /* dev ttyx */  // 4 - ttyx 设备。{ NULL, NULL },  /* dev tty */   // 5 - tty 设备。{ NULL, NULL }    /* dev lp */    // 6 - lp 打印机设备。
};// 锁定指定的缓冲区 bh。如果指定的缓冲区已经被其它任务锁定,则使自己睡眠(不可中断地等待),
// 直到被执行解锁缓冲区的任务明确地唤醒。
static inline void lock_buffer(struct buffer_head * bh)
{cli(); // 清中断许可。while (bh->b_lock) // 如果缓冲区已被锁定,则睡眠,直到缓冲区解锁。sleep_on(&bh->b_wait);bh->b_lock=1; // 立刻锁定该缓冲区。sti(); // 开中断。
}// 释放(解锁)锁定的缓冲区。
static inline void unlock_buffer(struct buffer_head * bh)
{if (!bh->b_lock) // 如果该缓冲区并没有被锁定,则打印出错信息。printk( "ll_rw_block.c: buffer not locked\n\r" );bh->b_lock = 0; // 清锁定标志。wake_up(&bh->b_wait); // 唤醒等待该缓冲区的任务。
}/*
* add-request adds a request to the linked list.
* It disables interrupts so that it can muck with the
* request-lists in peace.
*/
/*
* add-request()向链表中加入一项请求。它会关闭中断,
* 这样就能安全地处理请求链表了
*/向链表中加入请求项。参数 dev 指定块设备,req 是请求项结构信息指针。
static void add_request(struct blk_dev_struct * dev, struct request * req)
{struct request * tmp;req->next = NULL;cli(); // 关中断。if (req->bh)req->bh->b_dirt = 0; // 清缓冲区“脏”标志。// 如果 dev 的当前请求(current_request)子段为空,则表示目前该设备没有请求项,本次是第 1 个// 请求项,因此可将块设备当前请求指针直接指向该请求项,并立刻执行相应设备的请求函数。if (!(tmp = dev->current_request)) {dev->current_request = req;sti(); // 开中断。(dev->request_fn)(); // 执行设备请求函数,对于硬盘是 do_hd_request()。return;}// 如果目前该设备已经有请求项在等待,则首先利用电梯算法搜索最佳插入位置,然后将当前请求插入// 到请求链表中。电梯算法的作用是让磁盘磁头的移动距离最小,从而改善硬盘访问时间。for ( ; tmp->next ; tmp=tmp->next)if ((IN_ORDER(tmp,req) ||!IN_ORDER(tmp,tmp->next)) &&IN_ORDER(req,tmp->next))break;req->next=tmp->next;tmp->next=req;sti();
}创建请求项并插入请求队列。参数是:主设备号 major,命令 rw,存放数据的缓冲区头指针 bh。
static void make_request(int major,int rw, struct buffer_head * bh)
{struct request * req;int rw_ahead;/* WRITEA/READA is special case - it is not really needed, so if the *//* buffer is locked, we just forget about it, else it's a normal read *//* WRITEA/READA 是一种特殊情况 - 它们并并非必要,所以如果缓冲区已经上锁,*//* 我们就不管它而退出,否则的话就执行一般的读/写操作。 */// 这里'READ'和'WRITE'后面的'A'字符代表英文单词 Ahead,表示提前预读/写数据块的意思。// 对于命令是 READA/WRITEA 的情况,当指定的缓冲区正在使用,已被上锁时,就放弃预读/写请求。// 否则就作为普通的 READ/WRITE 命令进行操作。if (rw_ahead = (rw == READA || rw == WRITEA)) {if (bh->b_lock)return;if (rw == READA)rw = READ;elserw = WRITE;}// 如果命令不是 READ 或 WRITE 则表示内核程序有错,显示出错信息并死机。if (rw!=READ && rw!=WRITE)panic( "Bad block dev command, must be R/W/RA/WA" );// 锁定缓冲区,如果缓冲区已经上锁,则当前任务(进程)就会睡眠,直到被明确地唤醒。lock_buffer(bh);// 如果命令是写并且缓冲区数据不脏(没有被修改过),或者命令是读并且缓冲区数据是更新过的,// 则不用添加这个请求。将缓冲区解锁并退出。if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {unlock_buffer(bh);return;}repeat:/* we don't allow the write-requests to fill up the queue completely:* we want some room for reads: they take precedence. The last third* of the requests are only for reads.*//* 我们不能让队列中全都是写请求项:我们需要为读请求保留一些空间:读操作* 是优先的。请求队列的后三分之一空间是为读准备的。*/// 请求项是从请求数组末尾开始搜索空项填入的。根据上述要求,对于读命令请求,可以直接// 从队列末尾开始操作,而写请求则只能从队列 2/3 处向队列头处搜索空项填入。if (rw == READ)req = request+NR_REQUEST; // 对于读请求,将队列指针指向队列尾部。elsereq = request+((NR_REQUEST*2)/3); // 对于写请求,队列指针指向队列 2/3 处。/* find an empty request *//* 搜索一个空请求项 */// 从后向前搜索,当请求结构 request 的 dev 字段值=-1 时,表示该项未被占用。while (--req >= request)if (req->dev<0)break;/* if none found, sleep on new requests: check for rw_ahead *//* 如果没有找到空闲项,则让该次新请求睡眠:需检查是否提前读/写 */// 如果没有一项是空闲的(此时 request 数组指针已经搜索越过头部),则查看此次请求是否是// 提前读/写(READA 或 WRITEA),如果是则放弃此次请求。否则让本次请求睡眠(等待请求队列// 腾出空项),过一会再来搜索请求队列。if (req < request) { // 如果请求队列中没有空项,则if (rw_ahead) { // 如果是提前读/写请求,则解锁缓冲区,退出。unlock_buffer(bh);return;}sleep_on(&wait_for_request); // 否则让本次请求睡眠,过会再查看请求队列。goto repeat;}/* fill up the request-info, and add it to the queue *//* 向空闲请求项中填写请求信息,并将其加入队列中 */// 程序执行到这里表示已找到一个空闲请求项。请求结构参见(kernel/blk_drv/blk.h,23)。req->dev = bh->b_dev;            // 设备号。req->cmd = rw;                   // 命令(READ/WRITE)。req->errors=0;                    // 操作时产生的错误次数。req->sector = bh->b_blocknr<<1;// 起始扇区。块号转换成扇区号(1 块=2 扇区)。req->nr_sectors = 2;          // 读写扇区数。req->buffer = bh->b_data;       // 数据缓冲区。req->waiting = NULL;           // 任务等待操作执行完成的地方。req->bh = bh;                  // 缓冲块头指针。req->next = NULL;                 // 指向下一请求项。add_request(major+blk_dev,req); // 将请求项加入队列中 (blk_dev[major],req)。
}低层读写数据块函数,是块设备与系统其它部分的接口函数。
// 该函数在fs/buffer.c中被调用。主要功能是创建块设备读写请求项并插入到指定块设备请求队列中。
// 实际的读写操作则是由设备的 request_fn()函数完成。对于硬盘操作,该函数是 do_hd_request();
// 对于软盘操作,该函数是 do_fd_request();对于虚拟盘则是 do_rd_request()。
// 另外,需要读/写块设备的信息已保存在缓冲块头结构中,如设备号、块号。
// 参数:rw – READ、READA、WRITE 或 WRITEA 命令;bh – 数据缓冲块头指针。
void ll_rw_block(int rw, struct buffer_head * bh)
{unsigned int major; // 主设备号(对于硬盘是 3)。// 如果设备的主设备号不存在或者该设备的读写操作函数不存在,则显示出错信息,并返回。if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||!(blk_dev[major].request_fn)) {printk( "Trying to read nonexistent block-device\n\r" );return;}make_request(major,rw,bh); // 创建请求项并插入请求队列。
}块设备初始化函数,由初始化程序 main.c 调用(init/main.c,128)。
// 初始化请求数组,将所有请求项置为空闲项(dev = -1)。有 32 项(NR_REQUEST = 32)。
void blk_dev_init(void)
{int i;for (i=0 ; i<NR_REQUEST ; i++) {request[i].dev = -1;request[i].next = NULL;}
}

linux0.11内核完全剖析 - ll_rw_blk.c相关推荐

  1. linux内核完全剖析0.11,linux0.11内核完全剖析 - ramdisk.c

    // 段操作头文件.定义了有关段寄存器操作的嵌入式汇编函数. #include // 内存拷贝头文件.含有 memcpy()嵌入式汇编宏函数. #define MAJOR_NR 1 // RAM 盘主 ...

  2. LINUX0.11内核阅读笔记

    我是通过阅读赵炯老师编的厚厚的linux内核完全剖析看完LINUX0.11的代码,不得不发自内心的说Linus真的是个天才.虽然我觉得很多OS设计的思想他是从UNIX学来的,但是他自己很周全很漂亮很巧 ...

  3. Ubuntu14.04下搭建Bochs仿真平台,同时用该平台安装Linux0.11内核

    因为Linux0.11内核需要在80X86硬件平台上运行,现在已经没有该硬件系统了,所以需要搭建Bochs这个仿真平台.Bochs是一个X86硬件平台的开源模拟器. 安装步骤参考的是如下一篇文章:ht ...

  4. Linux-0.11内核学习-添加系统调用

    1.参考资料 赵炯博士的网站oldlinux Linux内核完全注释 Linux0.11 源码 2.概要 操作系统作为软件应用层和底层硬件之间的部分,向下提供服务,向上提供接口.系统调用便是操作系统向 ...

  5. 编译linux0.11内核

    编译linux0.11内核 一.实验环境 二.下载文件 三.配置Linux0.11所需环境 四.编译内核 五.运行linux0.11 六.说明 1.setup.sh脚本里进行了什么操作? 2.最后弹出 ...

  6. 一站式linux0.11内核head.s代码段图表详解

    阅读本文章需要的基础: 计算机组成原理:针对8086,80386CPU架构的计算机硬件体系要有清楚的认知,我们都知道操作系统是用来管理硬件的,那我们就要对本版本的操作系统所依赖的硬件体系有系统的了解, ...

  7. Linux0.11内核源码解析-setup.s

    学习资料: Linux内核完全注释 操作系统真像还原 极客时间-Linux内核源码趣读 Linux0.11内核源码 ->setup程序将system模块从0x10000~0x8ffff整块向下移 ...

  8. Linux0.11内核源码解析-bootsect.s

    学习资料: Linux内核完全注释 操作系统真像还原 极客时间-Linux内核源码趣读 Linux0.11内核源码 ->上电 ->80x86架构CPU会自动进入实模式 ->从地址0x ...

  9. Linux0.11内核剖析--内核体系结构

    一个完整可用的操作系统主要由 4 部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如下图所示: 用户应用程序是指那些字处理程序. Internet 浏览器程序或用户自行编制的各种应用程序: ...

最新文章

  1. 在Linux部署Nodejs项目,一旦断掉XShell就会导致服务被停掉,真的很郁闷~怎么办?果断用forever
  2. springMvc解决json中文乱码
  3. Leetcode 剑指offer 22. 链表中第k个节点 (每日一题 20210716)
  4. 【自动驾驶】8. MDC通信架构 + DDS + SOME/IP
  5. 不用一行代码,用 API 操作数据库,你信吗
  6. python自动发邮件富文本_django 实现后台从富文本提取纯文本
  7. 表空间检测异常的问题诊断
  8. 快速入门 TensorFlow2 模型部署
  9. linux中的mysql启动失败(一直连续出现点点的状态)
  10. 性冷淡风的麻将,获红点奖!网友:没有烟火气了
  11. 外设驱动库开发笔记38:RTD热电阻测温驱动
  12. 如何使用Arrays工具类操作数组
  13. linux mint 19 中国镜像,Beta版Linux Mint 19.3 Tricia的ISO镜像已开放下载
  14. java开发面试 自我介绍 与 项目介绍是重点
  15. Unity hold on.. importing Assets问题
  16. Linux ora-12514多实例,ORA-12514: 错误的解决
  17. 大学学嵌入式技术的优势
  18. jbox弹窗_jbox很好的弹出层 很好的弹出层 - 下载 - 搜珍网
  19. 蚂蚁森林中能量自动收取
  20. ndwi是什么意思_ASD是什么意思

热门文章

  1. ps cs6导出html,Photoshop CS6新功能:软件设置和预置迁移
  2. 中兴软件笔试 c语言,中兴通讯软件工程师面试经验
  3. PHP文字间距怎么调,在html中怎么设置文字间距
  4. 硬盘服务器 路由器哪个好用吗,NAS网络存储设备与路由器+硬盘之间有什么不同之处?...
  5. lna的噪声参数以及功率传输S11 S22
  6. matlab画图命令fplot,matlab绘图方法fplot
  7. 大学物理实验之分光计调节和使用
  8. 硬盘无法格式化及RAW格式的另一种处理方法
  9. D. New Year and the Permutation Concatenation 题解翻译+思路解释(官方为主,我为补充)+普通人能看得懂的代码(我照着思路写的哈哈哈)
  10. 基于LC push的浏览器桌面提醒快速集成方案