FrameBuffer浅谈

本文深入探讨了Linux FrameBuffer的概念,作为LCD控制器的驱动,它提供了显示内存的抽象映像,允许用户直接读写操作。内容包括Framebuffer数据结构、初始化过程、驱动注册、设备文件的打开、映射和命令控制过程,揭示了Framebuffer如何连接上层应用和底层硬件驱动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FrameBuffer通常作为LCD控制器或者其他显示设备的驱动,FrameBuffer驱动是一个字符设备,设备节点是/dev/fbX,主设备号为29,次设备号递增,用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。Framebuffer设备为上层应用程序提供系统调用,也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己。

Framebuffer数据结构

kernel\include\linux\fb.h

fb_info是Linux为帧缓冲设备定义的驱动层接口。它不仅包含了底层函数,而且还有记录设备状态的数据。每个帧缓冲设备都与一个fb_info结构相对应。

struct fb_info {
  atomic_t count;
  int node;  /*一个FrameBuffer设备的次设备号*/
  int flags;
  struct mutex lock;		/* Lock for open/release/ioctl funcs */
  struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
  struct fb_var_screeninfo var;/* Current var */
  struct fb_fix_screeninfo fix;/* Current fix */
  struct fb_monspecs monspecs;/* Current Monitor specs */
  struct work_struct queue;	/* Framebuffer event queue */
  struct fb_pixmap pixmap;	/* Image hardware mapper */
  struct fb_pixmap sprite;	/* Cursor hardware mapper */
  struct fb_cmap cmap;		/* Current cmap */
  struct list_head modelist;  /* mode list */
  struct fb_videomode *mode;	/* current mode */
#ifdef CONFIG_FB_BACKLIGHT
  struct backlight_device *bl_dev;
  /* Backlight level curve */
  struct mutex bl_curve_mutex;	
  u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
  struct delayed_work deferred_work;
  struct fb_deferred_io *fbdefio;
#endif
  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 */
#ifdef CONFIG_FB_TILEBLITTING
  struct fb_tile_ops *tileops;/* Tile Blitting */
#endif
  char __iomem *screen_base;	/* Virtual address */
  unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
  void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
  u32 state;			/* Hardware state i.e suspend */
  void *fbcon_par;    /* fbcon use-only private area */
  void *par;
  struct apertures_struct {
    unsigned int count;
    struct aperture {
      resource_size_t base;
      resource_size_t size;
    } ranges[0];
  } *apertures;
};

fb_var_screeninfo:用于记录用户可修改的显示控制器参数,包括屏幕分辨率、每个像素点的比特数等

struct fb_var_screeninfo {
  __u32 xres;			/* 行可见像素*/
  __u32 yres;         /* 列可见像素*/
  __u32 xres_virtual;	/* 行虚拟像素*/
  __u32 yres_virtual; /* 列虚拟像素*/
  __u32 xoffset;		/* 水平偏移量*/
  __u32 yoffset;		/* 垂直偏移量*/
  __u32 bits_per_pixel;/*每个像素所占bit位数*/
  __u32 grayscale;	/* 灰色刻度*/
  struct fb_bitfield red;	/* bitfield in fb mem if true color, */
  struct fb_bitfield green;	/* else only length is significant */
  struct fb_bitfield blue;
  struct fb_bitfield transp;	/* transparency			*/	
  __u32 nonstd;			/* != 0 Non standard pixel format */
  __u32 activate;			/* see FB_ACTIVATE_*		*/
  __u32 height;			/* 图像高度*/
  __u32 width;			/* 图像宽度*/
  __u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */
  __u32 pixclock;			/* pixel clock in ps (pico seconds) */
  __u32 left_margin;		/* time from sync to picture	*/
  __u32 right_margin;		/* time from picture to sync	*/
  __u32 upper_margin;		/* time from sync to picture	*/
  __u32 lower_margin;
  __u32 hsync_len;		/* length of horizontal sync	*/
  __u32 vsync_len;		/* length of vertical sync	*/
  __u32 sync;			/* see FB_SYNC_*		*/
  __u32 vmode;			/* see FB_VMODE_*		*/
  __u32 rotate;			/* angle we rotate counter clockwise */
  __u32 reserved[5];		/* Reserved for future compatibility */
};

fb_fix_screeninfo:记录了用户不能修改的显示控制器的参数,这些参数是在驱动初始化时设置的

