buffer它到底做了个啥,源码级分析linux内核的文件系统的缓冲区

news2024/9/24 1:22:04

最近一直在学习linux内核源码,总结一下
https://github.com/xiaozhang8tuo/linux-kernel-0.11 自己整理过的带注释的源码。

为什么要有buffer

​ 高速缓冲区是文件系统访问块设备中数据的必经要道(PS:如果所有程序结果都不落盘,只是int a, a++直接在主存中搞事情,就不需要缓冲了,即使如此加载程序还是要读取文件系统的)。为了访问文件系统等块设备上的数据,内核可以每次都访问块设备,进行读或写操作。但是每次/O操作的时间与内存和CPU的处理速度相比是非常慢的。为了提高系统的性能,内核就在内存中开辟了一个高速数据冲区(池)(buffer cache),并将其划分成一个个与磁盘数据块大小相等的缓冲块来使用和管理,以期减少访问块设备的次数。

​ 在linux内核中,高速缓冲区位于内核代码和主内存区之间。高速缓冲中存放最近被使用过的各个块设备中的数据块。当需要从块设备中读取数据时,缓冲区管理程序首先会在高速缓冲中寻找。如果相应数据已经在缓冲中,就无需再从块设备上读。如果数据不在高速缓冲中,就发出读块设备的命令,将数据读到高速缓冲中。当需要把数据写到块设备中时,系统就会在高速缓冲区中申请一块空闲的缓冲块来临时存放这些数据。至于什么时候把数据真正地写到设备中去,则是通过设备数据同步实现的。Linux内核实现高速缓冲区的程序是buffer.c。文件系统中其他程序通过指定需要访问的设备号和数据逻辑块号来调用它的块读写函数。这些接口函数有:块读取函数bread、块提前预读函数breada和页块读取函数bread_page(全是b开头的函数,从buffer中读设备的block)。页块读取函数一次读取一页内存所能容纳的缓冲块数(4块)。

buffer在哪

main函数,mem_init ,初始化mem_map,buffer_init中初始化缓冲区

在这里插入图片描述

// 缓冲区初始化函数。
// 参数buffer_end是缓冲区内存末端。对于具有16MB内存的系统,缓冲区末瑞被设置为4MB.
// 对于有8MB内存的系统,缓冲区末瑞被设置为2MB。该函数从缓冲区开始位置start_buffer
// 处和缓冲区未端buffer_end处分别同时设置(初始化)缓冲块头结构和对应的数据块。直到
// 缓冲区中所有内存被分配完毕。参见程序列表前面的示意图。
void buffer_init(long buffer_end)
{
	struct buffer_head * h = start_buffer;
	void * b;
	int i;

	// 首先根据参数提供的缓冲区高端位置确定实际缓冲区高端位置b。如果缓冲区高端等于1Mb,
	// 则因为从640KB一1MB被显示内存和BIOS占用,所以实际可用缓冲区内存高端位置应该是
	// 640KB。否则缓冲区内存高端一定大于1MB。
	if (buffer_end == 1<<20)
		b = (void *) (640*1024);
	else
		b = (void *) buffer_end;
	
	// 这段代码用于初始化缓冲区,建立空闲缓冲块循环链表,并获取系统中缓冲块数目。操作的
	// 过程是从缓冲区高瑞开始划分1KB大小的缓冲块,与此同时在缓冲区低端建立描述该缓冲块
	// 的结构buffer_head,并将这些buffer_head组成双向链表,
	// h是指向缓冲头结构的指针,而h+1是指向内存地址连续的下一个缓冲头地址,可以说是
	// 指向h缓冲头的末瑞外。为了保证有足够长度的内存来存储一个缓冲头结构,需要b所指向
	// 的内存块地址>=h缓冲头的末端,即要求>=h+1。
	// ------------------------------------------------------
	// ↑			 (640KB)↑--------↑(1MB)					↑
	// start_buffer         BIOS + 显存			   buffer_end
	while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {		// 尽可能多的分配直到block不够了/buffer_head不够了(两者相交)
		h->b_dev = 0;										// 使用该缓冲块的设备号
		h->b_dirt = 0;										// 脏标志,缓冲修改标志
		h->b_count = 0;										// 缓冲块引用计数
		h->b_lock = 0;										// 缓冲块锁定标志
		h->b_uptodate = 0;									// 缓冲块更新标志(或称有效标志)
		h->b_wait = NULL;									// 指向等待该缓冲块解锁的进程。
		h->b_next = NULL;									// 指向具有相同hash值的下一个缓冲头。
		h->b_prev = NULL;									// 指向具有相同hash值的前一个缓冲头。
		h->b_data = (char *) b;								// 指向对应缓冲块数据块(1024字节)。
		h->b_prev_free = h-1;								// 指向链表中前一项。
		h->b_next_free = h+1;								// 指向链表中下一项。
		h++;
		NR_BUFFERS++;
		if (b == (void *) 0x100000)							// 若b递减到等于1MB,则跳过384KB
			b = (void *) 0xA0000;							// 让b指向640KB
	}

	h--;									//让h指向最后一个有效缓冲块头。				
	free_list = start_buffer;  				//让空闲链表头指向头一个缓冲块。
	free_list->b_prev_free = h; 			//链表头的b_prev_free指向前一项(即最后一项)。
	h->b_next_free = free_list; 			//h的下一项指针指向第一项,形成一个环链.

	// 初始化hash表(哈希表,散列数组),置表中所有指针为NULL
	for (i=0;i<NR_HASH;i++)
		hash_table[i]=NULL;
}	

