回到usb_stor_invoke_transport()中来,540行,还是老套路,又问是不是命令被放弃了,放弃了当然下面的就别执行了.goto Handle_Abort去.
546行,如果有错误,注意正如前面所说,USB_STOR_TRANSPORT_ERROR表示传输本身就是有问题的,比如管道堵塞.而USB_STOR_TRANSPORT_FAILED则只是说明命令传输是没有问题的,就比如你作为场外观众给非常六加一发短信了,然后李咏随机抽到你,给你打电话,电话通了,让你砸金蛋,砸出了金花你就能获得自己想要的奖品,但是问题是你没有砸中,这样你就失去了机会,这属于FAILED,但是另一种更惨的情况是,咏哥给你打电话还赶上你小子把手机关了,那你就只好认倒霉了,这就属于ERROR.对于这种疑似管道堵塞的问题,我们会调用自己写的一个函数us->transport_reset(us),us->transport_reset()其实也是一个指针,我们也是很早以前和us->transport()一起赋的值,对于U盘,我们赋的值是usb_stor_Bulk_reset().所谓reset,就相当于我们重起计算机,每次遇到些什么乱七八糟的问题,我们二话不说,重起机器通常就会发现一切都好了.关于设备reset的冬冬,我们讲完命令的执行这一块之后再来专门讲.暂且不表.对于这种情况,当然我们会设置srb->result为DID_ERROR.然后返回.
554行,看到了吧,这里就判断是不是USB_STOR_TRANSPORT_NO_SENSE了.如果是的,那么返回给scsi的结果是SAM_STAT_CHECK_CONDITION.返回这个值,scsi核心层那边就知道会去读srb->sense_buffer里边的东西.SAM_STAT_CHECK_CONDITION是scsi那边定义的宏,scsi协议规定,scsi总线上有若干个阶段,比如命令阶段,比如数据阶段,比如状态阶段,这三个阶段其实咱们Bulk-Only spec里边也有.不过scsi协议里还规定了更多的一些阶段,在scsi协议里边称一个阶段为一个phase.除了这三个phase以外,还可以有bus free phase,有selection phase,有message phase等等.而状态阶段就是要求目标设备返回给主机一个状态码(status code).关于这些状态码,在scsi的规范里边定义得很清楚.在include/scsi/scsi.h中也有相关的宏定义.
    117 /*
    118  *  SCSI Architecture Model (SAM) Status codes. Taken from SAM-3 draft
    119  *  T10/1561-D Revision 4 Draft dated 7th November 2002.
    120  */
    121 #define SAM_STAT_GOOD            0x00
    122 #define SAM_STAT_CHECK_CONDITION 0x02
    123 #define SAM_STAT_CONDITION_MET   0x04
    124 #define SAM_STAT_BUSY            0x08
    125 #define SAM_STAT_INTERMEDIATE    0x10
    126 #define SAM_STAT_INTERMEDIATE_CONDITION_MET 0x14
    127 #define SAM_STAT_RESERVATION_CONFLICT 0x18
    128 #define SAM_STAT_COMMAND_TERMINATED 0x22        /* obsolete in SAM-3 */
    129 #define SAM_STAT_TASK_SET_FULL   0x28
    130 #define SAM_STAT_ACA_ACTIVE      0x30
    131 #define SAM_STAT_TASK_ABORTED    0x40
