Linux驱动——mmc card热插拔检测机制(十)

备注:
  1. Kernel版本:5.4
  2. 使用工具:Source Insight 4.0
  3. 参考博客:
[sd card] mmc硬件总线扫描流程(以sd card为例)

文章目录

  • Linux驱动——mmc card热插拔检测机制(十)
    • 前言
    • 卡检测时机
      • 启动host时,检测
      • 中断检测
      • 轮询检测
    • 如何扫描mmc硬件总线
      • detect task的创建
      • detect task的唤醒
      • detect task的实现
    • sd card热插拔状态的获取
      • 获取sd card当前的插入状态
      • 通过GPIO获取当前sd card的插入状态
    • sd card热插拔的实现
      • 中断监控
      • 轮询监控

前言

  扫描mmc硬件总线,也就是检测mmc硬件总线上是否有挂载card。更加通俗的,就是卡槽上是否有插入card。
  检测机制有如下两种:

  • 中断检测
  • 轮询检测

卡检测时机

  mmc core在如下情况下会去扫描mmc硬件总线:

    1. 启动host时,调用_mmc_detect_change,唤醒host->detect;
    1. cd-gpio中断检测到card状态发送变化,调用mmc_detect_change,唤醒host->detect;
    1. host要求轮询sd card插入状态的情况下,所进行的轮询操作;

启动host时,检测

  当启动一个host的时候,并不知道当前是否有card插入,此时需要调用mmc_detect_change来假设card插入状态发生了变化,并且进行第一次扫描mmc硬件总线。

mmc_add_host——>mmc_start_host——>_mmc_detect_change

核心代码如下:

void mmc_start_host(struct mmc_host *host)
{......//申请 cd_gpiommc_gpiod_request_cd_irq(host);/* 到这里host已经可以工作了,可以开始进行后续的card操作了 */_mmc_detect_change(host, 0, false); // 调用mmc_detect_change检测card变化
}

中断检测

  底层硬件发现card插入状态发生变化而调用mmc_detect_change的时候(sd card插入状态监控)。
  当底层硬件发现card插入状态发生变化,例如sd card的插入或者拔出可以触发某个GPIO产生中断。
  此时,可以在中断处理中调用mmc_detect_change来进行扫描mmc硬件总线,并且根据总线上的card状态变化进行处理。
这种情况的主要目的是为了实现sd card的热插拔。

static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{/* Schedule a card detection after a debounce timeout */struct mmc_host *host = dev_id;struct mmc_gpio *ctx = host->slot.handler_priv;host->trigger_card_event = true;// 调用mmc_detect_change对card的插入状态变化进行处理 // 注意,这里ctx->cd_debounce_delay_ms=200,延时了200ms是用来进行消抖mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));return IRQ_HANDLED;
}

轮询检测

  host要求轮询sd card插入状态的情况下,所进行的轮询操作(sd card插入状态监控)。

  一般来说,在host无法根据硬件来及时获取card插入状态发生变化的情况下,会要求mmc_core每隔一段时间(一般是HZ,一秒)扫描一次mmc硬件总线。

  在这种情况下,mmc_host的MMC_CAP_NEEDS_POLL属性会被设置。
  这种情况的主要目的也是为了实现sd card的热插拔。

void mmc_rescan(struct work_struct *work)
{struct mmc_host *host =container_of(work, struct mmc_host, detect.work);int i;......out:// 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,// 调度了host->detect工作,对应就是mmc_rescan// INIT_DELAYED_WORK(&host->detect, mmc_rescan) // 这样,通过mmc_schedule_delayed_work(&host->detect, HZ)就会每隔HZ时间就会执行一次mmc_rescanif (host->caps & MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(&host->detect, HZ);
}

如何扫描mmc硬件总线

从上述,我们知道了可以通过调用mmc_detect_change和执行host->detect工作来发起mmc硬件总线的扫描。而mmc_detect_change最终也是执行mmc_host->detect工作来发起mmc硬件总线扫描的。

detect task的创建

struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{......// 初始化detect工作为mmc_rescan,// 后续调度host->detect来检测是否有card插入时,// 就会调用到mmc_rescanINIT_DELAYED_WORK(&host->detect, mmc_rescan);......}

detect task的唤醒

mmc_detect_change:

void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{_mmc_detect_change(host, delay, true);
}
EXPORT_SYMBOL(mmc_detect_change);

_mmc_detect_change:

void _mmc_detect_change(struct mmc_host *host, unsigned long delay, bool cd_irq)
{/** If the device is configured as wakeup, we prevent a new sleep for* 5 s to give provision for user space to consume the event.*/if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) &&device_can_wakeup(mmc_dev(host)))pm_wakeup_event(mmc_dev(host), 5000);host->detect_change = 1; // 检测到card状态发生变化的标识mmc_schedule_delayed_work(&host->detect, delay); // 间隔delay jiffies之后调用host->detect的工作(mmc_rescan)
}

detect task的实现

  首先说明,当mmc core检测到一张card并且初始化该card完成时,会将该mmc_bus于该card绑定,相应的mmc_bus的操作mmc_bus_ops就会被设置成对应card类型的操作集。

  例如emmc的话就是mmc_ops,sd card的话就是mmc_sd_ops,sdio card就是mmc_sdio_ops。

  所以mmc_rescan主要是进行以下两种操作:

  • 当mmc_host的mmc_bus_ops没有被设置的时候
      这种情况下,说明mmc_bus并没有和card绑定。也就是之前并没有card插入或者识别初始化card的过程中失败了。
      所以只需要去判断当前是否有card插入,如果有的话,进行card的识别和初始化操作(并且会设置mmc_bus_ops)。否则,说明都不做。

  • 当mmc_host的mmc_bus_ops被设置的时候
      这种情况下,说明mmc_bus已经和card绑定了。也就是之前已经有card插入并且初始化成功了。
      那么此时,需要调用mmc_bus_ops中的detect方法(对于sd card来说就是mmc_sd_detect)来判断card是否被移除,并且进行相应的动作。

void mmc_rescan(struct work_struct *work)
{struct mmc_host *host =container_of(work, struct mmc_host, detect.work);int i;/* 如果rescan_disable被设置,说明host此时还禁止rescan */if (host->rescan_disable)return;/* If there is a non-removable card registered, only scan once *//* 对于设备不可移除的host来说,只能rescan一次 */if (!mmc_card_is_removable(host) && host->rescan_entered)return;host->rescan_entered = 1;if (host->trigger_card_event && host->ops->card_event) {mmc_claim_host(host);host->ops->card_event(host);mmc_release_host(host);host->trigger_card_event = false;}mmc_bus_get(host);// 获取host对应的bus/* Verify a registered card to be functional, else remove it. */if (host->bus_ops && !host->bus_dead)host->bus_ops->detect(host);host->detect_change = 0;/** Let mmc_bus_put() free the bus/bus_ops if we've found that* the card is no longer present.*/mmc_bus_put(host);// 因为在这个函数的前面已经获取了一次host,// 可能导致host->bus_ops->detect中检测到card拔出之后,// 没有真正释放到host的bus,所以这里先put一次// host bus的计数(bus_refs)为0的时候,会调用__mmc_release_bus清空host bus的信息mmc_bus_get(host);/* if there still is a card present, stop here */if (host->bus_ops != NULL) {mmc_bus_put(host);goto out;}/** Only we can add a new handler, so it's safe to* release the lock here.*/mmc_bus_put(host);mmc_claim_host(host);if (mmc_card_is_removable(host) && host->ops->get_cd &&host->ops->get_cd(host) == 0) {mmc_power_off(host);mmc_release_host(host);goto out;}// 调用mmc_rescan_try_freq,以支持的最低频率作为工作频率尝试搜索cardfor (i = 0; i < ARRAY_SIZE(freqs); i++) {if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))break;if (freqs[i] <= host->f_min)break;}mmc_release_host(host);out:// 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,// 调度了host->detect工作,对应就是mmc_rescanif (host->caps & MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(&host->detect, HZ);
}

sd card热插拔状态的获取

获取sd card当前的插入状态