buffer结构

struct buffer_head {
	char * b_data;			/* pointer to data block (1024 bytes) */	// 指向该缓冲块中数据区(1024字节)的指针
	unsigned long b_blocknr;	/* block number */						// 块号
	unsigned short b_dev;		/* device (0 = free) */					// 数据源的设备号(0=free)
	unsigned char b_uptodate;											// 更新标志,表示数据是否已经更新
	unsigned char b_dirt;		/* 0-clean,1-dirty */					// 修改标志: 0-未修改(clean) 1-已修改(dirty)
	unsigned char b_count;		/* users using this block */			// 使用该块的用户数
	unsigned char b_lock;		/* 0 - ok, 1 -locked */					// 缓冲区是否被锁定 0-ok 1-locked
	struct task_struct * b_wait;										// 指向等待该缓冲区解锁的任务
	struct buffer_head * b_prev;										// buffer hash队列的前一块
	struct buffer_head * b_next;										// buffer hash队列的后一块
	struct buffer_head * b_prev_free;									// 空闲列表的前一块
	struct buffer_head * b_next_free;									// 空闲列表的下一块
};


// 		变量end是由编译时的连接程序ld生成,用于表明内核代码的末端,即指明内核模块末端
// 位置。也可以从编译内核时生成的System.map文件中查出。这里用它来表明高速缓冲区开始于内核代码末瑞位置。
// 		buffer_wait变量是等待空闲缓冲块而睡眠的任务队列头指针。它与缓冲块头
// 部结构中b_wait指针的作用不同。当任务申请一个缓冲块而正好遇到系统缺乏可用空闲缓
// 冲块时,当前任务就会被添加到buffer_wait睡眠等待队列中。而b_wait则是专门供等待
// 指定缓冲块(即b_wait对应的缓冲块)的任务使用的等待队列头指针。
extern int end;
struct buffer_head * start_buffer = (struct buffer_head *) &end;
struct buffer_head * hash_table[NR_HASH];
static struct buffer_head * free_list;					//空闲缓冲块链表头指针
static struct task_struct * buffer_wait = NULL;			//等待空闲缓冲块而睡眠的任务队列

在这里插入图片描述