其中,SAM_STAT_CHECK_CONDITION就是对应scsi协议中的CHECK CONDITION,这一状态表明有sense data被放置在相应的buffer里,于是scsi core那边就会去读sense buffer.而我们这里遇到这种情况,当然就可以返回了.
559行,要是没别的啥意外的话,到了这里我们就可以设置srb->result为SAM_STAT_GOOD了,说明一切都是ok的.当然,对于之后会出现的REQUEST SENSE的执行失败,我们会再次修改srb->result的.
下面566行,出现了一个叫做need_auto_sense的变量,这是我们定义的临时变量,这里赋初值为0.574到588行,两个if语句,为need_auot_sense赋了值,赋值为1.我们首先很容易理解第二个if,正如我们前面介绍的那样,REQUEST SENSE这个艰巨的任务被下放给了我们底层驱动,那么我们就勇敢的去承担它,在USB_STOR_TRANSPORT_FAILED的情况下,我们就去发送一个REQUEST SENSE命令.设了这个flag稍后我们就会看到我们会因此而执行REQUEST SENSE.那么第一个if呢?相信那些注释已经说得很清楚了吧,如果你对自己的智商还有一丁点儿信心的话,应该就不需要我解释了.我只说一句,对于那些遵守CB或者DPCM协议的设备它们没有自己没有办法决定状态,所以scsi核心层当然就不知道去读它们的sense buffer了,但是不读sense buffer我们连这个命令执行成功与否都不知道,那怎么行?而设备对于大多数读操作的错误也不需要使用sense buffer,因为它们对于读操作的错误通常会停止掉bulk-in pipe,这已经是一个很明显的信号了,不需要再检测sense buffer,因为检测sense buffer或者说检测sense data的目的无非就是出错了以后想知道出错的原因,而这种情况下原因已经清楚了嘛不是.至于你问为什么读操作会有这种特点,那我只能说两个字,经验.写设备驱动靠的就是经验.
594行,srb->resid大于0,说明有问题,希望传输n个字节,结果汇报上来说只传递了n-m个字节.对于这里列出的这五个命令,少传几个字节倒是无所谓,比如INQUIRY,我就想知道设备的基本信息,那你说你姓甚名谁,生辰八字,学历如何,婚否,等等这些信息,你多说两句就多说两句,少说两句就少说两句,无所谓,没什么影响,但是有些命令就不能少传输了,比如我要传一个pdf文档,你传到一半就不传了,那肯定不行,直接导致我可能打不开这个pdf文档.驱动程序要是写成这样,那人家不跟你急就怪了.这里咱们就是调用US_DEBUGP打印一句调试语句,也就不退出了,咱们忍了.
接下来的一些行,604行开始,一直到713行,我们就是为need_auto_sense的情况发送REQUEST SENSE命令了.其实和之前一样,我们还是等于再进行一次Bulk传输,还是三个阶段,不过我们有了之前的经验,现在再看Bulk传输就简单多了,无非就是准备一个struct scsi_cmnd,调用us->transport(us->srb,us),然后结束了之后检查结果.这正是这一百多行所做的事情.不过我们偷了点懒,没有另外申请一个struct scsi_cmnd,而是直接利用之前的那个srb,只是调用us->transport之前先把一些关键的东西备份起来,然后执行完us->transport之后再恢复过来.所以接下来我们看到如下事件:
用old_cmnd保存了srb->cmnd;
用old_cmd_len保存了srb->cmd_len;
先把srb->cmnd清零,然后对它重新赋值,按REQUEST SENSE的意思来赋值.
不同的命令集里边REQUEST SENSE的长度也不同,对于RBC或者咱们的SCSI,长度为6,而对于别的命令集,其长度为12.
然后用old_sc_data_direction保存了srb->sc_data_direction,而把srb->sc_data_direction设置为REQUEST SENSE的要求,DMA_FROM_DEVICE,很显然,REQUEST SENSE是向设备要sense data,那么当然数据传输方向是从设备到主机.
然后用old_request_buffer来保存srb->request_buffer,而将srb->request_buffer设置为srb->sense_buffer,同时用old_request_bufflen来备份srb->request_bufflen,同时把srb->request_bufflen设置为18.
用old_sg来备份srb->use_sg,而把srb->use_sg设置为0,传这么点数据也就别用那麻烦的scatter-gather机制了.
然后用old_serial_number来备份srb->serial_number,并把srb->serial_number的最高位取反.
最后用old_resid来备份srb->resid,而把srb->resid再次初始化为0.
这时候就可以调用us->transport(us->srb,us)了.并且用一个临时变量temp_result来保存这个结果.
这次命令完了之后,我们659到666行,就把刚才备份的那些变量给恢复原来的值.
668行,再一次判断是不是被放弃了,如果是又goto Handle_Abort.
672行,然后判断temp_result,如果这个result说明这次传输还有问题,那就说明连REQUEST SENSE都fail了.于是我们会设置srb->result=DID_ERROR<<16.在这之前我们还会调用us->transport_reset(us)把设备reset,因为REQUEST SENSE都出错本身就说明很不正常,这种情况下我们不得不来点狠的了,男人嘛,就是要对自己狠一点!当然这里有一个条件,我们判断的是US_FL_SCM_MULT_TARG这个flag没有设置,因为设了这个flag的设备有多个target,这种情况下咱们就不好胡乱给人全reset了,因为REQUEST SENSE这个命令虽然是一个基本的命令,但是毕竟执行成功与否无所谓,我们只是出于好奇才想知道一个命令执行出错的原因.即使不知道也没有太大的关系.没必要非得大张旗鼓的把一个多个target的设备给人家reset了,不该管的事情不要管.
然后685到696行无非就是把temp_result的值打印出来,把sense_buffer里的值打印出来.这些都是调试信息.对调试设备驱动非常管用.
继续讲之前,我们先看一下,692行,usb_stor_show_sense().这个函数,是第一次出现,曾经我们见过一个类似的函数,名叫usb_stor_show_command().她们是老乡,都来自drivers/usb/storage/debug.c:

159 void usb_stor_show_sense(
    160                 unsigned char key,
    161                 unsigned char asc,
    162                 unsigned char ascq) {
    163
    164         const char *what, *keystr;
    165
    166         keystr = scsi_sense_key_string(key);
    167         what = scsi_extd_sense_format(asc, ascq);
    168
    169         if (keystr == NULL)
    170                 keystr = "(Unknown Key)";
    171         if (what == NULL)
    172                 what = "(unknown ASC/ASCQ)";
    173
    174         US_DEBUGP("%s: ", keystr);
    175         US_DEBUGPX(what, ascq);
    176         US_DEBUGPX("/n");
    177 }

这里面又调用了其她函数,scsi_sense_key_string和scsi_extd_sense_format,这两个函数来自driver/scsi/constants.c,暂且不表.先来看对usb_stor_show_sense这个函数的调用.传递给她的实参是srb->sense_buffer中的几个元素,对比咱们前面贴出来的那个sense data的格式,可知sense_buffer[2]的低四位被称为Sense Key,而sense_buffer[12]是Additional sense code,也称ASC,sense_buffer[13]是Additional sense code qulifier,也称ASCQ.这三个冬冬联手为mid level提供了需要的信息,主要也就是错误信息或者异常信息.为什么要三个冬冬呢?实际上就是一个分层的描述方法,比如要描述某个房间就要说某城市某街道某门牌号.这三个冬冬也是起着这么一个作用,Sense Key是第一层,ASC则是对她的补充,而ASCQ则又是对ASC的补充,或者说解释.这样我们再来看看usb_stor_show_sense就很清楚了,咱们传递进来的是三个char变量,而实际的信息就像某种编码一样被融入在了这些char变量中,而调用的两个来自scsi核心层的函数scsi_sense_key_string和scsi_extd_sense_format就是起着翻译的作用,也叫解码.解码了就可以打印出来了.Yeah!

699行,srb->result设置为SAM_STAT_CHECK_CONDITION.为什么?不为什么,Request Sense执行完之后,scsi规范告诉我们应该把srb->result设为SAM_STAT_CHECK_CONDITION,酱紫mid level就知道去检查sense data.这也是为什么在554行,555行会令srb->result也为这个值,只不过那次sense data是咱们自己手工准备的,不是通过命令获得的.

