将文件系统安装到指定的分区

本文详细阐述了硬盘驱动程序的修正与文件系统的设计过程,包括修正硬盘驱动进程的栈空间,实现获取分区信息的宏,设计超级块、i结点映射表、扇区映射表与i结点数组等关键数据结构,并最终完成文件系统的初始化与填充。

    开始之前先修正一个BUG,害叔找了半天,就是硬盘驱动进程的栈空间太小了,搞得栈溢出波及到其它的数据,在proc.h把栈空间设为2K就好。

    下面再来补充硬盘驱动程序,我们先取得对应分区的信息,则先增加相应的宏:

    include/const.h

Code:
  1. #define HD_GET_PRT_INFO             5           /* 得到指定分区的信息 */  

    再在硬盘驱动进程的执行体中添加:

    kernel/hd.c

Code:
  1. case HD_GET_PRT_INFO:   
  2.     Get_Prt_Info(&m);   
  3.     break;  

    接着添加Get_Prt_Info函数,hd.c追加:

Code:
  1. /*------------------------------------------------------------------Get_Prt_Info  
  2.     获得指定分区(设备号指出)的信息  
  3. */  
  4. static void Get_Prt_Info(Message *m)   
  5. {   
  6.     int device = m->i1;     /* 得到设备号 */  
  7.     /* 得到要返回的缓冲区,强制转换为分区信息结构 */  
  8.     Prt_Info *ret_prt_info = (Prt_Info*)m->r1;   
  9.     /* 主分区 */  
  10.     if(device >= 0 && device <= 5)   
  11.     {   
  12.         ret_prt_info->base = hd_prt_info.primary[device].base;   
  13.         ret_prt_info->sec_num = hd_prt_info.primary[device].sec_num;   
  14.     }   
  15.     /* 扩展分区 */  
  16.     else if(device >=16 && device <=80)   
  17.     {   
  18.         ret_prt_info->base = hd_prt_info.logical[device - 16].base;   
  19.         ret_prt_info->sec_num = hd_prt_info.logical[device - 16].sec_num;   
  20.     }   
  21. }  

    OK,现在来进行文件系统的设计,文件系统有两方面的意思:一是在硬盘上的一组数据结构,二是一组能对与之对应的数据结构管理文件的机制,我们先来做物理意义的工作,就是数据结构的定义。

    在前面写boot和loader的时候已经接触过这方面的内容,就是FAT12文件系统。在这里我们的文件系统也很类似。

    在指定的分区中,0扇区为引导扇区,我们单纯的在里面放置引导代码,先不管它。

    下一步就是超级块,放在1扇区,记录此文件系统的各种信息,比如最多可以存放多少个文件等等,下面给出结构,以下结构均在include/fs.h中定义:

Code:
  1. /* 超级块 */  
  2.     typedef struct s_super_block   
  3.     {   
  4.         u32 identity;           /* 表示本文件系统的信息 */  
  5.         u32 inode_num;          /* i结点的个数 */  
  6.         u32 sector_total;       /* 此文件系统所在分区的扇区总数 */  
  7.         u32 imap_sec_total;     /* i结点映射表所占用的扇区总数 */  
  8.         u32 smap_sec_total;     /* 数据区的扇区的使用映射表的扇区总数 */  
  9.         u32 first_data_sec;     /* 数据区的第一个扇区的索引 */  
  10.         u32 inodes_sec_total;   /* i结点数组所占用的扇区数 */  
  11.         u32 root_dir_inode;     /* 根目录所占用的文件所属的i节点索引 */  
  12.         u32 inode_size;         /* 一个i结点的字节数 */  
  13.         u32 dir_entry_size;     /* 一个目录项的字节数 */  
  14.     }Super_Block;   
  15.        
  16.     #define SUPER_BLOCK_SIZE    10 * 4  /* 超级块占用的字节数 */  

    再下面是i结点的映射表,放在1扇区。记录i结点的使用情况,占据一个扇区,每一位记录一个i结点的使用情况,1代表已使用,0表示未使用。这样就可以记录512*8=4096个i结点,就是说此文件系统最多可以记录4096个文件,但索引为0的i结点不使用,表示错误的i结点。根目录也是一个文件,占用一个i结点。并且3个TTY也算是一个文件,这是借鉴LINUX的结果。所以最多只有4096-5=4091个文件。i结点的作用稍后再说。

    接着是文件数据区的扇区部分的映射表,起始扇区在2扇区,作用与i结点的映射表一样,用来管理扇区的使用情况,占据扇区个数为:整个硬盘的扇区数/512*8,我们知道实际上有些扇区是不能来存放数据的,这里懒得算了,就当浪费一些扇区吧,但要在这个表的第0位是代表实际的数据区的第一个扇区的使用情况的,这里需要注意。还有第0个数据扇区我们是不用的。

    再下面是i结点数据,,i结点的作用是指明一个文件的类型,占用的扇区大小等信息,下面是定义:

