`
des40des
  • 浏览: 15267 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

FrameBuffer 原理、实现与应用

 
阅读更多

FrameBuffer 原理、实现与应用
2011年03月22日
  本文转自:http://www.dzjs.net/html/qianrushixitong/2007/0516 /2090.html
  个人Notes: What: A framebuffer device is an abstraction for the graphic hardware. It represents (名字源于)the frame buffer of some video  hardware, and allows application software to access the graphic hardware through a well-defined interface, so  that the software doesn't need to know anything about the low-level interface stuff [Taken from Geert  Uytterhoeven's framebuffer.txt in the linux kernel sources]
  Why:方便导向,轻松控制导向, 提供源码级跨平台、统一的图形控制接口。
  How: 内核增加抽象层(规范接口)、相关显卡驱动"framebuffer规范化"。
  Use:
  控制方法
  内核:内核源码里写死(部分参数)、传递内核启动参数。
  shell命令:系统调用/库函数接口->fbset(需要显卡framebuffer驱动的支持)。 
  图形界面:
  Notes:
  (1)名字源于显卡的帧缓存概念,但实则图形设备(主要是显卡)的完整抽象接口层,不是非要不可,但有了方便很多。=》同一功能通常有多种不同实现策略,按需选择即可!
  (2)所有显示任务由cpu完成(读写framebuffer区),速度不及专用显卡驱动。
  (3)层次高于显卡驱动,相当于显卡驱动的外衣,该外衣并非非穿不可,然从linux 2.2.*后通常会穿。
  (4)查看具体的fb:  lsmod|grep fb
  一、FrameBuffer的原理
  FrameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。
  Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
  但Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池.CPU将运算后的结果放到这个水池,水池再将结果流到显示器.中间不会对数据做处理. 应用程序也可以直接读写这个水池的内容.在这种机制下,尽管Framebuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU负担很重.
  framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。
  可以用命令: #dd if=/dev/zero of=/dev/fb 清空屏幕.
  如果显示模式是 1024x768-8 位色,用命令:$ dd if=/dev/zero of=/dev/fb0 bs=1024 count=768 清空屏幕;
  用命令: #dd if=/dev/fb of=fbfile  可以将fb中的内容保存下来;
  可以重新写回屏幕: #dd if=fbfile of=/dev/fb;
  在使用Framebuffer时,Linux是将显卡置于图形模式下的.
  在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0 设备,并通过 mmap 系统调用进行地址映射,随后用 memset 将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式):
  int fb;
  unsigned char* fb_mem;
  fb = open ("/dev/fb0", O_RDWR);
  fb_mem = mmap (NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
  memset (fb_mem, 0, 1024*768);
  FrameBuffer 设备还提供了若干 ioctl 命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。
  通过 FrameBuffer 设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(2.4.9)中,就包含有对 S3、Matrox、nVidia、3Dfx 等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将 PCI 设备的内存I/O(memio)映射到进程的地址空间。这些 memio 一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。
  PCI 设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为"memio"。一旦被映射到物理内存,Linux 的普通进程就可以通过 mmap 将这些内存 I/O 映射到进程地址空间,这样就可以直接访问这些寄存器了。
  当然,因为不同的显示芯片具有不同的加速能力,对memio 的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。
  FrameBuffer 只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在 FrameBuffer 之上进行图形编程,还需要自己动手完成其他许多工作。
  二、FrameBuffer在Linux中的实现和机制
  Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。
  (一)、分析Framebuffer设备驱动
  需要特别提出的是在INTEL平台上,老式的VESA 1.2 卡,如CGA/EGA卡,是不能支持Framebuffer的,因为Framebuffer要求显卡支持线性帧缓冲,即CPU可以访问显缓冲中的每一位,但是VESA 1.2 卡只能允许CPU一次访问64K的地址空间。
  FrameBuffer设备驱动基于如下两个文件:
  1) linux/include/linux/fb.h
  2) linux/drivers/video/fbmem.c
  下面分析这两个文件。
  1、fb.h
  几乎主要的结构都是在这个中文件定义的。这些结构包括:
  1)fb_var_screeninfo
  这个结构描述了显示卡的特性:
  struct fb_var_screeninfo
  {
  __u32 xres; /* visible resolution */
  __u32 yres;
  __u32 xres_virtual; /* virtual resolution */
  __u32 yres_virtual;
  __u32 xoffset; /* offset from virtual to visible resolution */
  __u32 yoffset;
  __u32 bits_per_pixel; /* guess what */
  __u32 grayscale; /* != 0 Gray levels instead of colors */
  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; /* height of picture in mm */
  __u32 width; /* width of picture in mm */
  __u32 accel_flags; /* acceleration flags (hints) */
  /* Timing: All values in pixclocks, except pixclock (of course) */
  __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 reserved[6]; /* Reserved for future compatibility */
  };
  2) fb_fix_screeninfon
  这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
  struct fb_fix_screeninfo {
  char id[16]; /* identification string eg "TT Builtin" */
  unsigned long smem_start; /* Start of frame buffer mem */
  /* (physical address) */
  __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 */
  /* (physical address) */
  __u32 mmio_len; /* Length of Memory Mapped I/O */
  __u32 accel; /* Type of acceleration available */
  __u16 reserved[3]; /* Reserved for future compatibility */
  };
  3) fb_cmap
  描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜色映射信息.
  struct fb_cmap {
  __u32 start; /* First entry */
  __u32 len; /* Number of entries */
  __u16 *red; /* Red values */
  __u16 *green;
  __u16 *blue;
  __u16 *transp; /* transparency, can be NULL */
  };
  4) fb_info
  定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。
  struct fb_info {
  char modename[40]; /* default video mode */
  kdev_t node;
  int flags;
  int open; /* Has this been open already ? */
  #define FBINFO_FLAG_MODULE 1 /* Low-level driver is a module */
  struct fb_var_screeninfo var; /* Current var */
  struct fb_fix_screeninfo fix; /* Current fix */
  struct fb_monspecs monspecs; /* Current Monitor specs */
  struct fb_cmap cmap; /* Current cmap */
  struct fb_ops *fbops;
  char *screen_base; /* Virtual address */
  struct display *disp; /* initial display variable */
  struct vc_data *display_fg; /* Console visible on this display */
  char fontname[40]; /* default font name */
  devfs_handle_t devfs_handle; /* Devfs handle for new name */
  devfs_handle_t devfs_lhandle; /* Devfs handle for compat. symlink */
  int (*changevar)(int); /* tell console var has changed */
  int (*switch_con)(int, struct fb_info*);
  /* tell fb to switch consoles */
  int (*updatevar)(int, struct fb_info*);
  /* tell fb to update the vars */
  void (*blank)(int, struct fb_info*); /* tell fb to (un)blank the screen */
  /* arg = 0: unblank */
  /* arg > 0: VESA level (arg-1) */
  void *pseudo_palette; /* Fake palette of 16 colors and
  the cursor's color for non
  palette mode */
  /* From here on everything is device dependent */
  void *par;
  };
  5) struct fb_ops
  用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。
  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);
  /* get non settable parameters */
  int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con,
  struct fb_info *info);
  /* get settable parameters */
  int (*fb_get_var)(struct fb_var_screeninfo *var, int con,
  struct fb_info *info);
  /* set settable parameters */
  int (*fb_set_var)(struct fb_var_screeninfo *var, int con,
  struct fb_info *info);
  /* get colormap */
  int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con,
  struct fb_info *info);
  /* set colormap */
  int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con,
  struct fb_info *info);
  /* pan display (optional) */
  int (*fb_pan_display)(struct fb_var_screeninfo *var, int con,
  struct fb_info *info);
  /* perform fb specific ioctl (optional) */
  int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd,
  unsigned long arg, int con, struct fb_info *info);
  /* perform fb specific mmap */
  int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma);
  /* switch to/from raster image mode */
  int (*fb_rasterimg)(struct fb_info *info, int start);
  };
  6) structure map
  struct fb_info_gen | struct fb_info | fb_var_screeninfo
  | | fb_fix_screeninfo
  | | fb_cmap
  | | modename[40]
  | | fb_ops ---|--->ops on var
  | | ... | fb_open
  | | | fb_release
  | | | fb_ioctl
  | | | fb_mmap
  | struct fbgen_hwswitch -|-> detect
  | | encode_fix
  | | encode_var
  | | decode_fix
  | | decode_var
  | | get_var
  | | set_var
  | | getcolreg
  | | setcolreg
  | | pan_display
  | | blank
  | | set_disp
  [编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了]
  这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用.
  2、 fbmem.c
  fbmem.c 处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.
  1) 全局变量
  struct fb_info *registered_fb[FB_MAX];
  int num_registered_fb;
  这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info 结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info 结构就会添加到这个结构中,同时num_registered_fb 为自动加1.
  static struct {
  const char *name;
  int (*init)(void);
  int (*setup)(void);
  } fb_drivers[] __initdata= { ....};
  如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。
  static struct file_operations fb_ops ={
  owner: THIS_MODULE,
  read: fb_read,
  write: fb_write,
  ioctl: fb_ioctl,
  mmap: fb_mmap,
  open: fb_open,
  release: fb_release
  };
  这是一个提供给应用程序的接口.
  2)fbmem.c 实现了如下函数.
  register_framebuffer(struct fb_info *fb_info);
  unregister_framebuffer(struct fb_info *fb_info);
  这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
  (二)一个LCD显示芯片的驱动实例
  以Skeleton LCD 控制器驱动为例,在LINUX中存有一个/fb/skeleton.c的skeleton的Framebuffer驱动程序,很简单,仅仅是填充了 fb_info结构,并且注册/注销自己。设备驱动是向用户程序提供系统调用接口,所以我们需要实现底层硬件操作并且定义file_operations 结构来向系统提供系统调用接口,从而实现更有效的LCD控制器驱动程序。
  1)在系统内存中分配显存
  在fbmem.c文件中可以看到, file_operations 结构中的open()和release()操作不需底层支持,但read()、write()和 mmap()操作需要函数fb_get_fix()的支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数的LCD控制器都没有自己的显存空间,被分配的地址空间的起始地址与长度将会被填充到fb_fix_screeninfo 结构的smem_start 和smem_len 的两个变量中.被分配的空间必须是物理连续的。
  2)实现 fb_ops 中的函数
  用户应用程序通过ioctl()系统调用操作硬件,fb_ops 中的函数就用于支持这些操作。(注: fb_ops结构与file_operations 结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
  ioctl()系统调用在文件fbmem.c中实现,通过观察可以发现ioctl()命令与fb_ops's 中函数的关系:
  FBIOGET_VSCREENINFO fb_get_var
  FBIOPUT_VSCREENINFO fb_set_var
  FBIOGET_FSCREENINFO fb_get_fix
  FBIOPUTCMAP fb_set_cmap
  FBIOGETCMAP fb_get_cmap
  FBIOPAN_DISPLAY fb_pan_display
  如果我们定义了fb_XXX_XXX 方法,用户程序就可以使用FBIOXXXX宏的ioctl()操作来操作硬件。
  文件linux/drivers/video/fbgen.c或者linux/drivers/video目录下的其它设备驱动是比较好的参考资料。在所有的这些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:
  1)检测是否必须设定模式
  2)设定模式
  3)设定颜色映射
  4) 根据以前的设定重新设置LCD控制器的各寄存器。
  第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到LCD控制器的各寄存器中(一般通过fb_set_var() 函数),显存中的内容将自动被LCD控制器输出到屏幕上。另一方面,用户程序通过函数mmap()将显存映射到用户进程地址空间中,然后用户进程向映射空间发送的所有数据都将会被显示到LCD显示器上。
  三、FrameBuffer的应用
  (一)、一个使用FrameBuffer的例子 FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
  由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动的话,是可以实现的) .
  对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。
  好,现在可以让我们开始实现直接写屏:
  1、打开一个FrameBuffer设备
  2、通过mmap调用把显卡的物理内存空间映射到用户空间
  3、直接写内存。
  /********************************
  File name : fbtools.h
  */
  #ifndef _FBTOOLS_H_
  #define _FBTOOLS_H_
  #include 
  //a framebuffer device structure;
  typedef struct fbdev{
  int fb;
  unsigned long fb_mem_offset;
  unsigned long fb_mem;
  struct fb_fix_screeninfo fb_fix;
  struct fb_var_screeninfo fb_var;
  char dev[20];
  } FBDEV, *PFBDEV;
  //open & init a frame buffer
  //to use this function,
  //you must set FBDEV.dev="/dev/fb0"
  //or "/dev/fbX"
  //it's your frame buffer.
  int fb_open(PFBDEV pFbdev);
  //close a frame buffer
  int fb_close(PFBDEV pFbdev);
  //get display depth
  int get_display_depth(PFBDEV pFbdev);
  //full screen clear
  void fb_memset(void *addr, int c, size_t len);
  #endif 
  /******************
  File name : fbtools.c
  */
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include "fbtools.h"
  #define TRUE        1
  #define FALSE       0
  #define MAX(x,y)        ((x)>(y)?(x)y))
  #define MIN(x,y)        ((x)fb = open(pFbdev->dev, O_RDWR);
  if(pFbdev->fb dev);
  return FALSE;
  }
  if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_va r)))
  {
  printf("ioctl FBIOGET_VSCREENINFO\n");
  return FALSE;
  }
  if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fi x)))
  {
  printf("ioctl FBIOGET_FSCREENINFO\n");
  return FALSE;
  }
  //map physics address to virtual address
  pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) & (~PAGE_MASK);
  pFbdev->fb_mem = (unsigned long int)mmap(NULL, pFbdev->fb_fix.smem_len + pFbdev->fb_mem_offset,              PROT_READ | PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);
  if (-1L == (long) pFbdev->fb_mem)
  {
  printf("mmap error! mem:%d offset:%d\n", pFbdev->fb_mem, pFbdev->fb_mem_offset);
  return FALSE;
  }
  return TRUE;
  }
  //close frame buffer
  int fb_close(PFBDEV pFbdev)
  {
  close(pFbdev->fb);
  pFbdev->fb=-1;
  }
  //get display depth
  int get_display_depth(PFBDEV pFbdev);
  {
  if(pFbdev->fbfb_var.bits_per_pixel;
  }
  //full screen clear
  void fb_memset (void *addr, int c, size_t len)
  {
  memset(addr, c, len);
  }
  //use by test
  #define DEBUG
  #ifdef DEBUG
  main()
  {
  FBDEV fbdev;
  memset(&fbdev, 0, sizeof(FBDEV));
  strcpy(fbdev.dev, "/dev/fb0");
  if(fb_open(&fbdev)==FALSE)
  {
  printf("open frame buffer error\n");
  return;
  }
  fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0, fbdev.fb_fix.smem_len);
  fb_close(&fbdev);
  }
  (二)基于Linux核心的汉字显示的尝试
  我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下运行一个如下的简单程序。
  main ( )
  {
  puts("hello, world.\n");
  }
  puts 函数向缺省输出文件(/dev/tty1)发出写的系统调用write(2)。系统调用到linux核心里面对应的核心函数是console.c中的 con_write(),con_write()最终会调用do_con_write( )。在do_con_write( )中负责把"hello, world.\n"这个字符串放到tty1对应的缓冲区中去。
  do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下do_con_write()这个函数的声明。
  static int do_con_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
  其中tty是指向tty_struct结构的指针,这个结构里面存放着关于这个tty的所有信息(请参照 linux/include/linux/tty.h)。Tty_struct结构中定义了通用(或高层)tty的属性(例如宽度和高度等)。在do_con_write( )函数中用到了tty_struct结构中的driver_data变量。driver_data是一个vt_struct指针。在vt_struct结构中包含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的数组vc_cons,这个数组就是各虚拟终端的私有数据。
  static int do_con_write(struct tty_struct * tty, int from_user,const unsigned char *buf, int count)
  {
  struct vt_struct *vt = (struct vt_struct *)tty->driver_data;//我们用到了driver_data变量
  . . . . .
  currcons = vt->vc_num; file://我们在这里的vc_nums就是1
  . . . . .
  }
  要访问虚拟终端的私有数据,需使用vc_cons〔currcons〕.d指针。这个指针指向的结构含有当前虚拟终端上光标的位置、缓冲区的起始地址、缓冲区大小等等。
  "hello, world.\n"中的每一个字符都要经过conv_uni_to_pc( )这个函数转换成8位的显示字符。这要做的主要目的是使不同语言的国家能把16位的UniCode码映射到8位的显示字符集上,目前还是主要针对欧洲国家的语言,映射结果为8位,不包含对双字节(double byte)的范围。
  这种UNICODE到显示字符的映射关系可以由用户自行定义。在缺省的映射表上,会把中文的字符映射到其他的字符上,这是我们不希望看到也是不需要的。所以我们有两个选择∶ 不进行conv_uni_to_pc( )的转换。
  加载符合双字节处理的映射关系,即对非控制字符进行1对1的不变映射。我们自己定制的符合这种映射关系的UNICODE码表是direct.uni。要想查看/装载当前系统的unicode映射表,可使外部命令loadunimap。
  经过conv_uni_to_pc( )转换之后,"hello, world.\n"中的字符被一个一个地填写到tty1的缓冲区中。然后do_con_write( )调用下层的驱动,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA显存中去)。
  sw->con_putcs(vc_cons〔currcons〕.d, (u16 *)draw_from, (u16*)draw_to-(u16 *)draw_from, y, draw_x);
  之所以要调用底层驱动,是因为存在不同的显示设备,其对应VGA显存的存取方式也不一样。
  上面的Sw->con_putcs( )就会调用到fbcon.c中的fbcon_putcs()函数(con_putcs是一个函数的指针,在Framebuffer模式下指向 fbcon_putcs()函数)。也就是说在do_con_write( )函数中是直接调用了fbcon_putcs()函数来进行字符的绘制。比如说在256色模式下,真正负责输出的函数是void fbcon_cfb8_putcs(struct vc_data *conp, struct display *p,const unsigned short *s, int count, int yy, int xx)
  显示中文
  比如说我们试图输出一句中文∶putcs(你好\n );(你好的内码为0xc4,0xe3,0xba,0xc3)。这时候会怎么样呢,有一点可以肯定,"你好"肯定不会出现在屏幕上,国为核心中没有汉字字库,中文显示就是无米之炊了.
  1 在负责字符显示的void fbcon_cfb8_putcs( )函数中,原有操作如下∶对于每个要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(低位字节是ASCII码,高8位是字符的属性),由于汉字是双字节编码方式,所以这种操作是不可能显示出汉字的,只能显示出xxxx_putcs()是一个一个VGA字符.
  要解决的问题∶
  确保在do_con_write( )时uni□pc转换不会改变原有编码。一个很直接的实现方式就是加载一个我们自己定制的UNICODE映射表,loadunimapdirect.uni,或者直接把direct.uni置为核心的缺省映射表。
  针对如上问题,我们要做的第一个尝试方案是如下。
  首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs()函数,在fbcon_cfb8_putcs( )中一次读两个WORD,检查这两个WORD的低位字节是否能拼成一个汉字,如果发现能拼成一个汉字,就算出这个汉字在汉字字库中的偏移,然后把它当成一个16 x 16的VGA字符来显示。
  试验的结果表明∶ 能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一串汉字,则这半个汉字后面的汉字都会是乱码。这是半个汉字的问题。
  光标移动会破坏汉字的显示。表现为,光标移动过的汉字会变成乱码。这是因为光标的更新是通过xxxx_putc( )函数来完成的。
  xxxx_putc( )函数与xxxx_putcs( )函数实现的功能类似,但是xxxx_putc()函数只刷新一个字符而不是一个字符串,因而xxxx_putc()的输入参数是一个整数,而不是一个字符串的地址。Xxxx_putc( )函数的声明如下∶void fbcon_cfb8_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx)
  下一个尝试方案就是同时修改xxxx_putcs( )函数和xxxx_putc()函数。为了解决半个汉字的问题,每一次输出之前,都从屏幕当前行的起始位置开始扫描,以确定要输出的字符是否落在半个汉字的位置上。如果是半个汉字的位置,则进行相应的调整,即从向前移动一个字节的位置开始输出。
  这个方案有一个困难,即xxxx_putc( )函数不用缓冲区的地址,而是用一个整数作为参数。所以xxxx_putc( )无法直接利用相邻的字符来判别该定符是否是汉字。
  解决方案是,利用xxxx_putc( )的光标位置参数(yy, xx),可以逆推出该字符在缓冲区中的位置。但仍有一些小麻烦,在Linux的虚拟终端下,用户可能会上卷该屏幕(shift + pageup),导致光标的y座标和相应字符在缓冲区的行数不一致。相应的解决方案是,在逆推的过程中,考虑卷屏的参量。
  这样一来,我们就又进了一步,得到了一个相对更好的版本。但仍有问题没有解决。敲入turbonetcfg,会发现菜单的边框字符也被当成汉字显示。这是因为,这种边框字符是扩展字符,也使用了字符的第8位,因而被当作汉字来显示。例如,单线一的制表符内码为0xC4,当连成一条长线就是由一连串0xC4组成,而0xC4C4正是汉字哪。于是水平的制表符被一连串的哪字替代了。要解决这个问题就非常不容易了,因为制表符的种类比较多,而且垂直制表符与其后面字符的组合型式又多种多样,因而很难判断出相应位置的字符是不是制表符,从理论上说,无论采取什么样的排除算法,都必然存在误判的情况,因为总存在二义性,没有充足的条件来推断出当前字符究竟是制表符还是汉字。
  我们一方面寻找更好的排除组合算法,一方面试图寻找其它的解决方案。要想从根本上解决定个问题,必须利用其它的辅助信息,仅仅从缓冲区的字符来判断是不够的。
  经过一番努力,我们发现,在UNIX中使用扩展字符时,都要先输出字符转义序列(Escape sequence)来切换当前字符集。字符转义序列是以控制字符Esc为首的控制命令,在UNIX的虚拟终端中完成终端控制命令,这种命令包括,移动光标座标、卷屏、删除、切换字符集等等。也就是说在输出代表制表符的字符串之前,通常是要先输出特定的字符转义序列。在console.c里,有根据字符转义序列命令来记录字符状态的变量。结合该变量提供的信息,就可以非常干净地把制表符与汉字区别开来。
  在如上思路的指引下,我们又产生了新的解决方案。经过改动得到了另一各版本.
  在这个新版本上,turbonetcfg在初次绘制的时候,制表符与汉字被清晰地区分开来,结果是非常正确的。但还有新的问题存在∶turbonetcfg 在重绘的时候(如切换虚拟终端或是移动鼠标光标的时候),制表符还是变成了汉字,因为重绘完全依赖于缓冲区,而这时用来记录字符集状态的变量并不反映当前字符集状态。问题还是没有最终解决。我们又回到了起点。∶( 看来问题的最终解决手段必须是把字符集的状态伴随每一个字符存在缓冲区中。让我们来研究一下缓冲区的结构。每一个字符占用16bit的缓冲区,低8位是ASCII值,完全被利用,高8位包含前景颜色和背景颜色的属性,也没有多余的空间可以利用。因而只能另外开辟新的缓冲区。为了保持一致性,我们决定在原来的缓冲区后面添加相同大小的缓冲区,用来存放是否是汉字的信息。
  也许有读者会问,我们只需要为每个字符添加一bit的信息来标志是否是汉字就足够了,为什么还要开辟与原缓冲区大小相同的双倍缓冲区,是不是太浪费呢?我们先放下这个问题,稍后再作回答。
  其实,如果再添加一bit来标志是当前字符是汉字的左半边还是右半边的话,就会省去扫描屏幕上当前整行字符串的工作,这样一来,编程会更简单。但是有读者会问,即使是这样,使用8bit总够用了吧?为什么还要使用16bit呢?
  我们的作法是∶用低8位来存放汉字另外一半的内码,用高8位中的2 bit来存放上面所讲的辅助信息,高8位的剩余6位可以用来存放汉字或其它编码方式(如BIG5或日文、韩文)的信息,从而使我们可以实现同屏显示多种双字节语言的字符而不会有相互干扰。另外,在编程时,双倍缓冲也比较容易计算。这样我们就回答了如上的两个问题。
  迄今为止,我们有了一套彻底解决汉字和制表符相互干扰、半个汉字的刷新、重绘等问题的方案。剩下的就是具体编程实现的问题了。
  但是,由于Framebuffer的驱动很多,修改每一个驱动的xxxx_putc()函数和xxxx_putcs( )函数会是一项不小的工作,而且,改动驱动程序后,每种驱动的测试也是很麻烦的,尤其是对于有硬件加速的显卡,修改和测试会更不容易。那么,存不存在一种不需要修改显卡驱动程序的方法呢?
  经过努力,我们发现,可以在调用xxxx_putcs( )或xxxx_putc()函数输出汉字之前,修改vga字库的指针使其指向所需显示的汉字在汉字字库中的位置,即把一个汉字当成两个vga ASCII字符输出。也就是说,在内核中存在两个字库,一个是原有的vga字符字库,另一个是汉字字库,当我们需要输出汉字的时候,就把vga字库的指针指向汉字字库的相应位置,汉字输出完之后,再把该指针指向vga字库的原有位置。
  这样一来,我们只需要修改fbcon.c和console.c,其中console.c负责维护双倍缓冲区,把每一个字符的信息存入附加的缓冲区;而fbcon.c负责利用双倍缓冲区中附加的信息,调整vga字库的指针,调用底层的显示驱动程序。这里还有几个需要注意的地方∶ 由于屏幕重绘等原因,调用底层驱动xxxx_putc( )和xxxx_putcs()的地方有多处。我们作了两个函数分别包装这两个调用,完成替换字库、调用xxxx_putcs( )或xxxx_putc( )、恢复字库等功能。
  为了实现向上滚屏(shift + pageup)时也能看到汉字,我们需要作另外的修改。
  Linux 在设计虚拟终端的时候,提供了回顾被卷出屏幕以外的信息的功能,这就是用热键来向上滚屏(shift + pageup)。当前被使用的虚拟终端拥有一个公共的缓冲区(soft back),用来存放被滚出屏幕以外的信息。当切换虚拟终端的时候,公共缓冲区的内容会被清除而被新的虚拟终端使用。向上滚屏的时候,显示的是公共缓冲区中的内容。因此,如果我们想在向上滚屏的时候看到汉字,公共缓冲区也必须加倍,以确保没有信息丢失。当滚出屏幕的信息向公共缓冲区填写的时候,必须把相应的附加信息也填写进公共缓冲区的附加区域。这就要求fbcon.c必须懂得利用公共缓冲区的附加信息。
  当然,有另外一种偷懒的方法,那就是不允许用户向上滚屏,从而避免对公区缓冲区的处理。
  把不同的编码方式(GB、BIG5、日文和韩文)写成不同的module,以实现动态加载,从而使得扩展新的编码方式不需要重新编译核心。
  测试 本文实现的Kernel Patch文件(patch.kernel.chinese)可以从http://www.turbolinux.com.cn 下载。Cd /usr/src/(该目录下应有Linux核心源程序所在的目录linux/) patch -p0 -b  Virtual Frame Buffer support (ONLY FOR TESTING!)
   8 bpp packed pixels support
   16 bpp packed pixels support
   VGA characters/attributes support
  〔*〕 Select compiled-in fonts
  〔*〕VGA 8x8 font
  〔*〕VGA 8x16 font
  make dep
  make bzImage
  make modules
  make install
  make modules_install
  然后用新的核心启动。
  Insmod encode-gb.o 
  四、其它
  (一)   设置FrameBuffer
  FrameBuffer,可以译作"帧缓冲",有时简称为 fbdrv,基于fbdrv的console也被称之为fbcon。这是一种独立于硬件的抽象图形设备。FrameBuffer的优点在于其高度的可移植 性、易使用性、稳定性。使用Linux内核的 FrameBuffer驱动(vesafb),可以轻松支持到1024X768X32bpp以上的分辩率。而且目前可得到的绝大多数linux版本所发行 的内核中,已经预编译了FrameBuffer支持,通常不需要重新编译内核就可以使用。所以FrameBuffer也是zhcon推荐使用的驱动方式。
  进入FrameBuffer可以简单地在系统启动时向kernel传送vga=mode-number的参数来激活FrameBuffer设备 ,如:
  lilo:linux vga=305
  将会启动1024x768x8bpp模式。
  640x480    800x600    1024x768    1280x1024
  8 bpp      769          771       773        775
  16 bpp     785          788       791        794
  32 bpp     786          789       792        795
  (二)   要使linux缺省进入FrameBuffer,可以修
  /etc/lilo.conf,加入一下语句:
  vga=0x303
  退出编辑,执行:
  lilo -v
  重新启动linux,可以使其进入800x600的256色模式。
  grub也是一样,在grub.conf中的kernel行后面写上vga=xxx就行了,也可以用vga=ask,让系统启动的时候询问你用多大的分辨率
  (三)我编译内核时,选择framebuffer模式,启动时屏幕上有一企鹅图片,不知这是如何造成的这个图片可以去掉或改动吗?
  可以将drivers/video/fbcon.c: fbcon_setup()中if (logo) { } 代码去掉。
  Framebuffer HOWTO  :   http://tldp.org/HOWTO/pdf/Framebuffer-HOWTO.pdf
  其他优秀信息搜集
  专用显卡更快! http://www.minigui.org/cgi-bin/lb5000/topic.cgi?fo rum=6&topic=4255 framebuffer就是把显示屏幕用内存的一块空间来虚拟屏幕而已。然后具体的底层硬件驱动再负责将这一块内存的内容写道显示RAM。此时就可以使用诸如DMA等技术来加快数据的传输速度。当然,如果硬件直接支持,也可以直接将framebuffer映射到显示RAM,从而直接完成写屏幕,不需要再经过中间的传输过程。
  所谓专业的驱动程序,无非就是完成一些更快速的操作,比如你要将屏幕上一块区域反白,如果直接用程序操作,就得先度回这写区域的内容,然后和0xff异或,再写到屏幕上,如此修改一个点的数据就需要三个步骤。但是如果显示硬件可以直接支持这种特性的话,你就可以只需要设定需要操作的区域,然后设定操作的模式(比如异或),显示的硬件就可以帮助你完成剩余的工作,所以速度会成倍的增加。有此可见显示驱动的一斑。 
分享到:
评论

相关推荐

    Android技术内幕.系统卷(扫描版)

    第8章 dalvik虚拟机的构架、原理与实现 /463 8.1 dalvik虚拟机概述 /464 8.1.1 什么是dalvik虚拟机 /464 8.1.2 dalvik虚拟机的功能 /464 8.1.3 dalvik虚拟机与java虚拟机的区别 /465 8.2 dalvik构架与实现 /466 ...

    Android技术内幕.系统卷 pdf

    第8章 dalvik虚拟机的构架、原理与实现 /463 8.1 dalvik虚拟机概述 /464 8.1.1 什么是dalvik虚拟机 /464 8.1.2 dalvik虚拟机的功能 /464 8.1.3 dalvik虚拟机与java虚拟机的区别 /465 8.2 dalvik构架与实现 /466...

    嵌入式系统Linux 下液晶显示的实现

    介绍了嵌入式微处理器PXA270的LCD控制器与SHARP公司的液晶屏LQ035Q7DH06,分析了TFT显示器基本原理,结合它们的工作时序设计了LCD的驱动电路,并在嵌入式操作系统Linux下设计了相应的驱动程序,实现了嵌入式设备的TFT...

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part1

    本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入...

    android系统原理及开发要点详解

     3.3.1 Framebuffer显示驱动 46  3.3.2 Event输入设备驱动 48  3.3.3 v4l2摄像头——视频驱动 50  3.3.4 OSS音频驱动 53  3.3.5 ALSA音频驱动 54  3.3.6 MTD驱动 56  3.3.7 蓝牙驱动 57  3.3.8 Wlan驱动 58 ...

    嵌入式Linux程序设计案例与实验教程(配套光盘)第二部分

    5.1 嵌入式WebServer GoAhead的移植与应用101 5.1.1 嵌入式Web服务器101 5.1.2 GoAhead介绍101 5.1.3 GoAhead在ARM平台上的移植102 5.1.4 页面操作102 实验5.1 嵌入式WebServer GoAhead实验103 5.2 嵌入式Web ...

    嵌入式Linux程序设计案例与实验教程(配套光盘)第三部分

    5.1 嵌入式WebServer GoAhead的移植与应用101 5.1.1 嵌入式Web服务器101 5.1.2 GoAhead介绍101 5.1.3 GoAhead在ARM平台上的移植102 5.1.4 页面操作102 实验5.1 嵌入式WebServer GoAhead实验103 5.2 嵌入式Web ...

    嵌入式Linux程序设计案例与实验教程-实例代码

    5.1 嵌入式WebServer GoAhead的移植与应用101 5.1.1 嵌入式Web服务器101 5.1.2 GoAhead介绍101 5.1.3 GoAhead在ARM平台上的移植102 5.1.4 页面操作102 实验5.1 嵌入式WebServer GoAhead实验103 5.2 ...

    嵌入式Linux程序设计案例与实验教程(配套光盘)第一部分

    5.1 嵌入式WebServer GoAhead的移植与应用101 5.1.1 嵌入式Web服务器101 5.1.2 GoAhead介绍101 5.1.3 GoAhead在ARM平台上的移植102 5.1.4 页面操作102 实验5.1 嵌入式WebServer GoAhead实验103 5.2 嵌入式Web ...

    基于ARM9&Linux的液晶驱动终端设计--【千锋培训】

    文档介绍了1 引言,2 系统组成及工作原理,3 系统主要硬件模块设计,3.1 arm9微控制器,3.2 液晶驱动接口,3.3 CF卡存储接口,4 软件设计,4.1 整体软件架构,4.2 Framebuffer驱动的实现,4.3 应用程序,5 ...

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part3

    本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入...

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part2

    本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入...

    嵌入式Linux之我行系列

    ·嵌入式Linux之我行——虚拟机中实现Linux与Windows之间的文件传输 ·嵌入式Linux之我行——开发环境的建立与Eclipse的使用 ·嵌入式Linux之我行——配置内核时出现“ncurses-devel”错误 ·嵌入式Linux之我行——C...

    基于ARM与FPGA的LCD控制器系统设计

    这里提出一种基于ARM与FPGA的LCD控制器设计方案,该设计方案一方面能够通过操作LinuxOS下的Framebuffer设备提高显存的写入速率及减轻处理器的负担,另一方面用FPGA来实现LCD控制器的设计,开发周期短、功耗低,同时...

    精通Qt4编程(第二版)源代码

    \蔡志明首次引入Qt设计器的使用,绘制并实现了一个查找文件功能的部件,介绍了Qt应用程序中使用ui文件的基本方法以及Qt样式表;较深入地分析了Qt对象模型的一些基本知识,涉及信号和槽机制、Qt元对象系统、属性系统...

    精通qt4编程(源代码)

    \蔡志明首次引入Qt设计器的使用,绘制并实现了一个查找文件功能的部件,介绍了Qt应用程序中使用ui文件的基本方法以及Qt样式表;较深入地分析了Qt对象模型的一些基本知识,涉及信号和槽机制、Qt元对象系统、属性系统...

Global site tag (gtag.js) - Google Analytics