嵌入式实时操作系统的设计与开发 (第一级内存管理算法)

news2024/12/24 21:59:58

伙伴算法及实现上的改进

可变内存管理,随着内存的不断分配和回收,即使系统中有1MB的内存,也可能因没法分配大小为100KB的连续内存块而造成分配失败。
伙伴系统,可以大大改善这一情况。

伙伴系统的缺点:

  1. 仅管大小为2K内存块回收时只需要搜索同样字节大小的块以判断是否需要合并,但是时间还是没法确定的。内存块回收时链表遍历的时间之所以无法确定,是因为链表只能顺序搜索,那么复杂度就是O(n),如果能实现一种O(1)复杂度的搜索算法就可以解决这个问题。
  2. 内存控制块的组织问题。一般的实现方式是在内存块的开始部分预留出一段内存来作为内存控制块。
    这样做有一个缺点,如用户分配512B的内存块,内存管理系统经过计算后知道需要的内存块其实为522B,因此,系统会分配一个1024B大小的内存块,本来用户为了充分利用内存,分配512B,但由于多了一个内存控制块,反而浪费得更多:1024-522=502B。

问题二的产生主要是因为内存控制块本身也属于内存块的一部分,把控制块单独抽出来,而真正可用的内存块留给用户就可以解决问题。

aCoral伙伴算法的实现思路

首先真正的物理内存块被分成了两部分,一部分为内存控制结构所使用,内存初始化函数buddy_init()将逐个初始化这些结构。
剩下的内存是用户可用内存。

这些内存被划分为总多基本块,每个基本块的大小可以通过常量BLOCK_SIZE配置。(默认1<<7字节)
这样内存的分配和回收都是基于序列的了,换句话说,这些基本内存块是从0到n逐个标记的。

在逻辑上这些内存块被组织成了m层,最大层数m可以通过常量LEVEL(14)进行配置。

第0层每个内存块的大小为BLOCK_SIZE,第1层每个内存块的大小为2*BLOCK_SIZE,以此类推,第n层内存块的大小为BLOCKSIZE<<n。

在这里插入图片描述
如图所示,这里的内存共包括8个基本内存块,在逻辑上被组织成了3层。

当需要分配2i字节大小的内存块时,用公式log (2i/BLOCK_SIZE),可求出从哪一个逻辑层分配内存。
如果该层有空闲内存块,即可分配。
当这一层没有空闲的内存块时,就向上层申请,最终会得到两个空闲的2i字节大小的内存块。

为了最终对应到物理块,这些逻辑的内存块始终是有序号标记的。

以8个基本内存块大小的内存举例。

  1. 开始的时候,8个基本内存块全部空闲(其序号依次为0,…,7),内存初始化时,可能将这8个基本内存块注册在第3层(详见伙伴系统初始化buddy_init(),第0层的基本内存块可能不能直接使用,用户只感觉到第3层的内存块(该层的内存块大小就是BLOCK_SIZE<<8=3))。
  2. 当系统申请一个BLOCK_SIZE大小的内存块,根据公式可得,从第0层开始分配,而这时除了第3层,其余层的内存块都不可用(内存初始化时设定)。
  3. 那么第0层向第1层申请,第1层的内存块也不可以,再向第2层申请,直到第3层。
  4. 第3层将唯一的一个内存块分成两个,供第二层使用,第二层取出一个(通常是序列号小的,即由基本块0,1,2,3组成)分配给第1层,另外一个标记为空闲。
  5. 依次向下,第0层有了两个空闲块,即基本内存块0、1.
  6. 根据基本内存块的序号(这里是0)转换成相应的物理地址返回给调用函数。
  7. 内存回收的时候,传入的参数是地址,先把地址转换成序号,再做回收。
  8. 回收的同时如果发现伙伴也是空闲,则向上合并成一个大的空闲块(最高层除外),从而减少外碎片。

为了标记每个逻辑内存块的空闲状态和快速找到一个空闲块,每层需要一个空闲状态位图块、空闲内存块链表数组、空闲内存块链表头三个结构。同时为了回收的效率,还需要为每个逻辑内存块存储逻辑层信息,即原来从哪一个逻辑层分配。

