MTD分析

2023-05-16

概述:本文对mtd的整个结构进行了分析,分析得并非很深入,但可以了解大体框架和目录结构,另外本文会对源码文件进行分析,大致描述其作用,针对本文的内容中,如有不恰当的地方,请留言指教,多谢

3 驱动结构

3.1 源码结构

3.1.1 文件结构

drivers/mtd/:
在这里插入图片描述
chips目录:
nor flash的驱动程序位于该目录,目录下的文件包含nor flash的一些操作接口,其中nor flash分cfi和jedec两种,其他则是除前面两种以外的,cfi_cmdset_00XX.c是不同厂商的nor flash的操作接口,比如读写擦除等等,chipreg.c的左右就是调用cfi_probe.c这些文件中的probe函数
在这里插入图片描述
devices目录:
提供具体设备的驱动,例如m25p80,其他的暂不了解
在这里插入图片描述
maps目录:
这个目录下的文件,主要是对读写擦除这些接口实现,以及分区解析和创建,对比了该目录下几个文件的代码,不管函数经过了几次调用,最终基本步骤都是do_map_probe、mtd_device_parse_register,说是nor flash(大部分文件是针对nor flash)驱动代码的具体实现也可以,与之类似的还是nand目录下的文件,基本上也是先实现mtd_info结构体里的函数接口,然后调用mtd_device_parse_register,使用这里我理解为maps是nor flash不同芯片驱动具体实现,nand则是nand flash不同芯片驱动的具体实现
在这里插入图片描述
nand目录:
nand flash不同芯片驱动的实现,描述参见maps目录
在这里插入图片描述
spi-nor目录:
和maps、nand类似,针对spi nor flash
在这里插入图片描述

3.1.2 MTD框架

在这里插入图片描述
PS:图片来源于网络

flash硬件驱动: maps目录、nand目录、devices目录
mtd原始设备: mtdpart.c(分区相关接口实现)、mtdcore.c(提供分区解析以及mtd注册等一系列通用的接口)、mtdoops.c、
MTD字符设备: mtdchar.c(注册字符设备)
MTD块设备: mtdblock.c(缓存读写及注册块设备)、mtd_blkdevs.c

3.1.3 结构体

mtd_info: mtd原始设备,记录设备总大小、擦除大小、写大小以及读写擦除接口等,每一个分区里都有一个mtd_info结构体成员,用于记录该分区的大小以及读写擦除操作时调用的接口
mtd_part: 每一个mtd_part表示一个MTD分区,结构体包含了两个mtd_info类型的成员,一个mtd和一个*parent,一般来说,parent指向一整个flash,而mtd只是这片flash上的一个分区,而mtd_part里的offset表示mtd在parent里的偏移
mtd_partitions: 这个结构体用于flash驱动里解析分区时,用于存放分区信息及个数以及解析分区的函数接口
mtd_partition: 每一个mtd_partition表示一个分区,记录了分区名字和分区大小和偏移
mtd_part_parser: 记录分区解析的函数接口
mtdblk_dev: 结构体记录了打开的计数,每open一次,count值加一,结构体里的mdb成员记录了读写函数接口、分区信息、request_queue、workqueue_struct、work_struct、gendisk,后边这几个主要的成员没有深挖,应该是和读写有关的,猜测应该是写操作时有一个工作队列,我们知道块设备随机写肯定是需要对写入的顺序进行优化的,这几个成员应该就是干这个工作,其中gendisk成员根据名字来看就是表示的一个磁盘设备,从代码来看显然也是一个分区
mtd_blktrans_ops: 提供访问的接口,在mtdblock.c中实现了具体的函数接口

3.1.4 mtdblock.c

这是个例外,后来感觉每个文件贴出来太多了,就只保留了这一节,这一块分析得很少,写了这么些了就懒得删了
mtdblock.c提供块设备的缓存读写接口以及注册字符设备,在文件开始处,定义了以下结构体:

struct mtdblk_dev {
	struct mtd_blktrans_dev mbd;
	int count;				//计数值,每次open一次加一,主要用于在第一次打开时初始化一些信息,见mtdblock_open函数
	struct mutex cache_mutex;		//互斥锁
	unsigned char *cache_data;		//缓冲区数据指针
	unsigned long cache_offset;		//数据偏移
	unsigned int cache_size;		//缓冲区大小
	enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; //缓冲区的状态,空状态或其他
};

结构体中的cache_data指针用于读写数据时存放数据,分析到具体函数时再去探究怎么使用该指针

struct mtd_blktrans_dev mbd,后续分析作用
mtd_blktrans_dev结构体如下:
	struct mtd_blktrans_dev {
	struct mtd_blktrans_ops *tr;//提供接口、函数指针以及一个devs链表和一个list链表
	struct list_head list;//list链表
	struct mtd_info *mtd;//提供块设备的各种信息,比如oobsize、writesize、erasesize以及块设备的操作函数(读写擦除、lock、unlock等)
	struct mutex lock;//互斥锁
	int devnum;
	bool bg_stop;
	unsigned long size;
	int readonly;
	int open;
	struct kref ref;
	struct gendisk *disk;
	struct attribute_group *disk_attributes;
	struct workqueue_struct *wq;
	struct work_struct work;
	struct request_queue *rq;
	spinlock_t queue_lock;
	void *priv;
	fmode_t file_mode;
};

mtdblock主要接口如下:
在这里插入图片描述
结构体mtd_blktrans_ops定义如下:
在这里插入图片描述
其中mtdblock_readsect和mtdblock_writesect是读写接口,mtdblock_add_mtd,mtdblock_remove_dev

3.1.4.1 register_mtd_blktrans函数

该函数原型在mtd_blkdevs.c中,实现如下:
该文件中定义struct mtd_notifier变量,结构体如下:

struct mtd_notifier {
	void (*add)(struct mtd_info *mtd);
	void (*remove)(struct mtd_info *mtd);
	struct list_head list;
};

一个添加函数,一个移除函数,一个链表,在mtd_blkdevs.c中定义了一个链表static LIST_HEAD(blktrans_majors); ,看一下add和remove函数

static void blktrans_notify_remove(struct mtd_info *mtd)
{
	struct mtd_blktrans_ops *tr;
	struct mtd_blktrans_dev *dev, *next;
	//循环blktrans_majors链表,在每一个链表里遍历它的devs链表,找到与mtd相等的项,移除
	list_for_each_entry(tr, &blktrans_majors, list)
		list_for_each_entry_safe(dev, next, &tr->devs, list)
			if (dev->mtd == mtd)
tr->remove_dev(dev);//经过调用mtdblock.c中的mtdblock_tr 的mtdblock_remove_dev 调用mtd_blkdevs.c中实现的del_mtd_blktrans_dev函数
}

static void blktrans_notify_add(struct mtd_info *mtd)
{
	struct mtd_blktrans_ops *tr;
	//非有效的类型,直接返回
	if (mtd->type == MTD_ABSENT)
		return;
//遍历blktrans_majors链表中的每一项,调用它的add_mtd函数,遍历tr里的devs链表,add_mtd将mtd加入到tr的devs链表里
	list_for_each_entry(tr, &blktrans_majors, list)
		tr->add_mtd(tr, mtd);
}
static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,
	.remove = blktrans_notify_remove,
};
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
	struct mtd_info *mtd;
	int ret;

	/* Register the notifier if/when the first device type is
	   registered, to prevent the link/init ordering from fucking
	   us over. */
	//第一次注册
	if (!blktrans_notifier.list.next)
		register_mtd_user(&blktrans_notifier);


	mutex_lock(&mtd_table_mutex);
	//注册块设备,返回0成功,根据传入的major,如果为0,则会找到第一个空闲的位置,然后添加自己的信息,实际上就是在genhd.c中定义了一个结构体(blk_major_name)指针数组,里边包括major,name,和next指针
	ret = register_blkdev(tr->major, tr->name);
	if (ret < 0) {
		printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
		       tr->name, tr->major, ret);
		mutex_unlock(&mtd_table_mutex);
		return ret;
	}

	if (ret)
		tr->major = ret;

	tr->blkshift = ffs(tr->blksize) - 1;
	//初始化tr的devs链表,将list链表添加到blktrans_majors
	INIT_LIST_HEAD(&tr->devs);
	list_add(&tr->list, &blktrans_majors);
	//循环mtd_idr里不为空的每一项,调用add_mtd,mtd_idr在mtdcore.c中定义,
	mtd_for_each_device(mtd)
		if (mtd->type != MTD_ABSENT)
			tr->add_mtd(tr, mtd);

	mutex_unlock(&mtd_table_mutex);
	return 0;
}