704这个if这一小段,首先咱们需要明白,need_auot_sense这个flag被设为1实际上是有两种可能的,它本身是在usb_stor_invoke_transport()中第一行所定义的一个局部变量,并且在这个函数中特意把它初始化为0. 第一处设置为1的位置是574行当时check us->protocol为US_PR_CB或者US_PR_DPCM_USB,对于这种设备,(如果您只关心u盘,那么就甭理这种设备了.)第二处设置这个flag的就是我们确实遇到了failure,585行,result如果等于USB_STOR_TRANSPORT_FAILED,这种情况当然要设置need_auto_sense了.而704行这里判断result是否等于USB_STOR_TRANSPORT_GOOD,那么很显然,如果result等于USB_STOR_TRANSPORT_FAILED,那么它就不可能等于USB_STOR_TRANSPORT_GOOD,因此,这里这个判断一定是针对第一种need_auto_sense的情况,正如我们曾经说过的,遵守US_PR_CB/US_PR_DPCM_USB协议的设备是不会自己返回命令执行之后的Status,所以我们不管它执行到底成功与否,我们都会对它来一次REQUEST SENSE,就是为了尽可能多的获取一些信息,这样一旦出了问题,我们至少能多一些辅助信息来帮我们判断问题出在哪.那么对于USB_STOR_TRANSPORT_GOOD的情况,首先这说明命令执行是没有问题的了,我们仔细看一下这个if语句,除了这个条件以外还判断了另外三个条件,(srb->sense_buffer[2]&0xaf)结果为0,那么说明srb->sense_buffer[2]的bit0~bit3都为0,bit5为0,bit7也为0,而bit4和bit6是什么我们无所谓.(如果这个你还要问为什么那么我只能说你没救了.没办法,这个世界上只有10种人,一种是懂二进制的,一种是不懂二进制的.)虽然我们没有兴趣熟悉每一个SCSI命令的细节,但我们毕竟是共产主义接班人,应该对社会主义建设的方方面面都有所了解,所以让我们来仔细看看这个sense_buffer[2].对照sense data的格式那张图,sense data的第二个字节,bit0~bit3是sense key,bit4是Reserved,即保留的,不使用的.bit5是ILI,全称incorrect length indicator,bit6是EOM,全称End of Medium,bit7是Filemark,伟大的不朽的金山词霸告诉我们这个词叫做卷标.关于sense key,Scsi协议是这么规定的,如果sense key为0h,那么这种情况表示NO SENSE.这种情况通常对应于命令的成功执行或者就是Filemark/EOM/ILI bits中的任一个被设置为了1.需要注意的是,scsi协议里边定义了四样东西,Filemark/EOM/ILI/Sense Key,它们都是为了提供错误信息的,只是前三者只要一个bit就能表达明确的意思了,而最后一个包含很多信息,所以需要用4个bits,并且还在后面附有很多额外信息,即sense_buffer[12]和sense_buffer[13],这里也要求它们为0,即所谓的ASC和ASCQ都为0,在scsi协议里面,这种情况称之为NO ADDITIONAL SENSE INFORMATION.关于这一点scsi协议是这么说的:”The REQUEST SENSE command requests that the target transfer sense data to the initiator. If the target has no other sense data available to return, it shall return a sense key of NO SENSE and an additional sense code of NO ADDITIONAL SENSE INFORMATION.”而这正是我们这里的代码所表达的意思.(什么?你要我翻译这段话?有没有搞错啊,难道你没上过新东方,没听过老罗的课?那么我代表人民代表党义正严辞的告诉你,同志,你真的落伍了耶!)

(filemark和eom都是针对磁带设备的,跟磁盘设备无关.也就是说跟咱们无关.)

最后,满足了这四个条件的情况就表示刚才这次scsi命令的传输确实是圆满完成了.应该说这次检测还是蛮严格的,毕竟开源社区的同志们觉得写代码不像我们开会,每次看新闻,发现凡是会议必然是圆满成功的.这里人家检查了这么多条件都满足然后就设置srb->result为SAM_STAT_GOOD,并且把srb->sense_buffer[0]也置为0.sense data的byte 0由两部分组成,Valid和Error code,如果置为0,首先就说明这整个sense data是无效的, 用scsi标准的说法叫invalid, 所以scsi core自然没法识别这么一个sense data,而我们既然认定这个命令是成功执行的,当然就没有必然让scsi core 再去理睬这么一个sense data了.

以上花了大量笔墨就讲了704到712这个if语句段.需要重新强调一点,正如我们已经说过的,对于U盘,这段代码根本就不可能执行,理由我们已经说过了.但是既然它出现在我们眼前了,我们又有什么理由去逃避呢?写代码,尤其是写这种通用的设备驱动程序,必然要考虑各种情况,不是完全跟着感觉走,也不是纯粹的追求华丽的算法和数据结构,更应该接近实际,华丽的代码堆砌的东西缺乏骨质感.

这样,关于need_auto_sense设置了的这一段就结束了.最后还想重复一点,说起来,REQUEST SENSE这种命令应该由mid level来发,不应该由底层驱动来发,不过通常mid-level并不愿意发这个命令,因为实际上很多SCSI主机适配卡(SCSI host adapter)会自动request the sense.所以为了让事情变得简单,设计上要求底层驱动去对付这个问题.所以要么SCSi host adapters自动获得sense data,要么就是咱们LLD(底层驱动程序)去发送这个命令,对于咱们这个模拟的scsi系统,当然只能是用软件去实现,即咱们必须在LLD中用代码来发送request sense.