struct fb_fix_screeninfo {
  char id[16];			/* identification string eg "TT Builtin" */
  unsigned long smem_start;/* Start of frame buffer mem */
  __u32 smem_len;			/* Length of frame buffer mem */
  __u32 type;			    /* see FB_TYPE_*		*/
  __u32 type_aux;			/* Interleave for interleaved Planes */
  __u32 visual;			/* see FB_VISUAL_*		*/ 
  __u16 xpanstep;			/* zero if no hardware panning  */
  __u16 ypanstep;			/* zero if no hardware panning  */
  __u16 ywrapstep;		/* zero if no hardware ywrap    */
  __u32 line_length;		/* length of a line in bytes    */
  unsigned long mmio_start;/* Start of Memory Mapped I/O   */
  __u32 mmio_len;			/* Length of Memory Mapped I/O  */
  __u32 accel;			/* Indicate to driver which	*/
  __u16 reserved[3];		/* Reserved for future compatibility */
};

fb_ops是提供给底层设备驱动的一个接口。当我们编写一个FrameBuffer的时候,就要依照Linux FrameBuffer编程的套路,填写fb_ops结构体。

struct fb_ops {
  /* open/release and usage marking */
  struct module *owner;
  int (*fb_open)(struct fb_info *info, int user);
  int (*fb_release)(struct fb_info *info, int user);

  /* For framebuffers with strange non linear layouts or that do not
   * work with normal memory mapped access
   */
  ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
         size_t count, loff_t *ppos);
  ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
          size_t count, loff_t *ppos);

  /* checks var and eventually tweaks it to something supported,
   * DO NOT MODIFY PAR */
  int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

  /* set the video mode according to info->var */
  int (*fb_set_par)(struct fb_info *info);

  /* set color register */
  int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
          unsigned blue, unsigned transp, struct fb_info *info);

  /* set color registers in batch */
  int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

  /* blank display */
  int (*fb_blank)(int blank, struct fb_info *info);

  /* pan display */
  int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

  /* Draws a rectangle */
  void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
  /* Copy data from area to another */
  void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
  /* Draws a image to the display */
  void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

  /* Draws cursor */
  int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

  /* Rotates the display */
  void (*fb_rotate)(struct fb_info *info, int angle);

  /* wait for blit idle, optional */
  int (*fb_sync)(struct fb_info *info);

  /* perform fb specific ioctl (optional) */
  int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
      unsigned long arg);

  /* Handle 32bit compat ioctl (optional) */
  int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
      unsigned long arg);

  /* perform fb specific mmap */
  int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

  /* get capability given var */
  void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
          struct fb_var_screeninfo *var);

  /* teardown any resources to do with this framebuffer */
  void (*fb_destroy)(struct fb_info *info);

  /* called at KDB enter and leave time to prepare the console */
  int (*fb_debug_enter)(struct fb_info *info);
  int (*fb_debug_leave)(struct fb_info *info);
};

 Framebuffer模块初始化过程

FrameBuffer驱动是以模块的形式注册到系统中,在模块初始化时,创建FrameBuffer对应的设备文件及proc文件,并注册FrameBuffer设备操作接口函数。 
kernel\drivers\video\Fbmem.c

module_init(fbmem_init);
static int __init fbmem_init(void)
{
  //在proc文件系统中创建/proc/fb文件,并注册proc接口函数
  proc_create("fb", 0, NULL, &fb_proc_fops);
  //注册字符设备fb,并注册fb设备文件的操作接口函数,主设备号为29,
  //#define FB_MAJOR		29 	
  if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
    printk("unable to get major %d for fb devs\n", FB_MAJOR);
  //创建sys/class/graphics目录
  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;
}

首先在proc文件系统中创建fb文件,同时注册操作该文件的接口函数:

static const struct file_operations fb_proc_fops = {
  .owner		= THIS_MODULE,
  .open		= proc_fb_open,
  .read		= seq_read,
  .llseek		= seq_lseek,
  .release	= seq_release,
};

因此可以对/proc/fb文件进行打开,读写操作。然后注册一个主设备号为29的字符设备,fbmem_init函数中注册了字符设备的文件操作接口函数fb_fops,定义如下:

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,
};

Framebuffer驱动注册过程

变量定义:

//保存注册的所有Framebuffer驱动
extern struct fb_info *registered_fb[FB_MAX];
//已注册的Framebuffer驱动的个数
extern int num_registered_fb;

任何一个特定硬件Framebuffer驱动在初始化时都必须向fbmem.c注册,FrameBuffer模块提供了驱动注册接口函数register_framebuffer:

int register_framebuffer(struct fb_info *fb_info)
{
  int ret;
  mutex_lock(®istration_lock);
  ret = do_register_framebuffer(fb_info);
  mutex_unlock(®istration_lock);
  return ret;
}

