Linux驱动编程 - Framebuffer子系统

目录

简介:

一、fbmem.c分析

[二、mxsfb.c 分析](#二、mxsfb.c 分析)

三、总结

[1、fbmem.c 文件:搭建了LCD驱动框架](#1、fbmem.c 文件:搭建了LCD驱动框架)

[2、mxsfb.c 文件:底层硬件层](#2、mxsfb.c 文件:底层硬件层)

[四、应用层如何使用 /dev/fb0](#四、应用层如何使用 /dev/fb0)


简介:


Framebuffer(帧缓冲)是Linux系统中为显示设备提供的一套应用程序接口,它将显存抽象为一种设备,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。

  • 核心层( fbmem.c**):** 主要实现字符设备的创建,为不同的显示设备提供文件通用处理接口;同时创建graphics设备类,占据主设备号29。内核自带,无需自己开发

  • 硬件设备层( mxsfb.c**):** 主要提供显示设备的时序、显存、像素格式等硬件信息,实现显示设备的私有文件接口,并创建显示设备文件/dev/fbx(x=0~n)暴露给用户空间。 硬件设备层的代码需要驱动开发人员根据具体的显示设备完成开发。

一、fbmem.c分析


FrameBuffer是抽象出来的通用的 LCD驱动框架程序,它依赖底层LCD驱动调用 register_framebuffer() 注册 "fb_info" 结构体,之后会生成 /dev/fb0 字符设备,/dev/fb0 的file_operations 操作集就定义在 drivers/video/fbdev/core/fbmem.c 文件中。fbmem.c 为所有支持FrameBuffer的设备驱动提供通用的接口,避免重复工作。

fbmem.c 处于Framebuffer设备驱动核心层,它为上层应用程序提供系统调用,也为下一层的特定硬件驱动提供接口。

fbmem.c核心代码如下:

cpp 复制代码
/* 路径:drivers/video/fbdev/core/fbmem.c */
/* procfs操作集 */
static const struct file_operations fb_proc_fops = {
	.owner		= THIS_MODULE,
	.open		= proc_fb_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release,
};

/* file_operations 操作集 */
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,
};

static int __init
fbmem_init(void)
{
    /* 创建 /proc/fb 文件,并设置文件操作集 */
    proc_create("fb", 0, NULL, &fb_proc_fops);
    /* 注册字符设备,fb_fops提供系统调用 write、read、ioctl 等 */
    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;
}

subsys_initcall(fbmem_init);

fbmem_init() 中做了3件事:

1)创建 /proc/fb 文件操作接口;

2)register_chrdev 注册字符设备驱动生成,提供 fb_fops 操作集;

3)创建类 "graphics"(没有创建设备)

此时并未生成 /dev 下的设备节点,因为没有创建设备,那么在哪里创建设备呢?继续往下看。

二、mxsfb.c 分析


下面分析 i.MX 6ULL 开发板的 LCD 驱动框架。

mxsfb.c 是NXP官方提供的 i.MX 6ULL 芯片针对一款 LCD 的驱动程序,它定义在 drivers/video/fbdev/mxsfb.c。当 of_match_table 与 imx6ull.dtsi 设备树文件中的compatible 属性值 "fsl,imx28-lcdif" 匹配时,则调用 mxsfb_probe 函数,该函数初始化并配置LCDIF控制器。

cpp 复制代码
lcdif: lcdif@021c8000 {
    compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
    reg = <0x021c8000 0x4000>;
    interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
            <&clks IMX6UL_CLK_LCDIF_APB>,
            <&clks IMX6UL_CLK_DUMMY>;
    clock-names = "pix", "axi", "disp_axi";
    status = "okay";
};

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat			/* 使用到的 IO */
		     &pinctrl_lcdif_ctrl>;
	display = <&display0>;
	status = "okay";

	display0: display {						/* LCD 属性信息 */
		bits-per-pixel = <24>;				/* 一个像素占用 24bit */
		bus-width = <24>;					/* 总线宽度 */

		display-timings {
			native-mode = <&timing0>;		/* 时序信息 */
			timing0: timing0 {
			clock-frequency = <51200000>;	/* LCD 像素时钟,单位 Hz */
			hactive = <1024>;				/* LCD X 轴像素个数 */
			vactive = <600>;				/* LCD Y 轴像素个数 */
			hfront-porch = <160>;			/* LCD hfp 参数 */
			hback-porch = <140>;			/* LCD hbp 参数 */
			hsync-len = <20>;				/* LCD hspw 参数 */
			vback-porch = <20>;				/* LCD vbp 参数 */
			vfront-porch = <12>;			/* LCD vfp 参数 */
			vsync-len = <3>;				/* LCD vspw 参数 */

			hsync-active = <0>;				/* hsync 数据线极性 */
			vsync-active = <0>;				/* vsync 数据线极性 */
			de-active = <1>;				/* de 数据线极性 */
			pixelclk-active = <0>;			/* clk 数据线先极性 */
			};
		};
	};
};

