百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Linux显卡驱动,DRM显示框架简单介绍

sinye56 2024-11-14 17:42 24 浏览 0 评论

随着技术的发展,从古老的光栅命令行式的人机接口到现在的大型虚拟现实场景,计算机的显示技术始终在不停的更新换代。Linux是如何显示图形界面的呢?在这里简单的介绍一下。

一、DRM框架结构

我们看到的Linux系统界面多姿多彩,主要由两部分来实现。第一部分,窗口管理系统,比如X Windows,QWS等,负责把窗口部件渲染成一帧图形保存在显存里。第二部分,DRM(Direct Rendering Manager)框架,负责驱动显卡,把显存的内容以适当的格式传递给显示器加以显示。现在的显卡早已不仅包含了图形存储和传递的功能,还包含利用GPU渲染2D/3D图形的功能,因此,DRM框架还要负责把与GPU相关的功能通过某种方式暴露给用户空间,协助窗口系统产生美轮美奂的显示效果。 这里涉及到两个概念,窗口系统渲染和GPU渲染。

窗口系统渲染

即我们常说的GUI渲染。平时我们操作的按钮,输入框,列表框等由窗口管理系统渲染成一帧一帧的图形保存在内存中,然后通过内核把图片发送到显示器上,每一次窗口的改变都产生一帧图像,像电影一样,一帧一帧的刷新到显示器上,从而我们就看到了动态的内容。

GPU渲染

我们都知道,像大型3D游戏等应用需要大量的渲染工作,如果把这些工作放在CPU上执行将是繁重的任务,而且现在CPU是为了更好的流程控制和逻辑计算而设计的,在并行计算方便并不具有优势,所以,就有了GPU的出现。把渲染工作交给GPU,就相当于找到了一个得力的助手,而且GPU是专门为并行计算而设计的,大大减轻了CPU的负担。在实现上,相当于窗口管理系统只负责创建和管理窗口,并在窗口上挖一个矩形,但里面的内容却是交由GPU来渲染了。

二、DRM主要对象

DRM 是Linux 下的图形渲染框架,具体的说是显卡驱动框架。也就是说,一个显卡如果想在Linux中玩的溜溜溜,只有遵循DRM框架的逻辑才能把驱动程序和内核融合起来,上层的应用才可以充分的利用显卡特性,把显示功能发挥到极致。由于DRM的代码十分庞大,显卡的逻辑又特别复杂,所以很难一下子全部了解通透,只有通过对关键对象的了解,把简单的例子串起来,才能慢慢的揭开它的面纱。

DRM框架

DRM框架代码就是实现DRM具体功能的代码,实现了对显卡的文件抽象,创建显卡驱动文件(如:/dev/dri/card0)。用户程序通过open/close/ioctl 等标准接口,来驱动设备。在系统初始化的时候,内核按其他设备一样的方式加载驱动,发现设备,及其他初始化工作。 框架代码位于: drivers/gpu/drm GPU调度的代码放在 drivers/gpu/drm/scheduler TTM相关代码放在 drivers/gpu/drm/ttm 其他的子目录是各种厂家的驱动程序目录,如amd,vmwgfx等。

Framebuffer

帧缓冲区对象(struct drm_framebuffer)是帧内存对象的抽象,它提供了像素源给到CRTC。帧缓冲区依赖于底层内存管理器分配内存。应用程序通过调用ioctl,指定DRM_IOCTL_MODE_ADDFB参数显式地创建帧缓冲区,创建的对象以句柄的形式返回用户空间,随后可以给到后续CRTC,Plane及页面更新等函数。对于使用GEM缓冲区管理的驱动程序来说,这将是一个GEM句柄;使用TTM的是TTM句柄,例如vmwgfx驱动就直接向用户空间暴露TTM句柄。经过drm_framebuffer_init()函数初始化后,userspace可以使用和访问fb对象。通常通过mmap系统调用映射一段内存区,然后像读写内存一样方便的更新帧数据,之后再提交到CRTC上。帧缓冲区的生命周期由引用计数控制,驱动程序可以使用drm_framebuffer_get()获取引用,然后使用drm_framebuffer_put()释放引用。

Plane

一个平面对象(struct drm_plane)表示一个图像源,从drm_framebuffer对象获取输入数据,在输出过程中与CRTC的顶部混合或覆盖。平面本身不单指定该图像的裁剪和缩放,而且指定了它放置在CRTC中的位置,当然它还有其他属性,例如像素的位置和混合方式,旋转或Z位置等,所有属性都存储在drm_plane_state中。要创建平面,驱动程序分配初始化drm_plane对象实例,然后通过drm_universal_plane_init()注册它。光标和覆盖平面是Plane的一个实例,所有驱动程序都应该至少为每个CRTC提供一个主平面。

CRTC

一个CRTC(struct drm_crtc)表示一整个显示管线。它从drm_plane平面接收像素数据并将它们混合在一起。drm_display_mode也绑定到CRTC,指定显示时序等功能。在输出端,数据被送到一个或多个drm_encoder对象中,每个drm_encoder对象又连接到一个drm_connector对象上。 要创建CRTC,KMS驱动程序分配一个drm_crtc的一个实例,然后调用drm_crtc_init_with_planes()将其注册。CRTC仍然包含有旧模式集操作的入口点,例如drm_crtc_funcs.page_flip和drm_crtc_funcs.cursor_set2,以及drm_crtc_funcs.gamma_set等。 对于更新的atomic驱动,所有功能通过drm_property和drm_mode_config_funcs.atomic_check(),及drm_mode_config_funcs.atomic_check()进行控制。

Encoder

Encoder对象(struct drm_encoder)是CRTC和连接器(drm_connector)之间的连接元素。编码器从CRTC获取像素数据,并将其转换成适合任何连接的连接器的格式。编码器用drm_encoder_init初始化,并用drm_encoder_cleanup清理。