再然后,716行,如果经过了这么一番折腾,srb->result仍然等于SAM_STAT_GOOD,(我们在559行,即进行autosense之前把srb->result设置成了SAM_STAT_GOOD.)那么说明真金不怕火炼,我们再判断最后一个条件,即我们要求传输的数据长度是srb->request_bufflen,而实际上还剩下srb->resid个字节没有被传送,这种情况本身没什么,但是struct scsi_cmnd中有一个成员叫做underflow,其意思是如果传输的数据连这个值都没有达到的话,不管其它条件如何,必须向上层反映,出错了.换句话说,有些scsi命令有一个底线,你至少得达到我这个底线,否则我跟你急!所以这里就是判断这么一个条件是否满足,如果传输的长度小于srb->underflow,那么不用废话,即便你其它条件判断下来都觉得这个命令是成功的,我还是要汇报说你这个命令执行有误.而关于这种情况,我们反馈给scsi core的result是DID_ERROR<<16或上SUGGEST_RETRY<<24.DID_ERROR被定义为0x07,SUGGEST_RETRY为0x10.其定义都在include/scsi/scsi.h中.所以这里srb->result就最终被设置为0x10070000了.还记不记得当初我们贴出来的那个关于US_FL_IGNORE_RESIDUE关于MP3的调试信息了?回过去看一下,没错,当时的result就是0x10070000,也就是这里赋的值.而当时之所以导致执行了这段代码,原因正是设备报虚警,明明读写正常,它偏要瞎写一个residue到状态字节里去.导致我们的代码在这里判断出读写出了错.这个疑案只有到了这里我们才能真正明白,哈哈!

至此,我们的这个故事也快接近尾声了.故事总有结束的时候,但Linux所反映的那种人们对自由的追求却是永无止境的.天长地久有时尽,此恨绵绵无绝期!--开源社区追求自由的战士白居易.

720行, usb_stor_invoke_transport()函数终于返回了.如果之前有执行goto Handle_Abort,那么724行会被执行,实际上就是设置srb->result为DID_ABORT<<16,并且执行reset.关于reset,您别急我们马上就会讲.
usb_stor_invoke_transport()返回就回到了usb_stor_transparent_scsi_command(),这个函数实际上不干什么正经事,就是调用usb_stor_invoke_transport().然后它又判断了一个flag, US_FL_FIX_CAPACITY,又是对了对付某些硬件bug.我们可以看到,如果这个flag没有设置,那么usb_stor_transparent_scsi_command()函数就会就此返回了,返回到usb_stor_control_thread()中,别忘了我们曾经是从375行us->proto_handler(us->srb,us)进入usb_stor_transparent_scsi_command()的,返回了375行之后怎么走我们之前就已经分析过了,usb_stor_control_thread()这个守护进程,它还将永垂不朽的循环下去,它会进入睡眠等待着下一个命令的到来,如果你不强行kill它,或者卸载模块,那么它将守护到天长地久,到海枯石烂,到山峰无棱到河水不再流...
然后我们就可以去看US_FL_FIX_CAPACITY,这里又是一个硬件bug,前几年这种硬件bug出现得还不是很多,在2.6.10的内核中只有松下等几家公司的产品有这么一个问题,不过随着改革开放的发展,很多事情都变了,猪肉涨价了,方便面涨价了,Apple公司的iPod,诺基亚 3250,E70,E60,N91,N80,E61,NIKON的DSC D70,DSC D70s,DSC D80,索尼爱立信的P990i,M600i,摩托罗拉的RAZR V3x,RAZR V3i,等一大批产品都出现了这个问题.虽然偶自己的Nokia 6108没有这种问题,但是考虑到Apple的iPod最近被评为最伟大的20件IT产品之一,而Nokia作为我们Intel多年来最重要的customer之一,我觉得这里我们还是有必要来看看这个bug的.再说了,了解了这个,将来去Nokia面试,去Apple面试,或者次一点,去摩托罗拉或者索尼爱立信面试,跟人家谈一下,喂,伙计,我听说你家的产品有这么这么一个bug,怎么回事啊?兄弟们是不是待遇不佳压力太大啊?人家一看你这么拽,连这种内幕都知道,能不要你么?

