5
13
2014
37

pcmfs的mkfs分析

在文章最前面说一下mkfs的意义:即在dev设备上建立相应的数据布局。也就是你输入mkfs指令后,要跟上一个设备参数,于是乎,mkfs执行完毕后,就有了常见的数据布局。

在我们这个mkfs函数中,最终数据布局如下

 

 

一:函数流程图

下面是mkfs.pcmfs.c函数流程图

          图1 流程图

解释:

1.根据参数获取blocksize

   在输入mkfs指令时,要输入设备参数。如mkfs.pcmfs /dev/sdb1。该指令就是把/dev/sdb1设备格式化为pcmfs文件系统。Main函数会获取参数的设备名,然后通过get_size函数计算出该设备的字节数。在计算出设备的字节数后,在根据blocksize大小计算出该设备的block总数。    get_size函数获取设备大小主要通过count_block函数实现。基本思想是通过read函数返回值得正负来判断是否越界。该函数先利用二乘的思想来确定该dev的粗略的范围,即n未越界,则下次判断2n是否越界。若2n越界则可判断该设备大小在n与2n之间,粗略判断后在通过在n和2n之间的二分查找确定该设备的具体大小。

2.计算block数目

  在上述操作后获取了该设备的字节数,于是可以通过BLOCKS=get_size(device_name)/blocksize,来得到该设备包含的block数目。

3.初始化superblock以及bitmap所需要的数据结构

    Mkfs的目的是格式化非易失介质,即将非意识介质上文件系统的必要的信息准备好,而superblock以及bitmap的结构是文件系统格式化时写入非易失介质的重要信息,将该信息写入到非意识介质中有两步,第一步是在内存中申请一个对应的空间,将该空间的内容赋值成我们所需要写入非易失介质中的数据;第二步就是通过调用write函数将该数据写入到介质当中。

    mkfs.pcmfs.c文件通过setup_tables函数在内存中准备好要写入非易失介质中的superblock信息以及bitmap区域的信息,其中bitmap有inode的bitmap以及block的bitmap。分别对应着IMAPS以及ZMAPS。

    Superblock的该文件系统整体信息的赋值,如第一个data区域所在的block编号,IMAP、ZMAP所占的block数目的赋值等。在该pcmfs文件系统中inode的bitmap占两个block,block的bitmap占8个block。

    同样在内存中申请IMAPS和ZMAPS所占的空间大小,然后通过memset的函数把该区域的数据置0。

4. 初始化根节点inode的数据结构

    在初始化文件系统时,必须初始化根节点的信息,整个系统才有了开始索引、操作的入口。根节点所指向的数据区域是整个data区的第一个数据块。即inode->i_zone[0] =Super.s_firstdatazone;

    由于根节点是一个目录,所以其数据区其实就是一个个的目录项。每个目录项占16个字节。目录项的定义在头文件Pcmfs_fs.h中。目录项如下

struct pcmfs_dir_entry {

       __u16 inode;

       char name[0];

}

   初始化是需要在目录项中写入两个内容。即当前目录的inode号,以及父目录的inode号。由于为根节点,故父目录的inode号与当前目录inode号实则是一样的。写入的两项内容如下

#define ROOT_INO_STRING "\001\000"

static char root_dentry[BLOCK_SIZE] =

ROOT_INO_STRING ".\0\0\0\0\0\0\0\0\0\0\0\0\0"

ROOT_INO_STRING "..\0\0\0\0\0\0\0\0\0\0\0\0";

其中ROOT_INO_STRING是inode号,".\0\0\0\0\0\0\0\0\0\0\0\0\0"为该inode号对应内容。这里实则填充了两项,表示的是当前目录 . 以及父目录 .. 的inode号。为了方便理解,见下图

图2 dentry结构图

如上图,每个目录的数据区都可理解为上述表,每一个表项有16个字节,前两个表项的文件名是固定的,分别是当前目录对应的inode号以及父目录对应的inode号。上述初始化实则初始前两项。在内存中准备好该数据后,在通过write函数写到非易失介质中。

5.将初始化的数据写到介质中

   该功能更通过write_tables函数实现。该函数主要通过调用write函数将准备好在内存中的超级块的内容以及bitmap的内容写入到介质中。这里指的注意的是write函数要按顺序写。由于pcmfs文件系统的整体结构图如下

