【调试】pstore原理和使用方法总结

什么是pstore

pstore最初是用于系统发生oops或panic时,自动保存内核log buffer中的日志。不过在当前内核版本中,其已经支持了更多的功能,如保存console日志、ftrace消息和用户空间日志。同时,它还支持将这些消息保存在不同的存储设备中,如内存、块设备或mtd设备。 为了提高灵活性和可扩展性,pstore将以上功能分别抽象为前端和后端,其中像dmesg、console等为pstore提供数据的模块称为前端,而内存设备、块设备等用于存储数据的模块称为后端,pstore core则分别为它们提供相关的注册接口。

通过模块化的设计,实现了前端和后端的解耦,因此若某些模块需要利用pstore保存信息,就可以方便地向pstore添加新的前端。而若需要将pstore数据保存到新的存储设备上,也可以通过向其添加后端设备的方式完成。

除此之外,pstore还设计了一套pstore文件系统,用于查询和操作上一次重启时已经保存的pstore数据。当该文件系统被挂载时,保存在backend中的数据将被读取到pstore fs中,并以文件的形式显示。

pstore工作原理

pstore 源文件主要有以下几个:fs/pstore/ram_core.c

fs/pstore/
├── ftrace.c		# ftrace 前端的实现
├── inode.c		# pstore 文件系统的注册与操作
├── internal.h
├── Kconfig
├── Makefile
├── platform.c		# pstore 前后端功能的核心
├── pmsg.c		# pmsg 前端的实现
├── ram.c			# pstore/ram 后端的实现,dram空间分配与管理
├── ram_core.c		# pstore/ram 后端的实现,dram的读写操作

文件创建

pstore文件系统位置在:

# ls /sys/fs/pstore
console-ramoops-0 dmesg-ramoops-0

控制台日志位于 pstore 目录下的console-ramoops文件中,因为采用console机制,该文件中的日志信息也受printk level控制,并不一定是全的。

oops/panic日志位于 pstore 目录下的dmesg-ramoops-x文件中,根据缓冲区大小可以有多个文件,x从0开始。
函数调用序列日志位于 pstore 目录下的ftrace-ramoops文件中。

相关代码在inode.c pstore_mkfile里:

/** Make a regular file in the root directory of our file system.* Load it up with "size" bytes of data from "buf".* Set the mtime & ctime to the date that this record was originally stored.*/
int pstore_mkfile(enum pstore_type_id type, char *psname, u64 id, int count,char *data, bool compressed, size_t size,struct timespec time, struct pstore_info *psi)
{
........................rc = -ENOMEM;inode = pstore_get_inode(pstore_sb);
..............................switch (type) {case PSTORE_TYPE_DMESG:scnprintf(name, sizeof(name), "dmesg-%s-%lld%s",psname, id, compressed ? ".enc.z" : "");break;case PSTORE_TYPE_CONSOLE:scnprintf(name, sizeof(name), "console-%s-%lld", psname, id);break;case PSTORE_TYPE_FTRACE:scnprintf(name, sizeof(name), "ftrace-%s-%lld", psname, id);break;case PSTORE_TYPE_MCE:scnprintf(name, sizeof(name), "mce-%s-%lld", psname, id);break;case PSTORE_TYPE_PPC_RTAS:scnprintf(name, sizeof(name), "rtas-%s-%lld", psname, id);break;case PSTORE_TYPE_PPC_OF:scnprintf(name, sizeof(name), "powerpc-ofw-%s-%lld",psname, id);break;case PSTORE_TYPE_PPC_COMMON:scnprintf(name, sizeof(name), "powerpc-common-%s-%lld",psname, id);break;case PSTORE_TYPE_PMSG:scnprintf(name, sizeof(name), "pmsg-%s-%lld", psname, id);break;case PSTORE_TYPE_PPC_OPAL:sprintf(name, "powerpc-opal-%s-%lld", psname, id);break;case PSTORE_TYPE_UNKNOWN:scnprintf(name, sizeof(name), "unknown-%s-%lld", psname, id);break;default:scnprintf(name, sizeof(name), "type%d-%s-%lld",type, psname, id);break;}
....................dentry = d_alloc_name(root, name);
.......................d_add(dentry, inode);
................
}

