文章目录

  • 硬盘控制器端口
  • 常用硬盘操作方法
  • 操纵硬盘
  • fdisk命令 创建磁盘分区
    • 磁盘分区表
    • 硬件驱动程序
      • 硬盘读写
  • 参考文献

写在前面:自制操作系统Gos 第二章第四篇:主要内容是如何操纵外设,如何操纵硬盘

关于硬盘的原理我在Linux —— 文件系统及相关操作命令其实是已经简单讲了一下了,这里就不再赘述了。

在上一篇中我们讲到了CPU和显示器交互的接口其实就是显存。那CPU和硬盘打交道也是同样的道理:硬盘控制器


硬盘控制器是专门驱动外部设备的模块电路,CPU只同它们交互,由它们将信息传递给外部设备

硬盘控制器端口

让硬盘工作,我们需要通过读写硬盘控制器的端口。下面列出了部分的端口,也是我们开发Gos需要用到的端口:

Primary通道端口 Secondary通道端口 读操作时用途 写操作时用途

首先是命令寄存器:Command register

0x1F0 0X170 data data
0X1F1 0X171 error features
0X1F2 0X172 sector count sector count
0X1F3 0X173 LBA low LBA low
0X1F4 0X174 LBA mid LBA mid
0X1F5 0X175 LBA high LBA high
0X1F6 0X176 device device
0X1F7 0X177 status command

除此之外是控制寄存器:Control register

0x3F6 0x376 alternate status device control


电脑主板上有两个硬盘串行接口(STAT),每个接口可以插一块硬盘。其中第一个STAT被称之为Primary通道,其连接主盘master;第二个STAT称之为Secondary通道,其连接从盘slave
但是这里要注意一个问题:端口是按照通道给出的
也就是说:一个通道的主、从两块硬盘都用这些端口号。想要操作某通道所对应的某块硬盘,直接单独指定device寄存器的第四位(硬盘标志位)为1就可以了。

其中命令寄存器Command register 用于向硬盘驱动器写入命令或者从硬盘控制器获得硬盘状态
控制寄存器Control register 用于控制硬盘的工作状态

下面我们具体讲一下这些寄存器的作用:

  • data:负责管理数据。在读硬盘时,硬盘准备好的数据后,硬盘控制器就将其放在内存的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据;写硬盘时,我们要把数据源源不断地输送到此端口,之后便存入缓冲区,之后再写入相应的扇区。
  • error:当0x171或者0x1F1读硬盘失败的时候才有用,里面会记录着失败的信息。其中尚未读取的扇区在sector count寄存器中。在写硬盘的时候,其则被称之为feature寄存器。
  • sector count:用来指定待读取或待写入的扇区数。硬盘每完成一个扇区,就会将此寄存器的值-1.如果失败,那么其就存储着尚未读取的扇区数
  • LBA:逻辑块地址(Logical block address),一共有两种:第一种时LBA28,用28为来描述一个扇区的地址。可以这次228*512=128GB大小的空间,Gos用LBA28就可以了;第二种是LBA48,顾名思义,其可以支持128PB的空间
  • LBA low:存储28位地址的0~7位
  • LBA middle:存储28位地址的8~15位
  • LBA high:存储28位地址和的16~23位
  • device:0~3位用来存储28位地址的剩下4位,第4位表示是主盘还是从盘,0表示主盘,1代表从盘。第六位表示是否开启LBA,1代表启用。第五位和第七位表示MBS位。
  • status:用来给出给出硬盘的状态信息。第0位表示error位;第三位表示data request,如果此位为1,表示数据准备好了;第6位表示drdy,表示硬盘就绪;第七位是BSY,表示硬盘是否繁忙。

:
在Gos中,我们主要使用三个命令:
identify:0xEC,即硬盘识别
read sector:0x20,即读扇区
write sector:0x30,即写扇区

下面是device status两个寄存器的示意图:

常用硬盘操作方法

硬盘的指令很多,用法也有很多。有的直接往command寄存器中写,有的则是在feature寄存器中写入参数,可谓是多种多样。但是不管是哪种写法,command寄存器都是最后写。因为其一旦一开始就写,那么整个硬盘就轰隆隆的启动了。