mxsfb.c 驱动代码分析:

cpp 复制代码
static const struct of_device_id mxsfb_dt_ids[] = {
	{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
	{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
	{ /* sentinel */ }
};

static struct platform_driver mxsfb_driver = {
	.probe = mxsfb_probe,
	.remove = mxsfb_remove,
	.shutdown = mxsfb_shutdown,
	.id_table = mxsfb_devtype,
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = mxsfb_dt_ids,
		   .pm = &mxsfb_pm_ops,
	},
};

module_platform_driver(mxsfb_driver);

mxsfb_probe() 函数主要功能:

1)framebuffer_alloc()分配一个fb_info

2)设置fb_info,如:fb_info->fbops = &mxsfb_ops; 设置操作集

3)硬件相关寄存器配置

4)register_framebuffer()注册fb_info

cpp 复制代码
static int mxsfb_probe(struct platform_device *pdev)
{
    const struct of_device_id *of_id =
            of_match_device(mxsfb_dt_ids, &pdev->dev);
    struct resource *res;
    struct mxsfb_info *host;
    struct fb_info *fb_info;
    struct pinctrl *pinctrl;
    int irq = platform_get_irq(pdev, 0);
    int gpio, ret;

    if (of_id)
        pdev->id_entry = of_id->data;

    gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
    if (gpio == -EPROBE_DEFER)
        return -EPROBE_DEFER;

    if (gpio_is_valid(gpio)) {
        ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
        if (ret) {
            dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
            return ret;
        }
    }

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "Cannot get memory IO resource\n");
        return -ENODEV;
    }

    host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
    if (!host) {
        dev_err(&pdev->dev, "Failed to allocate IO resource\n");
        return -ENOMEM;
    }

    fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
    if (!fb_info) {
        dev_err(&pdev->dev, "Failed to allocate fbdev\n");
        devm_kfree(&pdev->dev, host);
        return -ENOMEM;
    }
    host->fb_info = fb_info;
    fb_info->par = host;

    ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
              dev_name(&pdev->dev), host);
    if (ret) {
        dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
                irq, ret);
        ret = -ENODEV;
        goto fb_release;
    }

    host->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(host->base)) {
        dev_err(&pdev->dev, "ioremap failed\n");
        ret = PTR_ERR(host->base);
        goto fb_release;
    }

    host->pdev = pdev;
    platform_set_drvdata(pdev, host);

    host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];

    host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
    if (IS_ERR(host->clk_pix)) {
        host->clk_pix = NULL;
        ret = PTR_ERR(host->clk_pix);
        goto fb_release;
    }

    host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
    if (IS_ERR(host->clk_axi)) {
        host->clk_axi = NULL;
        ret = PTR_ERR(host->clk_axi);
        goto fb_release;
    }

    host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
    if (IS_ERR(host->clk_disp_axi)) {
        host->clk_disp_axi = NULL;
        ret = PTR_ERR(host->clk_disp_axi);
        goto fb_release;
    }

    host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
    if (IS_ERR(host->reg_lcd))
        host->reg_lcd = NULL;

    fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16,
                           GFP_KERNEL);
    if (!fb_info->pseudo_palette) {
        ret = -ENOMEM;
        goto fb_release;
    }

    INIT_LIST_HEAD(&fb_info->modelist);

    pm_runtime_enable(&host->pdev->dev);

    /* fb_info->fbops = &mxsfb_ops; 设置fb_ops操作集 */
    ret = mxsfb_init_fbinfo(host);
    if (ret != 0)
        goto fb_pm_runtime_disable;

    mxsfb_dispdrv_init(pdev, fb_info);

    if (!host->dispdrv) {
        pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
        if (IS_ERR(pinctrl)) {
            ret = PTR_ERR(pinctrl);
            goto fb_pm_runtime_disable;
        }
    }

    if (!host->enabled) {
        writel(0, host->base + LCDC_CTRL);
        mxsfb_set_par(fb_info);
        mxsfb_enable_controller(fb_info);
        pm_runtime_get_sync(&host->pdev->dev);
    }

    ret = register_framebuffer(fb_info);
    if (ret != 0) {
        dev_err(&pdev->dev, "Failed to register framebuffer\n");
        goto fb_destroy;
    }

    console_lock();
    ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
    console_unlock();
    if (ret < 0) {
        dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
        goto fb_unregister;
    }

    dev_info(&pdev->dev, "initialized\n");

    return 0;

fb_unregister:
    unregister_framebuffer(fb_info);
fb_destroy:
    if (host->enabled)
        clk_disable_unprepare(host->clk_pix);
    fb_destroy_modelist(&fb_info->modelist);