Linux那些事儿之我是U盘(50)跟着感觉走(二)相关推荐

  1. Linux那些事儿之我是U盘(49)跟着感觉走(一)

    接下来的时间里我们会接触两个变量,fake_sense和need_auto_sense,sense顾名思义,感觉.所以就让我们跟着感觉走.我们前面提到过,如果设备想发送比期望值更多的数据,那么我们前面 ...

  2. linux 那些事儿之我是 u 盘,《Linux那些事儿之我是USB》.PDF

    <Linux 那些事儿之我是 USB> 作者:华清远见 第 1 章 Linux 那些事儿之我是 USB Core 专业始于专注 卓识源于远见 1 .引子 老夫子们痛心疾首地总结说,现代青年 ...

  3. 转载本论坛 (fudan_abc ) :linux那些事儿之我是u盘(16)冰冻三尺非一日之寒

    不是一天建成的 . 在让 U 盘工作之前 , 其实我们的驱动作了很多准备工作 . 我们继续跟着感觉走,storage_probe(),943行至948行,一系列的以init_*命名的函数在此刻被调用, ...

  4. Linux那些事儿之我是Hub(9)While You Were Sleeping(二)

    老实说,从函数一个开始的598行直到627行都没有什么可说的.其中需要一提的是,606行,调用usb_buffer_alloc()申请内存,赋给hub->buffer.614行,调用kmallo ...

  5. Linux那些事儿之我是U盘(5)外面的世界很精彩

    看代码之前,我曾经认真的思考过这么一个问题,我需要关注的仅仅是drivers/usb/storage/目录下面那相关的3000多行代码吗?就是这样几个文件就能让一个个不同的U盘在Linux下面工作起来 ...

  6. Linux那些事儿之我是U盘(4)想到达明天,现在就要启程

    既然知道了怎么编写一个模块,那么编写设备驱动程序自然也就不难了.我相信,每一个会写模块的人都不会觉得写设备驱动有困难.对自己行不行不确定的话,可以去问一下葛优,他准说:"(神州行),我看行. ...

  7. Linux那些事儿之我是U盘(1)小城故事

    这个故事中使用的是2.6.10的内核代码.Linux内核代码目录中, 所有去设备驱动程序有关的代码都在drivers/目录下面,在这个目录中我们用ls命令可以看到很多子目录. localhost:/u ...

  8. 【转】Linux那些事儿之我是U盘(4)想到达明天,现在就要启程

    既然知道了怎么编写一个模块,那么编写设备驱动程序自然也就不难了.我相信,每一个会写模块的人都不会觉得写设备驱动有困难.对自己行不行不确定的话,可以去问一下葛优,他准说:"(神州行),我看行. ...

  9. Linux那些事儿之我是U盘--引子

    也许是在复旦养成了昼伏夜出的坏习惯,工作之后也总是很晚也不愿意睡.来到北京之后,开始听广播听都市之声的北京不眠夜.这个节目是从23点直到第二天凌晨一点,我常常是听完了才会睡觉.无论是北京还是上海,对我 ...

最新文章

  1. Qt字符编码,创建中文文件
  2. python三个小题
  3. 《JAVA练习题目10》请对图书馆系统中的CatalogItem类、Book类和Recording类进行改造,实现其code属性的自动编码。
  4. 2018,开工第一天
  5. mysql 索引分析工具_Mysql:性能分析以及Explain工具的使用
  6. 一篇教你xftp连接阿里云轻量级应用服务器。超级详细,避免踩坑
  7. Python pandas库159个常用方法使用说明(转载)
  8. 一个没有停止的android闹钟,一旦开始就要把歌唱完 .
  9. 切换回Chrome上的上次标签及打开设置快捷键
  10. C/C++代码调试:快速定位内存的申请和释放的位置
  11. 摩拜服务器维护,摩拜的后台是云服务器
  12. Android中突发情况数据的保存和恢复
  13. html 酷狗音乐教程,仿酷狗html5手机音乐播放器主要部分代码_html5教程技巧
  14. scala练习——fold函数
  15. 你必须知道的指针基础-6.内存的初始化及结构体的使用
  16. 【Python】import class/import module
  17. 探索女性角色扮演游戏Top Girl成功的秘诀
  18. 2020-10-19 进制转换
  19. Flyway-数据库迁移工具
  20. android studio 中配置groovy源码_麻省理工教程:使用Unity AR Foundation在AR中查看模型...

热门文章

  1. 检测com类中CLSID为{F9364159-6AED-4F9C-8BAF-D7C7ED6160A8}的组件时失败,错误80040154
  2. 架构师速成-如何高效编程 for java
  3. 网站/页面即时聊天客服功能
  4. 2022.6.6 特长生模拟
  5. 什么是dns,dns的工作流程是什么
  6. OpenSSL密码库算法笔记——第5.4.1章 椭圆曲线上点的仿射坐标表示
  7. 良品铺子的鲜果月饼 能成为月饼“新物种”吗?
  8. 求生之路2怎么联机显示服务器,求生之路2联机如何创建服务器?
  9. Karl Guttag:现有Micro LED/LCoS+光波导AR眼镜对比解析
  10. BZOJ1022 [SHOI2008]小约翰的游戏John(Anti-Nim游戏)