图3 pcmfs文件系统结构图

    仔细观察上图。inode bitmap 占2个block;data bitmap占8个block。故该文件系统设计中,理想情况,每个inode索引节点只是的文件占4个block(8/2=4),这样就不会出现data bitmap用完,而inode bitmap和inode table未用完 或者 inode bitmap和inode table用完,而data bitmap未用完的情况。

   故在调用write函数,要按顺序写入,即先写superblock,然后写bitmap区域。因为write函数写的区域是设备上当前指针所指向的区域。在写过程中,指针后移,故要按顺序写入。

   另外注意一点的是图3中若block大小为1k,(1024B),则inode bitmap的起始block为第3个block。若为blokc大小大于1k,如为2k,3k等等,则预留字节和super block的字节都可以写在第一个block中,即inode bitmap的起始block为第2个block

   至此,设备的mkfs工作完毕。

 

Category: 文件系统 | Tags:
5
13
2014
8

pcmfs初始化过程

此文是基于基于minxfs修改的文件系统得到的,不过文件系统大体的流程是大同小异的,故初始化过程其实与ext2,ext3是通用的

inode.c中有模块的入口。即module_init(init_pcmfs_fs).初始化这儿主要分析的pcmfs_fill_super函数。该函数即mount时从磁盘读取数据,填充内存超级块字段信息。该函数主要做了如下三件事情

1.sbi = kzalloc(sizeof(struct pcmfs_sb_info), GFP_KERNEL);申请内存超级快的空间,然后把磁盘超级块的内容拷贝到其中。在此处需要把VFS的 s->s_fs_info = sbi;赋值,即全局有很多文件系统数据结构,如ext2,ext3等。此处即指定当前为该sb代表的文件系统

2.申请bitmap表的空间。然后根据bitmap使用情况算出s_used_inode以及s_used_block

i = (sbi->s_imap_blocks + sbi->s_zmap_blocks) * sizeof(bh);

map = kzalloc(i, GFP_KERNEL);//step2:申请空间2 bitmap位
 
3.获取根目录的信息。即根目录所指向的数据块的dentry信息。root结点的数据信息为第一个数据块中的信息
root_inode = pcmfs_iget(s, PCMFS_ROOT_INO);
Category: 文件系统 | Tags:
5
11
2014
0

C/C++中函数、变量为何使用static

在C语言中,即非面向对象,与如下三点好处

1.用static修饰某个函数、变量,可以不用担心和别文件中的函数、变量重名问题。因为未加static则默认全局可见,这一功能就可以称为隐藏功能(函数唯一好处;非函数还可见2、3点)。

2.在运行过程中保持值不变。存储在静态数据区的变量只会在函数开始时初始化一次。有两种变量存储在静态数据区,一种是全局变量,另一种就是static。两者区别,全局变量全局可见、static隐藏了可见性,即该文件可见。看如下代码

#include <stdio.h>

int fun(void){
    static int count = 10;    // 函数初始化时赋值
    return count--;
}

int count = 1;

int main(void)
{    
    printf("global\t\tlocal static\n");
    for(; count <= 10; ++count)
        printf("%d\t\t%d\n", count, fun());    
    
    return 0;
}

运行结果如下

global          local static

1               10

2               9

3               8

4               7

5               6

6               5

7               4

8               3

9               2

10              1

3.自动初始化。数值类型自动初始化为0,static char *a[]初始化为一个串尾符。

#include <stdio.h>

int a;

int main(void)
{
    int i;
    static char str[10];

    printf("integer: %d;  string: (begin)%s(end)", a, str);

    return 0;
}

结果为integer: 0; string: (begin)(end)

 

若为面向对象,则:

1.在类中的static成员变量意味着被类的所有实例共享,即某个实例修改了改对象,则该修改对其他所有实例可见。

2.类中的static成员函数无this指针,故只能访问static成员变量。且还可以退出通过类名就可以访问成员和函数

3.指向static成员指针和指向static函数指针都是普通指针,即存放的是绝对地址。这一点同全局变量和全局函数,因为其特点差不多,但是类中的普通函数指针和普通成员指针是相对地址,即相对于这个类起始地址的偏移值,如下原因:1类的成员函数需要通过一个对象来调用

