精华内容
下载资源
问答
  • dbeaver创建er
    千次阅读
    2021-01-27 16:45:24

     

     

    er图的关系存储在哪里?

    hive外键存储位置:

    mysql外键存储位置

     

     

    er图(数据库关系图)的关系存储在哪里?

     

    外键:

    是一个(或数个)指向另外一个表格主键的栏位。外来键的目的是确定资料的参考完整性(referential integrity)

     

    er图包含表之间的关系 但是有的数据库不支持存储 说白了就是使用外键存储关系 比如mysql设置好外键 dbeaver就能自动根据外键关系画出er图,但是hive这种不支持主键,不支持外键的数据库,就不能通过数据库自己来存储er图,所以hive数据库的er图是存储再客户端的,也就是你安装dbeaver的电脑上.

    hive外键存储位置:

    就是存储在你的win电脑

     

    mysql外键存储位置

    更多相关内容
  • 2048是一款单人bbs门游戏,其目标是在网格上滑动图块以将它们组合起来并创建数字为2048的图块注意:如果您运行Synchronet,则可能需要查看 。 基于 ,基于 ,在概念上类似于 。 ANSI木板和瓷砖是经许可借用的。 ...
  • 2048是一个匹配游戏,您的工作是将匹配的图块合并以创建更大编号的图块。 如果再也没有其他动作,并且棋盘已满,则游戏结束。 如果您想使用神话般的2048磁贴,则必须进行战略性思考并仔细计划您的移动。 要了解有关...
  • 瓦工 作者: 执照: 创建地理和非地理地图图块 的tiler包提供用于创建... 创建图块而无需直接在其他软件中进行编码,直接与Python交互或在命令行进行调用; 使R用户可以在熟悉的R环境中舒适地生活。 为希望创建非标准
  • 是用于从和其他数据源创建Mapbox矢量图块的工具包。 该项目已获得。 Baremaps受到启发,但它具有其他功能,例如: 与Java 8中引入的并行处理数据 使用Postgresql的更快地导入数据 使用即时创建Postgis几何 创建并...
  • 创建Ceph存储 目标 使用Ceph集群的存储功能,实现以下目标创建块存储镜像 客户端映射镜像 删除镜像 步骤 实现此案例需要按照如下步骤进行。 步骤一:创建镜像 1)查看存储池,默认存储池名称为rbd。 [root@...

    创建Ceph块存储

    目标
    使用Ceph集群的块存储功能,实现以下目标:

    创建块存储镜像
    客户端映射镜像
    删除镜像
    步骤
    实现此案例需要按照如下步骤进行。

    步骤一:创建镜像

    1)查看存储池,默认存储池名称为rbd。

    [root@node1 ~]# ceph osd lspools
    0 rbd,
    #查看结果显示,共享池的名称为rbd,这个共享池的编号为0,英语词汇:pool(池塘、水塘)
    

    2)创建镜像、查看镜像

    [root@node1 ~]# rbd create demo-image --image-feature  layering --size 10G
    #创建demo-image镜像,这里的demo-image创建的镜像名称,名称可以为任意字符。
    #size可以指定镜像大小
    [root@node1 ~]# rbd create rbd/jacob  --image-feature  layering --size 10G
    #在rbd池中创建名称为jacob的镜像(rbd/jacob),镜像名称可以任意
    #--image-feature参数指定我们创建的镜像有哪些功能,layering是开启COW功能。
    

    #提示:ceph镜像支持很多功能,但很多是操作系统不支持的,我们只开启layering。

    [root@node1 ~]# rbd list                    #列出所有镜像
    [root@node1 ~]# rbd info demo-image        #查看demo-image这个镜像的详细信息
    rbd image 'demo-image':
    size 10240 MB in 2560 objects
    order 22 (4096 kB objects)
    block_name_prefix: rbd_data.d3aa2ae8944a
    format: 2
    features: layering
    

    步骤二:动态调整

    1)扩容容量

    [root@node1 ~]# rbd resize --size 15G jacob             
    #调整jacob镜像的大小,jacob是镜像的名称,size指定扩容到15G
    [root@node1 ~]# rbd info jacob
    

    2)缩小容量

    [root@node1 ~]# rbd resize --size 7G jacob --allow-shrink
    #英文词汇:allow(允许),shrink(缩小)
    [root@node1 ~]# rbd info jacob
    #查看jacob这个镜像的详细信息(jacob是前面创建的镜像)
    

    步骤三:通过KRBD访问

    Linux内核可用直接访问Ceph块存储,KVM可用借助于librbd访问Ceph块存储。

    客户端访问结构如图所示。

    在这里插入图片描述

    1)客户端通过KRBD访问

    #客户端需要安装ceph-common软件包
    #拷贝配置文件(否则不知道集群在哪)
    #拷贝连接密钥(否则无连接权限)
    [root@client ~]# yum -y  install ceph-common
    [root@client ~]# scp 192.168.4.11:/etc/ceph/ceph.conf  /etc/ceph/
    [root@client ~]# scp 192.168.4.11:/etc/ceph/ceph.client.admin.keyring \
    /etc/ceph/
    [root@client ~]# rbd map  jacob          #客户端访问映射服务器的jacob共享镜像
    [root@client ~]#  lsblk                   #查看结果(会多一块磁盘)
    [root@client ~]# rbd showmapped          #查看磁盘名和共享镜像名称的对应关系
    id pool image snap device    
    0  rbd  jacob -    /dev/rbd0
    
    1. 客户端格式化、挂载分区

      [root@client ~]# mkfs.xfs /dev/rbd0                     #格式化,格式为xfs
      [root@client ~]# mount /dev/rbd0 /mnt/                  #挂载(可以挂载到任意目录)
      [root@client ~]# echo "test" > /mnt/test.txt           #写入数据
      

    步骤四:删除镜像

    1) 客户端撤销磁盘映射

    [root@client ~]# umount /mnt                      #卸载
    [root@client ~]# rbd showmapped                  #查看磁盘名和共享镜像名称的对应关系
    id pool image        snap device    
    0  rbd  jacob        -    /dev/rbd0
    [root@client ~]# rbd unmap /dev/rbd0            #撤销磁盘映射
    

    附加信息:Ceph操作思路(知识总结)

    一、准备工作:

    IP,主机名,hosts解析,ssh密钥,时间同步,yum源,防火墙,selinux

    二、部署ceph:

    1.安装软件

    ceph-deploy(脚本)
    ceph-mon ceph-osd ceph-mds ceph-radosgw(集群)
    2.修改配置启动服务mon

    mkdir 目录;cd 目录
    ceph-deploy new node1 node2 node3 (生成配置文件)
    ceph-deploy mon create-initial (拷贝配置文件并启动mon服务)
    3.启动osd服务共享硬盘

    ceph-deploy disk zap 主机名:磁盘名 … …
    ceph-deploy osd create 主机名:磁盘 … …
    三、使用Ceph的思路:

    1.块共享

    服务器: rbd create 创建一个共享镜像
    客户端: 安装cpeh-common; cp 配置文件和密钥
    rbd map | rbd unmap
    附加知识(如何删除某个OSD,下面的假设是删除osd.4)

    ceph osd tree
    ceph osd out osd.4
    ceph osd tree
    ceph -s
    ceph osd crush remove osd.4
    ceph auth del osd.4
    ceph -s
    ceph osd rm osd.4
    最后要找到对应的主机,umount把osd.4对应的磁盘卸载

    展开全文
  • 游戏的目的是在网格上滑动编号的图块以将其组合以创建数字为2048的图块。 但是,达到目标后就可以继续玩游戏,创建更大数量的图块。 它最初是在一个周末用JavaScript和CSS编写的,并于2014年3月9日作为受MIT许可的...
  • 以基本为单位,进行运算上的推导优化。堪称妙!原来编译器这么强大!

    本次笔记内容:
    8-1 流图
    8-2 常用代码优化方法一
    8-3 常用代码优化方案二
    8-4 基本快的优化

    本节课幻灯片,见于我的 GitHub 仓库:第16讲 代码优化_1.pdf

    流图

    基本块(Basic Block)

    基本块是满足下列条件的最大连续三地址指令序列:

    • 控制流只能从基本块的第一个指令进入该块。也就是说,没有跳转到基本块中间或末尾指令的转移指令
    • 除了基本块的最后一个指令,控制流在离开基本块之前不会跳转或者停机

    基本块划分算法

    输入:

    • 三地址指令序列

    输出:

    • 输入序列对应的基本块列表,其中每个指令恰好被分配给一个基本块

    方法:

    • 首先,确定指令序列中哪些指令是首指令(leaders),即某个基本块的第一个指令
    1. 指令序列的第一个三地址指令是一个首指令
    2. 任意一个条件或无条件转移指令的目标指令是一个首指令
    3. 紧跟在一个条件或无条件转移指令之后的指令是一个首指令
    • 然后,每个首指令对应的基本块包括了从它自己开始,直到下一个首指令(不含)或者指令序列结尾之间的所有指令


    如上是快速排序法的部分源代码。

    根据跳转指令找首指令(如跳转指令后的一条指令)。

    流图(Flow Graphs)

    • 流图的结点是一些基本块
    • 从基本块B到基本块C之间有一条当且仅当基本块C的第一个指令可能紧跟在B的最后一条指令之后执行

    此时称B是C的前驱(predecessor) ,C是B的后继(successor)。

    有两种方式可以确认这样的边:

    • 有一个从B的结尾跳转到C的开头的条件或无条件跳转语句
    • 按照原来的三地址语句序列中的顺序,C紧跟在之B后,且B的结尾不存在无条件跳转语句



    感觉像是描述各个运算部分的关系。

    常用的代码优化方法(1)

    优化的分类

    • 机器无关优化:针对中间代码
    • 机器相关优化:针对目标代码
    • 局部代码优化:单个基本块范围内的优化
    • 全局代码优化:面向多个基本块的优化

    常用的优化方法

    • 删除公共子表达式
    • 删除无用代码
    • 常量合并
    • 代码移动
    • 强度削弱
    • 删除归纳变量

    ①删除公共子表达式

    公共子表达式:

    • 如果表达式x op y先前已被计算过,并且从先前的计算到现在,x op y中变量的值没有改变,那么x op y的这次出现就称为公共子表达式(common subexpression)


    将 B3 重构如黄色区域。


    由 B3 “逆流而上”,发现 4 ∗ i 4*i 4i 没有被修改过,则其是一个公共子表达式。

    进行了再次的画家如上。

    发现 a [ t 2 ] a[t_2] a[t2] a [ t 4 ] a[t_4] a[t4] 也是公共子表达式。




    a [ t 1 ] a[t_1] a[t1]能否作为公共子表达式?

    a [ t 1 ] a[t_1] a[t1]作为公共子表达式是不稳妥的,因为控制离开 B 1 B_1 B1进入 B 6 B_6 B6之前可能进入 B 5 B_5 B5,而 B 5 B_5 B5有对 a a a的赋值。



    关键问题:如何自动识别公共子表达式?

    会在后面的课程详细介绍。

    常用的代码优化方法(2)

    ②删除无用代码

    复制传播:常用的公共子表达式消除算法和其它一些优化算法会引入一些复制语句(形如x=y的赋值语句)


    复制传播:在复制语句x= y之后尽可能地用y代替x。

    无用代码(死代码Dead-Code):其计算结果永远不会被使用的语句。


    程序员不大可能有意引入无用代码,无用代码通常是因为前面执行过的某些转换而造成的。

    如何自动识别无用代码?

    也将在后文详细介绍。

    如上,通过删除公共子表达式删除无用代码,将 B5 与 B6 简化了不少。

    ③常量合并(Constant Folding)

    如果在编译时刻推导出一个表达式的值是常量,就可以使用该常量来替代这个表达式。该技术被称为常量合并

    ④代码移动(Code Motion)

    这个转换处理的是那些不管循环执行多少次都得到相同结果的表达式(即循环不变计算,loop-invariant computation) ,在进入循环之前就对它们求值。


    如何自动识别循环不变计算?

    循环不变计算的相对性

    对于多重嵌套的循环,循环不变计算是相对于某个循环而言的。可能对于更加外层的循环,它就不是循环不变计算。

    ⑤强度削弱(Strength Reduction)

    用较快的操作代替较慢的操作,如用加代替乘。

    循环中的强度削弱

    对于一个变量x ,如果存在一个正的或负的常数c使得每次x被赋值时它的值总增加c ,那么x就称为归纳变量(Induction Variable)。


    归纳变量可以通过在每次循环迭代中进行一次简单的增量运算(加法减法)来计算。

    ⑥删除归纳变量

    在沿着循环运行时,如果有一组归纳变量的值的变化保持步调一致,常常可以将这组变量删除为只剩一个。


    如上, i i i j j j 都无用了。

    基本块的优化

    很多重要的局部优化技术首先把一个基本块转换成为一个无环有向图(directed acyclic graph,DAG)。

    基本块的 DAG 表示

    基本块中的每个语句s都对应一个内部结点N:

    • 结点N的标号是s中的运算符;同时还有一个定值变量表被关联到N ,表示s是在此基本块内最晚对表中变量进行定值的语句
    • N的子结点是基本块中在s之前、最后一个对s所使用的运算分量进行定值的语句对应的结点。如果s的某个运算分量在基本块内没有在s之前被定值,则这个运算分量对应的子结点就是代表该运算分量初始值的叶结点(为区别起见,叶节点的定值变量表中的变量加上下脚标0)
    • 在为语句x=y+z构造结点N的时候,如果x已经在某结点M的定值变量表中,则从M的定值变量表中删除变量x

    例,有基本块:

    a = b + c
    b = a - d
    c = b + c
    d = a - d
    


    对于形如 x=y+z 的三地址指令,如果已经有一个结点表示 y+z,就不往 DAG 中增加新的结点,而是给已经存在的结点附加定值变量x。

    基于基本块的 DAG 删除无用代码

    从一个DAG上删除所有没有附加活跃变量(活跃变量是指其值可能会在以后被使用的变量)的根结点(即没有父结点的结点) 。重复应用这样的处理过程就可以从DAG中消除所有对应于无用代码的结点。

    数组元素赋值指令的表示


    如上,因为有可能出现 i = j i=j i=j ,因此不能轻易把 a [ i ] a[i] a[i] 算作公共子表达式。

    • 对于形如a[j] = y的三地址指令,创建一个运算符为“[]=”的结点,这个结点有3个子结点,分别表示a、j和y
    • 该结点没有定值变量表
    • 该结点的创建将杀死所有已经建立的、其值依赖于a的结点
    • 一个被杀死的结点不能再获得任何定值变量,也就是说,它不可能成为一个公共子表达式

    根据基本块的DAG可以获得一些非常有用的信息

    • 确定哪些变量的值在该基本块中赋值前被引用过:在DAG中创建了叶结点的那些变量
    • 确定哪些语句计算的值可以在基本块外被引用:在DAG构造过程中为语句s(该语句为变量x定值)创建的节点N,在DAG构造结束时x仍然是N的定值变量

    从 DAG 到基本块的重组

    对每个具有若干定值变量的节点,构造一个三地址语句来计算其中某个变量的值。

    倾向于把计算得到的结果赋给一个在基本块出口处活跃的变量(如果没有全局活跃变量的信息作为依据,就要假设所有变量都在基本块出口处活跃,但是不包含编译器为处理表达式而生成的临时变量)。

    如果结点有多个附加的活跃变量,就必须引入复制语句,以便给每一个变量都赋予正确的值。


    构建 DAG 如右边。常量直接标记出来。

    最终,根据 DAG 得到优化后的基本块如下:

    D = A + C
    E = A * C
    F = E + D
    L = 15 + F
    
    展开全文
  • 一、块设备框架以下内容摘自http://blog.csdn.net/jianchi88/article/details/7212370.1、块设备框架图块设备框架图2、说明 各个层次说明由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层...

    一、块设备框架

    以下内容摘自http://blog.csdn.net/jianchi88/article/details/7212370.

    1、块设备框架图

    块设备框架图

    avatar

    2、说明

    • 各个层次说明

      • 由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。
      • 在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在IO调度层中用request结构体描述。
      • 但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求(request结构体)添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
      • Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。
    • 整体数据流程说明
      文件系统——》通用块层——》IO调度层——》块设备驱动层

      • 文件系统会向通用块层发起块读写请求
      • 通用块层将读写请求封装成bio结构体(block io),下发到IO调度层
      • IO调度层将bio封装成request结构体,并将request添加到对应块设备(gendisk)的请求队列(request_queue)中,并做一些预处理。
      • IO调度层对请求队列(request_queue)中的request进行预处理之后,调用请求队列(request_queue)的回调函数来对队列中的IO(request)进行处理
      • 而请求队列(request_queue)以及request处理回调函数则是由块设备驱动层提供,也就是块设备驱动层会从请求队列(request_queue)提取request并且进行实质性的处理。
    • 各个层次的请求(IO)以及请求队列对应的结构体

      • 通用块层:struct bio
      • IO调度层:struct request(封装了struct bio)、struct request_queue
      • 块设备驱动层:
        各类块设备驱动层对于IO请求的封装是不一样的。以sd card的mmc块设备驱动层为例:
        struct mmc_queue_req(封装了struct request)、struct mmc_queue(封装了struct request_queue)。

    3、mmc块设备驱动层

    块设备驱动有很多个种类,对于sd card来说,使用的是mmc块设备驱动层。
    这部分内容可以参考《[mmc subsystem] 概念与框架
    mmc块设备驱动层已经属于mmc subsystem结构的一部分。其框架图如下:

    avatar
    (上图摘自蜗窝科技http://www.wowotech.net/comm/mmc_framework_arch.html

    • 其主要分成三个层次:
      • mmc card drivers层(mmc_blk层)
        向上:为mmc card(如sd card)实现对应的request_queue以及gendisk并注册,生成的对应的块设备文件。
        请求处理:对上层传下来的request进行提取处理
        向下:向mmc core层提交mmc请求
      • mmc core层(只提取部分内容)
        可以参考《[mmc subsystem] mmc core(第一章)——概述
        向上:为mmc card drivers提供注册mmc driver的接口。为mmc card drivers提供发起mmc请求的接口。
        请求处理:异步mmc请求的处理
        向下:向mmc host层提交mmc请求
      • mmc host层(只提取部分内容)
        可以参考《[mmc subsystem] host(第一章)——概述
        对mmc core下发的mmc请求进行处理,然后通过mmc总线发起实际的通讯内容。

    5、总结

    综上,对于sd card而言,整个数据流是:
    文件系统——》通用块层——》IO调度层——》mmc_blk层——》mmc core层——》mmc host层——》mmc硬件总线——》sd card。
    而这里我们要学习的就是mmc_blk层。
    对应代码是drivers/mmc/card/block.c、driver/mmc/card/queue.c
    同时,我们也知道了mmc_blk层的核心内容是实现块设备,为了实现块设备,实现块设备的核心就是要实现对应的gendisk以及request_queue。后续就是围绕这个内容对mmc_blk设备驱动进行展开。

    二、mmc_blk实现块设备核心框架

    1、实现块设备的驱动框架

    通过第一节的内容,可以知道实现块设备主要是实现如下内容

    • 创建struct request_queue
      请求队列,IO调度层会把IO请求(struct request)往对应块设备的这个结构体里面挂,然后调用request_queue里面的回调函数对request进行处理。
      可以调用blk_init_queue来创建一个request_queue。
      创建方法eg:blk_init_queue(mmc_request_fn, lock),其中,mmc_request_fn则是从request_queue提取request并进行处理的回调函数。

    • 创建struct gendisk
      struct gendisk用来代表一个独立的磁盘设备或者分区。一个块设备对应一个gendisk。块设备驱动通过创建对应的gendisk并注册来产生对应的块设备节点。
      可以调用alloc_disk来分配一个gendisk。
      分配方法eg:alloc_disk(perdev_minors),perdev_minors表示分配给每个块设备的从设备号数量

    • 初始化struct gendisk
      主要初始化如下内容:

      • struct gendisk->major,块设备的主设备号,对于mmcblk块设备来说,其主设备号是MMC_BLOCK_MAJOR,179
      • struct gendisk->first_minor,分配的第一个从设备号
      • struct gendisk->fops,块设备的操作函数,对于mmcblk来说,是mmc_bdops
      • struct gendisk->private_data,私有数据指针,一般关联到对应块设备驱动的数据结构体
      • struct gendisk->driverfs_dev,父sys节点
      • struct gendisk->flags
      • struct gendisk->disk_name,块设备名
    • 关联struct gendisk和struct request_queue
      因为IO调度层里面的request_queue一般有不止一个。当数据从通用块层下来的时候,IO调度层只知道要写到哪个gendisk,然后根据gendisk里面获取到对应的request_queue,然后再request往request_queue里面放。
      所以就要求gendisk必须和request_queue关联起来。
      关联方法:struct gendisk->queuerequest_queue.

    • 注册块设备
      不管前面的流程怎么样,最终的目的都是注册一个块设备到系统中。
      可以通过调用add_disk(gendisk)来注册块设备。

    2、mmc_blk实现块设备的驱动框架补充

    核心框架就是上述“实现块设备的驱动框架”的部分。

    以下要特别注意,方便理解代码。

    • 根据mmc_blk的实现方式,有如下注意点:

      • 构造mmc_queue_req作为一个MMC IO请求,把从request_queue提取request封装到这里面来。
      • 构造mmc_queue作为块设备的请求队列,把request_queue封装到mmc_queue中。同时存储了当前正在处理以及上一次正在处理的MMC IO请求mmc_queue_req。
      • 构造mmc_blk_data作为块设备的私有数据,同时把mmc块设备请求对应mmc_queue以及块设备gendisk存储到mmc_blk_data中。
      • 同时mmc_blk_data也存放了mmc_blk驱动相关的部分内容。
    • 因此mmc_blk实现块设备的整体流程如下

      • 创建struct mmc_blk_data
      • 初始化mmc_blk_data
      • 初始化struct mmc_blk_data->mmc_queue
      • 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
      • 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
      • 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
      • 注册mmc_blk_data->gendisk到系统中 (注册块设备)

    三、mmc_blk的设备驱动模型实现

    对应代码:drivers/mmc/card/block.c

    1、mmc bus结构

    注意,这里指的是软件结构,而不是指硬件结构。
    建议先参考《[mmc subsystem] mmc core(第三章)——bus模块说明
    mmc结构体如下图所示

    avatar
    (上图摘自蜗窝科技http://www.wowotech.net/comm/mmc_framework_arch.html

    从硬件上来看,每一个mmc host对应一条实际的mmc总线。
    但是mmc subsystem只存在一条虚拟的mmc bus。并且mmc host并不会作为这个设备驱动总线模型的一个部分。
    相应的:
    * 在mmc bus上挂载的device是由mmc core根据实际mmc设备抽象出来的card设备。
    例如在sd.c中,mmc_attach_sd会构造mmc_card,并且调用mmc_add_card(struct mmc_card *card)将mmc_card挂在了mmc_bus上。
    而mmc_card就是mmc_bus上的device。

    • 在mmc bus上挂载的driver是在card目录下实现的card driver,用于驱动虚拟card设备、对接其他subsystem,实现其实际的功能。
      例如在block.c中,mmc_blk_init会构造一个mmc_driver,并且调用mmc_register_driver(struct mmc_driver *drv)将mmc_driver挂在了mmc_bus上。
      而mmc_driver就是mmc_bus上driver。

    根据mmc_bus的匹配规则(mmc_bus_match),只要有mmc_card注册到mmc_bus上,就会匹配所有mmc_driver,也就是所有mmc_driver的probe都会执行。
    然后mmc_driver就开始为mmc_card实现对应的功能了,比如mmc_blk这个mmc_driver就是为mmc_card实现块设备的功能。

    2、mmc_blk驱动模型结构实现

    • 根据上述,mmc_blk驱动模型结构要包括如下两点

      • 构造对应的struct mmc_driver
      • 调用mmc_register_driver(struct mmc_driver *drv)将其注册到mmc_bus上
    • 代码如下

    static struct mmc_driver mmc_driver = {
        .drv        = {
            .name   = "mmcblk",
        },
        .probe      = mmc_blk_probe,
        .remove     = mmc_blk_remove,
        .suspend    = mmc_blk_suspend,
        .resume     = mmc_blk_resume,
        .shutdown   = mmc_blk_shutdown,
    };
    
    static int __init mmc_blk_init(void)
    {
        res = mmc_register_driver(&mmc_driver);   
             // mmc_driver注册到mmc_bus上,当有mmc_card注册到mmc_bus上时,其probe方法就会执行。
    }
    
    module_init(mmc_blk_init);

    随后可以查询到mmkblk driver的节点为/sys/bus/mmc/drivers/mmcblk,内容如下

    root@:/sys/bus/mmc/drivers/mmcblk # ls
    ls
    bind
    mmc0:0001    // 通过该driver probe成功的mmc_card,这里是一个emmc设备
    mmc1:e624    // 通过该driver probe成功的mmc_card,这里是一个sd设备
    uevent
    unbind

    mmc_blk_probe就是为mmc_card(比如sd card、emmc)实现存储设备的功能的核心,也就是将mmc_card实现出对应块设备的实现所在,也就是这里要学习的入口函数。后续会从mmc_blk_probe展开对“mmc_blk层为sd card创建块设备流程”的代码说明。

    四、mmc_blk层为sd card创建块设备流程(mmc_blk_probe)

    0、节点

    • 块设备的dev节点
    root@:/dev/block # ls -l | grep mmcblk
    brw------- root     root     179,   0 1970-01-03 03:46 mmcblk0
    brw------- root     root     179,   1 1970-01-03 03:46 mmcblk0p1
    brw------- root     root     179,  10 1970-01-03 03:46 mmcblk0p10
    brw------- root     root     179,  11 1970-01-03 03:46 mmcblk0p11
    brw------- root     root     179,  12 1970-01-03 03:48 mmcblk0p12
    brw------- root     root     179,  13 1970-01-03 03:46 mmcblk0p13
    ...
    brw------- root     root     179,  32 1970-01-03 03:46 mmcblk0rpmb
    brw------- root     root     179,  64 1970-01-03 03:46 mmcblk1
    brw------- root     root     179,  65 1970-01-03 03:46 mmcblk1p1

    可以观察到设备都是以mmcblk为前缀,并且主设备号为179,从设备号则是依次递增。

    • 块设备的sys节点
    root@msm8916_64:/sys/bus/mmc/devices/mmc1:e624/block/mmcblk1 # ls
    alignment_offset    bdi   bkops_check_threshold   capability
    dev    device   discard_alignment   ext_range   force_ro
    holders    inflight   mmcblk1p1   no_pack_for_random   num_wr_reqs_to_start_packing
    power    queue   range   removable   ro
    ...

    1、整体核心流程说明

    mmc_blk层为sd card创建块设备流程是在mmc_blk_probe中实现的。
    复习一下前面的内容,mmc_blk_probe主要做如下事情
    1. 创建struct mmc_blk_data
    2. 初始化mmc_blk_data
    3. 初始化struct mmc_blk_data->mmc_queue
    4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
    5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
    6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
    7. 注册mmc_blk_data->gendisk到系统中 (注册块设备)

    后面看代码的过程中可以发现整个probe过程都是围绕上述工作进行的。

    2、mmc_blk_probe

    只考虑sd card的情况,分析和sd card相关的代码。

    static int mmc_blk_probe(struct mmc_card *card)
    {
        struct mmc_blk_data *md, *part_md;
        char cap_str[10];
    
        /*
         * Check that the card supports the command class(es) we need.
         */
    /** 判断card是不是一个block设备 **/
        if (!(card->csd.cmdclass & CCC_BLOCK_READ))   
            return -ENODEV;
            // 前面说过了,只要是mmc_card(包括SDIO card)被注册到mmc_bus上,那么所有mmc_bus上的mmc_driver都会被匹配到
            // 而mmc_blk只使用于存储设备(emmc、sd card、mmc card),并不能驱动于SDIO card
            // 因此,这里根据是否支持块读写属性判断card是不是一个存储设备,如果不是的话,说明并不能使用mmc_blk这个mmc_driver来驱动mmc_card.
    
    /** 为mmc_card分配和设置mmc_blk_data **/
    /** 在mmc_blk_alloc中会去分配和设置mmc_queue、request_queue、gendisk **/
        md = mmc_blk_alloc(card);   
          // 负责第1小节中的步骤1-6的工作,所以也是分析“mmc_blk层为sd card创建块设备流程”的核心函数
            // 1. 创建struct mmc_blk_data
            // 2. 初始化mmc_blk_data
            // 3. 初始化struct mmc_blk_data->mmc_queue
            // 4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
            // 5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
            // 6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)
        if (IS_ERR(md))
            return PTR_ERR(md);
    
    /** 一个存储设备上(例如emmc)上可能有多个物理分区,这里用于为这些物理分区(例如rpmb分区)分配和设置mmc_blk_data **/
    /** SD card上只有一个物理分区,所以我们这里不care,后面学习emmc的时候再说明 **/
        if (mmc_blk_alloc_parts(card, md))
            goto out;
    
    /** 关联mmc_card和mmc_blk_data **/
        mmc_set_drvdata(card, md);
        mmc_fixup_device(card, blk_fixups);
    
    /** 将mmc_blk构造的gendisk注册到系统中,生成对应的块设备 **/
        if (mmc_add_disk(md))
            // 负责第1小节中的步骤7的工作
            // 注册mmc_blk_data->gendisk到系统中 (注册块设备)
            goto out;
    
    /** 将其他物理分区的gendisk注册到系统中,生成对应的块设备。 **/
    /** sd card 上只有一个分区,所以这里我们同样不关心 **/
        list_for_each_entry(part_md, &md->part, part) {
            if (mmc_add_disk(part_md))
                goto out;
        }
        return 0;
    }

    通过上述代码,可以知道对于“mmc_blk层为sd card创建块设备流程”来说,有两个核心代码来实现。

    • mmc_blk_alloc
    • mmc_add_disk

    后续主要分析这两个函数的代码。

    3、mmc_blk_alloc

    需要实现如下内容:
    1. 创建struct mmc_blk_data
    2. 初始化mmc_blk_data
    3. 初始化struct mmc_blk_data->mmc_queue
    4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)
    5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)
    6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)

    代码如下,去掉一些无关内容:

    static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
    {
        sector_t size;
        struct mmc_blk_data *md;
    
    /** 以下先获取card容量,以扇区为单位 **/
        if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
            size = card->ext_csd.sectors;      // 对于emmc设备来说,其容量是从ext_csd寄存器的sectors域获取
        } else {
            size = card->csd.capacity << (card->csd.read_blkbits - 9);
                    // 对于sd card来说,其容量是从csd的capacity域获取的
                    // 计算方法memory capacity = (C_SIZE+1) * 512K byte  
                    // 可以参考SD 3.0协议中的5.3.3节
        }
    
    /** 调用mmc_blk_alloc_req来实现前面所说的工作 **/
        md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL,
                        MMC_BLK_DATA_AREA_MAIN);
            // 参数说明如下:
            // card:对应的mmc设备,mmc_card
            // card->dev:作为块设备的sys节点的父设备,/sys/bus/mmc/devices/mmc1:e624/block/mmcblk1,这里的mmc1:e624就是card->dev
            // size:块设备的大小,也就是card的容量
            // false:bool default_ro,默认并不作为只读设备
            // NULL:subname,块设备的后缀名,例如mmcblk0rpmb,rpmb就是后缀名
            // MMC_BLK_DATA_AREA_MAIN:分区类型,UDA分区
    
        return md;
    }
    
    /-----------------------------mmc_blk_alloc_req实现------------------------------/
    static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
                              struct device *parent,
                              sector_t size,
                              bool default_ro,
                              const char *subname,
                              int area_type)
    {
        struct mmc_blk_data *md;
        int devidx, ret;
        unsigned int percentage =
            BKOPS_SIZE_PERCENTAGE_TO_QUEUE_DELAYED_WORK;
    
    /** 分配一个mmcblk的从设备号 **/
        devidx = find_first_zero_bit(dev_use, max_devices);
        __set_bit(devidx, dev_use);
    
    /** 以下就是分配mmc_blk_data并进行设置,也是mmc_blk_alloc_req的核心工作 **/
    /* 对应“1. 创建struct mmc_blk_data” */
        md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
    
    /* 以下对应“2. 初始化mmc_blk_data” */
       //---- 设置mmc_blk_data->name_idx
        if (!subname) {
            md->name_idx = find_first_zero_bit(name_use, max_devices);
            __set_bit(md->name_idx, name_use);
        } else
            md->name_idx = ((struct mmc_blk_data *)
                    dev_to_disk(parent)->private_data)->name_idx;
            // 从name_use中分配一个名称的id号,mmcblk+id号会作为完整的块设备的名称
            // 例如mmcblk0的0就是名称的id号,mmcblk1的1就是名称的id号
    
       //---- 设置mmc_blk_data->area_type
        md->area_type = area_type;   // 物理分区类型,SD card只有一个物理分区,对应为MMC_BLK_DATA_AREA_MAIN
    
       //---- 设置mmc_blk_data->read_only 
        md->read_only = mmc_blk_readonly(card);   // 设置只读属性
    
       //---- 分配mmc_blk_data->disk
        md->disk = alloc_disk(perdev_minors);   // 调用alloc_disk分配一个gendisk结构体
    
       //---- 设置mmc_blk_data->lock、part、usage 
        spin_lock_init(&md->lock);   // 给request_queue使用的锁
        INIT_LIST_HEAD(&md->part);   // 挂载其他物理分区的链表
        md->usage = 1;   // 使用计数设置为1
    
       //---- 核心:设置mmc_blk_data->queue,会调用mmc_init_queue来分配和设置mmc_blk_data->queue,在这里面会创建对应的request_queue.
    /* 以下对应“3. 初始化struct mmc_blk_data->mmc_queue” */
    /* 以下对应“4. 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue)” */
        ret = mmc_init_queue(&md->queue, card, &md->lock, subname);   // 后面说明
        md->queue.issue_fn = mmc_blk_issue_rq;   // 将mmc_queue的IO请求(request)下发方法设置为mmc_blk_issue_rq
        md->queue.data = md;   // 关联mmc_queue和mmc_blk_data
    
       //---- 核心:设置mmc_blk_data->disk
    /* 以下对应“5. 分配和初始化struct mmc_blk_data->gendisk(创建struct gendisk & 初始化struct gendisk)” */
            // 分配gendisk放在前面完成了
        md->disk->major = MMC_BLOCK_MAJOR;   // 设置块设备的主设备号为MMC_BLOCK_MAJOR,179
        md->disk->first_minor = devidx * perdev_minors;   // 设置块设备的从设备号
        md->disk->fops = &mmc_bdops;   // 设置操作集
        md->disk->private_data = md;   // 关联gendisk和mmc_blk_data
    /* 以下对应“6. 关联struct mmc_blk_data->gendisk 和 mmc_blk_data->mmc_queue->request_queue (关联struct gendisk和struct request_queue)” */
        md->disk->queue = md->queue.queue;  // 重要,关联gendisk和request_queue!!!
        md->disk->driverfs_dev = parent;
        set_disk_ro(md->disk, md->read_only || default_ro);   // 设置gendisk的只读属性
        md->disk->flags = GENHD_FL_EXT_DEVT;   // 设置gendisk的一些标识
        if (area_type & MMC_BLK_DATA_AREA_RPMB)
            md->disk->flags |= GENHD_FL_NO_PART_SCAN;
        snprintf(md->disk->disk_name, sizeof(md->disk->disk_name),   // 设置块设备的设备名,例如mmcblk0、mmcblk1,而mmcblk0rpmb中的rpmb则是指subname
             "mmcblk%d%s", md->name_idx, subname ? subname : "");
        // 设置逻辑块大小,这里先不关心
        if (mmc_card_mmc(card))
            blk_queue_logical_block_size(md->queue.queue, card->ext_csd.data_sector_size);
        else
            blk_queue_logical_block_size(md->queue.queue, 512);
        set_capacity(md->disk, size);   // 设置gendisk的容量,size是以扇区为单位。
        return md;
    }
    
    /-----------------------------mmc_init_queue实现------------------------------/
    // 两个重要的功能
    // 1、创建request_queue并封装到mmc_queue中
    // 2、创建IO请求(request)的提取和处理进程
    int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
               spinlock_t *lock, const char *subname)
    {
        struct mmc_host *host = card->host;
        u64 limit = BLK_BOUNCE_HIGH;
        int ret;
        struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
        struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
    
        if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
            limit = *mmc_dev(host)->dma_mask;
    
        mq->card = card;
    /** 创建struct mmc_blk_data->mmc_queue->request_queue(创建struct request_queue) **/
        mq->queue = blk_init_queue(mmc_request_fn, lock);   // request的处理回调函数设置为mmc_request_fn
        if (!mq->queue)
            return -ENOMEM;
    
        if ((host->caps2 & MMC_CAP2_STOP_REQUEST) &&
                host->ops->stop_request &&
                mq->card->ext_csd.hpi_en)
            blk_urgent_request(mq->queue, mmc_urgent_request);
    
        mq->mqrq_cur = mqrq_cur;
        mq->mqrq_prev = mqrq_prev;
        mq->queue->queuedata = mq;
        mq->num_wr_reqs_to_start_packing =
            min_t(int, (int)card->ext_csd.max_packed_writes,
                 DEFAULT_NUM_REQS_TO_START_PACK);
    
        blk_queue_prep_rq(mq->queue, mmc_prep_request);
        queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
        if (mmc_can_erase(card))
            mmc_queue_setup_discard(mq->queue, card);
    
    success:
        sema_init(&mq->thread_sem, 1);
    
    //---- 创建IO请求(request)的提取和处理进程
        mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", host->index, subname ? subname : "");
    
        if (IS_ERR(mq->thread)) {
            ret = PTR_ERR(mq->thread);
            goto free_bounce_sg;
        }
        return 0;
    }

    另外,重点关注一下以下几个东西。在mmcblk块设备读写流程的时候会用到。

    md->queue.issue_fn = mmc_blk_issue_rq;
    md->disk->queue = md->queue.queue;
    (mmc_blk_data->mmc_queue) mq->queue = blk_init_queue(mmc_request_fn, lock); 
    (mmc_blk_data->mmc_queue) mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", host->index, subname ? subname : "");

    4、mmc_add_disk

    static int mmc_add_disk(struct mmc_blk_data *md)
    {
        int ret;
        struct mmc_card *card = md->queue.card;
    
    /** 注册mmc_blk_data->gendisk到系统中 (注册块设备) **/
        add_disk(md->disk);    // 直接调用add_disk将mmc_blk_data->gendisk注册到系统中
            // 此处略掉很多关于属性的代码
    
        return ret;
    }

    五、补充说明,软件分区对应块设备的创建

    在存储设备上有软件分区,例如emmc上可能有很多个软件分区,而sd card上只有一个软件分区,他们也有自己的块设备节点,如下:

    brw------- root     root     179,   1 1970-01-03 03:46 mmcblk0p1
    brw------- root     root     179,  10 1970-01-03 03:46 mmcblk0p10
    brw------- root     root     179,  11 1970-01-03 03:46 mmcblk0p11
    brw------- root     root     179,  12 1970-01-03 03:48 mmcblk0p12
    brw------- root     root     179,  13 1970-01-03 03:46 mmcblk0p13
    ...
    brw------- root     root     179,  65 1970-01-03 03:46 mmcblk1p1

    这些都是软件分区对应的块设备节点,但是这些块设备节点是在对mmcblk进行add_disk的时候,从mmcblk上识别出来的,由通用块设备层自己完成,而不需要mmcblk层的关心。
    具体代码在kernel/block/partitions/check.c check_partition()中。
    调用流程可以参考如下调用堆栈:

    [    7.442971] ooonebook check_partition check gendisk mmcblk1
    [    7.442977] CPU: 3 PID: 49 Comm: kworker/u8:3 Tainted: G        W    3.10.49-g5e97c06-dirty #64
    [    7.442988] Workqueue: kmmcd mmc_rescan
    [    7.442989] Call trace:
    [    7.442997] [<ffffffc000206b40>] dump_backtrace+0x0/0x270
    [    7.443002] [<ffffffc000206dc0>] show_stack+0x10/0x1c
    [    7.443007] [<ffffffc000c2ad74>] dump_stack+0x1c/0x28
    [    7.443012] [<ffffffc00043c22c>] check_partition+0x34/0x20c      // 在这里识别软件分区
    [    7.443016] [<ffffffc00043bf6c>] rescan_partitions+0x90/0x270
    [    7.443022] [<ffffffc00032ba84>] __blkdev_get+0x13c/0x320
    [    7.443026] [<ffffffc00032be0c>] blkdev_get+0x1a4/0x2c8
    [    7.443031] [<ffffffc000439bb8>] add_disk+0x3b8/0x3e8   // 在这里把mmc_blk层创建的gendisk注册到系统中
    [    7.443036] [<ffffffc00089b3b8>] mmc_add_disk+0x44/0x228
    [    7.443040] [<ffffffc00089ba1c>] mmc_blk_probe+0x224/0x270
    [    7.443044] [<ffffffc00088c9d4>] mmc_bus_probe+0x14/0x20
    [    7.443050] [<ffffffc0005bc7d8>] driver_probe_device+0x160/0x368
    [    7.443055] [<ffffffc0005bca08>] __device_attach+0x28/0x4c
    [    7.443060] [<ffffffc0005baa28>] bus_for_each_drv+0x70/0x90
    [    7.443064] [<ffffffc0005bc5ec>] device_attach+0x68/0x98
    [    7.443069] [<ffffffc0005bb9fc>] bus_probe_device+0x28/0xa0
    [    7.443073] [<ffffffc0005b9d90>] device_add+0x324/0x600
    [    7.443077] [<ffffffc00088d208>] mmc_add_card+0x2a8/0x36c
    [    7.443082] [<ffffffc000893758>] mmc_attach_sd+0x204/0x25c
    [    7.443086] [<ffffffc00088c8cc>] mmc_rescan+0x25c/0x2ec
    [    7.443091] [<ffffffc0002359e0>] process_one_work+0x258/0x3d0
    [    7.443095] [<ffffffc000236ac4>] worker_thread+0x1f0/0x340
    [    7.443100] [<ffffffc00023bc94>] kthread+0xac/0xb8

    这里就没有进行深入的学习了。

    展开全文
  • python-igraph创建网络(

    千次阅读 2021-01-10 03:03:44
    2.连接矩阵创建图,给出邻接矩阵和创建模式 3.非对称定点类型和连接分布创建图 4.产生Atlas图 5.Barabasi图 6.Bruijn图 7.根据度序列产生图 8.ER随机图 9.根据生长模型产生图 10.产生几个Famous图 11.根据森林火灾...
  • 在公众号列表中,长按我的公众号,置顶公众号,就可以随时看到我。读者朋友“*无止*”留言:看完了这个(MDK-ARM)系列的教程,还是没学会如何在一个工程下创建多个目标?我...
  • Java创建柱状及饼状

    万次阅读 2015-11-29 14:50:13
    Java创建图表其实还是很方便的,但是要引入相关的jar包。如下 jfreechart.jar jcommon,jar gnujaxp.jar 其中最主要的是jfreechart.jar。 下面就让我们先看看创建的图标的运行结果吧。 是不是感觉很生动形象,...
  • 如何在线创建数据流(DFD)?

    千次阅读 2019-02-28 17:07:51
    想要创建数据流(DFD)?我们将为您提供有关DFD的介绍,并告诉您如何使用在线DFD软件创建DFD。 什么是数据流? 数据流(DFD)是在软件设计中经常使用的。它直观地表示给定系统中整个过程的数据流。DFD显示将...
  • 重拾图形图像处理 ---- 笔试题

    千次阅读 2020-11-30 17:00:38
    一、填空题(每题1分,共15分) ...4、直方均衡化适用于增强直方呈尖峰 分布的图像。 5、依据图像的保真度,图像压缩可分为无损压缩 和有损压缩 6、图像压缩是建立在图像存在编码冗余 、像素...
  • week2: 1 Chunking—...我们会讨论一下组是如何形成的,以及大家如何利用这些组加深自己对材料的理解与创造,以及组是如何帮我们更好地应付考试的。我们也会讨论对于学习能力方面的一些误解,当大家使用低效...
  • 如何在线创建维恩 (Venn Diagram)?

    千次阅读 2019-02-28 04:10:18
    创建维恩?我们将向您介绍维恩,并让您了解如何使用在线维恩软件创建维恩。 维恩示例(由Visual Paradigm Online创建) [在Visual Paradigm Online中打开和编辑此维恩] 什么是维恩? 维恩,也称...
  • 【Linux】Linux进程的创建与管理

    万次阅读 多人点赞 2018-07-27 19:21:29
    在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建,新创建的进程叫做子进程,而创建子进程的...Linux进程创建的过程示意如下所示:   子进程的创建 在Li...
  • 创建网络?我们将为您提供网络简介,并告诉您如何使用免费的在线网络软件创建网络。 网络软件- Visual Paradigm Online [在Visual Paradigm Online中打开和编辑此网络] 什么是网络? 网络...
  • UML基本构造之十种(二)

    千次阅读 2016-04-19 19:58:21
     这几天一直在学习UML的十种,之前是对UML十种主要概念进行理解,现在对其及其之间的关系进行理解 ... 最开始学习的第一个是用例,用例图主要用于为系统的功能需求建模,它主要描述系统功能,也就是从
  • 可以看完这个就懵了,以下是创建对象的六个步骤: 1、判断是否能在常量池中能找到类符号引用,并检查是否已被加载、解析、初始化(即判断类元信息是否存在) 如果没有则在双亲委派模式下,使用当前类加载器以...
  • RAID创建

    千次阅读 2020-09-22 23:57:38
    简单的说,就是把多儿硬盘绑定成一个磁盘阵列(从操作系统中最直观的看,就是我们不再看到有多儿盘存在,而是做完raid后的一儿盘存在),然后不同的raid等级又有不同的数据冗余、校验的机制,保证数据的可靠性...
  • fork()创建子进程步骤、函数用法及常见考点(内附fork()过程
  • 原文|《Unreal Engine 4 Tutorial...文章目录开始吧绘制方法创建画布材质创建渲染目标显示渲染目标创建笔刷材质 简单来讲,渲染目标(Render Target)就是一种可以在运行时写入的纹理。从引擎的角度讲,渲染目标会存储.
  • 1.1 题目的主要研究内容及预期达到的目标 【设计目的】 (1)理解进程创建相关理论。 (2)掌握进程创建方法。 (3)掌握进程相关数据结构。 【课题描述】 本课题针对操作系统中进程的创建相关理论进行设计。要求...
  • StarUML 序列建模

    千次阅读 2017-12-15 11:37:06
    StarUML 序列建模
  • Access应用实例——图书借阅管理系统

    万次阅读 多人点赞 2019-06-15 20:00:25
    图书借阅管理系统主要实现对图书管工作的信息化管理,本系统实现对图书的基本信息和会员(图书借遇者)的基本信息的登记,保存,统计,和查询等功能,另外还对会员的借阅信息进行了登记,保存,统计并能查询,同时...
  • 直方广泛应用于很多计算机视觉...边缘、色彩、角等直方构成了可以被传递给目标识别分类器的一个通用特征类型。色彩和边缘的直方序列还可以用来识别网络视频是否被复制等。直方是计算机视觉中最经典的工具之一。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 346,084
精华内容 138,433
关键字:

创建图块的主要目的是