关闭

Linux那些事儿 之 戏说USB(19)设备

标签: linuxstructdescriptorstringtablereturning
5047人阅读 评论(4) 收藏 举报
分类:

第一眼看到struct usb_device这个结构,我仿佛置身于衡山路的酒吧里,盯着舞池里扭动的符号,眼神迷离。

交大里苟了几年,毕业了又是住在学校附近的徐虹北路上,沿着虹桥路走过去,到徐家汇不过10多分钟,再溜达几步就可以到衡山路。学校里睡的就比较晚,毕业了仍然一样,其它好习惯还是坏习惯扔掉了不少,就这个保持的还不错,于是经常穷极无聊的晚上,只好和同学沿着虹桥路也好,沿着番愚路再走到广元路或淮海路也好,慢慢的向东走。走着走着就会到衡山路,有时遇到许多老年人在衡山电影院对面的小广场上跳各种各样的舞,就会在旁边一曲一曲的看,感慨咱们爬到60岁,爬到那般年纪是否会有那样的乐趣。有时想疯了,就会到旁边的酒吧坐坐,麻醉一下别人眼里的自己。有时只是在旁边儿安静的巷子里四处走走。

衡山路的夜晚杂乱而又冗长,struct usb_device结构冗长而又杂乱。衡山路还是会去,struct usb_device还是得说。

328 /*
329  * struct usb_device - kernel's representation of a USB device
330  *
331  * FIXME: Write the kerneldoc!
332  *
333  * Usbcore drivers should not set usbdev->state directly.  Instead use
334  * usb_set_device_state().
335  */
336 struct usb_device {
337         int             devnum;         /* Address on USB bus */
338         char            devpath [16];   /* Use in messages: /port/port/... */
339         enum usb_device_state   state;  /* configured, not attached, etc */
340         enum usb_device_speed   speed;  /* high/full/low (or error) */
342         struct usb_tt   *tt;            /* low/full speed dev, highspeed hub */
343         int             ttport;         /* device port on that tt hub */
345         unsigned int toggle[2];         /* one bit for each endpoint
346                                          * ([0] = IN, [1] = OUT) */
348         struct usb_device *parent;      /* our hub, unless we're the root */
349         struct usb_bus *bus;            /* Bus we're part of */
350         struct usb_host_endpoint ep0;
352         struct device dev;              /* Generic device interface */
354         struct usb_device_descriptor descriptor;/* Descriptor */
355         struct usb_host_config *config; /* All of the configs */
357         struct usb_host_config *actconfig;/* the active configuration */
358         struct usb_host_endpoint *ep_in[16];
359         struct usb_host_endpoint *ep_out[16];
361         char **rawdescriptors;          /* Raw descriptors for each config */
363         unsigned short bus_mA;          /* Current available from the bus */
364         u8 portnum;                     /* Parent port number (origin 1) */
365         u8 level;                       /* Number of USB hub ancestors */
367         unsigned discon_suspended:1;    /* Disconnected while suspended */
368         unsigned have_langid:1;         /* whether string_langid is valid */
369         int string_langid;              /* language ID for strings */
371         /* static strings from the device */
372         char *product;                  /* iProduct string, if present */
373         char *manufacturer;             /* iManufacturer string, if present */
374         char *serial;                   /* iSerialNumber string, if present */
376         struct list_head filelist;
377 #ifdef CONFIG_USB_DEVICE_CLASS
378         struct device *usb_classdev;
379 #endif
380 #ifdef CONFIG_USB_DEVICEFS
381         struct dentry *usbfs_dentry;    /* usbfs dentry entry for the device */
382 #endif
383         /*
384          * Child devices - these can be either new devices
385          * (if this is a hub device), or different instances
386          * of this same device.
387          *
388          * Each instance needs its own set of data structures.
389          */
391         int maxchild;                   /* Number of ports if hub */
392         struct usb_device *children[USB_MAXCHILDREN];
394         int pm_usage_cnt;               /* usage counter for autosuspend */
395         u32 quirks;                     /* quirks of the whole device */
397 #ifdef CONFIG_PM
398         struct delayed_work autosuspend; /* for delayed autosuspends */
399         struct mutex pm_mutex;          /* protects PM operations */
401         unsigned long last_busy;        /* time of last use */
402         int autosuspend_delay;          /* in jiffies */
404         unsigned auto_pm:1;             /* autosuspend/resume in progress */
405         unsigned do_remote_wakeup:1;    /* remote wakeup should be enabled */
406         unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
407         unsigned autoresume_disabled:1;  /*  disabled by the user */
408 #endif
409 };