typedef struct{
	int *free_list[LEVEL]; //各层空闲内存块链表
	unsigned int *bitmap[LEVEL];//各层空闲位图
	int free_cur[LEVEL];//各层空闲内存块链表头
	unsigned int num[LEVEL]; //各层内存块个数
	char level;//伙伴系统的层数
	unsigned int start_add; //伙伴系统管理的内存起始地址
	unsigned int end_addr; //末尾地址
	unsigned int block_num; //基本内存块的数量,等于num[0]
	unsigned int free_num; //剩余基本内存块数量
	unsigned int block_size; //基本内存块大小
}

内存控制块acoral_block_ctrl_t是aCoral进行内存分配和回收的关键数据结构,其中的一个重要数据结构unsigned int*bitmap[LEVEL]是描述内存块状态的状态位图块数组,每一层均有一个空闲状态位图块数组bitmap,bitmap实际上是一个二维数组bitmap[m][n],第一个下标代表位图块所在的层数m,第二个下标代表该层的第n个位图块。

状态位图块数组bitmap[m][n]的每个值都是int类型,32位的,每一位要么为1,要么为0.
为0表示相邻两块内存块没有空闲的,为1表示相邻两块内存块至少有一块是空闲的。
由于bitmap[m][n]的每个值是32位,而每一位代表相邻两块内存块,所以biitmap[m][n]的每个值可以表示64个内存块的分配情况。

0~(m-1)层中,相邻的两块内存块由空闲位图块中的一位来标识是否空闲,对于第i层,每个内存块由空闲位图块中的一位来标识是否空闲。
1位只有0和1两种状态,则两块内存块有4种状态:两块都空闲、没有空闲块、只有奇数块空闲、只有偶数块空闲。两个状态如何表示4种状态?

这得从伙伴系统思想说起了,当伙伴系统回收时,如果导致某一层相邻两个内存块都空闲时,就会向上一层回收,将两个伙伴合并成一个更大的内存块。因此,正常情况下不存在两块都空闲的情况,0~(m-1)层相邻的两块内存块只有两种状态:没有空闲块、有一块是空闲的。

虽然通过状态位图块数组解决了回收时复杂度O(n)的问题,但没有解决空闲内存块分配问题,即分配内存时如何查找某一层空闲的内存块。
大家可能说,直接查看该层的内存状态位图数组中哪一位为1就可以了,但是如果是这样的话,和遍历链表没有本质区别,复杂度也是O(n)。

所以,aCoral增加了空闲块链表数组int *free_list[LEVEL],实现了分配时O(1)的复杂度,同时还可以解决内存控制块(链表的实体)占用部分内存块导致的问题。

首先,定义了空闲位图块链表头数组int free_cur[LEVEL];该数组元素指向第一个空闲位图块的标号。然后free_list[LEVEL]的值指出了下一个空闲位图块。

例如,对于第0层,free_cur[0]=2,那么读取free_list[0][2],得到下一个空闲块,假设其值为4,则读取free_list[0][4],再得到下一个空闲位图块,依次往后,形成一个空闲位图块的表链。

注意,这里的2和4表示的是第0层内存状态位图块的标号,这样2表示此时标号为2的内存状态位图块中的32位中有非0位,即这个非零位所对应的相邻内存块由空闲。

根据前面的描述,只要根据第m层的free_cur[m]找出空闲位图块对应的标号i,然后读取bitmap[m][i]的值,再判断bitmap[m][i]首先出现“1”的那一位,并找到该位对应的内存块序号,便可确定该内存块对应的基本内存块标号,最后得到相应物理地址返回给用户,并将刚才的“1”置为0,如果此时空闲状态位图块变为了0(32位每一位都为0),更改free_cur[m]=free_list[m][i],由此可见,根据空闲内存块链表数组就能快速找到空闲内存块,而对链表的维护的复杂度也是O(1)。

还有一个问题就是,系统回收内存块的时候,传送的是地址,根据地址可以知道这个内存块开始地址对应的基本内存块的标号,但是如何知道这块内存块的大小呢?即从第几层分配呢?我们知道不同层分配的内存块包含的基本内存块是不一样的,0层包含1个基本内存块,1层包含2个基本内存块。
因此,不同层分配出去的内存块的起始地址可能相同(对应的基本内存块编号相同),这就需要一个数据结构来保存基本内存块i(i=1,2,3)的起始地址所对应的逻辑内存块大小。

typedef struct{
	char level;
}acoral_block_t;

由于标号为奇数的基本内存块肯定是从第0层分配出去(因为偶数的基本内存块由非0层分配出去),因此不用保存其是从第几层分配出去的。
如果某个基本内存块尚未分配出去,则level的值为-1。
level的值同时也可用来区分内存块位图管理时的两块兄弟内存块的状态。

