Gos —— 掌控硬盘
文章目录
- 硬盘控制器端口
- 常用硬盘操作方法
- 操纵硬盘
- 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寄存器都是最后写。因为其一旦一开始就写,那么整个硬盘就轰隆隆的启动了。
而我们的操作顺序如下:
- 先选择通道,往该通道的sector count寄存器 写入待操作的扇区数
- 往该通道上的三个LBA寄存器写入扇区起始地址的低24位
- 往device寄存器写入剩下四位,之后置第6位为1;设置第4位,选择要操作的硬盘
- 往该通道的command寄存器写入操作命令
- 读取该通道上的status寄存器,判断硬盘工作是否完成
- 如果是读硬盘,进入下一个步骤。否则,完成。
- 将硬盘数据读出
注:
我们使用的读取方式是以下两种结合:
无条件传送方式:数据随时随地准备好,直接拿来吧你
查询传送方式:取之前先查一查数据准备好了没有,然后再拿来吧你
操纵硬盘
上硬菜咯!
# 文件 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个分区,其中有一个做扩展分区,扩展分区可用无限再分,而另外三个则是主分区。
而分区的过程如下:
- 我们先用以下命令查看一下当前的硬盘信息
[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 字节
- 使用
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 获取帮助):
- 进入专家模式,开始创建主分区
命令(输入 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
- 创建扩展分区
命令(输入 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
- 创建逻辑扇区
命令(输入 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 —— 掌控硬盘相关推荐
- 刘奇:能否掌控复杂性,决定着分布式数据库的生死存亡
本文回顾了 PingCAP 创始人兼 CEO 刘奇在 9 月 22 日的 用户峰 会 上以<现在决定未来>为主题的演讲, 分享了 PingCAP 在技术演进.用户价值.数据库技术趋势.国际 ...
- mpython掌控板_AppInventor+掌控板:为硬件编程(1)
一直以来都有读者询问是否有硬件控制类的案例,这也是我一直期待涉及的话题,但由于种种原因始终未能付诸行动.就在上个月,张路老师(roadlabs)收到了谢作如老师寄来的一块掌控板,并共同讨论了关于掌控板 ...
- 云米冰箱能控制扫地机器人_用冰箱就能掌控全屋家电?云米21Face 428L确实可以...
现代年轻人都很懂时尚科技,他们都很懂利用智能手机来智能扩展更多的玩法.然而对于"宅"在家中时,掌控全屋家电已经无需拿起手机那么麻烦,只需面对着云米21Face互联网冰箱 428L, ...
- 深度强化学习的前景:帮助机器掌控复杂性
作者:数据实战派 来源:数据实战派 深度强化学习,即机器通过测试其行为后果来学习的方法,是人工智能最有前途和影响力的领域之一.它将深度神经网络与强化学习结合在一起,可以通过训练实现多个步骤的目标. 它 ...
- 技术总监的反思录:我是如何失去团队掌控的?
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者 | zer0black 来源 | https://w ...
- mpython掌控板作品_mPython掌控板Easy-IoT物联
[原由] 10月底,在做一个项目时用SioT物联掌控,发现了一个小问题,总是不稳定. 以前也用过SIoT和掌控板来做物联,没有感觉到问题啊,难道在测试时总是通了就好,没有做稳定性测试么? 和谢老师进行 ...
- 趋势科技全球首席安全官ED:人类迈向智能社会进程中不能失去掌控力
如果你还对<速度与激情8>中大反派塞弗控制几百上千辆自动驾驶汽车演绎"僵尸车"."汽车雨"的街头较量的画面感到印象深刻,那么你总该相信这并非完全杜撰 ...
- 青源LIVE第29期|清华叉院高阳:使用1/500数据掌控Atari游戏-EfficientZero算法详解
当前强化学习已在许多应用中取得了巨大成功.但样本效率仍是强化学习中一个重大挑战,重要的方法需要数百万(甚至数十亿)的环境步骤来训练.虽然,当前在基于图像的样本高效RL算法方面取得了重大进展:但是,在A ...
- 一文读懂深度学习:这个AI核心技术被美国掌控,很危险
2019-11-28 18:39:22 "中国有多少数学家投入到人工智能的基础算法研究中?" 今年4月底,中国工程院院士徐匡迪等多位院士的发声,直击我国在算法这一核心技术上的缺失, ...
- 多巴胺如何驱使我们克服复杂情况、逆境、情绪, 让我们掌控周遭的环境的
来源:本文摘自<贪婪的多巴胺> 仅仅是"想要"很少能让你得到任何东西.你必须弄清楚如何获得它,以及它是否值得拥有.事实上,如果我们做事时不考虑怎么做和下一步做什么,失败 ...
最新文章
- Android10.0 Binder通信原理(五)-Binder驱动分析
- 《大话数据结构》读书笔记-查找
- 转]Window, Linux动态链接库的分析对比
- 百度ERNIE新突破,登顶中文医疗信息处理权威榜单CBLUE冠军
- SMT精密电阻对照表
- Ubuntu18.04.1系统安装mmdetection(含torch、torchvision、mmcv-full)
- 解决AutoCAD2010安装完毕后闪退问题
- 高门槛的动作捕捉技术,真的会成为VR行业灾难的缔造者吗?
- 【keras】有关loss function的定义-返回的是`矩阵`还是`标量`
- Mybatis3.5.4官网下载
- 省二级计算机考试VB题库,2015河南省全国计算机等级考试二级笔试试卷VB考试题库...
- 浙大中控T9100系统在压缩机上的应用
- 新手学python笔记--3--爬取天天基金数据
- It was possible to detect the usage of the deprecated TLSv1.0 and/or TLSv1.1 protocol on this system
- 到底什么是阿里味?能否在不加入阿里的时候可以体验一下
- STL容器底层数据结构
- vue使用echarts图表自适应的几种解决方案
- linux下mysql(rpm)安装使用手册
- numeric类型对应java的类型
- ✨【Code皮皮虾】一次通过99.90%,思路详解【找到需要补充粉笔的学生编号】