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

相关推荐
无聊的烤苕皮30 分钟前
RHCE(RHCSA复习:npm、dnf、源码安装实验)
linux·npm·云计算·dnf·rhcsa
xxxx12344539 分钟前
Linux驱动开发-①pinctrl 和 gpio 子系统②并发和竞争③内核定时器
linux·驱动开发·单片机
stone082344 分钟前
ABAP语言的动态编程(4) - 综合案例:管理费用明细表
linux·运维·服务器
厂里英才1 小时前
docker无法正常拉取镜像问题的解决
linux·docker
mljy.1 小时前
Linux《进度条》
linux
顾林海1 小时前
解锁Android应用进程启动:从代码到原理深度剖析
android·linux·操作系统
沢田纲吉2 小时前
Linux:万字博客带你学会线程!
linux·后端·操作系统
努力犯错玩AI2 小时前
轻松部署Gemma3-27B,L20服务器+最新版vLLM高效推理
linux·后端·python
qwfys2002 小时前
How to install cangjie on Linux mint 22.1
linux·install·cangjie·mint
源远流长jerry2 小时前
Linux内核传输层UDP源码分析
linux·网络·网络协议·udp