根据前面的描述,在1~m-1层,状态位图块bitmap[m][n]的某一位为1时说明该位图管理的奇数块或偶数基本内存块在使用,但是如果偶数块使用了的话,其对应的acoral_block_t的level值大于0,否则为-1,因此可区分这两种状态。

伙伴算法的初始化

aCoral伙伴算法能正常工作以前,需要通过buddy_init()进行初始化。
buddy_init()传入参数是start_addr和end_addr,分别是系统可用物理内存的起始和终止地址。

#defiine acoral_mem_init(start,end)  buddy_init(start,end)
acoral_mem_init((unsigned int)&head_start, (unsigned int)*heap_end);
acoral_block_ctrl_t *acoral_mem_ctrl;
acoral_block_t *acoral_mem_blocks;
unsigned int buddy_init(unsigned int start_adr, unsigned int end_adr)
{
	int i, k;
	unsigned int resize_size;
	unsigned int save_adr;
	unsigned int index;
	unsigned int num = 1;
	unsigned int adjust_level = 1;
	int level = 0;
	unsigned int max_num, o_num;
	unsigned int cur;
	start_adr += 3;
	start_adr &= ~(4-1);
	end_adr &= ~(4-1);
	resize_size = BASIC_BLOCK_SIZE;
	end_adr = end_adr - sizeof(acoral_block_ctrl_t); //减去内存控制块的大小,剩下的才是可分配内存
	end_adr &= ~(4-1);
	acoral_mem_ctrl = (acoral_block_ctr_t *)end_adr; //内存控制块的地址
	 //内存少,不分配
	if(start_adr > end_adr || end_adr - start_adr < BASIC_BLOCK_SZE)
	{
		acoral_mem_ctrl->state = MEM_NO_ALLOC;
		return -1;
	}
	acoral_mem_ctrl->state = MEM_OK;
	/*根据基本内存块的值和堆的大小获得最大层数,如基本内存块BLOCK_SIZE=4B,而内存的大小为18B,则最大层数为3,第0层是4B,第1层是8B,第二层16B,num记录系统总内存可以分成多少个BLOCK_SIZE大小的基本内存。*/
	while(1)
	{
		if(end_adr <= start_adr + resize_size)
			break;
		resize_size = resize_size << 1;
		num = num << 1;
		adjust_level++;
	}
	//根据num为最小内存控制块(第0层)acoral_block_t分配空间(每一个最小内存控制块均有一个acoral_block_t),因为一个acoral_block_t的大小就是1B,结束地址减去num正好是最小内存控制块数组的开始地址
	acoral_mem_blocks = (acoral_blck_t *)end_adr - num;
	save_adr = (unsigned int)acoral_mem_blocks;
	//如果层数较小,则最大层用一块构成,如果层数较多,限制层数范围,最大层由多块构成
	level = adjust_delevl;
	if(adjust_level > LEVEL)
		level = LEVEL;
	//用刚刚计算的基本内存块num除以32,就变成了所需内存块位图数组bitmap的维数,即需要多个32位的内存位图块。
	num = num/32;

	//for循环是用来分配0~m-1层内存控制块的空间。由于内存块位图数组bitmap的某一位代表了两块内存块,因此,对于第0层需要num/2个32位的内存位图块,所以num/2,level变量用来记录以该基本内存块为起始地址的内存块分配时所在的层
	for(i=0; i<level-1; i++)
	{
		num = num >> 1; //除去最大层,其它每层的32位图都是64个块构成,所以要除以2
		if(num == 0)
		{
			num = 1; //不足一个位图的,用一个位图表示
		}
		save_adr -= num*4; //每一个32位位图4个字节
		save_adr &= ~(4-1); //四字节对齐
		acoral_mem_ctrl->bitmap[i] = (unsigned int *)save_adr;
		acoral_mem_ctrl->num[i] = num;
		save_adr -= num*4;
		save_adr &= ~(4-1); //四字节对齐
		acoral_mem_ctrl->free_list[i] = (int *)save_adr;
		for(k=0; k<num; k++)
		{
			acoral_mem_ctrl->bitmap[i][k] = 0;
			acoral_mem_ctrl->free_list[i][k] = -1;
		}
		acoral_mem_ctrl->free_cur[i] = -1;
	}

	//最大内存块层不足一个位图的,用一个位图表示,这里num没有除以二,因为最高内存块位图一位对应一个内存块,这一层的相邻内存块都空闲时无法向上回收,存在相邻两块空闲的情况,故需要1位对应一块
	if(num == 0)
	{
		num = 1;
	}
	save_adr -= num*4; //每一个32位位图4个字节
		save_adr &= ~(4-1); //四字节对齐
		acoral_mem_ctrl->bitmap[i] = (unsigned int *)save_adr;
		acoral_mem_ctrl->num[i] = num;
		save_adr -= num*4;
		save_adr &= ~(4-1); //四字节对齐
		acoral_mem_ctrl->free_list[i] = (int *)save_adr;
		for(k=0; k<num; k++)
		{
			acoral_mem_ctrl->bitmap[i][k] = 0;
			acoral_mem_ctrl->free_list[i][k] = -1;
		}
		acoral_mem_ctrl->free_cur[i] = -1;

	// 如果剩余内存大小不够形成现在的level,对伙伴系统层数m进行调整,如果将刚才描述的数据结构分配出去后,最初的层数比当前系统的层数少1,则减少层数。例如,基本内存块为1KB,而初始的堆内存大小为1.024MB,则可知最开始算的层数为11。但是将内存管理需要的控制块的内存分配后,可能只剩下999KB,只需要10层即可管理,所以对层数进行调整
	if(save_adr <= (start_adr + (resize_size >> 1)))
		adjust_level--;
	if(adject_level > LEVEL)
		level = LEVEL;
	
	//初始化内存控制块
	acoral_mem_ctrl->level = level;
	acoral_mem_ctrl->start_adr = start_adr;
	num = (save_adr - start_adr) >> BLOCK_SHIFT;
	acoral_mem_ctrl->end_adr = start_adr + (num << BLOCK_SHIFT);
	acoral_mem_ctrl->block_num = num;
	acoral_mem_ctrl->free_num = num;
	acoral_mem_ctrl->block_size = BASIC_BLOCK_SIZE;

	i = 0;
	max_num = (1 << level) - 1; 
	o_num = 0;

	//设置第level-1层(即最高层)空闲位图块链表头的值,可见空闲位图块指向该层的0号空闲内存块free_list。
	if (num > 0)
	{ // 有内存块,则最大内存块层的free_cur为0
		acoral_mem_ctrl->free_cur[level - 1] = 0;
	}
	else
	{ // 无内存块,则最大内存块层的free_cur为-1
		acoral_mem_ctrl->free_cur[level - 1] = -1;
	}

	
	//有了前面的准备工作,接下来开始把实际可用的内存分配到各个逻辑层,分配的原则是:首先将内存都尽量分配给高层,直到剩下的内存不够这一层的一个内存块大小,再依次分配给低层。
	
	//首先考虑最高层:(level-1)层,如果该层的内存块数是32的倍数,整块内存优先分给最大内存块层,计算当前可分配内存容量能否直接形成一个最大内存块层的32位图
	while (num >= max_num * 32)
	{
		acoral_mem_ctrl->bitmap[level - 1][i] = -1;
		;
		acoral_mem_ctrl->free_list[level - 1][i] = i + 1;
		num -= max_num * 32;
		o_num += max_num * 32;
		i++;
	}
	if (num == 0)
	{ // 所有块正好分配到最大内存块层的32位图
		acoral_mem_ctrl->free_list[level - 1][i - 1] = -1;
	}

	// 计算当前可分配内存是否还能形成最大内存块层的一块
	while (num >= max_num)
	{
		index = (o_num >> level) - 1;
		acoral_set_bit(index, acoral_mem_ctrl->bitmap[level - 1]);
		num -= max_num;
		o_num += max_num;
	}
	acoral_mem_ctrl->free_list[level - 1][i] = -1;

	// 接下来的每层初始化
	while (--level > 0)
	{
		index = o_num >> level;
		if (num == 0)
			break;
		cur = index / 32;
		max_num = (1 << level) - 1; // 每层的内存块大小
		if (num >= max_num)
		{
			acoral_mem_blocks[BLOCK_INDEX(o_num)].level = -1;
			acoral_set_bit(index, acoral_mem_ctrl->bitmap[level - 1]);
			acoral_mem_ctrl->free_list[level - 1][cur] = -1;
			acoral_mem_ctrl->free_cur[level - 1] = cur;
			o_num += max_num;
			num -= max_num;
		}
	}
	return 0;
}

