qos rule list
sockaddr结构体 socket
sockaddr结构体
sockaddr的缺陷:sa_data把目标地址和端口信息混在一起了
struct sockaddr { unsigned short sa_family; char sa_data[14]; }; |
sa_family是通信类型,最常用的值是 "AF_INET" sa_data14字节,包含套接字中的目标地址和端口信息 |
sockaddr_in 结构体
sockaddr_in结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr;
unsigned char sin_zero[8]; } |
sin_port和sin_addr都必须是NBO
一般可视化的数字都是HBO(本机字节顺序)
sin_zero 初始值应该使用函数 bzero() 来全部置零。
一般采用下面语句
struct sockaddr_in cliaddr; bzero(&cliaddr,sizeof(cliaddr)); |
sockaddr_in结构体变量的基本配置
struct sockaddr_in ina; bzero(&ina,sizeof(ina)); ina.sin_family=AF_INET; ina.sin_port=htons(23); ina.sin_addr.s_addr = inet_addr("132.241.5.10"); |
sockaddr 和 sockaddr_in的相互关系
一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数
- sockaddr_in用于socket定义和赋值
- sockaddr用于函数参数
最典型的源、目的节点socket定义
对于源、目的地址和源、目的地址端口,需要建立两个socket变量
cliaddr绑定源地址和源端口
servaddr用于connect和sendto的设定目的地址和目的端口
struct sockaddr_in servaddr,cliaddr; create_socket(char *server_addr_string,unsigned int server_port) { 源socket赋值 bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family = AF_INET; 通常TCP/UDP 协议源地址和端口都是随机的 cliaddr.sin_addr.s_addr = htons(INADDR_ANY); cliaddr.sin_port = htons(0); 目的socket赋值 bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_aton(server_addr_string,&servaddr.sin_addr); servaddr.sin_port = htons(server_port); } |
网络字节顺序 (Network Byte Order) NBO
结构体的sin_port和sin_addr都必须是NBO
本机字节顺序 (Host Byte Order) HBO
一般可视化的数字都是HBO
NBO,HBO二者转换
inet_addr() 将字符串点数格式地址转化成无符号长整型(unsigned long s_addr s_addr;)
inet_aton() 将字符串点数格式地址转化成NBO
inet_ntoa () 将NBO地址转化成字符串点数格式
htons() "Host to Network Short"
htonl() "Host to Network Long"
ntohs() "Network to Host Short"
ntohl() "Network to Host Long"
常用的是htons(),inet_addr()正好对应结构体的端口类型和地址类型
三种给socket赋值地址的方法
inet_aton(server_addr_string,&myaddr.sin_addr); |
myaddr.sin_addr.s_addr = inet_addr("132.241.5.10"); |
INADDR_ANY转不转NBO随便 myaddr.sin_addr.s_addr = htons(INADDR_ANY); myaddr.sin_addr.s_addr = INADDR_ANY; |
两种给socket 赋值端口的方法
#define MYPORT 3490 myaddr.sin_port = htons(MYPORT); |
0(随机端口)转不转NBO随便 myaddr.sin_port = htons(0); myaddr.sin_port = 0; |
htons/l和ntohs/l等数字转换都不能用于地址转换,因为地址都是点数格式,所以地址只能采用数字/字符串转换如inet_aton,inet_ntoa;
唯一可以用于地址转换的htons是针对INADDR_ANY
cliaddr.sin_addr.s_addr = htons(INADDR_ANY)
inet_addr()与inet_aton()的区别
- inet_addr() 是返回值型
struct sockaddr_in ina; ina.sin_addr.s_addr = inet_addr("132.241.5.10"); |
- inet_aton() 是参数指针型
struct sockaddr_in ina; inet_aton("132.241.5.10",&ina.sin_addr); |
inet_ntoa 将NBO地址转化成字符串点数格式
参数:结构体变量.sinaddr
返回值:字符串指针
a1 = inet_ntoa(ina.sin_addr); printf("address 1: %s\n",a1); |
address 1: 132.241.5.10 |
inet_addr()的缺陷:必须对-1做检测处理
因为inet_addr()的结果是整型,而发生错误时返回-1。
而 ina.sin_addr.s_addr是unsigned long型
-1在long short显示成111111111,和IP地址255.255.255.255相符合!会被误认为广播地址!
sockaddr结构体 socket
sockaddr的缺陷:sa_data把目标地址和端口信息混在一起了
struct sockaddr { unsigned short sa_family; char sa_data[14]; }; |
sa_family是通信类型,最常用的值是 "AF_INET" sa_data14字节,包含套接字中的目标地址和端口信息 |
sockaddr_in 结构体
sockaddr_in结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr;
unsigned char sin_zero[8]; } |
sin_port和sin_addr都必须是NBO
一般可视化的数字都是HBO(本机字节顺序)
sin_zero 初始值应该使用函数 bzero() 来全部置零。
一般采用下面语句
struct sockaddr_in cliaddr; bzero(&cliaddr,sizeof(cliaddr)); |
sockaddr_in结构体变量的基本配置
struct sockaddr_in ina; bzero(&ina,sizeof(ina)); ina.sin_family=AF_INET; ina.sin_port=htons(23); ina.sin_addr.s_addr = inet_addr("132.241.5.10"); |
sockaddr 和 sockaddr_in的相互关系
一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数
- sockaddr_in用于socket定义和赋值
- sockaddr用于函数参数
最典型的源、目的节点socket定义
对于源、目的地址和源、目的地址端口,需要建立两个socket变量
cliaddr绑定源地址和源端口
servaddr用于connect和sendto的设定目的地址和目的端口
struct sockaddr_in servaddr,cliaddr; create_socket(char *server_addr_string,unsigned int server_port) { 源socket赋值 bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family = AF_INET; 通常TCP/UDP 协议源地址和端口都是随机的 cliaddr.sin_addr.s_addr = htons(INADDR_ANY); cliaddr.sin_port = htons(0); 目的socket赋值 bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_aton(server_addr_string,&servaddr.sin_addr); servaddr.sin_port = htons(server_port); } |
网络字节顺序 (Network Byte Order) NBO
结构体的sin_port和sin_addr都必须是NBO
本机字节顺序 (Host Byte Order) HBO
一般可视化的数字都是HBO
NBO,HBO二者转换
inet_addr() 将字符串点数格式地址转化成无符号长整型(unsigned long s_addr s_addr;)
inet_aton() 将字符串点数格式地址转化成NBO
inet_ntoa () 将NBO地址转化成字符串点数格式
htons() "Host to Network Short"
htonl() "Host to Network Long"
ntohs() "Network to Host Short"
ntohl() "Network to Host Long"
常用的是htons(),inet_addr()正好对应结构体的端口类型和地址类型
三种给socket赋值地址的方法
inet_aton(server_addr_string,&myaddr.sin_addr); |
myaddr.sin_addr.s_addr = inet_addr("132.241.5.10"); |
INADDR_ANY转不转NBO随便 myaddr.sin_addr.s_addr = htons(INADDR_ANY); myaddr.sin_addr.s_addr = INADDR_ANY; |
两种给socket 赋值端口的方法
#define MYPORT 3490 myaddr.sin_port = htons(MYPORT); |
0(随机端口)转不转NBO随便 myaddr.sin_port = htons(0); myaddr.sin_port = 0; |
htons/l和ntohs/l等数字转换都不能用于地址转换,因为地址都是点数格式,所以地址只能采用数字/字符串转换如inet_aton,inet_ntoa;
唯一可以用于地址转换的htons是针对INADDR_ANY
cliaddr.sin_addr.s_addr = htons(INADDR_ANY)
inet_addr()与inet_aton()的区别
- inet_addr() 是返回值型
struct sockaddr_in ina; ina.sin_addr.s_addr = inet_addr("132.241.5.10"); |
- inet_aton() 是参数指针型
struct sockaddr_in ina; inet_aton("132.241.5.10",&ina.sin_addr); |
inet_ntoa 将NBO地址转化成字符串点数格式
参数:结构体变量.sinaddr
返回值:字符串指针
a1 = inet_ntoa(ina.sin_addr); printf("address 1: %s\n",a1); |
address 1: 132.241.5.10 |
inet_addr()的缺陷:必须对-1做检测处理
因为inet_addr()的结果是整型,而发生错误时返回-1。
而 ina.sin_addr.s_addr是unsigned long型
-1在long short显示成111111111,和IP地址255.255.255.255相符合!会被误认为广播地址!
PCI设备驱动 三
一个PCI设备的驱动程序必须要向内核中的PCI核心描述自己。同时,它也必须告诉PCI核心自己能够驱动哪些设备。下面,就介绍两个相关的重要数据结构。
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};
struct pci_driver {
struct list_head node;
char *name;
struct module *owner;
const struct pci_device_id *id_table; //驱动所能操纵的设备id列表。
int (*probe)(struct pci_dev *dev, const struct pci_device_id
*id); //插入新设备
void (*remove)(struct pci_dev *dev); //移除设备。
int (*suspend)(struct pci_dev *dev, pm_message_t state);
int (*resume)(struct pci_dev *dev);
int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);
void (*shutdown) (struct pci_dev *dev);
struct device_driver driver;
struct pci_dynids dynids;
};
pci_device_id唯一标识一个PCI设备。它的几个成员依次分别表示:厂商号,设备号,子厂商号,子设备号,类别,类别掩码(类可分为基类,子类),私有数据。每一个PCI设备的驱动程序都有一个pci_device_id的数组,用于告诉PCI核心自己能够驱动哪些设备。8139too的驱动程序定义它的pci_device_id数组如下:
static struct pci_device_id rtl8139_pci_tbl[];
该数组被初始化为8139系列的一组网卡,当PCI核心得到这个数组后,会拿数组中的每一项跟从PCI配置空间中读取到的数据进行比对,从而为该驱动程序找到正确的设备。而pci_driver代表一个pci驱动程序。成员id_talbe即是指向pci_device_id数组的指针。name是驱动程序的名字,probe完成探测工作,即拿pci_device_id数组与内核中的数据进行比对。remove完成驱动程序的移除工作。关键的成员就这几个。
驱动程序通过pci_module_init向内核注册自己(我们有时会看到pci_register_driver函数,其实它们是同一个,在内核代码中会看到,只是一个简单的#define):
pci_module_init(&pci_driver);
调用函数后,如果pci_device_id数组中标识的设备存在于系统中,并且该设备恰好还没有驱动程序,则该驱动程序会被安装。下面我们来看从8139too驱动代码中裁剪的pci设备初始化代码:
pci_driver.h:
/* pci_driver.h
* helinqiang@hotmail.com
* 2006-3-5
*/
#ifndef PCI_DRIVER_H
#define PCI_DRIVER_H
#include <linux/mod_devicetable.h> //for struct pci_device_id
#include <linux/module.h> //for MODULE_DEVICE_TABLE
#include <linux/pci.h> //for struct pci_driver
#define DRV_NAME "8139too"
#define DRV_VERSION "0.9.27"
#define RTL8139_DRIVER_NAME DRV_NAME " Fast Ethernet driver " DRV_VERSION
typedef enum{
RTL8139 = 0,
RTL8129,
}board_t;
static struct pci_device_id rtl8139_pci_tbl[] = {
{0x10ec, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x10ec, 0x8138, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1113, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1500, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x4033, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1186, 0x1300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1186, 0x1340, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x13d1, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1259, 0xa117, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1259, 0xa11e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x14ea, 0xab06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x14ea, 0xab07, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x11db, 0x1234, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1432, 0x9130, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x02ac, 0x1012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x018a, 0x0106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x126c, 0x1211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x1743, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
{0x021b, 0x8139, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
#ifdef CONFIG_SH_SECUREEDGE5410
/* Bogus 8139 silicon reports 8129 without external PROM :-( */
{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, RTL8139 },
Linux 设置环境变量
通过hello world介绍2.6内核模块编译的最基本原理
1、makefile的预备知识
本文旨在介绍编译模块的原理,不详细介绍makefile。
下面是一个简单的没有任何用途的makefile:
1 MAKE_TEST = make test
2
3 all:
4 @echo "make all"
5 @echo "MAKE_TEST = $(MAKE_TEST)"
6
7 test:
8 @echo "make test"
9 @echo "MAKE_TEST = $(MAKE_TEST)"本Makefile首先定义了一个变量MAKE_TEST,然后有两个目标:all和test
下面是分别执行make和make test的运行结果:
wangjiankun@driver:~/make_study$ make
make all
MAKE_TEST = make test
wangjiankun@driver:~/make_study$ make test
make test
MAKE_TEST = make test
wangjiankun@driver:~/make_study$make test的运行结果说明:虽然指定了目标test,但是目标test之前定义的变量仍然是起作用的,这是介绍这个例子的唯一目的,用来解释下面的变量KERNELRELEASE在目标modules之前定义。
2、hello world的makefile文件
1 ifneq ($(KERNELRELEASE),)
2 obj-m := hello_world.o
3
4 else
5 KERNELDIR ?= /lib/modules/2.6.19/build
6 PWD := $(shell pwd)
7 default:
8 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
9 endif上面是hello world模块的Makefile文件,当在当前目录下执行make命令时,由于变量KERNELRELEASE没有定义,所以make走else分支;
?=操作符make手册是如下解释的:
This statement:
FOO ?= bar
is exactly equivalent to this
ifeq ($(origin FOO), undefined)
FOO = bar
endif
Note that a variable set to an empty value is still defined, so ‘?=’ will not set that variable.
也就是说,第5行只有变量KERNELDIR没有被定义时,才给它赋值,所以此时给KERNELDIR赋值为:/lib/modules/2.6.19/build;
第7行,make遇到了第一个目标:default,执行第8行的命令:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules。第8行中有如下3个介绍点:(1)、-C选项是进目录的意思;(2)、定义了变量M,对于make来说,M就是一个变量,除此之外没有任何特殊的含义,make更不会因为他是module的首字母而认为是在编译模块;(3)、第8行的编译目标是modules。这样make带着两个信息(一个是变量M及其值,另一个是目标是modules)进入由变量KERNELDIR定义的目录来编译,在此是进入目录:/lib/modules/2.6.19/build。如下所示:
wangjiankun@driver:/lib/modules/2.6.19$ pwd
/lib/modules/2.6.19
wangjiankun@driver:/lib/modules/2.6.19$ ls -l
total 1313
lrwxrwxrwx 1 root root 37 Dec 14 16:18 build -> /home/wangjiankun/kernel/linux-2.6.19
drwxr-xr-x 9 root root 1024 Dec 14 16:18 kernel
-rw-r--r-- 1 root root 296327 Dec 14 16:18 modules.alias
-rw-r--r-- 1 root root 69 Dec 14 16:18 modules.ccwmap
-rw-r--r-- 1 root root 299229 Dec 14 16:18 modules.dep
-rw-r--r-- 1 root root 813 Dec 14 16:18 modules.ieee1394map
-rw-r--r-- 1 root root 730 Dec 14 16:18 modules.inputmap
-rw-r--r-- 1 root root 21916 Dec 14 16:18 modules.isapnpmap
-rw-r--r-- 1 root root 74 Dec 14 16:18 modules.ofmap
-rw-r--r-- 1 root root 226143 Dec 14 16:18 modules.pcimap
-rw-r--r-- 1 root root 1135 Dec 14 16:18 modules.seriomap
-rw-r--r-- 1 root root 135623 Dec 14 16:18 modules.symbols
-rw-r--r-- 1 root root 342460 Dec 14 16:18 modules.usbmap
lrwxrwxrwx 1 root root 37 Dec 14 16:18 source -> /home/wangjiankun/kernel/linux-2.6.19
wangjiankun@driver:/lib/modules/2.6.19$这个目录是在debian下编译内核时生成的,/lib/modules/2.6.19/build指向内核源码包。这样make就要运行内核源码包的主Makefile文件了。
3、linux 2.6内核主Makefile文件对编译模块的支持
由上面的介绍可知:make进入内核源码包带着两个信息:(1)定义了变量M(= 模块的当前目录路径)和(2)目标是modules来执行make命令。先看一下modules目标。
内核主Makefile定义了目标modules:
1202 module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD))
1203 PHONY += $(module-dirs) modules
1204 $(module-dirs): crmodverdir $(objtree)/Module.symvers
1205 $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@)
1206
1207 modules: $(module-dirs)
1208 @echo ' Building modules, stage 2.';
1209 @echo "wangjiankun noted 1"
1210 $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpostmodules目标依赖于由变量module-dirs定义的目标,所以要先编译由变量module-dirs定义的目标,1202行定义了变量module-dirs( := $(addprefix _module_,$(KBUILD_EXTMOD)) ),内核主Makefile有如下定义:
63 # Use make M=dir to specify directory of external module to build
64 # Old syntax make ... SUBDIRS=$PWD is still supported
65 # Setting the environment variable KBUILD_EXTMOD take precedence
66 ifdef SUBDIRS
67 KBUILD_EXTMOD ?= $(SUBDIRS)
68 endif
69 ifdef M
70 ifeq ("$(origin M)", "command line")
71 KBUILD_EXTMOD := $(M)
72 endif
73 endif由以上定义可知变量KBUILD_EXTMOD就是我们在模块的Makefile中定义的变量M的值,即模块的当前目录。1202行用到了make的一个函数addprefix,addprefix在make手册中如下解释:
$(addprefix prefix,names...)
The argument names is regarded as a series of names, separated by whitespace;
prefix is used as a unit. The value of prefix is prepended to the front of each
individual name and the resulting larger names are concatenated with single
spaces between them. For example,
$(addprefix src/,foo bar)
produces the result ‘src/foo src/bar’.这样变量module-dirs就等于_module_模块的当前目录路径,在我的测试系统中的值为:_module_/home/wangjiankun/ldd3/ch2
1204行目标:$(module-dirs)依赖于crmodverdir和$(objtree)/Module.symvers
内核主Makefile文件定义了目标:crmodverdir如下所示,其实就是在模块的当前目录下建立一个文件夹.tmp_versions,并将其内部文件全部删除
1190 crmodverdir:
1191 $(Q)mkdir -p $(MODVERDIR)
1192 $(Q)rm -f $(MODVERDIR)/*内核主Makefile文件定义了目标:$(objtree)/Module.symvers如下所示,其实就是检查内核源码根目录下是否存在文件Module.symvers,如果不存在打印出警告信息,但是模块仍然可以成功编译。
1195 $(objtree)/Module.symvers:
1196 @test -e $(objtree)/Module.symvers || ( /
1197 echo; /
1198 echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; /
1199 echo " is missing; modules will have no dependencies and modversions."; /
1200 echo )
1205行的$(patsubst _module_%,%,$@)就是变量module-dirs的值:_module_/home/wangjiankun/ldd3/ch2除去_module_的结果,即:/home/wangjiankun/ldd3/ch2。表达式中的$@意为:The file name of the target.
$(build)的值为:-f scripts/Makefile.build obj(没有找到在什么地方定义,待查,此处是在我的测试系统上,通过echo打印出来的),这样1205行的实际语句为:
@make -f scripts/Makefile.build obj=/home/wangjiankun/ldd3/ch2
scripts/Makefile.build文件比较复杂没有详细研究,但有一点是肯定的,make二次执行模块目录下的Makefile文件就是通过这个脚本直接或间接进行的(目前猜测,可能不是二次执行了模块的Makefile文件,而是将模块的Makefile文件包含到了内核的build系统的脚本或Makefile中使得obj-m := hello_world.o起作用而编译了模块)。通过这个脚本,make二次执行模块的Makefile文件,此时变量KERNELRELEASE已经定义,内核主Makefile有如下定义:
320 # Read KERNELRELEASE from include/config/kernel.release (if it exists)
321 KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)include/config/kernel.release文件内容如下:
wangjiankun@driver:~/kernel/linux-2.6.19/include/config$ pwd
/home/wangjiankun/kernel/linux-2.6.19/include/config
wangjiankun@driver:~/kernel/linux-2.6.19/include/config$ cat kernel.release
2.6.19
wangjiankun@driver:~/kernel/linux-2.6.19/include/config$也就是说,此时KERNELRELEASE不但定义了,而且值为:内核版本号,所以make走第2行的分支并通过隐含规则将hello_world.c编译成hello_world.o文件。
make回到1208行打印出:Building modules, stage 2.,并通过1210行生成模块。
eFuse技术
| |
eFuse的诞生源于几年前IBM工程师的一个发现:与更旧的激光熔断技术相比,电子迁移(EM)特性可以用来生成小得多的熔丝结构。EM熔丝可以在芯片上编程,不论是在晶圆探测阶段还是在封装中。采用I/O电路的片上电压(通常为2.5V),一个持续200微秒的10毫安直流脉冲就足以编程单根熔丝。
不同于大多数FPGA使用的SRAM阵列,eFuse一次只有一根熔丝能够被编程,这是该方法的配置能力存在限制范围的原因。但当与日益成熟的内置自测试(BIST)引擎组合使用时,这些熔丝就变成了强大的工具,能减少测试和自修复的成本,而这正是复杂芯片设计所面临的重大挑战。
eFuse就好像在硅片上建立了无数个交通岗哨,控制信号的传输或停止,据悉这将把芯片中的电路运行效率提高上千倍。这种功能将会为电子领域带来一种“大规模市场效应。”FPGA提供商加州Xilinx公司CTO里奥.波尔森先生表示:“比如您购买了一个新的控制器,最开始的时候控制器的功能是空的,不过在把它带回家后,它重新识别了您家中的所有系统,电视、音响、DVD、并且自动对自身进行改造,来控制这些电器。”