buffer

	|bread,			 breada,		bread_page	|
    |      getblk   			| 	 brelse		|
    | get_hash_table/find_buffer |				|

缓冲块的获取和释放

remove_from_queuesinsert_into_queues操作,hash_table o(1) 访问,free_listo(1) 添加/删除,其中free_list插入是尾插,LRU的思想。

find_bufferget_hash_table 是哈希表中的find和get,其中get_hash_table成功后会让b_count++

getblk

返回值不为NULL时,能确定的是表项肯定在hash表中了,但是有没有dev,block的数据不一定,要看uptodate字段的值。没更新的话需要调缓冲块的读取函数

// 取高速缓冲中指定的缓冲块。
// 检查指定(设备号和块号)的缓冲区是否已经在高速缓冲中。如果指定块已经在高速缓冲中
// 则返回对应缓冲区头指针退出:如果不在,就需要在高速缓中中设置一个对应设备号和块号的
// 新项。返回相应缓冲区头指针。
struct buffer_head * getblk(int dev,int block)
{
	struct buffer_head * tmp, * bh;

repeat:
	// 搜索hash表,如果指定块已经在高速缓冲中,则返回对应缓冲区头指针,退出。
	if (bh = get_hash_table(dev,block))
		return bh;

	// 扫描空闲数据块链表,寻找空闲缓冲区。
	// 首先让tmp指向空闲链表的第一个空闲缓冲区头。
	tmp = free_list;
	do {

		// 如果该缓冲区正被使用(引用计数不等于0),则继续扫描下一项。对于b_count=0的块,
		// 即高速缓冲中当前没有引用的块不一定就是干净的(b_dirt=0)或没有锁定的(b_lock=0)。
		// 因此,我们还是需要继续下面的判断和选择。例如当一个任务改写过一块内容后就释放了,
		// 于是该块b_count=0,但b_lock不等于0:当一个任务执行breada()预读几个块时,只要
		// ll_rw_block()命令发出后,它就会递减b_count:但此时实际上硬盘访问操作可能还在进行,
		// 因此此时b_lock=1,但b_count=0.
		if (tmp->b_count)
			continue;
		
		// 如果缓冲头指针bh为空,或者tmp所指缓冲头的标志(修改、锁定)权重小于bh头标志的权
		// 重,则让bh指向tmp缓冲块头。如果该tmp缓冲块头表明缓冲块既没有修改也没有锁定标
		// 志置位,则说明已为指定设备上的块取得对应的高速缓冲块,则退出循环。否则我们就继续
		// 执行本循环,看看能否找到一个BADNESS最小的缓冲快。
		if (!bh || BADNESS(tmp)<BADNESS(bh)) {
			bh = tmp;
			if (!BADNESS(tmp))
				break;
		}
/* and repeat until we find something good */
	} while ((tmp = tmp->b_next_free) != free_list);

	// 如果循环检查发现所有缓冲块都正在被使用(所有缓冲块的头部引用计数都>0)中,则睡服
	// 等待有空闲缓冲块可用。当有空闲缓冲块可用时本进程会被明确地唤醒。然后我们就跳转到
	// 函数开始处重新查找空闲缓冲块。
	if (!bh) {
		sleep_on(&buffer_wait);
		goto repeat;
	}
	
	// 执行到这里,说明我们已经找到了一个比较适合的空闲缓冲块了。于是先等待该缓冲区解锁
	// (如果已被上锁的话)。如果在我们睡眠阶段该缓冲区又被其他任务使用的话,只好重复上述寻找过程。
	wait_on_buffer(bh);
	if (bh->b_count)
		goto repeat;
	
	// 如果该缓冲区已被修改,则将数据写盘,并再次等待缓冲区解锁。同样地,若该缓冲区又被
	// 其他任务使用的话,只好再重复上述寻找过程。
	while (bh->b_dirt) {
		sync_dev(bh->b_dev);
		wait_on_buffer(bh);
		if (bh->b_count)
			goto repeat;
	}
	
