fbmem驱动框架分析

2023-11-10

fbmem驱动框架分析

1、与misc驱动框架类比

1.1 my_first_cdev的记录

从/proc/devices中可以看到test设备,但是不能够看到节点,这是之前分析字符设备时就遇到过的问题,注册了字符设备后需要自己创建节点,下图是在3月份自己写第一个字符设备驱动VirtualDisk时所用到的一个应用程序,当时备注了通过mknod自己创建节点的过程。

image-20200511150559775

后来在misc驱动框架下写字符设备驱动VirtualDisk时,就可以自动创建设备节点了。

image-20200511150825447

1.2、关于misc驱动框架的总结

image-20200511154338033

2、fbmem驱动框架

驱动框图

首先分析它的入口函数。

fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);//创建proc中的fb,并提供操作接口

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//注册字符设备,提供操作接口
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");//创建类
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

入口函数比较简单,关键的一句就是注册字符设备,提供操作接口,其中提供操作接口十分重要。

root@am335x-evm:/proc# cat devices
Character devices:
···
29 fb
···

可以看到这是与1.1小节遇到的问题类似,在devices里可以看到fb设备,但是没有相应的节点,当不使用驱动框架注册设备时,是不会自动创建节点的。

2.1提供注册函数

/*register_framebuffer - registers a frame buffer device
 *	@fb_info: frame buffer info structure
 *	Registers a frame buffer device @fb_info.
 *	Returns negative errno on error, or zero for success. */
int
register_framebuffer(struct fb_info *fb_info)
{
	int ret;
	mutex_lock(&registration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(&registration_lock);
	return ret;
}
/*unregister_framebuffer - releases a frame buffer device
 *	@fb_info: frame buffer info structure
 *	Unregisters a frame buffer device @fb_info.
 *	Returns negative errno on error, or zero for success.
 *      This function will also notify the framebuffer console
 *      to release the driver.
 *      This is meant to be called within a driver's module_exit()
 *      function. If this is called outside module_exit(), ensure
 *      that the driver implements fb_open() and fb_release() to
 *      check that no processes are using the device.*/
int
unregister_framebuffer(struct fb_info *fb_info)
{
	int ret;
	mutex_lock(&registration_lock);
	ret = do_unregister_framebuffer(fb_info);
	mutex_unlock(&registration_lock);
	return ret;
}

这两个接口是framebuffer总线给出的两个接口,通过这里的接口注册framebuffer设备,就会出现/dev/fb0节点,然后对fb0节点就可以进行操作,其中fbmem.c中提供了通用的操作函数。

2.2、操作函数分析

我们追踪一下他的操作函数都有哪些。

static const struct file_operations fb_fops = {
    .owner =    THIS_MODULE,  
    .read =     fb_read,      
    .write =    fb_write,
    .unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = fb_compat_ioctl,
#endif
    .mmap =     fb_mmap,
    .open =     fb_open,
    .release =  fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
    .get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
    .fsync =    fb_deferred_io_fsync,
#endif
    .llseek =   default_llseek,
};

2.2.1、open函数分析

static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
	int fbidx = iminor(inode);//通过节点获取副设备号
	struct fb_info *info;
    /*
    在本函数中有一个贯穿函数的变量,info,info变量的结构体里边存着许多的信息,有设备、操作函数等信息,具体fb_info的使用情况在函数中继续分析。
    */
	int res = 0;

	info = get_fb_info(fbidx);//通过副设备号获取info结构体
	if (!info) {
		request_module("fb%d", fbidx);
		info = get_fb_info(fbidx);
		if (!info)
			return -ENODEV;
	}
	if (IS_ERR(info))
		return PTR_ERR(info);

	mutex_lock(&info->lock);
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}
	file->private_data = info;//将info结构体作为file的私有信息
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);//如果info存在open函数,则调用其open函数
      
 /*此处做了一个实验,为info创建一个fb_open,观察发生的现象,可以看到以下打印信息。
[    3.673068]   [KERNEL_DEBUG:]  fb_open
[    3.677050]   [KERNEL_DEBUG:]  nfk_open
该实验为info增加了一个open函数,实验证明,open函数还是可以重定义的。
 */
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);
	if (res)
		put_fb_info(info);
	return res;
}

open函数的主要作用就是通过inode节点找到驱动程序,然后将驱动程序中的ops赋值给file结构体,就可以实现对file文件的操作。

