学习笔记10

一、块设备IO

1.块设备基本概念
块设备将信息存储在固定大小的块中,每个块都有自己的地址。对操作系统而言,块设备是以字符设备的外观展现的,例如/dev/sda,虽然对这种字符设备可以按照字节为单位访问,但是实际上到块设备上却是以块为单位(最小512byte,即一个扇区),这之间的转换是由操作系统来实现的。
下面介绍几个块设备的基本概念:
1)扇区:磁盘盘片上的扇形区域,逻辑化数据,方便管理磁盘空间,是硬件设备数据传送的基本单位,一般512Byte;
2)块:块是VFS和文件系统数据传送的基本单位,必须是扇区的整数倍,格式化文件系统时,可以指定块大小(一般512,1024,2048,4096字节);
3)段:一个内存页或者内存页中的一部分,包含一些相邻磁盘扇区中的数据;磁盘的每个I/O操作就是在磁盘与一些RAM单元之间相互传一些相邻扇区的内容,大多数情况下,磁盘控制器采用DMA方式进行数据传送。如果不同的段在RAM中相应的页框是连续的并且在磁盘上相应的数据块也是相邻的,就可以在通用块层合并它们,产生更大的内存区域,这个区域称为物理段。
通常情况下,我们是通过文件系统来访问块设备,也可以直接使用裸设备,通过指定偏移和大小来读写裸设备。
常见的块存储设备就是物理磁盘,在Linux系统下,还提供基于其他块设备之上的逻辑设备,如Device Mapper,软RAID等。

2.块设备I/O栈
2.1基本概念
介绍块设备的I/O栈之前,我们先来了解一下块I/O栈的几个基本概念。
1)bio:bio是通用块层I/O请求的数据结构,表示上层提交的I/O请求,一个bio包含多个page,这些page必须对应磁盘上一段连续的空间。由于文件在磁盘上并不连续存放,文件I/O提交到块设备之前,极有可能被拆成多个bio结构;
2)request:表示块设备驱动层I/O请求,经由I/O调度层转换后的I/O请求,将会发到块设备驱动层进行处理;
3)request_queue: 维护块设备驱动层I/O请求的队列,所有的request都插入到该队列,每个磁盘设备都只有一个queue(多个分区也只有一个);
这3个结构的关系如下图示:一个request_queue中包含多个request,每个request可能包含多个bio,请求的合并就是根据各种原则将多个bio加入到同一个requesst中。

二、I/O缓冲区

一、缓冲区的概念和作用
缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可利用内存作为缓冲区。

使用硬件作为缓冲区的成本较高,容量也较小,一般仅用在对速度要求非常高的场合(如存储器管理中所用的相联寄存器,由于对页表的访问频率极高,因此使用速度很快的相联寄存器来存放页表项的副本)
一般情况下,更多的是利用内存作为缓冲区,“设备独立性软件”的缓冲区管理就是要组织管理好这些缓冲区
缓冲区的作用如下:

  • 缓和 CPU 与 I/O 设备之间速度不匹配的矛盾
  • 减少对 CPU 的中断频率,放宽对 CPU 中断相应时间的限制
  • 解决数据粒度不匹配的问题(如:输出进程每次可以生成一块数据,但 I/O 设备每次只能输出一个字符)
  • 提高 CPU 与 I/O 设备之间的并行性

三、缓冲区管理算法

1.实现菜单显示及相应处理
显示菜单实现函数:

void show_menu()
{
    printf("------ 1. Linux   ------\n");
    printf("------ 2. Windows ------\n");
    printf("------ 3. Mac OS  ------\n");
    printf("please input:[1 - 3]: ");
}

main函数:

int main(void)
{
    int n;
    int quit = 0;

    while (!quit)
    {
        show_menu();
        scanf("%d", &n);        
        switch(n)
        {
            case 1:
                printf("\nos: Linux\n\n");
                break;
            case 2:
                printf("\nos: Windows\n\n");
                break;
            case 3:
                printf("\nos: Mac OS\n\n");
                break;
            case -1:
                quit = 1;
                break;
            default:                
                break;
        }       
    }   

    return 0;
}

如上程序若键入数字可以正确运行,但是键入非数字的字符,程序便陷入循环打印中。
scanf()和getchar()函数都是从标准输入中去获取数据,前者获取成功返回获取到的数据个数,失败返回0;后者获取一个字符,获取成功返回该字符,失败返回-1。程序之所以会陷入循环打印中,是因为标准输入缓冲区的缘故:scanf函数会先从输入缓存区获取数据,假设输入缓存区数据为空就会去终端陷入阻塞获取,反之就去读缓存区上的数据。在这它要获取的是int型的数据,假设程序使用者键入的是非数字的字符,那么scanf获取数据失败,缓存区上的非数字数据会一直遗留(若是数字数据会被读取,缓存区就不存在该数据),程序会往下执行。下次调用scanf时候,同理会先去判断缓冲区上的数据,缓冲区有数据,所以不会使得终端阻塞等待输入,但该数据又不是int型数据,依次获取失败,程序又会接下往下执行…因此便陷入循环了。解决办法就是不管scanf()函数获取成功与否,都把输入缓存区的数据清空。

在Linux启动时候会启动bash进程,该进程会默认打开着3个设备文件。由于Linux上最小的文件描述符是从0开始,且打开文件时 返回最小可用文件描述符,所以它们的文件描述符分别为0、1、2。
既然知道了文件的描述符,那么自然可以调用系统调用open、write来读写文件了:

int main(void)
{
    int ret;
    int i;
    char buf[1024] = {};

    //往标准输出缓冲区写入数据
    write(1, "hello\n", 6);  //在终端打印"hello\n"

    //读取标准输入缓存区的数据
    ret = read(0, buf, 1024);   //阻塞获取终端输入
    for (i = 0; i < 1024; i++)
        printf("%c", buf[i]);

    return 0;
}

三、代码练习

https://gitee.com/zhang_yu_peng/practice-code/blob/master/代码练习.cpp

posted @ 2021-11-21 17:25  20191306张宇鹏  阅读(22)  评论(0编辑  收藏  举报