Code:
  1. /* i结点 */  
  2. typedef struct s_i_node   
  3. {   
  4.     u32 i_mode;             /* 访问模式 */  
  5.     u32 i_size;             /* 所表示的文件实际占用的字节数 */  
  6.     u32 i_start_sec;        /* 所表示的文件的首扇区的索引 */  
  7.     u32 i_sect_total;       /* 所表示的文件实际占用的扇区数,是固定的 */  
  8.     u8  unused[16];         /* 无意义,用于对齐 */  
  9. }I_Node;   
  10.   
  11. #define I_NODE_SIZE         8 * 4   /* 一个i结点所占用的字节数 */  

    最后就是根目录区了,是一个Dir_Entry数组,根目录区也算一个文件,所以它的起始扇区位于数据区的第1个扇区,第0个扇区不使用,这里的结构如下:

Code:
  1. #define MAX_FILENAME_SIZE   12      /* 一个文件的最长的文件名 */   
  2.        
  3. typedef struct s_dir_entry    
  4. {   
  5.     u32 inode_index;                    /* 此文件所属的i结点的索引 */  
  6.     char file_name[MAX_FILENAME_SIZE];  /* 文件名 */  
  7. }Dir_Entry;   
  8.        
  9. #define DIR_ENTRY_SIZE  sizeof(Dir_Entry)   /* 一个根目录文件项的字节大小 */  

    OK,整个系统就设计好了,下面的工作就是在实际的硬盘上填充这些信息了。

    kernel/fs.c

