Max472 发表于 2021-9-10 21:50:57

操作系统DMA概念问题

本帖最后由 Max472 于 2021-9-10 21:52 编辑

这里说的主存是什么?内存条?还是硬盘?往内存条写也要进行I/O操作吗?

人造人 发表于 2021-9-10 23:13:54

本帖最后由 人造人 于 2021-9-10 23:21 编辑

主存指的是内存

先看一个例子
现在有 2 个程序
程序 1 读取硬盘中的 100 个扇区数据,然后计算平均数
程序 2 计算 从 1 到 1000000 的所有质数和
如果不使用 dma,那就是先读取硬盘的扇区 0,然后读取硬盘的扇区 1,然后读取扇区 2,。。。
就是这样
    for(size_t i = 0; i < 100; ++i) {
      read_sector(&buff, i); // 读取硬盘数据,读取结果存储到 buff 中
    }

然后进行计算平均数
然后程序 1 结束
开始运行程序 2,计算 从 1 到 1000000 的所有质数和

如果使用 dma,那就是这样
先执行程序 1
程序 1 这样写
    dma_set_address(buff);      // 告诉 dma 把数据读取到 buff 中
    dma_set_start_sector(0);    // 告诉 dma 从扇区 0 开始读取
    dma_set_data_length(100);   // 告诉 dma 读取 100 个扇区的数据
    dma_start();                // 开始读取,上面 3 行只是设置 dma 的参数,这一行直接导致 dma 开始工作

// 上面 4 行代码就是告诉 dma 读取硬盘,从扇区 0 开始读取,读取 100 个扇区,把读取的数据放在 buff 中

然后程序 1,执行一个 wait();
这个函数会挂起程序 1,直到 dma 完成任务

现在程序 1 暂停执行,启动程序 2,计算 从 1 到 1000000 的所有质数和
程序 2 可能计算到 80% 了,就快计算完成了
但是 dma 完成任务了,这时候程序 2 暂停,去执行程序 1,就是程序 1 中的那个 wait 函数返回了,然后执行 wait 函数下面的计算平均数的代码
完成后,程序 1 退出
然后返回到程序 2 继续执行剩下的 20% 的计算任务

第二个方案,因为使用了 dma,在读取硬盘数据的时候 cpu 去执行另一个程序了(程序 2)
而方案一中,程序 1 是一直循环读取硬盘的,读取硬盘是一个非常慢的过程,cpu 一直在等非常慢的硬盘,不能去做其他事(不能去执行程序 2)
肯定方案二更好,因为 cpu 不会去等非常慢的硬盘
其实 dma 也是这样工作的
先读取硬盘的扇区 0,然后读取硬盘的扇区 1,然后读取扇区 2,。。。
只不过是和 cpu 同时工作
dma 做 dma 的事情,读取硬盘的扇区 0,然后读取硬盘的扇区 1,然后读取扇区 2,。。。
cpu 执行 cpu 的程序,(程序 2)
两个同时工作,谁也不打扰谁

下面是程序 1 的两个版本的伪代码
void read_sector(char buff[], size_t index) {
    disk_set_sector(i);         // 告诉硬盘读取第 i 个扇区
    while(disk_status() == 0);// 等待硬盘把数据准备好
    disk_read(buff);            // 从硬盘读取数据
}

void task1(void) {
    char buff;   // 一个扇区 512 个字节,一共有 100 个扇区
    for(size_t i = 0; i < 100; ++i) {
      read_sector(&buff, i); // 读取硬盘数据,读取结果存储到 buff 中
    }

    // 下面计算 100 个扇区数据的平均数
    // ...
}

void task1_dma(void) {
    char buff;
    dma_set_address(buff);      // 告诉 dma 把数据读取到 buff 中
    dma_set_start_sector(0);    // 告诉 dma 从扇区 0 开始读取
    dma_set_data_length(100);   // 告诉 dma 读取 100 个扇区的数据
    dma_start();                // 开始读取,上面 3 行只是设置 dma 的参数,这一行直接导致 dma 开始工作
    wait();                     // 等待 dma 完成任务

    // 下面计算 100 个扇区数据的平均数
    // ...
}



其实学习这些东西之前,应该先学习汇编语言
如果你没有用汇编语言读取过硬盘数据,真的很难理解这些概念

再举一个例子
一个公司有两个人
一个领导(cpu)
一个员工(dma)
有两个任务,写文案和跑业务
有两种方案
一、
领导先出去跑业务,跑完业务回来再写文案
那个员工就在公司坐着看,然后大喊 加油,^_^
二、
领导安排员工去跑业务,然后自己在公司写文案
过了很长时间,领导的文案写的也差不多了,员工也回来了
员工拿出一份文件递给领导说,这是这一次的数据
领导说,嗯,我一会处理这个,现在没你什么事了,一边凉快去,之后有事的时候再找你
然后领导简单的看了看那份数据,他决定先写完当前这个文案,然后再去处理那份数据
cpu 完全可以选择先把程序 2 剩下的 20% 算完,然后再去处理程序 1 剩下的部分

