鱼C论坛

 找回密码
 立即注册
查看: 2450|回复: 7

[已解决]操作系统DMA概念问题

[复制链接]
发表于 2021-9-10 21:50:57 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 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,。。。
就是这样
  1.     for(size_t i = 0; i < 100; ++i) {
  2.         read_sector(&buff[i * 512], i); // 读取硬盘数据,读取结果存储到 buff 中
  3.     }
复制代码

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

如果使用 dma,那就是这样
先执行程序 1
程序 1 这样写
  1.     dma_set_address(buff);      // 告诉 dma 把数据读取到 buff 中
  2.     dma_set_start_sector(0);    // 告诉 dma 从扇区 0 开始读取
  3.     dma_set_data_length(100);   // 告诉 dma 读取 100 个扇区的数据
  4.     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 的两个版本的伪代码
  1. void read_sector(char buff[], size_t index) {
  2.     disk_set_sector(i);         // 告诉硬盘读取第 i 个扇区
  3.     while(disk_status() == 0);  // 等待硬盘把数据准备好
  4.     disk_read(buff);            // 从硬盘读取数据
  5. }

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

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

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

  21.     // 下面计算 100 个扇区数据的平均数
  22.     // ...
  23. }
复制代码



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

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

使用道具 举报

发表于 2021-9-10 23:13:54 | 显示全部楼层    本楼为最佳答案   
本帖最后由 人造人 于 2021-9-10 23:21 编辑

主存指的是内存

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

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

如果使用 dma,那就是这样
先执行程序 1
程序 1 这样写
  1.     dma_set_address(buff);      // 告诉 dma 把数据读取到 buff 中
  2.     dma_set_start_sector(0);    // 告诉 dma 从扇区 0 开始读取
  3.     dma_set_data_length(100);   // 告诉 dma 读取 100 个扇区的数据
  4.     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 的两个版本的伪代码
  1. void read_sector(char buff[], size_t index) {
  2.     disk_set_sector(i);         // 告诉硬盘读取第 i 个扇区
  3.     while(disk_status() == 0);  // 等待硬盘把数据准备好
  4.     disk_read(buff);            // 从硬盘读取数据
  5. }

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

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

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

  21.     // 下面计算 100 个扇区数据的平均数
  22.     // ...
  23. }
复制代码



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

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

使用道具 举报

发表于 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 端口和硬盘打交道的,这个我也不清楚

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 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
是这样吗?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