Code:
  1. /*-----------------------------------------------------------------------Init_FS  
  2.     初始化文件系统,取得硬盘的分区信息  
  3. */  
  4. static void Init_FS()   
  5. {   
  6.     Message m;   
  7.     m.msg_type = HD_INFO;   
  8.     Send_Receive_Shell(BOTH,PROC_HD_PID,&m);   
  9. }   
  10.   
  11. /*-----------------------------------------------------------------------Make_FS  
  12.     把文件系统的信息写到磁盘中  
  13. */  
  14. static void Make_FS()   
  15. {   
  16.     /* 取得文件系统的宿主分区的信息 */  
  17.     Message m;   
  18.     Prt_Info prt_info;   
  19.        
  20.     m.msg_type = HD_GET_PRT_INFO;   
  21.     m.i1 = FS_DEV;   
  22.     m.r1 = &prt_info;   
  23.     Send_Receive_Shell(BOTH,PROC_HD_PID,&m);   
  24.        
  25.     Printf("FILE SYSTEM PARTITION HAS 0x%x SECTORS/n",prt_info.sec_num);   
  26.        
  27.     /* 开始填充填充超级块 */  
  28.     Super_Block sb;   
  29.     sb.identity = IDENTITY;                 /* 填充文件系统的标识 */  
  30.     sb.inode_num = 4096;                    /* i结点的个数 */  
  31.     sb.sector_total = prt_info.sec_num;     /* 此文件系统所在分区的扇区总数 */  
  32.     sb.imap_sec_total = 1;                  /* i结点映射表所占用的扇区总数 */  
  33.     sb.smap_sec_total = prt_info.sec_num / 4096 + 1;                /* 数据区的映射表所占的扇区数 */  
  34.     sb.inodes_sec_total = I_NODE_SIZE * sb.inode_num / SECTOR_SIZE; /* i结点数组所占用的扇区数 */  
  35.     /* 数据区的第一个扇区的索引 */  
  36.     sb.first_data_sec = 1 + 1 + sb.imap_sec_total + sb.smap_sec_total + sb.inodes_sec_total;           
  37.     sb.root_dir_inode = 1;                  /* 根目录所占用的文件所属的i节点索引 */  
  38.     sb.inode_size = I_NODE_SIZE;            /* 一个i结点的字节数 */  
  39.     sb.dir_entry_size = DIR_ENTRY_SIZE;     /* 一个目录项的字节数 */  
  40.        
  41.     Memory_Set((u8*)FS_BUF,0,FS_BUF_SIZE);  /* 缓冲区清零 */  
  42.     Memory_Copy((void*)FS_BUF,&sb,sizeof(Super_Block)); /* 复制超级块到缓冲区中 */  
  43.        
  44.     Write_Sector(FS_DEV,1); /* 写入硬盘 */  
  45.        
  46.     /* 打印文件系统各部分在分区的偏移字节 */  
  47.     Printf("DEV:0x%x00 SUPER BLOCK:0x%x00/nINODE MAP:0x%x00 SECTOR MAP:0x%x00/nINODE ARRAY:0x%x00,FIRST DATA SECTOR:0x%x00/n",   
  48.             prt_info.base * 2,(prt_info.base + 1) * 2,(prt_info.base + 2) * 2,   
  49.             (prt_info.base + 3) * 2,(prt_info.base + 3 + sb.smap_sec_total) * 2,   
  50.             (prt_info.base + sb.first_data_sec) * 2);   
  51.                
  52.     /* 填充i结点映射表 */  
  53.     Memory_Set((u8*)FS_BUF,0,SECTOR_SIZE);  /* 缓冲区清零 */  
  54.     /*   
  55.         前5个文件被占用,分别是:  
  56.         保留的i结点  
  57.         根目录区代表的文件  
  58.         TTY0-3  
  59.     */  
  60.     *(u8*)FS_BUF = 0x1f;               
  61.        
  62.     Write_Sector(FS_DEV,2); /* 写入硬盘 */  
  63.        
  64.     /* 填充扇区使用映射表 */  
  65.        
  66.     /* 先把硬盘中相关的扇区清零 */  
  67.     Memory_Set((u8*)FS_BUF,0,FS_BUF_SIZE);  /* 缓冲区清零 */  
  68.     int i;   
  69.     int j;   
  70.     for(i = 0;i < sb.smap_sec_total;i++)   
  71.     {   
  72.         Write_Sector(FS_DEV,3 + i);   
  73.     }   
  74.        
  75.     /* 根目录区占用ROOT_DIR_SEC_NUM个扇区 + 保留扇区 */  
  76.     int occupy_data_sec_num = ROOT_DIR_SEC_NUM + 1;   
  77.        
  78.     /* 在缓冲中先写入正确的值 */  
  79.     for(i = 0;i < occupy_data_sec_num / 8;i++)   
  80.     {   
  81.         *(u8*)(FS_BUF + i) = 0xff;   
  82.     }   
  83.        
  84.     for(j = 0;j < occupy_data_sec_num % 8;j++)   
  85.     {   
  86.         *(u8*)(FS_BUF + i) |= (1 << j);    
  87.     }   
  88.        
  89.     Write_Sector(FS_DEV,3); /* 写入硬盘 */  
  90.        
  91.     /* 填充i结点数组 */  
  92.     Memory_Set((u8*)FS_BUF,0,SECTOR_SIZE);  /* 缓冲区清零 */  
  93.        
  94.     /* 先把硬盘中相关的扇区清零 */  
  95.     for(i = 0;i < sb.inodes_sec_total;i++)   
  96.     {   
  97.         Write_Sector(FS_DEV,3 + sb.smap_sec_total + i);   
  98.     }   
  99.        
  100.     /* 填充根目录区的i结点 */  
  101.        
  102.     /* 执行缓冲区的下一个i结点的位置,因为0结点不用 */  
  103.     I_Node *i_node = (I_Node*)(FS_BUF + I_NODE_SIZE);      
  104.        
  105.     /* 填充根目录的i结点 */  
  106.     i_node->i_mode = INODE_MODE_DIR;            /* 文件类型为根目录 */  
  107.     i_node->i_size = DIR_ENTRY_SIZE * 4;        /* 实际字节数为4个Dir_Entry的大小 */  
  108.     i_node->i_start_sec = sb.first_data_sec;    /* 起始扇区为数据区的第一个字节 */  
  109.     i_node->i_sect_total = ROOT_DIR_SEC_NUM;    /* 实际占用扇区大小为ROOT_DIR_SEC_NUM */  
  110.        
  111.     /* 填充TTY的i结点 */  
  112.     for(i = 0;i < TTY_NUM;i++)   
  113.     {   
  114.         i_node++;      
  115.         i_node->i_mode = INODE_MODE_SPECIAL;    /* 文件类型为特殊的 */  
  116.         i_node->i_size = 0;                     /* 不占磁盘,当然为0 */  
  117.         i_node->i_start_sec = i;                /* 这里表示TTY的编号 */  
  118.         i_node->i_sect_total = 0;               /* 不占磁盘,当然为0 */  
  119.     }   
  120.        
  121.     Write_Sector(FS_DEV,3 + sb.smap_sec_total); /* 写入硬盘 */  
  122.        
  123.     /* 填充根目录区的Dir_Entry数组 */  
  124.     Memory_Set((u8*)FS_BUF,0,SECTOR_SIZE);      /* 缓冲区清零 */  
  125.        
  126.     /* 先把硬盘中相关的扇区清零,加1是因为数据区的第一个扇区不用 */  
  127.     for(i = 0;i < ROOT_DIR_SEC_NUM;i++)   
  128.     {   
  129.         Write_Sector(FS_DEV,sb.first_data_sec + i + 1);   
  130.     }   
  131.        
  132.     /* 填充根目录文件的Dir_Entry */  
  133.     Dir_Entry *dir_entry = (Dir_Entry*)FS_BUF;   
  134.     dir_entry->inode_index = 1;         /* 填充i结点的索引 */  
  135.     Str_Cpy(dir_entry->file_name,".");  /* 名字复制 */  
  136.        
  137.     /* 填充各TTY文件 */  
  138.     char *tty_name[] = {"tty0","tty1","tty2"};   
  139.     for(i = 0;i < TTY_NUM;i++)   
  140.     {   
  141.         dir_entry++;   
  142.         dir_entry->inode_index = i + 2;   
  143.         Str_Cpy(dir_entry->file_name,tty_name[i]);   
  144.     }   
  145.        
  146.     Write_Sector(FS_DEV,sb.first_data_sec + 1);  /* 写入硬盘 */ 
  147. }   
  148.   
  149. /*------------------------------------------------------------------Write_Sector  
  150.     填充一个扇区的数据到指定的device的分区的offset_sec个扇区中  
  151.     数据来自FS_BUF缓冲区  
  152. */  
  153. static void Write_Sector(int device,int offset_sec)   
  154. {   
  155.     Message m;   
  156.     m.msg_type = HD_WRITE;   
  157.     m.i1 = device;   
  158.     m.i2 = offset_sec;   
  159.     m.i3 = SECTOR_SIZE;   
  160.     m.r1 = (void*)FS_BUF;   
  161.     Send_Receive_Shell(BOTH,PROC_HD_PID,&m);   
  162. }   

    注释得还可以了。。不赘述。。

    用到的宏如下:

    include/fs.h