	// 注意!!当进程为了等待该缓冲块而睡眠时,其他进程可能已经将该缓冲块加入进高速缓冲中,所以我们也要对此进行检查。
	// 在高速缓冲ash表中检查指定设备和块的缓冲块是否乘我们睡眠之即已经被加入进去。如果是的话,就再次重复上述寻找过程。
	if (find_buffer(dev,block))
		goto repeat;

	// 该缓冲块是指定参数的唯一一块,而且目前还没有被占用
	// (b_count=0),也未被上锁(b_lock=0),并且是干净的(b_dirt=0)
	// 占用此缓冲块。置引用计数为1,复位修改标志和有效(更新)标志。
	bh->b_count=1;
	bh->b_dirt=0;
	bh->b_uptodate=0;

	// 从hash队列和空闲块链表中移出该缓冲区头,让该缓冲区用于指定设备和其上的指定块.
	// 然后根据此新的设备号和块号重新插入空闲链表和hash队列新位置处。并最终返回缓冲头指针。
	remove_from_queues(bh);
	bh->b_dev=dev;
	bh->b_blocknr=block;
	insert_into_queues(bh); //LRU
	return bh;
}

brelse

// 释放指定缓冲块
// 等待该缓冲块解锁。引用计数-1,并明确的唤醒等待空闲缓冲块的进程
void brelse(struct buffer_head * buf)
{
	if (!buf)
		return;
	wait_on_buffer(buf);
	if (!(buf->b_count--))
		panic("Trying to free free buffer");
	wake_up(&buffer_wait);
}

缓冲块的读取

bread

b_uptodate,表明数据是否是最新的,只要bread返回的数据不是NULL,那它一定是最新的

/*
从设备上读取指定的数据块并返回含有数据的缓冲区。如果指定的块不存在
则返回NULL。
 */
// 从设备上读取数据块。
// 该函数根据指定的设备号dev和数据块号block,首先在高速缓冲区中申请一块缓冲块。
// 如果该缓冲块中已经包含有有效的数据就直接返回该缓冲块指针,否则就从设备中读取
// 指定的数据块到该缓冲块中并返回缓冲块指针。
struct buffer_head * bread(int dev,int block)
{
	struct buffer_head * bh;

	// 在高速缓冲区中申请一块缓冲块。如果返回值是NULL,则表示内核出错,停机。然后我们
	// 判断其中是否已有可用数据。如果该缓冲块中数据是有效的(己更新的)可以直接使用,
	// 则返回。
	if (!(bh=getblk(dev,block)))
		panic("bread: getblk returned NULL\n");
	if (bh->b_uptodate)
		return bh;
	
	// 否则我们就调用底层块设备读写ll_rw_block函数,产生读设备块请求。然后等待指定
	// 数据块被读入,并等待缓冲区解锁。在睡眠醒来之后,如果该缓冲区已更新,则返回缓冲
	// 区头指针,退出。否则表明读设备操作失败,于是释放该缓冲区,返回NULL,退出。
	ll_rw_block(READ,bh);
	wait_on_buffer(bh);
	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	return NULL;
}

bread_page

把dev的blocks数据通过buffer读到address物理地址处
1 缓冲区中先确保有数据,没有就去读
2 把buffer中的数据拷贝到物理地址处,释放buffer,因为已经放入了进程自己的物理地址中备份,buffer可以释放了
在这里插入图片描述

// bread_page一次读四个缓冲块数据读到内存指定的地址处。它是一个完整的函数
// 因为同时读取四块可以获得速度上的好处,不用等着读一块,再读一块了。
// 读设备上一个页面(4个缓冲块)的内容到指定内存地址处:
// 参数address是保存页面数据的地址:dev是指定的设备号:b[4]是含有4个设备数据块号
// 的数组。该函数仅用于mm/memory.c文件的do_no_page函数中(第386行)。
void bread_page(unsigned long address,int dev,int b[4])
{
	struct buffer_head * bh[4];
	int i;

	// 该函数循环执行4次,根据放在数组b[]中的4个块号从设备dev中读取一页内容放到指定
	// 内存位置address处。对于参数b[i]给出的有效块号,函数首先从高速缓冲中取指定设备
	// 和块号的缓冲块。如果缓冲块中数据无效(未更新)则产生读设备请求从设备上读取相应数
	// 据块。对于b[i]无效的块号则不用去理它了。因此本函数其实可以根据指定的b[]中的块号
	// 随意读取1一4个数据块。
	for (i=0 ; i<4 ; i++)
		if (b[i]) {
			if (bh[i] = getblk(dev,b[i]))
				if (!bh[i]->b_uptodate)
					ll_rw_block(READ,bh[i]);
		} else
			bh[i] = NULL;

	// 随后将4个缓冲块上的内容顶序复制到指定地址处。在进行复削(使用)缓冲块之前我们
	// 先要睡眠等待缓冲块解锁(若被上锁的话)。另外,因为可能睡眠过了,所以我们还需要
	// 在复制之前再检查一下缓冲块中的数据是否是有效的。复制完后我们还需要释放缓冲块。
	for (i=0 ; i<4 ; i++,address += BLOCK_SIZE)
		if (bh[i]) {
			wait_on_buffer(bh[i]);
			if (bh[i]->b_uptodate)
				COPYBLK( (unsigned long)bh[i]->b_data, address );
			brelse(bh[i]);
		}
}

breada

传入的第一块数据肯定是要读的,预读随后的数据块,只需读进高速缓冲区但并不是马上就使用,tmp->b_count–,目的是发起尽量少的IO。

// 从指定设备读取指定的一些块。函数参数个数可变,是一系列指定的块号。成功时返回第1块的缓冲块头指针,否则返回NULL
struct buffer_head * breada(int dev,int first, ...)
{
	va_list args;
	struct buffer_head * bh, *tmp;

	// 首先取可变参数表中第1个参数(块号)。接着从高速缓冲区中取指定设备和块号的缓冲
	// 块。如果该缓冲块数据无效(更新标志未置位),则发出读设备数据块请求。
	va_start(args,first);
	if (!(bh=getblk(dev,first)))
		panic("bread: getblk returned NULL\n");
	if (!bh->b_uptodate)
		ll_rw_block(READ,bh);
	
	// 然后顺序取可变参数表中其他预读块号,并作与上面同样处理,但不引用。注意,483行上
	// 有一个bug。其中的bh应该是tmp。这个bug直到在0.96版的内核代码中才被纠正过来。
	// 因为这里是预读随后的数据块,只需读进高速缓冲区但并不是马上就使用,所以第484行语
	// 句需要将其引用计数递减释放掉该块(因为getblk函数会增加引用计数值)。
	while ((first=va_arg(args,int))>=0) {
		tmp=getblk(dev,first);
		if (tmp) {
			if (!tmp->b_uptodate)
				ll_rw_block(READA,bh);
			tmp->b_count--;					//暂时释放该预读块
		}
	}

	// 此时可变参数表中所有参数处理完毕。于是等待第1个缓冲区解锁(如果已被上锁)。在等
	// 待退出之后如果缓冲区中数据仍然有效,则返回缓冲区头指针退出。否则释放该缓冲区返回NULL,退出。
	va_end(args);
	wait_on_buffer(bh);
	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	return (NULL);
}

总结

  • buffer的目的,设计的思想,减少IO
  • buffer的管理,hash表+双链表,淘汰策略LRU
  • buffer中uptodatedirt来控制是否发起IO读写,lock(只在lock_buffer中上锁,做保护),count有无引用作为分配空闲buffer的依据

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

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

相关文章

TryHackMe-Debug(ez php反序列化)

Debug Linux机器CTF&#xff01;您将了解枚举&#xff0c;查找隐藏的密码文件以及如何利用php反序列化&#xff01; 端口扫描 循例&#xff0c;nmap Web枚举 进到web是apache默认页面&#xff0c;直接开扫 由于题目告诉我们涉及php反序列化&#xff0c;那直接找php文件来看&…

Linux学习第十四节-shell脚本

1.Shell概述 Shell连接了用户和Linux内核&#xff0c;他可以解释用户输入的命令传输给内核&#xff0c;让用户可以更加方便的使用Linux系统&#xff1b; Shell本身并不是内核的一部分&#xff0c;他只是站在内核的基础上编写一个应用程序&#xff1b; Shell具备编程的能力&a…

C++回顾(八)—— 继承

8.1 继承的概念 继承是类与类之间的关系&#xff0c;是一个很简单很直观的概念&#xff0c;与现实世界中的继承类似&#xff0c;例如儿子继承父亲的财产。 继承&#xff08;Inheritance&#xff09;可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类…

jconsole远程linux下的tomcat

修改Tomcat的配置 进去 Tomcat 安装目录下的 bin 目录&#xff0c;编辑 catalina.sh vi catalina.sh定位到 ----- Execute The Requested Command ----------------------------------------- vi 编辑模式下&#xff0c;点击 Esc&#xff0c;输入 / &#xff0c;然后粘贴 -…

什么是Netty

一&#xff0e;Netty介绍 1.什么是netty Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架&#xff0c;用以快速开发高性能、高可靠性的网络 IO 程序,是目前最流行的 NIO 框架&#xff0c;Netty 在互联网领域、大数据分布式计算…

自动驾驶介绍系列 ———— 看门狗

文章目录硬件看门狗软件看门狗差异分析延申窗口看门狗硬件看门狗 硬件看门狗的本质上是一个定时器电路。通常存在一个输入&#xff0c;输入到MCU的RST端。在正常工作状态下&#xff0c;MCU每隔固定时间间隔会输出一个信号给RST端&#xff0c;实现对看门狗端清零。如果在指定的时…

RK3568驱动OV13850摄像头模组调试过程

摄像头介绍品牌&#xff1a;Omnivision型号&#xff1a;CMK-OV13850接口&#xff1a;MIPI像素&#xff1a;1320WOV13850彩色图像传感器是一款低电压、高性能1/3.06英寸1320万像素CMOS图像传感器&#xff0c;使用OmniBSI?技术提供了单-1320万像素&#xff08;42243136)摄像头的…

C++20 协程体验

1 介绍协程是比线程更加轻量级并发编程方式&#xff0c;CPU资源在用户态进行切换,CPU切换信息在用户态保存。协程完成异步的调用流程&#xff0c;并对用户展示出同步的使用方式。协程的调度由应用层决定&#xff0c;所以不同的实现会有不同的调度方式&#xff0c;调度策略比较灵…

麻雀算法SSA优化LSTM长短期记忆网络实现分类算法

1、摘要 本文主要讲解&#xff1a;麻雀算法SSA优化LSTM长短期记忆网络实现分类算法 主要思路&#xff1a; 准备一份分类数据&#xff0c;数据介绍在第二章准备好麻雀算法SSA&#xff0c;要用随机数据跑起来用lstm把分类数据跑起来将lstm的超参数交给SSA去优化优化完的最优参数…

Python可变对象与不可变对象的浅拷贝与深拷贝

前言 本文主要介绍了python中容易面临的考试点和犯错点&#xff0c;即浅拷贝与深拷贝 首先&#xff0c;针对Python中的可变对象来说&#xff0c;例如列表&#xff0c;我们可以通过以下方式进行浅拷贝和深拷贝操作&#xff1a; import copya [1, 2, 3, 4, [a, b]]b a …