337行,devnum,设备的地址。此地址非彼地址,和咱们写程序时说的地址是不一样的,devnum只是usb设备在一条usb总线上的编号。你的usb设备插到hub上时,hub观察到这个变化,于是来了精神,会在一个漫长而又曲折的处理过程中调用一个名叫choose_address的函数,为你的设备选择一个地址。就像在那个浪漫的季节的一个温馨的下午,你去吃港汇下边儿的那个必胜客,同样会领取一个属于自己的编号陪伴自己度过一个漫长的过程。有人说我没有用hub,我的usb设备直接插到主机的usb接口上了。我哭,即使你没有用hub,也总要明白主机里还会有个叫root hub的东东吧,不管是一般的hub还是root hub,你的usb设备总要通过一个hub才能在usb的世界里生活。

现在来认识一下usb子系统里面关于地址的游戏规则。在usb世界里,一条总线就是大树一棵,一个设备就是叶子一片。为了记录这棵树上的每一个叶子节点,每条总线设有一个地址映射表,即struct usb_bus结构体里有一个成员struct usb_devmap devmap

268 /* USB device number allocation bitmap */
269 struct usb_devmap {
270         unsigned long devicemap[128 / (8*sizeof(unsigned long))];
271 };

什么是usb_bus?前面不是已经有了一个struct bus_type类型的usb_bus_type了么?没错,在usb子系统的初始化函数usb_init里已经注册了usb_bus_type,不过那是让系统知道有这么一个类型的总线。而一个总线类型和一条总线是两码子事儿。从硬件上来讲,一个host controller就会连出一条usb总线,而从软件上来讲,不管你有多少个host controller,或者说有多少条总线,它们通通属于usb_bus_type这么一个类型,只是每一条总线对应一个struct usb_bus结构体变量,这个变量在host controller的驱动程序中去申请。

上面的devmap地址映射表就表示了一条总线上设备连接的情况,假设unsigned long=4bytes,那么unsigned long devicemap[128/(8*sizeof(unsigned long)]]就等价于unsigned long devicemap[128/(8*4)],进而等价于unsigned long devicemap[4],而4bytes就是32bits,因此这个数组最终表示的就是128bits。而这也对应于一条总线可以连接128usb设备。之所以这里使用sizeof(unsigned long),就是为了跨平台应用,不管unsigned long到底是几,总之这个devicemap数组最终可以表示128位,也就是说每条总线上最多可以连上128个设备。

338行,devpath [16],它显然是用来记录一个字符串的,这个字符串啥意思?给你看个直观的东西

localhost:~ # ls /sys/bus/usb/devices/
1-0:1.0  2-1      2-1:1.1  4-0:1.0  4-5:1.0  usb2  usb4
2-0:1.0  2-1:1.0  3-0:1.0  4-5      usb1     usb3

Sysfs文件系统下,我们看到这些乱七八糟的东西,它们都是啥?usb1/usb2/usb3/usb4表示哥们的计算机上接了4usb总线,即4usb主机控制器,事物多了自然就要编号,就跟我们中学或大学里面的学号一样,就是用于区分多个个体,而4-0:1.0表示什么?4表示是4号总线,或者说4Root Hub0就是这里我们说的devpath1表示配置为1号,0表示接口号为0。也即是说,4号总线的0号端口的设备,使用的是1号配置,接口号为0。那么devpath是否就是端口号呢?显然不是,这里我列出来的这个例子是只有Root Hub没有级联Hub的情况,如果在Root Hub上又接了别的Hub,然后一级一级连下去,子又生孙,孙又生子,子又有子,子又有孙。子子孙孙,无穷匮也。那么如何在sysfs里面来表征这整个大家族呢?这就是devpath的作用,顶级的设备其devpath就是其连在Root Hub上的端口号,而次级的设备就是其父Hubdevpath后面加上其端口号,即如果4-0:1.0如果是一个Hub,那么它下面的1号端口的设备就可以是4-0.1:1.02号端口的设备就可以是4-0.2:1.03号端口就可以是4-0.3:1.0。总的来说,就是端口号一级一级往下加。这个思想是很简单的,也是很朴实的。