Code:
  1. #define FS_BUF              0x600000    /* 填充FS信息使用的缓冲区 */   
  2. #define FS_BUF_SIZE         0x100000    /* 此缓冲区的大小 */   
  3. #define FS_DEV              32          /* FS安装的逻辑分区的设备号 */   
  4. #define ROOT_DIR_SEC_NUM    128         /* 根目录占用的扇区数 */   
  5.   
  6. #define IDENTITY            0xabcd      /* 此文件系统的标识 */     
  7.   
  8. /* 以下为i结点的模式 */  
  9. #define INODE_MODE_DIR      0x1         /* 表示目录 */   
  10. #define INODE_MODE_FILE     0x2         /* 表示普通文件 */   
  11. #define INODE_MODE_SPECIAL  0x4         /* 表示特殊文件 */  

    还添加了一个Memory_Set函数,如下:

    lib/lib_kernel_in_c.c

Code:
  1. /*--------------------------------------------------------------------Memory_Set  
  2.     把addr指定的内存开始的size个字节置为set_value  
  3. */  
  4. void Memory_Set(u8 *addr,u8 set_value,u32 size)   
  5. {   
  6.     while(size-- > 0)   
  7.     {   
  8.         *addr++ = set_value;   
  9.     }   
  10. }  

    下面是检验结果的时候了,运行结果如下:

     从中我们得到了文件系统的各部分在硬盘的偏移字节数,下面依次来查看。

    首先是超级块:

    i结点映射表:

    数据区扇区映射表:

    i结点数组:

    最后是根目录区:

    仔细对照下,嗯,没错。