人造人 发表于 2021-9-10 23:20:01

往内存条写也要进行I/O操作吗?
cpu 操作内存中的数据不需要进行 I/O 操作
cpu 自己读取硬盘,需要通过 I/O 端口告诉硬盘一些参数
通过 dma 读取
cpu 需要通过 I/O 端口告诉 dma 一些参数,dma 要通过 I/O 端口告诉硬盘一些参数
我想 dma 应该也是通过 I/O 端口和硬盘打交道的,这个我也不清楚

Max472 发表于 2021-9-11 14:30:00

人造人 发表于 2021-9-10 23:13
主存指的是内存

先看一个例子


不采用dma的时候cpu在读取硬盘扇区的时候,是通过中断方式进行I/O操作的吧
如果这样,在读取的时候,由于进程的并发性,cpu不应该在程序1进行I/O操作的时候切换到程序2进行计算吗
程序1读完1个扇区进行中断返回,cpu切换回程序1再进行读取扇区2
是这样吗?{:10_277:}

人造人 发表于 2021-9-11 15:02:45

Max472 发表于 2021-9-11 14:30
不采用dma的时候cpu在读取硬盘扇区的时候,是通过中断方式进行I/O操作的吧
如果这样,在读取的时候,由 ...

嗯,程序 1 也可以这么写,但是还是没有程序 2 效率高
程序 1 的 cpu 会被打扰 100 次
而程序 2 只被打扰 1 次

Max472 发表于 2021-9-11 18:02:16

人造人 发表于 2021-9-11 15:02
嗯,程序 1 也可以这么写,但是还是没有程序 2 效率高
程序 1 的 cpu 会被打扰 100 次
而程序 2 只被打 ...

{:10_256:}

人造人 发表于 2021-9-13 18:00:24

Max472 发表于 2021-9-11 18:02


不对,就算是使用中断的方式读取硬盘数据
也是在硬盘准备数据的这段时间可以切换到其他程序去执行
等硬盘准备好了,处理器需要切回来,从硬盘的 io 端口两个字节两个字节的读取 256 次数据到内存
然后再切去别的程序,等下一个中断到来了,还要切回来,循环 256 次读取硬盘 io 端口
就是说,如果不使用 dma,处理器就需要从硬盘 io 端口,两个字节两个字节的读取数据到内存
这几天我找了好多相关的资料,自己也写程序验证了
之前回复你的,有一些是错误的,^_^

还有,书上,百度上说的,PC/AT 机器,dma 通道只有一个 SDLC 通信适配器(通道1) 和 一个 软盘驱动器(通道2),没有硬盘的
我在 bochs 上找到的是软盘的和 sb16(Sound Blaster 16)的,没有硬盘的
我在百度上找到了一个使用 PCI-IDE控制器 的
使用 dma 要枚举 pci 配置空间
或者改用 软盘
两个的代码量都有点多,而且考虑到要用汇编语言,我实在写不下去了,^_^
未来有机会的话,可以考虑用 C语言
学习这些东西之前要先学汇编语言,学了汇编语言你就可以使用汇编语言验证你的猜想
    .code16
    .globl_start
_start:
    ljmpw   $0x07c0, $1f
1:xorw    %ax, %ax
    movw    %ax, %ss
    movw    $0x7c00, %sp

    callw   interrupt_init
    callw   register_disk_interrupt
    callw   enable_disk_interrupt

    # 全局变量初始化
    # flag = 0;
    movb    $0, %cs:0   # 就把它放在这里,这不会有任何问题

    # 读取从扇区 0 开始的 8 个扇区到 ds:si 指定的位置
    movw    $0x07e0, %ax
    movw    %ax, %ds
    xorw    %si, %si
    movw    $8, %cx
    xorw    %bx, %bx
    callw   read_sect

    movw    $0xb800, %ax
    movw    %ax, %ds
    movb    o', 0x06e0
    movb    k', 0x06e2
    movb    $0x04, 0x06e1
    movb    $0x04, 0x06e3

    cli
    hlt
3:jmp   3b

# void read_sect(uint8_t *buff, size_t start_sect, size_t count);
# buff -> ds:si
# start_sect -> bx
# count -> cx
read_sect:
    pushw   %ax
    pushw   %cx
    pushw   %dx
    pushw   %si
    pushw   %bx
    # 读取 count 个扇区
    movb    %cl, %al
    movw    $0x01f2, %dx
    outb    %al, (%dx)
    # 发送逻辑扇区号
    movb    %bl, %al
    movw    $0x01f3, %dx
    outb    %al, (%dx)
    movb    %bh, %al
    movw    $0x01f4, %dx
    outb    %al, (%dx)
    movb    $0, %al
    movw    $0x01f5, %dx
    outb    %al, (%dx)
    movb    $0xe0, %al      # 主盘,LBA 模式
    movw    $0x01f6, %dx
    outb    %al, (%dx)
    # 发送读命令
    movb    $0x20, %al
    movw    $0x01f7, %dx
    outb    %al, (%dx)

    movw    %cx, %bx
    # 等待硬盘准备好