339行,state,设备的状态。这是个枚举类型

558         /* NOTATTACHED isn't in the USB spec, and this state acts
559          * the same as ATTACHED ... but it's clearer this way.
560          */
561         USB_STATE_NOTATTACHED = 0,
563         /* chapter 9 and authentication (wireless) device states */
564         USB_STATE_ATTACHED,
565         USB_STATE_POWERED,                      /* wired */
566         USB_STATE_UNAUTHENTICATED,              /* auth */
567         USB_STATE_RECONNECTING,                 /* auth */
568         USB_STATE_DEFAULT,                      /* limited function */
569         USB_STATE_ADDRESS,
570         USB_STATE_CONFIGURED,                   /* most functions */
572         USB_STATE_SUSPENDED
574         /* NOTE:  there are actually four different SUSPENDED
575          * states, returning to POWERED, DEFAULT, ADDRESS, or
576          * CONFIGURED respectively when SOF tokens flow again.
577          */
578 };

上面定义了9种状态,spec里只定义了6种,AttachedPoweredDefaultAddressConfiguredSuspended,对应于Table 9.1

Attached表示设备已经连接到usb接口上了,是hub检测到设备时的初始状态。那么这里所谓的USB_STATE_NOTATTACHED就是表示设备并没有Attached

Powered是加电状态。USB设备的电源可以来自外部电源,协议里叫做self-powered,也可以来自hub,叫bus-powered。尽管self-poweredUSB设备可能在连接上USB接口以前已经上电,但它们直到连上USB接口后才能被看作是Powered的,你觉得它已经上电了那是站在你的角度看,可是现在你看的是usbcore,所以要放弃个人的成见,团结在core的周围。

Default缺省状态,在Powered之后,设备必须在收到一个复位(reset)信号并成功复位后,才能使用缺省地址回应主机发过来的设备和配置描述符的请求。

Address状态表示主机分配了一个唯一的地址给设备,此时设备可以使用缺省管道响应主机的请求。真羡慕这些usb设备,住的地方都是包分配的,哪像咱们辛辛苦苦一路小跑着也不一定能达到Address状态。

Configured状态表示设备已经被主机配置过了,也就是协议里说的处理了一个带有非0值的SetConfiguration()请求,此时主机可以使用设备提供的所有功能。

Suspended挂起状态,为了省电,设备在指定的时间内,3ms吧,如果没有发生总线传输,就要进入挂起状态。此时,usb设备要自己维护包括地址、配置在内的信息。

USB设备从生到死都要按照这么几个状态,遵循这么一个过程。它不可能像咱们的房价,林志伶的胸部一样跳跃式的发展。

340行,speed,设备的速度,这也是个枚举变量

548 /* USB 2.0 defines three speeds, here's how Linux identifies them */
551         USB_SPEED_UNKNOWN = 0,                  /* enumerating */
552         USB_SPEED_LOW, USB_SPEED_FULL,          /* usb 1.1 */
553         USB_SPEED_HIGH,                         /* usb 2.0 */
554         USB_SPEED_VARIABLE,                     /* wireless (usb 2.5) */
555 };

地球人都知道,USB设备有三种速度,低速,全速,高速。USB1.1那会儿只有低速,全速,后来才出现了高速,就是所谓的480Mbps/s。这里还有个USB_SPEED_VARIABLE,是无线USB的,号称usb 2.5,还在发展中,据说小黑的T61已经支持了,向往中。USB_SPEED_UNKNOWN只是表示现阶段还不知道这个设备究竟什么速度。