参数fb_info描述特定硬件的FrameBuffer驱动信息

static int do_register_framebuffer(struct fb_info *fb_info)
{
  int i;
  struct fb_event event;
  struct fb_videomode mode;
  if (fb_check_foreignness(fb_info))
    return -ENOSYS;
  //根据当前注册的fb_info的apertures属性从FrameBuffer驱动数组registered_fb中查询是否存在冲突
  do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
           fb_is_primary_device(fb_info));
  //判断已注册的驱动是否超过32个FrameBuffer驱动
  if (num_registered_fb == FB_MAX)
    return -ENXIO;
  //增加已注册的驱动个数
  num_registered_fb++;
  //从数组registered_fb中查找空闲元素,用于存储当前注册的fb_info
  for (i = 0 ; i < FB_MAX; i++)
    if (!registered_fb[i])
      break;
  //将当前注册的fb_info在数组registered_fb中的索引位置保存到fb_info->node
  fb_info->node = i;
  //初始化当前注册的fb_info的成员信息
  atomic_set(&fb_info->count, 1);
  mutex_init(&fb_info->lock);
  mutex_init(&fb_info->mm_lock);
    //在/dev目录下创建一个fbx的设备文件,次设备号就是该fb_info在数组registered_fb中的索引
  fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
  if (IS_ERR(fb_info->dev)) {
    printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
    fb_info->dev = NULL;
  } else
    //初始化fb_info
    fb_init_device(fb_info);
  if (fb_info->pixmap.addr == NULL) {
    fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
    if (fb_info->pixmap.addr) {
      fb_info->pixmap.size = FBPIXMAPSIZE;
      fb_info->pixmap.buf_align = 1;
      fb_info->pixmap.scan_align = 1;
      fb_info->pixmap.access_align = 32;
      fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
    }
  }	
  fb_info->pixmap.offset = 0;
  if (!fb_info->pixmap.blit_x)
    fb_info->pixmap.blit_x = ~(u32)0;
  if (!fb_info->pixmap.blit_y)
    fb_info->pixmap.blit_y = ~(u32)0;
  if (!fb_info->modelist.prev || !fb_info->modelist.next)
    INIT_LIST_HEAD(&fb_info->modelist);
  fb_var_to_videomode(&mode, &fb_info->var);
  fb_add_videomode(&mode, &fb_info->modelist);
  //将特定硬件对应的fb_info注册到registered_fb数组中
  registered_fb[i] = fb_info;
  event.info = fb_info;
  if (!lock_fb_info(fb_info))
    return -ENODEV;
  //使用Linux事件通知机制发送一个FrameBuffer注册事件FB_EVENT_FB_REGISTERED
  fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
  unlock_fb_info(fb_info);
  return 0;
}

注册过程就是将指定的设备驱动信息fb_info存放到registered_fb数组中。因此在注册具体的fb_info时,首先要构造一个fb_info数据结构,并初始化该数据结构,该结构用于描述一个特定的FrameBuffer驱动。

fbX设备文件的打开过程

open("/dev/fb0")打开设备文件fb0对应的操作过程如下:

static int fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
  //从文件节点中取得次设备号
  int fbidx = iminor(inode);
  struct fb_info *info;
  int res = 0;
  //根据次设备号从registered_fb数组中取出对应的fb_info
  info = get_fb_info(fbidx);
  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;
  }
  //将当前的fb_info保存到/dev/fbx设备文件的private_data成员中
  file->private_data = info;
  if (info->fbops->fb_open) {
    //调用当前fb_info的fb_open函数打开当前FrameBuffer设备
    res = info->fbops->fb_open(info,1);
    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;
}
打开过程很简单,首先从文件节点中取出要打开FrameBuffer的fb_info数据信息,并保存到设备文件file的private_data变量中,然后调用当前fb_info中的fb_open函数完成设备打开过程。该函数在构造具体的fb_info并注册FrameBuffer时,就已注册了对应的打开操作函数指针。


fbX设备文件的映射过程