3.2 NOR FLASH代码分析

(分析代码过程中总结记录的,排版不是很好,下面的分析比较杂乱,主要是函数的调用流程,缩进相同的表示平级)
比如:

	a_func
		b_func
			c_func
		d_func

上边的例子表示在a_func函数里调用了两个函数,一个b_func和一个d_func函数,其中b_func 调用了c_func函数

这里内核版本为4.14,下面的分析针对内核版本4.14
CONFIG_MTD_PHYSMAP_OF必须要,CONFIG_MTD_PHYSMAP无关紧要

mtdblock.c
注册块设备,主设备号为31
提供缓存读写接口
将分区信息填充到一个新的mtd_blktrans_dev结构体中,将该结构体的list链表加入到tr的devs链表中
每一个mtd_blktrans_dev结构体里包含一个mtd_info结构体成员,用于记录分区信息

mtdchar.c
注册主设备号为90的mtd字符设备
init_mtdchar #define MTD_CHAR_MAJOR 90 #define MINORBITS 20 ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,"mtd", &mtd_fops);
提供应用层读写接口

mtdcore.c

static LIST_HEAD(mtd_notifiers);

在文件系统下生成对应的名字,如下边的erasesize,可在设备目录下看到该名字,可以通过cat查看值或echo更改值
static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL);

DEVICE_ATTR的参数:
DEVICE_ATTR(_name, _mode, _show, _store)
_name:在sys fs中的名字
_mode:访问权限
_show:显示函数
_store:写函数

mtdcore提供了很多DEVICE_ATTR,可以在文件系统中查看erasesize,writesize等属性


在主设备号90下创建字符设备mtdX和mtdXro
	add_mtd_device
		
		#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
		dev_set_name(&mtd->dev, "mtd%d", i);
		error = device_register(&mtd->dev);
		device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
		      "mtd%dro", i);
		使用idr_alloc函数通过传入的mtd参数在mtd_idr中分配一个ID
		根据该ID的值,创建/dev/mtdX和/dev/mtdXro两个节点
		轮询mtd_notifiers链表中的每一项,调用它的add接口,add接口在mtd_blkdevs.c中实现,在mtdblock.c中的init_mtdblock函数中初始化
		该函数的作用就是将每个分区添加到系统的MTD设备链表里,调用mtd_notifiers中每一个MTD 'user'(这里的每一个user就是一个mtd_notifier)中的add接口
		mtd_notifiers中的MTD 'user'在mtd_blkdevs.c中的register_mtd_blktrans函数中通过register_mtd_user函数添加
		该函数还添加mtd_blktrans_ops到blktrans_majors链表中,然后对mtd_idr中的每一个mtd_info(也就是每一个分区)调用mtdblock.c中mtdblock_tr的add_mtd函数,也就是mtdblock_add_mtd
		mtdblock_add_mtd函数会分配一个mtdblk_dev结构体,填充mbd成员(mtd_blktrans_dev类型)的mtd及tr成员,然后调用add_mtd_blktrans_dev函数
		add_mtd_blktrans_dev函数将mdb加入到tr的devs链表中,并调用device_add_disk添加disk设备
		
	mtd_device_parse_register
		该函数负责解析分区信息和分区个数,最终调用mtd_add_device_partitions函数添加分区
	mtd_add_device_partitions
		函数中调用add_mtd_partitions函数添加分区,函数实现在mtdpart.c中
	mtd_device_unregister
	
	mtdcore.c中init_mtd调用mtdchar.c中的init_mtdchar注册字符设备
	
	module_init(init_mtd);
		init_mtd
			ret = class_register(&mtd_class);
			//这部分还没完全了解,网上的解释大概是后端存储设备I/O较慢,用于缓存延迟写入,这个应该比较重要
			//个人理解应该是对写操作进行优化,对于不同顺序的扇区写操作进行优化,
			mtd_bdi = mtd_bdi_init("mtd");
			//proc
			proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops);
			//调用mtdchar.c中的函数
			ret = init_mtdchar();
			//debugfs
			dfs_dir_mtd = debugfs_create_dir("mtd", NULL);