pstore_mkfile根据不同的type,使用snprintf函数生成文件名name。生成的文件名格式为<type>-<psname>-<id>,其中typeenum pstore_type_id类型的一个值,psname是给定的psname参数,id是给定的id参数。

接着使用d_alloc_name函数为根目录创建一个目录项dentry,最后使用d_add函数将目录项dentry与索引节点inode关联起来,将其添加到文件系统中。

pstore_register

ramoops负责把message write到某个ram区域上,platform负责从ram读取存到/sys/fs/pstore,ok,先来看机制代码platform.c

backend需要用pstore_register来注册:

/** platform specific persistent storage driver registers with* us here. If pstore is already mounted, call the platform* read function right away to populate the file system. If not* then the pstore mount code will call us later to fill out* the file system.*/
int pstore_register(struct pstore_info *psi)
{struct module *owner = psi->owner;if (backend && strcmp(backend, psi->name))return -EPERM;spin_lock(&pstore_lock);if (psinfo) {spin_unlock(&pstore_lock);return -EBUSY;}if (!psi->write)psi->write = pstore_write_compat;if (!psi->write_buf_user)psi->write_buf_user = pstore_write_buf_user_compat;psinfo = psi;mutex_init(&psinfo->read_mutex);spin_unlock(&pstore_lock);.../** Update the module parameter backend, so it is visible* through /sys/module/pstore/parameters/backend*/backend = psi->name;module_put(owner);

backend判断确保一次只能有一个并记录了全局psinfo

看下结构体pstore_info:

struct pstore_info {struct module    *owner;char        *name;spinlock_t    buf_lock;    /* serialize access to 'buf' */char        *buf;size_t        bufsize;struct mutex    read_mutex;    /* serialize open/read/close */int        flags;int        (*open)(struct pstore_info *psi);int        (*close)(struct pstore_info *psi);ssize_t        (*read)(u64 *id, enum pstore_type_id *type,int *count, struct timespec *time, char **buf,bool *compressed, ssize_t *ecc_notice_size,struct pstore_info *psi);int        (*write)(enum pstore_type_id type,enum kmsg_dump_reason reason, u64 *id,unsigned int part, int count, bool compressed,size_t size, struct pstore_info *psi);int        (*write_buf)(enum pstore_type_id type,enum kmsg_dump_reason reason, u64 *id,unsigned int part, const char *buf, bool compressed,size_t size, struct pstore_info *psi);int        (*write_buf_user)(enum pstore_type_id type,enum kmsg_dump_reason reason, u64 *id,unsigned int part, const char __user *buf,bool compressed, size_t size, struct pstore_info *psi);int        (*erase)(enum pstore_type_id type, u64 id,int count, struct timespec time,struct pstore_info *psi);void        *data;
};

name就是backend的name了。

*write*write_buf_user如果backend没有给出会有个默认compat func,最终都走的*write_buf

if (!psi->write)psi->write = pstore_write_compat;if (!psi->write_buf_user)psi->write_buf_user = pstore_write_buf_user_compat;
static int pstore_write_compat(enum pstore_type_id type,enum kmsg_dump_reason reason,u64 *id, unsigned int part, int count,bool compressed, size_t size,struct pstore_info *psi)
{return psi->write_buf(type, reason, id, part, psinfo->buf, compressed,size, psi);
}static int pstore_write_buf_user_compat(enum pstore_type_id type,enum kmsg_dump_reason reason,u64 *id, unsigned int part,const char __user *buf,bool compressed, size_t size,struct pstore_info *psi)
{
...ret = psi->write_buf(type, reason, id, part, psinfo->buf,
...
}

继续pstore注册:

 if (pstore_is_mounted())pstore_get_records(0);

如果pstore已经mounted,那就创建并填充文件by pstore_get_records:

/** Read all the records from the persistent store. Create* files in our filesystem.  Don't warn about -EEXIST errors* when we are re-scanning the backing store looking to add new* error records.*/
void pstore_get_records(int quiet)
{struct pstore_info *psi = psinfo; //tj: global psinfo...mutex_lock(&psi->read_mutex);if (psi->open && psi->open(psi))goto out;while ((size = psi->read(&id, &type, &count, &time, &buf, &compressed,&ecc_notice_size, psi)) > 0) {if (compressed && (type == PSTORE_TYPE_DMESG)) {if (big_oops_buf)unzipped_len = pstore_decompress(buf,big_oops_buf, size,big_oops_buf_sz);if (unzipped_len > 0) {if (ecc_notice_size)memcpy(big_oops_buf + unzipped_len,buf + size, ecc_notice_size);kfree(buf);buf = big_oops_buf;size = unzipped_len;compressed = false;} else {pr_err("decompression failed;returned %d\n",unzipped_len);compressed = true;}}rc = pstore_mkfile(type, psi->name, id, count, buf,compressed, size + ecc_notice_size,time, psi);if (unzipped_len < 0) {/* Free buffer other than big oops */kfree(buf);buf = NULL;} elseunzipped_len = -1;if (rc && (rc != -EEXIST || !quiet))failed++;}if (psi->close)psi->close(psi);
out:mutex_unlock(&psi->read_mutex);    

if needed,call pstore_decompress解压然后创建pstore文件by vfs接口pstore_mkfile

pstore注册接下来是按类别分别注册:

    if (psi->flags & PSTORE_FLAGS_DMESG)pstore_register_kmsg();if (psi->flags & PSTORE_FLAGS_CONSOLE)pstore_register_console();if (psi->flags & PSTORE_FLAGS_FTRACE)pstore_register_ftrace();if (psi->flags & PSTORE_FLAGS_PMSG)pstore_register_pmsg();

psi->flags仍是由backend决定,只看pstore_register_kmsgpstore_register_console

pstore panic log注册

static struct kmsg_dumper pstore_dumper = {.dump = pstore_dump,
};/** Register with kmsg_dump to save last part of console log on panic.*/
static void pstore_register_kmsg(void)
{kmsg_dump_register(&pstore_dumper);
}

pstore_dump最终会call backend的write,直接用全局psinfo。

/** callback from kmsg_dump. (s2,l2) has the most recently* written bytes, older bytes are in (s1,l1). Save as much* as we can from the end of the buffer.*/
static void pstore_dump(struct kmsg_dumper *dumper,enum kmsg_dump_reason reason)
{...ret = psinfo->write(PSTORE_TYPE_DMESG, reason, &id, part,oopscount, compressed, total_len, psinfo);

kmsg_dump_register是内核一种增加log dumper方法,called when kernel oopses or panic。

/*** kmsg_dump_register - register a kernel log dumper.* @dumper: pointer to the kmsg_dumper structure** Adds a kernel log dumper to the system. The dump callback in the* structure will be called when the kernel oopses or panics and must be* set. Returns zero on success and %-EINVAL or %-EBUSY otherwise.*/
int kmsg_dump_register(struct kmsg_dumper *dumper)
{unsigned long flags;int err = -EBUSY;/* The dump callback needs to be set */if (!dumper->dump)return -EINVAL;spin_lock_irqsave(&dump_list_lock, flags);/* Don't allow registering multiple times */if (!dumper->registered) {dumper->registered = 1;list_add_tail_rcu(&dumper->list, &dump_list);err = 0;}spin_unlock_irqrestore(&dump_list_lock, flags);return err;
}
/*** kmsg_dump - dump kernel log to kernel message dumpers.* @reason: the reason (oops, panic etc) for dumping** Call each of the registered dumper's dump() callback, which can* retrieve the kmsg records with kmsg_dump_get_line() or* kmsg_dump_get_buffer().*/
void kmsg_dump(enum kmsg_dump_reason reason)
{struct kmsg_dumper *dumper;unsigned long flags;if ((reason > KMSG_DUMP_OOPS) && !always_kmsg_dump)return;rcu_read_lock();list_for_each_entry_rcu(dumper, &dump_list, list) {if (dumper->max_reason && reason > dumper->max_reason)continue;/* initialize iterator with data about the stored records */dumper->active = true;raw_spin_lock_irqsave(&logbuf_lock, flags);dumper->cur_seq = clear_seq;dumper->cur_idx = clear_idx;dumper->next_seq = log_next_seq;dumper->next_idx = log_next_idx;raw_spin_unlock_irqrestore(&logbuf_lock, flags);/* invoke dumper which will iterate over records */dumper->dump(dumper, reason);/* reset iterator */dumper->active = false;}rcu_read_unlock();
}

pstore console 注册

static struct console pstore_console = {.name    = "pstore",.write    = pstore_console_write,.flags    = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME,.index    = -1,
};static void pstore_register_console(void)
{register_console(&pstore_console);
}

->write最终也会call backend write:

#ifdef CONFIG_PSTORE_CONSOLE
static void pstore_console_write(struct console *con, const char *s, unsigned c)
{const char *e = s + c;while (s < e) {unsigned long flags;u64 id;if (c > psinfo->bufsize)c = psinfo->bufsize;if (oops_in_progress) {if (!spin_trylock_irqsave(&psinfo->buf_lock, flags))break;} else {spin_lock_irqsave(&psinfo->buf_lock, flags);}memcpy(psinfo->buf, s, c);psinfo->write(PSTORE_TYPE_CONSOLE, 0, &id, 0, 0, 0, c, psinfo);  // tj: herespin_unlock_irqrestore(&psinfo->buf_lock, flags);s += c;c = e - s;}
}

ramoops

下面来看下RAM backend: ramoops,先看probe:

static int ramoops_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;struct ramoops_platform_data *pdata = dev->platform_data;...if (!pdata->mem_size || (!pdata->record_size && !pdata->console_size &&!pdata->ftrace_size && !pdata->pmsg_size)) {pr_err("The memory size and the record/console size must be ""non-zero\n");goto fail_out;}...cxt->size = pdata->mem_size;cxt->phys_addr = pdata->mem_address;cxt->memtype = pdata->mem_type;cxt->record_size = pdata->record_size;cxt->console_size = pdata->console_size;cxt->ftrace_size = pdata->ftrace_size;cxt->pmsg_size = pdata->pmsg_size;cxt->dump_oops = pdata->dump_oops;cxt->ecc_info = pdata->ecc_info;

pdata应该来源ramoops_register_dummy:

static void ramoops_register_dummy(void)
{...pr_info("using module parameters\n");dummy_data = kzalloc(sizeof(*dummy_data), GFP_KERNEL);if (!dummy_data) {pr_info("could not allocate pdata\n");return;}dummy_data->mem_size = mem_size;dummy_data->mem_address = mem_address;dummy_data->mem_type = mem_type;dummy_data->record_size = record_size;dummy_data->console_size = ramoops_console_size;dummy_data->ftrace_size = ramoops_ftrace_size;dummy_data->pmsg_size = ramoops_pmsg_size;dummy_data->dump_oops = dump_oops;/** For backwards compatibility ramoops.ecc=1 means 16 bytes ECC* (using 1 byte for ECC isn't much of use anyway).*/dummy_data->ecc_info.ecc_size = ramoops_ecc == 1 ? 16 : ramoops_ecc;dummy = platform_device_register_data(NULL, "ramoops", -1,dummy_data, sizeof(struct ramoops_platform_data));

有几个可配参数:

/** Ramoops platform data* @mem_size    memory size for ramoops* @mem_address    physical memory address to contain ramoops*/struct ramoops_platform_data {unsigned long    mem_size;  phys_addr_t    mem_address; unsigned int    mem_type;unsigned long    record_size;unsigned long    console_size;unsigned long    ftrace_size;unsigned long    pmsg_size;int        dump_oops;struct persistent_ram_ecc_info ecc_info;
};
  • mem_size:用于Ramoops的内存大小,表示分配给Ramoops的物理内存的大小。
  • mem_address:用于Ramoops的物理内存地址,指定用于存储Ramoops的物理内存的起始地址。
  • mem_type:内存类型,用于进一步描述内存的属性和特征。
  • record_size:每个记录的大小
  • console_size:控制台记录的大小
  • ftrace_size:Ftrace记录的大小
  • pmsg_size:pmsg消息记录的大小
  • dump_oops:是否转储oops信息的标志,表示是否将oops信息转储到Ramoops中。
  • ecc_info:RAM的ECC(纠错码)信息,用于提供关于ECC配置和处理的详细信息。

有个结构表示了ramoops的context:

struct ramoops_context {struct persistent_ram_zone **przs;struct persistent_ram_zone *cprz;struct persistent_ram_zone *fprz;struct persistent_ram_zone *mprz;phys_addr_t phys_addr;unsigned long size;unsigned int memtype;size_t record_size;size_t console_size;size_t ftrace_size;size_t pmsg_size;int dump_oops;struct persistent_ram_ecc_info ecc_info;unsigned int max_dump_cnt;unsigned int dump_write_cnt;/* _read_cnt need clear on ramoops_pstore_open */unsigned int dump_read_cnt;unsigned int console_read_cnt;unsigned int ftrace_read_cnt;unsigned int pmsg_read_cnt;struct pstore_info pstore;
};

ramoops_probe时也是把ramoops_platform_data的成员赋给了context对应的。要了解具体含义,继续probe:

    paddr = cxt->phys_addr;dump_mem_sz = cxt->size - cxt->console_size - cxt->ftrace_size- cxt->pmsg_size;err = ramoops_init_przs(dev, cxt, &paddr, dump_mem_sz);if (err)goto fail_out;err = ramoops_init_prz(dev, cxt, &cxt->cprz, &paddr,cxt->console_size, 0);if (err)goto fail_init_cprz;err = ramoops_init_prz(dev, cxt, &cxt->fprz, &paddr, cxt->ftrace_size,LINUX_VERSION_CODE);if (err)goto fail_init_fprz;err = ramoops_init_prz(dev, cxt, &cxt->mprz, &paddr, cxt->pmsg_size, 0);if (err)goto fail_init_mprz;cxt->pstore.data = cxt;

可见,是逐个init每个persistant ram zone,size一共有4段:

dump_mem_sz + cxt->console_size + cxt->ftrace_size + cxt->pmsg_size = cxt->size

mem_size就是总大小了,mem_address是ramoops的物理地址,record_size再看下oops/panic ram:

static int ramoops_init_przs(struct device *dev, struct ramoops_context *cxt,phys_addr_t *paddr, size_t dump_mem_sz)
{int err = -ENOMEM;int i;if (!cxt->record_size)return 0;if (*paddr + dump_mem_sz - cxt->phys_addr > cxt->size) {dev_err(dev, "no room for dumps\n");return -ENOMEM;}cxt->max_dump_cnt = dump_mem_sz / cxt->record_size;if (!cxt->max_dump_cnt)return -ENOMEM;

ok dump_mem_size大小的区域分成max_dump_cnt个,每个记录大小是record_size

接着会call persistent_ram_new来分配内存给这个ram zone。

    for (i = 0; i < cxt->max_dump_cnt; i++) {cxt->przs[i] = persistent_ram_new(*paddr, cxt->record_size, 0,&cxt->ecc_info,cxt->memtype, 0);

console/ftrace/pmsg ram zone同上分配。

最后处理flags并注册pstore:

    cxt->pstore.flags = PSTORE_FLAGS_DMESG; //tj: 默认dump oops/panicif (cxt->console_size)cxt->pstore.flags |= PSTORE_FLAGS_CONSOLE;if (cxt->ftrace_size)cxt->pstore.flags |= PSTORE_FLAGS_FTRACE;if (cxt->pmsg_size)cxt->pstore.flags |= PSTORE_FLAGS_PMSG;err = pstore_register(&cxt->pstore);if (err) {pr_err("registering with pstore failed\n");goto fail_buf;}

来看下ramoops pstore的定义的callback,他们通过全局psinfo而来:

static struct ramoops_context oops_cxt = {.pstore = {.owner    = THIS_MODULE,.name    = "ramoops",.open    = ramoops_pstore_open,.read    = ramoops_pstore_read, // psi->read.write_buf    = ramoops_pstore_write_buf, //for non pmsg.write_buf_user    = ramoops_pstore_write_buf_user, //for pmsg.erase    = ramoops_pstore_erase,},
};

pstore使用方法

ramoops

配置内核
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_RAM=y
CONFIG_PANIC_TIMEOUT=-1

由于log数据存放于DDR,不能掉电,只能依靠自动重启机制来查看,故而要配置:CONFIG_PANIC_TIMEOUT,让系统在 panic 后能自动重启。

dts
ramoops_mem: ramoops_mem {reg = <0x0 0x110000 0x0 0xf0000>;reg-names = "ramoops_mem";
};ramoops {compatible = "ramoops";record-size = <0x0 0x20000>;console-size = <0x0 0x80000>;ftrace-size = <0x0 0x00000>;pmsg-size = <0x0 0x50000>;memory-region = <&ramoops_mem>;
};

mtdoops

内核配置
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_MTD_OOPS=y
CONFIG_MAGIC_SYSRQ=y
分区配置

cmdline方式:

bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs mtdoops.mtddev=pstore";blkparts = "mtdparts=spi0.0:64k(spl)ro,256k(uboot)ro,64k(dtb)ro,128k(pstore),3m(kernel)ro,4m(rootfs)ro,-(data)";

part of方式:

bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs mtdoops.mtddev=pstore";
partition@60000 {label = "pstore";reg = <0x60000 0x20000>;};

blkoops

配置内核
CONFIG_PSTORE=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_BLK=y
CONFIG_MTD_PSTORE=y
CONFIG_MAGIC_SYSRQ=y
配置分区

cmdline方式:

bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs pstore_blk.blkdev=pstore";blkparts = "mtdparts=spi0.0:64k(spl)ro,256k(uboot)ro,64k(dtb)ro,128k(pstore),3m(kernel)ro,4m(rootfs)ro,-(data)";

part of方式:

bootargs = "console=ttyS1,115200 loglevel=8 rootwait root=/dev/mtdblock5 rootfstype=squashfs pstore_blk.blkdev=pstore";
partition@60000 {label = "pstore";reg = <0x60000 0x20000>;
};

pstore fs

挂载pstore文件系统

mount -t pstore pstore /sys/fs/pstore

挂载后,通过mount能看到类似这样的信息:

# mount
pstore on /sys/fs/pstore type pstore (rw,relatime)

如果需要验证,可以这样主动触发内核崩溃:

# echo c > /proc/sysrq-trigger

不同配置方式日志名称不同

ramoops

# mount -t pstore pstore /sys/fs/pstore/
# cd /sys/fs/pstore/
# ls
console-ramoops-0  dmesg-ramoops-0    dmesg-ramoops-1

mtdoops

# cat /dev/mtd3 > 1.txt
# cat 1.txt

blkoops

cd /sys/fs/pstore/
ls
dmesg-pstore_blk-0  dmesg-pstore_blk-1

总结

pstore setup 流程:

ramoops_init
ramoops_register_dummy
ramoops_probe
ramoops_register

查看 pstore 数据保存流程:

register a pstore_dumper
// when panic happens, kmsg_dump is called
call dumper->dump
pstore_dump

查看 pstore 数据读取流程:

ramoops_probe
persistent_ram_post_init
pstore_register
pstore_get_records
ramoops_pstore_read
pstore_decompress (only for dmesg)
pstore_mkfile (save to files)

本文参考

https://heapdump.cn/article/1961461

https://blog.csdn.net/u013836909/article/details/129894795

https://zhuanlan.zhihu.com/p/545560128

https://docs.kernel.org/admin-guide/pstore-blk.html

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5 简约四色新科技风引导页源码

H5 简约四色新科技风引导页源码 源码介绍&#xff1a;一款四色切换自适应现代科技风动态背景的引导页源码&#xff0c;源码有主站按钮&#xff0c;分站按钮2个&#xff0c;QQ联系站长按钮一个。 下载地址&#xff1a; https://www.changyouzuhao.cn/11990.html

适合龙年春节的SVG模版

宝藏模版 往期推荐&#xff08;点击阅读&#xff09;&#xff1a; 趣味效果&#xff5c;高大上&#xff5c;可爱风&#xff5c;年终总结&#xff08;一&#xff09;&#xff5c;年终总结&#xff08;二&#xff09;&#xff5c;循环特效&#xff5c;情人节&#xff08;一&…

Quartus IP 之mif与hex文件创建与使用

一、mif与hex概述 ROM IP的数据需要满足断电不丢失的要求&#xff0c;ROM IP数据的文件格式一般有三种文件格式&#xff1a;.mif、.hex、.coe&#xff0c;Xilinx与Intel Altera支持的ROM IP数据文件格式如下&#xff1a; Xilinx与Altera支持的ROM文件格式 Alterahex、mifAM&am…

DolphinScheduler本地安装

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

如果品牌刚刚开始,切入私域社群团购,快团团是最好的选择

如果品牌刚刚开始&#xff0c;切入私域社群团购&#xff0c;快团团是最好的选择&#xff0c;借力新渠道社群团购&#xff0c;快团团&#xff0c;成就你的新品牌&#xff0c; 社群团购平台本身就有大量的信任你平台的流量&#xff0c;这个流量基数是巨大的。 你要知道的是&…

惟客数据地产经营分析解决方案-构建数字化经营体系,提高精细化管理能力

惟客数据地产经营分析解决方案以拉通数据底座&#xff0c;以管理行为、量化考核、预警机制为核心&#xff0c;强化对经营风险的识别和解决&#xff0c;以终为始&#xff0c;通过高频高价值场景的应用适配&#xff0c;支撑企业在数字化时代中不断创新、转型&#xff0c;提升企业…

Rhino.Inside带材质将Revit模型bake到Rhino

Hello大家好&#xff01;我是九哥~ 今天来讲一个小技巧&#xff0c;就是我通常采用RIR将Revit的模型的Geometry Bake到Rhino&#xff0c;肯定是没有材质的&#xff0c;那么如果我们需要带材质那要怎么办呢&#xff1f; 对于会的人&#xff0c;其实挺简单的&#xff0c;只需要…

力扣热门100题 - 4.寻找两个正序数组的中位数

力扣热门100题 - 4.寻找两个正序数组的中位数 题目描述&#xff1a;示例&#xff1a;提示&#xff1a;解题思路&#xff1a;代码&#xff1a; 题目链接&#xff1a;4.寻找两个正序数组的中位数 题目描述&#xff1a; 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&a…

用python编写爬虫,爬取房产信息

题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88816284使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件&#xff0c;再run all就行 注意事项 可能遇到的bug 暂无&#xff0c;有的话私信我

Verilog刷题笔记20

题目&#xff1a; Case statements in Verilog are nearly equivalent to a sequence of if-elseif-else that compares one expression to a list of others. Its syntax and functionality differs from the switch statement in C. 解题&#xff1a; module top_module ( …

docker自定义镜像并使用

写在前面 本文看下如何自定义镜像。 ik包从这里 下载。 1&#xff1a;自定义带有ik的es镜像 先看下目录结构&#xff1a; /opt/program/mychinese [rootlocalhost mychinese]# ll total 16 -rw-r--r-- 1 root root 1153 Feb 5 04:18 docker-compose.yaml -rw-rw-r-- 1 el…

C语言中的内存函数你知道多少呢?

目录 ​编辑 1. memcpy的使用和模拟实现 1.1函数介绍 ​编辑 1.2函数的使用 1.3模拟实现 2. memmove的使用和模拟实现 2.1函数介绍 2.2函数的使用 2.3模拟实现 3. memset函数的使用 3.1函数介绍 3.2函数的使用 ​编辑 4. memcmp函数的使用 4.1函数介绍 4.2函数…

压力测试工具-Jmeter使用总结

目录 一.前言 二.线程组 三.线程组的组件 四.线程组-HTTP请求 1、JSON提取器 2、XPATH提取器 3、正则表达式提取器 五.线程组-断言 1、响应断言 2、JSON断言 六.创建测试 1.创建线程组 2.配置元件 3.构造HTTP请求 4.添加HTTP请求头 5.添加断言 6.添加查看结果树…

Python 连接 mysql 详解(mysql-connector-python)

文章目录 1 概述1.1 第三方库&#xff1a;mysql-connector-python1.2 可视化工具&#xff1a;navicat1.3 创建测试数据库 2 连接 mysql 数据库2.1 创建一个连接2.2 捕获连接异常2.3 从配置文件中获取连接信息 3 执行 sql 语句3.1 插入、更新、删除3.2 查询 1 概述 1.1 第三方库…

物流自动化移动机器人|HEGERLS三维智能四向穿梭车助力优化企业供应链

智能化仓库/仓储贯穿于物流的各个环节&#xff0c;不局限于存储、输送、分拣、搬运等单一作业环节的自动化&#xff0c;更多的是利用科技手段实现整个物流供应链流程的自动化与智能化&#xff0c;将传统自动化仓储物流各环节进行多维度的有效融合。 例如在数智化物流仓储的建设…

UE4 C++创建摄像机摇臂和相机并且设置Transform

新建MyPawn C类 .h #include "GameFramework/SpringArmComponent.h" //SpringArm组件 #include "Camera/CameraComponent.h" //Camera组件class 工程名称_API AMyPawn : public APawn { //定义组件变量 public:UPROPERTY(VisibleAnywhere, BlueprintRead…

MATLAB计算多边形质心/矩心

前言&#xff1a;不规则四边形的中心 不规则四边形的出心有多种定义&#xff0c;以下是最常见的三种&#xff1a; 1.重心&#xff1a;重心是四边形内部所有顶点连线交点的平均位置。可以通过求解四个顶点坐标的平均值来找到重心。 2.质心&#xff1a;质心是四边形内部所有质点…

【动态规划】【子数组划分】【前缀和】1977. 划分数字的方案数

作者推荐 【动态规划】【状态压缩】【2次选择】【广度搜索】1494. 并行课程 II 本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode1977 划分数字的方案数 你写下了若干 正整数 &#xff0c;并将它们…

【Linux笔记】文件系统与软硬链接

一、文件系统概述 1.1、先来聊一聊“磁盘” 在讲解文件系统之前&#xff0c;我觉得有必要先聊一下“磁盘”&#xff0c;因为我觉得如果弄懂了磁盘的存储原理&#xff0c;大家可能更容易理解文件系统是怎么管理数据的&#xff0c;并且理解计算机是怎么将磁盘抽象到文件系统的。…

百度拟将量子实验室捐赠予北京量子院 /一汽解放与华为合作,预计2025年底量产自动驾驶产品 |魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 百度捐赠量子实验室推进量子计算研究&#xff0c;助力人工智能发展 一汽解放…