目录

一、概述

二、初始化流程

2.1 main之前

2.2 rte_eal_init​

2.3 rte_eal_cpu_init

2.4 eal_parse_args

2.4.1 eal_parse_common_option

2.4.2 eal_create_runtime_dir

2.4.3 eal_adjust_config

2.5 eal_plugins_init

2.6 eal_option_device_parse

2.7 rte_config_init

2.7.1 rte_eal_config_create

2.7.2 eal_update_mem_config


一、概述

分析一下dpdk源码,基于dpdk19.02

二、初始化流程

2.1 main之前

main执行之前,会执行constructor,DPDK实现都在如下函数中,有四个优先级

[lib/librte_eal/common/include/Rte_common.h]

#define RTE_PRIORITY_LOG 101
#define RTE_PRIORITY_BUS 110
#define RTE_PRIORITY_CLASS 120
#define RTE_PRIORITY_LAST 65535#define RTE_PRIO(prio) \RTE_PRIORITY_ ## prio
#define RTE_INIT_PRIO(func, prio) \
static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)#define RTE_INIT(func) \RTE_INIT_PRIO(func, LAST)

调用RTE_INIT_PRIO主要集中在如下几处:

[lib/librte_eal/common/eal_common_log.c]

RTE_INIT_PRIO(rte_log_init, LOG)

#define RTE_REGISTER_BUS(nm, bus) \
RTE_INIT_PRIO(businitfn_ ##nm, BUS) \
{\(bus).name = RTE_STR(nm);\rte_bus_register(&bus); \
}#define RTE_REGISTER_CLASS(nm, cls) \
RTE_INIT_PRIO(classinitfn_ ##nm, CLASS) \
{\(cls).name = RTE_STR(nm); \rte_class_register(&cls); \
}

和kernel的驱动模型类似,DPDK抽象了bus,class这些结构,

struct rte_bus {TAILQ_ENTRY(rte_bus) next;   /**< Next bus object in linked list */const char *name;            /**< Name of the bus */rte_bus_scan_t scan;         /**< Scan for devices attached to bus */rte_bus_probe_t probe;       /**< Probe devices on bus */rte_bus_find_device_t find_device; /**< Find a device on the bus */rte_bus_plug_t plug;         /**< Probe single device for drivers */rte_bus_unplug_t unplug;     /**< Remove single device from driver */rte_bus_parse_t parse;       /**< Parse a device name */struct rte_bus_conf conf;    /**< Bus configuration */rte_bus_get_iommu_class_t get_iommu_class; /**< Get iommu class */rte_dev_iterate_t dev_iterate; /**< Device iterator. */rte_bus_hot_unplug_handler_t hot_unplug_handler;/**< handle hot-unplug failure on the bus */rte_bus_sigbus_handler_t sigbus_handler;/**< handle sigbus error on the bus */
};
  • bus主要抽象了一组bus接口,是其他bus类型的基类。

bus的注册也很简单,就是将bus结构挂到全局的rte_bus_list上,在我阅读的这个版本中,主要有以下集中bus类型:

Dpaa_bus.c (drivers\bus\dpaa):RTE_REGISTER_BUS(FSL_DPAA_BUS_NAME, rte_dpaa_bus.bus);
Fslmc_bus.c (drivers\bus\fslmc):RTE_REGISTER_BUS(FSLMC_BUS_NAME, rte_fslmc_bus.bus);
Ifpga_bus.c (drivers\bus\ifpga):RTE_REGISTER_BUS(IFPGA_BUS_NAME, rte_ifpga_bus);
Pci_common.c (drivers\bus\pci):RTE_REGISTER_BUS(pci, rte_pci_bus.bus);
Vdev.c (drivers\bus\vdev):RTE_REGISTER_BUS(vdev, rte_vdev_bus);
Vmbus_common.c (drivers\bus\vmbus):RTE_REGISTER_BUS(vmbus, rte_vmbus_bus.bus);

本文关注红色部分的两种bus,pci bus和vdev bus,蓝色部分为bus name

[driver/bus/pci/pci_common.c]

struct rte_pci_bus rte_pci_bus = {.bus = {.scan = rte_pci_scan,.probe = rte_pci_probe,.find_device = pci_find_device,.plug = pci_plug,.unplug = pci_unplug,.parse = pci_parse,.get_iommu_class = rte_pci_get_iommu_class,.dev_iterate = rte_pci_dev_iterate,.hot_unplug_handler = pci_hot_unplug_handler,.sigbus_handler = pci_sigbus_handler,},.device_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.device_list),.driver_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.driver_list),
};