mtd_blkdevs.c
注册块设备
static LIST_HEAD(blktrans_majors);
提供mtdblock.c使用的register_mtd_blktrans、blktrans_notifier.add等接口
同时维护一个blktrans_majors链表,将mtdblock.c中的mtdblock_tr添加到链表中,然后对每一个分区调用mtdblock.c中tr(mtd_blktrans_ops)的add_mtd接口

cmdlinepart.c
和ofpart.c差不多,只是各自解析的分区信息来源不同

ofpart.c
提供解析设备数分区的接口,解析分区个数及偏移和大小
struct mtd_partition结构体记录分区的大小和偏移

	struct mtd_partition {
		const char *name;		分区名字
		const char *const *types;	cmdlinepart or ofpart
		uint64_t size;			分区大小
		uint64_t offset;		分区偏移
		uint32_t mask_flags;		MTD_WRITEABLE or MTD_BIT_WRITEABLE or MTD_NO_ERASE or other
		struct device_node *of_node; 设备树节点
		
	};

上边两个文件分别是针对命令行分区的设备树分区的解析,程序运行时会轮询两种方式,当然,也可以自己添加新的方式,如下:

/*
 * Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you
 * are changing this array!
 */
static const char * const default_mtd_part_types[] = {
	"cmdlinepart",
	"ofpart",
	NULL
};

在mtdpart.c中,该函数中可以看到for循环轮询了default_mtd_part_types数组

int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
			 struct mtd_partitions *pparts,
			 struct mtd_part_parser_data *data)
{
	struct mtd_part_parser *parser;
	int ret, err = 0;

	if (!types)
		types = default_mtd_part_types;

	for ( ; *types; types++) {
		pr_debug("%s: parsing partitions %s\n", master->name, *types);
		parser = mtd_part_parser_get(*types);
		if (!parser && !request_module("%s", *types))
			parser = mtd_part_parser_get(*types);
		pr_debug("%s: got parser %s\n", master->name,
			 parser ? parser->name : NULL);
		if (!parser)
			continue;
		ret = mtd_part_do_parse(parser, master, pparts, data);
		/* Found partitions! */
		if (ret > 0)
			return 0;
		mtd_part_parser_put(parser);
		/*
		 * Stash the first error we see; only report it if no parser
		 * succeeds
		 */
		if (ret < 0 && !err)
			err = ret;
	}
	return err;
}

cmdlinepart.c和ofpart.c都将在flash驱动中被调用,一般是在probe函数中,
例如jz4740_nand.c(随便找的一个nand flash的驱动)和physmap_core_of.c
的probe函数中都会调用mtd_device_parse_register函数,而该函数则通过层层调用,
最终轮询cmdlinepart.c和ofpart.c中定义的static struct mtd_part_parser结构体中的parse_fn函数,
cmdlinepart.c最终会调用parse_cmdline_partitions函数,而ofpart.c则调用parse_ofpart_partitions

mtdpart.c
定义了一个mtd_partitions链表,用于管理分区,每一个新的分区都会被添加到该链表中
static LIST_HEAD(mtd_partitions);
为分区提供读写擦除函数

	struct mtd_partitions {
		const struct mtd_partition *parts;		//分区信息
		int nr_parts;							//分区个数
		const struct mtd_part_parser *parser;	//解析分区的函数接口	
	};
	
	struct mtd_part_parser {
		struct list_head list;
		struct module *owner;
		const char *name;
		int (*parse_fn)(struct mtd_info *, const struct mtd_partition **,		//解析函数
				struct mtd_part_parser_data *);
		void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
	};
	
	add_mtd_partitions
		根据分区个数,分配对应个mtd_part,并添加到mtd_partitions链表中
		调用add_mtd_device函数,函数在mtdcore.c中实现

cfi_cmdset_0002.c

	实现mtd_info结构体的接口等
	struct mtd_info *mtd;
	..........
	mtd->_suspend = cfi_staa_suspend;
	mtd->_resume = cfi_staa_resume;
	..........

gen_probe.c

	mtd_do_chip_probe
		mtd = check_cmd_set(map, 1);
			check_cmd_set
				cfi_cmdset_0002(map, primary);

cfi_probe.c

	cfi_probe
		mtd_do_chip_probe

physmap_core_of.c

	do_map_probe
		drv->probe(map);
			cfi_probe
				return mtd_do_chip_probe(map, &cfi_chip_probe);
					cfi = genprobe_ident_chips(map, cp);
						for (i = 1; i < max_chips; i++) {
							cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi);
								//读flash的ID等
								cfi_probe_chip
					mtd = check_cmd_set(map, 1); /* First the primary cmdset */
						//设置读写函数等,cfi_cmdset_0001:Intel/Sharp flash chips,cfi_cmdset_0002:AMD/Fujitsu flash chips
						cfi_cmdset_0020:ST flash chips
						cfi_cmdset_0002/cfi_cmdset_0001/cfi_cmdset_0020/cfi_cmdset_unknown
							mtd->_erase   = cfi_amdstd_erase_varsize;
							mtd->_write   = cfi_amdstd_write_words;
							mtd->_read    = cfi_amdstd_read;
						
	/*解析分区并注册,该函数在mtdcore.c中*/
	mtd_device_parse_register	
		//解析分区信息
		parse_mtd_partitions
			mtd_part_do_parse(struct mtd_part_parser *parser,
				(*parser->parse_fn)
					//在ofpart.c中实现,解析设备树种分区的个数以及偏移和大小等
					parse_ofpart_partitions
		
		mtd_add_device_partitions
			//如果没有分区,则在/dev下创建mtdX、mtdXro节点以及添加分区
			if (nbparts == 0 || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
				ret = add_mtd_device(mtd);
					//分配一个没有使用的id
					i = idr_alloc
					dev_set_name(&mtd->dev, "mtd%d", i);
					//创建设备节点mtdX
					error = device_register(&mtd->dev);
					
					if (!IS_ERR_OR_NULL(dfs_dir_mtd)) {
						mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(&mtd->dev), dfs_dir_mtd);
						if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) {
							pr_debug("mtd device %s won't show data in debugfs\n",
								 dev_name(&mtd->dev));
						}
					}
					//创建mtdXro节点
					device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,"mtd%dro", i);
					list_for_each_entry(not, &mtd_notifiers, list)
						//调用mtd_blkdevs.c中提供的blktrans_notify_add函数
						not->add(mtd);
							struct mtd_blktrans_ops *tr;
							//调用mtdblock.c中的add_mtd
							tr->add_mtd(tr, mtd);
								//调用mtd_blkdevs.c中的add_mtd_blktrans_dev
								add_mtd_blktrans_dev(&dev->mbd)
									struct gendisk *gd;
									gd = alloc_disk(1 << tr->part_bits);
									//设置名字,这里的tr是mtdblock.c中的mtdblock_tr,name为"mtdblock"
									snprintf(gd->disk_name, sizeof(gd->disk_name),
										"%s%d", tr->name, new->devnum);
									//添加分区,可在文件系统下看到mtdblockX
									device_add_disk
			if (nbparts > 0) {
				ret = add_mtd_partitions(mtd, real_parts, nbparts);
					for (i = 0; i < nbparts; i++) {
						//为分区设置读写擦除函数,设置erasesize,writesize,size等,
						最终使用的是cfi_cmdset_0002中cfi_cmdset_0002函数设置的读写函数
						slave = allocate_partition(master, parts + i, i, cur_offset);
						if (IS_ERR(slave)) {
							del_mtd_partitions(master);
							return PTR_ERR(slave);
						}

						mutex_lock(&mtd_partitions_mutex);
						//添加到mtd_partitions链表
						list_add(&slave->list, &mtd_partitions);
						mutex_unlock(&mtd_partitions_mutex);

						//创建m注册tdx设备
						add_mtd_device(&slave->mtd);
						//在文件系统下创建节点
						mtd_add_partition_attrs(slave);
						if (parts[i].types)
							mtd_parse_part(slave, parts[i].types);

						cur_offset = slave->offset + slave->mtd.size;
					}
					
		if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
			mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
			register_reboot_notifier(&mtd->reboot_notifier);
		}

有关时序相关分析可以参考链接: AXI EMC使用总结.
这个文章介绍了flash相关的参数以及时序的设置

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MTD分析 的相关文章

  • c++ 常用转换vector<uint8_t> 转string

    std span class token double colon punctuation span vector span class token operator lt span span class token keyword uin
  • c语言解析协议常用套路

    简介 协议定义username len value while 读取 2个switch case解析 读取value 代码示例 span class token keyword struct span span class token cl
  • c++模板 --- 模板的嵌套

    简介 明白类型是什么即可 xff0c 可以适当运用using语法起别名 简化代码 一 函数模板嵌套 span class token keyword template span span class token operator lt sp
  • DWA算法详解

    首先在V m V d的范围内采样速度 xff1a allowable v 61 generateWindow robotV robotModel
  • C++OpenCV实现YOLO算法对目标进行检测实战

    基本概念 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 加载darknet框架的YoloV4模型做目标检测 OpenCV DNN模块支持常见深度学习框架如TensorFlowCaffe Darknet等 对
  • 使用GooSeeker软件进行爬虫

    一 使用GooSeeker软件进行爬虫 xff0c 它是进行读取大量数据的 xff0c 都出来的数据是xml格式的 xff0c 可以通过office excel将xml格式转化成表格形式 1 爬虫取数据 xff1a 1 xff09 先要在网
  • 51单片机--堆栈

    堆栈的作用是用在调用子程序或中断程序时保护现场 xff0c 因为51单片机的寄存器是十分有限的 xff0c 而相对来说普通数据存储器更充足 xff0c 通过入栈将寄存器中的内容临时保存到堆栈中 xff08 普通数据存储器 xff09 xff
  • Git-命令行-使用 Tag 标记你的代码

    前言 正文开始之前 xff0c 我想我们需要弄明白几个问题 xff1a tag 是什么 xff1f 使用tag 的好处 xff1f tag 和 branch 的区别以及使用场景 xff1f tag 是什么 xff1f tag 翻译过来是标签
  • 光流Zoom In and Out开发记录

    简介 2018年7月 xff0c 合作伙伴提出了一个想法 使用光流算法估计相机前后移动 经典的光流算法 xff08 Lukas Kanade算法 xff09 适用于相机左右和上下移动 xff0c 但是在相机前后移动时 xff0c 虽然各个像
  • 关于电子设计大赛无人机题的学习笔记(2)飞控代码分析和高精度IMU传感器的使用

    飞控代码分析和高精度IMU传感器的使用 最近着急备赛很多文档只是处于零零散散状态没办法细节的整理出来专门放一份 xff0c 等比赛结束会统一整理 xff0c 现在只能先放一些研发过程中的技术文档上来了 目前我们的调试进度是 xff0c 稳定
  • 关于电子设计大赛无人机题的学习笔记(3)常用设备总线的使用方法(IIC)

    关于电子设计大赛无人机题的学习笔记 3 常用设备设备总线的使用方法 IIC xff09 1 吐槽及日志 首先 电赛因为疫情原因推迟了 xff0c emmm在我看来跟取消了差不了太多 xff0c 这一波小系列的学习记录暂时只能在第三篇就停止更
  • undefined reference to `pthread_create‘问题的解决 cmake新方法解决

    最近在写相机SDK xff0c 测试中出现了一个小问题undefined reference to 96 pthread create 39 其实是缺少库文件 网上大部分介绍使用 lpthread xff0c 但是这个是老版本了 xff0c
  • POST请求返回:401 Unauthorized

    Postman 做接口测试 xff0c 提交请求后 xff0c 模拟获取数据 xff0c 输入入参正确时 xff0c 却返回status 401 Unauthorized 原因 xff1a 是我的token有误 xff0c token是一串
  • Django从header请求头中的Authorization获取token验证数据

    前言 之前使用django开发api接口时 xff0c 约定是要每次请求都要带token这个参数 xff0c 这样很不方便 xff0c 最近学了vue xff0c 也使用了axios xff0c 发现在axios拦截器中可以设置每次请求头中

随机推荐

  • char类型的数字转int

    char的本质 xff1a char The char data type is a single 16 bit Unicode character It has a minimum value of 39 u0000 39 or 0 an
  • Android串口通信

    编前记 今天在刷博客 郭老师 的时候评论区看见有人在聊单片机的串口通信 xff0c 刚好之前做过一个项目通过NFC读取IC门禁卡片的项目所以拿出来分享 复习 一下 xff0c 先讲了解一下什么是串口通信 xff1a 串口通信 xff08 S
  • Studio One 5机架设置一键切换效果通道

    Studio One是当前主流的直播机架软件 xff0c 操作非常方便 xff0c 但是呢默认情况下 xff0c 要切换效果时 xff0c 只能手动关闭一个效果的后 xff0c 再开另一个效果 xff0c 切换效果有点不方便 现在孤狼分享S
  • VScode设置C/C++编译环境详解

    一 xff1a 下载安装C C 43 43 编译器 在windows下有很多集成的编译器 xff0c 我们只是需要使用gcc exe 编译而已 xff0c 所以我们可以随便下 xff0c 这里推荐使用 xff1a MinGW xff1a x
  • 2021校招_大华

    大华面试 xff1a 一面和二面 一面 xff1a 首先自我介绍 1 序列化的使用方式以及情景 2 Springboot的启动过程 3 Mysq中lB 43 树和B树索引区别 xff0c 聚簇索引和非聚簇索引区别 4 Spring中bean
  • 2021校招_海康威视

    2021届海康威视面试 一面 xff1a 1 https与http协议的区别 2 Spring的启动过程 3 Springboot相比较Spring的优点 4 Linux修改文件权限命令 5 项目中所用到的技术 6 Restful风格 7
  • 2021校招_满帮(运满满)

    一面 xff08 电话面 xff09 xff1a 25min 1 询问HashMap相关结构以及原理 2 红黑树的基本结构 xff0c 以及什么时候会LL xff08 左转 xff09 3 Spring如何解决循环依赖的 4 Redis缓存
  • 2021校招_思科

    思科给我发的太晚了 xff0c 十一月份才给我消息 思科一面凉凉 主要是针对你的简历 问到我的主要内容包括 xff1a 数据库设计 xff0c 是否使用到设计模式 xff0c 以及遇到问题如何解决 包括ngnix xff0c redis h
  • 音视频开发之音频基础知识

    音视频开发之音频基础知识 转自https blog jianchihu net av develop audio basis html 什么是声音 介质振动在听觉系统中产生的反应 是一种波 因为是一种波 xff0c 所以我们可以用频率 振幅
  • 机器学习中神经网络,支持向量机以及贝叶斯分类器总结

    第五章神经网络 5 1神经元模型 神经网络中最基本的成分是神经元模型 xff0c 即 简单单元 在 M P神经元模型 中 xff0c 神经元接受收到来自n个其他神经元传递过来的输入信号 xff0c 这些输入信号经过带权重的连接进行传递 xf
  • 机器学习中的降维与度量学习(reduce dimension and metric learning)

    降维与度量学习 k近邻学习 k近邻 k Nearest Neighbor 简称kNN 学习是一种监督学习方法 其工作机制为 xff1a 在样本中 xff0c 根据距离度量找出训练集中临近的k个样本 xff0c 基于这k个样本进行预测 一般
  • Warning: Invalid argument “/map“ passed to canTransform argument target_frame in tf2 frame_ids···

    Warning Invalid argument map passed to canTransform argument target frame in tf2 frame ids cannot start with a like at l
  • CAN为什么会发送失败

    CAN总线调试过程中出现报文发送失败 xff0c 很多工程师都对此只知其一不知其二 xff0c 这里就CAN报文发送失败的问题我们来做一次探讨 在了解CAN报文为什么会发送失败之前我们先看看一条正确的CAN报文到底应该是怎么样的 xff0c
  • git分支和tag

    分支管理 查看当前分支 git branch创建分支 git branch git branch index one切换分支 git checkout lt 分支名称 xff0c 主分支是master gt git checkout ind
  • TT无人机扩展模块库分析(default.ino)补篇2

    这个简单 请对照 因为源码在这里出现了和手柄相关的源码 设置tof传感器的超时时间 xff08 500 xff09 什么单位 xff1f 没有搜索到 xff0c 我用SI了 搜索到了 有很多函数 定位位置 在这里 找到了 xff0c 为什么
  • TCP建立连接的过程

    TCP是面向连接的 可靠的 基于字节流的传输层协议 xff0c 是TCP IP协议中最重要的协议之一了 我们都知道TCP通过三次握手建立连接 xff0c 那么每一次握手的作用 为什么要三次握手 如果某次握手丢包会发生什么呢 xff1f 文章
  • CANanlystII 基于linux的二次开发实践

    1 USBCAN分析仪国内现状 这是目前国内市场上的USBCAN分析仪现状 2 创芯科技产品 创芯科技的这个红色盒子是我比较下来综合性价比最高的 同时支持windows和linux的设备只要320元左右 你既可以用可视化界面发送 接收报文
  • AXI DMA总结、内核axidmatest.c测试程序分析、SG mode

    AXI DMA 概述 xff1a XILINX提供的AXI DMA支持Scatter Gather mode和Direct Register mode 数据位宽支持32 64 128 256 512 1024bits xff0c strea
  • ZYNQ 平台 AD9361实现网络通信的一种方案+网卡驱动分析及实现

    声明 xff1a 文中若有不合理的地方 xff0c 欢迎讨论学习及指正 xff0c 本文仅仅涉及软件部分的代码 xff0c 不阐述逻辑代码的实现 功能 xff1a 通过AD9361芯片实现无线组网 xff0c 能实现视频 文件 音频等传输
  • MTD分析

    概述 xff1a 本文对mtd的整个结构进行了分析 xff0c 分析得并非很深入 xff0c 但可以了解大体框架和目录结构 xff0c 另外本文会对源码文件进行分析 xff0c 大致描述其作用 xff0c 针对本文的内容中 xff0c 如有