Linux block_device gendisk和hd_struct到底是个啥关系

news2024/12/25 22:39:57

本文的源码版本是Linux 5.15版本,有图有真相:

1.先从块设备驱动说起

安卓平台有一个非常典型和重要的块设备驱动:zram,我们来看一下zram这个块设备驱动加载初始化和swapon的逻辑,完整梳理完这个逻辑将对Linux块设备驱动模型有深入的理解。

zram驱动加载的时候会调用zram_add函数,源码如下:

1887/*
1888 * Allocate and initialize new zram device. the function returns
1889 * '>= 0' device_id upon success, and negative value otherwise.
1890 */
1891static int zram_add(void)
1892{
1893	struct zram *zram;
1894	int ret, device_id;
1895
1896	zram = kzalloc(sizeof(struct zram), GFP_KERNEL);
1909    ...
1910	/* gendisk structure */
1911	zram->disk = blk_alloc_disk(NUMA_NO_NODE);
1
1918
1919	zram->disk->major = zram_major;
1920	zram->disk->first_minor = device_id;
1921	zram->disk->minors = 1;
1922	zram->disk->fops = &zram_devops;
1923	zram->disk->private_data = zram;
1924	snprintf(zram->disk->disk_name, 16, "zram%d", device_id);
1925     ...
1957	device_add_disk(NULL, zram->disk, zram_disk_attr_groups);
1961	zram_debugfs_register(zram);
1962	pr_info("Added device: %s\n", zram->disk->disk_name);
1963	return device_id;
        ...
1970}

zram_add中有两个非常重要的函数:

  • blk_alloc_disk
  • device_add_disk

上面两个函数描述了块设备驱动的两个步骤:1)创建gendisk对象,代表的是一个“磁盘” 2)注册和激活磁盘,激活磁盘之后就可以正式使用了。

2. gendisk和hd_struct是啥

blk_alloc_disk函数创建了一个gendisk对象,这就出现了本文要讲述的非常重要的对象。怎么理解gendisk呢?Linux用gendisk代表一个“磁盘”,这里的磁盘可以是一个真实的硬盘,也可以是一个虚拟设备。

我们接触windows系统比较多,硬盘都会划分分区,在Linux是不是也有同样的概念呢?确实如此,这就要去struct gendisk数据结构来一探究竟了:

121struct gendisk {
122	/* major, first_minor and minors are input parameters only,
123	 * don't use directly.  Use disk_devt() and disk_max_parts().
124	 */
125	int major;			/* major number of driver */
126	int first_minor;
127	int minors;                     /* maximum number of minors, =1 for
128                                         * disks that can't be partitioned. */
129
130	char disk_name[DISK_NAME_LEN];	/* name of major driver */
131
132	unsigned short events;		/* supported events */
133	unsigned short event_flags;	/* flags related to event processing */
134
135	struct xarray part_tbl;
136	struct block_device *part0;
137
138	const struct block_device_operations *fops;
139	struct request_queue *queue;
    ...
    }

gendisk中的part_tbl就代表该磁盘的分区表,那么每个分区用什么结构体表示:struct hd_struct

注意gendisk结构体中还有一个重要的fops函数,代表了操作该块设备的操作函数列表,具体本文后面详细讲述。

3. gendisk是怎么创建的

前面知道gendisk是通过blk_alloc_disk函数创建的:

275#define blk_alloc_disk(node_id)						\
276({									\
277	static struct lock_class_key __key;				\
278									\
279	__blk_alloc_disk(node_id, &__key);				\
280})

1333struct gendisk *__blk_alloc_disk(int node, struct lock_class_key *lkclass)
1334{
1335	struct request_queue *q;
1336	struct gendisk *disk;
1337
1338	q = blk_alloc_queue(node);
1339	if (!q)
1340		return NULL;
1341
1342	disk = __alloc_disk_node(q, node, lkclass);
1343	if (!disk) {
1344		blk_cleanup_queue(q);
1345		return NULL;
1346	}
1347	return disk;
1348}

1279
1280struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id,
1281		struct lock_class_key *lkclass)
1282{
1283	struct gendisk *disk;
1284
1285	if (!blk_get_queue(q))
1286		return NULL;
1287
1288	disk = kzalloc_node(sizeof(struct gendisk), GFP_KERNEL, node_id);
1289	if (!disk)
1290		goto out_put_queue;
1291
1292	disk->bdi = bdi_alloc(node_id);
1293	if (!disk->bdi)
1294		goto out_free_disk;
1295
1296	disk->part0 = bdev_alloc(disk, 0);
1297	if (!disk->part0)
1298		goto out_free_bdi;
1299
1300	disk->node_id = node_id;
1301	mutex_init(&disk->open_mutex);
1302	xa_init(&disk->part_tbl);
1303	if (xa_insert(&disk->part_tbl, 0, disk->part0, GFP_KERNEL))
1304		goto out_destroy_part_tbl;
1305

总结一下调用关系:blk_alloc_disk->__alloc_disk_node->__alloc_disk_node,最终__alloc_disk_node创建了gendisk对象。

创建了gendisk之后,要给gendisk做一些初始化赋值,其中很重要的part0是block_device,通过调用bdev_alloc(disk,0)创建,这里出现了本文最后一个要介绍的对象:struct block_device,

4 block_device是啥

block_device具体可以对应一个磁盘,也可以对应磁盘里面的一个分区,也就说磁盘和磁盘都可用block_device表示,block_device可以想象成磁盘的描述信息,比如设备号,分区号,是否只读等等,具体定义如下:

4struct block_device {
25	sector_t		bd_start_sect;
26	struct disk_stats __percpu *bd_stats;
27	unsigned long		bd_stamp;
28	bool			bd_read_only;	/* read-only policy */
29	dev_t			bd_dev;
30	int			bd_openers;
31	struct inode *		bd_inode;	/* will die */
32	struct super_block *	bd_super;
33	void *			bd_claiming;
34	struct device		bd_device;
35	void *			bd_holder;
36	int			bd_holders;
37	bool			bd_write_holder;
38	struct kobject		*bd_holder_dir;
39	u8			bd_partno;
40	spinlock_t		bd_size_lock; /* for bd_inode->i_size updates */
41	struct gendisk *	bd_disk;
42
43	/* The counter of freeze processes */
44	int			bd_fsfreeze_count;
45	/* Mutex for freeze */
46	struct mutex		bd_fsfreeze_mutex;
47	struct super_block	*bd_fsfreeze_sb;
48
49	struct partition_meta_info *bd_meta_info;
50#ifdef CONFIG_FAIL_MAKE_REQUEST
51	bool			bd_make_it_fail;
52#endif
53
54	ANDROID_KABI_RESERVE(1);
55	ANDROID_KABI_RESERVE(2);
56	ANDROID_KABI_RESERVE(3);
57	ANDROID_KABI_RESERVE(4);
58} __randomize_layout;

5.block_device怎么创建的

bdev_alloc创建了disk->part0这个block_device对象,我们来看下非常重要的bdev_alloc函数:

478struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
479{
480	struct block_device *bdev;
481	struct inode *inode;
482
483	inode = new_inode(blockdev_superblock);
484	if (!inode)
485		return NULL;

    //块设备文件对应inode设置为块设备
486	inode->i_mode = S_IFBLK;
487	inode->i_rdev = 0;
488	inode->i_data.a_ops = &def_blk_aops;
489	mapping_set_gfp_mask(&inode->i_data, GFP_USER);
490
    //new_inode创建的本质上是bdev_inode,I_BDEV获取bdev_inode结构体的字段bdev
491	bdev = I_BDEV(inode);
492	mutex_init(&bdev->bd_fsfreeze_mutex);
493	spin_lock_init(&bdev->bd_size_lock);
    //初始化block_device,设置分区号,inode,gendisk对象
494	bdev->bd_partno = partno;
495	bdev->bd_inode = inode;
496	bdev->bd_stats = alloc_percpu(struct disk_stats);
497	if (!bdev->bd_stats) {
498		iput(inode);
499		return NULL;
500	}
501	bdev->bd_disk = disk;
502	return bdev;
503}

上面new_inode函数调用可以参考:zram压缩机制看swapon系统调用_swapon设置为zram-CSDN博客

总结来讲new_inode返回的本质上是一个bdev_inode对象,其定义如下:

32struct bdev_inode {
33	struct block_device bdev;
34	struct inode vfs_inode;
35};
36

也就是说new_inode创建bdev_inode的同时,本质上也创建了一个block_device对象。这里bdev_inode就代表块设备文件的inode,比如zram驱动来讲,对应的就是/dev/block/zram0块设备文件的inode对象。

6. 激活磁盘

激活磁盘使用的是device_add_disk函数:

/**
 * device_add_disk - add disk information to kernel list
 * @parent: parent device for the disk
 * @disk: per-device partitioning information
 * @groups: Additional per-device sysfs groups
 *
 * This function registers the partitioning information in @disk
 * with the kernel.
 */
int device_add_disk(struct device *parent, struct gendisk *disk,
		     const struct attribute_group **groups)

{
	struct device *ddev = disk_to_dev(disk);
	int ret;

	/*
	 * The disk queue should now be all set with enough information about
	 * the device for the elevator code to pick an adequate default
	 * elevator if one is needed, that is, for devices requesting queue
	 * registration.
	 */
	elevator_init_mq(disk->queue);

	/*
	 * If the driver provides an explicit major number it also must provide
	 * the number of minors numbers supported, and those will be used to
	 * setup the gendisk.
	 * Otherwise just allocate the device numbers for both the whole device
	 * and all partitions from the extended dev_t space.
	 */
	if (disk->major) {
		if (WARN_ON(!disk->minors))
			return -EINVAL;

		if (disk->minors > DISK_MAX_PARTS) {
			pr_err("block: can't allocate more than %d partitions\n",
				DISK_MAX_PARTS);
			disk->minors = DISK_MAX_PARTS;
		}
		if (disk->first_minor > MINORMASK ||
		    disk->minors > MINORMASK + 1 ||
		    disk->first_minor + disk->minors > MINORMASK + 1)
			return -EINVAL;
	} else {
		if (WARN_ON(disk->minors))
			return -EINVAL;

		ret = blk_alloc_ext_minor();
		if (ret < 0)
			return ret;
		disk->major = BLOCK_EXT_MAJOR;
		disk->first_minor = ret;
		disk->flags |= GENHD_FL_EXT_DEVT;
	}

	/* delay uevents, until we scanned partition table */
	dev_set_uevent_suppress(ddev, 1);

	ddev->parent = parent;
	ddev->groups = groups;
	dev_set_name(ddev, "%s", disk->disk_name);
	if (!(disk->flags & GENHD_FL_HIDDEN))
		ddev->devt = MKDEV(disk->major, disk->first_minor);

    //非常重要的函数
	ret = device_add(ddev);
	if (ret)
		goto out_free_ext_minor;

	ret = disk_alloc_events(disk);
	if (ret)
		goto out_device_del;

	if (!sysfs_deprecated) {
		ret = sysfs_create_link(block_depr, &ddev->kobj,
					kobject_name(&ddev->kobj));
		if (ret)
			goto out_device_del;
	}

	/*
	 * avoid probable deadlock caused by allocating memory with
	 * GFP_KERNEL in runtime_resume callback of its all ancestor
	 * devices
	 */
	pm_runtime_set_memalloc_noio(ddev, true);

	ret = blk_integrity_add(disk);
	if (ret)
		goto out_del_block_link;

	disk->part0->bd_holder_dir =
		kobject_create_and_add("holders", &ddev->kobj);
	if (!disk->part0->bd_holder_dir) {
		ret = -ENOMEM;
		goto out_del_integrity;
	}
	disk->slave_dir = kobject_create_and_add("slaves", &ddev->kobj);
	if (!disk->slave_dir) {
		ret = -ENOMEM;
		goto out_put_holder_dir;
	}

	ret = bd_register_pending_holders(disk);
	if (ret < 0)
		goto out_put_slave_dir;

	ret = blk_register_queue(disk);
	if (ret)
		goto out_put_slave_dir;

	if (disk->flags & GENHD_FL_HIDDEN) {
		/*
		 * Don't let hidden disks show up in /proc/partitions,
		 * and don't bother scanning for partitions either.
		 */
		disk->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO;
		disk->flags |= GENHD_FL_NO_PART;
	} else {
		ret = bdi_register(disk->bdi, "%u:%u",
				   disk->major, disk->first_minor);
		if (ret)
			goto out_unregister_queue;
		bdi_set_owner(disk->bdi, ddev);
		ret = sysfs_create_link(&ddev->kobj,
					&disk->bdi->dev->kobj, "bdi");
		if (ret)
			goto out_unregister_bdi;

        //非常重要
		bdev_add(disk->part0, ddev->devt);
		disk_scan_partitions(disk);

		/*
		 * Announce the disk and partitions after all partitions are
		 * created. (for hidden disks uevents remain suppressed forever)
		 */
		dev_set_uevent_suppress(ddev, 0);
		disk_uevent(disk, KOBJ_ADD);
	}

	disk_update_readahead(disk);
	disk_add_events(disk);
	return 0;

    ...
}

 总结起来device_add_disk调用了两个非常重要的函数:

  1. device_add
  2. bdev_add
device_add函数
device_add
    --->devtmpfs_create_node

devtmpfs会给devtmpfs文件系统的线程发送创建块文件的消息,类似mknode,然后再/dev/目录下创建出来块文件。 

bdev_add函数:
void bdev_add(struct block_device *bdev, dev_t dev)
{
	bdev->bd_dev = dev;
	bdev->bd_inode->i_rdev = dev;
	bdev->bd_inode->i_ino = dev;
	insert_inode_hash(bdev->bd_inode);
}

设置block_device->device的bd_dev为块设备号,同时设置block_device->bd_inode的i_rdev为块设备号,同时insert_inode_hash函数 block_device的bd_inode添加到superblock的inode hash表中,这里逻辑非常重要,对理解swapon非常重要,我们知道swapon系统调用有如下一段代码:

	swapon系统调用:

    swap_file = file_open_name(name, O_RDWR|O_LARGEFILE, 0);
	if (IS_ERR(swap_file)) {
		error = PTR_ERR(swap_file);
		swap_file = NULL;
		goto bad_swap;
	}

	p->swap_file = swap_file;
	mapping = swap_file->f_mapping;
	dentry = swap_file->f_path.dentry;
	inode = mapping->host;


static int claim_swapfile(struct swap_info_struct *p, struct inode *inode)
{
	int error;

	if (S_ISBLK(inode->i_mode)) {
		p->bdev = blkdev_get_by_dev(inode->i_rdev,
				   FMODE_READ | FMODE_WRITE | FMODE_EXCL, p);
		if (IS_ERR(p->bdev)) {
			error = PTR_ERR(p->bdev);
			p->bdev = NULL;
			return error;
		}
		p->old_block_size = block_size(p->bdev);
		error = set_blocksize(p->bdev, PAGE_SIZE);
		if (error < 0)
			return error;
		/*
		 * Zoned block devices contain zones that have a sequential
		 * write only restriction.  Hence zoned block devices are not
		 * suitable for swapping.  Disallow them here.
		 */
		if (blk_queue_is_zoned(p->bdev->bd_disk->queue))
			return -EINVAL;
		p->flags |= SWP_BLKDEV;
	} else if (S_ISREG(inode->i_mode)) {
		p->bdev = inode->i_sb->s_bdev;
	}

	return 0;
}

blkdev_get_by_dev函数返回了一个block_device,这个block_device跟前面zram块驱动blk_alloc_disk 生成的block_device有啥关系?就是同一个,我们还是要从源码视角看懂这一切:

struct block_device *blkdev_get_by_dev(dev_t dev, fmode_t mode, void *holder)
{
	bool unblock_events = true;
	struct block_device *bdev;
	struct gendisk *disk;
	int ret;

	ret = devcgroup_check_permission(DEVCG_DEV_BLOCK,
			MAJOR(dev), MINOR(dev),
			((mode & FMODE_READ) ? DEVCG_ACC_READ : 0) |
			((mode & FMODE_WRITE) ? DEVCG_ACC_WRITE : 0));
	if (ret)
		return ERR_PTR(ret);

	bdev = blkdev_get_no_open(dev);
	if (!bdev)
		return ERR_PTR(-ENXIO);
	disk = bdev->bd_disk;
    ...
    return bdev;
}
struct block_device *blkdev_get_no_open(dev_t dev)
{
	struct block_device *bdev;
	struct inode *inode;

	inode = ilookup(blockdev_superblock, dev);
	if (!inode) {
		blk_request_module(dev);
		inode = ilookup(blockdev_superblock, dev);
		if (!inode)
			return NULL;
	}

	/* switch from the inode reference to a device mode one: */
	bdev = &BDEV_I(inode)->bdev;
	if (!kobject_get_unless_zero(&bdev->bd_device.kobj))
		bdev = NULL;
	iput(inode);

	if (!bdev)
		return NULL;
	if ((bdev->bd_disk->flags & GENHD_FL_HIDDEN) ||
	    !try_module_get(bdev->bd_disk->fops->owner)) {
		put_device(&bdev->bd_device);
		return NULL;
	}

	return bdev;
}

 inode = ilookup(blockdev_superblock, dev);根据块设备号dev,从blockdev_superblock拿到inode节点,为什么这里能拿到块设备文件(/dev/block/zram0)的inode,就是因为bdev_add时候将inode对象加入到blockdev_superblock的inode hash表中了,这里就能拿到。

参考文章:

块设备剖析之关键数据结构分析 - block_device/gendisk/hd_struct-下雨夜-ChinaUnix博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.NET 一款用于解密web.config配置的工具

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

位运算:带带孩子吧,孩子很强的!

快速进制 在聊到位运算之前&#xff0c;不妨先简单过一遍二进制的东西。熟悉二进制和十进制的快速转换确实是掌握位运算的基础&#xff0c;因为位运算直接在二进制位上进行操作。如果不熟悉二进制表示&#xff0c;很难直观理解位运算的效果。 这里主要涉及二进制和十进制之间…

蓝桥杯嵌入式国三备赛经验分享

1 学习STM32入门视频 向大家推荐一套宝藏级别的视频&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 如果已经比过蓝桥杯单片机或学习过单片机相关课程的同学&#xff0c;你们可以尝试不需要STM32套件进行学习。如果没有学过单片机相关课程的同学&#xff0c;可以买…

直击智博会,supOS加快发展新质生产力,赋能全球工厂数字化转型

9月6日&#xff0c;第十四届智慧城市与智能经济博览会&#xff08;以下简称智博会&#xff09;开幕主题活动在宁波举办。本届智博会着重围绕“数字赋能新型工业化”主题&#xff0c;设置开幕活动、展览展示和前瞻研讨、产数合作、赛事赋能、开放活动等4大板块活动&#xff0c;旨…

全球AI产品Top100排行榜

Web Top50的榜单里&#xff0c;AIGC类型的应用占比52%&#xff0c;遥遥领先。AIGC类型包括图像、视频、音乐、语音等的内容生成和编辑。音乐生成应用Suno在过去六个月中的排名跃升最为显著&#xff0c;从第36位上升至第5位。排名第二大类是通用对话/AI聊天/角色扮演类型的应用&…

银河麒麟系统开机自动进入指定账户(如:root)桌面

目录 1. 需求的提出 2. 机器环境说明 3. 解决方法 4. 附加说明 1. 需求的提出 编写好的软件在客户的装有银河麒麟操作系统的机器上运行&#xff0c;但有些操作必须用root权限才能操作&#xff0c;如&#xff1a;打开串口。Linux下的普通账户打开串口时提示权限不足。在普通…

【HarmonyOS NEXT】实现截图功能

【HarmonyOS NEXT】实现截图功能 【需求】 实现&#xff1a;实现点击截图按钮&#xff0c;实现对页面/组件的截图 【步骤】 编写页面UI Entry Component struct Screenshot {BuildergetSnapContent() {Column() {Image().width(100%).objectFit(ImageFit.Auto).borderRadi…

C++入门(06)安装QT并快速测试体验一个简单的C++GUI项目

文章目录 1. 清华镜像源下载2. 安装3. 开始菜单上的 QT 工具4. 打开 Qt Creator5. 简单的 GUI C 项目5.1 打开 Qt Creator 并创建新项目5.2 设计界面5.3 添加按钮的点击事件5.4 编译并运行项目 6. 信号和槽&#xff08;Signals and Slots&#xff09; 这里用到了C类与对象的很多…

网络协议详解

目录 1.认识网络协议 2网络协议的设计 2.1网络通信的问题 2.2网络协议的分层设计 软件分层与网络分层 3.OSI七层网络模型 各层次的介绍如下 4.TCP/IP 五层协议 各层次说明 各层次所解决的问题 5.网络和操作系统之间的关系 单主机下 多主机下 6.重新理解网络协议 …

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数

【鸿蒙HarmonyOS NEXT】页面之间相互传递参数 一、环境说明二、页面之间相互传参 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、页面之间相互传参 说明&#xff1a; 页面间的导航可以通过页面路由router模块来实现。页面路由模块根据页…

kubernetes微服务基础及类型

目录 1 什么是微服务 2 微服务的类型 3 ipvs模式 ipvs模式配置方式 4 微服务类型详解 4.1 ClusterIP 4.2 ClusterIP中的特殊模式headless 4.3 nodeport 4.4 metalLB配合loadbalance实现发布IP 1 什么是微服务 用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&…

JavaWeb——JavaScript(3/4)-JS对象:BOM、DOM(Window、Location,概念、标准模型、获取元素对象)

目录 BOM 介绍 Window 案例(1) Location DOM 概念 标准模型 获取元素对象 案例(2) 原始代码 完整代码 JS对象 BOM 介绍 概念&#xff1a;Browser Object Model 浏览器对象模型&#xff0c;允许JavaScript与浏览器对话&#xff0c;JavaScript 将浏览器的各个组成部…

git 提交自动带上storyid

公司里的运维团队的产品经理&#xff0c;那老六提出说要在每个提交带上的jira storyid或者bugid&#xff0c;不用他自己弄不顾他人麻烦&#xff0c;真想问候他的xx。不过既然已经成为定局&#xff0c;还是想想有没有其他办法。经一番调研&#xff0c;网上有比较零碎的信息&…

攻防世界--->地穴

前言&#xff1a;学习笔记。 下载 解压 查壳。 64位ida打开。 进入主函数。 很容易得知&#xff0c;这是一个RC4加密。 【 &#xff08;题外话&#xff09; 在reverse中&#xff0c;RC4考点&#xff0c;不会很难。 reverse中RC4关键点就是&#xff0c;抓住异或。 一般解这种…

Open a folder or workspace... (File -> Open Folder)

问题&#xff1a;vscode Open with Live Server 时 显示Open a folder or workspace... (File -> Open Folder)报错 解决&#xff1a;不可以单独打开文件1.html ; 需要在文件夹里打开 像这样

【哈希表】深入理解哈希表

目录 1、哈希表简介2、哈希函数2.1、概念2.2、常用的哈希函数2.2.1、直接定址法2.2.2、除留余数法2.2.3、平方取中法2.2.4、基数转换法 3、哈希冲突3.1、概念3.2、开放地址法【闭散列&#xff1a;key存放到冲突位置的“下一个”空位置】3.3、链地址法【开散列&#xff1a;冲突位…

SAM 2:分割图像和视频中的任何内容

文章目录 摘要1 引言2 相关工作3 任务&#xff1a;可提示视觉分割4 模型5 数据5.1 数据引擎5.2 SA-V数据集 6 零样本实验6.1 视频任务6.1.1 提示视频分割6.1.2 半监督视频对象分割6.1.3 公平性评估 6.2 图像任务 7 与半监督VOS的最新技术的比较8 数据和模型消融8.1 数据消融8.2…

dr 航迹推算 知识介绍

DR&#xff08;Dead Reckoning&#xff09;航迹推算是一种在航海、航空、车辆导航等领域中广泛使用的技术&#xff0c;用于估算物体的位置。DR航迹推算主要通过已知的初始位置和运动参数&#xff08;如速度、方向&#xff09;来预测物体的当前位置。以下是 DR 航迹推算的详细知…

跨平台电商数据整合:item_get API在电商大数据平台中的角色

在电商行业蓬勃发展的今天&#xff0c;跨平台运营已成为众多商家的必然选择。然而&#xff0c;随之而来的数据孤岛问题却成为了制约电商企业进一步发展的瓶颈。为了解决这一问题&#xff0c;电商大数据平台应运而生&#xff0c;而item_get API作为获取商品详情的关键接口&#…

统计学第6天

1、变量间关系的度量 函数关系 &#xff08;1&#xff09;是一一对应的确定关系&#xff1b; &#xff08;2&#xff09;设有两个变量x和y&#xff0c;变量y随x一起变化&#xff0c;并完全依赖于x,当x取某个数值时&#xff0c;y根据确定的关系取相应的值&#xff0c;称y是x的…