4.由于长期占有存储空间,故局部类不能定义静态数据成员,因为局部类的生存期和局部静态成员的生存期矛盾

5.union数据成员共享空间,而静态数据成员占有各自独立的存储单元。故union{static int a;static int b}错误

6.注意static修饰类的成员函数时不能与friend同时使用,因为friend是友元,而非自己的成员函数

参考:

http://blog.163.com/sunshine_linting/blog/static/4489332320119785228616/

http://zhidao.baidu.com/link?url=8D2YngLhHg2k9Ma10tJgKK4KykkJWcrjuJWx-v2xjcZvVTFn9oNr08xcJOY8_P2WoQH3sB4gBxemHJboLVw0qK

Category: 编程理解 | Tags: static
5
9
2014
20

restudy of c

1 include"" 和include<>区别。

  前者在当前目录下找头文件,找不到再到系统目录下找,而include<>,直接在系统目录下找。所以include""涵盖了include<>

2.incline 声明函数,表示内联函数,内联函数即在调用处直接展开函数,而并非取调用函数,故不用保存现场,效率得到提高

3 宏定义简单函数,如#define TABLE_COMP(x) ((x)>0?(x):0)

  优点:若函数调用,则要将函数的执行顺序转移到函数所存放的某个地址,将函数程序内容执行完后,在返回到转去执行原函数,故要保护现场。所以函数调用有时间、空间的开销,效率低。而宏是在预处理的地方把代码展开,五上述额外时间、空间开销。

  缺点:容易产生二义性 如TABLE_MULTI(10+10)会(10+10*10+10),而非期望的400

4.递归代码简介,但缺点(1)递归是函数调用自身,函数调用存在时间消耗:每一次函数调用,需要在内存栈中分配空间以保存参数、返回地址及临时变量,栈的压入和弹出都需要时间。故递归实现不如循环。(2)调用栈溢出。每一次调用在内存栈中分配空间,每个进程栈的容量有限,若递归调用的层级太多,就会溢出

5.计算机表示小数(float和double)都有误差,故不能直接用等号判断两个小数是否相等。如果两个小数的差值很小,比如0.0000001,就可以认为它们相等

6.取模操作 % 操作数两边都应该为整形 如 12%3 (对) 2.2%1 (错) 2%1.1(错) 2.2%1.1(错)

7 float = int/int 则除出来的数已经自动省掉了小数点后面的余数。如 f1=int1/1024,可以改为 f1=int1/1024.0

8.sleep()内的单位为秒,gcc编译,在头文件#include<unistd>中

9.若要获取运行程序进程号,linux除了通过ps指令外。还可以通过getpid函数,该函数也在unistd.h中

/*process.c*/
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char **argv)
{
 pid_t my_pid,parent_pid;
 uid_t my_uid,my_euid;
 gid_t my_gid,my_egid;
 struct passwd *my_info;
 my_pid = getpid();
 parent_pid = getppid();
 my_uid = getuid();
 my_euid = geteuid();
 my_gid = getgid();
 my_egid = getegid();
}

10 strcmp函数使用strcmp("sundayhut",myname);注意不要写成strcmp("sundayhut\n",myname);

11 sscanf用法

sscanf(temp,"%s%s%s%s%s%s%s%[^\n]",last_major_minor,last_cpu_id,last_sequence_number,last_time,last_process_id,last_action,last_RWBS,last_detail);
	

读入文件形式如下:

  8,0    0        1     0.000000000  2113  A   W 38384096 + 8 <- (8,1) 38382048
  8,1    0        2     0.000001649  2113  Q   W 38384096 + 8 [blktrace_study]
  8,1    0        3     0.000055171  2113  G   W 38384096 + 8 [blktrace_study]
  8,1    0        4     0.000234077  2113  P   N [blktrace_study]
  8,1    0        5     0.000362702  2113  I   W 38384096 + 8 [blktrace_study]
  8,1    0        0     0.000362704     0  m   N cfq640 insert_request
  8,1    0        0     0.000362705     0  m   N cfq640 add_to_rr
  8,0    0        6     0.000362707  2113  A   W 38384104 + 8 <- (8,1) 38382056
  8,1    0        7     0.000362709  2113  Q   W 38384104 + 8 [blktrace_study]

%s能自动过滤掉空格,%[^\n]可以读入行直到行尾部

 

12.若open函数用O_DIRECT方式fd=open("/home/systemtap_study/dir1/25.txt", O_RDWR | O_CREAT | O_DIRECT);

则在*.c文件中需要

#define __USE_GNU

且编译时需要加上-D_GNU_SOURCE

gcc -o out code.c -D_GNU_SOURCE

 

13.打印unsigned long

unsigned long block;

printk("new block %lu\n",block);

 

14.编译错误

编译出现如下错误

/home/tss/share/7-7/printk-pcmfs/pcmfs/pcmfs/inode.c:417: warning: ISO C90 forbids mixed declarations and code

iso c90 不允许混合使用声明和代码

解决方法是,printk语句不能再变量声明前使用

例如:void fun()

{

printf("This is a strange World!\n");

int i;

char buf[128];

}

改正方法:

void fun()

{

int i;

char buf[128];

printf("This is a strange World!\n");

}

 

Category: 未分类 | Tags: C语言
5
9
2014
7

bitmap操作

一下代码实现了位图的基本操作

/*以addr指向位置为起始地址,第nr个bit位的操作
  内容来自于文件系统中内核源码。bitmap.h
*/


//返回1,则该bit位为1
static  int happyhut_test_bit(unsigned int nr,char * addr) 
{
   //addr为char * 类型,一个char* 为8个bit,故addr[nr>>3],使其指向该nr对应的字节
   //(1<<(nr&7))确定该字节要test的bit位
  return (addr[nr >> 3] & (1<<((nr & 7)))) != 0;
}

static  int happyhut_set_bit(unsigned int nr,char * addr)
{
  int __res = happyhut_test_bit(nr,addr);
  addr[nr >> 3] |= (1<<((nr & 7)));//按位或,置1
  return __res != 0; \
}
//置0
static  int happyhut_clear_bit(unsigned int nr,char * addr)
{
  int __res = happyhut_test_bit(nr,addr);
  addr[nr >> 3] &= ~(1<<((nr & 7)));
  return __res != 0;
}
static int happyhut_test_and_clear_bit(unsigned int nr,char * addr)
{
	int __res = happyhut_test_bit(nr,addr);
	if(__res)//返回值为1,则该bit位为1
	happyhut_clear_bit(nr,addr);
	return __res;
}
static  int happyhut_test_and_set_bit(unsigned int nr,char * addr)
{
       int __res=happyhut_test_bit(nr,addr);
	 if(!__res)
	 happyhut_set_bit(nr,addr);
	 return __res;
	 	
}
//找第一个0的bit位
static int happyhut_find_first_zero_bit(char *addr,unsigned int size)
{
 unsigned int index;
 for(index=0;index<size;index++)
	 if(!happyhut_test_bit(index,addr))
		 return index;
 return index;
}

下面看图,帮助理解。addr指向一个char,char占8个字节

 

Category: 编程理解 | Tags:
5
6
2014
21

sizeof函数

sizeof函数返回时所占的字节个数。同一段代码在32位机器和64位机器上的sizeof返回值可能不同。代码如下

#include"stdio.h"
struct{}str;
struct{int a;}str1;

void main(int argc,char *argv)
{
	char *string="";
	char *s="1",*s1="123456789012345",a[3]="1";	
	int i,j1,j,k,k1,k2;
	i=sizeof(string);
	j=sizeof(s);
	j1=sizeof(s1);
	k=sizeof(a);
	k1=sizeof(str);
	k2=sizeof(str1);
	printf("%d  %d  %d  %d  %d  %d\n",i,j,j1,k,k1,k2);
}

在32位机器和64位机器的运行结果如截图。注意该结果是gcc 编译c的规则的结果

 

有如下几点总结:

1.在gcc编译器下编译c,struct中间是空,则sizeof返回0。这点与下面g++编译器对比

2.char *s="124342";sizeof(s);大小与s中的内容多少无关系。都是分配了一个固定大小,即指针所占的字节数。

 

再看下面代码:gcc编译

#include"stdio.h"
struct{}str1;
struct{int a;}str2;