Connector

在DRM中,连接器(struct drm_connector)是显示接收器的抽象,包括als固定面板或任何其他可以以某种形式显示像素的东西。与表示硬件的所有其他KMS对象(如CRTC、编码器或平面抽象)不同,连接器可以在运行时热插拔,因此,使用drm_connector_get和drm_connector_put()控制引用计数。KMS驱动程序必须为每个接收器创建、初始化、注册并连接到结构drm_connector。通过drm_connector_init初始化,该调用带有指向drm_connector_funcs的指针和连接器类型,然后通过对drm_connector_register的调用公开给用户空间。连接器必须连接到编码器才能使用。

三、Drm简单例子

从例子开始学习是一个比较快捷的方法,先试一个最简单的modeset,显示一副纯色的图形。注意:例子不能在Linux桌面环境下执行,因为设备已经被窗口系统占用了,需要通过命令

[root@localhost ~]#init 3

把系统切换到多用户模式。 通过代码可以看到例子的主要步骤如下:

1. 打开设备文件

Drm框架在加载成功后会创建一个/dev/dri/card0设备文件,给用户程序一个统一的入口,操作显卡的各种特性,使用的时候通过文件系统调用打开即可。

 fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
 if (fd < 0) {
  ret = -errno;
  fprintf(stderr, "cannot open '%s': %m\n", node);
  return ret;
 }

2. 获取资源句柄

获取所有与显卡有关的资源,资源都是通过句柄来操作

/**
 1. Retrives all of the resources associated with a card.
 */
extern drmModeResPtr drmModeGetResources(int fd);

3. 获取连接对象

获取到资源对象drmModeRes之后,就可以通过它获取连接对象

/**
 2. Retrieve all information about the connector connectorId. This will do a
 3. forced probe on the connector to retrieve remote information such as EDIDs
 4. from the display device.
 */
extern drmModeConnectorPtr drmModeGetConnector(int fd,
                           uint32_t connectorId);

4. 创建FB

创建FrameBuffer,然后映射出一块内存,之后在内存中填入像素数据

static int modeset_create_fb(int fd, struct modeset_dev *dev)
{
 struct drm_mode_create_dumb creq;
 struct drm_mode_destroy_dumb dreq;
 struct drm_mode_map_dumb mreq;
 int ret;

 /* create dumb buffer */
 memset(&creq, 0, sizeof(creq));
 creq.width = dev->width;
 creq.height = dev->height;
 creq.bpp = 32;
 ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
 if (ret < 0) {
  fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
   errno);
  return -errno;
 }
 dev->stride = creq.pitch;
 dev->size = creq.size;
 dev->handle = creq.handle;

 /* create framebuffer object for the dumb-buffer */
 ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride,
      dev->handle, &dev->fb);
 if (ret) {
  fprintf(stderr, "cannot create framebuffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_destroy;
 }

 /* prepare buffer for memory mapping */
 memset(&mreq, 0, sizeof(mreq));
 mreq.handle = dev->handle;
 ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
 if (ret) {
  fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_fb;
 }

 /* perform actual memory mapping */
 dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
          fd, mreq.offset);
 if (dev->map == MAP_FAILED) {
  fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
   errno);
  ret = -errno;
  goto err_fb;
 }

 /* clear the framebuffer to 0 */
 memset(dev->map, 0, dev->size);

 return 0;

err_fb:
 drmModeRmFB(fd, dev->fb);
err_destroy:
 memset(&dreq, 0, sizeof(dreq));
 dreq.handle = dev->handle;
 drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
 return ret;
}

5. 设置Crtc模式

FB创建好了,并且清0了,也可以填充任何数据,然后设CRTC,FB的内容就会显示到屏幕上,drmModeSetCrtc的参数有fd,crtc句柄,FB句柄,x,y坐标等

/**
 * Set the mode on a crtc crtcId with the given mode modeId.
 */
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
                   uint32_t x, uint32_t y, uint32_t *connectors, int count,
           drmModeModeInfoPtr mode);

6. 清理工作

显示完成后,可以执行清理工作。一般的GUI会一直运行下去,不会自己执行清楚工作,但为了程序的完整性,还是要包含清理资源的代码。

static void modeset_cleanup(int fd)
{
 struct modeset_dev *iter;
 struct drm_mode_destroy_dumb dreq;

 while (modeset_list) {
  /* remove from global list */
  iter = modeset_list;
  modeset_list = iter->next;

  /* restore saved CRTC configuration */
  drmModeSetCrtc(fd,
          iter->saved_crtc->crtc_id,
          iter->saved_crtc->buffer_id,
          iter->saved_crtc->x,
          iter->saved_crtc->y,
          &iter->conn,
          1,
          &iter->saved_crtc->mode);
  drmModeFreeCrtc(iter->saved_crtc);

  /* unmap buffer */
  munmap(iter->map, iter->size);

  /* delete framebuffer */
  drmModeRmFB(fd, iter->fb);

  /* delete dumb buffer */
  memset(&dreq, 0, sizeof(dreq));
  dreq.handle = iter->handle;
  drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);

  /* free allocated memory */
  free(iter);
 }
}

例子的源码是参考网上的,地址: https://github.com/dvdhrm/docs/tree/master/drm-howto 里面有几个例子,都可以在虚拟机上运行通过

参考文章

DRM(Direct Rendering Manager)学习简介

https://blog.csdn.net/hexiaolong2009/article/details/83720940

Linux DRM(二)基本概念和特性

https://blog.csdn.net/dearsq/article/details/78394388

例子代码

https://github.com/dvdhrm/docs/tree/master/drm-howto

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: