【摘要】
【分析一】FDT与TAG
【分析二】boot中对FDT的支持
【分析三】Uboot下调试FDT
【分析四】kernel中对FDT的支持
【分析五】FDT兼容TAG
【总结】
【附录】

注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)

【摘要】

为何要写此文?
随着linux 内核版本的逐渐提升,在3.10.* 版本之后,linux系统arm架构中对驱动实现方式做了较大调整,驱动中广泛采用FDT的实现方式。因此对于linux系统上驱的动开发来说,了解FDT尤为重要。

FDT对系统软件开发工作的实际影响?
1) FDT取代了tag.这一点涉及bootloader和kernel的变动。
例如:经常用到的uboot里面bootargs、环境变量等相关实现要有所改动。
2) 驱动中板级信息的获取方式变动。这一点涉及到具体驱动实现,无论flash、usb还是音视频等驱动的实现都要涉及FDT,如果不了解,驱动就无法实现。
例如:我们要获取一个驱动的中断号或者控制器相关基地址等信息,都要通过FDT。

什么是FDT?
FDT:Flattened Device Tree即扁平设备树。linux系统最早引入 是在linux2.6*版本的powerpc架构中。早前曾做过powerpc架构的开发,不过当时系统已经对FDT做了很好的支持,驱动开发只是简单的配置了dts文件。当时想写一篇介绍FDT的文章,但一直未能如愿。

直到arm架构也引入了FDT,写一篇这样的文档显得很有必要,一方面希望记录工作。另一方面也希望为涉及FDT开发的同仁提供一点参考。

linux为何要引入FDT?
引入FDT目的很明确--降低代码冗余。
过去的linux源码,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多代码都是描述板级信息,对于内核来将,都是垃圾。随着arm的不断普及,垃圾代码越来越多,已经到了kernel无法容忍的地步,因此在3.10*之后,linux系统采用了FDT的实现方式,裁剪掉这部分代码冗余。

【分析一】FDT与TAG

本节将介绍两种FDT实现方案:FDT取代TAG 和FDT兼容TAG。不过在介绍之前,我们先明确下FDT到底是什么?我们需要做哪些工作?

1 首先要实现FDT功能,必须有一个*.dtb文件的输出。
dtb文件是通过dtc工具生成,dtc(device tree complier)是开源工具,可以理解为dts文件的编译器。一般uboot和kernel代码中都有集成,直接拿来用即可。为了生成dtb 需要修改makefile (一般可以修改uboot的顶级Makefile或者linux中arch/arm/boot/Makefile)如下:

$(DTB_OBJS):FORCE$(obj)/dts/dtc -I dts -O dtb -i $(obj)/dts -p 1024 -o $(DTB_OBJS) $(obj)/dts/example_plat_version.dts 

观察上述命令:

1) 首先输出 dtb文件,如DTB_OBJS=example_plat_version.dts.dtb

dtb文件如何使用?这个文件可以烧录到flash上的任何位置。
Ps:要想使用fdt兼容tag,则必须把dtb放到uImage后面,这是因为kernel在启动阶段有限制。如果用FDT替代tag则可放到任何位置。

2) dtc是开源工具,主要功能是解析dts文件,并把解析的结果保存到dtb文件中。

什么是dts文件? dts文件是设备树的源码。

举例:example_plat_version.dts

/include/ "example-plat.dtsi"
/{mode = "example plat evm Board";compatible = "example,**";chosen{bootargs = "console=ttyS0,115200 mem=100M root=/dev/mtdblock0 init=/linuxrc"; };apb@80000000{i2c0:i2c@e80030000{status= "ok";ak4293: codec@12{compatible = "example,ak4293";reg = <0x12>;};it66121@4c{compatible = "example,it66121";reg = <0x44>;};};};};

如上是dts文件的片段,这个文件是需要我们正确配置的。

1) 其中 chosen和apb@80000000可以被称为一个节点--node,而i2c0可以被称为一个子节点—subnode,”/”是唯一的根节点。
2) 不难发现,chosen中保存的bootargs就是我们uboot下使用的bootargs,以前它被保存在tag中,如今放到了dts文件里。
3) apb@80030000中保存的是i2c驱动的信息。以前的系统中,这一部分是被定义在arch/arm/plat-xxx和arch/arm/mach-xxx 代码中,如今放到dts文件中。

至此kernel代码得到精简。
Ps:dtc是如何解析dts文件的不必详查,如感兴趣可以参考dtc源码。

【分析二】boot中对FDT的支持

如果要使用tag,需boot和kernel同时支持,那么用FDT取代TAG也同样如此.

uboot中对FDT的支持:

1) uboot代码中已经对fdt有支持,可以通过开启CONFIG_OF_LIBFDT配置,实现FDT支持,如此会省掉一部分工作。例如:fdt调试命令可以直接使用。
2) 如前所述dtb文件被烧录到flash上。在Uboot引导内核启动之前会将dtb拷贝到内存上,并对dtb的内容进行修改,这一步是为了支持对环境变量的动态修改。

以下介绍u-boot中如何修改dts文件中定义的内容:重点观察example_plat_version.dts文件里chosen节点中bootargs参数的修改,关键函数fdt_chosen:

int fdt_chosen(void *fdt, int force)
{int nodeoffset;char *str;const char *path;nodeoffset= fdt_path_offset(fdt, "/chosen");if(nodeoffset<0){nodeoffset=fdt_add_subnode(fdt,0,"chosen");if(nodeoffset<0){return nodeoffset;}}str = getenv("bootargs");path = fdt_set_prop(fdt,nodeoffset,"bootargs",str,strlen(str)+1);return 0;
} 

上述函数的fdt_path_offset(fdt,”chosen”)获取的是dts文件中的:

  chosen{bootargs = "console=ttyS0,115200 mem=100M root=/dev/mtdblock0 init=/linuxrc"; };

要想修改bootargs则调用fdt_setprop_string; Ps:其中fdt_setprop_string等接口是FDT提供的标准接口,boot和kernel源码中都可调用。

3)将dtb从flash拷贝到dram上。假设拷贝到dram=0x200100的地址,则uboot跳转到kernel时再通过cpu的通用寄存器r2告知系统这个地址,这一点与tag没有分别。至此完成boot下对fdt的支持。

【分析三】Uboot下调试FDT

Uboot支持标准的fdt命令修改dtb的内容。

1 举例:常用调试命令
1>fdt list :列出系统中所有节点。即上面dts文件中的节点。

#fdt list
/{#address-cells = <0x1000000>;#size-cells = <0x1000000>;compatible = "example,**";model = "example plat evm Board";chosen{};aliases{};memory{};cpus{};apb@80000000{};
};

2>列出aliases节点:

#fdt list /aliases
aliases{serial0 = "/apb@80000000/uart@8005000";nand = "/apb@90000000/nand@9001000";i2c0 = "/ahb@70000000/i2c@7001000";
};

3>查看boot分区信息:

#fdt list nand
nand@9001000{compatible = "example,nand";#address-cells = <0x1000000>;#size-cells = <0x1000000>;partition@0{};partition@1{};
};
#fdt list nand/partition@0
partition@0{reg = <0x0 0x40000>;label = "uboot";
};

2 修改dtb举例:

#fdt list /memory
memory{device_type = "memory";reg = <0x2000,0xe007>;
};
#fdt set /memory reg <0x200000 0x6e00000>
#
#fdt list /memory
memory{device_type = "memory";reg = <0x2000,0xe006>;
};

1>修改前 reg= <0x2000 0xe007>(字节序反了):实际表示linux 系统内存从0x200000开始大小为0x7e00000

2>修改后 reg=<0x2000 0xe006>:实际表示linux 系统内存从0x200000开始大小为0x6e00000

3>以上介绍了boot下显示和修改dtb的命令,其他命令可实际摸索一下,不逐一举例。

【分析四】kernel中对FDT的支持