1:movb    %cs:0, %al
    cmpb    $1, %al
    jne   1b
    movb    $0, %cs:0
    # 从硬盘读取数据
    xorw    %cx, %cx
    movw    $0x01f0, %dx
2:inw   (%dx), %ax
    movw    %ax, (%si)
    addw    $2, %si
    incw    %cx
    cmpw    $256, %cx       # 512 字节,256 个字
    jb      2b
    decw    %bx
    cmpw    $0, %bx
    jne   1b

    popw    %bx
    popw    %si
    popw    %dx
    popw    %cx
    popw    %ax
    retw

# void interrupt_init(void);
interrupt_init:
    cli
    pushw   %ax
    pushw   %cx
    pushw   %dx
    pushw   %si
    pushw   %ds
    # ICW1: 边沿触发/级联方式
    movb    $0x11, %al
    movw    $0x20, %dx
    outb    %al, (%dx)
    # ICW2: 起始中断向量
    movb    $0x20, %al
    movw    $0x21, %dx
    outb    %al, (%dx)
    # ICW3: 从片级联到IR2
    movb    $0x04, %al
    movw    $0x21, %dx
    outb    %al, (%dx)
    # ICW4: 非总线缓存/特殊全嵌套/非自动结束
    movb    $0x11, %al
    movw    $0x21, %dx
    outb    %al, (%dx)
    # ICW1: 边沿触发/级联方式
    movb    $0x11, %al
    movw    $0xa0, %dx
    outb    %al, (%dx)
    # ICW2: 起始中断向量
    movb    $0x28, %al
    movw    $0xa1, %dx
    outb    %al, (%dx)
    # ICW3: 从片级联到IR2
    movb    $0x02, %al
    movw    $0xa1, %dx
    outb    %al, (%dx)
    # ICW4: 非总线缓存/普通全嵌套/非自动结束
    movb    $0x01, %al
    movw    $0xa1, %dx
    outb    %al, (%dx)

    # 设置默认中断
    xorw    %cx, %cx
    movw    %cx, %ds
    movw    $0x20 * 4, %si
    movw    $ignore_interrupt, %ax
    movw    %cs, %dx
1:movw    %ax, (%si)
    movw    %dx, 2(%si)
    addw    $4, %si
    incw    %cx
    cmpw    $16, %cx
    jb      1b

    popw    %ds
    popw    %si
    popw    %dx
    popw    %cx
    popw    %ax
    sti
    retw

# void ignore_interrupt(void);
ignore_interrupt:
    pushw   %ax
    pushw   %dx
    # 发送 OCW2
    movb    $0x20, %al
    movw    $0xa0, %dx
    outb    %al, (%dx)
    movb    $0x20, %al
    movw    $0x20, %dx
    outb    %al, (%dx)
    popw    %dx
    popw    %ax
    iretw

# void register_disk_interrupt(void);
register_disk_interrupt:
    cli
    pushw   %ax
    pushw   %dx
    pushw   %ds
    xorw    %ax, %ax
    movw    %ax, %ds
    movw    $disk_interrupt, %ax
    movw    %cs, %dx
    # IRQ14
    movw    %ax, 0xb8
    movw    %dx, 0xba
    popw    %ds
    popw    %dx
    popw    %ax
    sti
    retw

# void disk_interrupt(void);
disk_interrupt:
    pushw   %ax
    pushw   %dx
    # 读取硬盘状态
    movw    $0x01f7, %dx
    inb   (%dx), %al
    andb    $0x88, %al
    cmpb    $0x08, %al
    jne   1f

    # 硬盘数据准备好了
    # flag = 1;
    movb    $1, %cs:0
1:
    # 发送 OCW2
    movb    $0x20, %al
    movw    $0xa0, %dx
    outb    %al, (%dx)
    movb    $0x20, %al
    movw    $0x20, %dx
    outb    %al, (%dx)
    popw    %dx
    popw    %ax
    iretw

# void enable_disk_interrupt(void);
enable_disk_interrupt:
    pushw   %ax
    pushw   %dx
    movb    $0x00, %al
    movw    $0x03f6, %dx
    outb    %al, (%dx)
    popw    %dx
    popw    %ax
    retw

Max472 发表于 2021-9-13 18:30:42

人造人 发表于 2021-9-13 18:00
不对,就算是使用中断的方式读取硬盘数据
也是在硬盘准备数据的这段时间可以切换到其他程序去执行
等硬 ...

{:10_291:}
页: [1]
查看完整版本: 操作系统DMA概念问题