fb_pm_runtime_disable:
    pm_runtime_disable(&host->pdev->dev);
    devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:
    framebuffer_release(fb_info);
    devm_kfree(&pdev->dev, host);

    return ret;
}

先看下 struct fb_info,这个结构体非常重要

cpp 复制代码
struct fb_info {
	atomic_t count;         //打开计数器
	int node;               //文件node节点,存放有设备信息
	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
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	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
	union {
		char __iomem *screen_base;	/* Virtual address 显存的起始地址,虚拟地址*/
		char *screen_buffer;
	};
	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 */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

register_framebuffer()注册fb_info功能:

1)创建设备节点

2)registered_fb[i] 添加fb_info

register_framebuffer()函数,路径:drivers\video\fbdev\core\fbmem.c

cpp 复制代码
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;
}

do_register_framebuffer()函数,路径:drivers\video\fbdev\core\fbmem.c

cpp 复制代码
static int do_register_framebuffer(struct fb_info *fb_info)
{
    /* ... */
    for (i = 0 ; i < FB_MAX; i++)
        if (!registered_fb[i])
            break;
    fb_info->node = i;
    /* ... */
    /* 创建设备(在fbmem_init()中创建类)此时生成 /dev/fb0 节点 */
    fb_info->dev = device_create(fb_class, fb_info->device,
                     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
   /* ... */
    registered_fb[i] = fb_info;//registered_fb[i]添加fb_info
}

主要做了2件事:

1)用 fbmem_init()中创建的类创建设备,会生成 /dev/fb0 节点

2)fb_info 添加到 registered_fb[i] 中,数组索引为次设备号

**registered_fb[i] 又有什么用呢?**我们先来分析下应用程序 open("/dev/fb0", ...) 过程

open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0,最终调用fbmem.c 中的file_operations(fb_fops) 的.open(fb_open)。

cpp 复制代码
static struct fb_info *get_fb_info(unsigned int idx)
{
    ... ...
	fb_info = registered_fb[idx];    //registered_fb中通过index找到fb_info
    ... ...
    return fb_info;
}

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 = get_fb_info(fbidx);    // info = registered_fb[fbidx];
    ... ...
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);    // 调用 fb_info 的 fbops->fb_open
	}
}

fb_open 中以次设备号为索引找到 registered_fb[i] 的 fb_info(fb_info *info = registered_fb[次设备号])。最终调用 fb_info 的 fbops->fb_open,也就是 mxsfb_probe 中注册的 fb_info。write、read、ioctl 等系统调用过程都类似。

同理,应用程序调用 ioctl(g_tLcd.fd, FBIOGET_VSCREENINFO, &fb_var),会先以次设备号为索引找到 registered_fb[i] 的 fb_info,然后获取 fb_info 中的参数传给应用。

cpp 复制代码
static struct fb_info *file_fb_info(struct file *file)
{
	struct inode *inode = file_inode(file);
	int fbidx = iminor(inode);                    //获取次设备号
	struct fb_info *info = registered_fb[fbidx];  //获取 registered_fb[i] 中的 fb_info

	return info;
}

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct fb_info *info = file_fb_info(file);
    ... ...

	return do_fb_ioctl(info, cmd, arg);    // 处理数据
}

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	switch (cmd) {
	case FBIOGET_VSCREENINFO:
		if (!lock_fb_info(info))
			return -ENODEV;
		var = info->var;            //获取 fb_info 中的参数
		unlock_fb_info(info);

		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;//将 fb_info 中的参数传给应用
		break;
    }
}

三、总结


1、fbmem.c 文件:搭建了LCD驱动框架


1)创建 /proc/fb 文件操作接口;

2)构造 fb_fops(file_operation),register_chrdev 注册字符设备驱动;

3)class_create创建类 "graphics"(由设备相关驱动创建设备节点)

2、mxsfb.c 文件:底层硬件层


匹配过程就是platform设备驱动那套流程。设备树(platform_device)与 mxsfb.c(platform_driver) 匹配后,调用 .probe。这里 .probe就是 mxsfb_probe() 函数。

mxsfb_probe() 函数主要功能

1)framebuffer_alloc()分配一个fb_info

2)设置fb_info,如:fb_info->fbops = &mxsfb_ops; 设置操作集

3)硬件相关寄存器配置

4)register_framebuffer()注册fb_info

register_framebuffer()功能:

1)device_create() 创建设备,生成 /dev/fb0

2)registered_fb[i] = fb_info; //将fb_info放入空的registered_fb[i]位置,i设为次设备号

应用程序open,会获取 registered_fb[i] 中对应次设备号的 fb_info,并调用它的open函数。

由此可见,fb_info结构非常重要

四、应用层如何使用 /dev/fb0


  • open函数:用于打开Framebuffer设备文件(如/dev/fb0)。
  • ioctl函数:用于获取和设置Framebuffer的参数,如分辨率、颜色深度等。
  • mmap函数:用于将Framebuffer映射到进程的地址空间,以便进行读写操作。