起始地址要增加它的值然后对齐,结束地址要减少它的值然后对齐。
如起始地址:0x7 = 0111 加3 = 1010 & (1100) = 1000 = 0x8
结束地址:0x7 = 0111 & (1100) = 0100 = 0x4

递归把剩下的内存块分配给其它逻辑层。例如最高层(N)的一个内存块大小为16个基本内存块,而给顶层分配完了后,可能只剩下15个基本内存块(不足以形成一个最高层的内存块),则给N-1层分配8个基本内存块大小的内存,N-2层分配4个基本内存块大小,N-3层分配2个,0层分配1个基本内存块大小的内存。

经过buddy_init()对伙伴系统进行初始化后,内存都尽量分配给了高层 ,低层的free_list的值都被置为-1了,这意味着,底层的内存块不能之间被使用,直到剩下的内存不够某层的一个内存块大小,再依次分配给低层。

在这里插入图片描述

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

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

相关文章

中了devos勒索病毒后如何正确解决,勒索病毒解密,数据恢复

对于企业来说&#xff0c;企业的计算机网络安全防护非常有必要&#xff0c;不然很容易被黑客攻击&#xff0c;最近一段时间我们接到客户求助&#xff0c;企业的服务器被Devos勒索病毒攻击&#xff0c;导致企业的计算机无法正常使用。经过云天数据恢复中心的工程师分析&#xff…

Talk | UCSD博士生刘明华:在开放的世界中理解和生成3D物体

本期为TechBeat人工智能社区第539期线上Talk。 北京时间10月19日&#xff08;周四&#xff09;20:00&#xff0c;加州大学圣地亚哥分校博士生—刘明华的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “在开放的世界中理解和生成3D物体”&#xff0…

未来装备探索:数字孪生装备

源自&#xff1a;《计算机集成制造系统》 作者&#xff1a;陶飞 张辰源 张贺 程江峰 邹孝付 徐慧 王勇 谢兵兵 “人工智能技术与咨询” 发布 摘 要 工程装备、制造装备、医疗装备等各类装备是加快国家基础建设&#xff0c;提升国家经济实力和保障医疗健康的重要…

【Linux07-进程间通信】侧重 管道 和 SystemV 的进程间通信讲解

今天&#xff0c;带来Linux下的进程间通信讲解。文中不足错漏之处望请斧正&#xff01; 是什么 进程间通信&#xff0c;Inter-Process Communication(IPC)&#xff1a; 进程间通过访问同一块内存空间来进行数据的交流。 为什么 为什么要有IPC&#xff0c;它的作用是什么&am…

AI杀疯!2023上半年至今有趣的AI算法(内附视频)

公众号&#xff1a;算法一只狗 文章目录 第一个&#xff0c;一切都可以进行分割第二个&#xff0c;开源图文回答工具第三个&#xff0c;视频转换风格生成第四个&#xff0c;免费好用的文档对话工具文档对话能力文档联系功能 今年&#xff0c;我们见证了人工智能算法的起飞&…

java如何导入导出excel

在Java中&#xff0c;可以使用多种方式导入和导出Excel文件。下面将详细介绍几种常见的方法及其实现步骤&#xff1a; 1. Apache POI库&#xff1a; Apache POI是一个开源的Java库&#xff0c;提供了许多类和方法用于处理Microsoft Office格式的文档&#xff0c;包括Excel文件…

关于刷题时使用数组的小注意事项

&#x1f4af; 博客内容&#xff1a;关于刷题时使用数组的小技巧 &#x1f600; 作  者&#xff1a;陈大大陈 &#x1f680; 个人简介&#xff1a;一个正在努力学技术的准前端&#xff0c;专注基础和实战分享 &#xff0c;欢迎私信&#xff01; &#x1f496; 欢迎大家&#…

我们做播客这些年的自我进化

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 姝琦 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 这是一期荔枝播客十周年活动的特别节目&#xff0c;借这次机会&#xff0c;我们几位主播也借此机会沉淀下…

RHCE8 资料整理(二)

RHCE8 资料整理 第二篇 用户及权限管理第8章 用户管理8.1 基本概念8.2 管理用户8.2.1 创建用户8.2.2 修改用户属性 8.3 用户的密码策略8.4 用户授权8.5 重置root密码 第9章 权限管理9.1 所有者和所属组9.2 查看及修改权限9.3 数字权限9.4 默认权限9.5 特殊权限9.6 隐藏权限 第1…

C++前缀和算法应用:和至少为 K 的最短子数组的原理、源码及测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 题目 给你一个整数数组 nums 和一个整数 k &#xff0c;找出 nums 中和至少为 k 的 最短非空子数组 &#xff0c;并返回该子数组的长度。如果不存在这样的 子数组 &a…

配置公网和私网用户通过非公网口的IP地址访问内部服务器和Internet示例

组网需求 如配置公网和私网用户通过非公网口的IP地址访问内部服务器和Internet示例所示&#xff0c;某小型企业内网部署了一台路由器、一台FTP服务器和一台Web服务器。路由器作为接入网关&#xff0c;为下挂的内网用户提供上网服务&#xff0c;主要包括浏览网页、使用即时通信…

短视频矩阵系统源头开发

一、智能剪辑、矩阵分发、无人直播、爆款文案于一体独立应用开发 抖去推----主要针对本地生活的----移动端(小程序软件系统&#xff0c;目前是全国源头独立开发)&#xff0c;开发功能大拆解分享&#xff0c;功能大拆解&#xff1a; 7大模型剪辑法&#xff08;数学阶乘&#x…

Flink之输出算子Data Sink

Flink之输出算子Data Sink Data Sink常见输出算子print()printToErr()writeAsText()writeAsCsv()writeToSocket() 常用连接器File Sink连接器Kafka Sink连接器RabbitMQ Sink连接器JDBC Sink连接器Elasticsearch Sink连接器MongoDB Sink连接器 自定义SinkRichSinkFunctionSinkFu…

海外展预告 | 同立海源将参展美国圣地亚哥SITC 2023年会

第38届癌症免疫治疗学会&#xff08;Society for Immunotherapy of Cancer, SITC&#xff09;年会将于11月1日-5日在美国圣地亚哥举行。同立海源将携细胞分选磁珠试剂、真核/原核重组蛋白、免疫细胞培养基等CGT上游GMP级核心原料整体解决方案参加此次会议并设立展台&#xff0c…

4.5 互联网的路由器

思维导图&#xff1a; 4.5 互联网的路由选择协议 本节的核心内容是讨论如何确定路由表中的路由&#xff0c;具体通过何种路由选择协议实现。 --- **4.5.1 有关路由选择协议的几个基本概念** - **理想的路由算法:** 路由选择协议的关键是路由算法。一个理想的路由算法应具…

如何打造独立站?这4个要点必须做到!

“什么是独立站”独立站指的是个人或小团队独立创建和管理的网站&#xff0c;与依赖于第三方平台的博客、社交媒体或在线商店不同。独立站的所有权和控制权完全归个人或小团队所有&#xff0c;因此具有更大的自主性和独立性&#xff0c;不受第三方平台的限制。 独立站是由个人…

Lua-http库写一个爬虫程序怎么样 ?

以下是一个使用Lua-http库编写的一个爬虫程序&#xff0c;该爬虫使用Lua语言来抓取www.snapchat.com的内容。 代码必须使用以下代码&#xff1a;get_proxy -- 导入所需的库 local http require("http") local json require("json")-- 定义爬虫IP服务器 …

必示科技发布“早准快全易”智能运维产品,与生态伙伴共谋增长

2023年10月13日&#xff0c;“因智而聚 共谋增长”必示科技产品发布活动在北京中关村智造大街圆满召开&#xff0c;来自智能运维行业领域共40多家企业高层代表出席了本次闭门交流活动。 必示科技发布了三款智能运维产品&#xff1a;应用监控预警系统&#xff08;RiskSeer-App&…

[SQL开发笔记]创建SQL数据库

一、引言 在计算机软件开发以及业务流程中&#xff0c;大量数据不断产生&#xff0c;如何安全有效地存储、检索和管理它们已成为信息时代一个至关重要的问题。解决这个问题的关键在于使用数据库&#xff0c;数据库能够高效且条理分明地存储数据&#xff0c;方便用户进行迅速和…

TikTok Shop新结算政策:卖家选择权加强,电商市场蓄势待发

据悉&#xff0c;从2023年11月1日开始&#xff0c;TikTok Shop将根据卖家的店铺表现来应用3种不同类型的结算期&#xff0c;其中&#xff0c;标准结算期&#xff1a;资金交收期为8个日历日&#xff1b;快速结算期&#xff1a;资金交收期为3个日历日&#xff1b;延长结算期&…