一般来说有两种方式来获取到sd card当前的插入状态
(1)GPIO获取的方法
可以通过sd card的card detect引脚来判断当前是否有sd card插入
(2)host寄存器获取的方法
某些host在硬件上有识别sd card是否插入的能力。这种情况下,可以通过读取host的寄存器来获取到当前是否有sd card插入。

通过GPIO获取当前sd card的插入状态

  以TF card为例,卡座功能管脚定义:

可以观察到,虽然TF CARD(micro)只有8个引脚,但是卡座另外定义了一个CD引脚(DET_SWITCH)。
我的猜想是,对于某些tf card卡座来说,当tf card插入时,会把CD引脚(DET_SWITCH)直接定义到连接到groud上去。
所以,这里可以梳理出,

  • 当card没有插入的情况下,由于外部上拉的关系,cpu连接到DET_SWITCH的gpio为高电平。

  • 当card插入时,tf card卡座会把DET_SWITCH拉低,相应的,cpu连接到DET_SWITCH的gpio为低电平。

当然,上述只是一种情况,具体cd gpio的电平情况取决于使用的卡座的实现。

  • 注册card插入状态检测GPIO软件介绍

  mmc core中的mmc_start_host调用mmc_gpiod_request_cd_irq来为host定义自己的cd-detect引脚,也就是对应上面连接到卡座上的CD引脚的GPIO。
mmc_gpiod_request_cd_irq具体实现如下:

void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{// struct mmc_gpio用来表示连接卡槽的一些GPIO的状态struct mmc_gpio *ctx = host->slot.handler_priv;int irq = -EINVAL;int ret;if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)return;/** Do not use IRQ if the platform prefers to poll, e.g., because that* IRQ number is already used by another unit and cannot be shared.*/// cd-gpio 不支持中断功能时,使用轮询检测,不申请gpio irqif (!(host->caps & MMC_CAP_NEEDS_POLL))irq = gpiod_to_irq(ctx->cd_gpio);if (irq >= 0) {if (!ctx->cd_gpio_isr)ctx->cd_gpio_isr = mmc_gpio_cd_irqt;ret = devm_request_threaded_irq(host->parent, irq,NULL, ctx->cd_gpio_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,ctx->cd_label, host);if (ret < 0)irq = ret;}host->slot.cd_irq = irq;if (irq < 0)host->caps |= MMC_CAP_NEEDS_POLL;
}

所以后续就可以通过读取 mmc_host->slot.handler_priv->cd_gpio的电平状态来判断当前card的插入状态。

1. 通过GPIO获取当前sd card的插入状态

  在上述通过mmc_gpiod_request_cd_irq注册完成cd gpio之后,就可以通过mmc_gpio_get_cd来获取card的插入状态。当其返回1时,表示当前有card插入,当其返回0是,表示当前没有card插入。
其实现如下:

int mmc_gpio_get_cd(struct mmc_host *host)
{struct mmc_gpio *ctx = host->slot.handler_priv; // 提取出host对应的struct mmc_gpioint cansleep;if (!ctx || !ctx->cd_gpio) // 如果没有注册cd gpio的情况下,直接返回了return -ENOSYS;cansleep = gpiod_cansleep(ctx->cd_gpio);if (ctx->override_cd_active_level) {int value = cansleep ?gpiod_get_raw_value_cansleep(ctx->cd_gpio) :gpiod_get_raw_value(ctx->cd_gpio);return !value ^ !!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);}// 因为mmc_gpio_get_cd返回1表示有card插入,返回0是表示没有card插入// MMC_CAP2_CD_ACTIVE_HIGH属性被设置的话,表示当有card插入时为cd gpio为高电平(主要取决于卡座)// 假设这里MMC_CAP2_CD_ACTIVE_HIGH没有被设置,也就是有card插入时cd gpio为低电平// 当cd gpio检测到低电平的时候,相当于是!0 ^ 0=1,因此会返回1,有card插入// 当cd gpio检测到高电平的时候,相当于是!1 ^ 0=0,因此会返回0,没有card插入return cansleep ?gpiod_get_value_cansleep(ctx->cd_gpio) :gpiod_get_value(ctx->cd_gpio);
}
EXPORT_SYMBOL(mmc_gpio_get_cd);

2. 通过host寄存器获取当前card插入状态

  前提是某些host在硬件上有识别sd card是否插入的能力。这种情况下,可以通过读取host的寄存器来获取到当前是否有sd card插入。

以sdhci类host为例:

static int sdhci_get_cd(struct mmc_host *mmc)
{struct sdhci_host *host = mmc_priv(mmc);// 通过调用mmc_gpio_get_cd,通过cd gpio获取当前的card插入状态,对应上述“通过GPIO获取当前sd card的插入状态”int gpio_cd = mmc_gpio_get_cd(mmc);if (host->flags & SDHCI_DEVICE_DEAD)return 0;/* If nonremovable, assume that the card is always present. */// 对于不可移除的设备,总是返回1if (!mmc_card_is_removable(host->mmc))return 1;/** Try slot gpio detect, if defined it take precedence* over build in controller functionality*/// mmc_gpio_get_cd返回值有效的话,返回插入状态if (gpio_cd >= 0)return !!gpio_cd;/* If polling, assume that the card is always present. */// 因为host并不一定有通过mmc_gpio_request_cd注册cd gpio,也就是没有实现“通过GPIO获取当前sd card的插入状态” // 这种情况下就尝试通过用sdhci的寄存器来获取// 当SDHCI_QUIRK_BROKEN_CARD_DETECTION被设置的时候,说明该host并没有提供检测card插入状态检测的能力,
// 直接返回1,假设插入,由后续协议的工作来尝试初始化if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)return 1;/* Host native card detect */// 读取sdhci的SDHCI_PRESENT_STATE的bit SDHCI_CARD_PRESENT来获取card的插入状态。return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT);
}

sd card热插拔的实现

  sd card热插拔实现,就是对sd card的插入状态的监控。通常来说有两种方式中断监控和轮询监控。mmc core会选择其中的一种方式来监控sd card的插入状态。

中断监控

  通过sd card的cd引脚的电平变化来触发中断,从而告知cpu说sd card的插入状态已经发生了变化。实现方式如下:

cd-gpio的解析:
在dts中的定义如下:

&mmc0 {// cd-gpios这个属性名的定义取决于host driver将cd gpio定义成了什么名字// pio,也就是要使用的GPIO所使用的gpio controller// 2:sd card的cd引脚所连接的pin_group// 1:sd card的cd引脚所连接的pin_num//GPIO_ACTIVE_LOW:取决于host driver如何解释这个flag的,一般来说,GPIO_ACTIVE_LOW表示低电平有card插入,GPIO_ACTIVE_HIGH则表示高电平有card插入cd-gpios = <&pio 2 1 GPIO_ACTIVE_LOW>; /* PC1*/
};

在mmc host将对cd-gpios进行解析,具体实现方式如下:

int mmc_of_parse(struct mmc_host *host)
{struct device *dev = host->parent;....../* Parse Card Detection */// 获取卡不可移除标志if (device_property_read_bool(dev, "non-removable")) {host->caps |= MMC_CAP_NONREMOVABLE;} else {cd_cap_invert = device_property_read_bool(dev, "cd-inverted");if (device_property_read_u32(dev, "cd-debounce-delay-ms",&cd_debounce_delay_ms))cd_debounce_delay_ms = 200;// 获取轮询查询卡状态标志if (device_property_read_bool(dev, "broken-cd"))host->caps |= MMC_CAP_NEEDS_POLL;// 申请cd-gpio管脚ret = mmc_gpiod_request_cd(host, "cd", 0, false,cd_debounce_delay_ms * 1000,&cd_gpio_invert);if (!ret)dev_info(host->parent, "Got CD GPIO\n");else if (ret != -ENOENT && ret != -ENOSYS)return ret;/** There are two ways to flag that the CD line is inverted:* through the cd-inverted flag and by the GPIO line itself* being inverted from the GPIO subsystem. This is a leftover* from the times when the GPIO subsystem did not make it* possible to flag a line as inverted.** If the capability on the host AND the GPIO line are* both inverted, the end result is that the CD line is* not inverted.*/if (cd_cap_invert ^ cd_gpio_invert)host->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;}.......
}
int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,unsigned int idx, bool override_active_level,unsigned int debounce, bool *gpio_invert)
{struct mmc_gpio *ctx = host->slot.handler_priv;struct gpio_desc *desc;int ret;// 申请cd-gpio,并将配置为输入方向desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);if (IS_ERR(desc))return PTR_ERR(desc);// 若支持硬件防抖,则配置if (debounce) {ret = gpiod_set_debounce(desc, debounce);if (ret < 0)ctx->cd_debounce_delay_ms = debounce / 1000;}// 获取有效电平是否需要反转if (gpio_invert)*gpio_invert = !gpiod_is_active_low(desc);ctx->override_cd_active_level = override_active_level;ctx->cd_gpio = desc;return 0;
}
EXPORT_SYMBOL(mmc_gpiod_request_cd);bool mmc_can_gpio_cd(struct mmc_host *host)
{struct mmc_gpio *ctx = host->slot.handler_priv;return ctx->cd_gpio ? true : false;
}
EXPORT_SYMBOL(mmc_can_gpio_cd);