int main(int argc,char *argv)
{
	char *s1="";
	char *s2="1",*s3="123456789012345",s4[3]="1",s5[]="123456789";	
	int i,i1,j1,j2,j3,j4,j5,j,k,k1,k2,k3;
	i=sizeof(s1);
	j=sizeof(s2);
	j1=sizeof(s3);
	k=sizeof(s4);
	k3=sizeof(s5);//
	k1=sizeof(str1);
	k2=sizeof(str2);
	i1=sizeof(*s1);//s1为char * 类型,*s1其实就是去其指向的char,故sizeof=1,一个字节
	j2=sizeof(*s2);
	j3=sizeof(*s3);
	j4=sizeof(*s4);
	j5=sizeof(*s5);//s5为指向"123456789"中的1的指针,故sizeof结果为1字节
	printf("s1=%d  s2=%d  s3=%d  s4=%d  s5=%d  str1=%d  str2=%d\n",i,j,j1,k,k3,k1,k2);
	printf("s_1=%d  s_2=%d  s_3=%d  s_4=%d  s_5=%d\n",i1,j2,j3,j4,j5);
	return 1;
}

运行结果如下。

我们可以看出结果在注释中

 


再看如下一段cpp代码:该段代码g++编译cpp类型

#include"stdio.h"
#include <iostream>
using namespace std;
struct circle0
{	
}cir0;
struct circle1
{
	int radius;
}cir1;
struct circle2
{
	int radius;
	circle2() {radius=1.0;}
}cir2;
struct circle3
{
	int radius;
	circle3() {radius=1.0;}
	~circle3(){radius=0;}
}cir3;
struct circle4
{
	int radius;
	circle4() {radius=1.0;}
	virtual ~circle4(){radius=0;}
}cir4;
struct circle5
{
	int radius;
	circle5() {radius=1.0;}
	virtual void f1(){cout<<"f1()\n";}
	virtual ~circle5(){radius=0;}
}cir5;
struct circle6
{
	int radius;
	circle6() {radius=1.0;}
	virtual void f1(){cout<<"f1()\n";}
	virtual ~circle6(){radius=0;}
	void f2() {cout<<"f2()\n";}
}cir6;
int main()
{
	int c0,c1,c2,c3,c4,c5,c6,c_0,c_1,c_2,c_3,c_4,c_5,c_6;
	c0=sizeof(cir0);
	c1=sizeof(cir1);
	c2=sizeof(cir2);
	c3=sizeof(cir3);
	c4=sizeof(cir4);
	c5=sizeof(cir5);
	c6=sizeof(cir6);
	c_0=sizeof(circle0);
	c_1=sizeof(circle1);
	c_2=sizeof(circle2);
	c_3=sizeof(circle3);
	c_4=sizeof(circle4);
	c_5=sizeof(circle5);
	c_6=sizeof(circle6);
	printf("c0:%d  c1:%d  c2:%d  c3:%d  c4:%d  c5:%d   c6:%d\n",c0,c1,c2,c3,c4,c5,c5);
	printf("c_0:%d c_1:%d c_2:%d c_3:%d c_4:%d c_5:%d c_6:%d\n",c_0,c_1,c_2,c_3,c_4,c_5,c_6);
	
	return 1;
}

运行结果

我们可以得出如下结论

1.c编译时struct的结构如果为空,则sizeof(struct)结果为0;而c++编译则不然,结果为1

2.构造函数、析构函数、非virtual函数不占struct的空间,因为调用函数只需要知道函数的地址,而函数的地址只与类型有关,而与类型的实例无关,编译器不会因为函数而在实例中添加额外信息

3.struct中若有virtual声明的函数,则多一个指针类型的空间。因为若有虚函数,则会为该类型添加一个虚类型的表,该实例就会有一个指向虚类型的指针。故多一个指针的空间。故一个virtual函数和两个virtual函数实际上都只是多一个指针类型。

 

 


Category: 编程理解 | Tags:
5
5
2014
2

virtual box 中 Ubuntu安装增强包

virtual box 中安装增强包后可以实现windows中的文件夹内容在linux中直接访问,而不需要通过u盘转文件到linux中,很方便。方法:

step1:设备->安装增强功能。在弹出的文件夹框中点击运行 VBoxLinuxAddtions.run

       若没有弹出框,则cd /media/VBOXADDTIONS_*。然后在该文件夹下输入./VBoxLinuxAdditions.run。安装好后再reboot。我当时输入的指令如下:

 