小众实用!5款不为人知的Windows软件,让你工作更轻松

分享5款冷门但值得下载的Windows软件&#xff0c;个个都是实用&#xff0c;你可能一个都没见过&#xff0c;但是 我觉得你用过之后可能就再也离不开了。 1.键盘可视化——Keyviz Keyviz是一款免费开源的小工具&#xff0c;它的作用是可以实时展示键盘的操作&#xff0c;就可以…

编程语言分类

目录 ❤ 机器语言 机器语言的编程 ❤ 汇编语言 ❤ 高级语言(编程语言) 编译型 解释型 ❤ 动态语言和静态语言 ❤ 强类型定义语言和弱类型定义语言 ❤ 主流语言介绍 C语言 C java python JavaScript SQL PHP python从小白到总裁完整教程目录:https://blog…

浅入浅出keepalived+mysql实现高可用双机热备

当数据库发生宕机的情况&#xff0c;如果配置了数据库主从同步模式或主主同步模式&#xff0c;则可以从从库中获取数据。 当数据库发生宕机的情况&#xff0c;要求应用系统实现高可用&#xff0c;应用系统不会受到影响&#xff0c;需要对mysql进行双机热备实现数据库的高可用。…

断点调试(debug)

目录 F8案例 ​编辑 debug过程中报错 ​编辑用debug查看方法源码 一层一层查看 Arrays.sort()方法 F9 DebugExercise 介绍&#xff1a;断点调试是指在程序的某一行设置一个断电&#xff0c;调试时&#xff0c;程序运行到这一行就会停住&#xff0c;然后可以一步步往下调试…

微服务引擎 MSE 企业版全新升级

作者&#xff1a;流士 随着企业应用大规模云上迁徙与应用微服务化步伐加快&#xff0c;微服务治理的重要性对企业不言而喻&#xff0c;但微服务治理本身的规范化与标准化尚未形成&#xff0c;导致很多企业在微服务治理方面正经历着痛苦的试错期&#xff0c;甚至难以满足线上环境…

工作日志day03

同时构建静态和动态库 //如果用这种方式&#xff0c;只会构建一个动态库&#xff0c;虽然静态库的后缀是.a ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) //修改静态库的名字&#xff0c;这样是可以的&#xff0c;但是我们往往希望他…

RK3568平台开发系列讲解(显示篇)DRM的atomic接口

🚀返回专栏总目录 文章目录 一、Property二、Standard Properties三、代码案例沉淀、分享、成长,让自己和他人都能有所收获!😄 📢目前DRM主要推荐使用的是 Atomic(原子的) 接口。 一、Property Property(属性)—– Atomic操作必须依赖的基本元素 Property把前面的…

【教学典型案例】28.生产环境nginx限制上传大小

目录一&#xff1a;背景介绍二&#xff1a;Nginx限制上传大小1、Nginx官方文档说明2、设置参数1&#xff09;、在server模块中设置2&#xff09;、在http模块中设置三&#xff1a;问题分析过程四&#xff1a;总结一&#xff1a;背景介绍 二&#xff1a;Nginx限制上传大小 1、N…

X264简介-Android使用(二)

X264简介-Android使用&#xff08;二&#xff09; 4、Ubuntu上安装ffmpeg&#xff1a; 检查更新本地软件包&#xff08;如果未更新&#xff0c;reboot Vmware&#xff09;&#xff1a; sudo apt update sudo apt upgrade官网下载的source文件安装&#xff1a; http://ffmpe…

基于JSP的网上书城

技术&#xff1a;Java、JSP等摘要&#xff1a;随着科技的迅速发展&#xff0c;计算机技术已应用到社会的各个领域。随着计算机技术和通信技术的迅速发展&#xff0c;网络的规模也逐渐增大&#xff0c;网络的元素也随之不断增加&#xff0c;有的利用其通信&#xff0c;有的利用其…