struct fb_info {
	atomic_t count;
	int node;
	int flags;
	···
	struct fb_ops *fbops;
	struct device *device;		/* This is the parent */
	struct device *dev;		/* This is this fb device */
	int class_flag;                    /* private sysfs flags */
···
};
open函数究竟做了什么

分析完fbmem.c的open函数的调用关系之后,就要进一步的分析open函数到底做了什么事情。

open函数打开了一个文件,这个文件是内核空间的一个文件结构体,open函数要做的就是将这个结构体进行填充,将该文件的操作方法赋值给文件结构体的fops。

open函数中的这一行就可以看到面向对象编程的痕迹。

file->private_data = info;//将info结构体作为file的私有信息

file是一个对象,其中的私有数据就是info结构体内的数据。

进一步思考

在2.2.1小节,实验可以看到open函数可以重定义,那open函数又是如何被第一次调用的?

下图表述了open第一次的调用情况,首先系统调用了sys_open,然后通过设备特点选择了chrdev_open函数。

image-20200511161837893
static int chrdev_open(struct inode *inode, struct file *filp)
{
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	···
	filp->f_op = fops_get(p->ops);//此处为重点,通过inode节点中的设备号找到ops赋值给filp
	if (!filp->f_op)
		goto out_cdev_put;

	if (filp->f_op->open) {
		ret = filp->f_op->open(inode,filp);//如果filp有open则继续执行filp的open函数
		if (ret)
			goto out_cdev_put;
	}

···
}

这里会发现这种不断嵌套open函数是一脉相承的,刚才在分析fbmem.c中的open函数时,就发现执行完赋值之后,会根据赋值的file进一步的执行新的ops->open。

2.2.2 fb_read函数

open完成之后,就可以根据file当中被赋值的ops选择read函数,read函数直接执行file里的ops当中的read函数,然后在read函数中找到file当中的info,进一步调用info当中read函数。

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long p = *ppos;
    struct fb_info *info = file_fb_info(file);
    u8 *buffer, *dst;
    u8 __iomem *src;//IO存储空间
    int c, cnt = 0, err = 0;
    unsigned long total_size;
printd("fb_read\n");//此处测试cat /dev/fb0得出结果,证明确实调用了此处函数。
    if (!info || ! info->screen_base)
        return -ENODEV;//判断是否存在设备

    if (info->state != FBINFO_STATE_RUNNING)
        return -EPERM;//判断是否可操作

    if (info->fbops->fb_read)
        return info->fbops->fb_read(info, buf, count, ppos);//判断info是否存在fbops的read操作函数

    total_size = info->screen_size;

    if (total_size == 0)
        total_size = info->fix.smem_len;//framebuffer大小

    if (p >= total_size)
        return 0;

    if (count >= total_size)
        count = total_size;

    if (count + p > total_size)
        count = total_size - p;

    buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
             GFP_KERNEL);//为buffer申请内存
    if (!buffer)
        return -ENOMEM;

    src = (u8 __iomem *) (info->screen_base + p);

    if (info->fbops->fb_sync)
        info->fbops->fb_sync(info);

    while (count) { 
        c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
        dst = buffer;
        fb_memcpy_fromfb(dst, src, c);//将数据从framebuffer中拷贝出来
        dst += c;
        src += c;

        if (copy_to_user(buf, buffer, c)) {//将数据拷贝至用户空间
            err = -EFAULT;
            break;
        }
        *ppos += c;
        buf += c;
        cnt += c;
        count -= c;
    }

    kfree(buffer);

    return (err) ? err : cnt;
}

以上函数中,我们先分析它的传入参数,首先传入了一个非常关键的变量,*file变量是由内核创建。

目前了解到的是file参数是由内核创建,创建出来之后给该函数使用,file通过chrdev_open和fb_open都已经进行了赋值。这里可以拿来直接用了。

  struct fb_info *info = file_fb_info(file);

可以看到是这么使用的,将file通过file_fb_info进行了一个操作,进一步查看是如何进行操作的。

/* We hold a reference to the fb_info in file->private_data,
 * but if the current registered fb has changed, we don't
 * actually want to use it.
 * So look up the fb_info using the inode minor number,
 * and just verify it against the reference we have.*/
static struct fb_info *file_fb_info(struct file *file)
{
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];
	if (info != file->private_data)
		info = NULL;
	return info;
}

