Linux 5.10 Pstore 学习之(二) 原理学习

news2024/11/15 18:55:45

目录

    • 编译框架
    • 模块初始化
      • pstore子系统
        • ramoops模块
          • 初始化
          • 实例化
          • 注册回调
          • 数据结构
        • pstore_blk模块
        • pstore_zone模块
    • 测试
    • 扩展
    • 调试

编译框架

目标结构

linux_5.10/fs/pstore/
├── blk.c
├── ftrace.c
├── inode.c				// 核心1
├── internal.h
├── Kconfig
├── Makefile
├── platform.c			// 核心2,负责:注册分发工作、record写入
├── pmsg.c
├── ram.c				// ram后端1
├── ram_core.c			// ram后端2
└── zone.c

Makefile文件

# pstore文件系统
obj-$(CONFIG_PSTORE) += pstore.o
pstore-objs += inode.o platform.o

# ramoops模块,后端
ramoops-objs += ram.o ram_core.o
obj-$(CONFIG_PSTORE_RAM)	+= ramoops.o

可知:

  • pstore 模块,根据配置CONFIG_PSTORE=y,内嵌pstore

    核心实现文件:inode.o platform.c

  • ramoops 模块,根据配置CONFIG_PSTORE_RAM=m,生成模块ramoops.ko

    实现文件:ram.c、ram_core.c

模块初始化

pstore子系统

先查找模块初始化入口,文件:linux_5.10/fs/pstore/platform.c

late_initcall(pstore_init);				# late_initcall 阶段执行;

static int __init pstore_init(void)
  pstore_choose_compression();		# 选择压缩算法,加载时如果没有指定则跳过
  	if (!compress)					
  		return;

  allocate_buf_for_compression();		# 申请压缩用的缓存,暂不展开

  ret = pstore_init_fs();

// linux_5.10/fs/pstore/inode.c	//
int __init pstore_init_fs(void)
  sysfs_create_mount_point(fs_kobj, "pstore");	# 在sys下创建挂载目录:pstore,即完整路径:/sys/fs/pstore
  register_filesystem(&pstore_fs_type);    		# 注册pstore文件系统
  
static struct file_system_type pstore_fs_type = {
  .owner		= THIS_MODULE,
  .name		= "pstore",
  .mount		= pstore_mount,			# 关注mount实现
  .kill_sb	= pstore_kill_sb,
};

至此,ramoops模块初始化完成,主要完成如下工作:

  1. 选择压缩算法并申请内存;
  2. 注册pstore文件系统;
ramoops模块
初始化

跟读文件:linux_5.10/fs/pstore/ram.c

postcore_initcall(ramoops_init);

static int __init ramoops_init(void)
	struct ramoops_platform_data pdata;

	ramoops_register_dummy();
		if (!mem_size)							// 如果附带参数中没有指定mem_size,退出
			return;
		pr_info("using module parameters\n");	// 打印:
		
		memset(&pdata, 0, sizeof(pdata));		// 使用一个局部变量pdata,临时完成参数配置
		pdata.mem_size    = mem_size;			// 模块参数
		pdata.record_size = record_size;		// 模块参数,默认4KB;
		...										// 其他参数配置
		dummy = platform_device_register_data(NULL, "ramoops", -1, &pdata, sizeof(pdata));	// 作为全局变量dummy的平台数据
	platform_driver_register(&ramoops_driver);	// 平台驱动:ramoops_driver

// 跟进平台驱动:ramoops_driver
//--------------------------------------------------------------------------------------------------
static const struct of_device_id dt_match[] = {
	{ .compatible = "ramoops" },				// 匹配DTS中节点中compatible字段为ramoops的设备
	{}
};

static struct platform_driver ramoops_driver = {
	.probe		= ramoops_probe,
	.remove		= ramoops_remove,
	.driver		= {
		.name			= "ramoops",			// 匹配同名平台设备
		.of_match_table	= dt_match,
	},
    
// 跟进平台驱动:ramoops_driver,probe实现(基于平台总线的设备与驱动匹配测试)
//--------------------------------------------------------------------------------------------------    
static int ramoops_probe(struct platform_device *pdev)    
    。。。
	struct ramoops_context *cxt = &oops_cxt;	// oops能力集
	。。。
	if (cxt->max_dump_cnt) {					// 仅能有一个ramoops实例
		pr_err("already initialized\n");
		goto fail_out;        
	if (dev_of_node(dev) && !pdata) {			// 如果设备来自dts定义,解析节点,暂不展开
		pdata = &pdata_local;    
		。。。
	cxt->size = pdata->mem_size;				// cxt 初始化
	cxt->phys_addr = pdata->mem_address;		// 物理地址
	...        
	cxt->record_size = pdata->record_size;		// 记录大小
	...        									// 其他参数
	paddr = cxt->phys_addr;        
	dump_mem_sz = cxt->size - cxt->console_size - cxt->ftrace_size - cxt->pmsg_size;    // dump文件大小计算
	ramoops_init_przs("dmesg", dev, cxt, &cxt->dprzs, &paddr, dump_mem_sz, cxt->record_size, &cxt->max_dump_cnt, 0, 0);    
		if (mem_sz == 0 || record_size == 0) {	// 如果没有内存剩余或record_size参数值为0,退出
			return 0;            
		if (record_size < 0) {            		// 参数record_size如果为负数:重新计算
			...
		} else {                
			*cnt = mem_sz / record_size;		// 换算出record个数;
			...
		if (*paddr + mem_sz - cxt->phys_addr > cxt->size) {			// 检查mem_sz是否超出总大小,注:paddr = cxt->phys_addr
			...
		zone_sz = mem_sz / *cnt;        
		prz_ar = kcalloc(*cnt, sizeof(**przs), GFP_KERNEL);    
		for (i = 0; i < *cnt; i++) {
			if (*cnt == 1)
				label = kasprintf(GFP_KERNEL, "ramoops:%s", name);	// record 文件名,按顺序:ramoops:dmsg,ramoops:dmsg(1/n),ramoops:dmsg(2/n),...,ramoops:dmsg(n-1/n)
			...
*			prz_ar[i] = persistent_ram_new(*paddr, zone_sz, sig, &cxt->ecc_info, cxt->memtype, flags, label);	// 内存映射,关联擦物理内存            
			*paddr += zone_sz;
			prz_ar[i]->type = pstore_name_to_type(name);            // 名字转换成对应类型
	ramoops_init_prz("console", dev, cxt, &cxt->cprz, &paddr, cxt->console_size, 0);    
	ramoops_init_przs("ftrace", dev, cxt, &cxt->fprzs, &paddr, cxt->ftrace_size, -1, &cxt->max_ftrace_cnt, LINUX_VERSION_CODE, (cxt->flags & RAMOOPS_FLAG_FTRACE_PER_CPU) ? PRZ_FLAG_NO_LOCK : 0);        
	ramoops_init_prz("pmsg", dev, cxt, &cxt->mprz, &paddr, cxt->pmsg_size, 0);        
	cxt->pstore.data = cxt;						// psi私有数据关联cxt
	cxt->pstore.flags = 0;
	if (cxt->max_dump_cnt) {
		cxt->pstore.flags |= PSTORE_FLAGS_DMESG;
	...			// 其他参数
*	pstore_register(&cxt->pstore);       		// pstore 注册,已完善的oops_cxt;
	mem_size = pdata->mem_size;					// 更新模块参数,以便调试查看:/sys/module/ramoops/parameters/
	mem_address = pdata->mem_address;
	... 		// 其他参数
	pr_info("using 0x%lx@0x%llx, ecc: %d\n",	// 初始化完成打印

// 注
//--------------------------------------------------------------------------------------------------
// 全局变量            
static struct platform_device *dummy;			// dummy,平台设备
struct pstore_info *psinfo;						// psinfo,能力集(open、write,...);
static struct ramoops_context oops_cxt = {		// oops_cxt,
	.pstore = {									//     与上面的psinfo 紧密关联;
		.owner	= THIS_MODULE,
		.name	= "ramoops",
		.open	= ramoops_pstore_open,
		.read	= ramoops_pstore_read,
		.write	= ramoops_pstore_write,
		.write_user	= ramoops_pstore_write_user,
		.erase	= ramoops_pstore_erase,
	},
};                

总结ramoops驱动入口及完成的工作:

  1. oops_cxtramoops的基础能力集,其提供了最基本的文件读写方法,实现了数据的读写管理;

  2. postcore_initcall初始化函数,具体实现方法由宏CONFIG_PSTORE_RAM的值决定:

    • y,即ramoops内嵌到内核中,此时实现方法为__define_initcall(fn, 2),属于靠前的初始化阶段;
    • m,即ramoops编译成模块,此时实现方法为module_init
  3. dummy是一个临时使用的平台设备,用以辅助ramoops的初始化。dummy生效有以下几个条件:

    • 模块参数mem_size,不能为0。如果为零,相当于告知ramoops:没有内存可保存dump信息;

    当然,mem_size也是相对于模块参数mem_address而言的。总结就是:已知ramoops初始化至少需要两个参数:mem_address、mem_size

  4. ramoops_driver是一个平台驱动,是ramoops模块的载体。

    需要关注ramoops_driver这个平台驱动的其probe实现,即如何与平台设备匹配的,有以下两种方式:

    • 直接创建的平台设备,如:上面提及的平台设备dummy(使用platform_device_register_data创建平台设备);
    • 基于平台设备模式的DTS中定义的of设备;
  5. ramoops_init_przs,是具体前端的实例化过程实现,实例化为prz(struct persistent_ram_zone)对象;

    该对象的特性在章节《数据结构》中说明,而涉及内存申请、映射的细节在章节《前端实例化》中;

  6. pstore_register,向各前端所属子系统注册pstore回调,各前端的回调所有不同,细节较多,在章节《前端注册》中详细说明,以下列出大致关联:

    • kmesg,所属:printk,回调注册:kmsg_dump_register,回调对象:struct kmsg_dumper,实体:pstore_dumper
    • console,所属:printk,回调注册:register_console,回调对象:struct console,实体:pstore_console
    • 其他,暂不展开;
实例化

persistent_ram_new为入口,跟读ramoops模块如何针对不同前端进制实例化工作,参考文件:linux_5.10/fs/pstore/ram_core.c

struct persistent_ram_zone *persistent_ram_new(phys_addr_t start, size_t size, u32 sig, struct persistent_ram_ecc_info *ecc_info, unsigned int memtype, u32 flags, char *label)
	struct persistent_ram_zone *prz;
	prz = kzalloc(sizeof(struct persistent_ram_zone), GFP_KERNEL);
	...
	persistent_ram_buffer_map(start, size, prz, memtype);	// 映射物理内存
		prz->paddr = start;
		prz->size  = size;            
		if (pfn_valid(start >> PAGE_SHIFT))					// 检查mem_addr所在页帧是否落在DRAM范围内
			prz->vaddr = persistent_ram_vmap(start, size, memtype);					// 在DRAM范围内,使用vmap
				page_start = start - offset_in_page(start);							// 页对齐处理:start、count
				page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);            
				if (memtype)								// 0(默认),pgprot_writecombine,
					prot = pgprot_noncached(PAGE_KERNEL);	// 页属性:无缓存
				else
					prot = pgprot_writecombine(PAGE_KERNEL);
				pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);	// kmalloc申请多个库     
				for (i = 0; i < page_count; i++) {
					phys_addr_t addr = page_start + i * PAGE_SIZE;
					pages[i] = pfn_to_page(addr >> PAGE_SHIFT);
				vaddr = vmap(pages, page_count, VM_MAP, prot);	// 虚拟地址:从物理页帧映射到虚拟内存
				kfree(pages);
				return vaddr + offset_in_page(start);		// 返回映射后的虚拟地址
		else
			prz->vaddr = persistent_ram_iomap(start, size, memtype, prz->label);	// 在DRAM范围外,使用iomap
				request_mem_region(start, size, label ?: "ramoops")if (memtype)
					va = ioremap(start, size);
				else
					va = ioremap_wc(start, size);            
				return va; 
		...                    
		prz->buffer      = prz->vaddr;						// buffer信息保存
		prz->buffer_size = size - sizeof(struct persistent_ram_buffer);                    
	persistent_ram_post_init(prz, sig, ecc_info);			// 内存引导初始化
		persistent_ram_init_ecc(prz, ecc_info);				// ECC初始化
		sig ^= PERSISTENT_RAM_SIG;                    
		if (prz->buffer->sig == sig) {
			if (buffer_size(prz) == 0) {                    
				pr_debug("found existing empty buffer\n");
				return 0;                
                
	pr_debug("attached %s 0x%zx@0x%llx: %zu header, %zu data, %zu ecc (%d/%d)\n",	// 打印(Debug等级):内存信息
	return prz;												// 返回prz;             

总结ramoops实例化的核心工作是完成一个或多个prz的创建,其中过程涉及的工作:

  1. prz实例化,prz只是个门面,承载的是ram、blk或mtd,面对的是ramoops

  2. prz内存管理,依据内存范围、属性,申请与映射方式有所不同:

    • 内存申请,依据类型内存可分为DRAM寄存器,两者的区别如下:

      • DRAM,使用kmalloc_array申请物理页;
      • 寄存器,使用request_mem_region声明资源使用;

      另外,依据模块参数mem_type与平台是否支持,在申请页时会附带属性:noncachedwritecombine

    • 内存映射,依据类型内存可分为DRAM寄存器,两者的区别如下:

      • DRAM,使用vmap映射,物理内存的映射;
      • 寄存器,使用iomap映射,I/O内存的直接映射;

    总结prz内存管理策略:

    • 物理内存,使用kmalloc_array + vmap,最终得到虚拟地址;

    • I/O内存,使用request_mem_region + iomap,最终得到虚拟地址;

  3. prz读写管理,使用persistent_ram_buffer完成数据的环形覆盖读写;

注册回调

pstore_register为入口,跟读ramoops模块如何依据不同前端向相应子系统注册回调,参考文件:linux_5.10/fs/pstore/ram_core.c

// 跟进注册实现:pstore_register     
//--------------------------------------------------------------------------------------------------
//linux_5.10/fs/pstore/platform.c
int pstore_register(struct pstore_info *psi)
	if (backend && strcmp(backend, psi->name)) {		// 后端检查,此时为:ramoops
	if (!psi->flags) {									// 前端检查 
	if (!psi->read || !psi->write) {					// 能力集检查
	...
	if (!psi->write_user)
		psi->write_user = pstore_write_user_compat;		// 写实现
	psinfo = psi;        								// 全局变量:psinfo
	mutex_init(&psinfo->read_mutex);					// 锁初始化:读、buf
	sema_init(&psinfo->buf_lock, 1);       
	if (psi->flags & PSTORE_FLAGS_DMESG) {				
		pstore_dumper.max_reason = psinfo->max_reason;
		pstore_register_kmsg();							// 注册dmsg前端
*			kmsg_dump_register(&pstore_dumper);      	// 		注册并设置回调dump方法:pstore_deump
	if (psi->flags & PSTORE_FLAGS_CONSOLE)
		pstore_register_console();        				// 注册console前端
	        ...
*			register_console(&pstore_console);			// 		注册并设置回调wirte方法:pstore_console_write
	...													// 其他前端        
	pstore_timer_kick();								// 启动timer
		if (pstore_update_ms < 0)						//		如果模块参数update_ms为负数,退出
			return;
		mod_timer(&pstore_timer, jiffies + msecs_to_jiffies(pstore_update_ms));	// 定时update_ms
	...        
	pr_info("Registered %s as persistent store backend\n", psi->name);	// 前端注册成功打印:
                                   
// 向各子系统中注册回调:pstore_dump 
//--------------------------------------------------------------------------------------------------
static struct kmsg_dumper pstore_dumper = {
	.dump = pstore_dump,
};
        
static void pstore_dump(struct kmsg_dumper *dumper, num kmsg_dump_reason reason)...   
	why = kmsg_dump_reason_str(reason);    
	if (down_trylock(&psinfo->buf_lock)) {
		if (pstore_cannot_wait(reason)) {		// 如果pstore中不做等待的事件类型
		...
	oopscount++;
	while (total < kmsg_bytes) {
		struct pstore_record record;
		pstore_record_init(&record, psinfo);	// 创建一个record,并初始化
		record.type = PSTORE_TYPE_DMESG;
		...			// 其他参数  
		header_size = snprintf(dst, dst_size, "%s#%d Part%u\n", why, oopscount, part);	// 文件加头处理
		dst_size -= header_size;            
		kmsg_dump_get_buffer(dumper, true, dst + header_size, dst_size, &dump_size)// 实质数据
		if (big_oops_buf) {						// 如果文件过大,压缩处理
			zipped_len = pstore_compress(dst, psinfo->buf, header_size + dump_size, psinfo->bufsize);                                         
		psinfo->write(&record);            		// 写操作
		total += record.size;
		part++;
	up(&psinfo->buf_lock);						// 对上down_trylock    
            
static struct console pstore_console = {
	.write	= pstore_console_write,
	.index	= -1,
};  

static void pstore_console_write(struct console *con, const char *s, unsigned c)
	struct pstore_record record;
	...           
	pstore_record_init(&record, psinfo);
	record.type = PSTORE_TYPE_CONSOLE;
	record.buf = (char *)s;
	record.size = c;
	psinfo->write(&record);                       

总结ramoops注册工作的流程,其中过程涉及的重要工作:

  1. psi,是向pstore框架注册和管理的对象;

  2. psinfo,全局变量初始化,指示ramoops是否已经注册并防止重入;

  3. 锁初始化,涉及两个锁:

    • psinfo->read_mutex,psinfo 读锁;
    • psinfo->buf_lock,psinfo buf数据锁;
  4. psinfo->flags,前端类型的位置映射。在注册过程中将会遍历并相应创建实例:

    • PSTORE_FLAGS_DMESG, dmesg前端,使用kmsg_dump_register向prink注册struct kmsg_dumper类型的回调:pstore_dumper;

    • PSTORE_FLAGS_CONSOLE,console前端,使用register_console向printk注册struct console类型的回调:pstore_console;

    • PSTORE_FLAGS_FTRACE,ftrace前端,使用register_ftrace_function向ftrace注册struct ftrace_ops类型的回调:pstore_ftrace_ops;

      使用debugfs_create_{dir,file}在debugfs挂载路径下创建节点:xxx/pstore/record_ftrace

    • PSTORE_FLAGS_PMSG,pmsg前端,使用register_chrdev创建一个字符设备(主设备号随机)并初始化其集:psg_fops;

      使用{clase,device}_create创建了设备节点:dev/pmsg0

  5. pstore_timer_kick,启动定时。该定时器定期检查pstore_new_entry是否置位并提交工作到对列pstore_dowork最终创建新record;

数据结构

由以上章节梳理出ramoops模块主要数据结构的关系图如下:

在这里插入图片描述

总结ramoops主要数据结构,对其中关键对象进制说明:

  1. ramoops_diver,基于平台驱动,是模块的入口;

  2. struct persistent_ram_zone,即prz。用于管理内存,依据不同前端实例化,手段为:

    • 申请一个przramoops_init_prz
    • 申请多个przramoops_init_przs
  3. struct pstore_info,即psi。ramoops的能力集包含:

    • name,后端名,固定为:“ramoops”;
    • openclose,record文件的打开、关闭方法;
    • readwriteerase,record文件的读、写、清空方法;
  4. struct ramoops_context,即cxt,ramoops对象,是关联其他数据的枢纽是最关键的数据结构

    • oops_cxt是cxt的一个实例,是ramoops模块的一个全局变量;
    • pstore是cxt的一个成员,是接入pstore子系统的入口;

    注:psore->data在驱动probe阶段与cxt绑定,在后续record文件操作(读、写)中使用。

pstore_blk模块

待跟进

pstore_zone模块

待跟进

测试

  • 将dts中已定义了ramoops

    [root@milkv-duo]~# ll /sys/devices/platform/9fd00000.ramoops/
    total 0
    drwxr-xr-x  2 root root    0 Jan  1 00:01 ./
    drwxr-xr-x 79 root root    0 Jan  1 00:00 ../
    lrwxrwxrwx  1 root root    0 Jan  1 00:01 driver -> ../../../bus/platform/drivers/ramoops/
    -rw-r--r--  1 root root 4096 Jan  1 00:01 driver_override
    -r--r--r--  1 root root 4096 Jan  1 00:01 modalias
    lrwxrwxrwx  1 root root    0 Jan  1 00:01 of_node -> ../../../firmware/devicetree/base/reserved-memory/ramoops/
    lrwxrwxrwx  1 root root    0 Jan  1 00:01 subsystem -> ../../../bus/platform/
    -rw-r--r--  1 root root 4096 Jan  1 00:01 uevent
    [root@milkv-duo]~# 
    [root@milkv-duo]~# dmesg |tail
    [   21.504624] bm-dwmac 4070000.ethernet eth0: Link is Down
    [   35.841599] bm-dwmac 4070000.ethernet eth0: Link is Up - 100Mbps/Full - flow control rx/tx
    [   37.888625] bm-dwmac 4070000.ethernet eth0: Link is Down
    [   43.824771] ramoops: using module parameters
    [   43.826552] pstore: Using crash dump compression: deflate
    [   43.826622] printk: console [ramoops-1] enabled
    [   43.826634] pstore: Registered ramoops as persistent store backend
    [   43.826646] ramoops: using 0x100000@0x9fd00000, ecc: 0
    [   43.826968] ramoops: already initialized						# 提示已初始化,退出probe
    [   43.831937] ramoops: probe of ramoops failed with error -22
    
  • 无dts,加载时附带参数

    [root@milkv-duo]~# ll /sys/devices/platform/ramoops/
    total 0
    drwxr-xr-x  2 root root    0 Apr 11 03:17 ./
    drwxr-xr-x 78 root root    0 Jan  1  1970 ../
    lrwxrwxrwx  1 root root    0 Apr 11 03:17 driver -> ../../../bus/platform/drivers/ramoops/
    -rw-r--r--  1 root root 4096 Apr 11 03:17 driver_override
    -r--r--r--  1 root root 4096 Apr 11 03:17 modalias
    lrwxrwxrwx  1 root root    0 Apr 11 03:17 subsystem -> ../../../bus/platform/
    -rw-r--r--  1 root root 4096 Apr 11 03:17 uevent
    [root@milkv-duo]~# 
    [root@milkv-duo]~# dmesg |tail
    [  209.152219] sysrq: Changing Loglevel
    [  209.156272] ttyS ttyS0: 3 input overrun(s)
    [  209.163388] sysrq: Loglevel set to 7
    [  217.217505] sysrq: Changing Loglevel
    [  217.222373] sysrq: Loglevel set to 7
    [  231.098897] ramoops: using module parameters
    [  231.106996] pstore: Using crash dump compression: deflate
    [  231.112913] printk: console [ramoops-1] enabled
    [  231.118193] pstore: Registered ramoops as persistent store backend
    [  231.124866] ramoops: using 0x100000@0x9fd00000, ecc: 0
    

扩展

  1. request_mem_region() 是一个在 Linux 内核中用于内存管理的函数,它的主要作用是请求指定的物理内存区域供设备驱动程序或其他内核组件使用。具体来说:

    • 内存区域声明: 当一个设备驱动或其他内核模块需要访问特定的物理内存区域时,如设备的硬件寄存器或共享内存,它会调用 request_mem_region() 函数来声明对该区域的使用权。这样做可以确保内核知道这块内存区域已经被某个模块“预定”,防止其他模块误用或冲突。
    • 资源保护: 通过调用 request_mem_region(),内核可以防止其他驱动程序或模块意外覆盖或干扰请求的内存区域。这样可以确保在多设备、多驱动共存的复杂环境中,内存资源得到妥善管理和保护,避免数据损坏或系统崩溃。
    • 调试辅助: 函数接受一个 label 参数,该参数用于为请求的内存区域提供一个可读的标签或名称。当系统日志中报告内存相关的问题时,这个标签有助于快速识别问题所涉及的特定内存区域,便于调试和故障排查。
    • 权限管理: 请求内存区域后,内核会赋予请求者对这片内存的访问权限。在某些情况下,内核可能会要求驱动程序进一步使用 ioremap() 函数将物理地址映射到内核虚拟地址空间,以便驱动程序能够通过虚拟地址安全、有效地访问这片物理内存。
    • 资源释放: 当设备驱动不再需要访问该内存区域时,应调用 release_mem_region() 函数释放先前请求的资源,以便其他驱动或模块可以重新使用这片内存。
      总结来说,request_mem_region() 是Linux内核中用于管理物理内存资源的重要接口,它确保了多个驱动程序或内核模块在访问共享内存资源时的互斥性和安全性,是构建健壮、稳定的设备驱动程序不可或缺的一部分。
  2. 在 Linux 内核中,pgprot_noncachedpgprot_writecombine 都是用于定义页面保护属性的宏,主要用于设置页面的缓存属性。它们分别表示两种不同的页面属性设置:

    • pgprot_noncached:表示页面是非缓存的,即在对该页面进行读写时不会使用缓存。通常用于需要直接和硬件设备进行交互的内存页面,以避免缓存带来的影响。

    • pgprot_writecombine:表示页面使用写组合属性,即页面被标记为写组合缓存属性。写组合缓存是一种缓存策略,通常用于提高写入性能。在写组合缓存中,系统先将写入操作积累在缓冲区中,然后一次性写入内存,减少了直接写入内存的频率,从而提高了性能。

      总结来说,pgprot_noncached 适用于需要避免缓存影响的内存页面,而 pgprot_writecombine 主要用于提高写入性能的内存页面。选择使用哪种页面保护属性取决于具体的硬件设备要求或性能需求。

  3. __builtin_constant_p 是 GCC(GNU Compiler Collection)提供的一个内建函数,用于在编译时检查表达式是否是常量表达式。这个能力允许程序员编写更高效或条件性的代码,特别是在宏和内联函数中,可以根据表达式是否为常量来选择不同的执行路径或优化策略。

调试

  • 编译环境,初始化

    source build/milkvsetup.sh
    
    # Duo S
    source device/milkv-duos-sd/boardconfig.sh 
    defconfig cv1813h_milkv_duos_sd
    
  • Server,资源更新

    cp -rf ./linux_5.10/build/cv1813h_milkv_duos_sd/fs/pstore/ramoops.ko           ~/tftp/
    cp -rf linux_5.10/build/cv1813h_milkv_duos_sd/lib/reed_solomon/reed_solomon.ko ~/tftp/
    cp -rf ./install/soc_cv1813h_milkv_duos_sd/rawimages/boot.sd                   ~/tftp/
    
  • Device,资源更新

    tftp -b 65536 -gr ramoops.ko      -l /mnt/system/ko/ramoops.ko      192.168.8.108
    tftp -b 65536 -gr reed_solomon.ko -l /mnt/system/ko/reed_solomon.ko 192.168.8.108
    tftp -b 65535 -gr boot.sd         -l /mnt/boot/boot.sd              192.168.8.108
    
  • Device,测试环境搭建

    # 调试系统日志等级
    echo 8 > /proc/sysrq-trigger
    
    # 挂载pstore文件系统
    mount -t pstore pstore /sys/fs/pstore
    
    # 加载Pstore与依赖模块
    insmod /mnt/system/ko/reed_solomon.ko
    insmod /mnt/system/ko/ramoops.ko mem_address=0x9FD00000 mem_size=0x100000 mem_type=1
    
  • Device,测试Panic

    # Panic发生3S后重启
    echo 3 > /proc/sys/kernel/panic
    # 触发panic
    echo c > /proc/sysrq-trigger
    
  • Device,动态日志调试

    mkdir -p /data/debugfs && mount -t debugfs none /data/debugfs
    
    echo -n 'file fs/pstore/ram.c +p' > /data/debugfs/dynamic_debug/control
    

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

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

相关文章

音乐文件逆向破解

背景 网易云等在线音乐文件的加密源码都按照一定的规则加密&#xff0c;通过对音乐文件的源码分析转化&#xff0c;有望实现对加密文件的解密 实现内容 实现对加密音乐文件的解密 实现对无版权的音乐文件的转化 实现环境 010editor 010 Editor是一个专业的文本编辑器和十六…

IDEA pom.xml显示灰色并被划线

在使用 IDEA 进行开发的过程中&#xff0c;有时候会遇到 pom.xml 显示灰色并被划线的情况&#xff0c;如下图&#xff1a; 这一般是因为该文件被 Maven 忽略导致的&#xff0c;可以进行如下操作恢复&#xff1a; 设置保存后&#xff0c;可以看到 pom.xml 恢复了正常&#xff1a…

【UE5.1】使用MySQL and MariaDB Integration插件——(1)连接MySQL

效果 步骤 1. 在虚幻商城下载“MySQL and MariaDB Integration”插件 2. 购买安装后&#xff0c;我们将插件添加到一个新工程中&#xff0c;打开新工程可以看到已经添加了插件 3. 新建一个蓝图&#xff0c;选择父类为“MySQLDBConnectionActor” 这里命名为该蓝图为“BP_MySQL…

【菜狗学前端】npm i -g nodemon 遇到的下载卡住及运行权限问题解决记录

一、下载nodemon原因 nodemon作用&#xff1a;用node环境运行js文件时可以实时刷新运行出结果 (即修改js代码后不需再手动重新运行js文件) 二、下载卡住 reify:semver:timing reifyNode:node_modules/nodemon Completed 卡住位置&#xff1a;reify:semver: timing reifyNode…

【Java探索之旅】数组概念与初始化指南:动静结合

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、初识数组1.1 为什么要有数组&#xff1f;1.2 数组的的概念 二、数组的创建及初始化…

【Node.js】Express学习笔记(黑马)

目录 初识 ExpressExpress 简介Express 的基本使用托管静态资源nodemon Express 路由路由的概念路由的使用 Express 中间件中间件的概念Express 中间件的初体验中间件的分类 初识 Express Express 简介 什么是 Express&#xff1f; 官方给出的概念&#xff1a;Express 是基于…

SpringMVC--获取请求参数 / 域对象共享数据

目录 1. SpringMVC 获取请求参数 1.1. 通过ServletAPI获取 1.2. 控制器方法形参获取 1.3. RequestParam 1.4. RequestHeader 1.5. CookieValue 1.6. 通过POJO获取请求参数 1.7. 解决获取请求参数的乱码问题 2. 域对象共享数据 2.1. 三大域对象 2.2. 准备工作 2.3. S…

海外短剧系统开发:引领全球短剧新潮流,打造跨文化娱乐新体验

随着全球化和互联网的快速发展&#xff0c;跨文化娱乐已经成为人们日常生活中不可或缺的一部分。海外短剧作为一种新颖、便捷的娱乐形式&#xff0c;正逐渐受到越来越多观众的喜爱。为了满足广大用户的需求&#xff0c;我们荣幸地推出全新的海外短剧系统开发方案&#xff0c;旨…

IDEA 找不到或无法加载主类

IDEA 中&#xff0c;有时候会遇到明明存在这个类&#xff0c;import 也没有报错&#xff0c;但编译时会报找不到或无法加载主类。 解决方法&#xff1a; 图像化操作 右侧 Maven > 根项目 > Lifecycle > clean > install 命令操作 mvn clean install

Java单例集合

Collection接口介绍 Collection 表示一组对象&#xff0c;它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。 Collection接口中定义的方法 方法说明boolean add(Object element)增加元素到容器中boolean remove(Object element)从容器中移除元素 boolean c…

Angular 使用DomSanitizer防范跨站脚本攻击

跨站脚本Cross-site scripting 简称XSS&#xff0c;是代码注入的一种&#xff0c;是一种网站应用程序的安全漏洞攻击。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在使用网页时就会收到影响&#xff0c;这类攻击通常包含了HTML和用户端脚本语言&#xff08;JS&…

代码随想录算法训练营三刷day55 | 动态规划之子序列 392.判断子序列 115.不同的子序列

day55 392.判断子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 115.不同的子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历…

【Linux学习】初识Linux指令(二)

文章标题 1.rm 指令2.man指令3.nano指令4.cp指令5.mv指令6.alias指令7. cat与8.echo指令 ⚶文章简介 ⚶本篇文章继上篇文章Linux指令讲解&#xff0c;本篇文章主要会涉及到的指令会有&#xff1a;rm指令与 *&#xff08;通配符&#xff09;的搭配使用&#xff0c;man指令&…

CRMEB 开源/标准版商城系统客服配置教程

管理后台/设置/系统设置/商城配置/客服端配置 有系统客服/拨打电话/跳转链接可选&#xff0c;系统客服为系统自带的客服系统&#xff0c;拨打电话为用户点击联系客服为拨打客服电话的方式&#xff0c;跳转链接为可以跳转自己开发的客服系统或者第三方的客服系统或者企业微信的…

文献学习-33-一个用于生成手术视频摘要的python库

VideoSum: A Python Library for Surgical Video Summarization Authors: Luis C. Garcia-Peraza-Herrera, Sebastien Ourselin, and Tom Vercauteren Source: https://arxiv.org/pdf/2303.10173.pdf 这篇文章主要关注的是如何通过视频摘要来简化和可视化手术视频&#xff0c…

mediapipe人体姿态检测(全方位探索手部、面部识别、姿势识别与物体检测及自拍分割技术)

引言 本文将聚焦于MediaPipe对人体姿态检测的全面支持&#xff0c;包括手部、面部识别、全身姿势识别、物体检测以及自拍分割五大关键技术。通过深入了解这些功能&#xff0c;读者将能更好地运用MediaPipe在各种应用中实现精准的人体动作捕捉与分析。 一、手部关键点检测 Me…

Web应用程序中的常见安全漏洞

大家好&#xff0c;我是咕噜铁蛋&#xff01;今天&#xff0c;我想和大家聊聊一个在我们日常开发中经常遇到的问题——Web应用程序中的安全漏洞。在这个数字化时代&#xff0c;Web应用几乎无处不在&#xff0c;它们不仅方便了我们的生活&#xff0c;也推动了社会的进步。然而&a…

python实现简单的车道线检测

描述 python实现简单的车道线检测&#xff0c;本文章将介绍两种简单的方法 颜色阈值区域掩模canny边缘检测霍夫变换 这两种方法都能实现简单的车道线检测demo&#xff0c;注意仅仅是demo 下面的图片是用到的测试图片 方法1&#xff1a;颜色阈值&#xff08;Color Selection…

李廉洋:4.15黄金,原油最新资讯,美盘走势分析及策略。

由于欧洲央行很可能先于美联储降息&#xff0c;美元走强。法国兴业银行分析师基特•朱克斯表示&#xff0c;市场“假设我们看到欧洲央行将在6月降息&#xff0c;但美联储不会”&#xff0c;这对美元有利。朱克斯表示&#xff0c;尽管在货币政策决定之前会公布一些相关数据&…

JMeter多个线程组的使用说明!

当JMeter测试计划中存在多个线程组&#xff0c;您需要了解如何结合JMeter和PTS配置参数&#xff0c;使多个线程组并行或串行压测。 前提条件 创建JMeter压测场景。具体操作&#xff0c;请参见创建JMeter场景。 背景信息 JMeter线程组包括setUp线程组、tearDown线程组和主线…