342行,tt343行,ttport。知道tt干嘛的吗?tt叫做transaction translator。你可以把它想成一块特殊的电路,是hub里面的电路,确切的说是高速hub中的电路,我们知道usb设备有三种速度的,分别是low speedfull speedhigh speed。即所谓的低速/全速/高速,抗日战争那会儿,这个世界上只有low speed/full speed的设备,没有high speed的设备,后来解放后,国民生产力的大幅度提升催生了一种high speed的设备,包括主机控制器,以前只有两种接口的,OHCI/UHCI,这都是在usb spec 1.0的时候,后来2.0推出了EHCI,高速设备应运而生。Hub也有高速hub和过去的hub,但是这里就有一个兼容性问题了,高速的hub是否能够支持低速/全速的设备呢?一般来说是不支持的,于是有了一个叫做TT的电路,它就负责高速和低速/全速的数据转换,于是,如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。

345行,toggle[2],这个数组只有两个元素,分别对应INOUT端点,每一个端点占一位。似乎这么说仍是在雾中看花,黑格尔告诉我们,存在就是有价值的,那么这个数组存在的价值是什么?一言难尽,说来话长,那就长话长说好了。

咱们前边儿说,你要想和你的usb通信,创建一个urb,为它赋好值,交给咱们的usb core就可以了。这个urb是站在咱们的角度,实际上在usb cable里流淌的根本就不是那么回事儿,咱们提交的是urbusb cable里流淌的是一个一个的数据包(packet),就像咱们吃的是社会主义的粮,身体里流淌的是无产阶级的鲜血。咱们无产阶级的鲜血里,虽说不包括房产财产什么的,但是还是有许多的成分一定的结构的,usb底层传输的packets也一样。

咱们凄苦的人生是从第一声哭开始,所有的packets都从一个SYNC同步字段开始,SYNC是一个8位长的二进制串,只是用来同步用的,它的最后两位标志了SYNC的结束和PIDPacket Identifer)的开始,就像咱们的大四标志了梦想的结束和现实的开始。PID也是一个8位的二进制串,前四位用来区分不同的packet类型,后面四位只是前四位的反码,校验用的。packet的类型主要有四种,在specTable 8-1里有说明

主机和设备都是纯理性的东东,完全通过PID来判断送过来的packet是不是自己所需要的,不像咱们,往往缺乏这么一个用来判断的标准,不知道自己究竟需要的是什么。PID之后紧跟着的是地址字段,每个packet都需要知道自己要往哪里去,它们是一个一个目的明确的精灵,行走在usb cable里,而我们的前方在哪里?这个地址实际上包括两部分,7位表示了总线上连接的设备或接口的地址,4位表示端点的地址,这就是为什么前面说每条usb总线最多只能有128个设备,即使是高速设备每个接口除了0号端点也最多只能有15in端点和15out端点。地址字段再往后是11位的帧号(frame number),值达到7FFH时归零,像一个个无聊的夜晚一样循环往复。这个帧号并不是每一个packet都会有,它只在每帧或微帧(Mircoframe)开始的SOF Token包里发送。帧是对于低速和全速模式来说的,一帧就是1ms,对于高速模式的称呼是微帧,一个微帧为125微妙,每帧或微帧当然不会只能传一个packet。帧号再往后就是千呼万唤始出来的Data字段了,它可以有01024个字节不等。最后还有CRC校验字段来做扫尾工作。

咱们要学习packet,做一个有理想有目标的人,所以这里只看看Data类型的packet。前面的Table 8-1里显示,有四种类型的Data包,DATA0DATA1DATA2MDATA。存在就是有价值的,这里分成4种数据包自然有里面的道理,其中DATA0DATA1就可以用来实现data toggle同步,看到toggle,好像有点接近不久之前留下的疑问了。

对于批量传输、控制传输和中断传输来说,数据包最开始都是被初始化为DATA0的,然后为了传输的正确性,就一次传DATA0,一次传DATA1,一旦哪次打破了这种平衡,主机就可以认为传输出错了。对于等时传输来说,data toggle并不被支持。USB就是在使用这种简单的哲学来判断对于错,而我们的生活中有的只是复杂,即使一个馒头都能引起一个两亿多的血案。

我们的struct usb_device中的数组unsigned int toggle[2]就是为了支持这种简单的哲学而生的,它里面的每一位表示的就是每个端点当前发送或接收的数据包是DATA0还是DATA1