<think> 我们正在处理的是关于Ubuntu系统安装软件到指定分区的问题。根据引用内容,特别是引用[2]提到了使用dpkg命令指定安装路径的方法。用户希望将软件安装指定分区,我们需要提供具体的方法步骤和注意事项。 解决方案: 1. 理解分区挂载:在Linux系统中,分区通过挂载到某个目录(如/home、/opt、/usr/local等)来使用。要将软件安装指定分区,首先需要将该分区挂载到某个目录,然后在该目录下安装软件。 2. 使用dpkg的--instdir选项:引用[2]中提到了使用命令`sudo dpkg -i --instdir 安装路径 安装软件包名`,这种方法可以改变软件包的安装根目录。但需要注意的是,这实际上是将整个系统的根目录切换到指定的路径下进行安装,因此可能会导致依赖问题,因为它不会使用系统默认的库路径等。 3. 使用符号链接:另一种常见的方法是将软件默认安装系统目录,然后通过符号链接将安装目录链接到指定分区的目录。例如,将/opt链接到另一个分区上的目录。 4. 修改软件默认安装路径:对于源码编译安装的软件,可以在编译时通过`./configure --prefix=安装路径`来指定安装目录。对于deb包,也可以尝试重新打包,但比较复杂。 具体步骤: 方法一:使用dpkg的--instdir选项(适用于deb包安装) 步骤1:确保目标分区已经挂载到某个目录(例如挂载到/mnt/partition2) 步骤2:使用命令安装: sudo dpkg -i --instdir=/mnt/partition2 package.deb 注意:这种方法可能会导致软件依赖的其他包找不到,因为它改变了整个安装的根路径。因此,需要确保依赖的库也在新的根路径下(通常需要手动复制或挂载)。 方法二:挂载到默认目录(推荐) 步骤1:将目标分区挂载到默认安装目录之一,例如/opt(如果软件默认安装到/opt) sudo mount /dev/sdXn /opt # 其中sdXn是目标分区 步骤2:然后正常安装软件,软件就会安装到该分区的/opt目录下。 为了使挂载永久生效,需要编辑/etc/fstab文件,添加一行: /dev/sdXn /opt ext4 defaults 0 0 然后执行`sudo mount -a`使挂载生效。 方法三:使用符号链接(适用于已安装的软件或新安装) 步骤1:将目标分区挂载到某个目录,例如/mnt/partition2 步骤2:将软件默认安装目录(如/opt)移动到新位置(如果已有数据): sudo mv /opt /mnt/partition2/opt 步骤3:创建符号链接: sudo ln -s /mnt/partition2/opt /opt 方法四:编译安装指定路径(适用于源码安装) 步骤1:进入源码目录 步骤2:配置时指定安装路径(例如/mnt/partition2/usr/local): ./configure --prefix=/mnt/partition2/usr/local 步骤3:编译并安装 make sudo make install 注意事项: - 使用--instdir可能会破坏依赖关系,因为系统库、配置文件等都在原位置,而软件安装到了新的位置,运行时可能会找不到库。因此,推荐使用方法二(挂载)或方法三(符号链接)。 - 对于系统关键软件,不建议改变安装位置,以免造成系统不稳定。 - 如果使用挂载或符号链接,需要确保在系统启动时目标分区已经挂载(通过fstab配置)。 示例:将软件安装到挂载在/data的分区 1. 将分区挂载到/data(假设为/dev/sdb1): sudo mkdir /data sudo mount /dev/sdb1 /data # 若要永久挂载,在/etc/fstab中添加:/dev/sdb1 /data ext4 defaults 0 0 2. 对于通过源码安装的软件,使用--prefix=/data/software/package_name 3. 对于deb包,可以尝试使用--instdir=/data,但风险较高。 引用[2]中的用户就是将软件安装到了/usr/local下,并在该目录下创建了opt目录,因此软件实际安装在/usr/local/opt下。 根据以上方法,用户可以选择最适合自己需求的方式。 相关问题: 1. 如何查看Ubuntu中已挂载的分区及其挂载点? 2. 如何在Ubuntu中创建和管理符号链接? 3. 如何配置/etc/fstab文件以实现开机自动挂载? 4. 源码编译安装软件的具体步骤是什么? </think> ### Ubuntu系统将软件安装指定分区的方法 在Ubuntu中将软件安装指定分区需要结合**分区挂载策略**和**安装路径调整**,以下是具体步骤和原理: --- ### 一、核心方法:通过分区挂载实现 #### ▶ 方案1:将分区挂载到特定目录(推荐) 1. **准备目标分区** - 使用GParted或`fdisk`创建新分区(如`/dev/sda5`),格式化为`ext4` - 记录分区UUID: ```bash sudo blkid /dev/sda5 # 输出示例:UUID="5e5a8c1a-..." ``` 2. **挂载分区到自定义目录** 编辑`/etc/fstab`文件,添加: ```conf # 将新分区永久挂载到/opt目录(软件常用安装路径) UUID=5e5a8c1a-... /opt ext4 defaults 0 2 [^3] ``` 执行挂载: ```bash sudo mkdir /opt # 创建目录(若不存在) sudo mount -a # 应用挂载配置 ``` 3. **安装软件到该目录** ```bash # 源码编译安装指定路径 ./configure --prefix=/opt/mysoftware make && sudo make install # 使用dpkg指定安装目录 sudo dpkg -i --instdir=/opt package.deb [^2] ``` #### ▶ 方案2:修改默认安装路径(需谨慎) ```bash # 修改APT包管理器的安装根目录(影响所有后续安装) sudo dpkg --root=/mnt/custom -i package.deb # 临时改变安装根目录(重启后失效) sudo mount --bind /mnt/custom /var/cache/apt/archives ``` --- ### 二、分区规划建议(引用关键点) 1. **分区类型选择** - **不单独划分/boot分区**:现代系统无需小容量/boot分区,易导致空间不足[^4] - **推荐分区方案**(50GB示例): ```mermaid pie title 分区规划 "根分区 /" : 40 "交换分区 swap" : 8 "EFI分区" : 2 ``` 2. **软件目录特性** | 目录 | 推荐分区 | 存储内容 | 容量建议 | |-------------|----------|--------------------------|-----------| | `/opt` | 独立分区 | 第三方大型软件(如MATLAB)| ≥30GB | | `/usr/local`| 独立分区 | 用户编译安装的软件 | ≥20GB | | `/home` | 独立分区 | 用户配置文件 | 按需分配 | --- ### 三、验证安装路径 ```bash # 检查软件安装位置 which software_name # 查看可执行文件路径 dpkg -L package_name # 列出deb包所有安装文件 # 查看分区挂载情况 df -h /opt # 确认目标分区的挂载点和剩余空间 ``` --- ### 四、注意事项 1. **依赖库问题** 若将软件安装到非标准路径,需手动添加库路径: ```bash # 在/etc/ld.so.conf.d/下创建配置文件 echo "/opt/mysoftware/lib" | sudo tee /etc/ld.so.conf.d/custom.conf sudo ldconfig # 更新库缓存 ``` 2. **路径冲突解决** 当系统已有默认安装时,使用符号链接覆盖: ```bash sudo ln -sf /opt/new_version/bin/app /usr/local/bin/app ``` 3. **双系统分区建议** - Windows下压缩出的空间需≥50GB[^3] - 使用`ext4`而非NTFS格式保证性能 --- ### 关键原理总结 1. Linux通过**目录挂载**访问分区,而非直接操作分区[^1] 2. `--prefix`编译参数控制源码安装位置[^2] 3. EFI分区需保留(≥512MB),但无需专门/boot分区[^4]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值