注册cd-gpio中断引脚:

void mmc_gpiod_request_cd_irq(struct mmc_host *host)
{// struct mmc_gpio用来表示连接卡槽的一些GPIO的状态struct mmc_gpio *ctx = host->slot.handler_priv;int irq = -EINVAL;int ret;if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)return;/** Do not use IRQ if the platform prefers to poll, e.g., because that* IRQ number is already used by another unit and cannot be shared.*/// cd-gpio 不支持中断功能时,使用轮询检测,不申请gpio irqif (!(host->caps & MMC_CAP_NEEDS_POLL))irq = gpiod_to_irq(ctx->cd_gpio);if (irq >= 0) {if (!ctx->cd_gpio_isr)ctx->cd_gpio_isr = mmc_gpio_cd_irqt;ret = devm_request_threaded_irq(host->parent, irq,NULL, ctx->cd_gpio_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,ctx->cd_label, host);if (ret < 0)irq = ret;}host->slot.cd_irq = irq;// 如果设置成中断监控的方式失败的情况下,设置轮询标识MMC_CAP_NEEDS_POLL,使能轮询监控if (irq < 0)host->caps |= MMC_CAP_NEEDS_POLL;
}

中断监控的中断处理函数mmc_gpio_cd_irqt:

static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{/* Schedule a card detection after a debounce timeout */struct mmc_host *host = dev_id;struct mmc_gpio *ctx = host->slot.handler_priv;host->trigger_card_event = true;// 调用mmc_detect_change对card的插入状态变化进行处理 // 注意,这里ctx->cd_debounce_delay_ms=200,延时了200ms是用来进行消抖mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));return IRQ_HANDLED;
}

轮询监控

主要实现为每隔一段时间(一般是HZ,1s)扫描一下mmc硬件总线。

  • 对应需要设置的属性标识
    需要设置mmc_host的MMC_CAP_NEEDS_POLL属性标识实现方式
    dts的配置:
&mmc0 {broken-cd;// cd-gpios这个属性名的定义取决于host driver将cd gpio定义成了什么名字// pio,也就是要使用的GPIO所使用的gpio controller// 2:sd card的cd引脚所连接的pin_group// 1:sd card的cd引脚所连接的pin_num//GPIO_ACTIVE_LOW:取决于host driver如何解释这个flag的,一般来说,GPIO_ACTIVE_LOW表示低电平有card插入,GPIO_ACTIVE_HIGH则表示高电平有card插入cd-gpios = <&pio 2 1 GPIO_ACTIVE_LOW>; /* PC1*/
};

在mmc host将对broken-cd的解析同上。
实现方式:
每个1s中调用一次mmc_host->detect工作。

void mmc_rescan(struct work_struct *work)
{struct mmc_host *host =container_of(work, struct mmc_host, detect.work);int i;......out:// 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,// 调度了host->detect工作,对应就是mmc_rescanif (host->caps & MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(&host->detect, HZ);
}

  在host启动的时候,会执行一次mmc_host->detect工作,这时就开始进行轮询了。
  以上就实现了每隔1s执行一次mmc_rescan来扫描mmc硬件总线的变化情况。