348行,parentstruct usb_device结构体的parent自然也是一个struct usb_device指针。对于Root Hub,前面说过,它是和Host Controller是绑定在一起的,它的parent指针在Host Controller的驱动程序中就已经赋了值,这个值就是NULL,换句话说,对于Root Hub,它不需要再有父指针了,这个父指针就是给从Root Hub连出来的节点用的。USB设备是从Root Hub开始,一个一个往外面连的,比如Root Hub4个口,每个口连一个USB设备,比如其中有一个是Hub,那么这个Hub有可以继续有多个口,于是一级一级的往下连,最终连成了一棵树。

349行,bus,没什么说的,设备所在的那条总线。

350行,ep0,端点0的特殊地位决定了她必将受到特殊的待遇,在struct usb_device对象产生的时候它就要初始化。

353行,dev,嵌入到struct usb_device结构里的struct device结构。

354行,desc,设备描述符,四大描述符的第三个姗姗而来。它在include/linux/usb/ch9.h里定义

203 /* USB_DT_DEVICE: Device descriptor */
205         __u8  bLength;
206         __u8  bDescriptorType;
208         __le16 bcdUSB;
209         __u8  bDeviceClass;
210         __u8  bDeviceSubClass;
211         __u8  bDeviceProtocol;
212         __u8  bMaxPacketSize0;
213         __le16 idVendor;
214         __le16 idProduct;
215         __le16 bcdDevice;
216         __u8  iManufacturer;
217         __u8  iProduct;
218         __u8  iSerialNumber;
219         __u8  bNumConfigurations;
220 } __attribute__ ((packed));
222 #define USB_DT_DEVICE_SIZE              18

205行,bLength,描述符的长度,可以自己数数,或者看紧接着的定义USB_DT_DEVICE_SIZE

206行,bDescriptorType,这里对于设备描述符应该是USB_DT_DEVICE0x01

208行,bcdUSBUSB spec的版本号,一个设备如果能够进行高速传输,那么它设备描述符里的bcdUSB这一项就应该为0200H

209行,bDeviceClass210行,bDeviceSubClass211行,bDeviceProtocol,和接口描述符的意义差不多,前面说了这里就不再罗唆了。

212行,bMaxPacketSize0,端点0一次可以处理的最大字节数,端点0的属性却放到设备描述符里去了,更加彰显了它突出的江湖地位,它和机器人公敌里的机器人Sonny,交大南门外的老赵烤肉一样特别一样独一无二。

前面说端点的时候说了端点0并没有一个专门的端点描述符,因为不需要,基本上它所有的特性都在spec里规定好了的,然而,别忘了这里说的是“基本上”,有一个特性则是不一样的,这叫做maximum packet size,每个端点都有这么一个特性,即告诉你该端点能够发送或者接收的包的最大值。对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0。而且spec还规定了,这个值只能是81632或者64这四者之一,如果一个设备工作在高速模式,这个值还只能是64,如果是工作在低速模式,则只能是8,取别的值都不行。

213行,idVendor214行,idProduct,分别是厂商和产品的ID

215行,bcdDevice,设备的版本号。

216行,iManufacturer217行,iProduct218行,iSerialNumber,分别是厂商,产品和序列号对应的字符串描述符的索引值。

219行,bNumConfigurations,设备当前速度模式下支持的配置数量。有的设备可以在多个速度模式下操作,这里包括的只是当前速度模式下的配置数目,不是总的配置数目。

这就是设备描述符,它和spec Table 9-8是一一对应的。咱们回到struct usb_device355行,config357行,actconfig,分别表示设备拥有的所有配置和当前激活的,也就是正在使用的配置。usb设备的配置用struct usb_host_config结构来表示,下节再说。

358行,ep_in[16]359行,ep_out[16],除了端点0,一个设备即使在高速模式下也最多只能再有15IN端点和15OUT端点,端点0太特殊了,对应的管道是Message管道,又能进又能出特能屈能伸的那种,所以这里的ep_inep_out数组都有16个值。

361行,rawdescriptors,这是个字符指针数组,数组里的每一项都指向一个使用GET_DESCRIPTOR请求去获得配置描述符时所得到的结果。考虑下,为什么我只说得到的结果,而不直接说得到的配置描述符?不是请求的就是配置描述符么?这是因为你使用GET_DESCRIPTOR去请求配置描述符时,设备返回给你的不仅仅只有配置描述符,它把该配置所包括的所有接口的接口描述符,还有接口里端点的端点描述符一股脑的都塞给你了。第一个接口的接口描述符紧跟着这个配置描述符,然后是这个接口下面端点的端点描述符,如果有还有其它接口,它们的接口描述符和端点描述符也跟在后面,这里面,专门为一类设备定义的描述符和厂商定义的描述符跟在它们对应的标准描述符后面。这和我们去买水果,买了5斤苹果却只有5个真实天壤之别,现实生活中的愤懑在USB世界里得到了发泄。