嗯,程序 1 也可以这么写,但是还是没有程序 2 效率高
程序 1 的 cpu 会被打扰 100 次
而程序 2 只被打扰 1 次
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-9-13 18:00:24 | 显示全部楼层

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

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

  8.     callw   interrupt_init
  9.     callw   register_disk_interrupt
  10.     callw   enable_disk_interrupt

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

  14.     # 读取从扇区 0 开始的 8 个扇区到 ds:si 指定的位置
  15.     movw    $0x07e0, %ax
  16.     movw    %ax, %ds
  17.     xorw    %si, %si
  18.     movw    $8, %cx
  19.     xorw    %bx, %bx
  20.     callw   read_sect

  21.     movw    $0xb800, %ax
  22.     movw    %ax, %ds
  23.     movb    o', 0x06e0
  24.     movb    k', 0x06e2
  25.     movb    $0x04, 0x06e1
  26.     movb    $0x04, 0x06e3

  27.     cli
  28.     hlt
  29. 3:  jmp     3b

  30. # void read_sect(uint8_t *buff, size_t start_sect, size_t count);
  31. # buff -> ds:si
  32. # start_sect -> bx
  33. # count -> cx
  34. read_sect:
  35.     pushw   %ax
  36.     pushw   %cx
  37.     pushw   %dx
  38.     pushw   %si
  39.     pushw   %bx
  40.     # 读取 count 个扇区
  41.     movb    %cl, %al
  42.     movw    $0x01f2, %dx
  43.     outb    %al, (%dx)
  44.     # 发送逻辑扇区号
  45.     movb    %bl, %al
  46.     movw    $0x01f3, %dx
  47.     outb    %al, (%dx)
  48.     movb    %bh, %al
  49.     movw    $0x01f4, %dx
  50.     outb    %al, (%dx)
  51.     movb    $0, %al
  52.     movw    $0x01f5, %dx
  53.     outb    %al, (%dx)
  54.     movb    $0xe0, %al      # 主盘,LBA 模式
  55.     movw    $0x01f6, %dx
  56.     outb    %al, (%dx)
  57.     # 发送读命令
  58.     movb    $0x20, %al
  59.     movw    $0x01f7, %dx
  60.     outb    %al, (%dx)

  61.     movw    %cx, %bx
  62.     # 等待硬盘准备好
  63. 1:  movb    %cs:0, %al
  64.     cmpb    $1, %al
  65.     jne     1b
  66.     movb    $0, %cs:0
  67.     # 从硬盘读取数据
  68.     xorw    %cx, %cx
  69.     movw    $0x01f0, %dx
  70. 2:  inw     (%dx), %ax
  71.     movw    %ax, (%si)
  72.     addw    $2, %si
  73.     incw    %cx
  74.     cmpw    $256, %cx       # 512 字节,256 个字
  75.     jb      2b
  76.     decw    %bx
  77.     cmpw    $0, %bx
  78.     jne     1b

  79.     popw    %bx
  80.     popw    %si
  81.     popw    %dx
  82.     popw    %cx
  83.     popw    %ax
  84.     retw

  85. # void interrupt_init(void);
  86. interrupt_init:
  87.     cli
  88.     pushw   %ax
  89.     pushw   %cx
  90.     pushw   %dx
  91.     pushw   %si
  92.     pushw   %ds
  93.     # ICW1: 边沿触发/级联方式
  94.     movb    $0x11, %al
  95.     movw    $0x20, %dx
  96.     outb    %al, (%dx)
  97.     # ICW2: 起始中断向量
  98.     movb    $0x20, %al
  99.     movw    $0x21, %dx
  100.     outb    %al, (%dx)
  101.     # ICW3: 从片级联到IR2
  102.     movb    $0x04, %al
  103.     movw    $0x21, %dx
  104.     outb    %al, (%dx)
  105.     # ICW4: 非总线缓存/特殊全嵌套/非自动结束
  106.     movb    $0x11, %al
  107.     movw    $0x21, %dx
  108.     outb    %al, (%dx)
  109.     # ICW1: 边沿触发/级联方式
  110.     movb    $0x11, %al
  111.     movw    $0xa0, %dx
  112.     outb    %al, (%dx)
  113.     # ICW2: 起始中断向量
  114.     movb    $0x28, %al
  115.     movw    $0xa1, %dx
  116.     outb    %al, (%dx)
  117.     # ICW3: 从片级联到IR2
  118.     movb    $0x02, %al
  119.     movw    $0xa1, %dx
  120.     outb    %al, (%dx)
  121.     # ICW4: 非总线缓存/普通全嵌套/非自动结束
  122.     movb    $0x01, %al
  123.     movw    $0xa1, %dx
  124.     outb    %al, (%dx)

  125.     # 设置默认中断
  126.     xorw    %cx, %cx
  127.     movw    %cx, %ds
  128.     movw    $0x20 * 4, %si
  129.     movw    $ignore_interrupt, %ax
  130.     movw    %cs, %dx
  131. 1:  movw    %ax, (%si)
  132.     movw    %dx, 2(%si)
  133.     addw    $4, %si
  134.     incw    %cx
  135.     cmpw    $16, %cx
  136.     jb      1b

  137.     popw    %ds
  138.     popw    %si
  139.     popw    %dx
  140.     popw    %cx
  141.     popw    %ax
  142.     sti
  143.     retw

  144. # void ignore_interrupt(void);
  145. ignore_interrupt:
  146.     pushw   %ax
  147.     pushw   %dx
  148.     # 发送 OCW2
  149.     movb    $0x20, %al
  150.     movw    $0xa0, %dx
  151.     outb    %al, (%dx)
  152.     movb    $0x20, %al
  153.     movw    $0x20, %dx
  154.     outb    %al, (%dx)
  155.     popw    %dx
  156.     popw    %ax
  157.     iretw

  158. # void register_disk_interrupt(void);
  159. register_disk_interrupt:
  160.     cli
  161.     pushw   %ax
  162.     pushw   %dx
  163.     pushw   %ds
  164.     xorw    %ax, %ax
  165.     movw    %ax, %ds
  166.     movw    $disk_interrupt, %ax
  167.     movw    %cs, %dx
  168.     # IRQ14
  169.     movw    %ax, 0xb8
  170.     movw    %dx, 0xba
  171.     popw    %ds
  172.     popw    %dx
  173.     popw    %ax
  174.     sti
  175.     retw

  176. # void disk_interrupt(void);
  177. disk_interrupt:
  178.     pushw   %ax
  179.     pushw   %dx
  180.     # 读取硬盘状态
  181.     movw    $0x01f7, %dx
  182.     inb     (%dx), %al
  183.     andb    $0x88, %al
  184.     cmpb    $0x08, %al
  185.     jne     1f

  186.     # 硬盘数据准备好了
  187.     # flag = 1;
  188.     movb    $1, %cs:0
  189. 1:
  190.     # 发送 OCW2
  191.     movb    $0x20, %al
  192.     movw    $0xa0, %dx
  193.     outb    %al, (%dx)
  194.     movb    $0x20, %al
  195.     movw    $0x20, %dx
  196.     outb    %al, (%dx)
  197.     popw    %dx
  198.     popw    %ax
  199.     iretw

  200. # void enable_disk_interrupt(void);
  201. enable_disk_interrupt:
  202.     pushw   %ax
  203.     pushw   %dx
  204.     movb    $0x00, %al
  205.     movw    $0x03f6, %dx
  206.     outb    %al, (%dx)
  207.     popw    %dx
  208.     popw    %ax
  209.     retw
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-25 14:09

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表