此处是进行一个判断,看当前我们使用的file->private_data是否已经不再适用,通过inode重新寻找被注册的fb当中的fb_info结构体。而不是直接使用file->private_data当中的info。

2.2.3、fb_write函数

fb_write函数与fb_read函数大体类似。此处不再重复分析。

2.2.4、fb_ioctl函数

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	struct fb_ops *fb;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap cmap_from;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	long ret = 0;

	switch (cmd) {
	case FBIOGET_VSCREENINFO:/* 获得可变的屏幕参数 */ 
		if (!lock_fb_info(info)) /* 如果info->fbops不为空,则上锁,成功返回1 */
			return -ENODEV;
		var = info->var;/* 可变参数变量的设置 */ 
		unlock_fb_info(info);/* 解锁 */
		/* 从内核空间的var地址拷贝var大小的数据到用户空间的argp地址里去 */ 
		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
         /* 成功返回0 */
		break;
···
	}
	return ret;
}

ioctl函数是执行命令的函数,ioctl函数非常长,可以执行的命令也非常多,这里暂时不做分析,在后期用到这些命令时再进行分析。

2.2.5、 fb_mmap函数

这里分配的显存是在内核空间分配的,用户空间并不能直接访问, 所以需要用到这里的mmap函数,直接将这段内存空间映射到用户空间去,用户空间就能访问这段内存空间了。

static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
	struct fb_info *info = file_fb_info(file);
	struct fb_ops *fb;
	unsigned long off;
	unsigned long start;
	u32 len;
if (!info)
	return -ENODEV;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
	return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
fb = info->fbops;
if (!fb)
	return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
	int res;
	res = fb->fb_mmap(info, vma);
	mutex_unlock(&info->mm_lock);
	return res;
}

/* frame buffer memory */
start = info->fix.smem_start; /* fb缓冲内存的开始位置(物理地址) */  
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
if (off >= len) { /* 偏移值大于len长度 */
	/* memory mapped io */    /* 内存映射的IO */ 
	off -= len;
	if (info->var.accel_flags) {
		mutex_unlock(&info->mm_lock);
		return -EINVAL;
	}
	start = info->fix.mmio_start;   
	len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
}
mutex_unlock(&info->mm_lock);
start &= PAGE_MASK;
if ((vma->vm_end - vma->vm_start + off) > len)
	return -EINVAL;
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
/* This is an IO map - tell maydump to skip this VMA */
vma->vm_flags |= VM_IO | VM_RESERVED;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, off);
/* io_remap_pfn_range正式映射物理内存到用户空间虚拟地址 */  
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
		     vma->vm_end - vma->vm_start, vma->vm_page_prot))
	return -EAGAIN;
return 0;
}

3、结合psplash应用分析

psplash应用是典型的C语言编程。与QT应用不同,QT应用使用的C++编程,并且大量的程序都是被封装过的,通过分析C语言应用编程可以充分了解驱动的使用。

当前仅分析了framebuffer总线驱动,具体还需要分析LCD驱动,才能够结合实际的硬件进行分析。在此处仅简单分析psplash调用的总线驱动当中的函数,下一步将结合LCD驱动再进行分析。

int
main (int argc, char** argv)
{
  char      *tmpdir;
  int        pipe_fd, i = 0, angle = 0, ret = 0;
  PSplashFB *fb;
  bool       disable_console_switch = FALSE;

  signal(SIGHUP, psplash_exit);
  signal(SIGINT, psplash_exit);
  signal(SIGQUIT, psplash_exit);

···
  if ((fb = psplash_fb_new(angle)) == NULL) {//此处为重点
      ret = -1;
      goto fb_fail;
  }
···
  psplash_main (fb, pipe_fd, 0);//执行的循环
···
  return ret;
}

下面分析重点函数psplash_fb_new();