1 设备树初始化及解析
分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:
(1)首先在内核入口处将从u-boot传递过来的镜像基地址。这一步系统已经有实现好,不需要再开发。
(2)通过调用early_init_dt_scan()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。这一步需要二次开发,可以在系统搭建的框架中,加入项目特有的初始参数。
(3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。
(4)调用unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。这一步不需要二次开发
(5)内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备等。这一步可能会二次开发。

如上:步骤1-4主要通过如下函数实现:

void __init setup_arch(char **cmdline_p)
{   /*实现步骤1-3*/mdesc = setup_machine_fdt(__atags_pointer);/* 实现步骤4 */unflatten_device_tree();
}

如上:步骤5主要通过如下函数实现:

int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)
{root = root ? of_node_get(root) : of_find_node_by_path("/");
/*
为每个node创建dev.  实现步骤5
struct platform_device也是在此时创建的
*/for_each_child_of_node(root, child) {rc = of_platform_bus_create(child, matches, lookup, parent, true);if (rc)break;}
}

在此不对代码进行逐行分析,只对重点函数的功能予以说明,并且对可能需要二次开发的地方重点举例说明。实际工作中,主要集中在第(2)、(5)步。

2 重点函数分析.
1)setup_machine_fdt()
该函数主要解析设备树中 和系统初始启动有关的参数,比如bootargs、Linux使用的物理内存等。实际项目中新添加的uboot参数,也可以在此解析。如下:

举例:如何通过FDT将uboot参数example_para传入kernel:
第一步:将example_para作为一个子节点,加入dts的根节点中。

如下example_plat_version.dts文件:

/include/ "example-plat.dtsi"
/{mode = "example plat evm Board";compatible = "example,**";chosen{bootargs = "console=ttyS0,115200 mem=100M root=/dev/mtdblock0 init=/linuxrc"; };apb@80000000{};/* 新添加的节点*/example_para{test = <1>;ethaddr = "0x00";};};

第二步kernel中解析example_para。如下early_init_dt_scan_example_para需要我们自己来实现。

struct machine_desc *__init setup_machine_fdt(unsigned int dt_phys)
{of_scan_fdt_dt(early_init_dt_scan_chosen,boot_command_line);of_scan_fdt_dt(early_init_dt_scan_memory,NULL);of_scan_fdt_dt(early_init_dt_scan_example_para,NULL);
}

第三步解析dtb文件 查找到example_para

int __init early_init_dt_scan_example_para(unsigned node,char *uname,int depth,void *data)
{char *pchar;unsigned int leng;unsigned int *pvar;if(strcmp(uname,"example_para")==0){pvar=of_get_flat_dt_prop(node,"ethaddr",&leng);pchar=of_get_flat_dt_prop(node,"ethaddr",&leng);}
}

至此,完成example_para节点添加,大部分初始参数都可以如此实现.以下简单分析一下上述实现过程.

2) of_scan_flat_dt()实现功能:
dtc工具在解析dts时,会生成一个描述dts的结构,并放在dtb的头部。
dtc工具在解析dts时,example_para被保存到dtb的结构块中,因此从结构块的起始偏移地址开始遍历,能够在dtb中找到name为example_para的节点。

Ps:注意此时dtb被拷贝到了DRAM.

DTB的头结构如下:struct boot_param_header {__be32 magic;                //设备树魔数,固定为0xd00dfeed__be32 totalsize;            //整个设备树的大小__be32 off_dt_struct;        //保存结构块在整个设备树中的偏移__be32 off_dt_strings;        //保存的字符串块在设备树中的偏移__be32 off_mem_rsvmap;        //保留内存区,该区保留了不能被内核动态分配的内存空间__be32 version;            //设备树版本__be32 last_comp_version;    //向下兼容版本号__be32 boot_cpuid_phys;    //为在多核处理器中用于启动的主cpu的物理id__be32 dt_strings_size;    //字符串块大小__be32 dt_struct_size;     //结构块大小
};

3) early_init_dt_scan_example_para()->of_get_flat_dt_prop()实现功能。
找到example_para节点后,继续查找property,例如test对应一个属性名为test的属性结构体即struct property。

struct property {char *name;        //属性名int length;        //属性值长度void *value;        //属性值struct property *next; //指向下一个属性unsigned long _flags; //标志unsigned int unique_id;
};

到此,linux系统可以解析出example_para的值。
如下函数中实现example_para解析:
Ps: 注意查找方式遵循dtb的生成方式,即dtc对dts的解析规则。

4)unflatten_device_tree()实现功能。
实际上 如前所述,系统启动初期已经解析了dtb,不过只是查找少部分启动所需的初始化参数,如chosen(example_para)。系统真正完成对整个dtb文件解析是在该函数中实现。只要合理配置dts文件,该函数就能自动解析出对应node,不需要我么再次开发。unflatten_device_tree()函数解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备。

设备节点结构体:

struct device_node {const char *name;    //设备nameconst char *type; //设备类型phandle phandle;const char *full_name; //设备全称,包括父设备名struct property *properties; //设备属性链表struct property *deadprops; //removed propertiesstruct device_node *parent; //指向父节点struct device_node *child; //指向子节点struct device_node *sibling; //指向兄弟节点struct device_node *next; //相同设备类型的下一个节点struct device_node *allnext; //next in list of all nodesstruct proc_dir_entry *pde; //该节点对应的procstruct kref kref;unsigned long _flags;void *data;
};

其中,dts文件中每个{ }内的内容对应一个device_node,即上述结构体。

5)of_platform_populate()
该函数能完成驱动设备初始化,其实在此并未实现真正意义上的硬件初始化,只是把驱动所需资源 关联到了platform设备。
实现过程如下:
(1) 找到根节点。从of_allnodes开始遍历,找到 name为 “/” 的节点。
(2) 为根节点的每个子节点创建 struct platform_device *dev;并把子节点对应的device_node结构关联到platform_dev,如下实现:
of_device_alloc()中 dev->dev.of_node = of_node_get(np);
(3) 注册驱动 driver_register()时,通过platform_device找到device_node,从而找到dts中配置的驱动资源。

【分析五】FDT兼容TAG

Linux系统在实现 FDT时,除了考虑直接用FDT替代tag外,还考虑的兼容tag的做法。

考虑FDT兼容tag的情况,不需要对uboot进行修改。即前面所述的分析二和分析三都可以不用实现,不过内核中要做好兼容措施。下面介绍一下实现步骤:

第一步:打开内核对FDT兼容TAG的配置,make menuconfig如下:

->


第二步 分析内核启动第一阶段,将tag转换成FDT。
分析下面的小段代码arch/arm/boot/compressed/head.s:
1) 至此 r6中保存的是dtb在dram上的起始地址,r8中保存atag在dram上地址。把dtb 头部的magic信息保存到r1,并校验是否为0xd00dfeed,这一步实际是检查是否有dtb文件。
2) 只考虑存在dtb的情况,此时会跳转到atag_to_fdt()中去把tag信息转换为fdt信息。

  ldr lr, [r6, #0]
#ifndef __ARMEB__ldr r1, =0xedfe0dd0  @ sig is 0xd00dfeed big endian
#elseldr r1, =0xd00dfeed
#endifcmp lr, r1bne dtb_check_done  @ not found
#ifdef CONFIG_ARM_ATAG_DTB_COMPAT/** OK... Let's do some funky business here.* If we do have a DTB appended to zImage, and we do have* an ATAG list around, we want the later to be translated* and folded into the former here.  To be on the safe side,* let's temporarily move  the stack away into the malloc* area.  No GOT fixup has occurred yet, but none of the* code we're about to call uses any global variable.*/add sp, sp, #0x10000stmfd sp!, {r0-r3, ip, lr}mov r0, r8mov r1, r6sub r2, sp, r6bl atags_to_fdt/** If returned value is 1, there is no ATAG at the location* pointed by r8.  Try the typical 0x100 offset from start* of RAM and hope for the best.*/cmp r0, #1sub r0, r4, #TEXT_OFFSETbic r0, r0, #1add r0, r0, #0x100mov r1, r6sub r2, sp, r6bleq atags_to_fdtldmfd sp!, {r0-r3, ip, lr}sub sp, sp, #0x10000
#endifmov r8, r6   @ use the appended device tree

第三步 观察atags_to_fdt()内核接口,该函数已经实现了将ATAG_CMDLINE转换为FDT的过程,若要实现其他转换,可参照实现:

/** Convert and fold provided ATAGs into the provided FDT.** REturn values:*    = 0 -> pretend success*    = 1 -> bad ATAG (may retry with another possible ATAG pointer)*    < 0 -> error from libfdt*/
int atags_to_fdt(void *atag_list, void *fdt, int total_space)
{for_each_tag(atag, atag_list) {if (atag->hdr.tag == ATAG_CMDLINE) {if (do_extend_cmdline)merge_fdt_bootargs(fdt,atag->u.cmdline.cmdline);elsesetprop_string(fdt, "/chosen", "bootargs",atag->u.cmdline.cmdline);} }
}

【总结】

系统切换到linux3.10之后,驱动中获取资源的方式、启动中获取初始信息的方式都有所改变,在系统移植或者驱动编写过程要尤为注意。
也许你会发现,没有配置dts文件,驱动也能正常运行,那很可能是dts中的默认配置已经满足了驱动的要求,而不是不需要配置dts。

【附录】

网上找到一些OF提供的常用API函数,驱动开发中可能会用到,一并奉上:

//OF提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等
1. 用来查找在dtb中的根节点
unsigned long __init of_get_flat_dt_root(void)

2. 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
例如:
struct device_node *cpus;
cpus=of_find_node_by_path("/cpus");

3. 若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
例如:
struct device_node *np;
np = of_find_node_by_name(NULL,"firewire");

4. 根据设备类型查找相应的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
例如:
struct device_node *tsi_pci;
tsi_pci= of_find_node_by_type(NULL,"pci");

5. 根据compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)

6. 根据节点属性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)

7. 根据phandle查找device_node
struct device_node *of_find_node_by_phandle(phandle handle)

8. 根据alias的name获得设备id号
int of_alias_get_id(struct device_node *np, const char *stem)

9. device node计数增加/减少
struct device_node *of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)

10. 根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)

11. 根据property结构的name参数,返回该属性的属性值
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)

12. 根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)

13. 获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)

14. 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)

15. 根据属性名propname,读出属性值中的第index个u32数值给out_value
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value)

16. 根据属性名propname,读出该属性的数组中sz个属性值给out_values
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz)

17. 根据属性名propname,读出该属性的u64属性值
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)

18. 根据属性名propname,读出该属性的字符串属性值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)

19. 根据属性名propname,读出该字符串属性值数组中的第index个字符串
int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)

20. 读取属性名propname中,字符串属性值的个数
int of_property_count_strings(struct device_node *np, const char *propname)

21. 读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

22. 读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

23. 获取该设备的irq个数
int of_irq_count(struct device_node *dev)

24. 获取设备寄存器地址,并填充寄存器资源结构体
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)

25. 获取经过映射的寄存器虚拟地址
void __iomem *of_iomap(struct device_node *np, int index)

24. 根据device_node查找返回该设备对应的platform_device结构
struct platform_device *of_find_device_by_node(struct device_node *np)

25. 根据device node,bus id以及父节点创建该设备的platform_device结构
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
                                                            void *platform_data,struct device *parent)

26. 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)

27. 遍历of_allnodes中的所有节点,生成并初始化platform_device结构
int of_platform_populate(struct device_node *root,const struct of_device_id *matches,
                        const struct of_dev_auxdata *lookup,struct device *parent)





linux系统之驱动与FDT相关推荐

  1. linux系统网络驱动简介

    网络设备驱动简介 网络设备驱动是linux内核中三大类设备驱动之一,它用来完成高层网络协议的底层数据传输及设备控制. 网络设备与其他两种设备的区别: 网络接口不存在于linux的文件系统中,及/dev ...

  2. linux系统LCD驱动(三):mtk lcd驱动lcm的加载以及初始化

    上一篇博文(linux系统LCD驱动(二):mtk lcd驱动fb_info初始化)https://blog.csdn.net/Ian22l/article/details/105929192 提到m ...

  3. linux系统无线驱动在哪下载,在linux上怎么安装无线网卡驱动?

    在linux上怎么安装无线网卡驱动? 在linux上安装无线网卡驱动的方法: (1)先确定无线网卡型号,因驱动安装和型号是密切相关的,不同的型号,安装和下载驱动有所不同,但原理是一样的.图例为无线网卡 ...

  4. fb驱动安装linux系统,drm 驱动是如何创建 fb device 的

    drm 驱动是如何创建 fb device 的 什么是 drm? drm 是一个 Linux 内核的显示系统驱动框架,区别于另外一个 DRM数字版权保护 drm 是一个管理 GPU 的显示框架 在内核 ...

  5. linux系统显卡驱动下载官网,NVIDIA显卡Linux系统驱动313.09版下载

    日前,从NVIDIA服务器中又再次泄漏了一款Linux系统驱动,该驱动版本号为313.09,要知道Linux系统下的R310系列驱动才刚发布到310.19版. 不过因为是泄漏版驱动,官网还未发布,因此 ...

  6. linux系统无线网卡驱动安装,在linux上怎么安装无线网卡驱动?

    在linux上怎么安装无线网卡驱动? 在linux上安装无线网卡驱动的方法: (1)先确定无线网卡型号,因驱动安装和型号是密切相关的,不同的型号,安装和下载驱动有所不同,但原理是一样的.图例为无线网卡 ...

  7. 迅为IMX6ULL教程更新至2060+页,裸机开发,Linux系统移植,驱动开发,构建文明系统,QT开发,系统编程

    教程更新至2060+页 彻底让零基础的同学真正学会 更完善的教程更全面的讲解更高效的学习 第一部分 总领及学习指引:主要探讨的学习方法,我们将尽量用比较简洁的方式,让大家明白嵌入式系统知识体系,以及它 ...

  8. Linux系统USB驱动目录,linux安装usb驱动命令

    有时我们会用到usb设备,这时我们就要学会如何在linux系统下安装usb驱动了.下面由学习啦小编为大家整理了linux安装usb驱动命令的相关知识,希望大家喜欢! linux安装usb驱动命令 安装 ...

  9. linux系统usb驱动怎么安装教程,MX Linux的闪存驱动器安装教程-电脑系统安装手册...

    MX Linux无疑是流行的中重Linux操作系统之一.它依赖于 Linux 和开源社区的出色上游工作.它的基础设计结合了优雅高效的桌面与简单的配置,高稳定性,坚实的性能和中等尺寸的占地面积.最重要的 ...

最新文章

  1. 微型计算机技术 第三章,微型计算机技术第三章部分答案概要.docx
  2. numpy库学习 向量 矩阵 均为有两个[[ ,而秩为1的数组只有一个[ np.array([[]]) 与np.array([])的区别
  3. Cocos2d-X中实现菜单特效
  4. 马虎的算式 - 蓝桥杯
  5. 封装一个流水号ID生成器:id-spring-boot-starter
  6. Nacos源码HostReactor
  7. eclipse3.4 SVN插件安装
  8. 用java创建一个单例模式,采用Java实现单例模式
  9. 今天学到的几个有用的awk命令用法
  10. 【Python】【Python库】Python3.7.2 - 字符串str类 (2)
  11. Q133:PBRT-V3,BSSRDF的采样(15.4章节)
  12. 微软专家推荐11个Chrome 插件
  13. SVG-edit 是一个快速的、基于 Web 的、由 JavaScript 驱动的 SVG 绘图编辑器
  14. centos7 禁止ip访问_centos7下使用iptables屏蔽所有中国IP
  15. shell中的until循环
  16. windowsPE系统的制作
  17. python实训报告怎么写_python实验报告
  18. 华硕重装后进入bios_华硕台式机重装系统win10按哪个键进入bios设置
  19. Docker网络管理
  20. Linux挂载Windows共享文件夹

热门文章

  1. 数据挖掘技术在商业银行CRM中的应用理论与模型研究
  2. 从投递到拿到offer,这份Android面试秘籍一文全解,2021年阿里+头条+腾讯大厂Android笔试真题
  3. android跨进程事件注入——直接往linux底层写事件
  4. 计算机视觉 - 基于黄金模板比较技术的缺陷检测
  5. mobiscroll实践:移动端仿苹果select效果的实现
  6. 【算法】袋鼠过河,动态规划问题(C++源码)
  7. 今日诗句第一期:路人攀折半成荒,何似山中自在芳 --严粲(折半查找)
  8. UPS电源在线远程监控及多方式告警方案
  9. 关于Keil出现Browse Information of one or more files is not avaliable
  10. 2020最新版Java学习路线图--妈妈再也不用担心我误删数据库被开除了