帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备为标准字符设备,主设备号为29,对应于/dev/fbn设备文件。
见:drivers\video\fbmem.c 中的 module_init(fbmem_init); 就可知道 fbmem_init 是其字符设备加载函数,具体定义如下
其中fb_fops定义如下
同时,fb_open()定义如下
当用户使用系统调用打开设备节点时,内核就会调用 fb_open() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_open()函数。
fb_read() 和 fb_write()定义如下
当用户使用系统调用读写设备节点时,内核就会调用 fb_read() 和 fb_write() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_read()和fb_write()函数。
fb_ioctl()函数定义如下
其中,也会尝试去调用具体设备的fbops中的相关函数
大家可以参考下面的结构图,它说明了Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构体由fbmem.c中的fb_fops实例提供,并向下提供了相应的接口,让驱动开发者挂接具体的设备驱动。

见:drivers\video\fbmem.c 中的 module_init(fbmem_init); 就可知道 fbmem_init 是其字符设备加载函数,具体定义如下
点击(此处)折叠或打开
-
static int __init
-
fbmem_init(void)
-
{
-
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
-
-
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
//可知其file_operations操作集为fb_fops //#define FB_MAJOR 29
-
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;
- }
其中fb_fops定义如下
点击(此处)折叠或打开
-
static const struct file_operations fb_fops = {
-
.owner = THIS_MODULE,
-
.read = fb_read,
-
.write = fb_write,
-
.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
- };
同时,fb_open()定义如下
点击(此处)折叠或打开
-
static int
-
fb_open(struct inode *inode, struct file *file)
-
{
-
int fbidx = iminor(inode); //获得设备的次设备号,在register_framebuffer中,次设备号被定为缓冲设备在registered_fb数组中的索引值
-
struct fb_info *info;
-
int res = 0;
-
-
if (fbidx >= FB_MAX) //如果fbidx大于最大设备数目,则返回错误
-
return -ENODEV;
-
#ifdef CONFIG_KMOD
-
if (!(info = registered_fb[fbidx]))
-
try_to_load(fbidx);
-
#endif /* CONFIG_KMOD */
-
if (!(info = registered_fb[fbidx])) //如果通过fbidx无法在registered_fb中,索引到对象的fb_info结构体实例,则返回错误
-
return -ENODEV;
-
if (!try_module_get(info->fbops->owner))
-
return -ENODEV;
-
file->private_data = info; //将得到的fb_info结构体实例,赋给file->private_data,方便后续的操作,可以通过此方法获取此值
-
if (info->fbops->fb_open) { //如果对应的缓冲设备有open函数,则调用。
-
res = info->fbops->fb_open(info,1);
-
if (res)
-
module_put(info->fbops->owner);
-
}
-
return res;
- }
当用户使用系统调用打开设备节点时,内核就会调用 fb_open() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_open()函数。
fb_read() 和 fb_write()定义如下
点击(此处)折叠或打开
-
static ssize_t
-
fb_read(struct file *file, char __user *buf, size_t
count, loff_t *ppos)
-
{
-
unsigned long p = *ppos;
-
struct inode *inode = file->f_path.dentry->d_inode;
-
int fbidx = iminor(inode);
-
struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
-
u32 *buffer, *dst;
-
u32 __iomem *src;
-
int c, i, cnt = 0, err = 0;
-
unsigned long total_size;
-
-
if (!info || ! info->screen_base) //若info不存在
或 DMA缓冲区的虚拟地址不存在,则出错返回
-
return -ENODEV;
-
-
if (info->state != FBINFO_STATE_RUNNING)
-
return -EPERM;
-
-
if (info->fbops->fb_read) //若设备fb_ops操作集中有fb_read函数,则调用之
-
return info->fbops->fb_read(info, buf, count, ppos);
-
-
total_size = info->screen_size;
-
-
if (total_size == 0)
-
total_size = info->fix.smem_len; //total_size为0的话,取
info->fix.smem_len
-
-
if (p >= total_size) //若
读取偏移量 > total_size,则出错返回。
-
return 0;
-
-
if (count >= total_size) //若
读取数目 > total_size,则修正为total_size
-
count = total_size;
-
-
if (count + p > total_size) //若
读取数目 + 偏移量 > total_size,则 读取数目 修正为total_size - 偏移量
-
count = total_size - p;
-
-
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
-
GFP_KERNEL); //动态申请内存,用于缓冲数据,最大申请PAGE_SIZE大小,后面会分段传输
-
if (!buffer)
-
return -ENOMEM;
-
-
src = (u32 __iomem *) (info->screen_base + p); //DMA缓冲区的首地址 + 偏移量
设为源地址
-
-
if (info->fbops->fb_sync) //若设备fb_ops操作集中有fb_sync函数,则调用之
-
info->fbops->fb_sync(info);
-
-
while (count) {
-
c = (count > PAGE_SIZE) ? PAGE_SIZE : count; //缓冲大小为PAGE_SIZE,若count较大,则分段。
-
dst = buffer; //目的地址为缓冲区首地址
-
for (i = c >> 2; i--; ) //每次读取4字节
-
*dst++ = fb_readl(src++);
-
if (c & 3) { //对于最后小于4字节的数据,进行特别操作,每次传输1字节
-
u8 *dst8 = (u8 *) dst;
-
u8 __iomem *src8 = (u8 __iomem *) src;
-
-
for (i = c & 3; i--;)
-
*dst8++ = fb_readb(src8++);
-
-
src = (u32 __iomem *) src8;
-
}
-
-
if (copy_to_user(buf, buffer, c)) { //将内核缓冲区中的数据传输给用户层
-
err = -EFAULT;
-
break;
-
}
-
*ppos += c; //?
-
buf += c; //用户层接收区域需要后移
-
cnt += c; //已传输数据数目需要增加
-
count -= c; //读取的数目需要减少
-
}
-
-
kfree(buffer); //传输完毕后,释放内核缓冲区
-
-
return (err) ? err : cnt;
-
}
-
-
static ssize_t
-
fb_write(struct file *file, const char
__user *buf, size_t count, loff_t *ppos)
-
{
-
unsigned long p = *ppos;
-
struct inode *inode = file->f_path.dentry->d_inode;
-
int fbidx = iminor(inode);
-
struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
-
u32 *buffer, *src;
-
u32 __iomem *dst;
-
int c, i, cnt = 0, err = 0;
-
unsigned long total_size;
-
-
if (!info || !info->screen_base) //若info不存在
或 DMA缓冲区的虚拟地址不存在,则出错返回
-
return -ENODEV;
-
-
if (info->state != FBINFO_STATE_RUNNING)
-
return -EPERM;
-
-
if (info->fbops->fb_write) //若设备fb_ops操作集中有fb_write函数,则调用之
-
return info->fbops->fb_write(info, buf, count, ppos);
-
-
total_size = info->screen_size;
-
-
if (total_size == 0)
-
total_size = info->fix.smem_len; //total_size为0的话,取
info->fix.smem_len
-
-
if (p > total_size) //若
写入偏移量 > total_size,则出错返回。
-
return -EFBIG;
-
-
if (count > total_size) { //若
写入数目 > total_size,则修正为total_size
-
err = -EFBIG;
-
count = total_size;
-
}
-
-
if (count + p > total_size) { //若
读取数目 + 偏移量 > total_size,则 写入数目 修正为 total_size - 偏移量
-
if (!err)
-
err = -ENOSPC;
-
-
count = total_size - p;
-
}
-
-
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
-
GFP_KERNEL); //动态申请内存,用于缓冲数据,最大申请PAGE_SIZE大小,后面会分段传输
-
if (!buffer)
-
return -ENOMEM;
-
-
dst = (u32 __iomem *) (info->screen_base + p); //DMA缓冲区的首地址 + 偏移量
设为目的地址
-
-
if (info->fbops->fb_sync) //若设备fb_ops操作集中有fb_sync函数,则调用之
-
info->fbops->fb_sync(info);
-
-
while (count) { //同读取操作类似,不详述
-
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
-
src = buffer;
-
-
if (copy_from_user(src, buf, c)) {
-
err = -EFAULT;
-
break;
-
}
-
-
for (i = c >> 2; i--; )
-
fb_writel(*src++, dst++);
-
-
if (c & 3) {
-
u8 *src8 = (u8 *) src;
-
u8 __iomem *dst8 = (u8 __iomem *) dst;
-
-
for (i = c & 3; i--; )
-
fb_writeb(*src8++, dst8++);
-
-
dst = (u32 __iomem *) dst8;
-
}
-
-
*ppos += c;
-
buf += c;
-
cnt += c;
-
count -= c;
-
}
-
-
kfree(buffer);
-
-
return (cnt) ? cnt : err;
- }
当用户使用系统调用读写设备节点时,内核就会调用 fb_read() 和 fb_write() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_read()和fb_write()函数。
fb_ioctl()函数定义如下
点击(此处)折叠或打开
-
static int
-
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
-
unsigned long arg)
-
{
-
int fbidx = iminor(inode);
-
struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
-
struct fb_ops *fb = info->fbops; //得到设备的fb_ops操作集
-
struct fb_var_screeninfo var;
-
struct fb_fix_screeninfo fix;
-
struct fb_con2fbmap con2fb;
-
struct fb_cmap_user cmap;
-
struct fb_event event;
-
void __user *argp = (void __user *)arg;
-
int i;
-
-
if (!fb) //如果设备没有自定义fb_ops操作集,则返回出错
-
return -ENODEV;
-
switch (cmd) { //根据cmd命令,执行相应的操作
-
case FBIOGET_VSCREENINFO: //获取fb_var_screeninfo(可变参数),直接将info->var传输给用户层
-
return copy_to_user(argp, &info->var,
-
sizeof(var)) ? -EFAULT : 0;
-
case FBIOPUT_VSCREENINFO: //设置fb_var_screeninfo(可变参数)
-
if (copy_from_user(&var, argp, sizeof(var))) //通过argp,将用户层传递过来的参数保存在var变量中
-
return -EFAULT;
-
acquire_console_sem(); //lock
the console system for exclusive use
-
info->flags |= FBINFO_MISC_USEREVENT;
-
i = fb_set_var(info, &var); //调用fb_set_var,进行设定,FBINFO_MISC_USEREVENT用于指定需要设定参数
-
info->flags &= ~FBINFO_MISC_USEREVENT;
-
release_console_sem(); //unlock
the console system
-
if (i) return i; //若fb_set_var出错,则将错误码返回
-
if (copy_to_user(argp, &var, sizeof(var))) //若没出错,则通过argp地址将var参数返回
-
return -EFAULT;
-
return 0;
-
case FBIOGET_FSCREENINFO: //获取fb_fix_screeninfo(可变参数),直接将info->fix传输给用户层
-
return copy_to_user(argp, &info->fix,
-
sizeof(fix)) ? -EFAULT : 0;
-
case FBIOPUTCMAP: //设置用户提供的颜色表
-
if (copy_from_user(&cmap, argp, sizeof(cmap))) //通过argp,将用户设定的调色板参数保存在cmap中
-
return -EFAULT;
-
return (fb_set_user_cmap(&cmap, info)); //调用fb_set_user_cmap,设置调色表
-
case FBIOGETCMAP: //查询用户提供的颜色表
-
if (copy_from_user(&cmap, argp, sizeof(cmap)))
-
return -EFAULT;
-
return fb_cmap_to_user(&info->cmap, &cmap);
-
case FBIOPAN_DISPLAY:
-
if (copy_from_user(&var, argp, sizeof(var)))
-
return -EFAULT;
-
acquire_console_sem();
-
i = fb_pan_display(info, &var);
-
release_console_sem();
-
if (i)
-
return i;
-
if (copy_to_user(argp, &var, sizeof(var)))
-
return -EFAULT;
-
return 0;
-
case FBIO_CURSOR:
-
return -EINVAL;
-
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.info = info;
-
event.data = &con2fb;
-
fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
-
return copy_to_user(argp, &con2fb,
-
sizeof(con2fb)) ? -EFAULT : 0;
-
case FBIOPUT_CON2FBMAP:
-
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
-
return - EFAULT;
-
if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
-
return -EINVAL;
-
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
-
return -EINVAL;
-
#ifdef CONFIG_KMOD
-
if (!registered_fb[con2fb.framebuffer])
-
try_to_load(con2fb.framebuffer);
-
#endif /* CONFIG_KMOD */
-
if (!registered_fb[con2fb.framebuffer])
-
return -EINVAL;
-
event.info = info;
-
event.data = &con2fb;
-
return fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP,
-
&event);
-
case FBIOBLANK:
-
acquire_console_sem();
-
info->flags |= FBINFO_MISC_USEREVENT;
-
i = fb_blank(info, arg);
-
info->flags &= ~FBINFO_MISC_USEREVENT;
-
release_console_sem();
-
return i;
-
default:
-
if (fb->fb_ioctl == NULL)
-
return -EINVAL;
-
return fb->fb_ioctl(info, cmd, arg);
-
}
- }
其中,也会尝试去调用具体设备的fbops中的相关函数
大家可以参考下面的结构图,它说明了Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构体由fbmem.c中的fb_fops实例提供,并向下提供了相应的接口,让驱动开发者挂接具体的设备驱动。

来源:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=11366591&id=3864559