psplash_fb_new (int angle)
{
  struct fb_var_screeninfo fb_var;
  struct fb_fix_screeninfo fb_fix;
  int                      off;
  char                    *fbdev;
  PSplashFB *fb = NULL;
  fbdev = getenv("FBDEV");
  if (fbdev == NULL)
    fbdev = "/dev/fb0";
  if ((fb = malloc (sizeof(PSplashFB))) == NULL){
      perror ("Error no memory");
      goto fail;
    }
  memset (fb, 0, sizeof(PSplashFB)); 
  fb->fd = -1;
  if ((fb->fd = open (fbdev, O_RDWR)) < 0)//首先调用open函数,
    {
      perror ("Error opening /dev/fb0");
      goto fail;
    }
  if (ioctl (fb->fd, FBIOGET_VSCREENINFO, &fb_var) == -1)//ioctl函数的命令调用
    {
      perror ("Error getting variable framebuffer info");
      goto fail;
    }
​```
  if (ioctl (fb->fd, FBIOGET_VSCREENINFO, &fb_var) == -1)//ioctl函数的命令调用
    {
      perror ("Error getting variable framebuffer info (2)");
      goto fail;
    }

  /* NB: It looks like the fbdev concept of fixed vs variable screen info is
   * broken. The line_length is part of the fixed info but it can be changed
   * if you set a new pixel format. */
  if (ioctl (fb->fd, FBIOGET_FSCREENINFO, &fb_fix) == -1)//ioctl函数的命令调用
    {
      perror ("Error getting fixed framebuffer info");
      goto fail;
    }

  fb->real_width  = fb->width  = fb_var.xres;
  fb->real_height = fb->height = fb_var.yres;
  fb->bpp    = fb_var.bits_per_pixel;
  fb->stride = fb_fix.line_length;
  fb->type   = fb_fix.type;
  fb->visual = fb_fix.visual;

  fb->red_offset = fb_var.red.offset;
  fb->red_length = fb_var.red.length;
  fb->green_offset = fb_var.green.offset;
  fb->green_length = fb_var.green.length;
  fb->blue_offset = fb_var.blue.offset;
  fb->blue_length = fb_var.blue.length;
//通过ioctl函数获取到的数值赋值情况
  if (fb->red_offset == 11 && fb->red_length == 5 &&
      fb->green_offset == 5 && fb->green_length == 6 &&
      fb->blue_offset == 0 && fb->blue_length == 5) {
         fb->rgbmode = RGB565;
  } else if (fb->red_offset == 0 && fb->red_length == 5 &&
      fb->green_offset == 5 && fb->green_length == 6 &&
      fb->blue_offset == 11 && fb->blue_length == 5) {
         fb->rgbmode = BGR565;
  } else if (fb->red_offset == 16 && fb->red_length == 8 &&
      fb->green_offset == 8 && fb->green_length == 8 &&
      fb->blue_offset == 0 && fb->blue_length == 8) {
         fb->rgbmode = RGB888;
  } else if (fb->red_offset == 0 && fb->red_length == 8 &&
      fb->green_offset == 8 && fb->green_length == 8 &&
      fb->blue_offset == 16 && fb->blue_length == 8) {
         fb->rgbmode = BGR888;
  } else {
         fb->rgbmode = GENERIC;
  }

​```
    /*调用mmap函数*/
  fb->base = (char *) mmap ((caddr_t) NULL,
                /*fb_fix.smem_len */
                fb->stride * fb->height,
                PROT_READ|PROT_WRITE,
                MAP_SHARED,
                fb->fd, 0);
​```
  off = (unsigned long) fb_fix.smem_start % (unsigned long) getpagesize();
  fb->data = fb->base + off;

​```
​```
  return fb;
​```
}

可以看到在framebuffer驱动总线上给出的接口,在psplash一开始的时候就被调用了,相当于在初始化数据的阶段将这些接口调用,然后对用户空间的结构体进行赋值。

初始化完成之后,就可以对framebuffer内存进行写入了,主要是在mmap函数执行之后进行。

下面代码是关于一个像素点的绘制,所有绘制最终都要落脚到像素点的绘制,像素点绘制最终落脚到对framebuffer内存的写入。