而我们的操作顺序如下:

  1. 先选择通道,往该通道的sector count寄存器 写入待操作的扇区数
  2. 往该通道上的三个LBA寄存器写入扇区起始地址的低24位
  3. 往device寄存器写入剩下四位,之后置第6位为1;设置第4位,选择要操作的硬盘
  4. 往该通道的command寄存器写入操作命令
  5. 读取该通道上的status寄存器,判断硬盘工作是否完成
  6. 如果是读硬盘,进入下一个步骤。否则,完成。
  7. 将硬盘数据读出


我们使用的读取方式是以下两种结合:
无条件传送方式:数据随时随地准备好,直接拿来吧你
查询传送方式:取之前先查一查数据准备好了没有,然后再拿来吧你

操纵硬盘

上硬菜咯!

# 文件 mbr.S 中的内容
; MBR引导程序,从BIOS接过权力,交接给loader内核加载器
;*************************
%include "boot.inc"
SECTION MBR vstart=0x7c00; 初始化显卡控制信息,本质是往显存写入数据mov ax,csmov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax
;*************************
; 清屏利用0x06号功能,上卷全部行,进行清屏
; int 0x10  功能号:0x60    功能描述:上卷窗口
; 输入:
; AH 功能号: 0x06
; AL = 上卷的行数(0代表全部)
; BH = 上卷的行属性
; (CL,CH) = 窗口左上角(x,y) 的位置
; (DL,DH) = 窗口右下角(x,y)的位置
; 无返回值!mov ax,0600hmov bx,0700hmov cx,0        ;左上角(0,0)mov dx,0x184f   ;右下角(80,25);VAG文本模式中,一行只能容纳80个字符,总共25行;下标从0开始,所以0x18=24,0x4f=79int 10h
;*************************;输出字符串:hello,Gos!mov byte [gs:0x00],'h'mov byte [gs:0x01],0x0F     ;黑底亮白不闪烁mov byte [gs:0x02],'e'mov byte [gs:0x03],0x0Fmov byte [gs:0x04],'l'mov byte [gs:0x05],0x0Fmov byte [gs:0x06],'l'mov byte [gs:0x07],0x0Fmov byte [gs:0x08],'o'mov byte [gs:0x09],0x0Fmov byte [gs:0x0a],','mov byte [gs:0x0b],0x0Fmov byte [gs:0x0c],'G'mov byte [gs:0x0d],0x0Fmov byte [gs:0x0e],'o'mov byte [gs:0x0f],0x0Fmov byte [gs:0x10],'s'mov byte [gs:0x11],0x0Fmov byte [gs:0x12],'!'mov byte [gs:0x13],0x0F
;*************************
;初始化磁盘信息mov eax,LOADER_START_SECTOR     ;起始扇区LBA地址mov bx,LOADER_BASE_ADDR         ;写入的地址mov cx,1                        ;待读入的扇区数call read_disk_m_16             ;调用读取程序起始部分的函数jmp LOADER_BASE_ADDR
;*************************
;读取磁盘的n个扇区
read_disk_m_16:;eax=LBA扇区号;bx=将数据写入的内存地址;cx=读入的扇区数mov esi,eax                     ;备份eaxmov di,cx                       ;备份cx
;读写硬盘;1.设置要读取的扇区数量mov dx,0x1f2mov al,clout dx,al                       ;读取的扇区数mov eax,esi                     ;恢复eax;2.将LBA地址存入0x1f3~0x1f6;LBA地址7~0位写入端口0x1f3mov dx,0x1f3out dx,al;LBA地址15~8位写入端口0x1f4mov cl,8shr eax,clmov dx,0x1f4out dx,al;LBA地址23~16位写入端口0x1f5shr eax,clmov dx,0x1f5out dx,alshr eax,cland al,0x0f                     ;LBA第24~27位or al,0xe0                      ;设置7~4位为1110,表示LBA模式mov dx,0x1f6out dx,al;3.向0x1f7端口写入读命令,0x20mov dx,0x1f7mov al,0x20out dx,al;4.检测硬盘状态
.not_ready:nop                             ;同一端口,写时表示写入命令字,读时表示读入硬盘状态in al,dx                        ;and al,0x88                     ;第3位为1表示硬盘控制器已经准备号数据传输了;第7位为1表示硬盘忙cmp al,0x08                     jnz .not_ready                  ;若未准备号,继续等;5.从0x1f0端口读数据mov ax,dimov dx,256                      ;一个扇区512字节,1字=2字节,所以时256mul dxmov cx,axmov dx,0x1f0.go_on_read:in ax,dxmov [bx],axadd bx,2loop .go_on_readrettimes 510-($-$$) db 0
db 0x55,0xaa

可以看到,我们在这里包含了一个头文件boot.inc,这个文件中的内容很简单,只是定义了两个常量:

# boot.inc 文件中的内容
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2     ;第二扇区

之后,我们开始编译这个文件:

# -I 指定头文件搜索路径
# boot.inc 文件在同级目录include下
nasm -I ../include/ mbr.bin mbr.S

编译成功之后,会在目录中生成mbr.bin文件:

之后,我们使用dd工具将其写入我们之前创建好的磁盘hd60.img中:

# 记得换成自己的路径
sudo dd if=./mbr.bin of=/bochs/bo_tmp/bin/hd60M.img bs=512 count=1 conv=notrunc

然后,我们转到bochs的bin目录下,使用以下命令开启bochs:

sudo ./bochs -f boch.conf


进入到如下的界面了,默认选项是6,我们直接回车就好了:

之后,在bochs模拟的机器上便会打印出:hello Gos!(虽然这个和操纵硬盘没有关系,硬盘操纵是为了我们之后进入保护模式做铺垫用的)

fdisk命令 创建磁盘分区

分区时逻辑上划分磁盘空间的方式,本质上是人为的将柱面扇区划分成不同的分组,每个分组都是单独的分区。各个分区都有自己的元数据,用来记录分区本身在硬盘上的起止界限等信息。在硬盘的MBR中有个数据机构,64字节大小,就是分区表。其中的每个表现就是一个分区,每个表项大小是16字节,所以最多可用容纳4个分区。

但是,现在的操作系统通过在每个元数据中增加一个ID信息来支持更多的分区。原理是,这个ID新增了一种分区叫做逻辑分区,逻辑分区可用无限再分。所以这4个分区,其中有一个做扩展分区,扩展分区可用无限再分,而另外三个则是主分区

而分区的过程如下:

  1. 我们先用以下命令查看一下当前的硬盘信息
[ik@localhost bin]$ sudo fdisk -l ./hd10M.img磁盘 ./hd10M.img:10 MB, 10321920 字节,20160 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
  1. 使用fdisk命令开始分区:
[ik@localhost bin]$ sudo fdisk ./hd10M.img
欢迎使用 fdisk (util-linux 2.23.2)。更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。Device does not contain a recognized partition table
使用磁盘标识符 0xb2a716ed 创建新的 DOS 磁盘标签。命令(输入 m 获取帮助):
  1. 进入专家模式,开始创建主分区
命令(输入 m 获取帮助):x       #进入专家模式专家命令(输入 m 显示帮助):c # 设置柱面数
柱面数 (1-1048576,默认为 1):162专家命令(输入 m 显示帮助):h # 设置磁头数
磁头数 (1-256,默认为 255):16专家命令(输入 m 显示帮助):r    # 返回上一级命令(输入 m 获取帮助):n   # 开始创建分区
Partition type:p   primary (0 primary, 0 extended, 4 free)e   extended
Select (default p): p   # 创建主分区
分区号 (1-4,默认 1):1  # 指定分区号为1
起始 扇区 (2048-20159,默认为 2048):2048  # 指定起始扇区和终止扇区
Last 扇区, +扇区 or +size{K,M,G} (2048-20159,默认为 20159):4096
分区 1 已设置为 Linux 类型,大小设为 1 MiB
  1. 创建扩展分区
命令(输入 m 获取帮助):n   # 开始创建分区
Partition type:p   primary (1 primary, 0 extended, 3 free)e   extended
Select (default p): e   # 创建扩展分区
分区号 (2-4,默认 2):2  # 指定分区号
起始 扇区 (4097-20159,默认为 6144):5000  # 指定起始扇区和终止扇区
Last 扇区, +扇区 or +size{K,M,G} (5000-20159,默认为 20159):10000
分区 2 已设置为 Extended 类型,大小设为 2.5 MiB
  1. 创建逻辑扇区
命令(输入 m 获取帮助):n   # 开始分区
Partition type:p   primary (1 primary, 1 extended, 2 free)l   logical (numbered from 5)
Select (default p): l   # 开始创建逻辑分区
添加逻辑分区 5
起始 扇区 (7048-10000,默认为 8192):8000
Last 扇区, +扇区 or +size{K,M,G} (8000-10000,默认为 10000):8000
分区 5 已设置为 Linux 类型,大小设为 512 B

磁盘分区表

磁盘分区表(Disk Partition Table,DPT)是多个分区的元数据组成的表,表中的每一项都对应一个分区。所以其本质其实是个数组,此数组的长度跟分区个数一致,为4 。这四个元素位于MBR的0x1BE~0x1FD空间,总共64个字节,但值得提一下的是在这64字节之后是两字节的魔数0x55AA。至于支持扩展分区则是采用链式结构,将多张分区表串联起来。

对于分区表中的每一项则是一个16位的元数据:

硬件驱动程序

硬盘上有两个ata通道,也叫做IDE通道。第一个IDE通道上的两个硬盘挂在IRQ14上。当我们对硬盘操作的时候,我们需要提前指定是对主盘还是从盘执行,这个是device寄存器的第四位dev位指定的。

对于硬盘来说其属性也就是我们刚刚介绍的:

// * @brief 磁盘结构
struct disk
{char name[8];                    //磁盘名称struct ide_channel *my_channel;  //此硬盘归属哪个ide通道uint8_t dev_no;                  //主硬盘0,从硬盘1struct partition prim_parts[4];  //主分区struct partition logic_parts[8]; //逻辑分区
};

这里我们设定每个硬盘都有四个主分区和8个逻辑分区。每个分区主要包含这个分区的一些表述性信息:起始扇区、扇区数、磁盘位图等等。

// * @brief 分区结构体
struct partition
{uint32_t start_lba;           //起始扇区uint32_t sec_cnt;             //扇区数struct disk *my_disk;         //分区所属的硬盘struct list_elem part_tag;    //队列中的标记char name[8];                 //分区名称struct super_block *su_block; //本分区的超级块struct bitmap block_bitmap;   //块位图struct bitmap inode_bitmap;   //i节点位图struct list open_inodes;      //本分区打开的i节点队列
};

那么我们首先要做的就是接收来自可编程中断控制器PIC的信号,这样就需要设置一下:

   // 打开从片上的IRQ14,此引脚接收硬盘控制器的中断outb(PIC_S_DATA, 0xbf);

然后开始对硬盘进行初始化操作:

  • 设置硬盘对应的中断引脚
  • 设置硬盘中断处理函数
  • 获取每个的参数信息
/** @brief 硬盘数据结构初始化* @note    1.获取硬盘数量* @note    2.获取通道数量 = 硬盘数量/2* @note    3.处理每个通道上的信息,包括起始端口和通道号* @note    4.获得每个通道上硬盘的信息、扫描硬盘分区信息*/
void ide_init()
{printk("ide init start...\n");uint8_t hd_cnt = *((uint8_t *)(0x475)); //0x475BIOS规定的读取硬盘的位置ASSERT(hd_cnt > 0);list_init(&partition_list);channel_cnt = DIV_ROUND_UP(hd_cnt, 2); //一个通道两个硬盘,根据硬盘数量反推ide通道数struct ide_channel *channel;uint8_t channelogic_hd_no = 0;uint8_t dev_no = 0; //硬盘标号printk("harddisk info:\n");printk("    harddisk number: %d\n", hd_cnt);printk("    channle number: %d\n", channel_cnt);//处理每个通道上的硬盘while (channelogic_hd_no < channel_cnt){channel = &channels[channelogic_hd_no];sprintf(channel->name, "ide%d", channelogic_hd_no);switch (channelogic_hd_no){case 0:                          //主硬盘channel->port_base = 0x1f0;  //ide0的通道起始端口号channel->irq_no = 0x20 + 14; //从片上倒数第二个中断引脚break;case 1:channel->port_base = 0x170;channel->irq_no = 0x20 + 15; //从片的最后一个中断引脚break;default:break;}channel->expecting_intr = false;lock_init(&channel->lock);/** 初始化为0,目的是向硬盘控制器请求数据后,硬盘驱动器sema_down此信号会阻塞线程* 直到硬盘完成后通过发中断,由中断处理程序将此信号量sema_up,唤醒线程去处理*/sema_init(&channel->disk_done, 0);register_handler(channel->irq_no, intr_hd_handler);//分别获取两个硬盘的参数和分区信息while (dev_no < 2){struct disk *hd = &channel->devices[dev_no];hd->my_channel = channel;hd->dev_no = dev_no;sprintf(hd->name, "sd%c", 'a' + channelogic_hd_no * 2 + dev_no);identify_disk(hd); // 获取硬盘参数if (dev_no != 0){                          // 内核本身的裸硬盘(hd60M.img)不处理partition_scan(hd, 0); // 扫描该硬盘上的分区}primary_hd_no = 0;logic_hd_no = 0;dev_no++;}dev_no = 0; // 将硬盘驱动器号置0,为下一个channel的两个硬盘初始化。channelogic_hd_no++;}printk("\n    all partition info:\n");/* 打印所有分区信息 */list_traversal(&partition_list, partition_info, (int)NULL);printk("ide init done!\n");
}