static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
  //从文件节点中取出fb_info,并且判断是否和private_data变量中的fb_info相同
  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);
  //如果fb_info中注册了fb_mmap函数,则调用fb_info中的fb_mmap来完成地址空间映射
  if (fb->fb_mmap) {
    int res;
    res = fb->fb_mmap(info, vma);
    mutex_unlock(&info->mm_lock);
    return res;
  }
  //如果具体的fb_info没有实现fb_mmap
  start = info->fix.smem_start;
  len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
  if (off >= len) {
    /* memory mapped 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);
  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;
}
这里和fb打开过程类似,仍然是调用具体的fb_info的映射函数来完成地址空间映射过程,但是也有区别,就是在具体的fb_info没有实现地址空间映射时,就在FrameBuffer这一层完成映射过程。

fbX设备文件的命令控制过程

FrameBuffer

Fb_info

FBIOGET_VSCREENINFO

 

FBIOPUT_VSCREENINFO

 

FBIOGET_FSCREENINFO

 

FBIOPUTCMAP

 

FBIOGETCMAP

 

FBIOPAN_DISPLAY

 

FBIO_CURSOR

 

FBIOGET_CON2FBMAP

 

FBIOPUT_CON2FBMAP

 

FBIOBLANK

 

 

 

fb->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))
      return -ENODEV;
    var = info->var;
    unlock_fb_info(info);

    ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
    break;
  case FBIOPUT_VSCREENINFO:
    if (copy_from_user(&var, argp, sizeof(var)))
      return -EFAULT;
    if (!lock_fb_info(info))
      return -ENODEV;
    console_lock();
    info->flags |= FBINFO_MISC_USEREVENT;
    ret = fb_set_var(info, &var);
    info->flags &= ~FBINFO_MISC_USEREVENT;
    console_unlock();
    unlock_fb_info(info);
    if (!ret && copy_to_user(argp, &var, sizeof(var)))
      ret = -EFAULT;
    break;
  case FBIOGET_FSCREENINFO:
    if (!lock_fb_info(info))
      return -ENODEV;
    fix = info->fix;
    unlock_fb_info(info);

    ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
    break;
  case FBIOPUTCMAP:
    if (copy_from_user(&cmap, argp, sizeof(cmap)))
      return -EFAULT;
    ret = fb_set_user_cmap(&cmap, info);
    break;
  case FBIOGETCMAP:
    if (copy_from_user(&cmap, argp, sizeof(cmap)))
      return -EFAULT;
    if (!lock_fb_info(info))
      return -ENODEV;
    cmap_from = info->cmap;
    unlock_fb_info(info);
    ret = fb_cmap_to_user(&cmap_from, &cmap);
    break;
  case FBIOPAN_DISPLAY:
    if (copy_from_user(&var, argp, sizeof(var)))
      return -EFAULT;
    if (!lock_fb_info(info))
      return -ENODEV;
    console_lock();
    ret = fb_pan_display(info, &var);
    console_unlock();
    unlock_fb_info(info);
    if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
      return -EFAULT;
    break;
  case FBIO_CURSOR:
    ret = -EINVAL;
    break;
  case FBIOGET_CON2FBMAP:
    if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
      return -EFAULT;
    if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
      return -EINVAL;
    con2fb.framebuffer = -1;
    event.data = &con2fb;
    if (!lock_fb_info(info))
      return -ENODEV;
    event.info = info;
    fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
    unlock_fb_info(info);
    ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
    break;
  case FBIOPUT_CON2FBMAP:
    if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
      return -EFAULT;
    if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
      return -EINVAL;
    if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
      return -EINVAL;
    if (!registered_fb[con2fb.framebuffer])
      request_module("fb%d", con2fb.framebuffer);
    if (!registered_fb[con2fb.framebuffer]) {
      ret = -EINVAL;
      break;
    }
    event.data = &con2fb;
    if (!lock_fb_info(info))
      return -ENODEV;
    event.info = info;
    ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
    unlock_fb_info(info);
    break;
  case FBIOBLANK:
    if (!lock_fb_info(info))
      return -ENODEV;
    console_lock();
    info->flags |= FBINFO_MISC_USEREVENT;
    ret = fb_blank(info, arg);
    info->flags &= ~FBINFO_MISC_USEREVENT;
    console_unlock();
    unlock_fb_info(info);
    break;
  default:
    if (!lock_fb_info(info))
      return -ENODEV;
    fb = info->fbops;
    if (fb->fb_ioctl)
      ret = fb->fb_ioctl(info, cmd, arg);
    else
      ret = -ENOTTY;
    unlock_fb_info(info);
  }
  return ret;
}
FrameBuffer驱动的框架就介绍到这来,总结一下:

1)构建一个fb_info数据结构,用来描述帧缓冲设备;

2)调用FrameBuffer驱动模块提供的接口函数register_framebuffer来注册帧缓冲设备;

3)对FrameBuffer设备文件的操作过程是,首先执行FrameBuffer驱动函数,然后根据注册的帧缓冲设备的次设备号得到注册的fb_info,最后调用具体的帧缓冲设备的操作函数;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值