static inline void
psplash_fb_plot_pixel (PSplashFB    *fb,
               int          x,
               int          y,
               uint8        red,
               uint8        green,
               uint8        blue)
{
  int off;

  if (x < 0 || x > fb->width-1 || y < 0 || y > fb->height-1)
    return;

  switch (fb->angle)
    {   
    case 270:
      off = OFFSET (fb, fb->height - y - 1, x); 
      break;
···//关于屏幕旋转的操作
    default:
      off = OFFSET (fb, x, y); 
      break;
    }   
//根据fb->bpp绘制相应的像素点。byts per pixel
  if (fb->rgbmode == RGB565 || fb->rgbmode == RGB888) {
··· 
  }
    else {
    switch (fb->bpp)
      {   
      case 32: 
        *(volatile uint32_t *) (fb->data + off)
      = ((red >> (8 - fb->red_length)) << fb->red_offset) 
          | ((green >> (8 - fb->green_length)) << fb->green_offset)
          | ((blue >> (8 - fb->blue_length)) << fb->blue_offset);
        break;
      case 16: 
        *(volatile uint16_t *) (fb->data + off)
      = ((red >> (8 - fb->red_length)) << fb->red_offset) 
          | ((green >> (8 - fb->green_length)) << fb->green_offset)
          | ((blue >> (8 - fb->blue_length)) << fb->blue_offset);
        break;
      default:
        /* depth not supported yet */
        break;
      }   
  }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

fbmem驱动框架分析 的相关文章

  • autocomplete实现原理

    autocomplete实现原理主要用在input 和 textarea这两个标签上 为这标签增加键盘监听事件和提示列表添加鼠标事件 例如 input的id为txt 为input增加键盘监听事件 keyup var mindex 1 txt
  • postman使用教程——接口测试

    Postman 之前是作为Chrome 的一个插件 现在要下载应用才能使用 以下是postman 的界面 各个功能区的使用如下 快捷区 快捷区提供常用的操作入口 包括运行收藏夹的一组测试数据 导入别人共享的收藏夹测试数据 Import fr
  • 程序设计和C语言

    TOC程序设计和C语言 1 什么是计算机程序 一组计算机能够识别和执行的指令 计算机本质就是程序的机器 2 什么是计算机预言 机器指令 计算机能直接识别和接受的二进制代码 机器语言 机器指令的集合 符号语言 又称为汇编语言 计算机的低级语言
  • 新手月入10k很难?方法是关键!写给迷茫的测试从业者!

    关于软件测试行业前景 行业薪资 一直都是准备转行 刚刚入行甚至是在职一 两年的朋友密切关注的问题 毕竟这关系着自己未来的 钱途 也决定着自己的职业发展路线 那软件测试员 究竟该如何规划职业发展之路 如何才能月薪过万 作为过来人 分享一下我的
  • yarn清理缓存命令

    1 查看yarn全局缓存目录 yarn cache dir 2 清除缓存 yarn cache clean
  • 服务器安装文件共享软件权限,服务器共享文件权限

    服务器共享文件权限 内容精选 换一换 文件系统创建完成后 用户需在客户端挂载文件共享 在本地共享目录和CSG的文件共享建立映射 通过操作本地的目录实现对CSG共享目录的操作 实现数据实时上云管理 本章节主要介绍在Windows Server
  • 面试了30多家大厂后,整合出这份1658 页《Java 面试突击核心讲》

    面试神技 主要包含 Java 基础 JVM 多线程 MySQL Spring SpringBoot SpringCloud 分布式 Dubbo Mybatis Redis 网络 Linux MQ Zookeeper Netty 大数据 算法
  • 面试系列之JVM

    说说JVM的内存模型 方法区 存储已被虚拟机加载的类信息 常量 静态变量 即时编译后的代码等数据 堆 存放对象实例 几乎所有的对象实例都要在堆上分配 程序计数器 当前线程所执行的字节码的行号指示器 虚拟机栈 描述的是Java方法执行的内存模
  • Java如何使用dom4j获取,添加,删除,查找,设置Element节点呢?

    转自 Java如何使用dom4j获取 添加 删除 查找 设置Element节点呢 下文笔者讲述DOM4J操作Element节点的示例分享 如下所示 获取文档的根节点 Element rootElm document getRootEleme
  • 'gbk' codec can't decode byte 0x91 in position 2: illegal multibyte sequence的解决

    今天在使用Pycharm运行程序的时候 在对文件进行分割时 出现此问题 当时代码是 f open 对话 txt r 运行就会报错 在上网搜索后发现 原来问题是出现在字符编码上 改成 f open 对话 txt r encoding UTF
  • 程序员大大们,平时都喜欢逛什么技术论坛?

    先安排个工作 再带你学技术 闲来无事戳一戳 有小惊喜 1 CSDN 中国开发者网络 https mp csdn net 中国专业IT社区 为中国软件开发者提供知识传播 在线学习 职业发展等全生命周期服务 2 GitHub 开发者最最最重要的
  • cmd中如何进入某文件目录

    步骤如下 1 按下键盘的 win R 组合键 或者是点击开始菜单中的 运行 选项 来打开运行窗口 然后在打开的运行窗口中输入 CMD 回车 2 然后就打开了CMD命令窗口了 3 如 要进入F盘中的某个目录 则输入 F 回车 然后就进入了F盘
  • Qt 多窗口的调用

    方法一 用于一个父窗口和多个子窗口的处理 不知道怎么用于处理子窗口的子窗口的处理 1 建立一个父窗口 然后建立一个QDialog类型的窗口作为子窗口 2 在父类窗口建立子窗口的对象 void 父类名 on pushButton clicke
  • 红黑树之歌

    译文 我看到一个全新的节点 我想把它涂成黑色 我们需要一棵平衡的树 我们得把它漆成黑色 我想在log n的时间内找到键 就这样 旋转子树可以是一个球 我看到一个全新的节点 我想把它涂成黑色 不能有很多红节点 我们必须把它们涂成黑色 不幸的是
  • TortoiseGit 如何回退到以前的版本?

    要在 TortoiseGit 中回退到以前的版本 可以按照以下步骤进行操作 在资源管理器中 右键单击你的 Git 仓库文件夹 然后选择 TortoiseGit 再选择 Show log 这将打开 TortoiseGit 的日志界面 在日志界
  • 【华为OD】

    华为OD试题注意事项 使用合适的编程语言 在华为OD机试中多数情况下使用C 或Java 按照题目要求进行编码 仔细阅读题目描述并理解要求 在编码前可以进行伪代码编写或画流程图有助于理解和排除逻辑错误 注意代码的规范性 注重代码的可读性和可维
  • 测试架构师的职责及困境

    架构师 架构师来自于建筑学 英文是Architect 建筑工程中的架构师是负责整体建筑的架构设计 因此从宏观上看 软件行业的架构师也类似 是负责整体架构的设计 在软件工程中架构师是一个团队的技术的领头者 主要工作内容除去对项目的整体设计和规
  • redis一主二从时,主中读取不到从的信息

    一 错误情境描述 1 主 6379 2 从1 6380 3 从2 6381 二 错误原因 主中带有密码 三 解决办法 1 将主中配置文件中注释掉代码 2 在从的配置文件中添加主的密码 当master服务设置了密码保护时 slav服务连接ma
  • uniapp--- 微信小程序 用户隐私新规相关代码调整【vue3+ts+uView框架】

    uniapp 微信小程序 用户隐私新规相关代码调整 vue3 ts uView框架 官方公告地址 https developers weixin qq com community develop doc 00042e3ef54940ce85

随机推荐

  • elementUI中的$confirm调换两个按钮的位置

    confirm默认两个按钮的位置为 取消在前 确认在后 而我们在项目中经常要求 确认在前 取消在后 所以需要调换两个按钮的位置 修改后的样式如下图所示 用css样式调换两个按钮的位置 代码如下 给取消按钮添加样式 this confirm
  • 关于取模运算的特点与应用

    对于取模 取余 运算 比如A M 结果永远都是在 0 M 1 之间循环 并且如果A lt M 则结果和没有进行取模运算一样 这一特点有很多应用场景 1 最常见的就是对2取模来判断奇偶数 2 循环队列中通过对最大容量取模来控制数组下标 防止索
  • 资源记录

    AE插件 https zhuanlan zhihu com p 26304609 GLSL内置函数使用 https blog csdn net jeffasd article details 77989274 ops request mis
  • js中forEache()和Map()的区别

    定义剖析 我们首先来看一看MDN上对Map和ForEach的定义 forEach 针对每一个元素执行提供的函数 executes a provided function once for each array element map 创建一
  • 地埋式积水在线监测系统助力城市内涝解决方案

    一 方案背景 随着我国城镇化快速发展 城市建设产生的大量地面硬底化 大部分的降雨将形成地表径流 仅有少量雨水渗入地下 导致城市内涝等一系列问题 当前 全国多地发生洪涝 我国南北方全面进入主汛期 与往年相比 今年的汛期不仅提前4天 而且汛情呈
  • 3.app自动化项目

    app自动化项目 我们可以使用AirtestIDE工具进行脚本的调试 元素的定位等辅助功能 但是真正意义上的脚本 在 AirtestIDE 工具中实现还是比较麻烦 问题 1 如何使用pycharm实现airtest内容脚本 解决方案 如果是
  • JQuery入门

    JQuery jQuery 是一个 JavaScript 库 所谓的库 就是一个 JS 文件 里面封装了很多预定义的函数 比如获取元素 执行隐藏 移动等 目的就 是在使用时直接调用 不需要再重复定义 这样就可以极大地简化了 JavaScri
  • 华为OD机试 -求最大连续bit数(C++ & Java & JS & Python)

    描述 求一个int类型数字对应的二进制数字中1的最大连续数 例如3的二进制为00000011 最大连续2个1 数据范围 数据组数 1 5 1 t 5 1 500000 1 n 500000 进阶 时间复杂度 O logn 空间复杂度 1 O
  • 出现d3dcompiler_41.dll错误怎么解决

    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题 如果是新手第一时间会认为是软件或游戏出错了 其实并不是这样 其主要原因就是你电脑系统的该dll文件丢失了或者损坏了 这时你只需下载这个d3dcompiler 41 dll文件进行安装
  • React+AntDesign结合Table、Modal+Form的使用,以及Ant Design 4.x Modal + Form搭配使用时,如何在Modal中使用表单验证

    React AntDesign结合Table Modal Form的使用 最近写React AntDesign时 使用到Modal和Form组件时候 因为Modal组件自带onOk按钮 因此不能使用Form自带的onFinish函数进行表单
  • 【转载】三十而已,信智依然

    1993年4月17日 我和丁健等几位留美学生在德克萨斯创立了AsiaInfo 亚信 如今正好30周年 图 亚信科技初期骨干人员合影 论语 讲 三十而立 对于人生来说 三十岁既意味着触摸到青春的峰值 也代表着爬上了成熟的阶梯 而对于企业来说
  • 一文了解快手广告生态:磁力智投、磁力金牛、磁力聚星、磁力万象、磁力方舟!

    抖音和快手的对照表 通过抖音和快手的对照表 我们可以知道磁力智投是快手的信息流投放平台 主要做站外引流 磁力金牛则对标巨量千川 主要做快手电商的站内闭环 磁力聚星则是围绕着达人营销 协助于品牌做出达播和自播兼具的经营阵地 也帮助达人在变现上
  • 【2023持续更新】网络安全工程师常用网站集合

    文章目录 信息搜集 子域名搜集 在线工具 信息处理 威胁情报及分析 在线靶场 综合学习 安全资讯技术 大会演讲PPT 安全社区 社工工程学 信息搜集 http www yunsee cn http finger tidesec com ht
  • Vue.js子级向父级传递数据

    组件之间的数据的传递 子传父 注意 html对大小写不敏感 事件名字最好用 隔开或全部小写 步骤 1 在父组件中在子组件上添加事件 自定义 监听 代表的含意 事件分两类 浏览器自带的事件 click mouseover mousedown
  • 11-2_Qt 5.9 C++开发指南_QSqlQueryModel的使用(QSqlQueryModel 只能作为只读数据源使用,不可以编辑数据)

    文章目录 1 QSqlQueryModel 功能概述 2 使用 QSqlQueryModel 实现数据查询 2 1 实例功能 2 2 可视化UI设计 2 3 主窗口类定义 去除自动生成的槽函数 2 4 打开数据库 2 5 记录移动 1 QS
  • java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.Integer

    因为BigDecimal不能强制转换成 String类型 要用toString 作为中间桥梁转换 同理 Object BigDecimal转换成int doulbe long 也是一样的 List
  • 灰灰-判断是否存在重复数

    给定一个整数数组 判断是否存在重复元素 如果任何值在数组中出现至少两次 函数返回 true 如果数组中每个元素都不相同 则返回 false 示例 1 输入 1 2 3 1 输出 true 示例 2 输入 1 2 3 4 输出 false 示
  • QT笔记——QTableWidget 之 指定某列排序

    指定某列共有2种方式 1 重写 bool QTableWidgetItem operator lt const QTableWidgetItem other const 2 点击表头进行排序 connect ui tableWidget g
  • Python配置第三方库最全教程

    学Python最让人头疼的莫过于去配置一大堆杂乱的第三方库 而下载第三方库时报错也能让你崩溃 今天 我整理了一份全的教程 最常用的办法下载第三方库 在终端中执行 pip install 第三方库名称 大部分人一般不会报错 但偏偏我去安装最基
  • fbmem驱动框架分析

    文章目录 fbmem驱动框架分析 1 与misc驱动框架类比 1 1 my first cdev的记录 1 2 关于misc驱动框架的总结 2 fbmem驱动框架 2 1提供注册函数 2 2 操作函数分析 2 2 1 open函数分析 op