cpp 复制代码
#include "framebuffer.h"
#include <linux/fb.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <math.h>
void *pmem;
struct fb_var_screeninfo vinf;
 
int init_fb(char *devname)
{
	//1. 打开显示设备
	int fd = open(devname, O_RDWR);	
	if (-1 == fd)
	{
		perror("fail open fb");
		return -1;
	}
	
	//2、获取显示设备相关参数 分辨率 位深度
	int ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinf);
	if (-1 ==ret)
	{
		perror("fail ioctl");
		return -1;
	}
	
	printf("xres = %d, yres = %d\n", vinf.xres, vinf.yres);
	printf("xres_virtual = %d, yres_virtual = %d\n", vinf.xres_virtual, vinf.yres_virtual);
	printf("bits_per_pixel : %d\n", vinf.bits_per_pixel);
 
	size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel/8;
	//3, 建立显存和用户空间的映射关系
	pmem = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if ((void *)-1 == pmem)
	{
		perror("fail mmap");
		return -1;
	}
	
 
	return fd;
}
 
void draw_point(int x, int y, unsigned int col)
{
	if (x >= vinf.xres || y >= vinf.yres)
	{
		return ;
	}
	if (vinf.bits_per_pixel == RGB888_FMT)
	{
		unsigned int *p = (unsigned int *)pmem;
		*(p + y * vinf.xres_virtual + x) = col;
	}
	else if (vinf.bits_per_pixel == RGB565_FMT)
	{
		unsigned short *p  = (unsigned short *)pmem;	
		*(p + y * vinf.xres_virtual + x) = col;
	}
	return ;
}
void draw_x_line(int x, int y, int len, unsigned int col)
{
	if (x >= vinf.xres || y >= vinf.yres)
	{
		return ;
	}
	for (int i = x; i < x+len; i++)
	{
		draw_point(i, y, col);
	}
    return;
}
void draw_y_line(int x, int y, int len, unsigned int col)
{
	if (x >= vinf.xres || y >= vinf.yres)
	{
		return ;
	}
	for (int i = y; i < y+len; i++)
	{
		draw_point(x, i, col);
	}
    return;
}
 
void draw_bias(int x, int y, int len, unsigned int col)
{
	if (x >= vinf.xres || y >= vinf.yres)
	{
		return ;
	}
    int i=0;
    while(i<len)
    {   
		draw_point(x, y, col);
        ++x;
        ++y;
        ++i;
    }
}
void draw_circle(int x, int y, int r, unsigned int col)
{
	int x0, y0;
	for (int i = 0; i <= 360; ++i)
	{
		x0 = r * cos(2 * 3.1415/360 * i) + x;
		y0 = r * sin(2 * 3.1415/360 * i) + y;
		draw_point(x0, y0, col);
		draw_point(x0+1, y0, col);
		draw_point(x0, y0+1, col);
		draw_point(x0-1, y0, col);
		draw_point(x0, y0-1, col);
	}
}
 
void draw_rectangle(int x,int y,int len,int wide,unsigned int col)
{
	if (x >= vinf.xres || y >= vinf.yres)
	{
		return ;
	}
    draw_x_line(x,y,len,col);
    draw_y_line(x,y,wide,col);
    draw_x_line(x,y+wide,len,col);
    draw_y_line(x+len,y,wide,col);
}
 
void draw_clear(unsigned int col)
{
    for(int i =0;i<vinf.xres_virtual;++i)
    {
        for(int j=0;j<vinf.yres_virtual;++j)
        {
            draw_point(i,j,col);
        }
    }
}
void uninit_fb(int fd)
{
 
	size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel/8;
	munmap(pmem, len);
	close(fd);
}
 
 

相关推荐
suijishengchengde10 分钟前
****LINUX时间同步配置*****
linux·运维
qiuqyue34 分钟前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
linux·运维·网络
切糕师学AI1 小时前
Linux 操作系统简介
linux
南烟斋..1 小时前
GDB调试核心指南
linux·服务器
爱跑马的程序员2 小时前
Linux 如何查看文件夹的大小(du、df、ls、find)
linux·运维·ubuntu
oMcLin4 小时前
如何在 Ubuntu 22.04 LTS 上部署并优化 Magento 电商平台,提升高并发请求的响应速度与稳定性?
linux·运维·ubuntu
Qinti_mm4 小时前
Linux io_uring:高性能异步I/O革命
linux·i/o·io_uring
优雅的38度4 小时前
linux环境下,使用docker安装apache kafka (docker-compose)
linux·架构
想唱rap5 小时前
表的约束条件
linux·数据库·mysql·ubuntu·bash
山上三树5 小时前
对比用户态线程与内核态轻量级进程
linux