[driver/bus/vdev/vdev.c]

static struct rte_bus rte_vdev_bus = {.scan = vdev_scan,.probe = vdev_probe,.find_device = rte_vdev_find_device,.plug = vdev_plug,.unplug = vdev_unplug,.parse = vdev_parse,.dev_iterate = rte_vdev_dev_iterate,
};RTE_REGISTER_BUS(vdev, rte_vdev_bus);

rte_pci_bus继承rte_bus,主要还是应该借鉴了内核的实现——一种bus下挂多个设备,设备由基于特定总线的驱动程序来驱动,构成了三者的对应关系,关于bus的接口调用会在初始化的时候展开。

RTE_INIT对应最低优先级的注册,RTE_INIT对应的注册很多,主要是驱动的注册,也有对其进行的基本封装,如pci

#define RTE_PMD_REGISTER_PCI(nm, pci_drv) \
RTE_INIT(pciinitfn_ ##nm) \
{\(pci_drv).driver.name = RTE_STR(nm);\rte_pci_register(&pci_drv); \
} \

基于pci总线的驱动有很多,列一下intel相关的网卡:

I40e_ethdev.c (drivers\net\i40e):RTE_PMD_REGISTER_PCI(net_i40e, rte_i40e_pmd);
I40e_ethdev_vf.c (drivers\net\i40e):RTE_PMD_REGISTER_PCI(net_i40e_vf, rte_i40evf_pmd);
Igb_ethdev.c (drivers\net\e1000):RTE_PMD_REGISTER_PCI(net_e1000_igb, rte_igb_pmd);
Igb_ethdev.c (drivers\net\e1000):RTE_PMD_REGISTER_PCI(net_e1000_igb_vf, rte_igbvf_pmd);
Ixgbe_ethdev.c (drivers\net\ixgbe):RTE_PMD_REGISTER_PCI(net_ixgbe, rte_ixgbe_pmd);
Ixgbe_ethdev.c (drivers\net\ixgbe):RTE_PMD_REGISTER_PCI(net_ixgbe_vf, rte_ixgbevf_pmd);

2.2 rte_eal_init

2.3 rte_eal_cpu_init

这个函数的目的就是要确定运行环境中node(socket), lcore, core的数量和他们之间的对应关系。

确定lcore_id和socket_id (node)的对应关系,注释写的很明白,通过判定下面的路径是否可访问

/sys/devices/system/node/nodeX/cpuX

确定cpu的在位情况,通过下面的信息是否可访问确定(每个lcore对应的core_id):

/sys/devices/system/cpu/cpuX/topology/core_id

最终这些信息都会在DPDK启动时输出出来

RTE_LOG(DEBUG, EAL, "Detected lcore %u as " "core %u on socket %u\n", lcore_id, lcore_config[lcore_id].core_id, core_config[lcore_id].socket_id);
RTE_LOG(INFO, EAL, "Detected %u lcore(s)\n", config->lcore_count);
RTE_LOG(INFO, EAL, "Detected %u NUMA nodes\n", config->numa_node_count);

这些信息存储的位置在全局配置(rte_config)中,红色部分

struct rte_config {
    uint32_t master_lcore;     
    uint32_t lcore_count;                                                                   //逻辑核的数量
    uint32_t numa_node_count;                                                        //node的数量
    uint32_t numa_nodes[RTE_MAX_NUMA_NODES];                  //node对应的node id
    uint32_t service_lcore_count;
    enum rte_lcore_role_t lcore_role[RTE_MAX_LCORE];              //每个逻辑核的在位情况

enum rte_proc_type_t process_type;

enum rte_iova_mode iova_mode;

struct rte_mem_config *mem_config;
} __attribute__((__packed__));

lcore内部配置lcore_config中

struct lcore_config {
    unsigned detected;         /**< true if lcore was detected */
    pthread_t thread_id;       /**< pthread identifier */
    int pipe_master2slave[2];  /**< communication pipe with master */
    int pipe_slave2master[2];  /**< communication pipe with master */
    lcore_function_t * volatile f;         /**< function to call */
    void * volatile arg;       /**< argument of function */
    volatile int ret;          /**< return value of function */
    volatile enum rte_lcore_state_t state; /**< lcore state */
    unsigned socket_id;        /**< physical socket id for this lcore */
    unsigned core_id;          /**< core number on socket for this lcore */
    int core_index;            /**< relative index, starting from 0 */
    rte_cpuset_t cpuset;       /**< cpu set which the lcore affinity to */
    uint8_t core_role;         /**< role of core eg: OFF, RTE, SERVICE */
};

2.4 eal_parse_args

eal_parse_args和eal_log_level_parse流程一致,都是解析DPDK基本的参数,具体过程没什么可说的,把参数选项列一下。

  • --log-level在eal_log_level_parse解析

2.4.1 eal_parse_common_option

  • -b 黑名单
  • -w 白名单,不能与-b同时指定 (eg. -w0000:02:01.0,-w0000:02:05.0)

上述两个参数都会将对应的类型及传递的参数加入devopt_list,具体参见eal_option_device_add。这里仅列一下类型:

enum rte_devtype {RTE_DEVTYPE_WHITELISTED_PCI,RTE_DEVTYPE_BLACKLISTED_PCI,RTE_DEVTYPE_VIRTUAL,
};struct device_option {TAILQ_ENTRY(device_option) next;enum rte_devtype type;char arg[];
};
  • -c coremask, 以掩码的形式指定使用哪个lcore,会综合lcore的在位情况,取交集——只将用户指定使用的cpu置为对应的状态
  • -l corelist
  • --lcores,用法在eal_parse_lcores的注释中找到

使用下面的定义描述core指定方式:

#define LCORE_OPT_LST 1   // -l
#define LCORE_OPT_MSK 2   //-c
#define LCORE_OPT_MAP 3   //--lcores
  • -s 指定service core,和-c用法一致
  • -S指定service corelist,和-l用法一致

在lcore_config的core_role中以下面的定义标记

enum rte_lcore_role_t {ROLE_RTE,ROLE_OFF,ROLE_SERVICE,
};
  • -m memory      指定memory大小
  • -n  channels    强制channels
  • -r   ranks         强制ranks
  • -d 强制加载外部driver,其实就是使用动态库做插件机制,加入solib_list管理,后续分析eal_plugins_init会说明。
/* Definition for shared object drivers. */
struct shared_driver {TAILQ_ENTRY(shared_driver) next;char    name[PATH_MAX];void*   lib_handle;
};
  • --huge-unlink
  • --no-huge
  • --legacy-mem
  • --hugepage_unlink
  • --no-pci
  • --no-hpet
  • --vmware-tsc-map
  • --no-shconf                 不使用shared conf
  • --in-memory               开启后默认打开--hugepage_unlink和--no-shconf
  • --proc-type

proc-type主要包括如下类型:

/*** The type of process in a linuxapp, multi-process setup*/
enum rte_proc_type_t {RTE_PROC_AUTO = -1,   /* allow auto-detection of primary/secondary */RTE_PROC_PRIMARY = 0, /* set to zero, so primary is the default */RTE_PROC_SECONDARY,RTE_PROC_INVALID
};
  • --master-lcore ,指定master lcore,默认0
  • --vdev   和-b/-w 用法类似,只不过 指定类型是RTE_DEVTYPE_VIRTUAL
  • --syslog
  • --single-file-segments
  • --iova-mode 指定iommu的工作模式,pa/va
/*** IOVA mapping mode.** IOVA mapping mode is iommu programming mode of a device.* That device (for example: IOMMU backed DMA device) based* on rte_iova_mode will generate physical or virtual address.**/
enum rte_iova_mode {RTE_IOVA_DC = 0,   /* Don't care mode */RTE_IOVA_PA = (1 << 0), /* DMA using physical address */RTE_IOVA_VA = (1 << 1)  /* DMA using virtual address */
};
  • --vfio-intr
{ "legacy", RTE_INTR_MODE_LEGACY },
{ "msi", RTE_INTR_MODE_MSI },
{ "msix", RTE_INTR_MODE_MSIX },
  • --huge-dir     指定hugepage的mount目录
  • --file-prefix   hugefile的前缀
  • --socket-mem
  • --socket-limit
  • --base-virtaddr          指定mmap起始地址,16M对齐
  • --create-uio-dev
  • --mbuf-pool-ops-name
  • --match-allocations

2.4.2 eal_create_runtime_dir

函数比较简单,就是 为dpdk创建统一的runtime的各种文件,其目录默认如下,对应全局变量runtime_dir

/var/run/dpdk/rte

2.4.3 eal_adjust_config

主要是调整internal_config,包括

  1. 如果没有指定-c/-l/--lcores这些指定运行于哪个lcore的参数(core_parsed), 自动检测亲和性关系
  2. 如果没有指定--master-lcore(master_lcore_parsed),自动选择,一般是第一个可用lcore

基本上,参数解析都存储在internal_config这个结构:

struct internal_config {
    volatile size_t memory;                                         // -m memory 
    volatile unsigned force_nchannel;                        // -n  channels
    volatile unsigned force_nrank;                              // -r   ranks
    volatile unsigned no_hugetlbfs;                            // --no-huge
    unsigned hugepage_unlink;                                 // --hugepage_unlink
    volatile unsigned no_pci;                                     // --no-pci
    volatile unsigned no_hpet;                                   // --no-hpet
    volatile unsigned vmware_tsc_map;                    // --vmware-tsc-map                                        
    volatile unsigned no_shconf;                               // --no-shconf
    volatile unsigned in_memory;                             // --in-memory 
    
    volatile unsigned create_uio_dev;                      // --create-uio-dev
    volatile enum rte_proc_type_t process_type;     // --proc-type

volatile unsigned force_sockets;                                                      // --socket-mem
    volatile uint64_t socket_mem[RTE_MAX_NUMA_NODES];           // --socket-mem
    volatile unsigned force_socket_limits;                                             // --socket-limit
    volatile uint64_t socket_limit[RTE_MAX_NUMA_NODES];            // --socket-limit
    uintptr_t base_virtaddr;                                                                   // --base-virtaddr
    volatile unsigned legacy_mem;                                                      // --legacy-mem, 使用--no-huge时默认打开

volatile unsigned match_allocations;                                            // --match-allocations
    volatile unsigned single_file_segments;                                       // --single-file-segments
    volatile int syslog_facility;                                                             // --syslog
    volatile enum rte_intr_mode vfio_intr_mode;                              /// --vfio-intr
    char *hugefile_prefix;                                                                   // --file-prefix
    char *hugepage_dir;                                                                    // --huge-dir    
    char *user_mbuf_pool_ops_name;                                             // --mbuf-pool-ops-name
            
    unsigned num_hugepage_sizes;     
    struct hugepage_info hugepage_info[MAX_HUGEPAGE_SIZES];
    enum rte_iova_mode iova_mode ;                 // --iova-mode
    volatile unsigned int init_complete;
};

参数解析过后,rte_config赋值情况:

struct rte_config {
    uint32_t master_lcore;                                                                 // --master-lcore
    uint32_t lcore_count;                                                                   //逻辑核的数量
    uint32_t numa_node_count;                                                        //node的数量
    uint32_t numa_nodes[RTE_MAX_NUMA_NODES];                  //node对应的node id
    uint32_t service_lcore_count;
    enum rte_lcore_role_t lcore_role[RTE_MAX_LCORE];              //每个逻辑核的在位情况

enum rte_proc_type_t process_type;

enum rte_iova_mode iova_mode;

struct rte_mem_config *mem_config;
} __attribute__((__packed__));

2.5 eal_plugins_init

插件机制,就是动态库,插件的路径可以由-d参数指定,也可以由RTE_EAL_PMD_PATH(通过CONFIG_RTE_EAL_PMD_PATH)指定,函数会加载-d指定的so或者将指定路径下满足条件的插件加入管理(eal_plugin_add),并通过dlopen加载

2.6 eal_option_device_parse

还记得在参数解析的时候,有三个参数(-b/-w/--vdev),他们分别将设备的特性注册到了devopt_list上,在本节的函数调用时,bus的scan和probe还没有进行,先将用户对device的一些限制或者要求解析出来,这样后面做bus相关处理的时候就可以应用了。

函数的实现很简单,就是将之前devopt_list的项都摘下来,构造新参数rte_devargs并挂到新的全局devargs_list上

/**
 * Structure that stores a device given by the user with its arguments
 *
 * A user device is a physical or a virtual device given by the user to
 * the DPDK application at startup through command line arguments.
 *
 * The structure stores the configuration of the device, its PCI
 * identifier if it's a PCI device or the driver name if it's a virtual
 * device.
 */
struct rte_devargs {
    TAILQ_ENTRY(rte_devargs) next;
    enum rte_devtype type;
    enum rte_dev_policy policy;
    char name[RTE_DEV_NAME_MAX_LEN];
    RTE_STD_C11
    union {
    /** Arguments string as given by user or "" for no argument. */
        char *args;
        const char *drv_str;
    };
    struct rte_bus *bus; /**< bus handle. */
    struct rte_class *cls; /**< class handle. */
    const char *bus_str; /**< bus-related part of device string. */
    const char *cls_str; /**< class-related part of device string. */
    const char *data; /**< Device string storage. */
};

rte_devarg才是描述一个device构造参数的真正结构,之前用户传递的参数对应args变量,这里我们重点关注:

__rte_experimental int rte_devargs_parse(struct rte_devargs *da, const char *dev)
{.../* Retrieve eventual bus info */do {devname = dev;bus = rte_bus_find(bus, bus_name_cmp, dev);if (bus == NULL)break;devname = dev + strlen(bus->name) + 1;if (rte_bus_find_by_device_name(devname) == bus)break;} while (1);/* Store device name */i = 0;while (devname[i] != '\0' && devname[i] != ',') {da->name[i] = devname[i];i++;if (i == maxlen) {RTE_LOG(WARNING, EAL, "Parsing \"%s\": device name should be shorter than %zu\n",dev, maxlen);da->name[i - 1] = '\0';return -EINVAL;}}da->name[i] = '\0';if (bus == NULL) {bus = rte_bus_find_by_device_name(da->name);if (bus == NULL) {RTE_LOG(ERR, EAL, "failed to parse device \"%s\"\n",da->name);return -EFAULT;}}da->bus = bus;/* Parse eventual device arguments */if (devname[i] == ',')da->args = strdup(&devname[i + 1]);elseda->args = strdup("");if (da->args == NULL) {RTE_LOG(ERR, EAL, "not enough memory to parse arguments\n");return -ENOMEM;}return 0;
  • rte_bus_find匹配dev和bus->name,如果是参数-w/-b dev是DBDF/BDF的格式,bus->name是总线名称,这时候不会匹配
  • 如果不匹配(没有找到bus)就使用DBDF/BDF填充rte_devargs中的name
  • 这时候rte_bus_find_by_device_name需找匹配bus,深入这个函数可以发现匹配DBDF/BDF这种格式,这时候一定会匹配pci bus,因为只有pci bus是这种格式
  • 最后,对rte_devargs中的bus字段进行赋值。

解析过后,就是对其他rte_devargs字段进行赋值,只贴下代码,很简单,主要就是根据指定类型确定scan_mode和policy:

 devargs->type = devtype;bus = devargs->bus;if (devargs->type == RTE_DEVTYPE_BLACKLISTED_PCI)devargs->policy = RTE_DEV_BLACKLISTED;if (bus->conf.scan_mode == RTE_BUS_SCAN_UNDEFINED) {if (devargs->policy == RTE_DEV_WHITELISTED)bus->conf.scan_mode = RTE_BUS_SCAN_WHITELIST;else if (devargs->policy == RTE_DEV_BLACKLISTED)bus->conf.scan_mode = RTE_BUS_SCAN_BLACKLIST;}TAILQ_INSERT_TAIL(&devargs_list, devargs, next);

2.7 rte_config_init

在这个阶段,默认情况下执行PRIMARY的流程

2.7.1 rte_eal_config_create

映射/var/run/dpdk/rte/config文件(mem_cfg_fd)为文件共享

rte_mem_cfg_addr = mmap(rte_mem_cfg_addr, sizeof(*rte_config.mem_config),PROT_READ | PROT_WRITE, MAP_SHARED, mem_cfg_fd, 0);

这块内存对应全局rte_config的mem_config,映射完成后,将基本的内容进行赋值:

 memcpy(rte_mem_cfg_addr, &early_mem_config, sizeof(early_mem_config));rte_config.mem_config = rte_mem_cfg_addr;/* store address of the config in the config itself so that secondary* processes could later map the config into this exact location */rte_config.mem_config->mem_cfg_addr = (uintptr_t) rte_mem_cfg_addr;rte_config.mem_config->dma_maskbits = 0;

2.7.2 eal_update_mem_config

将配置的参数进行赋值

static void eal_update_mem_config(void)
{struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;mcfg->legacy_mem = internal_config.legacy_mem;mcfg->single_file_segments = internal_config.single_file_segments;
}

最终看下赋值情况:

/**
 * the structure for the memory configuration for the RTE.
 * Used by the rte_config structure. It is separated out, as for multi-process
 * support, the memory details should be shared across instances
 */
struct rte_mem_config {
    volatile uint32_t magic;   /**< Magic number - Sanity check. */

uint32_t nchannel;    /**< Number of channels (0 if unknown). */
    uint32_t nrank;       /**< Number of ranks (0 if unknown). */

rte_rwlock_t mlock;   /**< only used by memzone LIB for thread-safe. */
    rte_rwlock_t qlock;   /**< used for tailq operation for thread safe. */
    rte_rwlock_t mplock;  /**< only used by mempool LIB for thread-safe. */

rte_rwlock_t memory_hotplug_lock;
    struct rte_fbarray memzones; /**< Memzone descriptors. */

struct rte_memseg_list memsegs[RTE_MAX_MEMSEG_LISTS];
    struct rte_tailq_head tailq_head[RTE_MAX_TAILQ]; /**< Tailqs for objects */

struct malloc_heap malloc_heaps[RTE_MAX_HEAPS];

int next_socket_id;

uint64_t mem_cfg_addr;

uint32_t legacy_mem;
    uint32_t single_file_segments;

uint8_t dma_maskbits;
} __attribute__((__packed__));

DPDK初始化分析(一)相关推荐

  1. SECTIONS bss段初始化分析

    SECTIONS bss段初始化分析 都知道bss段需要初始化,但是这是为什么呢? 通过浏览资料,都会发现,bss段是不会出现在程序下载文件(*.bin .hex)中的,因为全都是0.如果把出现在程序 ...

  2. sparkContext之一:sparkContext的初始化分析

    Spark源码学习:sparkContext的初始化分析 spark可以运行在本地模式local下,可以运行在yarn和standalone模式下,但是本地程序是通过什么渠道和这些集群交互的呢?那就是 ...

  3. linux libata初始化分析

    进来分析libata模块,颇有所感,记录如下,希望能对大家有所帮助,同时也对自己的理解进一步深入. linux版本:linux-2.6.24.3 注:因完全是个人理解,理解不当难免,恳请批评指正!!! ...

  4. dpdk 18 ixgbe驱动初始化分析

    rte_log_set_global_level rte_log_set_global_level(uint32_t level) pci bus注册 TE_REGISTER_BUS(pci, rte ...

  5. DPDK rte_eal_init 初始化分析(根据最新的更新)

    端口初始化流程 1. 注册设备驱动到"dev_driver_list"链表中 2.扫描系统中的pci设备,并注册到"pci_device_list"中 3.初始 ...

  6. dpdk代码分析——内存初始化

    一. 前言 dpdk 是 intel 开发的x86芯片上用于高性能网络处理的基础库,业内比较常用的模式是linux-app模式,即利用该基础库,在用户层空间做数据包处理,有了这个基础库,可以方便地在写 ...

  7. celery源码分析-worker初始化分析(下)

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery的worker启动 在上文中分析到了Hub类的初始化,接下来继续分析Pool类的 ...

  8. celery源码分析-wroker初始化分析(上)

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery与Django的配合使用 首先,在安装有django的环境中创建一个django ...

  9. Service Manger的初始化分析

    在init.rc文件中,有定义serviceservicemanager /system/bin/servicemanager,在系统初始化会解析init.rc文件时,遇到这句话,将会把service ...

  10. Android编译系统环境过程初始化分析【转】

    本文转载自:http://blog.csdn.net/luoshengyang/article/details/18928789 Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就 ...

最新文章

  1. 【Linux探索之旅】第二部分第五课:用户和权限,有权就任性
  2. Spring框架5.1将提供对Java 11的支持
  3. python数字类型-Python3 - 数字类型
  4. Java常用分析工具Jps、Jstat、Jinfo、Jstack以及Jconsole的简单介绍和使用
  5. mysql外键约束查询语句_MySQL数据库 : 查询语句,连接查询及外键约束
  6. 安卓linux环境 查看进程,查看基于Android 系统单个进程内存、CPU使用情况的几种方法...
  7. canvas离屏技术与放大镜实现
  8. 简单暴力到dp的优化(初级篇)
  9. 基于Qt的P2P局域网聊天及文件传送软件设计
  10. 在rac集群上开启OEM
  11. 利用元数据管理数据质量
  12. 项目二:2020年华为软件精英挑战赛
  13. 中职网络安全大赛攻防阶段加固(仅供参考!!!)
  14. 倾斜摄影技术应用之:自动三维建模对输电线路走廊资源快速调查
  15. el-table 表尾行合并,列合并,统计
  16. win10升级助手_关闭win10系统更新
  17. RxJava串行执行任务
  18. 失传千年AE特效真经(二)
  19. Python网络编程(一)
  20. 网络能ping通,浏览器无法上网

热门文章

  1. ChipScope用法总结
  2. 软件公司内部协作平台的选型(项目/BUG/需求管理及用户支持)
  3. 台媒体:IBM PC将成特洛伊木马【ZZ】【另一个角度】
  4. 《勒索软件经济分析》:“理想赎金”950英镑
  5. 用Markdown格式写一份前端简历
  6. mybatis # $区别
  7. Action以外的类中来获得Spring所管理的Service对象
  8. MDataTable属性RecordsAffected新应用:WebService与Json交互的记录总数
  9. wamp环境单独安装(windows下apache2.4、mysql5.5、php5.5的版本)
  10. MYSQL 取上一条记录,与下一条记录