这里提到了GET_DESCRIPTOR请求,就顺便简单提一下USB的设备请求(device request)。协议里说了,所有的设备通过缺省的控制管道来响应主机的请求,既然使用的是控制管道,那当然就是控制传输了,这些请求的底层packet属于Setup类型,前面的那张表里也可以看到它,在Setup包里包括了请求的各种参数。协议里同时也定义了一些标准的设备请求,并规定所有的设备必须响应它们,即使它们还处于DefaultAddress状态。这些标准的设备请求里,GET_DESCRIPTOR就赫然在列。

363行,bus_mA这个值是在host controller的驱动程序中设置的通常来讲计算机的usb端口可以提供500mA的电流

364行,portnum,不管是root hub还是一般的hub,你的USB设备总归要插在一个hub的端口上才能用,portnum就是那个端口号。当然,对于root hub这个usb设备来说它本身没有portnum这么一个概念,因为它不插在别的Hub的任何一个口上。所以对于Root Hub来说,它的portnumHost Controller的驱动程序里给设置成了0

365行,level,层次,也可以说是级别,表征usb设备树的级连关系。Root Hublevel当然就是0,其下面一层就是level 1,再下面一层就是level 2,依此类推。

366行,discon_suspendedDisconnected while suspended

368行,have_langid369行,string_langidusb设备里的字符串描述符使用的是UNICODE编码,可以支持多种语言,string_langid就是用来指定使用哪种语言的,have_langid用来判断string_langid是否有效。

372行,product373行,manufacturer374行,serial,分别用来保存产品、厂商和序列号对应的字符串描述符信息。

376~382行,usbfs相关的,不可知的未来说usbfs的时候再聊它们。

391行,maxchildhub的端口数,注意可不包括上行端口。

392行,children[USB_MAXCHILDREN]USB_MAXCHILDRENinclude/linux/usb.h里定义的一个宏,值为31

315 /* This is arbitrary.
316  * From USB 2.0 spec Table 11-13, offset 7, a hub can
317  * have up to 255 ports. The most yet reported is 10.
318  *
319  * Current Wireless USB host hardware (Intel i1480 for example) allows
320  * up to 22 devices to connect. Upcoming hardware might raise that
321  * limit. Because the arrays need to add a bit for hub status data, we
322  * do 31, so plus one evens out to four bytes.
323  */
324 #define USB_MAXCHILDREN         (31)

其实hub可以接一共255个端口不过实际上遇到的usb hub最多的也就是说自己支持10个端口的所以31基本上够用了

394行,pm_usage_cntstruct usb_interface结构里也有,想知道吗?想知道回那儿看吧。

396行,quirks,祭起我们法宝金山词霸看看,怪僻的意思,白了说就是大家的常用语“毛病”。本来指定usb spec就是让大家团结一致好办事,但总是有些厂商不太守规矩,拿出一些有点毛病的产品给我们用,你说它大毛病吧,也不是,就像俺这儿的厦X彩电一样,绝对能看,只是动不动就罢次工。不说远了,总之这里的quirk就是用来判断这些有毛病的产品啥毛病的。谁去判断?不像咱们的中国足协,把中国足球折腾成这样子,也就是出来声明一下完事儿,咱们usb这儿实行的可是责任制,你的设备接哪儿哪儿负责,也就是说hub去判断,就不用咱费心了。

397行,看到#ifdef CONFIG_PM这个标志,我们就知道从这里直到最后的那个#endif都是关于电源管理的。让我们先大胆的忽略它们,struct usb_device这个结构已经够让我们疲惫了,还是换换口味吧。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2433234次
    • 积分:7548
    • 等级:
    • 排名:第3053名
    • 原创:297篇
    • 转载:0篇
    • 译文:0篇
    • 评论:2042条
    博客专栏