最近在研究wifi模组, 是sdio接口的, 而手头刚好有一块mini2440,了解了一下sdio加载的过程, 发现和sd卡加载的过程是类似的。

这里用mini2440的内核源码, 来剖析一下sd卡的加载过程:

首先, mini2440内核加载的时候, 就会指定一部分设备初始化列表:

//---------------------------- arch/arm/mach-s3c2440/mach-mini2440.c ----------------------------//
/* devices we initialise */
static struct platform_device *mini2440_devices[] __initdata = {&s3c_device_usb,&s3c_device_rtc,&s3c_device_lcd,&s3c_device_wdt,&s3c_device_i2c0,&s3c_device_iis,&mini2440_device_eth,&s3c24xx_uda134x,&s3c_device_nand,&s3c_device_sdi,                                        // sd设备&s3c_device_usbgadget,
};
/* MMC/SD  */
static struct s3c24xx_mci_pdata mini2440_mmc_cfg = {.gpio_detect   = S3C2410_GPG(8),                        // 插入检测引脚.gpio_wprotect = S3C2410_GPH(8),                        // 写保护引脚.set_power     = NULL,.ocr_avail     = MMC_VDD_32_33|MMC_VDD_33_34,           // 有效电压范围是3.2~3.3V, 3.3~3.4V
};static void __init mini2440_machine_init(void)
{s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;                   platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
}

来看看s3c_device_sdi的定义:

//---------------------------- arch/arm/plat-s3c24xx/devs.c ----------------------------//
/* SDI */
static struct resource s3c_sdi_resource[] = {[0] = {.start = S3C24XX_PA_SDI,.end   = S3C24XX_PA_SDI + S3C24XX_SZ_SDI - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_SDI,.end   = IRQ_SDI,.flags = IORESOURCE_IRQ,}};struct platform_device s3c_device_sdi = {.name          = "s3c2410-sdi",.id          = -1,.num_resources    = ARRAY_SIZE(s3c_sdi_resource),.resource   = s3c_sdi_resource,
};

有些人会奇怪, 我们这里不是在讲解2440吗, 为什么这里是s3c2410-sdi, 别急, 往下看:

//---------------------------- arch/arm/mach-s3c2440/mach-mini2440.c ----------------------------//
static void __init mini2440_machine_init(void)
{s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;                   platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
}//---------------------------- arch/arm/plat-s3c24xx/s3c244x.c ----------------------------//
void __init s3c244x_map_io(void)
{iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));/* rename any peripherals used differing from the s3c2410 */s3c_device_sdi.name  = "s3c2440-sdi";                           // 重命名为s3c2440-sdis3c_device_i2c0.name  = "s3c2440-i2c";s3c_device_nand.name = "s3c2440-nand";s3c_device_usbgadget.name = "s3c2440-usbgadget";
}

理解了吧?

接下来讲sd驱动的加载:

//---------------------------- drivers/mmc/host/s3cmci.c ----------------------------//
static struct platform_device_id s3cmci_driver_ids[] = {{.name = "s3c2410-sdi",.driver_data = 0,}, {.name  = "s3c2412-sdi",.driver_data = 1,}, {.name  = "s3c2440-sdi",.driver_data = 1,                            // 下面会用到!!!},{ }
};static struct platform_driver s3cmci_driver = {.driver   = {.name   = "s3c-sdi",.owner   = THIS_MODULE,.pm  = s3cmci_pm_ops,},.id_table    = s3cmci_driver_ids,.probe     = s3cmci_probe,.remove     = __devexit_p(s3cmci_remove),.shutdown = s3cmci_shutdown,
};static int __init s3cmci_init(void)
{return platform_driver_register(&s3cmci_driver);
}module_init(s3cmci_init); 

驱动和设备匹配上的话,就会调用驱动的probe函数:

static int __devinit s3cmci_probe(struct platform_device *pdev)
{struct s3cmci_host *host;struct mmc_host   *mmc;is2440 = platform_get_device_id(pdev)->driver_data;                 // 是否是2440, 这里就用到上面提到的driver_data,mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);       // 分配mmc_host结构体for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {               // SDCLK SDCMD DATA0~DATA3ret = gpio_request(i, dev_name(&pdev->dev));                    // 申请gpio资源if (ret) {dev_err(&pdev->dev, "failed to get gpio %d\n", i);for (i--; i >= S3C2410_GPE(5); i--)gpio_free(i);goto probe_free_host;}}host = mmc_priv(mmc);                                               // 获取private域host->mmc    = mmc;host->pdev    = pdev;host->is2440 = is2440;host->pdata = pdev->dev.platform_data;                              // 即上面提到的mini2440_mmc_cfgif (!host->pdata) {pdev->dev.platform_data = &s3cmci_def_pdata;host->pdata = &s3cmci_def_pdata;}if (is2440) {host->sdiimsk  = S3C2440_SDIIMSK;host->sdidata = S3C2440_SDIDATA;host->clk_div = 1;} else {host->sdiimsk   = S3C2410_SDIIMSK;host->sdidata = S3C2410_SDIDATA;host->clk_div = 2;}// 申请内存host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);         // 这里会读取IORESOURCE_MEM域host->mem = request_mem_region(host->mem->start,resource_size(host->mem), pdev->name);host->base = ioremap(host->mem->start, resource_size(host->mem));host->irq = platform_get_irq(pdev, 0);                              // 这里会读取IORESOURCE_IRQ域, 返回start地址if (host->irq == 0) {dev_err(&pdev->dev, "failed to get interrupt resouce.\n");ret = -EINVAL;goto probe_iounmap;}// 申请中断,该中断为读写SD卡数据时所产生的中断if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {dev_err(&pdev->dev, "failed to request mci interrupt.\n");ret = -ENOENT;goto probe_iounmap;}// 禁止上面所申请的中断disable_irq(host->irq);                                             host->irq_state = false;if (!host->pdata->no_detect) {                                      // 如果SD控制器具有检测SD卡插拔状态的功能ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");  // 申请插入检测iohost->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);if (host->irq_cd >= 0) {if (request_irq(host->irq_cd, s3cmci_irq_cd,                // 注册插入检测中断处理函数, 申请中断,当有SD卡插入或拔出时,则进入该中断IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING,DRIVER_NAME, host)) {dev_err(&pdev->dev,"can't get card detect irq.\n");ret = -ENOENT;goto probe_free_gpio_cd;}} else {dev_warn(&pdev->dev,"host detect has no irq available\n");gpio_direction_input(host->pdata->gpio_detect);}}if (!host->pdata->no_wprotect) {                                    // 如果SD控制器具有写保护的功能ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");    // 申请写保护ioif (ret) {dev_err(&pdev->dev, "failed to get writeprotect\n");goto probe_free_irq_cd;}gpio_direction_input(host->pdata->gpio_wprotect);}if (s3cmci_host_usedma(host)) {                                     // 如果使用dma...}host->clk = clk_get(&pdev->dev, "sdi");ret = clk_enable(host->clk);                                        // 使能clkhost->clk_rate = clk_get_rate(host->clk);                           // 通过时钟信号源获取时钟频率mmc->ops    = &s3cmci_ops;mmc->ocr_avail    = MMC_VDD_32_33 | MMC_VDD_33_34;                    // 默认是支持3.2~3.3V和3.3~3.4V
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQmmc->caps   = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#elsemmc->caps   = MMC_CAP_4_BIT_DATA;
#endifmmc->f_min     = host->clk_rate / (host->clk_div * 256);mmc->f_max   = host->clk_rate / host->clk_div;if (host->pdata->ocr_avail)mmc->ocr_avail = host->pdata->ocr_avail;                        // 如果用户自定义了ocr_avail,即支持的工作电压,则使用自定义mmc->max_blk_count = 4095;                                         // 一次请求的最大block数mmc->max_blk_size   = 4095;                                         // block的最大值mmc->max_req_size   = 4095 * 512;                                   // 一次请求的最大字节数mmc->max_seg_size  = mmc->max_req_size;                            // 最大块大小mmc->max_phys_segs   = 128;mmc->max_hw_segs  = 128;ret = s3cmci_cpufreq_register(host);ret = mmc_add_host(mmc);                                            // 将mmc_host添加进系统s3cmci_debugfs_attach(host);                                        platform_set_drvdata(pdev, mmc);
}其中:
static struct mmc_host_ops s3cmci_ops = {.request  = s3cmci_request,                                       // 实现命令和数据的传输.set_ios  = s3cmci_set_ios,                                       // 设置硬件的IO, 包括工作电压等 .get_ro        = s3cmci_get_ro,                                        // 读取写保护的状态.get_cd     = s3cmci_card_present,                                  // 获取卡是否还存在的状态.enable_sdio_irq = s3cmci_enable_sdio_irq,
};

从对s3cmci_probe函数的分析可以看出,该函数除了对s3cmci_host和mmc_host这两个结构体赋值外,
最重要的工作就是调用了mmc_alloc_host函数和mmc_add_host函数。前者分配一个mmc_host,后者添加一个mmc_host。
下面我们就来介绍这两个函数,这两个函数都在drivers/mmc/core/host.c文件内。

//---------------------------- drivers/mmc/core/host.c ----------------------------//
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);err = idr_get_new(&mmc_host_idr, host, &host->index);           // 分配新的idr入口dev_set_name(&host->class_dev, "mmc%d", host->index);           // 设置设备的名字host->parent = dev;host->class_dev.parent = dev;host->class_dev.class = &mmc_host_class;// 初始化设备结构体device_initialize(&host->class_dev);spin_lock_init(&host->lock);init_waitqueue_head(&host->wq);                                 // 初始化等待队列,工作队列INIT_DELAYED_WORK(&host->detect, mmc_rescan);                   // 添加到等待队列INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable);/** By default, hosts do not support SGIO or large requests.* They have to set these according to their abilities.*/host->max_hw_segs = 1;host->max_phys_segs = 1;host->max_seg_size = PAGE_CACHE_SIZE;host->max_req_size = PAGE_CACHE_SIZE;host->max_blk_size = 512;host->max_blk_count = PAGE_CACHE_SIZE / 512;return host;
}int mmc_add_host(struct mmc_host *host)
{...err = device_add(&host->class_dev);mmc_start_host(host);...
}

在mmc_add_host函数内,调用mmc_start_host函数来启动SD:

//---------------------------- drivers/mmc/core/core.c ----------------------------//
void mmc_start_host(struct mmc_host *host)
{mmc_power_off(host);mmc_detect_change(host, 0);
}static void mmc_power_off(struct mmc_host *host)
{host->ios.clock = 0;host->ios.vdd = 0;if (!mmc_host_is_spi(host)) {host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;host->ios.chip_select = MMC_CS_DONTCARE;}host->ios.power_mode = MMC_POWER_OFF;host->ios.bus_width = MMC_BUS_WIDTH_1;              // 单线模式host->ios.timing = MMC_TIMING_LEGACY;mmc_set_ios(host);                                  // 设置工作电压和模式
}void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{mmc_schedule_delayed_work(&host->detect, delay);
}static int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay)
{return queue_delayed_work(workqueue, work, delay);
}

在mmc_start_host函数内调用mmc_detect_change函数来检测是否有SD卡已经插入。mmc_detect_change函数又调用mmc_schedule_delayed_work函数,mmc_schedule_delayed_work函数又调用了queue_delayed_work函数。
这样经过一段延时后,会执行在mmc_alloc_host函数中定义的工作队列(INIT_DELAYED_WORK(&host->detect,mmc_rescan);)所指向的mmc_rescan函数。
为什么要延时,这和按键去抖延时相类似。

下面我们来介绍mmc_rescan函数,它用来具体执行扫描并检测是否有SD卡插入的:

void mmc_rescan(struct work_struct *work)
{struct mmc_host *host = container_of(work, struct mmc_host, detect.work);/* if there is a card registered, check whether it is still present */if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)host->bus_ops->detect(host);                                            // 如果已经注册过该卡,则检测卡是否还在/* if there still is a card present, stop here */// host->bus_ops != NULL,说明这次mmc_rescan不是在上电初始化时调用的// 所以不需要执行if后面的内容,要直接执行mmc_schedule_delayed_work函数if (host->bus_ops != NULL) {                                                // 如果卡还在, 则直接退出mmc_bus_put(host);goto out;}/* detect a newly inserted card */                                          // 检测新插入的卡/** Only we can add a new handler, so it's safe to* release the lock here.*/mmc_bus_put(host);if (host->ops->get_cd && host->ops->get_cd(host) == 0)                      // 如果有插入检测引脚, 但是检测该引脚的状态是无卡,则退出goto out;// 判断当前mmc控制器是否被占用mmc_claim_host(host);mmc_power_up(host);                                                         // 上电mmc_go_idle(host);                                                          // 发送命令CMD0,使其处于IDLE状态mmc_send_if_cond(host, host->ocr_avail);                                    // 发送SD_SEND_IF_COND命令,以支持SD2.0卡// 依次探测SDIO、SD、MMC/** First we search for SDIO...*/err = mmc_send_io_op_cond(host, 0, &ocr);if (!err) {if (mmc_attach_sdio(host, ocr))mmc_power_off(host);goto out;}/** ...then normal SD...*/err = mmc_send_app_op_cond(host, 0, &ocr);if (!err) {if (mmc_attach_sd(host, ocr))mmc_power_off(host);goto out;}/** ...and finally MMC.*/err = mmc_send_op_cond(host, 0, &ocr);if (!err) {if (mmc_attach_mmc(host, ocr))mmc_power_off(host);goto out;}mmc_release_host(host);mmc_power_off(host);
}

我们以SDIO卡为例,因此会调用mmc_attach_sdio函数,它实现了SDIO卡的初始化:

//---------------------------- drivers/mmc/core/sd.c ----------------------------//
/** Starting point for SD card init.*/
int mmc_attach_sd(struct mmc_host *host)
{int err;u32 ocr, rocr;err = mmc_send_app_op_cond(host, 0, &ocr);if (err)return err;mmc_sd_attach_bus_ops(host);if (host->ocr_avail_sd)host->ocr_avail = host->ocr_avail_sd;/** We need to get OCR a different way for SPI.*/if (mmc_host_is_spi(host)) {mmc_go_idle(host);err = mmc_spi_read_ocr(host, 0, &ocr);if (err)goto err;}// 设置电压,最终调用的是s3cmci_set_ios函数rocr = mmc_select_voltage(host, ocr);/** Can we support the voltage(s) of the card(s)?*/if (!rocr) {err = -EINVAL;goto err;}/* 检测和初始化卡* Detect and init the card.* 在mmc_sd_init_card函数内,会通过发送一系列命令来启动SD卡,并得到寄存器CID,CSD,SCR,RCA等的数据。*/err = mmc_sd_init_card(host, rocr, NULL);if (err)goto err;mmc_release_host(host);/* 添加卡, 它通过调用device_add函数把mmc_card设备添加进系统中,并通知mmc块设备驱动。* First add the card to the driver model...*/err = mmc_add_card(host->card);mmc_claim_host(host);if (err)goto remove_card;return 0;remove_card:mmc_release_host(host);mmc_remove_card(host->card);host->card = NULL;mmc_claim_host(host);
err:mmc_detach_bus(host);pr_err("%s: error %d whilst initialising SD card\n",mmc_hostname(host), err);return err;
}static void mmc_sd_attach_bus_ops(struct mmc_host *host)
{const struct mmc_bus_ops *bus_ops;if (!mmc_card_is_removable(host))bus_ops = &mmc_sd_ops_unsafe;elsebus_ops = &mmc_sd_ops;mmc_attach_bus(host, bus_ops);
}static const struct mmc_bus_ops mmc_sd_ops = {.remove = mmc_sd_remove,                        // 拔出sd卡的操作.detect = mmc_sd_detect,                        // 探测sd卡的操作.suspend = NULL,.resume = NULL,.power_restore = mmc_sd_power_restore,.alive = mmc_sd_alive,.shutdown = mmc_sd_suspend,
};

至此,mmc子系统的启动及检测SD卡设备就完成了。这个SD卡的检测是在上电之前就已插入插槽的,那么如果上电以后再把SD卡插入插槽中,系统又是如何检测的呢?
在前面介绍s3cmci_probe函数的时候,申请了两个中断,一个用于数据传输,另一个就是用于检测SD卡的。也就是当SD卡插入时,会引起相关引脚的电平变化,从而触发该中断,进入中断处理函数s3cmci_irq_cd:

//---------------------------- drivers\mmc\host/s3cmci.c ----------------------------//
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{struct s3cmci_host *host = (struct s3cmci_host *)dev_id;dbg(host, dbg_irq, "card detect\n");mmc_detect_change(host->mmc, msecs_to_jiffies(500));return IRQ_HANDLED;
}

mmc_detect_change函数前面介绍过,它是用来检测是否存在SD卡。它的第二个参数表示延时的时间。在这里一定是要有延时的。
调用mmc_detect_change函数后所执行的内容与前面介绍的一样,这里就不再重复了。
 
无论是上电之前还是上电之后,SD卡的插入检测我们在上面都详细介绍完了,那么SD卡的拔出是如何检测的呢?当然一定是带电状态下才能检测SD卡的拔出的。
是回到mmc_rescan函数中:

void mmc_rescan(struct work_struct *work)
{...struct mmc_host *host = container_of(work, struct mmc_host, detect.work);/* if there is a card registered, check whether it is still present */if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)host->bus_ops->detect(host);                                            // 如果已经注册过该卡,则检测卡是否还在...
}

上面的语句就是用来检测SD卡的拔出的。我们在前面也已经介绍过了,在mmc_attach_bus函数内,把mmc_sd_ops赋给了host->bus_ops,
所以host->bus_ops->detect(host);实际回调的是mmc_sd_detect函数。
这里可能会有一个疑问:mmc_attach_bus函数是在mmc_rescan函数之后被调用的,那么就是说在mmc_rescan函数内,mmc_host->bus_ops还没有被赋值,
所以就不会回调mmc_sd_detect函数了。
我们要注意一点的是,mmc_rescan函数是在上电初始化过程中会被执行一次,因此在初始化的过程中完成了mmc_host->bus_ops的赋值。
而检测SD卡的拔出一定是在完成初始化以后,这时mmc_host->bus_ops已经被赋值,所以执行mmc_rescan函数中的
host->bus_ops->detect(host);语句不会出现任何问题。

我们来看看回调函数mmc_sd_detect,它定义在drivers/mmc/core/sd.c文件内:

/** Card detection callback from host.*/
static void mmc_sd_detect(struct mmc_host *host)
{int err = 0;
#ifdef CONFIG_MMC_PARANOID_SD_INITint retries = 5;
#endifBUG_ON(!host);BUG_ON(!host->card);mmc_get_card(host->card);/** Just check if our card has been removed.*/
#ifdef CONFIG_MMC_PARANOID_SD_INITwhile(retries) {err = mmc_send_status(host->card, NULL);if (err) {retries--;udelay(5);continue;}break;}if (!retries) {printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n",__func__, mmc_hostname(host), err);}
#elseerr = _mmc_detect_card_removed(host);
#endifmmc_put_card(host->card);if (err) {mmc_sd_remove(host);mmc_claim_host(host);mmc_detach_bus(host);mmc_power_off(host);mmc_release_host(host);}
}

为什么要用给SD卡发送状态信息来判断SD卡是否存活(即是否被拔出)?因为当把SD卡拔出时,当然是得不到任何SD卡的信息,所以会出错。
我们再回到mmc_sdio_detect函数内,如果SD卡已拔出,mmc_select_card函数返回非零值,则执行下面的if里的内容。

下面我们来验证一下SD驱动。把SD卡插入开发板的插槽中,上电启动开发板,如果系统能够打印出类似下面的信息,则说明开发板能够识别出SD卡:

s3c-sdi s3c2440-sdi: running at 0kHz (requested: 0kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 398kHz (requested: 400kHz).
s3c-sdi s3c2440-sdi: running at 16875kHz (requested: 25000kHz).
s3c-sdi s3c2440-sdi: running at 16875kHz (requested: 25000kHz).
mmc0: new SDHC card at address aaaa
mmcblk0: mmc0:aaaa SS08G 7.40 GiB mmcblk0: p1

如果先把开发板上电,然后再把SD卡插入插槽中,则系统也会自动打印出上面的信息。如果我们在带电的情况下把SD卡拔出,则系统会自动打印出类似下面的信息:

mmc0: card aaaa removed
s3c-sdis3c2440-sdi: powered down.

mini2440 sd卡加载过程详解相关推荐

  1. Java类的加载过程详解 面试高频!!!值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话: 八小时内谋生活,八小时外谋发展. 望别日与 ...

  2. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  3. 类加载顺序及加载过程详解

    转自: 类加载顺序及加载过程详解 下文笔者讲述类的加载顺序及加载过程的详解说明,如下所示 java创建对象的方式分为以下四种 new 反射克隆反序列化 class对象获取的方式分享 //没有完成初始化 ...

  4. 类的加载过程详解:加载、验证、准备、解析、初始化

    想要弄明白的知识点: 类加载的过程,加载.验证.准备.解析.初始化.每个部分详细描述. 加载阶段读入.class文件,class文件时二进制吗,为什么需要使用二进制的方式? 验证过程是防止什么问题?验 ...

  5. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  6. jboss之启动加载过程详解(-)

    今天看了看jboss的boot.log和server.log日志,结合自己的理解和其他的资料,现对jboss的启动和加载过程做出如下总结: 本文以JBoss Application Server 4. ...

  7. Java虚拟机(JVM)之类的加载过程详解

    java程序在对某个类进行引用.使用时,就会开始对该类进行加载,比如直接使用类加载器进行显式加载.创建该类的对象.使用该类的类变量等情况.类的加载是通过java虚拟机的类加载子系统完成的.类的加载主要 ...

  8. U-Boot 之一 零基础编译 U-Boot 过程详解 及 编译后的使用说明

      在之前的博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解 中我们说了要一步步搭建整个嵌入式 Linux 运行环境,今天就开始编译 U-Boot.我所使用的硬件平台及整个要搭建 ...

  9. linux系统启动过程详解-开机加电后发生了什么 --linux内核剖析(零)

    本文参考了如下文章 深入理解linux启动过程 mbr (主引导记录(Master Boot Record)) 电脑从开机加电到操作系统main函数之前执行的过程 详解linux系统的启动过程及系统初 ...

最新文章

  1. 【代码片段】base样式--针对bootstrap
  2. c/c++编码规范(2)--作用域
  3. mybatis $和#源代码分析
  4. springboot发送qq邮件
  5. Cisco IOS Unicast NAT 工作原理 [一]
  6. 计算概论(A)/基础编程练习1(8题)/3:晶晶赴约会
  7. java 名词解释等
  8. svn忽略class文件夹上传的方法
  9. php变量控制结构与函数,LAMP兄弟连原创视频教程(PHP笔记一--变量,流程控制结构,函数)...
  10. python递归输出斐波那契数列_艾艾精工涨停
  11. 小蒟蒻的blog美化汇总~
  12. 批量下载npm离线安装包
  13. Mac OS X:显示/设置分辨率的命令(源程序)
  14. 前端大串讲,狂神,狂神和飞哥
  15. 【Solr空间搜索SpatialSearch】
  16. FileSaver.js下载图片
  17. Unity开发笔记(五)—— 制作第四个小游戏《坦克大战》
  18. 【代码注释】浅谈对于代码注释的理解
  19. 巨亏超10亿!“汽车金融第一股”易鑫业绩腰斩,上半年却傍上腾讯
  20. GitLab CI/CD artifacts 属性的配置与使用

热门文章

  1. 中国科大的毕业生去向
  2. SQL计算某个字符的出现次数
  3. 科技云报道:云原生安全,腾讯产业互联网的底色
  4. 精彩回顾 | 一文盘点2021年中小企业数字化转型最新趋势
  5. visudo精确用户赋权与sudo日志跟踪
  6. 微信小程序开发笔记 进阶篇④——基于iconfont快速实现icon图标显示
  7. DOTA2无法找到有效的direct 3D
  8. HDU 5183 Negative and Positive (NP) (set + 读入外挂 乱搞)
  9. 分销小程序定制开发|分销系统开发对商家有哪些好处?
  10. 《Android源码设计模式解析与实战》读书笔记(十四)