Linux驱动——mmc card热插拔检测机制(十)相关推荐

  1. Linux驱动——mmc sd card初始化流程(十一)

    Linux驱动--mmc sd card初始化流程(十一) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客:   (1)[sd ...

  2. Linux驱动——mmc sd card 块设备读写流程(十三)

    Linux驱动--mmc sd card 块设备读写流程(十三) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客:   (1) ...

  3. Linux驱动——mmc数据结构(二)

    Linux驱动--mmc数据结构(二) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: 2. [mmc subsystem] ...

  4. Linux驱动——mmc概念与框架(一)

    Linux驱动--mmc概念与框架(一) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux MMC framewo ...

  5. Linux驱动——mmc host controller(九)

    Linux驱动--mmc host controller(九) 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0 文章目录 Linux驱动--mmc ...

  6. linux驱动开发音频设备驱动,linux驱动开发—基于Device tree机制的驱动编写

    摘要:媒介 Device Tree是一种用去描绘硬件的数据布局,类似板级描绘说话,发源于OpenFirmware(OF).正在现在遍及应用的kernel 2.6.x版本中,对分歧仄台.分歧硬件,往] ...

  7. Linux中基于eBPF的恶意利用与检测机制(rootkit、驱动)

    目录 前言 现状分析 海外资料 国内资料 eBPF技术恶意利用的攻击原理 网络层恶意利用 Linux系统运行时恶意利用 综述 检测防御 运行前 运行时 运行后 防御 工程实现 系统兼容性 CO-RE ...

  8. 【正点原子MP157连载】第二十二章 新字符设备驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  9. cmd52命令发送 mmc_乾坤合一~Linux SD/MMC/SDIO驱动分析(上)

    一.SD/MMC/SDIO概念区分 SD(SecureDigital)与 MMC(MultimediaCard) SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆 ...

最新文章

  1. GridView添加自动编号列
  2. 为什么方差的自由度是n-1啦?
  3. 六十七、Leetcode数组系列(下篇)
  4. win10水印烦死了_win10系统下word去水印的方法【图文】
  5. hive求差集和交集
  6. ConcurrentHashMapK, V的实现
  7. 手把手教你从零开始搭建个人博客,20分钟上手
  8. Linux常用命令 创建文本 清屏操作
  9. #数组元素相乘_C++ 矩阵相乘
  10. BZOJ2160 拉拉队排练
  11. Python入门书 - 简明Python教程(A Byte of Python)
  12. Cesium地图不加载或者加载有问题怎么办
  13. ERROR: 错误 1406。未能将值 写入项 \Software\Classes\CLSID\{4B75FA16-56AF-4DC4-941D-F84B279DDB15}\LocalServer3
  14. 如何写好一个2Take1 Lua - 搭建Lua环境
  15. 淘宝商品api,天猫商品api,京东商品api,1688商品api,速卖通商品api,微商相册api,拼多多商品api
  16. AliOS-Things--EMW3060 (9)uart
  17. html动态图片怎么设背景,微信8.0状态背景视频怎么设置?状态视频动态背景图设置教程[多图]...
  18. centos7设置基础软件仓库时出错
  19. c++读取cfg文件
  20. 2021/4/29刷题

热门文章

  1. A. Déjà Vu
  2. Sentinel 为 RocketMQ 服务保驾护航
  3. 如何自己动手为家庭做一套安防监控系统
  4. 破解Linux操作系统root 权限不能使用问题
  5. React Native关于使用CameraRoll出现的问题@react-native-camera-roll/camera-roll@react-native-community/cameraro
  6. 2020-10-02
  7. 力扣 面试题 10.11. 峰与谷
  8. 数据结构之简单迷宫求解
  9. MySQL——表的导出和导入
  10. 影像组学平台助力又一位培训班学员论文见刊:基于机器学习的多参数MRI放射组学预测直肠癌患者新辅助放化疗后不良反应