ext2_try_to_allocate解析

本文详细阐述了如何在指定块组内高效分配空闲磁盘块,包括预留窗口分配和普通分配方式,并解释了关键参数的含义及函数处理逻辑。

        前面博客中我们描述了如何为文件分配一个预留窗口,而且我们知道,该预留窗口内的磁盘块有可能已经被占用了,而我们的最终目的是分配空闲磁盘块,而非预留窗口,所以我们这里描述的就是如何分配空闲磁盘块。

        这里我们分配磁盘块有两种途径:第一是通过预留窗口,即在某个预留窗口内分配磁盘块;第二是普通分配方式,这个只是限制在特定块组内分配即可,没有第一种的情况要求那么苛刻。至于使用哪种分配方式,是由上层调用者来决定的。

        对这个函数我们还必须明确一点:1. 分配的空闲磁盘块一定连续;2. 不一定能分配得到想要数量的磁盘块。

static int
ext2_try_to_allocate(struct super_block *sb, int group,
			struct buffer_head *bitmap_bh, ext2_grpblk_t grp_goal,
			unsigned long *count,
			struct ext2_reserve_window *my_rsv)
{
	ext2_fsblk_t group_first_block;
       	ext2_grpblk_t start, end;
	unsigned long num = 0;

	/* we do allocation within the reservation window if we have a window */
	/* here what we should decide is the range(start ~ end) between which we allocate free block(s)
	*/
	/*
	** 如果使用预留窗口,我们必须
	** 保证分配的范围落在预留窗口
	** 并且,要落在goal所在的块组中
	*/
	if (my_rsv) {
		//好像这里面的设置有点儿复杂
		group_first_block = ext2_group_first_block_no(sb, group);
		if (my_rsv->_rsv_start >= group_first_block)
			start = my_rsv->_rsv_start - group_first_block;
		else
			/* reservation window cross group boundary */
			start = 0;
		end = my_rsv->_rsv_end - group_first_block + 1;
		if (end > EXT2_BLOCKS_PER_GROUP(sb))
			/* reservation window crosses group boundary */
			end = EXT2_BLOCKS_PER_GROUP(sb);
		if ((start <= grp_goal) && (grp_goal < end))
			start = grp_goal;
		else
			grp_goal = -1;
	} 
	//如果不使用预留窗口,那么
	//就直接从goal处开始分配好了
	else {
		if (grp_goal > 0)
			start = grp_goal;
		else
			start = 0;
		end = EXT2_BLOCKS_PER_GROUP(sb);
	}

	BUG_ON(start > EXT2_BLOCKS_PER_GROUP(sb));

repeat:
	//如果grp_goal < 0 表示什么呢
	if (grp_goal < 0) {

		//找到grp_goal之后的第一个空闲磁盘块
		//当然查找是有个范围的
		grp_goal = find_next_usable_block(start, bitmap_bh, end);
		if (grp_goal < 0)
			goto fail_access;
		if (!my_rsv) {
			int i;

			for (i = 0; i < 7 && grp_goal > start &&
					!ext2_test_bit(grp_goal - 1,
					     		bitmap_bh->b_data);
			     		i++, grp_goal--)
				;
		}
	}
	start = grp_goal;

	//如果当前的goal 被占用了该函数会执行失败
	// 继续从下一个块开始,即转到repeat开始重试
	if (ext2_set_bit_atomic(sb_bgl_lock(EXT2_SB(sb), group), grp_goal,
			       				bitmap_bh->b_data)) {
		/*
		 * The block was allocated by another thread, or it was
		 * allocated and then freed by another thread
		 */
		start++;
		grp_goal++;
		if (start >= end)
			goto fail_access;
		goto repeat;
	}
	/* 如果之前的goal是空闲的
	** 那么再继续从下一个搜索
	** 看看下一个连续的块是否也是空闲的
	** 如果有,当然最好,我们可以多分配点
	** 
	*/
	num++;
	grp_goal++;
	while (num < *count && grp_goal < end
		&& !ext2_set_bit_atomic(sb_bgl_lock(EXT2_SB(sb), group),
					grp_goal, bitmap_bh->b_data)) {
		num++;
		grp_goal++;
	}
	*count = num;
	return grp_goal - num;
fail_access:
	*count = num;
	return -1;
}
        我们要明确该函数参数的意义:

  1. group:代表当前要分配的磁盘块所在的块组,无论我们使用的预留窗口有多大,我们最终的分配都必须限制在该块组之内;
  2. bitmap_bh:块组的位图信息,用于寻找空闲磁盘块;
  3. grp_goal:上层建议的从哪开始搜索分配,以达到最佳文件连续性;
  4. *count:上层传入的要分配磁盘块数量,同时这个也作为返回值告知调用者实际分配的磁盘块数量;
  5. my_rsv:是否使用预留窗口,如果其不为NULL,则在该预留窗口中进行分配。
        接下来让我们看看你函数的处理逻辑:

        首先,我们得判断本次分配是否使用预留窗口,如果使用,那么我们必须得计算好我们从哪开始分配,到哪里为止。至于其需要处理的原因在于预留窗口可能是跨块组的,所以我们必须得小心处理这种情况,如果真的跨块组,我们必须得截断,分配必须得在这个块组中进行。而且,我们得根据goal计算出其在块组内的偏移,即grp_goal,因为查找位图的时候是根据块组内偏移来查的。如果不适用预留窗口,那么就简单多了,如果设置了goal,直接从这里开始查找空闲块,否则从块组开始处查找。

         完成了上面查找的设置,接下来我们就是搜索位图开始查找空闲磁盘块了。我们从grp_goal这个位置开始判断:如果这个块被占用了,那么我们得从后面一个块开始重试,所以有一个repeat;如果这个块没被占用,那我们就继续往后找,看看后面的块是否还空闲,如果是,那连这个块一并分配。一直找到不是空闲磁盘块为止,我们的策略是尽可能分配多的连续块。

        最后,*count中保存了本次分配到的连续空闲磁盘块数量,而返回值则是分配的起始磁盘块号。

static int mmc_init_card(struct mmc_host *host, u32 ocr, 1607 struct mmc_card *oldcard) 1608 { 1609 struct mmc_card *card; 1610 int err; 1611 u32 cid[4]; 1612 u32 rocr; 1613 1614 WARN_ON(!host->claimed); 1615 1616 /* Set correct bus mode for MMC before attempting init */ 1617 if (!mmc_host_is_spi(host)) 1618 mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); 1619 1620 /* 1621 * Since we're changing the OCR value, we seem to 1622 * need to tell some cards to go back to the idle 1623 * state. We wait 1ms to give cards time to 1624 * respond. 1625 * mmc_go_idle is needed for eMMC that are asleep 1626 */ 1627 mmc_go_idle(host); 1628 1629 /* The extra bit indicates that we support high capacity */ 1630 err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr); 1631 if (err) 1632 goto err; 1633 1634 /* 1635 * For SPI, enable CRC as appropriate. 1636 */ 1637 if (mmc_host_is_spi(host)) { 1638 err = mmc_spi_set_crc(host, use_spi_crc); 1639 if (err) 1640 goto err; 1641 } 1642 1643 /* 1644 * Fetch CID from card. 1645 */ 1646 err = mmc_send_cid(host, cid); 1647 if (err) 1648 goto err; 1649 1650 if (oldcard) { 1651 if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { 1652 pr_debug("%s: Perhaps the card was replaced\n", 1653 mmc_hostname(host)); 1654 err = -ENOENT; 1655 goto err; 1656 } 1657 1658 card = oldcard; 1659 } else { 1660 /* 1661 * Allocate card structure. 1662 */ 1663 card = mmc_alloc_card(host, &mmc_type); 1664 if (IS_ERR(card)) { 1665 err = PTR_ERR(card); 1666 goto err; 1667 } 1668 1669 card->ocr = ocr; 1670 card->type = MMC_TYPE_MMC; 1671 card->rca = 1; 1672 memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); 1673 } 1674 1675 /* 1676 * Call the optional HC's init_card function to handle quirks. 1677 */ 1678 if (host->ops->init_card) 1679 host->ops->init_card(host, card); 1680 1681 /* 1682 * For native busses: set card RCA and quit open drain mode. 1683 */ 1684 if (!mmc_host_is_spi(host)) { 1685 err = mmc_set_relative_addr(card); 1686 if (err) 1687 goto free_card; 1688 1689 mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); 1690 } 1691 1692 if (!oldcard) { 1693 /* 1694 * Fetch CSD from card. 1695 */ 1696 err = mmc_send_csd(card, card->raw_csd); 1697 if (err) 1698 goto free_card; 1699 1700 err = mmc_decode_csd(card); 1701 if (err) 1702 goto free_card; 1703 err = mmc_decode_cid(card); 1704 if (err) 1705 goto free_card; 1706 } 1707 1708 /* 1709 * handling only for cards supporting DSR and hosts requesting 1710 * DSR configuration 1711 */ 1712 if (card->csd.dsr_imp && host->dsr_req) 1713 mmc_set_dsr(host); 1714 1715 /* 1716 * Select card, as all following commands rely on that. 1717 */ 1718 if (!mmc_host_is_spi(host)) { 1719 err = mmc_select_card(card); 1720 if (err) 1721 goto free_card; 1722 } 1723 1724 if (!oldcard) { 1725 /* Read extended CSD. */ 1726 err = mmc_read_ext_csd(card); 1727 if (err) 1728 goto free_card; 1729 1730 /* 1731 * If doing byte addressing, check if required to do sector 1732 * addressing. Handle the case of <2GB cards needing sector 1733 * addressing. See section 8.1 JEDEC Standard JED84-A441; 1734 * ocr register has bit 30 set for sector addressing. 1735 */ 1736 if (rocr & BIT(30)) 1737 mmc_card_set_blockaddr(card); 1738 1739 /* Erase size depends on CSD and Extended CSD */ 1740 mmc_set_erase_size(card); 1741 } 1742 1743 /* 1744 * Reselect the card type since host caps could have been changed when 1745 * debugging even if the card is not new. 1746 */ 1747 mmc_select_card_type(card); 1748 1749 /* Enable ERASE_GRP_DEF. This bit is lost after a reset or power off. */ 1750 if (card->ext_csd.rev >= 3) { 1751 err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, 1752 EXT_CSD_ERASE_GROUP_DEF, 1, 1753 card->ext_csd.generic_cmd6_time); 1754 1755 if (err && err != -EBADMSG) 1756 goto free_card; 1757 1758 if (err) { 1759 /* 1760 * Just disable enhanced area off & sz 1761 * will try to enable ERASE_GROUP_DEF 1762 * during next time reinit 1763 */ 1764 card->ext_csd.enhanced_area_offset = -EINVAL; 1765 card->ext_csd.enhanced_area_size = -EINVAL; 1766 } else { 1767 card->ext_csd.erase_group_def = 1; 1768 /* 1769 * enable ERASE_GRP_DEF successfully. 1770 * This will affect the erase size, so 1771 * here need to reset erase size 1772 */ 1773 mmc_set_erase_size(card); 1774 } 1775 } 1776 1777 /* 1778 * Ensure eMMC user default partition is enabled 1779 */ 1780 if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) { 1781 card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; 1782 err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG, 1783 card->ext_csd.part_config, 1784 card->ext_csd.part_time); 1785 if (err && err != -EBADMSG) 1786 goto free_card; 1787 } 1788 1789 /* 1790 * Enable power_off_notification byte in the ext_csd register 1791 */ 1792 if (card->ext_csd.rev >= 6) { 1793 err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, 1794 EXT_CSD_POWER_OFF_NOTIFICATION, 1795 EXT_CSD_POWER_ON, 1796 card->ext_csd.generic_cmd6_time); 1797 if (err && err != -EBADMSG) 1798 goto free_card; 1799 1800 /* 1801 * The err can be -EBADMSG or 0, 1802 * so check for success and update the flag 1803 */ 1804 if (!err) 1805 card->ext_csd.power_off_notification = EXT_CSD_POWER_ON; 1806 } 1807 1808 /* set erase_arg */ 1809 if (mmc_can_discard(card)) 1810 card->erase_arg = MMC_DISCARD_ARG; 1811 else if (mmc_can_trim(card)) 1812 card->erase_arg = MMC_TRIM_ARG; 1813 else 1814 card->erase_arg = MMC_ERASE_ARG; 1815 1816 /* 1817 * Select timing interface 1818 */ 1819 err = mmc_select_timing(card); 1820 if (err) 1821 goto free_card; 1822 1823 if (mmc_card_hs200(card)) { 1824 host->doing_init_tune = 1; 1825 1826 err = mmc_hs200_tuning(card); 1827 if (!err) 1828 err = mmc_select_hs400(card); 1829 1830 host->doing_init_tune = 0; 1831 1832 if (err) 1833 goto free_card; 1834 } else if (mmc_card_hs400es(card)) { 1835 if (host->ops->execute_hs400_tuning) { 1836 err = host->ops->execute_hs400_tuning(host, card); 1837 if (err) 1838 goto free_card; 1839 } 1840 } else { 1841 /* Select the desired bus width optionally */ 1842 err = mmc_select_bus_width(card); 1843 if (err > 0 && mmc_card_hs(card)) { 1844 err = mmc_select_hs_ddr(card); 1845 if (err) 1846 goto free_card; 1847 } 1848 } 1849 1850 /* 1851 * Choose the power class with selected bus interface 1852 */ 1853 mmc_select_powerclass(card); 1854 1855 /* 1856 * Enable HPI feature (if supported) 1857 */ 1858 if (card->ext_csd.hpi) { 1859 err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, 1860 EXT_CSD_HPI_MGMT, 1, 1861 card->ext_csd.generic_cmd6_time); 1862 if (err && err != -EBADMSG) 1863 goto free_card; 1864 if (err) { 1865 pr_warn("%s: Enabling HPI failed\n", 1866 mmc_hostname(card->host)); 1867 card->ext_csd.hpi_en = 0; 1868 } else { 1869 card->ext_csd.hpi_en = 1; 1870 } 1871 } 1872 1873 /* 1874 * If cache size is higher than 0, this indicates the existence of cache 1875 * and it can be turned on. Note that some eMMCs from Micron has been 1876 * reported to need ~800 ms timeout, while enabling the cache after 1877 * sudden power failure tests. Let's extend the timeout to a minimum of 1878 * DEFAULT_CACHE_EN_TIMEOUT_MS and do it for all cards. 1879 */ 1880 if (card->ext_csd.cache_size > 0) { 1881 unsigned int timeout_ms = MIN_CACHE_EN_TIMEOUT_MS; 1882 1883 timeout_ms = max(card->ext_csd.generic_cmd6_time, timeout_ms); 1884 err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, 1885 EXT_CSD_CACHE_CTRL, 1, timeout_ms); 1886 if (err && err != -EBADMSG) 1887 goto free_card; 1888 1889 /* 1890 * Only if no error, cache is turned on successfully. 1891 */ 1892 if (err) { 1893 pr_warn("%s: Cache is supported, but failed to turn on (%d)\n", 1894 mmc_hostname(card->host), err); 1895 card->ext_csd.cache_ctrl = 0; 1896 } else { 1897 card->ext_csd.cache_ctrl = 1; 1898 } 1899 } 1900 1901 /* 1902 * Enable Command Queue if supported. Note that Packed Commands cannot 1903 * be used with Command Queue. 1904 */ 1905 card->ext_csd.cmdq_en = false; 1906 if (card->ext_csd.cmdq_support && host->caps2 & MMC_CAP2_CQE) { 1907 err = mmc_cmdq_enable(card); 1908 if (err && err != -EBADMSG) 1909 goto free_card; 1910 if (err) { 1911 pr_warn("%s: Enabling CMDQ failed\n", 1912 mmc_hostname(card->host)); 1913 card->ext_csd.cmdq_support = false; 1914 card->ext_csd.cmdq_depth = 0; 1915 } 1916 } 1917 /* 1918 * In some cases (e.g. RPMB or mmc_test), the Command Queue must be 1919 * disabled for a time, so a flag is needed to indicate to re-enable the 1920 * Command Queue. 1921 */ 1922 card->reenable_cmdq = card->ext_csd.cmdq_en; 1923 1924 if (host->cqe_ops && !host->cqe_enabled) { 1925 err = host->cqe_ops->cqe_enable(host, card); 1926 if (!err) { 1927 host->cqe_enabled = true; 1928 1929 if (card->ext_csd.cmdq_en) { 1930 pr_info("%s: Command Queue Engine enabled\n", 1931 mmc_hostname(host)); 1932 } else { 1933 host->hsq_enabled = true; 1934 pr_info("%s: Host Software Queue enabled\n", 1935 mmc_hostname(host)); 1936 } 1937 } 1938 } 1939 1940 if (host->caps2 & MMC_CAP2_AVOID_3_3V && 1941 host->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_330) { 1942 pr_err("%s: Host failed to negotiate down from 3.3V\n", 1943 mmc_hostname(host)); 1944 err = -EINVAL; 1945 goto free_card; 1946 } 1947 1948 if (!oldcard) 1949 host->card = card; 1950 1951 return 0; 1952 1953 free_card: 1954 if (!oldcard) 1955 mmc_remove_card(card); 1956 err: 1957 return err; 1958 }逐行解释下这个函数
07-25
C:\Users\admin\PyCharmMiscProject\.venv\Scripts\python.exe F:\issue\main.py ✅ 配置文件已加载: F:\issue\config\config.json 🔁 已从配置恢复 channel_set_map: {} ✅ 配置文件已加载: F:\issue\config\config.json 🔁 已从配置恢复 channel_set_map: {} 📊 开始解析 Excel 文件: input/Archer BE900US 2.xlsx ℹ️ 备份已存在: input/wlc_clm_data_6726b0.c.bak,跳过备份 🧹 'Cover' → 清洗后: 'Cover' 🟡 未匹配到 'Cover' 的模式,跳过... 🧹 '版本历史' → 清洗后: '版本历史' 🟡 未匹配到 '版本历史' 的模式,跳过... 🧹 '2.4G功率表(版本1.0)' → 清洗后: '2.4G功率表版本1.0' ✅ 匹配成功!'2.4G功率表(版本1.0)' → [2G] 配置 ✅ 找到表头行: 第 4 行 🔍 开始向上查找 '认证功率',扫描第 0 ~ 3 行... 📌 发现合并单元格含 '证功率': '认证功率' → G4 🔍 解析 CH 行(第 6 行),限定列范围: Col 6 ~ 18 👉 发现 CH1 @ Col6 👉 发现 CH2 @ Col7 👉 发现 CH3 @ Col8 👉 发现 CH4 @ Col9 👉 发现 CH5 @ Col10 👉 发现 CH6 @ Col11 👉 发现 CH7 @ Col12 👉 发现 CH8 @ Col13 👉 发现 CH9 @ Col14 👉 发现 CH10 @ Col15 👉 发现 CH11 @ Col16 👉 发现 CH12 @ Col17 👉 发现 CH13 @ Col18 ✔️ 成功提取 CH1-13 共 13 个信道 🟡 无法识别物理模式: 'Mode' 🟡 无法识别物理模式: 'Mode' 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 20M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🔍 解析复合模式 '11AC/AX 40M' → ['11AC', '11AX'] 🟡 无法识别物理模式: '说明: 1、Boardlimit填写各信道均满足EVM要求的最大功率;根据实际情况调整表格,如存在不同chain不一样的情况,则分开填写。 2、认证功率中填写各信道的认证功率限制;注意空间流,TXBF等信息需对应。 3、实发功率会根据Boardlimit和认证 power取小,生成DUT实发功率。实发功率表根据实际情况增减。' 📊 已采集第 7 行 → 11b 20M, 11 个信道, 使用宏: RATE_SET_2G_20M_11b 📊 已采集第 7 行 → 11b 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11b 📊 已采集第 7 行 → 11b 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11b 📊 已采集第 7 行 → 11b 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11b 📊 已采集第 8 行 → 11g 20M, 11 个信道, 使用宏: RATE_SET_2G_20M_11g 📊 已采集第 8 行 → 11g 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11g 📊 已采集第 8 行 → 11g 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11g 📊 已采集第 8 行 → 11g 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11g 🗑️ 跳过空行: 第 9 行(无任何有效功率值) 🗑️ 跳过空行: 第 10 行(无任何有效功率值) 🗑️ 跳过空行: 第 11 行(无任何有效功率值) 🗑️ 跳过空行: 第 12 行(无任何有效功率值) 🗑️ 跳过空行: 第 13 行(无任何有效功率值) 📊 已采集第 14 行 → 11n 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11n 📊 已采集第 14 行 → 11n 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11n 📊 已采集第 14 行 → 11n 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11n 🗑️ 跳过空行: 第 15 行(无任何有效功率值) 🗑️ 跳过空行: 第 16 行(无任何有效功率值) 🗑️ 跳过空行: 第 17 行(无任何有效功率值) 🗑️ 跳过空行: 第 18 行(无任何有效功率值) 🗑️ 跳过空行: 第 19 行(无任何有效功率值) 🗑️ 跳过空行: 第 20 行(无任何有效功率值) 📊 已采集第 21 行 → 11AC 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11AC 📊 已采集第 21 行 → 11AC 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11AC 📊 已采集第 21 行 → 11AC 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11AC 📊 已采集第 21 行 → 11AX 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11AX 📊 已采集第 21 行 → 11AX 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11AX 📊 已采集第 21 行 → 11AX 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11AX 🗑️ 跳过空行: 第 22 行(无任何有效功率值) 🗑️ 跳过空行: 第 23 行(无任何有效功率值) 🗑️ 跳过空行: 第 24 行(无任何有效功率值) 🗑️ 跳过空行: 第 25 行(无任何有效功率值) 🗑️ 跳过空行: 第 26 行(无任何有效功率值) 🗑️ 跳过空行: 第 27 行(无任何有效功率值) 🗑️ 跳过空行: 第 28 行(无任何有效功率值) 🗑️ 跳过空行: 第 29 行(无任何有效功率值) 🗑️ 跳过空行: 第 30 行(无任何有效功率值) 🗑️ 跳过空行: 第 31 行(无任何有效功率值) 📊 已采集第 32 行 → 11BE 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_11BE 📊 已采集第 32 行 → 11BE 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT_11BE 📊 已采集第 32 行 → 11BE 20M, 11 个信道, 使用宏: RATE_SET_2G_HT_20M_EXT4_11BE 🗑️ 跳过空行: 第 33 行(无任何有效功率值) 🗑️ 跳过空行: 第 34 行(无任何有效功率值) 🗑️ 跳过空行: 第 35 行(无任何有效功率值) 🗑️ 跳过空行: 第 36 行(无任何有效功率值) 🗑️ 跳过空行: 第 37 行(无任何有效功率值) 🗑️ 跳过空行: 第 38 行(无任何有效功率值) 🗑️ 跳过空行: 第 39 行(无任何有效功率值) 🗑️ 跳过空行: 第 40 行(无任何有效功率值) 🗑️ 跳过空行: 第 41 行(无任何有效功率值) 🗑️ 跳过空行: 第 42 行(无任何有效功率值) 🗑️ 跳过空行: 第 43 行(无任何有效功率值) 🗑️ 跳过空行: 第 44 行(无任何有效功率值) 📊 已采集第 46 行 → 11n 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_11n 📊 已采集第 46 行 → 11n 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT_11n 📊 已采集第 46 行 → 11n 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT4_11n 🗑️ 跳过空行: 第 47 行(无任何有效功率值) 🗑️ 跳过空行: 第 48 行(无任何有效功率值) 🗑️ 跳过空行: 第 49 行(无任何有效功率值) 🗑️ 跳过空行: 第 50 行(无任何有效功率值) 🗑️ 跳过空行: 第 51 行(无任何有效功率值) 🗑️ 跳过空行: 第 52 行(无任何有效功率值) 📊 已采集第 53 行 → 11AC 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_11AC 📊 已采集第 53 行 → 11AC 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT_11AC 📊 已采集第 53 行 → 11AC 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT4_11AC 📊 已采集第 53 行 → 11AX 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_11AX 📊 已采集第 53 行 → 11AX 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT_11AX 📊 已采集第 53 行 → 11AX 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT4_11AX 🗑️ 跳过空行: 第 54 行(无任何有效功率值) 🗑️ 跳过空行: 第 55 行(无任何有效功率值) 🗑️ 跳过空行: 第 56 行(无任何有效功率值) 🗑️ 跳过空行: 第 57 行(无任何有效功率值) 🗑️ 跳过空行: 第 58 行(无任何有效功率值) 🗑️ 跳过空行: 第 59 行(无任何有效功率值) 🗑️ 跳过空行: 第 60 行(无任何有效功率值) 🗑️ 跳过空行: 第 61 行(无任何有效功率值) 🗑️ 跳过空行: 第 62 行(无任何有效功率值) 🗑️ 跳过空行: 第 63 行(无任何有效功率值) 📊 已采集第 64 行 → 11BE 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_11BE 📊 已采集第 64 行 → 11BE 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT_11BE 📊 已采集第 64 行 → 11BE 40M, 7 个信道, 使用宏: RATE_SET_2G_HT_40M_EXT4_11BE 🗑️ 跳过空行: 第 65 行(无任何有效功率值) 🗑️ 跳过空行: 第 66 行(无任何有效功率值) 🗑️ 跳过空行: 第 67 行(无任何有效功率值) 🗑️ 跳过空行: 第 68 行(无任何有效功率值) 🗑️ 跳过空行: 第 69 行(无任何有效功率值) 🗑️ 跳过空行: 第 70 行(无任何有效功率值) 🗑️ 跳过空行: 第 71 行(无任何有效功率值) 🗑️ 跳过空行: 第 72 行(无任何有效功率值) 🗑️ 跳过空行: 第 73 行(无任何有效功率值) 🗑️ 跳过空行: 第 74 行(无任何有效功率值) 🗑️ 跳过空行: 第 75 行(无任何有效功率值) 🗑️ 跳过空行: 第 76 行(无任何有效功率值) 🆕 自动分配: RANGE_2G_20M_1_11 → CHANNEL_SET_1 🆕 自动分配: RANGE_2G_20M_1_1 → CHANNEL_SET_2 🆕 自动分配: RANGE_2G_20M_11_11 → CHANNEL_SET_3 🆕 自动分配: RANGE_2G_20M_2_10 → CHANNEL_SET_4 🆕 自动分配: RANGE_2G_40M_3_3 → CHANNEL_SET_5 🆕 自动分配: RANGE_2G_40M_4_8 → CHANNEL_SET_6 🆕 自动分配: RANGE_2G_40M_9_9 → CHANNEL_SET_7 📊 [Band=2G] 累计 2.4G 信道范围: CH1 – CH11 ✔️ 成功从 '2.4G功率表(版本1.0)' 添加 88 条压缩后 TX 限幅条目 📊 当前累计 2.4G 信道范围: CH1 – CH11 🧹 '5G功率表(版本1.0)' → 清洗后: '5G功率表版本1.0' ✅ 匹配成功!'5G功率表(版本1.0)' → [5G] 配置 ✅ 找到表头行: 第 3 行 🔍 开始向上查找 '认证功率',扫描第 0 ~ 2 行... 📌 发现合并单元格含 '证功率': '认证功率' → L3 🔍 解析 CH 行(第 5 行),限定列范围: Col 11 ~ 35 ❌ 在指定区域内未找到任何 CHx 列 ⚠️ 从 '5G功率表(版本1.0)' 未收集到有效数据 🧹 '6G功率表 (版本1.0)NSS=1' → 清洗后: '6G功率表版本1.0NSS=1' ✅ 匹配成功!'6G功率表 (版本1.0)NSS=1' → [6G] 配置 ✅ 找到表头行: 第 3 行 🔍 开始向上查找 '认证功率',扫描第 0 ~ 2 行... 📌 发现合并单元格含 '证功率': '认证功率' → P3 🔍 解析 CH 行(第 5 行),限定列范围: Col 15 ~ 39 ❌ 在指定区域内未找到任何 CHx 列 ⚠️ 从 '6G功率表 (版本1.0)NSS=1' 未收集到有效数据 🧹 '6G功率表 (版本1.0)NSS=2' → 清洗后: '6G功率表版本1.0NSS=2' ✅ 匹配成功!'6G功率表 (版本1.0)NSS=2' → [6G] 配置 ✅ 找到表头行: 第 3 行 🔍 开始向上查找 '认证功率',扫描第 0 ~ 2 行... 📌 发现合并单元格含 '证功率': '认证功率' → P3 🔍 解析 CH 行(第 5 行),限定列范围: Col 15 ~ 39 ❌ 在指定区域内未找到任何 CHx 列 ⚠️ 从 '6G功率表 (版本1.0)NSS=2' 未收集到有效数据 🧹 '6G功率表 (版本1.0)NSS=4' → 清洗后: '6G功率表版本1.0NSS=4' ✅ 匹配成功!'6G功率表 (版本1.0)NSS=4' → [6G] 配置 ✅ 找到表头行: 第 3 行 🔍 开始向上查找 '认证功率',扫描第 0 ~ 2 行... 📌 发现合并单元格含 '证功率': '认证功率' → P3 🔍 解析 CH 行(第 5 行),限定列范围: Col 15 ~ 39 ❌ 在指定区域内未找到任何 CHx 列 ⚠️ 从 '6G功率表 (版本1.0)NSS=4' 未收集到有效数据 🧹 'Sheet1' → 清洗后: 'Sheet1' 🟡 未匹配到 'Sheet1' 的模式,跳过... 🔧 正在执行 generate_outputs()... 📊 自动分类结果: ├─ Normal 模式(不含 HT): 4 条 └─ HT 模式(含 HT): 84 条 📊 已存在映射: RANGE_2G_20M_1_11 → CHANNEL_SET_1 🎉 已生成: output\locale_default.c 🎉 已生成: output\tx_limit_table.c 🎉 已生成: output\clm_macros.h ✅ 已生成精简 manifest 文件: output\generated_ranges_manifest.json 📊 共 7 个唯一 RANGE 宏被使用: - RANGE_2G_20M_11_11 - RANGE_2G_20M_1_1 - RANGE_2G_20M_1_11 - RANGE_2G_20M_2_10 - RANGE_2G_40M_3_3 - RANGE_2G_40M_4_8 - RANGE_2G_40M_9_9 🔄 开始同步 RANGE 到 C 数组... ✅ 已从 output\generated_ranges_manifest.json 加载 0 条 RANGE 映射 🟢 所有 RANGE 均已在正确数组中,无需修改 🎉 同步完成 ✅ 所有输出文件生成完成。 ✅ Excel 解析完成,共生成 88 条 TX 限幅记录 🔧 正在执行 generate_outputs()... 📊 自动分类结果: ├─ Normal 模式(不含 HT): 4 条 └─ HT 模式(含 HT): 84 条 📊 已存在映射: RANGE_2G_20M_1_11 → CHANNEL_SET_1 🎉 已生成: output\locale_default.c 🎉 已生成: output\tx_limit_table.c 🎉 已生成: output\clm_macros.h ✅ 已生成精简 manifest 文件: output\generated_ranges_manifest.json 📊 共 7 个唯一 RANGE 宏被使用: - RANGE_2G_20M_11_11 - RANGE_2G_20M_1_1 - RANGE_2G_20M_1_11 - RANGE_2G_20M_2_10 - RANGE_2G_40M_3_3 - RANGE_2G_40M_4_8 - RANGE_2G_40M_9_9 🔄 开始同步 RANGE 到 C 数组... ✅ 已从 output\generated_ranges_manifest.json 加载 0 条 RANGE 映射 🟢 所有 RANGE 均已在正确数组中,无需修改 🎉 同步完成 ✅ 所有输出文件生成完成。 这是日志,里面并没有说已成功将更新的 channel_set_map 写回配置文件: F:\issue\config\config.json
10-15
def generate_outputs(self, finalize_config=True): self.logger.info(" 正在执行 generate_outputs()...") self.logger.info(" ...") if not self.tx_limit_entries: self.logger.info(" 无 TX 限幅数据可输出") return # === Step 0: 记录变更摘要 === changes = { "added_ranges": set(), "removed_ranges": set(), "modified_ranges": set(), # 可留空,后续可扩展 "other_additions": [], # 其他文本类变更(如 fallback 变更) "other_deletions": [] } # 获取当前 used_ranges(去重宏名) current_used_ranges = set(entry["range_macro"] for entry in self.tx_limit_entries) # 读取旧的 used_ranges(如果 config 存在) previous_used_ranges = set() if "used_ranges" in self.config: try: previous_used_ranges = set(self.config["used_ranges"]) except Exception as e: self.logger.info(f" 解析旧 used_ranges 失败: {e}") # 对比变化 changes["added_ranges"] = current_used_ranges - previous_used_ranges changes["removed_ranges"] = previous_used_ranges - current_used_ranges if changes["added_ranges"]: self.logger.info(f" 新增 RANGE 宏 ({len(changes['added_ranges'])}):") for r in sorted(changes["added_ranges"]): self.logger.info(f" + {r}") if changes["removed_ranges"]: self.logger.info(f" 删除 RANGE 宏 ({len(changes['removed_ranges'])}):") for r in sorted(changes["removed_ranges"]): self.logger.info(f" - {r}") # === Step 0.5: 构建 enum 到 assigned_locale 的映射 === locale_by_enum = {} for tgt in self.config.get("locale_targets", []): enum = tgt.get("enum") loc = tgt.get("assigned_locale") if enum and loc: locale_by_enum[enum] = loc.strip() # 获取两个关键 locale locale_name_2_4G = locale_by_enum.get("locale_2g_idx", "UNKNOWN_NORMAL") locale_name_2_4G_HT = locale_by_enum.get("locale_2g_ht_idx", "UNKNOWN_HT") self.logger.info(f" 2.4G Normal 模式将使用 locale: {locale_name_2_4G}") self.logger.info(f" 2.4G HT 模式将使用 locale: {locale_name_2_4G_HT}") # === Step 1: 使用 "HT" 分类 entries === normal_entries = [] ht_entries = [] for e in self.tx_limit_entries: macro = e.get("rate_set_macro", "") if "HT" in macro: ht_entries.append(e) else: normal_entries.append(e) self.logger.info(f" 自动分类结果:") self.logger.info(f" ├─ Normal 模式(不含 HT): {len(normal_entries)} 条") self.logger.info(f" └─ HT 模式(含 HT): {len(ht_entries)} 条") # === Step 2: 构建 tx_limit_normal 结构(按 bw 排序)=== def build_normal_structure(entries): grouped = defaultdict(list) for e in entries: bw = str(e["bw"]) grouped[bw].append(e) result = [] for bw in ["20", "40", "80", "160"]: if bw in grouped: sorted_entries = sorted(grouped[bw], key=lambda x: (x["ch_start"], x["encoded_power"])) result.append((bw, sorted_entries)) return result normal_struct = build_normal_structure(normal_entries) # === Step 3: 构建 tx_limit_ht 结构(严格顺序)=== def build_ht_structure(entries): groups = defaultdict(list) for e in entries: bw = str(e["bw"]) if "EXT4" in e["rate_set_macro"]: level = "ext4" elif "EXT" in e["rate_set_macro"]: level = "ext" else: level = "base" groups[(level, bw)].append(e) order = [ ("base", "20"), ("base", "40"), ("ext", "20"), ("ext", "40"), ("ext4", "20"), ("ext4", "40") ] segments = [] active_segment_count = sum(1 for key in order if key in groups) for idx, (level, bw) in enumerate(order): key = (level, bw) if key not in groups: continue seg_entries = sorted(groups[key], key=lambda x: (x["ch_start"], x["encoded_power"])) count = len(seg_entries) header_flags = f"CLM_DATA_FLAG_WIDTH_{bw} | CLM_DATA_FLAG_MEAS_COND" if idx < active_segment_count - 1: header_flags += " | CLM_DATA_FLAG_MORE" if level != "base": header_flags += " | CLM_DATA_FLAG_FLAG2" segment = { "header_flags": header_flags, "count": count, "entries": seg_entries } if level == "ext": segment["flag2"] = "CLM_DATA_FLAG2_RATE_TYPE_EXT" elif level == "ext4": segment["flag2"] = "CLM_DATA_FLAG2_RATE_TYPE_EXT4" segments.append(segment) return segments ht_segments = build_ht_structure(ht_entries) # === Step 4: fallback range 和 CHANNEL_SET 自动创建逻辑 === channel_set_comment = "Fallback 2.4GHz channel set " old_fallback = self.config.get("fallback_range_macro", "UNKNOWN") if self.global_ch_min is not None and self.global_ch_max is not None: fallback_range_macro = f"RANGE_2G_20M_{self.global_ch_min}_{self.global_ch_max}" fallback_ch_start = self.global_ch_min fallback_ch_end = self.global_ch_max if old_fallback != fallback_range_macro: changes["other_deletions"].append(f"fallback range: {old_fallback}") changes["other_additions"].append(f"fallback range: {fallback_range_macro}") self.logger.info(f" fallback range 变更: {old_fallback} → {fallback_range_macro}") # 待修改 self.logger.info(f" 正在设置监管 fallback 范围: {fallback_range_macro}") fallback_channel_set_id = 1 self.channel_set_map[fallback_range_macro] = fallback_channel_set_id self.logger.info(f" 已绑定监管 fallback: {fallback_range_macro} → CHANNEL_SET_{fallback_channel_set_id}") else: fallback_range_macro = "RANGE_2G_20M_1_11" fallback_ch_start = 1 fallback_ch_end = 11 fallback_channel_set_id = 1 self.channel_set_map[fallback_range_macro] = fallback_channel_set_id self.logger.info(" 未检测到有效的 2.4G 信道范围,使用默认 fallback: RANGE_2G_20M_1_11 → CHANNEL_SET_1") # 待修改 # === Step 5: 渲染上下文集合 === timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") context_tables = { "timestamp": timestamp, "locale_display_name": self.locale_display_name, # 👇 新增:区分 normal 和 HT 的 locale 名 "locale_name_2_4G": locale_name_2_4G.replace('-', '_'), # C 标识符安全 "locale_name_2_4G_HT": locale_name_2_4G_HT.replace('-', '_'), "normal_table": normal_struct, "ht_segments": ht_segments, "fallback_encoded_eirp": 30, "fallback_range_macro": fallback_range_macro, "fallback_ch_start": fallback_ch_start, "fallback_ch_end": fallback_ch_end, "fallback_channel_set_id": fallback_channel_set_id, "channel_set_comment": channel_set_comment, } # 确保输出目录存在 output_dir = Path(self.output_dir) output_dir.mkdir(parents=True, exist_ok=True) #待修改 # 分析 tx_limit_table.c 的变更 output_path = output_dir / "tx_limit_table.c" template_path = "templates/tx_limit_table_2_4G.c.j2" # 根据是否有 HT 模式 + 频段决定模板 if "2g" in self.sheet_config["band"].lower(): template_path = "templates/tx_limit_table_2_4G.c.j2" elif "5g" in self.sheet_config["band"].lower(): template_path = "templates/tx_limit_table_5G.c.j2" elif "6g" in self.sheet_config["band"].lower(): template_path = "templates/tx_limit_table_6G.c.j2" else: self.logger.info(f" 未知的频段: {self.sheet_config['band']}") # 待修改 # 读取原始文件内容(如果存在) original_lines = [] file_existed = output_path.exists() if file_existed: try: original_lines = output_path.read_text(encoding='utf-8').splitlines() except Exception as e: self.logger.info(f" 无法读取旧文件 {output_path}: {e}") # 生成新内容 try: new_content = self.render_from_template_string( template_path=template_path, context=context_tables ) new_lines = new_content.splitlines() except Exception as e: self.logger.info(f" 模板渲染失败 ({template_path}): {e}") raise # 比较差异并决定是否写入 if not file_existed: self.logger.info(f" 将创建新文件: {output_path}") elif original_lines != new_lines: self.logger.info(f" 检测到变更,将更新文件: {output_path}") # 标记该文件更新 changes["other_additions"].append(f"更新了 {output_path.name}") else: self.logger.info(f" 文件内容未变,跳过写入: {output_path}") # 即使不写也要继续后续流程 # 写入新内容(除非完全一致且已存在) if not file_existed or original_lines != new_lines: try: output_path.write_text(new_content, encoding='utf-8') self.logger.info(f" 已写入 → {output_path}") except Exception as e: self.logger.info(f" 写入文件失败 {output_path}: {e}") raise # === Step 7: 添加到 used_ranges 和 used_ranges_count 到 config.json(仅当 finalize_config=True)=== if finalize_config: used_range_macros = sorted(set(entry["range_macro"] for entry in self.tx_limit_entries)) self.used_ranges = used_range_macros # 更新主配置字段(直接赋值 list 和 int) self.config["used_ranges"] = used_range_macros self.config["used_ranges_count"] = len(used_range_macros) try: # 写回文件 with open(self.config_file_path, 'w', encoding='utf-8') as f: json.dump(self.config, f, indent=4, ensure_ascii=False) f.flush() os.fsync(f.fileno()) # 确保落盘 self.logger.info(f" 已添加 'used_ranges' 到 config: {self.config_file_path}") self.logger.info(f" 共 {len(used_range_macros)} 个唯一 RANGE 宏被使用:") for macro in used_range_macros: self.logger.info(f" - {macro}") self.last_config = str(self.config_file_path) except Exception as e: self.logger.info(f" 写入 config 失败: {e}") else: self.logger.info(" 跳过 used_ranges 生成 (finalize_config=False)") # === Final Step: 保存 channel_set 映射配置 === self.save_channel_set_map_to_config() # === Log Changes === total_entries = len(self.tx_limit_entries) self.log_changes_to_file( changes=changes, locale_id=self.locale_name, total_entries=total_entries ) #待修改 # 最终总结 self.logger.info(f" 所有输出文件生成完成。") self.logger.info(f" 输出路径: {self.output_dir}") self.logger.info(f" 功率表名称: {self.locale_display_name} ({self.locale_name})") # 待修改这个根据sheet来选择模板生成不同文件其中band怎么来
10-29
### 功能 `get_or_allocate_page` 通常是操作系统内核中用于管理内存页面的函数。其主要功能是尝试获取一个已经存在的页面,如果该页面不存在,则会分配一个新的页面。在操作系统的内存管理中,页面是内存分配和管理的基本单位,这个函数有助于高效地管理和使用内存资源,避免不必要的页面分配,提高系统性能。 ### 使用方法 在不同的操作系统内核中,`get_or_allocate_page` 的使用方法会有所不同。以下是一个简化的伪代码示例,展示其可能的使用方式: ```c // 假设的函数原型 struct page *get_or_allocate_page(unsigned long flags); // 使用示例 struct page *page; // 获取或分配一个页面,传入分配标志 page = get_or_allocate_page(GFP_KERNEL); if (page != NULL) { // 对获取到的页面进行操作 // ... // 使用完后释放页面 // put_page(page); } else { // 页面获取或分配失败 // 进行错误处理 } ``` 在这个示例中,`GFP_KERNEL` 是一个分配标志,表示从内核空间分配页面。不同的标志可以用于不同的分配场景,例如从用户空间分配、紧急分配等。 ### 实现原理 `get_or_allocate_page` 的实现原理通常涉及到以下几个步骤: 1. **查找页面**:首先,函数会在页面缓存或页面表中查找是否已经存在所需的页面。这可能涉及到对页面的索引、哈希表查找等操作,以快速定位页面。 2. **页面存在处理**:如果找到了所需的页面,函数会对该页面进行一些必要的处理,例如增加页面的引用计数,以确保该页面不会被意外释放。 3. **页面分配**:如果没有找到所需的页面,函数会调用内存分配器来分配一个新的页面。内存分配器会根据系统的内存状态和分配标志,从空闲内存中分配一个合适的页面。 4. **初始化页面**:分配到新的页面后,函数会对页面进行初始化,例如清零页面内容、设置页面的元数据等。 5. **返回页面**:最后,函数会返回找到或分配的页面指针。 以下是一个简化的伪代码示例,展示其可能的实现: ```c struct page *get_or_allocate_page(unsigned long flags) { struct page *page; // 查找页面 page = find_page(...); if (page != NULL) { // 页面存在,增加引用计数 get_page(page); return page; } // 页面不存在,分配新页面 page = alloc_page(flags); if (page != NULL) { // 初始化页面 init_page(page); } return page; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值