这样初始化完成之后,当对应硬盘中如果有中断信号产生,便会调用intr_hd_handler函数进行处理。而产生中断的则是硬盘完成读写操作之后,会主动来提示驱动程序。而我们的驱动程序在收到中断之后,便会唤醒阻塞于硬盘读写的程序:

/** @brief 硬盘中断处理程序* @param irq_no 中断号*/
void intr_hd_handler(uint8_t irq_no)
{ASSERT(irq_no == 0x2e || irq_no == 0x2f);uint8_t ch_no = irq_no - 0x2e;struct ide_channel *channel = &channels[ch_no];ASSERT(channel->irq_no == irq_no);if (channel->expecting_intr){//正在等待中断channel->expecting_intr = false;//唤醒线程sema_up(&channel->disk_done);inb(reg_status(channel));}
}

硬盘读写

对于硬盘的读写在内核层面需要我们主动的传入往哪个硬盘去写、写什么以及写多少这些信息。这样,我们要做的第一件事情起始就是使用函数select_disk选择使用哪块硬盘,这步操作的本质还是对device寄存器的操作,如果写入主盘就将其位设置为0,如果是写入从盘那么置为0,之后把这个写入通道的起始端口号就可以了。

/** @brief 选择操纵硬盘* @param hd 硬盘的指针*/
static void select_disk(struct disk *hd)
{//设置硬件寄存器uint8_t reg_device = BIT_DEV_MBS | BIT_DEV_LBA;if (hd->dev_no == 1) //从盘,dev位设置为1{reg_device |= BIT_DEV_DEV;}outb(reg_dev(hd->my_channel), reg_device);
}

之后的下一步便是告诉硬盘我要从哪里开始写了。这个需要写入sector count寄存器中需要待读取的扇区数,之后再LBAlow LBAmid以及LBAhigh中写入LBA地址,这个过程也是对device寄存器操作。

/** @brief 向硬盘控制器写入起始扇区地址以及要读写的扇区数* @param hd 硬盘指针* @param lba 扇区起始地址* @param sec_cnt 扇区数*/
static void select_sector(struct disk *hd, uint32_t lba, uint8_t sec_cnt)
{ASSERT(lba <= max_lba);struct ide_channel *channel = hd->my_channel;//# 1.写入要读取的扇区数//若sec_cnt为0,表示写入256个扇区outb(reg_sect_cnt(channel), sec_cnt);//# 2.写入扇区号outb(reg_lba_l(channel), lba);                                                                       //lba的低8位outb(reg_lba_m(channel), lba >> 8);                                                                  //lba的8~15位outb(reg_lba_h(channel), lba >> 16);                                                                 //lba的16~23位outb(reg_dev(channel), BIT_DEV_MBS | BIT_DEV_LBA | (hd->dev_no == 1 ? BIT_DEV_DEV : 0) | lba >> 24); //写入lba的24~27位,这四位在device寄存器中
}

之后,我们将读磁盘命令0x20写入status寄存器,这个时候硬盘就开始根据我们传入的位置信息开始读扇区了,之后会把这部分数据放到硬盘的缓冲区。这里还有一步就是设置当前进程状态是阻塞的情况。

/** @brief 向通道channel发出命令cmd* @param channel 通道指针* @param cmd 硬盘操作命令*/
static void cmd_out(struct ide_channel *channel, uint8_t cmd)
{//只要向硬盘发出命令便将此标记置为truechannel->expecting_intr = true;outb(reg_cmd(channel), cmd);
}

硬盘读完之后便会触发中断处理程序唤醒等待中的线程。线程这个时候把数据从硬盘的缓冲区搬移到自己的缓存区。

/** @brief 硬盘读入sec_cnt个扇区的数据到buf* @param hd 硬盘指针* @param buff 存放读取数据的缓冲区* @param sec_cnt 读取的扇区数*/
static void read_from_sector(struct disk *hd, void *buff, uint8_t sec_cnt)
{uint32_t size_in_byte; //要读取的字节数if (sec_cnt == 0)      //当sec_cnt为0时,表示256{size_in_byte = 256 * 512;}else{size_in_byte = sec_cnt * 512;}//因为写入的单位是字,所以这里要除二啦insw(reg_data(hd->my_channel), buff, size_in_byte / 2);
}

硬盘写则是相同的过程,只不过最后需要操作写端口。

/** @brief 将buf中的sec_cnt个扇区数据写入硬盘* @param hd 硬盘指针* @param lba 扇区地址* @param buff 缓冲区* @param sec_cnt 扇区数量*/
void ide_write(struct disk *hd, uint32_t lba, void *buf, uint32_t sec_cnt)
{ASSERT(lba <= max_lba);ASSERT(sec_cnt > 0);get_lock(&hd->my_channel->lock);//1.选择操作的硬盘select_disk(hd);uint32_t per_op_sectors;uint32_t done_sectors = 0;while (done_sectors < sec_cnt){if ((done_sectors + 256) <= sec_cnt){per_op_sectors = 256;}else{per_op_sectors = sec_cnt - done_sectors;}//2.写入待写入的扇区数和起始扇区号select_sector(hd, lba + done_sectors, per_op_sectors);//3.执行命令写入red_cmd寄存器cmd_out(hd->my_channel, CMD_WRITE_SECTOR);//4.检测硬盘状态是否可读if (!busy_wait(hd)){char error[64];sprintf(error, "%s write sector %d failed!\n", hd->name, lba);PANIC(error);}//5.将数据写入硬盘write2sector(hd, (void *)((uint32_t)buf + done_sectors * 512), per_op_sectors);sema_down(&hd->my_channel->disk_done);done_sectors += per_op_sectors;}abandon_lock(&hd->my_channel->lock);
}

参考文献

[1] 操作系统真相还原     3.6 让MBR使用硬盘

Gos —— 掌控硬盘相关推荐

  1. 刘奇:能否掌控复杂性,决定着分布式数据库的生死存亡

    本文回顾了 PingCAP 创始人兼 CEO 刘奇在 9 月 22 日的 用户峰 会 上以<现在决定未来>为主题的演讲, 分享了 PingCAP 在技术演进.用户价值.数据库技术趋势.国际 ...

  2. mpython掌控板_AppInventor+掌控板:为硬件编程(1)

    一直以来都有读者询问是否有硬件控制类的案例,这也是我一直期待涉及的话题,但由于种种原因始终未能付诸行动.就在上个月,张路老师(roadlabs)收到了谢作如老师寄来的一块掌控板,并共同讨论了关于掌控板 ...

  3. 云米冰箱能控制扫地机器人_用冰箱就能掌控全屋家电?云米21Face 428L确实可以...

    现代年轻人都很懂时尚科技,他们都很懂利用智能手机来智能扩展更多的玩法.然而对于"宅"在家中时,掌控全屋家电已经无需拿起手机那么麻烦,只需面对着云米21Face互联网冰箱 428L, ...

  4. 深度强化学习的前景:帮助机器掌控复杂性

    作者:数据实战派 来源:数据实战派 深度强化学习,即机器通过测试其行为后果来学习的方法,是人工智能最有前途和影响力的领域之一.它将深度神经网络与强化学习结合在一起,可以通过训练实现多个步骤的目标. 它 ...

  5. 技术总监的反思录:我是如何失去团队掌控的?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者 | zer0black 来源 | https://w ...

  6. mpython掌控板作品_mPython掌控板Easy-IoT物联

    [原由] 10月底,在做一个项目时用SioT物联掌控,发现了一个小问题,总是不稳定. 以前也用过SIoT和掌控板来做物联,没有感觉到问题啊,难道在测试时总是通了就好,没有做稳定性测试么? 和谢老师进行 ...

  7. 趋势科技全球首席安全官ED:人类迈向智能社会进程中不能失去掌控力

    如果你还对<速度与激情8>中大反派塞弗控制几百上千辆自动驾驶汽车演绎"僵尸车"."汽车雨"的街头较量的画面感到印象深刻,那么你总该相信这并非完全杜撰 ...

  8. 青源LIVE第29期|清华叉院高阳:使用1/500数据掌控Atari游戏-EfficientZero算法详解

    当前强化学习已在许多应用中取得了巨大成功.但样本效率仍是强化学习中一个重大挑战,重要的方法需要数百万(甚至数十亿)的环境步骤来训练.虽然,当前在基于图像的样本高效RL算法方面取得了重大进展:但是,在A ...

  9. 一文读懂深度学习:这个AI核心技术被美国掌控,很危险

    2019-11-28 18:39:22 "中国有多少数学家投入到人工智能的基础算法研究中?" 今年4月底,中国工程院院士徐匡迪等多位院士的发声,直击我国在算法这一核心技术上的缺失, ...

  10. 多巴胺如何驱使我们克服复杂情况、逆境、情绪, 让我们掌控周遭的环境的

    来源:本文摘自<贪婪的多巴胺> 仅仅是"想要"很少能让你得到任何东西.你必须弄清楚如何获得它,以及它是否值得拥有.事实上,如果我们做事时不考虑怎么做和下一步做什么,失败 ...

最新文章

  1. Android10.0 Binder通信原理(五)-Binder驱动分析
  2. 《大话数据结构》读书笔记-查找
  3. 转]Window, Linux动态链接库的分析对比
  4. 百度ERNIE新突破,登顶中文医疗信息处理权威榜单CBLUE冠军
  5. SMT精密电阻对照表
  6. Ubuntu18.04.1系统安装mmdetection(含torch、torchvision、mmcv-full)
  7. 解决AutoCAD2010安装完毕后闪退问题
  8. 高门槛的动作捕捉技术,真的会成为VR行业灾难的缔造者吗?
  9. 【keras】有关loss function的定义-返回的是`矩阵`还是`标量`
  10. Mybatis3.5.4官网下载
  11. 省二级计算机考试VB题库,2015河南省全国计算机等级考试二级笔试试卷VB考试题库...
  12. 浙大中控T9100系统在压缩机上的应用
  13. 新手学python笔记--3--爬取天天基金数据
  14. It was possible to detect the usage of the deprecated TLSv1.0 and/or TLSv1.1 protocol on this system
  15. 到底什么是阿里味?能否在不加入阿里的时候可以体验一下
  16. STL容器底层数据结构
  17. vue使用echarts图表自适应的几种解决方案
  18. linux下mysql(rpm)安装使用手册
  19. numeric类型对应java的类型
  20. ✨【Code皮皮虾】一次通过99.90%,思路详解【找到需要补充粉笔的学生编号】

热门文章

  1. Java九阳神功-抽象方法与抽象类
  2. hive sql系列(七)——查询前20%时间的订单信息
  3. 【7gyy】利用F11恢复崩溃系统
  4. ChAMP 差异甲基化分析
  5. python爬虫代码大作业_爬虫大作业
  6. 计算机怎么不读u盘,老司机告诉你电脑不读U盘的解决方法
  7. 职工科研项目管理系统的设计与实现附代码
  8. 4个团队领导必备的技能和素质
  9. 牛顿二项式定理(广义二项式定理)
  10. 第十届蓝桥杯省赛Scratch编程真题解析