step2:设备->共享文件夹。在出现的界面中添加一个固定分配的文件夹。例如,我在windows的D盘底下建立了一个目录V-box。作为windows目录中linux访问点。

step3:在linux的终端中建立文件。然后mount。指令

       mkdir share 

       mount -t vboxsf V-box share   其中V-box, 是step2中文件名字,share是你该文件夹在linux中的名字。

我的指令如下

 

此时linux中的share文件就是共享的windows的D盘中V-box的数据内容了

 


ps:后来我开机在mount -t vboxsf V-box share时,出现如下提醒

sbin/mount.vboxsf:mounting failed with the error:no such device

然后我做法是

重新进入cd /media/VBOXADDTIONS_*。然后在该文件夹下输入./VBoxLinuxAdditions.run。

再mount -t vboxsf V-box share

 

Category: Linux | Tags:
4
22
2014
3

关于怎么写MakeFile

先看下面一个MakeFile的例子

cache: test.o happyhut_list.o
	gcc -g test.o happyhut_list.o -o cache -lpthread 

happyhut_list.o: happyhut_list.c happyhut_list.h plus.h
	gcc -g -c happyhut_list.c -o happyhut_list.o

test.o: test.c plus.h raid.h
	gcc -g -c test.c -o test.o

clean:
	rm -rf *.o cache

编译的过程从下往上执行。首先执行clean部分,rm -f表示force,强制执行,-r表示递归删除,即目录和子目录

然后生成test.o文件

test.o:冒号后的东西表示生成test.o所需要的东西。

以此往上生成。最后生成可执行文件cache。

gcc -g 是为了gdb调试,-c 表示只编译,不链接。

下面说说源代码变成可执行代码在内存中执行的三个步骤:

编译、链接、载入

1.编译:将原代码编译成若干个目标模块。

2.链接:将编译后形成的目标模块和库链接在一起,形成一个完整的载入模块。

3.载入:将载入模块载入内存

具体可以看

http://blog.csdn.net/monkey_d_meng/article/details/5651649


Category: 脚本语言 | Tags:
4
22
2014
23

关于char *用法

关于一下

char * pChar;

if(NULL==(pChar=(char *)malloc(siziof(char)*200)))printf("malloc error\n")

memset((void*)pChar,0,200);//把200个字节内容清零

print("pChar[0]=%c",pChar[0]);

i=(pChar[0]==0;)printf("~~~%ld\n",i);

pChar[0]=1;

print("pChar[0]=%c",pChar[0]);

i=(pChar[0]==0;)printf("~~~%ld\n",i);

pChar[0]=48;

print("pChar[0]=%c",pChar[0]);

i=(pChar[0]==0;)printf("~~~%ld\n",i);

其实*pChar的内容置0,其实就是置为串尾符。

执行结果是:

由上面的结果也可以看出pChar=0其实是赋值NULL。清空

pChar=0,或者pChar=1,进行时直接的acsii值表的赋值。直接查国际Ascii表就可以知道结果了。比如数字0对应的ascii表值为48,则:

print("pChar[0]=%c",pChar[0]);的结果和ASCII码表相对应。故pChar[0]=48;则print("pChar[0]=%c",pChar[0]);打印的结果pChar[0]=0

 

 

故可以有如下操作

...

i=**;

if(pChar[i]==0)pChar[i]=1;

...

即虽然pChar[i]为char类型,但是可以直接赋值0,1.即按照ACSII码表来赋值

Category: 编程理解 | Tags:
3
28
2014
2

牛掰的工具才是王道之------visio2013

最近越发感叹一个好的工具的重要性。之前画图一直用visio2007版。这软件相比以前直接用office画流程图之类好用太多。但昨天想画一个光盘库的图,弄了好久也觉得画得不满意。visio2007中画平行四边形的图形都没有,还得一笔一划的画。。。。无语。。。不过还是硬着头皮给画了几个。

今日看到了老师画的三维图,跟我的相比高大上哇!!后缀是.vsdx(我以前visio2007年版的默认后缀为.vsd)。果断自己装了一个2013版的visio。我想说好用多了!!各种方便。。。以下是我画的图

我这画的是一个光盘匣,顺便提示一下:最右边那一竖我原本最后才画上,因此遮挡了光盘。通过如下置于底层的选项我把它放到了弹出的光盘下方:

开始->置于底层

Category: 未分类 | Tags:

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com